From 8f3b0a8e48abaffe5707d401e37ae5d2b616d1b9 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Mon, 19 Aug 2019 08:56:01 -0700 Subject: [PATCH] fix(typescript-estree): improve missing project file error msg (#866) Fixes #853 --- .../no-unnecessary-type-assertion.test.ts | 6 -- .../rules/restrict-plus-operands.test.ts | 16 ++--- packages/parser/README.md | 29 +++++---- packages/typescript-estree/jest.config.js | 8 +++ packages/typescript-estree/src/convert.ts | 2 +- packages/typescript-estree/src/parser.ts | 39 +++++++++++- .../typescript-estree/src/tsconfig-parser.ts | 5 +- .../fixtures/invalidFileErrors/js/included.js | 1 + .../invalidFileErrors/js/included.jsx | 1 + .../invalidFileErrors/js/notIncluded.js | 1 + .../invalidFileErrors/js/notIncluded.jsx | 1 + .../invalidFileErrors/other/included.vue | 1 + .../invalidFileErrors/other/notIncluded.vue | 1 + .../other/unknownFileType.unknown | 1 + .../fixtures/invalidFileErrors/ts/included.ts | 1 + .../invalidFileErrors/ts/included.tsx | 1 + .../invalidFileErrors/ts/notIncluded.ts | 1 + .../invalidFileErrors/ts/notIncluded.tsx | 1 + .../fixtures/invalidFileErrors/tsconfig.json | 9 +++ .../tests/lib/__snapshots__/parse.ts.snap | 36 +++++++++++ packages/typescript-estree/tests/lib/parse.ts | 60 ++++++++++++++++++- .../tests/lib/semanticInfo.ts | 4 +- packages/typescript-estree/tsconfig.json | 3 +- 23 files changed, 190 insertions(+), 38 deletions(-) create mode 100644 packages/typescript-estree/tests/fixtures/invalidFileErrors/js/included.js create mode 100644 packages/typescript-estree/tests/fixtures/invalidFileErrors/js/included.jsx create mode 100644 packages/typescript-estree/tests/fixtures/invalidFileErrors/js/notIncluded.js create mode 100644 packages/typescript-estree/tests/fixtures/invalidFileErrors/js/notIncluded.jsx create mode 100644 packages/typescript-estree/tests/fixtures/invalidFileErrors/other/included.vue create mode 100644 packages/typescript-estree/tests/fixtures/invalidFileErrors/other/notIncluded.vue create mode 100644 packages/typescript-estree/tests/fixtures/invalidFileErrors/other/unknownFileType.unknown create mode 100644 packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/included.ts create mode 100644 packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/included.tsx create mode 100644 packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/notIncluded.ts create mode 100644 packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/notIncluded.tsx create mode 100644 packages/typescript-estree/tests/fixtures/invalidFileErrors/tsconfig.json diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index 4c06e95a042..4b4e496253f 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -99,12 +99,6 @@ foo(str!); ` declare function a(a: string): any; declare const b: string | null; -class Mx { - @a(b!) - private prop = 1; -} - `, - ` class Mx { @a(b!) private prop = 1; diff --git a/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts b/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts index fdda8fe0ef4..44583a202a1 100644 --- a/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts @@ -26,37 +26,37 @@ ruleTester.run('restrict-plus-operands', rule, { `var foo = BigInt(1) + 1n`, `var foo = 1n; foo + 2n`, ` -function test () : number { return 2; } +function test(s: string, n: number) : number { return 2; } var foo = test("5.5", 10) + 10; - `, + `, ` var x = 5; var z = 8.2; var foo = x + z; - `, + `, ` var w = "6.5"; var y = "10"; var foo = y + w; - `, + `, 'var foo = 1 + 1;', "var foo = '1' + '1';", ` var pair: { first: number, second: string } = { first: 5, second: "10" }; var foo = pair.first + 10; - `, + `, ` var pair: { first: number, second: string } = { first: 5, second: "10" }; var foo = pair.first + (10 as number); - `, + `, ` var pair: { first: number, second: string } = { first: 5, second: "10" }; var foo = "5.5" + pair.second; - `, + `, ` var pair: { first: number, second: string } = { first: 5, second: "10" }; var foo = ("5.5" as string) + pair.second; - `, + `, `const foo = 'hello' + (someBoolean ? 'a' : 'b') + (() => someBoolean ? 'c' : 'd')() + 'e';`, `const balls = true;`, `balls === true;`, diff --git a/packages/parser/README.md b/packages/parser/README.md index cbabebd95f8..718e2c82ba5 100644 --- a/packages/parser/README.md +++ b/packages/parser/README.md @@ -52,25 +52,30 @@ The following additional configuration options are available by specifying them - 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 - "include": [ - "src/**/*.ts", - "test/**/*.ts", - // etc - ] - } - ``` + ```ts + { + // extend your base config so you don't have to redefine your compilerOptions + "extends": "./tsconfig.json", + "include": [ + "src/**/*.ts", + "test/**/*.ts", + "typings/**/*.ts", + // etc + + // if you have a mixed JS/TS codebase, don't forget to include your JS files + "src/**/*.js" + ] + } + ``` - **`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 +- **`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. **Using this option will incur significant performance costs. This option is primarily included for backwards-compatibility.** See the **`project`** section above for more information. + ### .eslintrc.json ```json diff --git a/packages/typescript-estree/jest.config.js b/packages/typescript-estree/jest.config.js index 4005947d277..e01f6ed0775 100644 --- a/packages/typescript-estree/jest.config.js +++ b/packages/typescript-estree/jest.config.js @@ -10,4 +10,12 @@ module.exports = { collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], coverageReporters: ['text-summary', 'lcov'], + globals: { + 'ts-jest': { + diagnostics: { + // ignore the diagnostic error for the invalidFileErrors fixtures + ignoreCodes: [5056], + }, + }, + }, }; diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index 3a7d2de99dc..43ab7889c6b 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -67,7 +67,7 @@ export class Converter { */ constructor(ast: ts.SourceFile, options: ConverterOptions) { this.ast = ast; - this.options = options; + this.options = { ...options }; } getASTMaps(): ASTMaps { diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index f215fa44492..1b89136928a 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -1,3 +1,4 @@ +import path from 'path'; import semver from 'semver'; import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports import { astConverter } from './ast-converter'; @@ -9,6 +10,7 @@ import { TSESTree } from './ts-estree'; import { calculateProjectParserOptions, createProgram, + defaultCompilerOptions, } from './tsconfig-parser'; /** @@ -87,9 +89,39 @@ function getASTFromProject( ); 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.`, - ); + // the file was either not matched within the tsconfig, or the extension wasn't expected + const errorLines = [ + '"parserOptions.project" has been set for @typescript-eslint/parser.', + `The file does not match your project config: ${filePath}.`, + ]; + let hasMatchedAnError = false; + + const fileExtension = path.extname(filePath); + if (!['.ts', '.tsx', '.js', '.jsx'].includes(fileExtension)) { + const nonStandardExt = `The extension for the file (${fileExtension}) is non-standard`; + if (extra.extraFileExtensions && extra.extraFileExtensions.length > 0) { + if (!extra.extraFileExtensions.includes(fileExtension)) { + errorLines.push( + `${nonStandardExt}. It should be added to your existing "parserOptions.extraFileExtensions".`, + ); + hasMatchedAnError = true; + } + } else { + errorLines.push( + `${nonStandardExt}. You should add "parserOptions.extraFileExtensions" to your config.`, + ); + hasMatchedAnError = true; + } + } + + if (!hasMatchedAnError) { + errorLines.push( + 'The file must be included in at least one of the projects provided.', + ); + hasMatchedAnError = true; + } + + throw new Error(errorLines.join('\n')); } return astAndProgram; @@ -158,6 +190,7 @@ function createNewProgram(code: string): ASTAndProgram { noResolve: true, target: ts.ScriptTarget.Latest, jsx: extra.jsx ? ts.JsxEmit.Preserve : undefined, + ...defaultCompilerOptions, }, compilerHost, ); diff --git a/packages/typescript-estree/src/tsconfig-parser.ts b/packages/typescript-estree/src/tsconfig-parser.ts index 88c63171545..affbe090627 100644 --- a/packages/typescript-estree/src/tsconfig-parser.ts +++ b/packages/typescript-estree/src/tsconfig-parser.ts @@ -9,9 +9,10 @@ import { Extra } from './parser-options'; /** * Default compiler options for program generation from single root file */ -const defaultCompilerOptions: ts.CompilerOptions = { +export const defaultCompilerOptions: ts.CompilerOptions = { allowNonTsExtensions: true, allowJs: true, + checkJs: true, }; /** @@ -109,7 +110,7 @@ export function calculateProjectParserOptions( // create compiler host const watchCompilerHost = ts.createWatchCompilerHost( tsconfigPath, - /*optionsToExtend*/ { allowNonTsExtensions: true } as ts.CompilerOptions, + defaultCompilerOptions, ts.sys, ts.createSemanticDiagnosticsBuilderProgram, diagnosticReporter, diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/included.js b/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/included.js new file mode 100644 index 00000000000..25005f98f8f --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/included.js @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/included.jsx b/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/included.jsx new file mode 100644 index 00000000000..25005f98f8f --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/included.jsx @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/notIncluded.js b/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/notIncluded.js new file mode 100644 index 00000000000..25005f98f8f --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/notIncluded.js @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/notIncluded.jsx b/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/notIncluded.jsx new file mode 100644 index 00000000000..25005f98f8f --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/notIncluded.jsx @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/included.vue b/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/included.vue new file mode 100644 index 00000000000..25005f98f8f --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/included.vue @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/notIncluded.vue b/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/notIncluded.vue new file mode 100644 index 00000000000..25005f98f8f --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/notIncluded.vue @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/unknownFileType.unknown b/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/unknownFileType.unknown new file mode 100644 index 00000000000..25005f98f8f --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/unknownFileType.unknown @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/included.ts b/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/included.ts new file mode 100644 index 00000000000..25005f98f8f --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/included.ts @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/included.tsx b/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/included.tsx new file mode 100644 index 00000000000..25005f98f8f --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/included.tsx @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/notIncluded.ts b/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/notIncluded.ts new file mode 100644 index 00000000000..25005f98f8f --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/notIncluded.ts @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/notIncluded.tsx b/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/notIncluded.tsx new file mode 100644 index 00000000000..25005f98f8f --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/notIncluded.tsx @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/tsconfig.json b/packages/typescript-estree/tests/fixtures/invalidFileErrors/tsconfig.json new file mode 100644 index 00000000000..de5d69d736c --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/tsconfig.json @@ -0,0 +1,9 @@ +{ + "include": [ + "ts/included.ts", + "ts/included.tsx", + "js/included.js", + "js/included.jsx", + "other/included.vue" + ] +} diff --git a/packages/typescript-estree/tests/lib/__snapshots__/parse.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/parse.ts.snap index 38e378d9f7e..bca89ec8d8c 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/parse.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/parse.ts.snap @@ -189,6 +189,42 @@ Object { } `; +exports[`parse() invalid file error messages "parserOptions.extraFileExtensions" is non-empty the extension does not match 1`] = ` +"\\"parserOptions.project\\" has been set for @typescript-eslint/parser. +The file does not match your project config: tests/fixtures/invalidFileErrors/other/unknownFileType.unknown. +The extension for the file (.unknown) is non-standard. It should be added to your existing \\"parserOptions.extraFileExtensions\\"." +`; + +exports[`parse() invalid file error messages "parserOptions.extraFileExtensions" is non-empty the extension matches the file isn't included 1`] = ` +"\\"parserOptions.project\\" has been set for @typescript-eslint/parser. +The file does not match your project config: tests/fixtures/invalidFileErrors/other/notIncluded.vue. +The file must be included in at least one of the projects provided." +`; + +exports[`parse() invalid file error messages project includes errors for not included files 1`] = ` +"\\"parserOptions.project\\" has been set for @typescript-eslint/parser. +The file does not match your project config: tests/fixtures/invalidFileErrors/ts/notIncluded.ts. +The file must be included in at least one of the projects provided." +`; + +exports[`parse() invalid file error messages project includes errors for not included files 2`] = ` +"\\"parserOptions.project\\" has been set for @typescript-eslint/parser. +The file does not match your project config: tests/fixtures/invalidFileErrors/ts/notIncluded.tsx. +The file must be included in at least one of the projects provided." +`; + +exports[`parse() invalid file error messages project includes errors for not included files 3`] = ` +"\\"parserOptions.project\\" has been set for @typescript-eslint/parser. +The file does not match your project config: tests/fixtures/invalidFileErrors/js/notIncluded.js. +The file must be included in at least one of the projects provided." +`; + +exports[`parse() invalid file error messages project includes errors for not included files 4`] = ` +"\\"parserOptions.project\\" has been set for @typescript-eslint/parser. +The file does not match your project config: tests/fixtures/invalidFileErrors/js/notIncluded.jsx. +The file must be included in at least one of the projects provided." +`; + exports[`parse() non string code should correctly convert code to a string for parse() 1`] = ` Object { "body": Array [ diff --git a/packages/typescript-estree/tests/lib/parse.ts b/packages/typescript-estree/tests/lib/parse.ts index 57a4bc05742..6ed8db76cbc 100644 --- a/packages/typescript-estree/tests/lib/parse.ts +++ b/packages/typescript-estree/tests/lib/parse.ts @@ -1,8 +1,8 @@ +import { join, resolve, relative } from 'path'; 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'; @@ -145,7 +145,7 @@ describe('parse()', () => { }; const projectConfig: TSESTreeOptions = { ...baseConfig, - tsconfigRootDir: join(process.cwd(), FIXTURES_DIR), + tsconfigRootDir: FIXTURES_DIR, project: './tsconfig.json', }; @@ -241,4 +241,60 @@ describe('parse()', () => { ).toBeUndefined(); }); }); + + describe('invalid file error messages', () => { + const PROJECT_DIR = resolve(FIXTURES_DIR, '../invalidFileErrors'); + const code = 'var a = true'; + const config: TSESTreeOptions = { + comment: true, + tokens: true, + range: true, + loc: true, + tsconfigRootDir: PROJECT_DIR, + project: './tsconfig.json', + extraFileExtensions: ['.vue'], + }; + const testParse = (filePath: string) => (): void => { + parser.parseAndGenerateServices(code, { + ...config, + filePath: relative(process.cwd(), join(PROJECT_DIR, filePath)), + }); + }; + + describe('project includes', () => { + it("doesn't error for matched files", () => { + expect(testParse('ts/included.ts')).not.toThrow(); + expect(testParse('ts/included.tsx')).not.toThrow(); + expect(testParse('js/included.js')).not.toThrow(); + expect(testParse('js/included.jsx')).not.toThrow(); + }); + + it('errors for not included files', () => { + expect(testParse('ts/notIncluded.ts')).toThrowErrorMatchingSnapshot(); + expect(testParse('ts/notIncluded.tsx')).toThrowErrorMatchingSnapshot(); + expect(testParse('js/notIncluded.js')).toThrowErrorMatchingSnapshot(); + expect(testParse('js/notIncluded.jsx')).toThrowErrorMatchingSnapshot(); + }); + }); + + describe('"parserOptions.extraFileExtensions" is non-empty', () => { + describe('the extension matches', () => { + it('the file is included', () => { + expect(testParse('other/included.vue')).not.toThrow(); + }); + + it("the file isn't included", () => { + expect( + testParse('other/notIncluded.vue'), + ).toThrowErrorMatchingSnapshot(); + }); + }); + + it('the extension does not match', () => { + expect( + testParse('other/unknownFileType.unknown'), + ).toThrowErrorMatchingSnapshot(); + }); + }); + }); }); diff --git a/packages/typescript-estree/tests/lib/semanticInfo.ts b/packages/typescript-estree/tests/lib/semanticInfo.ts index 1afab0c5e44..157edec7412 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.ts @@ -236,9 +236,7 @@ describe('semanticInfo', () => { `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.`, - ); + ).toThrow(/The file does not match your project config: /); }); it('non-existent project file', () => { diff --git a/packages/typescript-estree/tsconfig.json b/packages/typescript-estree/tsconfig.json index e389d7edef3..2ea9199d263 100644 --- a/packages/typescript-estree/tsconfig.json +++ b/packages/typescript-estree/tsconfig.json @@ -3,5 +3,6 @@ "compilerOptions": { "outDir": "./dist" }, - "include": ["src", "tests", "tools"] + "include": ["src", "tests", "tools"], + "exclude": ["tests/fixtures/**/*"] }