Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fix(typescript-estree): improve missing project file error msg (#866)
Fixes #853
  • Loading branch information
bradzacher committed Aug 19, 2019
1 parent c68e033 commit 8f3b0a8
Show file tree
Hide file tree
Showing 23 changed files with 190 additions and 38 deletions.
Expand Up @@ -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;
Expand Down
Expand Up @@ -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;`,
Expand Down
29 changes: 17 additions & 12 deletions packages/parser/README.md
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions packages/typescript-estree/jest.config.js
Expand Up @@ -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],
},
},
},
};
2 changes: 1 addition & 1 deletion packages/typescript-estree/src/convert.ts
Expand Up @@ -67,7 +67,7 @@ export class Converter {
*/
constructor(ast: ts.SourceFile, options: ConverterOptions) {
this.ast = ast;
this.options = options;
this.options = { ...options };
}

getASTMaps(): ASTMaps {
Expand Down
39 changes: 36 additions & 3 deletions 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';
Expand All @@ -9,6 +10,7 @@ import { TSESTree } from './ts-estree';
import {
calculateProjectParserOptions,
createProgram,
defaultCompilerOptions,
} from './tsconfig-parser';

/**
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -158,6 +190,7 @@ function createNewProgram(code: string): ASTAndProgram {
noResolve: true,
target: ts.ScriptTarget.Latest,
jsx: extra.jsx ? ts.JsxEmit.Preserve : undefined,
...defaultCompilerOptions,
},
compilerHost,
);
Expand Down
5 changes: 3 additions & 2 deletions packages/typescript-estree/src/tsconfig-parser.ts
Expand Up @@ -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,
};

/**
Expand Down Expand Up @@ -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,
Expand Down
@@ -0,0 +1 @@
export var a = true;
@@ -0,0 +1 @@
export var a = true;
@@ -0,0 +1 @@
export var a = true;
@@ -0,0 +1 @@
export var a = true;
@@ -0,0 +1 @@
export var a = true;
@@ -0,0 +1 @@
export var a = true;
@@ -0,0 +1 @@
export var a = true;
@@ -0,0 +1 @@
export var a = true;
@@ -0,0 +1 @@
export var a = true;
@@ -0,0 +1 @@
export var a = true;
@@ -0,0 +1 @@
export var a = true;
@@ -0,0 +1,9 @@
{
"include": [
"ts/included.ts",
"ts/included.tsx",
"js/included.js",
"js/included.jsx",
"other/included.vue"
]
}
36 changes: 36 additions & 0 deletions packages/typescript-estree/tests/lib/__snapshots__/parse.ts.snap
Expand Up @@ -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 [
Expand Down
60 changes: 58 additions & 2 deletions 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';

Expand Down Expand Up @@ -145,7 +145,7 @@ describe('parse()', () => {
};
const projectConfig: TSESTreeOptions = {
...baseConfig,
tsconfigRootDir: join(process.cwd(), FIXTURES_DIR),
tsconfigRootDir: FIXTURES_DIR,
project: './tsconfig.json',
};

Expand Down Expand Up @@ -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();
});
});
});
});
4 changes: 1 addition & 3 deletions packages/typescript-estree/tests/lib/semanticInfo.ts
Expand Up @@ -236,9 +236,7 @@ describe('semanticInfo', () => {
`function M() { return Base }`,
createOptions('<input>'),
),
).toThrow(
`If "parserOptions.project" has been set for @typescript-eslint/parser, <input> must be included in at least one of the projects provided.`,
);
).toThrow(/The file does not match your project config: <input>/);
});

it('non-existent project file', () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/typescript-estree/tsconfig.json
Expand Up @@ -3,5 +3,6 @@
"compilerOptions": {
"outDir": "./dist"
},
"include": ["src", "tests", "tools"]
"include": ["src", "tests", "tools"],
"exclude": ["tests/fixtures/**/*"]
}

0 comments on commit 8f3b0a8

Please sign in to comment.