diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 31a96173d90..c57d8d09cd6 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -139,7 +139,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | [`@typescript-eslint/no-unsafe-call`](./docs/rules/no-unsafe-call.md) | Disallows calling an any type value | | | :thought_balloon: | | [`@typescript-eslint/no-unsafe-member-access`](./docs/rules/no-unsafe-member-access.md) | Disallows member access on any typed variables | | | :thought_balloon: | | [`@typescript-eslint/no-unsafe-return`](./docs/rules/no-unsafe-return.md) | Disallows returning any from a function | | | :thought_balloon: | -| [`@typescript-eslint/no-unused-vars-experimental`](./docs/rules/no-unused-vars-experimental.md) | Disallow unused variables and arguments | | | :thought_balloon: | +| [`@typescript-eslint/no-unused-vars-experimental`](./docs/rules/no-unused-vars-experimental.md) | Disallow unused variables and arguments | | | | | [`@typescript-eslint/no-var-requires`](./docs/rules/no-var-requires.md) | Disallows the use of require statements except in import statements | :heavy_check_mark: | | | | [`@typescript-eslint/prefer-as-const`](./docs/rules/prefer-as-const.md) | Prefer usage of `as const` over literal type | | :wrench: | | | [`@typescript-eslint/prefer-for-of`](./docs/rules/prefer-for-of.md) | Prefer a ‘for-of’ loop over a standard ‘for’ loop if the index is only used to access the array being iterated | | | | diff --git a/packages/eslint-plugin/src/rules/no-unused-vars-experimental.ts b/packages/eslint-plugin/src/rules/no-unused-vars-experimental.ts index ca46ef0527c..c8586973861 100644 --- a/packages/eslint-plugin/src/rules/no-unused-vars-experimental.ts +++ b/packages/eslint-plugin/src/rules/no-unused-vars-experimental.ts @@ -29,7 +29,6 @@ export default util.createRule({ description: 'Disallow unused variables and arguments', category: 'Best Practices', recommended: false, - requiresTypeChecking: true, }, schema: [ { @@ -68,7 +67,7 @@ export default util.createRule({ }, ], create(context, [userOptions]) { - const parserServices = util.getParserServices(context); + const parserServices = util.getParserServices(context, true); const tsProgram = parserServices.program; const afterAllDiagnosticsCallbacks: (() => void)[] = []; @@ -191,7 +190,7 @@ export default util.createRule({ parent: ts.ParameterDeclaration, ): void { const name = identifier.getText(); - // regardless of if the paramter is ignored, track that it had a diagnostic fired on it + // regardless of if the parameter is ignored, track that it had a diagnostic fired on it unusedParameters.add(identifier); /* diff --git a/packages/eslint-plugin/tests/docs.test.ts b/packages/eslint-plugin/tests/docs.test.ts index d7bc5ce97b9..9376af4daa5 100644 --- a/packages/eslint-plugin/tests/docs.test.ts +++ b/packages/eslint-plugin/tests/docs.test.ts @@ -70,6 +70,10 @@ describe('Validating rule docs', () => { }); describe('Validating rule metadata', () => { + function requiresFullTypeInformation(content: string): boolean { + return /getParserServices(\(\s*[^,\s)]+)\s*(,\s*false\s*)?\)/.test(content); + } + for (const [ruleName, rule] of rulesData) { describe(`${ruleName}`, () => { it('`name` field in rule must match the filename', () => { @@ -85,9 +89,10 @@ describe('Validating rule metadata', () => { // not perfect but should be good enough const ruleFileContents = fs.readFileSync( path.resolve(__dirname, `../src/rules/${ruleName}.ts`), + 'utf-8', ); - expect(ruleFileContents.includes('getParserServices')).toEqual( + expect(requiresFullTypeInformation(ruleFileContents)).toEqual( rule.meta.docs?.requiresTypeChecking ?? false, ); }); diff --git a/packages/eslint-plugin/tests/rules/no-unused-vars-experimental.test.ts b/packages/eslint-plugin/tests/rules/no-unused-vars-experimental.test.ts index 335b3986437..172dfdd793a 100644 --- a/packages/eslint-plugin/tests/rules/no-unused-vars-experimental.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unused-vars-experimental.test.ts @@ -14,8 +14,6 @@ const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018, sourceType: 'module', - project: './tsconfig.json', - tsconfigRootDir: rootDir, }, parser: '@typescript-eslint/parser', }); diff --git a/packages/eslint-plugin/tests/util/isUnsafeAssignment.test.ts b/packages/eslint-plugin/tests/util/isUnsafeAssignment.test.ts index bd644832b30..3862b38902c 100644 --- a/packages/eslint-plugin/tests/util/isUnsafeAssignment.test.ts +++ b/packages/eslint-plugin/tests/util/isUnsafeAssignment.test.ts @@ -16,8 +16,8 @@ describe('isUnsafeAssignment', () => { filePath: path.join(rootDir, 'file.ts'), tsconfigRootDir: rootDir, }); - const checker = services.program!.getTypeChecker(); - const esTreeNodeToTSNodeMap = services.esTreeNodeToTSNodeMap!; + const checker = services.program.getTypeChecker(); + const esTreeNodeToTSNodeMap = services.esTreeNodeToTSNodeMap; const declaration = ast.body[0] as TSESTree.VariableDeclaration; const declarator = declaration.declarations[0]; diff --git a/packages/experimental-utils/src/eslint-utils/getParserServices.ts b/packages/experimental-utils/src/eslint-utils/getParserServices.ts index 57b6bc59f65..481603d7ed5 100644 --- a/packages/experimental-utils/src/eslint-utils/getParserServices.ts +++ b/packages/experimental-utils/src/eslint-utils/getParserServices.ts @@ -4,31 +4,38 @@ import { ParserServices } from '../ts-estree'; const ERROR_MESSAGE = 'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.'; -type RequiredParserServices = { - [k in keyof ParserServices]: Exclude; -}; - /** * Try to retrieve typescript parser service from context */ function getParserServices< TMessageIds extends string, - TOptions extends unknown[] + TOptions extends readonly unknown[] >( context: TSESLint.RuleContext, -): RequiredParserServices { + allowWithoutFullTypeInformation = false, +): ParserServices { + // backwards compatibility check + // old versions of the parser would not return any parserServices unless parserOptions.project was set if ( !context.parserServices || !context.parserServices.program || - !context.parserServices.esTreeNodeToTSNodeMap + !context.parserServices.esTreeNodeToTSNodeMap || + !context.parserServices.tsNodeToESTreeNodeMap ) { - /** - * The user needs to have configured "project" in their parserOptions - * for @typescript-eslint/parser - */ throw new Error(ERROR_MESSAGE); } - return context.parserServices as RequiredParserServices; + + const hasFullTypeInformation = + context.parserServices.hasFullTypeInformation ?? + /* backwards compatible */ true; + + // if a rule requires full type information, then hard fail if it doesn't exist + // this forces the user to supply parserOptions.project + if (!hasFullTypeInformation && !allowWithoutFullTypeInformation) { + throw new Error(ERROR_MESSAGE); + } + + return context.parserServices; } export { getParserServices }; diff --git a/packages/typescript-estree/src/ast-converter.ts b/packages/typescript-estree/src/ast-converter.ts index 54f325429df..36ca1830ccd 100644 --- a/packages/typescript-estree/src/ast-converter.ts +++ b/packages/typescript-estree/src/ast-converter.ts @@ -10,7 +10,7 @@ export function astConverter( ast: SourceFile, extra: Extra, shouldPreserveNodeMaps: boolean, -): { estree: TSESTree.Program; astMaps: ASTMaps | undefined } { +): { estree: TSESTree.Program; astMaps: ASTMaps } { /** * The TypeScript compiler produced fundamental parse errors when parsing the * source. @@ -63,7 +63,7 @@ export function astConverter( estree.comments = convertComments(ast, extra.code); } - const astMaps = shouldPreserveNodeMaps ? instance.getASTMaps() : undefined; + const astMaps = instance.getASTMaps(); return { estree, astMaps }; } diff --git a/packages/typescript-estree/src/create-program/createProjectProgram.ts b/packages/typescript-estree/src/create-program/createProjectProgram.ts index c7949b02811..bca6fda1005 100644 --- a/packages/typescript-estree/src/create-program/createProjectProgram.ts +++ b/packages/typescript-estree/src/create-program/createProjectProgram.ts @@ -93,7 +93,6 @@ function createProjectProgram( errorLines.push( 'The file must be included in at least one of the projects provided.', ); - hasMatchedAnError = true; } throw new Error(errorLines.join('\n')); diff --git a/packages/typescript-estree/src/create-program/shared.ts b/packages/typescript-estree/src/create-program/shared.ts index 1aa6a4fe3c0..702e7884ccb 100644 --- a/packages/typescript-estree/src/create-program/shared.ts +++ b/packages/typescript-estree/src/create-program/shared.ts @@ -4,7 +4,7 @@ import { Extra } from '../parser-options'; interface ASTAndProgram { ast: ts.SourceFile; - program: ts.Program | undefined; + program: ts.Program; } /** @@ -16,6 +16,9 @@ const DEFAULT_COMPILER_OPTIONS: ts.CompilerOptions = { checkJs: true, noEmit: true, // extendedDiagnostics: true, + /** + * Flags required to make no-unused-vars work + */ noUnusedLocals: true, noUnusedParameters: true, }; diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index e901ee74957..0f423abb0cf 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -184,9 +184,8 @@ export interface ParserWeakMapESTreeToTSNode< } export interface ParserServices { - program: Program | undefined; - esTreeNodeToTSNodeMap: ParserWeakMapESTreeToTSNode | undefined; - tsNodeToESTreeNodeMap: - | ParserWeakMap - | undefined; + program: Program; + esTreeNodeToTSNodeMap: ParserWeakMapESTreeToTSNode; + tsNodeToESTreeNodeMap: ParserWeakMap; + hasFullTypeInformation: boolean; } diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 069e8af1960..57fbc01abab 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -12,7 +12,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'; +import { ASTAndProgram, ensureAbsolutePath } from './create-program/shared'; const log = debug('typescript-eslint:typescript-estree:parser'); @@ -48,11 +48,6 @@ function enforceString(code: unknown): string { return code; } -interface ASTAndProgram { - ast: ts.SourceFile; - program: ts.Program | undefined; -} - /** * @param code The code of the file being linted * @param shouldProvideParserServices True if the program should be attempted to be calculated from provided tsconfig files @@ -63,7 +58,7 @@ function getProgramAndAST( code: string, shouldProvideParserServices: boolean, shouldCreateDefaultProgram: boolean, -): ASTAndProgram | undefined { +): ASTAndProgram { return ( (shouldProvideParserServices && createProjectProgram(code, shouldCreateDefaultProgram, extra)) || @@ -103,7 +98,7 @@ function resetExtra(): void { jsx: false, loc: false, log: console.log, // eslint-disable-line no-console - preserveNodeMaps: undefined, + preserveNodeMaps: true, projects: [], range: false, strict: false, @@ -236,7 +231,7 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void { } /** - * Get the file extension + * Get the file path */ if (typeof options.filePath === 'string' && options.filePath !== '') { extra.filePath = options.filePath; @@ -298,14 +293,9 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void { /** * Allow the user to enable or disable the preservation of the AST node maps * during the conversion process. - * - * NOTE: For backwards compatibility we also preserve node maps in the case where `project` is set, - * and `preserveNodeMaps` is not explicitly set to anything. */ - extra.preserveNodeMaps = - typeof options.preserveNodeMaps === 'boolean' && options.preserveNodeMaps; - if (options.preserveNodeMaps === undefined && extra.projects.length > 0) { - extra.preserveNodeMaps = true; + if (typeof options.preserveNodeMaps === 'boolean') { + extra.preserveNodeMaps = options.preserveNodeMaps; } extra.createDefaultProgram = @@ -449,20 +439,13 @@ function parseAndGenerateServices( extra.createDefaultProgram, )!; - /** - * Determine if two-way maps of converted AST nodes should be preserved - * during the conversion process - */ - const shouldPreserveNodeMaps = - extra.preserveNodeMaps !== undefined - ? extra.preserveNodeMaps - : shouldProvideParserServices; - /** * Convert the TypeScript AST to an ESTree-compatible one, and optionally preserve * mappings between converted and original AST nodes */ - const { estree, astMaps } = astConverter(ast, extra, shouldPreserveNodeMaps); + const preserveNodeMaps = + typeof extra.preserveNodeMaps === 'boolean' ? extra.preserveNodeMaps : true; + const { estree, astMaps } = astConverter(ast, extra, preserveNodeMaps); /** * Even if TypeScript parsed the source code ok, and we had no problems converting the AST, @@ -481,15 +464,10 @@ function parseAndGenerateServices( return { ast: estree as AST, services: { - program: shouldProvideParserServices ? program : undefined, - esTreeNodeToTSNodeMap: - shouldPreserveNodeMaps && astMaps - ? astMaps.esTreeNodeToTSNodeMap - : undefined, - tsNodeToESTreeNodeMap: - shouldPreserveNodeMaps && astMaps - ? astMaps.tsNodeToESTreeNodeMap - : undefined, + hasFullTypeInformation: shouldProvideParserServices, + program, + esTreeNodeToTSNodeMap: astMaps.esTreeNodeToTSNodeMap, + tsNodeToESTreeNodeMap: astMaps.tsNodeToESTreeNodeMap, }, }; } diff --git a/packages/typescript-estree/tests/lib/__snapshots__/parse.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/parse.ts.snap index 4b6d7f2c783..8ea937e0cd9 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/parse.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/parse.ts.snap @@ -649,9 +649,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; @@ -938,9 +939,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; @@ -1117,9 +1119,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; @@ -1296,9 +1299,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; @@ -1514,9 +1518,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; @@ -1803,9 +1808,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; @@ -2092,9 +2098,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; @@ -2271,9 +2278,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; @@ -2450,9 +2458,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; @@ -2629,9 +2638,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; @@ -2808,9 +2818,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; @@ -3097,9 +3108,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; @@ -3386,9 +3398,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; @@ -3565,9 +3578,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; @@ -3744,9 +3758,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; @@ -4033,9 +4048,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; @@ -4212,9 +4228,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; @@ -4391,9 +4408,10 @@ Object { "type": "Program", }, "services": Object { - "esTreeNodeToTSNodeMap": undefined, - "program": undefined, - "tsNodeToESTreeNodeMap": undefined, + "esTreeNodeToTSNodeMap": WeakMap {}, + "hasFullTypeInformation": false, + "program": Object {}, + "tsNodeToESTreeNodeMap": WeakMap {}, }, } `; diff --git a/packages/typescript-estree/tests/lib/convert.ts b/packages/typescript-estree/tests/lib/convert.ts index 749f26737f0..0dd26908dd6 100644 --- a/packages/typescript-estree/tests/lib/convert.ts +++ b/packages/typescript-estree/tests/lib/convert.ts @@ -111,8 +111,8 @@ describe('convert', () => { instance.convertProgram(); const maps = instance.getASTMaps(); - function checkMaps(child: any): void { - child.forEachChild((node: any) => { + function checkMaps(child: ts.SourceFile | ts.Node): void { + child.forEachChild(node => { if ( node.kind !== ts.SyntaxKind.EndOfFileToken && node.kind !== ts.SyntaxKind.JsxAttributes && @@ -120,7 +120,7 @@ describe('convert', () => { ) { expect(node).toBe( maps.esTreeNodeToTSNodeMap.get( - maps.tsNodeToESTreeNodeMap.get(node), + maps.tsNodeToESTreeNodeMap.get(node as any), ), ); } @@ -145,15 +145,15 @@ describe('convert', () => { instance.convertProgram(); const maps = instance.getASTMaps(); - function checkMaps(child: any): void { - child.forEachChild((node: any) => { + function checkMaps(child: ts.SourceFile | ts.Node): void { + child.forEachChild(node => { if ( node.kind !== ts.SyntaxKind.EndOfFileToken && node.kind !== ts.SyntaxKind.JsxAttributes ) { expect(node).toBe( maps.esTreeNodeToTSNodeMap.get( - maps.tsNodeToESTreeNodeMap.get(node), + maps.tsNodeToESTreeNodeMap.get(node as any), ), ); } @@ -178,8 +178,8 @@ describe('convert', () => { const program = instance.convertProgram(); const maps = instance.getASTMaps(); - function checkMaps(child: any): void { - child.forEachChild((node: any) => { + function checkMaps(child: ts.SourceFile | ts.Node): void { + child.forEachChild(node => { if (node.kind !== ts.SyntaxKind.EndOfFileToken) { expect(ast).toBe( maps.esTreeNodeToTSNodeMap.get(maps.tsNodeToESTreeNodeMap.get(ast)), diff --git a/packages/typescript-estree/tests/lib/parse.ts b/packages/typescript-estree/tests/lib/parse.ts index 9c049ae882d..2504a2fe332 100644 --- a/packages/typescript-estree/tests/lib/parse.ts +++ b/packages/typescript-estree/tests/lib/parse.ts @@ -6,7 +6,7 @@ import { TSESTreeOptions } from '../../src/parser-options'; import * as sharedParserUtils from '../../src/create-program/shared'; import { createSnapshotTestBlock } from '../../tools/test-utils'; -const FIXTURES_DIR = './tests/fixtures/simpleProject'; +const FIXTURES_DIR = join(__dirname, '../fixtures/simpleProject'); describe('parse()', () => { describe('basic functionality', () => { @@ -176,11 +176,15 @@ describe('parse()', () => { ); }); - it('should not preserve node maps by default for parseAndGenerateServices(), unless `project` is set', () => { + it('should preserve node maps by default for parseAndGenerateServices()', () => { const noOptionSet = parser.parseAndGenerateServices(code, baseConfig); - expect(noOptionSet.services.esTreeNodeToTSNodeMap).toBeUndefined(); - expect(noOptionSet.services.tsNodeToESTreeNodeMap).toBeUndefined(); + expect(noOptionSet.services.esTreeNodeToTSNodeMap).toEqual( + expect.any(WeakMap), + ); + expect(noOptionSet.services.tsNodeToESTreeNodeMap).toEqual( + expect.any(WeakMap), + ); const withProjectNoOptionSet = parser.parseAndGenerateServices( code, @@ -195,55 +199,56 @@ describe('parse()', () => { ); }); - it('should preserve node maps for parseAndGenerateServices() when option is `true`, regardless of `project` config', () => { - const optionSetToTrue = parser.parseAndGenerateServices(code, { - ...baseConfig, - preserveNodeMaps: true, - }); - - expect(optionSetToTrue.services.esTreeNodeToTSNodeMap).toEqual( - expect.any(WeakMap), - ); - expect(optionSetToTrue.services.tsNodeToESTreeNodeMap).toEqual( - expect.any(WeakMap), - ); + function checkNodeMaps(setting: boolean): void { + it('without project', () => { + const parseResult = parser.parseAndGenerateServices(code, { + ...baseConfig, + preserveNodeMaps: setting, + }); - const withProjectOptionSetToTrue = parser.parseAndGenerateServices(code, { - ...projectConfig, - preserveNodeMaps: true, + expect(parseResult.services.esTreeNodeToTSNodeMap).toBeDefined(); + expect(parseResult.services.tsNodeToESTreeNodeMap).toBeDefined(); + expect( + parseResult.services.esTreeNodeToTSNodeMap.has( + parseResult.ast.body[0], + ), + ).toBe(setting); + expect( + parseResult.services.tsNodeToESTreeNodeMap.has( + parseResult.services.program.getSourceFile('estree.ts'), + ), + ).toBe(setting); }); - expect(withProjectOptionSetToTrue.services.esTreeNodeToTSNodeMap).toEqual( - expect.any(WeakMap), - ); - expect(withProjectOptionSetToTrue.services.tsNodeToESTreeNodeMap).toEqual( - expect.any(WeakMap), - ); - }); + it('with project', () => { + const parseResult = parser.parseAndGenerateServices(code, { + ...projectConfig, + preserveNodeMaps: setting, + }); - it('should not preserve node maps for parseAndGenerateServices() when option is `false`, regardless of `project` config', () => { - const optionSetToFalse = parser.parseAndGenerateServices(code, { - ...baseConfig, - preserveNodeMaps: false, + expect(parseResult.services.esTreeNodeToTSNodeMap).toBeDefined(); + expect(parseResult.services.tsNodeToESTreeNodeMap).toBeDefined(); + expect( + parseResult.services.esTreeNodeToTSNodeMap.has( + parseResult.ast.body[0], + ), + ).toBe(setting); + expect( + parseResult.services.tsNodeToESTreeNodeMap.has( + parseResult.services.program.getSourceFile( + join(FIXTURES_DIR, 'file.ts'), + ), + ), + ).toBe(setting); }); + } - expect(optionSetToFalse.services.esTreeNodeToTSNodeMap).toBeUndefined(); - expect(optionSetToFalse.services.tsNodeToESTreeNodeMap).toBeUndefined(); - - const withProjectOptionSetToFalse = parser.parseAndGenerateServices( - code, - { - ...projectConfig, - preserveNodeMaps: false, - }, - ); + describe('should preserve node maps for parseAndGenerateServices() when option is `true`, regardless of `project` config', () => { + checkNodeMaps(true); + }); - expect( - withProjectOptionSetToFalse.services.esTreeNodeToTSNodeMap, - ).toBeUndefined(); - expect( - withProjectOptionSetToFalse.services.tsNodeToESTreeNodeMap, - ).toBeUndefined(); + describe('should not preserve node maps for parseAndGenerateServices() when option is `false`, regardless of `project` config', () => { + checkNodeMaps(false); }); }); @@ -274,7 +279,9 @@ describe('parse()', () => { it(`should parse ${ext} file - ${ jsxContent ? 'with' : 'without' } JSX content - parserOptions.jsx = ${jsxSetting}`, () => { - let result; + let result: + | parser.ParseAndGenerateServicesResult + | undefined; const exp = expect(() => { result = parser.parseAndGenerateServices(code, { ...config, @@ -289,7 +296,16 @@ describe('parse()', () => { } if (!shouldThrow) { - expect(result).toMatchSnapshot(); + expect(result?.services.program).toBeDefined(); + expect(result?.ast).toBeDefined(); + expect({ + ...result, + services: { + ...result?.services, + // Reduce noise in snapshot + program: {}, + }, + }).toMatchSnapshot(); } }); }; diff --git a/packages/typescript-estree/tests/lib/semanticInfo.ts b/packages/typescript-estree/tests/lib/semanticInfo.ts index ba617b57744..ee9f4c9150d 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.ts @@ -151,7 +151,7 @@ describe('semanticInfo', () => { expect(parseResult).toHaveProperty('services.esTreeNodeToTSNodeMap'); const binaryExpression = (parseResult.ast .body[0] as TSESTree.VariableDeclaration).declarations[0].init!; - const tsBinaryExpression = parseResult.services.esTreeNodeToTSNodeMap!.get( + const tsBinaryExpression = parseResult.services.esTreeNodeToTSNodeMap.get( binaryExpression, ); expect(tsBinaryExpression.kind).toEqual(ts.SyntaxKind.BinaryExpression); @@ -159,7 +159,7 @@ describe('semanticInfo', () => { const computedPropertyString = ((parseResult.ast .body[1] as TSESTree.ClassDeclaration).body .body[0] as TSESTree.ClassProperty).key; - const tsComputedPropertyString = parseResult.services.esTreeNodeToTSNodeMap!.get( + const tsComputedPropertyString = parseResult.services.esTreeNodeToTSNodeMap.get( computedPropertyString, ); expect(tsComputedPropertyString.kind).toEqual(ts.SyntaxKind.StringLiteral); @@ -174,7 +174,7 @@ describe('semanticInfo', () => { // get type checker expect(parseResult).toHaveProperty('services.program.getTypeChecker'); - const checker = parseResult.services.program!.getTypeChecker(); + const checker = parseResult.services.program.getTypeChecker(); // get array node (ast shape validated by snapshot) // node is defined in other file than the parsed one @@ -185,14 +185,14 @@ describe('semanticInfo', () => { expect(arrayBoundName.name).toBe('arr'); expect(parseResult).toHaveProperty('services.esTreeNodeToTSNodeMap'); - const tsArrayBoundName = parseResult.services.esTreeNodeToTSNodeMap!.get( + const tsArrayBoundName = parseResult.services.esTreeNodeToTSNodeMap.get( arrayBoundName, ); expect(tsArrayBoundName).toBeDefined(); checkNumberArrayType(checker, tsArrayBoundName); expect( - parseResult.services.tsNodeToESTreeNodeMap!.get(tsArrayBoundName), + parseResult.services.tsNodeToESTreeNodeMap.get(tsArrayBoundName), ).toBe(arrayBoundName); }); @@ -206,19 +206,19 @@ describe('semanticInfo', () => { }, ); - expect(parseResult.services.program).toBeUndefined(); + expect(parseResult.services.program).toBeDefined(); // get bound name const boundName = (parseResult.ast.body[0] as TSESTree.VariableDeclaration) .declarations[0].id as TSESTree.Identifier; expect(boundName.name).toBe('x'); - const tsBoundName = parseResult.services.esTreeNodeToTSNodeMap!.get( + const tsBoundName = parseResult.services.esTreeNodeToTSNodeMap.get( boundName, ); expect(tsBoundName).toBeDefined(); - expect(parseResult.services.tsNodeToESTreeNodeMap!.get(tsBoundName)).toBe( + expect(parseResult.services.tsNodeToESTreeNodeMap.get(tsBoundName)).toBe( boundName, ); }); @@ -229,7 +229,7 @@ describe('semanticInfo', () => { { ...createOptions(''), project: undefined }, ); - expect(parseResult.services.program).toBeUndefined(); + expect(parseResult.services.program).toBeDefined(); }); it(`non-existent file should throw error when project provided`, () => { @@ -286,7 +286,7 @@ function testIsolatedFile( ): void { // get type checker expect(parseResult).toHaveProperty('services.program.getTypeChecker'); - const checker = parseResult.services.program!.getTypeChecker(); + const checker = parseResult.services.program.getTypeChecker(); // get number node (ast shape validated by snapshot) const declaration = (parseResult.ast.body[0] as TSESTree.VariableDeclaration) @@ -296,7 +296,7 @@ function testIsolatedFile( expect(parseResult).toHaveProperty('services.esTreeNodeToTSNodeMap'); // get corresponding TS node - const tsArrayMember = parseResult.services.esTreeNodeToTSNodeMap!.get( + const tsArrayMember = parseResult.services.esTreeNodeToTSNodeMap.get( arrayMember, ); expect(tsArrayMember).toBeDefined(); @@ -312,19 +312,17 @@ function testIsolatedFile( // make sure it maps back to original ESTree node expect(parseResult).toHaveProperty('services.tsNodeToESTreeNodeMap'); - expect(parseResult.services.tsNodeToESTreeNodeMap!.get(tsArrayMember)).toBe( + expect(parseResult.services.tsNodeToESTreeNodeMap.get(tsArrayMember)).toBe( arrayMember, ); // get bound name const boundName = declaration.id as TSESTree.Identifier; expect(boundName.name).toBe('x'); - const tsBoundName = parseResult.services.esTreeNodeToTSNodeMap!.get( - boundName, - ); + const tsBoundName = parseResult.services.esTreeNodeToTSNodeMap.get(boundName); expect(tsBoundName).toBeDefined(); checkNumberArrayType(checker, tsBoundName); - expect(parseResult.services.tsNodeToESTreeNodeMap!.get(tsBoundName)).toBe( + expect(parseResult.services.tsNodeToESTreeNodeMap.get(tsBoundName)).toBe( boundName, ); }