Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(typescript-estree): improve error messaging around project files #866

Merged
merged 6 commits into from Aug 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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/**/*"]
}