diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts index 5d975f7d6d1..f60905a9879 100644 --- a/packages/typescript-estree/src/create-program/createWatchProgram.ts +++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts @@ -395,11 +395,12 @@ function maybeInvalidateProgram( const folderWatchCallbacks = folderWatchCallbackTrackingMap.get(current); if (folderWatchCallbacks) { folderWatchCallbacks.forEach(cb => { - cb(currentDir, ts.FileWatcherEventKind.Changed); + if (currentDir !== current) { + cb(currentDir, ts.FileWatcherEventKind.Changed); + } cb(current!, ts.FileWatcherEventKind.Changed); }); hasCallback = true; - break; } next = canonicalDirname(current); diff --git a/packages/typescript-estree/src/create-program/shared.ts b/packages/typescript-estree/src/create-program/shared.ts index 8aceb204c47..be60f245374 100644 --- a/packages/typescript-estree/src/create-program/shared.ts +++ b/packages/typescript-estree/src/create-program/shared.ts @@ -38,12 +38,14 @@ function getCanonicalFileName(filePath: string): CanonicalPath { return correctPathCasing(normalized) as CanonicalPath; } +function ensureAbsolutePath(p: string, extra: Extra): string { + return path.isAbsolute(p) + ? p + : path.join(extra.tsconfigRootDir || process.cwd(), p); +} + function getTsconfigPath(tsconfigPath: string, extra: Extra): CanonicalPath { - return getCanonicalFileName( - path.isAbsolute(tsconfigPath) - ? tsconfigPath - : path.join(extra.tsconfigRootDir || process.cwd(), tsconfigPath), - ); + return getCanonicalFileName(ensureAbsolutePath(tsconfigPath, extra)); } function canonicalDirname(p: CanonicalPath): CanonicalPath { @@ -84,6 +86,7 @@ export { canonicalDirname, CanonicalPath, DEFAULT_COMPILER_OPTIONS, + ensureAbsolutePath, getCanonicalFileName, getScriptKind, getTsconfigPath, diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index fb62c090bb7..6a9e0a1c6b5 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -11,6 +11,7 @@ import { createSourceFile } from './create-program/createSourceFile'; import { Extra, TSESTreeOptions, ParserServices } from './parser-options'; import { getFirstSemanticOrSyntacticError } from './semantic-or-syntactic-errors'; import { TSESTree } from './ts-estree'; +import { ensureAbsolutePath } from './create-program/shared'; /** * This needs to be kept in sync with the top-level README.md in the @@ -145,6 +146,7 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void { } else { extra.filePath = getFileName(extra); } + extra.filePath = ensureAbsolutePath(extra.filePath, extra); /** * The JSX AST changed the node type for string literals diff --git a/packages/typescript-estree/tests/lib/persistentParse.ts b/packages/typescript-estree/tests/lib/persistentParse.ts index bdeed2f5538..fd9cbdc93bb 100644 --- a/packages/typescript-estree/tests/lib/persistentParse.ts +++ b/packages/typescript-estree/tests/lib/persistentParse.ts @@ -10,6 +10,7 @@ const CONTENTS = { 'bat/baz/bar': 'console.log("bat/baz/bar")', }; +const cwdCopy = process.cwd(); const tmpDirs = new Set(); afterEach(() => { // stop watching the files and folders @@ -18,6 +19,9 @@ afterEach(() => { // clean up the temporary files and folders tmpDirs.forEach(t => t.removeCallback()); tmpDirs.clear(); + + // restore original cwd + process.chdir(cwdCopy); }); function writeTSConfig(dirName: string, config: Record): void { @@ -54,14 +58,24 @@ function setup(tsconfig: Record, writeBar = true): string { return tmpDir.name; } -function parseFile(filename: keyof typeof CONTENTS, tmpDir: string): void { - parseAndGenerateServices(CONTENTS.foo, { +function parseFile( + filename: keyof typeof CONTENTS, + tmpDir: string, + relative?: boolean, +): void { + parseAndGenerateServices(CONTENTS[filename], { project: './tsconfig.json', tsconfigRootDir: tmpDir, - filePath: path.join(tmpDir, 'src', `${filename}.ts`), + filePath: relative + ? path.join('src', `${filename}.ts`) + : path.join(tmpDir, 'src', `${filename}.ts`), }); } +function existsSync(filename: keyof typeof CONTENTS, tmpDir = ''): boolean { + return fs.existsSync(path.join(tmpDir, 'src', `${filename}.ts`)); +} + function baseTests( tsConfigExcludeBar: Record, tsConfigIncludeAll: Record, @@ -161,6 +175,27 @@ function baseTests( expect(() => parseFile('foo', PROJECT_DIR)).not.toThrow(); expect(() => parseFile('bar', PROJECT_DIR)).not.toThrow(); }); + + it('should work with relative paths', () => { + const PROJECT_DIR = setup(tsConfigIncludeAll, false); + process.chdir(PROJECT_DIR); + + // parse once to: assert the config as correct, and to make sure the program is setup + expect(() => parseFile('foo', PROJECT_DIR, true)).not.toThrow(); + // bar should throw because it doesn't exist yet + expect(() => parseFile('bar', PROJECT_DIR, true)).toThrow(); + + // write a new file and attempt to parse it + writeFile(PROJECT_DIR, 'bar'); + + // make sure that file is correctly created + expect(existsSync('bar')).toEqual(true); + expect(existsSync('bar', PROJECT_DIR)).toEqual(true); + + // both files should parse fine now + expect(() => parseFile('foo', PROJECT_DIR, true)).not.toThrow(); + expect(() => parseFile('bar', PROJECT_DIR, true)).not.toThrow(); + }); } describe('persistent parse', () => {