From 74330d58ac1afae5ad952f341582d97713ea70e9 Mon Sep 17 00:00:00 2001 From: Ben Lichtman Date: Mon, 22 Jul 2019 16:50:12 -0700 Subject: [PATCH 1/9] feat(typescript-estree): throw error on external file --- packages/typescript-estree/src/parser.ts | 29 ++++++------- .../typescript-estree/src/tsconfig-parser.ts | 10 +++++ .../tests/fixtures/simpleProject/file.ts | 0 .../fixtures/simpleProject/tsconfig.json | 1 + packages/typescript-estree/tests/lib/parse.ts | 25 +++++++---- .../tests/lib/semanticInfo.ts | 41 +++++++++++-------- 6 files changed, 65 insertions(+), 41 deletions(-) create mode 100644 packages/typescript-estree/tests/fixtures/simpleProject/file.ts create mode 100644 packages/typescript-estree/tests/fixtures/simpleProject/tsconfig.json diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 81825267fcb..1abb8d91d6d 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -6,10 +6,7 @@ import { firstDefined } from './node-utils'; import { Extra, TSESTreeOptions, ParserServices } from './parser-options'; import { getFirstSemanticOrSyntacticError } from './semantic-errors'; import { TSESTree } from './ts-estree'; -import { - calculateProjectParserOptions, - createProgram, -} from './tsconfig-parser'; +import { calculateProjectParserOptions } from './tsconfig-parser'; /** * This needs to be kept in sync with the top-level README.md in the @@ -67,7 +64,7 @@ function resetExtra(): void { * @returns If found, returns the source file corresponding to the code and the containing program */ function getASTFromProject(code: string, options: TSESTreeOptions) { - return firstDefined( + const astAndProgram = firstDefined( calculateProjectParserOptions( code, options.filePath || getFileName(options), @@ -80,18 +77,17 @@ function getASTFromProject(code: string, options: TSESTreeOptions) { return ast && { ast, program: currentProgram }; }, ); -} -/** - * @param code The code of the file being linted - * @param options The config object - * @returns If found, returns the source file corresponding to the code and the containing program - */ -function getASTAndDefaultProject(code: string, options: TSESTreeOptions) { - const fileName = options.filePath || getFileName(options); - const program = createProgram(code, fileName, extra); - const ast = program && program.getSourceFile(fileName); - return ast && { ast, program }; + if (!astAndProgram) { + throw new Error( + `If "parserOptions.project" has been set for @typescript-eslint/parser, ${options.filePath || + getFileName( + options, + )} must be included in at least one of the projects provided.`, + ); + } + + return astAndProgram; } /** @@ -164,7 +160,6 @@ function getProgramAndAST( ) { return ( (shouldProvideParserServices && getASTFromProject(code, options)) || - (shouldProvideParserServices && getASTAndDefaultProject(code, options)) || createNewProgram(code) ); } diff --git a/packages/typescript-estree/src/tsconfig-parser.ts b/packages/typescript-estree/src/tsconfig-parser.ts index 641af07a77a..c329f7ce4bb 100644 --- a/packages/typescript-estree/src/tsconfig-parser.ts +++ b/packages/typescript-estree/src/tsconfig-parser.ts @@ -30,6 +30,16 @@ const watchCallbackTrackingMap = new Map(); const parsedFilesSeen = new Set(); +/** + * Clear tsconfig caches. + * Primarily used for testing. + */ +export function clearCaches() { + knownWatchProgramMap.clear(); + watchCallbackTrackingMap.clear(); + parsedFilesSeen.clear(); +} + /** * Holds information about the file currently being linted */ diff --git a/packages/typescript-estree/tests/fixtures/simpleProject/file.ts b/packages/typescript-estree/tests/fixtures/simpleProject/file.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/typescript-estree/tests/fixtures/simpleProject/tsconfig.json b/packages/typescript-estree/tests/fixtures/simpleProject/tsconfig.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/simpleProject/tsconfig.json @@ -0,0 +1 @@ +{} diff --git a/packages/typescript-estree/tests/lib/parse.ts b/packages/typescript-estree/tests/lib/parse.ts index 3c34f705556..8fbac2e33a9 100644 --- a/packages/typescript-estree/tests/lib/parse.ts +++ b/packages/typescript-estree/tests/lib/parse.ts @@ -2,6 +2,9 @@ import * as parser from '../../src/parser'; import * as astConverter from '../../src/ast-converter'; import { TSESTreeOptions } from '../../src/parser-options'; import { createSnapshotTestBlock } from '../../tools/test-utils'; +import { join } from 'path'; + +const FIXTURES_DIR = './tests/fixtures/simpleProject'; describe('parse()', () => { describe('basic functionality', () => { @@ -135,6 +138,12 @@ describe('parse()', () => { tokens: true, range: true, loc: true, + filePath: 'tests/fixtures/simpleProject/file.ts', + }; + const projectConfig: TSESTreeOptions = { + ...baseConfig, + tsconfigRootDir: join(process.cwd(), FIXTURES_DIR), + project: './tsconfig.json', }; it('should not impact the use of parse()', () => { @@ -165,10 +174,10 @@ describe('parse()', () => { expect(noOptionSet.services.esTreeNodeToTSNodeMap).toBeUndefined(); expect(noOptionSet.services.tsNodeToESTreeNodeMap).toBeUndefined(); - const withProjectNoOptionSet = parser.parseAndGenerateServices(code, { - ...baseConfig, - project: './tsconfig.json', - }); + const withProjectNoOptionSet = parser.parseAndGenerateServices( + code, + projectConfig, + ); expect(withProjectNoOptionSet.services.esTreeNodeToTSNodeMap).toEqual( expect.any(WeakMap), @@ -192,9 +201,8 @@ describe('parse()', () => { ); const withProjectOptionSetToTrue = parser.parseAndGenerateServices(code, { - ...baseConfig, + ...projectConfig, preserveNodeMaps: true, - project: './tsconfig.json', }); expect(withProjectOptionSetToTrue.services.esTreeNodeToTSNodeMap).toEqual( @@ -216,7 +224,10 @@ describe('parse()', () => { const withProjectOptionSetToFalse = parser.parseAndGenerateServices( code, - { ...baseConfig, preserveNodeMaps: false, project: './tsconfig.json' }, + { + ...projectConfig, + preserveNodeMaps: false, + }, ); expect( diff --git a/packages/typescript-estree/tests/lib/semanticInfo.ts b/packages/typescript-estree/tests/lib/semanticInfo.ts index 189816f08c6..906ad5456cf 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.ts @@ -10,6 +10,7 @@ import { } from '../../tools/test-utils'; import { parseAndGenerateServices } from '../../src/parser'; import { TSESTree } from '../../src/ts-estree'; +import { clearCaches } from '../../src/tsconfig-parser'; const FIXTURES_DIR = './tests/fixtures/semanticInfo'; const testFiles = glob.sync(`${FIXTURES_DIR}/**/*.src.ts`); @@ -25,11 +26,14 @@ function createOptions(fileName: string): TSESTreeOptions & { cwd?: string } { errorOnUnknownASTType: true, filePath: fileName, tsconfigRootDir: join(process.cwd(), FIXTURES_DIR), - project: './tsconfig.json', + project: `./tsconfig.json`, loggerFn: false, }; } +// ensure tsconfig-parser caches are clean for each test +beforeEach(() => clearCaches()); + describe('semanticInfo', () => { // test all AST snapshots testFiles.forEach(filename => { @@ -188,12 +192,14 @@ describe('semanticInfo', () => { it('non-existent file tests', () => { const parseResult = parseCodeAndGenerateServices( `const x = [parseInt("5")];`, - createOptions(''), + { + ...createOptions(''), + project: undefined, + preserveNodeMaps: true, + }, ); - // get type checker - expect(parseResult).toHaveProperty('services.program.getTypeChecker'); - const checker = parseResult.services.program!.getTypeChecker(); + expect(parseResult.services.program).toBeUndefined(); // get bound name const boundName = (parseResult.ast as any).body[0].declarations[0].id; @@ -204,8 +210,6 @@ describe('semanticInfo', () => { ); expect(tsBoundName).toBeDefined(); - checkNumberArrayType(checker, tsBoundName!); - expect(parseResult.services.tsNodeToESTreeNodeMap!.get(tsBoundName!)).toBe( boundName, ); @@ -214,18 +218,21 @@ describe('semanticInfo', () => { it('non-existent file should provide parents nodes', () => { const parseResult = parseCodeAndGenerateServices( `function M() { return Base }`, - createOptions(''), + { ...createOptions(''), project: undefined }, ); - // https://github.com/JamesHenry/typescript-estree/issues/77 - expect(parseResult.services.program).toBeDefined(); - expect( - parseResult.services.program!.getSourceFile(''), - ).toBeDefined(); - expect( - parseResult.services.program!.getSourceFile('')!.statements[0] - .parent, - ).toBeDefined(); + expect(parseResult.services.program).toBeUndefined(); + }); + + it(`non-existent file should throw error when project provided`, () => { + expect(() => + parseCodeAndGenerateServices( + `function M() { return Base }`, + createOptions(''), + ), + ).toThrow( + `If "parserOptions.project" has been set for @typescript-eslint/parser, must be included in at least one of the projects provided.`, + ); }); it('non-existent project file', () => { From 5a361abe1d8b95be311ddac696f150b77f674d63 Mon Sep 17 00:00:00 2001 From: Ben Lichtman Date: Mon, 22 Jul 2019 17:45:13 -0700 Subject: [PATCH 2/9] test(eslint-plugin): update tests to handle new constraint --- packages/eslint-plugin/tests/RuleTester.ts | 25 +++++++++++++++++++ packages/eslint-plugin/tests/fixtures/file.ts | 0 .../rules/no-unnecessary-qualifier.test.ts | 1 - 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 packages/eslint-plugin/tests/fixtures/file.ts diff --git a/packages/eslint-plugin/tests/RuleTester.ts b/packages/eslint-plugin/tests/RuleTester.ts index 769d7bc1aae..226a33ea1ed 100644 --- a/packages/eslint-plugin/tests/RuleTester.ts +++ b/packages/eslint-plugin/tests/RuleTester.ts @@ -7,6 +7,8 @@ type RuleTesterConfig = Omit & { parser: typeof parser; }; class RuleTester extends TSESLint.RuleTester { + private filename: string | undefined = undefined; + // as of eslint 6 you have to provide an absolute path to the parser // but that's not as clean to type, this saves us trying to manually enforce // that contributors require.resolve everything @@ -15,6 +17,10 @@ class RuleTester extends TSESLint.RuleTester { ...options, parser: require.resolve(options.parser), }); + + if (options.parserOptions && options.parserOptions.project) { + this.filename = path.join(getFixturesRootDir(), 'file.ts'); + } } // as of eslint 6 you have to provide an absolute path to the parser @@ -26,17 +32,36 @@ class RuleTester extends TSESLint.RuleTester { tests: TSESLint.RunTests, ): void { const errorMessage = `Do not set the parser at the test level unless you want to use a parser other than ${parser}`; + + if (this.filename) { + tests.valid = tests.valid.map(test => { + if (typeof test === 'string') { + return { + code: test, + filename: this.filename, + }; + } + return test; + }); + } + tests.valid.forEach(test => { if (typeof test !== 'string') { if (test.parser === parser) { throw new Error(errorMessage); } + if (!test.filename) { + test.filename = this.filename; + } } }); tests.invalid.forEach(test => { if (test.parser === parser) { throw new Error(errorMessage); } + if (!test.filename) { + test.filename = this.filename; + } }); super.run(name, rule, tests); diff --git a/packages/eslint-plugin/tests/fixtures/file.ts b/packages/eslint-plugin/tests/fixtures/file.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts index 53a349e552e..75236704c85 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts @@ -199,7 +199,6 @@ import * as Foo from './foo'; declare module './foo' { const x: Foo.T = 3; }`, - filename: path.join(rootPath, 'bar.ts'), errors: [ { messageId, From 22a5b8a56cc8236651c3d11e98d24332e49477ce Mon Sep 17 00:00:00 2001 From: Ben Lichtman Date: Tue, 23 Jul 2019 16:25:32 -0700 Subject: [PATCH 3/9] test(parser, eslint-plugin-tslint): fix more tests --- packages/eslint-plugin-tslint/tests/fixture-project/1.ts | 1 + packages/eslint-plugin-tslint/tests/fixture-project/2.ts | 1 + packages/eslint-plugin-tslint/tests/fixture-project/3.ts | 1 + packages/eslint-plugin-tslint/tests/fixture-project/4.ts | 1 + packages/eslint-plugin-tslint/tests/fixture-project/5.ts | 1 + packages/eslint-plugin-tslint/tests/fixture-project/6.ts | 1 + .../tests/fixture-project/tsconfig.json | 1 + packages/eslint-plugin-tslint/tests/index.spec.ts | 9 +++++++-- packages/parser/tests/lib/parser.ts | 4 ++-- 9 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 packages/eslint-plugin-tslint/tests/fixture-project/1.ts create mode 100644 packages/eslint-plugin-tslint/tests/fixture-project/2.ts create mode 100644 packages/eslint-plugin-tslint/tests/fixture-project/3.ts create mode 100644 packages/eslint-plugin-tslint/tests/fixture-project/4.ts create mode 100644 packages/eslint-plugin-tslint/tests/fixture-project/5.ts create mode 100644 packages/eslint-plugin-tslint/tests/fixture-project/6.ts create mode 100644 packages/eslint-plugin-tslint/tests/fixture-project/tsconfig.json diff --git a/packages/eslint-plugin-tslint/tests/fixture-project/1.ts b/packages/eslint-plugin-tslint/tests/fixture-project/1.ts new file mode 100644 index 00000000000..0870b5cd282 --- /dev/null +++ b/packages/eslint-plugin-tslint/tests/fixture-project/1.ts @@ -0,0 +1 @@ +var foo = true; diff --git a/packages/eslint-plugin-tslint/tests/fixture-project/2.ts b/packages/eslint-plugin-tslint/tests/fixture-project/2.ts new file mode 100644 index 00000000000..3da535b2cde --- /dev/null +++ b/packages/eslint-plugin-tslint/tests/fixture-project/2.ts @@ -0,0 +1 @@ +throw 'should be ok because rule is not loaded'; diff --git a/packages/eslint-plugin-tslint/tests/fixture-project/3.ts b/packages/eslint-plugin-tslint/tests/fixture-project/3.ts new file mode 100644 index 00000000000..e71f830c391 --- /dev/null +++ b/packages/eslint-plugin-tslint/tests/fixture-project/3.ts @@ -0,0 +1 @@ +throw 'err'; // no-string-throw diff --git a/packages/eslint-plugin-tslint/tests/fixture-project/4.ts b/packages/eslint-plugin-tslint/tests/fixture-project/4.ts new file mode 100644 index 00000000000..e1173e87a22 --- /dev/null +++ b/packages/eslint-plugin-tslint/tests/fixture-project/4.ts @@ -0,0 +1 @@ +var foo = true; // semicolon diff --git a/packages/eslint-plugin-tslint/tests/fixture-project/5.ts b/packages/eslint-plugin-tslint/tests/fixture-project/5.ts new file mode 100644 index 00000000000..2fc07810720 --- /dev/null +++ b/packages/eslint-plugin-tslint/tests/fixture-project/5.ts @@ -0,0 +1 @@ +var foo = true; // fail diff --git a/packages/eslint-plugin-tslint/tests/fixture-project/6.ts b/packages/eslint-plugin-tslint/tests/fixture-project/6.ts new file mode 100644 index 00000000000..e901f01b487 --- /dev/null +++ b/packages/eslint-plugin-tslint/tests/fixture-project/6.ts @@ -0,0 +1 @@ +foo; diff --git a/packages/eslint-plugin-tslint/tests/fixture-project/tsconfig.json b/packages/eslint-plugin-tslint/tests/fixture-project/tsconfig.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/packages/eslint-plugin-tslint/tests/fixture-project/tsconfig.json @@ -0,0 +1 @@ +{} diff --git a/packages/eslint-plugin-tslint/tests/index.spec.ts b/packages/eslint-plugin-tslint/tests/index.spec.ts index 88a3a648b07..45812089102 100644 --- a/packages/eslint-plugin-tslint/tests/index.spec.ts +++ b/packages/eslint-plugin-tslint/tests/index.spec.ts @@ -12,7 +12,7 @@ const ruleTester = new TSESLint.RuleTester({ * Project is needed to generate the parserServices * within @typescript-eslint/parser */ - project: './tests/tsconfig.json', + project: './tests/fixture-project/tsconfig.json', }, parser: require.resolve('@typescript-eslint/parser'), }); @@ -47,6 +47,7 @@ ruleTester.run('tslint/config', rule, { { code: 'var foo = true;', options: tslintRulesConfig, + filename: './tests/fixture-project/1.ts', }, { filename: './tests/test-project/file-spec.ts', @@ -62,6 +63,7 @@ ruleTester.run('tslint/config', rule, { { code: 'throw "should be ok because rule is not loaded";', options: tslintRulesConfig, + filename: './tests/fixture-project/2.ts', }, ], @@ -69,6 +71,7 @@ ruleTester.run('tslint/config', rule, { { options: [{ lintFile: './tests/test-project/tslint.json' }], code: 'throw "err" // no-string-throw', + filename: './tests/fixture-project/3.ts', errors: [ { messageId: 'failure', @@ -84,6 +87,7 @@ ruleTester.run('tslint/config', rule, { code: 'var foo = true // semicolon', options: tslintRulesConfig, output: 'var foo = true // semicolon', + filename: './tests/fixture-project/4.ts', errors: [ { messageId: 'failure', @@ -100,6 +104,7 @@ ruleTester.run('tslint/config', rule, { code: 'var foo = true // fail', options: tslintRulesDirectoryConfig, output: 'var foo = true // fail', + filename: './tests/fixture-project/5.ts', errors: [ { messageId: 'failure', @@ -174,7 +179,7 @@ describe('tslint/error', () => { }); }); - it('should not crash if there is no tslint rules specified', () => { + it('barf', () => { const linter = new TSESLint.Linter(); jest.spyOn(console, 'warn').mockImplementation(); linter.defineRule('tslint/config', rule); diff --git a/packages/parser/tests/lib/parser.ts b/packages/parser/tests/lib/parser.ts index 9545633cd6e..350d69946be 100644 --- a/packages/parser/tests/lib/parser.ts +++ b/packages/parser/tests/lib/parser.ts @@ -48,12 +48,12 @@ describe('parser', () => { jsx: false, }, // ts-estree specific - filePath: 'test/foo', + filePath: 'tests/fixtures/services/isolated-file.src.ts', project: 'tsconfig.json', useJSXTextNode: false, errorOnUnknownASTType: false, errorOnTypeScriptSyntacticAndSemanticIssues: false, - tsconfigRootDir: './', + tsconfigRootDir: 'tests/fixtures/services', extraFileExtensions: ['foo'], }; parseForESLint(code, config); From dd811ab63b8b5875e4c013f8d74985ae51d7ff52 Mon Sep 17 00:00:00 2001 From: Ben Lichtman Date: Wed, 24 Jul 2019 21:29:00 -0700 Subject: [PATCH 4/9] test(eslint-plugin-tslint): fix remaining tests --- .../eslint-plugin-tslint/tests/index.spec.ts | 24 +++++++++++-------- .../tests/test-project/extra.ts | 1 + 2 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 packages/eslint-plugin-tslint/tests/test-project/extra.ts diff --git a/packages/eslint-plugin-tslint/tests/index.spec.ts b/packages/eslint-plugin-tslint/tests/index.spec.ts index 45812089102..0de1046ecf3 100644 --- a/packages/eslint-plugin-tslint/tests/index.spec.ts +++ b/packages/eslint-plugin-tslint/tests/index.spec.ts @@ -179,26 +179,30 @@ describe('tslint/error', () => { }); }); - it('barf', () => { + it('should not crash if there are no tslint rules specified', () => { const linter = new TSESLint.Linter(); jest.spyOn(console, 'warn').mockImplementation(); linter.defineRule('tslint/config', rule); linter.defineParser('@typescript-eslint/parser', parser); expect(() => - linter.verify('foo;', { - parserOptions: { - project: `${__dirname}/test-project/tsconfig.json`, - }, - rules: { - 'tslint/config': [2, {}], + linter.verify( + 'foo;', + { + parserOptions: { + project: `${__dirname}/test-project/tsconfig.json`, + }, + rules: { + 'tslint/config': [2, {}], + }, + parser: '@typescript-eslint/parser', }, - parser: '@typescript-eslint/parser', - }), + `${__dirname}/test-project/extra.ts`, + ), ).not.toThrow(); expect(console.warn).toHaveBeenCalledWith( expect.stringContaining( - 'Tried to lint but found no valid, enabled rules for this file type and file path in the resolved configuration.', + `Tried to lint ${__dirname}/test-project/extra.ts but found no valid, enabled rules for this file type and file path in the resolved configuration.`, ), ); jest.resetAllMocks(); diff --git a/packages/eslint-plugin-tslint/tests/test-project/extra.ts b/packages/eslint-plugin-tslint/tests/test-project/extra.ts new file mode 100644 index 00000000000..e901f01b487 --- /dev/null +++ b/packages/eslint-plugin-tslint/tests/test-project/extra.ts @@ -0,0 +1 @@ +foo; From ae7c1293e97ef7002b163906f342d776db1498bd Mon Sep 17 00:00:00 2001 From: Ben Lichtman Date: Wed, 24 Jul 2019 21:35:10 -0700 Subject: [PATCH 5/9] test(eslint-plugin-tslint): fix test that prettier was breaking --- .prettierignore | 1 + packages/eslint-plugin-tslint/tests/fixture-project/4.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.prettierignore b/.prettierignore index 1dd0c399667..a86a2f04fc9 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,5 @@ **/tests/fixtures/**/* +**/tests/fixture-project/**/* **/dist **/coverage **/shared-fixtures diff --git a/packages/eslint-plugin-tslint/tests/fixture-project/4.ts b/packages/eslint-plugin-tslint/tests/fixture-project/4.ts index e1173e87a22..1ca8bbace36 100644 --- a/packages/eslint-plugin-tslint/tests/fixture-project/4.ts +++ b/packages/eslint-plugin-tslint/tests/fixture-project/4.ts @@ -1 +1 @@ -var foo = true; // semicolon +var foo = true // semicolon From 2c02bd1d9444fa6f082393e69dfeae3cf6ee8402 Mon Sep 17 00:00:00 2001 From: Ben Lichtman Date: Wed, 24 Jul 2019 21:42:49 -0700 Subject: [PATCH 6/9] docs(parser): update README with new error --- packages/parser/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/parser/README.md b/packages/parser/README.md index 78d17e6388f..479f07a26c2 100644 --- a/packages/parser/README.md +++ b/packages/parser/README.md @@ -48,7 +48,7 @@ The following additional configuration options are available by specifying them - **`useJSXTextNode`** - default `true`. Please set `false` if you use this parser on ESLint v4. If this is `false`, the parser creates the AST of JSX texts as the legacy style. -- **`project`** - default `undefined`. This option allows you to provide a path to your project's `tsconfig.json`. **This setting is required if you want to use rules which require type information**. You may want to use this setting in tandem with the `tsconfigRootDir` option below. +- **`project`** - default `undefined`. This option allows you to provide a path to your project's `tsconfig.json`. **This setting is required if you want to use rules which require type information**. You may want to use this setting in tandem with the `tsconfigRootDir` option below. Note that if this setting is specified, you must only lint files that are included in the projects as defined by the provided `tsconfig.json` files. - **`tsconfigRootDir`** - default `undefined`. This option allows you to provide the root directory for relative tsconfig paths specified in the `project` option above. From 335be688ed46218dee046dec24fe4ac8add5dd70 Mon Sep 17 00:00:00 2001 From: Ben Lichtman Date: Wed, 24 Jul 2019 21:52:18 -0700 Subject: [PATCH 7/9] fix(typescript-estree): extract to constant --- packages/typescript-estree/src/parser.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 1abb8d91d6d..02ea61a1de4 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -64,26 +64,18 @@ function resetExtra(): void { * @returns If found, returns the source file corresponding to the code and the containing program */ function getASTFromProject(code: string, options: TSESTreeOptions) { + const filePath = options.filePath || getFileName(options); const astAndProgram = firstDefined( - calculateProjectParserOptions( - code, - options.filePath || getFileName(options), - extra, - ), + calculateProjectParserOptions(code, filePath, extra), currentProgram => { - const ast = currentProgram.getSourceFile( - options.filePath || getFileName(options), - ); + const ast = currentProgram.getSourceFile(filePath); return ast && { ast, program: currentProgram }; }, ); if (!astAndProgram) { throw new Error( - `If "parserOptions.project" has been set for @typescript-eslint/parser, ${options.filePath || - getFileName( - options, - )} must be included in at least one of the projects provided.`, + `If "parserOptions.project" has been set for @typescript-eslint/parser, ${filePath} must be included in at least one of the projects provided.`, ); } From b8e54171a0596dfd5ae5935fdb3e3835c2485679 Mon Sep 17 00:00:00 2001 From: Ben Lichtman Date: Wed, 24 Jul 2019 22:08:35 -0700 Subject: [PATCH 8/9] test: introduce local eslintrc for js files --- tests/integration/utils/.eslintrc.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tests/integration/utils/.eslintrc.json diff --git a/tests/integration/utils/.eslintrc.json b/tests/integration/utils/.eslintrc.json new file mode 100644 index 00000000000..23278ae0621 --- /dev/null +++ b/tests/integration/utils/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "parserOptions": { + "project": null + } +} From 9f03dd0fc0e261f2165e4e391eeaece1a2ecd2be Mon Sep 17 00:00:00 2001 From: Ben Lichtman Date: Thu, 25 Jul 2019 22:46:09 -0700 Subject: [PATCH 9/9] fix: add back default program --- packages/parser/README.md | 17 ++++++++- .../typescript-estree/src/parser-options.ts | 2 + packages/typescript-estree/src/parser.ts | 38 +++++++++++++++++-- packages/typescript-estree/tests/lib/parse.ts | 1 + .../tests/lib/semanticInfo.ts | 9 +++++ 5 files changed, 62 insertions(+), 5 deletions(-) diff --git a/packages/parser/README.md b/packages/parser/README.md index 479f07a26c2..45c620280f1 100644 --- a/packages/parser/README.md +++ b/packages/parser/README.md @@ -48,10 +48,25 @@ The following additional configuration options are available by specifying them - **`useJSXTextNode`** - default `true`. Please set `false` if you use this parser on ESLint v4. If this is `false`, the parser creates the AST of JSX texts as the legacy style. -- **`project`** - default `undefined`. This option allows you to provide a path to your project's `tsconfig.json`. **This setting is required if you want to use rules which require type information**. You may want to use this setting in tandem with the `tsconfigRootDir` option below. Note that if this setting is specified, you must only lint files that are included in the projects as defined by the provided `tsconfig.json` files. +- **`project`** - default `undefined`. This option allows you to provide a path to your project's `tsconfig.json`. **This setting is required if you want to use rules which require type information**. You may want to use this setting in tandem with the `tsconfigRootDir` option below. + + - Note that if this setting is specified and `createDefaultProgram` is not, you must only lint files that are included in the projects as defined by the provided `tsconfig.json` files. If your existing configuration does not include all of the files you would like to lint, you can create a separate `tsconfig.eslint.json` as follows: + + ```ts + { + "extends": "./tsconfig.json", // path to existing tsconfig + "includes": [ + "src/**/*.ts", + "test/**/*.ts", + // etc + ] + } + ``` - **`tsconfigRootDir`** - default `undefined`. This option allows you to provide the root directory for relative tsconfig paths specified in the `project` option above. +- **`createDefaultProgram`** - default `false`. This option allows you to request that when the `project` setting is specified, files will be allowed when not included in the projects defined by the provided `tsconfig.json` files. However, this may incur significant performance costs, so this option is primarily included for backwards-compatibility. See the **`project`** section for more information. + - **`extraFileExtensions`** - default `undefined`. This option allows you to provide one or more additional file extensions which should be considered in the TypeScript Program compilation. E.g. a `.vue` file - **`warnOnUnsupportedTypeScriptVersion`** - default `true`. This option allows you to toggle the warning that the parser will give you if you use a version of TypeScript which is not explicitly supported diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index c224b7da565..77a649f6f30 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -18,6 +18,7 @@ export interface Extra { tsconfigRootDir: string; extraFileExtensions: string[]; preserveNodeMaps?: boolean; + createDefaultProgram: boolean; } export interface TSESTreeOptions { @@ -35,6 +36,7 @@ export interface TSESTreeOptions { tsconfigRootDir?: string; extraFileExtensions?: string[]; preserveNodeMaps?: boolean; + createDefaultProgram?: boolean; } // This lets us use generics to type the return value, and removes the need to diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 5f9bdc0209c..564114d7ccb 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -6,7 +6,10 @@ import { firstDefined } from './node-utils'; import { Extra, TSESTreeOptions, ParserServices } from './parser-options'; import { getFirstSemanticOrSyntacticError } from './semantic-errors'; import { TSESTree } from './ts-estree'; -import { calculateProjectParserOptions } from './tsconfig-parser'; +import { + calculateProjectParserOptions, + createProgram, +} from './tsconfig-parser'; /** * This needs to be kept in sync with the top-level README.md in the @@ -55,6 +58,7 @@ function resetExtra(): void { tsconfigRootDir: process.cwd(), extraFileExtensions: [], preserveNodeMaps: undefined, + createDefaultProgram: false, }; } @@ -63,7 +67,11 @@ function resetExtra(): void { * @param options The config object * @returns If found, returns the source file corresponding to the code and the containing program */ -function getASTFromProject(code: string, options: TSESTreeOptions) { +function getASTFromProject( + code: string, + options: TSESTreeOptions, + createDefaultProgram: boolean, +) { const filePath = options.filePath || getFileName(options); const astAndProgram = firstDefined( calculateProjectParserOptions(code, filePath, extra), @@ -73,7 +81,7 @@ function getASTFromProject(code: string, options: TSESTreeOptions) { }, ); - if (!astAndProgram) { + if (!astAndProgram && !createDefaultProgram) { throw new Error( `If "parserOptions.project" has been set for @typescript-eslint/parser, ${filePath} must be included in at least one of the projects provided.`, ); @@ -82,6 +90,18 @@ function getASTFromProject(code: string, options: TSESTreeOptions) { return astAndProgram; } +/** + * @param code The code of the file being linted + * @param options The config object + * @returns If found, returns the source file corresponding to the code and the containing program + */ +function getASTAndDefaultProject(code: string, options: TSESTreeOptions) { + const fileName = options.filePath || getFileName(options); + const program = createProgram(code, fileName, extra); + const ast = program && program.getSourceFile(fileName); + return ast && { ast, program }; +} + /** * @param code The code of the file being linted * @returns Returns a new source file and program corresponding to the linted code @@ -149,9 +169,14 @@ function getProgramAndAST( code: string, options: TSESTreeOptions, shouldProvideParserServices: boolean, + createDefaultProgram: boolean, ) { return ( - (shouldProvideParserServices && getASTFromProject(code, options)) || + (shouldProvideParserServices && + getASTFromProject(code, options, createDefaultProgram)) || + (shouldProvideParserServices && + createDefaultProgram && + getASTAndDefaultProject(code, options)) || createNewProgram(code) ); } @@ -241,6 +266,10 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void { if (options.preserveNodeMaps === undefined && extra.projects.length > 0) { extra.preserveNodeMaps = true; } + + extra.createDefaultProgram = + typeof options.createDefaultProgram === 'boolean' && + options.createDefaultProgram; } function warnAboutTSVersion(): void { @@ -373,6 +402,7 @@ export function parseAndGenerateServices< code, options, shouldProvideParserServices, + extra.createDefaultProgram, ); /** * Determine whether or not two-way maps of converted AST nodes should be preserved diff --git a/packages/typescript-estree/tests/lib/parse.ts b/packages/typescript-estree/tests/lib/parse.ts index 1a05ed3dbfe..57a4bc05742 100644 --- a/packages/typescript-estree/tests/lib/parse.ts +++ b/packages/typescript-estree/tests/lib/parse.ts @@ -94,6 +94,7 @@ describe('parse()', () => { tsconfigRootDir: expect.any(String), useJSXTextNode: false, preserveNodeMaps: false, + createDefaultProgram: false, }, false, ); diff --git a/packages/typescript-estree/tests/lib/semanticInfo.ts b/packages/typescript-estree/tests/lib/semanticInfo.ts index 4c432b3b5f7..7e5c634db9d 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.ts @@ -267,6 +267,15 @@ describe('semanticInfo', () => { parseCodeAndGenerateServices(readFileSync(fileName, 'utf8'), badConfig), ).toThrowErrorMatchingSnapshot(); }); + + it('default program produced with option', () => { + const parseResult = parseCodeAndGenerateServices('var foo = 5;', { + ...createOptions(''), + createDefaultProgram: true, + }); + + expect(parseResult.services.program).toBeDefined(); + }); }); function testIsolatedFile(