diff --git a/docs/Custom_Rules.mdx b/docs/Custom_Rules.mdx index 0590398b818..a635d93c989 100644 --- a/docs/Custom_Rules.mdx +++ b/docs/Custom_Rules.mdx @@ -210,17 +210,23 @@ Read TypeScript's [Compiler APIs > Using the Type Checker](https://github.com/mi The biggest addition typescript-eslint brings to ESLint rules is the ability to use TypeScript's type checker APIs. -`@typescript-eslint/utils` exports an `ESLintUtils` namespace containing a `getParserServices` function that takes in an ESLint context and returns a `parserServices` object. +`@typescript-eslint/utils` exports an `ESLintUtils` namespace containing a `getParserServices` function that takes in an ESLint context and returns a `services` object. -That `parserServices` object contains: +That `services` object contains: -- `program`: A full TypeScript `ts.Program` object +- `program`: A full TypeScript `ts.Program` object if type checking is enabled, or `null` otherwise - `esTreeNodeToTSNodeMap`: Map of `@typescript-eslint/estree` `TSESTree.Node` nodes to their TypeScript `ts.Node` equivalents - `tsNodeToESTreeNodeMap`: Map of TypeScript `ts.Node` nodes to their `@typescript-eslint/estree` `TSESTree.Node` equivalents -By mapping from ESTree nodes to TypeScript nodes and retrieving the TypeScript program from the parser services, rules are able to ask TypeScript for full type information on those nodes. +If type checking is enabled, that `services` object additionally contains: -This rule bans for-of looping over an enum by using the type-checker via typescript-eslint and TypeScript APIs: +- `getTypeAtLocation`: Wraps the type checker function, with a `TSESTree.Node` parameter instead of a `ts.Node` +- `getSymbolAtLocation`: Wraps the type checker function, with a `TSESTree.Node` parameter instead of a `ts.Node` + +Those additional objects internally map from ESTree nodes to their TypeScript equivalents, then call to the TypeScript program. +By using the TypeScript program from the parser services, rules are able to ask TypeScript for full type information on those nodes. + +This rule bans for-of looping over an enum by using the TypeScript type checker via typescript-eslint's services: ```ts import { ESLintUtils } from '@typescript-eslint/utils'; @@ -231,17 +237,13 @@ export const rule = createRule({ create(context) { return { ForOfStatement(node) { - // 1. Grab the TypeScript program from parser services - const parserServices = ESLintUtils.getParserServices(context); - const checker = parserServices.program.getTypeChecker(); + // 1. Grab the parser services for the rule + const services = ESLintUtils.getParserServices(context); - // 2. Find the backing TS node for the ES node, then that TS type - const originalNode = parserServices.esTreeNodeToTSNodeMap.get( - node.right, - ); - const nodeType = checker.getTypeAtLocation(originalNode); + // 2. Find the TS type for the ES node + const type = services.getTypeAtLocation(node); - // 3. Check the TS node type using the TypeScript APIs + // 3. Check the TS type using the TypeScript APIs if (tsutils.isTypeFlagSet(nodeType, ts.TypeFlags.EnumLike)) { context.report({ messageId: 'loopOverEnum', @@ -267,6 +269,11 @@ export const rule = createRule({ }); ``` +:::note +Rules can retrieve their full backing TypeScript type checker with `services.program.getTypeChecker()`. +This can be necessary for TypeScript APIs not wrapped by the parser services. +::: + ## Testing `@typescript-eslint/utils` exports a `RuleTester` with a similar API to the built-in [ESLint `RuleTester`](https://eslint.org/docs/developer-guide/nodejs-api#ruletester). diff --git a/packages/eslint-plugin-internal/src/rules/no-poorly-typed-ts-props.ts b/packages/eslint-plugin-internal/src/rules/no-poorly-typed-ts-props.ts index 0c671d8432f..a3b4a416021 100644 --- a/packages/eslint-plugin-internal/src/rules/no-poorly-typed-ts-props.ts +++ b/packages/eslint-plugin-internal/src/rules/no-poorly-typed-ts-props.ts @@ -51,9 +51,7 @@ export default createRule({ }, defaultOptions: [], create(context) { - const { program, esTreeNodeToTSNodeMap } = - ESLintUtils.getParserServices(context); - const checker = program.getTypeChecker(); + const services = ESLintUtils.getParserServices(context); return { 'MemberExpression[computed = false]'( @@ -65,15 +63,13 @@ export default createRule({ } // make sure the type name matches - const tsObjectNode = esTreeNodeToTSNodeMap.get(node.object); - const objectType = checker.getTypeAtLocation(tsObjectNode); + const objectType = services.getTypeAtLocation(node.object); const objectSymbol = objectType.getSymbol(); if (objectSymbol?.getName() !== banned.type) { continue; } - const tsNode = esTreeNodeToTSNodeMap.get(node.property); - const symbol = checker.getSymbolAtLocation(tsNode); + const symbol = services.getSymbolAtLocation(node.property); const decls = symbol?.getDeclarations(); const isFromTs = decls?.some(decl => decl.getSourceFile().fileName.includes('/node_modules/typescript/'), diff --git a/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts b/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts index 313a32eea5b..f9d36b29de9 100644 --- a/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts +++ b/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts @@ -148,9 +148,8 @@ export default createRule({ ], create(context, [{ formatWithPrettier }]) { const sourceCode = context.getSourceCode(); - const { program, esTreeNodeToTSNodeMap } = - ESLintUtils.getParserServices(context); - const checker = program.getTypeChecker(); + const services = ESLintUtils.getParserServices(context); + const checker = services.program.getTypeChecker(); const checkedObjects = new Set(); @@ -522,7 +521,7 @@ export default createRule({ const type = getContextualType( checker, - esTreeNodeToTSNodeMap.get(node), + services.esTreeNodeToTSNodeMap.get(node), ); if (!type) { return; diff --git a/packages/eslint-plugin-tslint/src/rules/config.ts b/packages/eslint-plugin-tslint/src/rules/config.ts index 97b77e7d080..77844d87932 100644 --- a/packages/eslint-plugin-tslint/src/rules/config.ts +++ b/packages/eslint-plugin-tslint/src/rules/config.ts @@ -104,8 +104,8 @@ export default createRule({ ) { const fileName = context.getFilename(); const sourceCode = context.getSourceCode().text; - const parserServices = ESLintUtils.getParserServices(context); - const program = parserServices.program; + const services = ESLintUtils.getParserServices(context); + const program = services.program; /** * Create an instance of TSLint diff --git a/packages/eslint-plugin/src/rules/await-thenable.ts b/packages/eslint-plugin/src/rules/await-thenable.ts index ab9f97da7c2..2e41aab2ea1 100644 --- a/packages/eslint-plugin/src/rules/await-thenable.ts +++ b/packages/eslint-plugin/src/rules/await-thenable.ts @@ -19,19 +19,19 @@ export default util.createRule({ defaultOptions: [], create(context) { - const parserServices = util.getParserServices(context); - const checker = parserServices.program.getTypeChecker(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); return { AwaitExpression(node): void { - const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node); - const type = checker.getTypeAtLocation(originalNode.expression); + const type = services.getTypeAtLocation(node.argument); + if (util.isTypeAnyType(type) || util.isTypeUnknownType(type)) { + return; + } + + const originalNode = services.esTreeNodeToTSNodeMap.get(node); - if ( - !util.isTypeAnyType(type) && - !util.isTypeUnknownType(type) && - !tsutils.isThenableType(checker, originalNode.expression, type) - ) { + if (!tsutils.isThenableType(checker, originalNode.expression, type)) { context.report({ messageId: 'await', node, diff --git a/packages/eslint-plugin/src/rules/consistent-type-exports.ts b/packages/eslint-plugin/src/rules/consistent-type-exports.ts index bb9e0c36e4a..576b59eac5d 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-exports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-exports.ts @@ -69,7 +69,7 @@ export default util.createRule({ create(context, [{ fixMixedExportsWithInlineTypeSpecifier }]) { const sourceCode = context.getSourceCode(); const sourceExportsMap: { [key: string]: SourceExports } = {}; - const parserServices = util.getParserServices(context); + const services = util.getParserServices(context); /** * Helper for identifying if an export specifier resolves to a @@ -81,9 +81,8 @@ export default util.createRule({ function isSpecifierTypeBased( specifier: TSESTree.ExportSpecifier, ): boolean | undefined { - const checker = parserServices.program.getTypeChecker(); - const node = parserServices.esTreeNodeToTSNodeMap.get(specifier.exported); - const symbol = checker.getSymbolAtLocation(node); + const checker = services.program.getTypeChecker(); + const symbol = services.getSymbolAtLocation(specifier.exported); const aliasedSymbol = checker.getAliasedSymbol(symbol!); if (!aliasedSymbol || aliasedSymbol.escapedName === 'unknown') { diff --git a/packages/eslint-plugin/src/rules/dot-notation.ts b/packages/eslint-plugin/src/rules/dot-notation.ts index 9f0b0d1304b..b94f6bc68e6 100644 --- a/packages/eslint-plugin/src/rules/dot-notation.ts +++ b/packages/eslint-plugin/src/rules/dot-notation.ts @@ -67,9 +67,7 @@ export default createRule({ ], create(context, [options]) { const rules = baseRule.create(context); - - const { program, esTreeNodeToTSNodeMap } = getParserServices(context); - const typeChecker = program.getTypeChecker(); + const services = getParserServices(context); const allowPrivateClassPropertyAccess = options.allowPrivateClassPropertyAccess; @@ -78,7 +76,7 @@ export default createRule({ const allowIndexSignaturePropertyAccess = (options.allowIndexSignaturePropertyAccess ?? false) || tsutils.isCompilerOptionEnabled( - program.getCompilerOptions(), + services.program.getCompilerOptions(), // @ts-expect-error - TS is refining the type to never for some reason 'noPropertyAccessFromIndexSignature', ); @@ -92,9 +90,7 @@ export default createRule({ node.computed ) { // for perf reasons - only fetch symbols if we have to - const propertySymbol = typeChecker.getSymbolAtLocation( - esTreeNodeToTSNodeMap.get(node.property), - ); + const propertySymbol = services.getSymbolAtLocation(node.property); const modifierKind = getModifiers( propertySymbol?.getDeclarations()?.[0], )?.[0].kind; @@ -110,9 +106,7 @@ export default createRule({ propertySymbol === undefined && allowIndexSignaturePropertyAccess ) { - const objectType = typeChecker.getTypeAtLocation( - esTreeNodeToTSNodeMap.get(node.object), - ); + const objectType = services.getTypeAtLocation(node.object); const indexType = objectType .getNonNullableType() .getStringIndexType(); diff --git a/packages/eslint-plugin/src/rules/naming-convention-utils/validator.ts b/packages/eslint-plugin/src/rules/naming-convention-utils/validator.ts index c2b87ccc33b..78fb3ca5170 100644 --- a/packages/eslint-plugin/src/rules/naming-convention-utils/validator.ts +++ b/packages/eslint-plugin/src/rules/naming-convention-utils/validator.ts @@ -435,11 +435,10 @@ function isCorrectType( return true; } - const { esTreeNodeToTSNodeMap, program } = util.getParserServices(context); - const checker = program.getTypeChecker(); - const tsNode = esTreeNodeToTSNodeMap.get(node); - const type = checker - .getTypeAtLocation(tsNode) + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); + const type = services + .getTypeAtLocation(node) // remove null and undefined from the type, as we don't care about it here .getNonNullableType(); diff --git a/packages/eslint-plugin/src/rules/no-base-to-string.ts b/packages/eslint-plugin/src/rules/no-base-to-string.ts index e2eb948b271..eedb18b516e 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -52,8 +52,8 @@ export default util.createRule({ }, ], create(context, [option]) { - const parserServices = util.getParserServices(context); - const typeChecker = parserServices.program.getTypeChecker(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); const ignoredTypeNames = option.ignoredTypeNames ?? []; function checkExpression(node: TSESTree.Expression, type?: ts.Type): void { @@ -62,10 +62,7 @@ export default util.createRule({ } const certainty = collectToStringCertainty( - type ?? - typeChecker.getTypeAtLocation( - parserServices.esTreeNodeToTSNodeMap.get(node), - ), + type ?? services.getTypeAtLocation(node), ); if (certainty === Usefulness.Always) { return; @@ -82,7 +79,7 @@ export default util.createRule({ } function collectToStringCertainty(type: ts.Type): Usefulness { - const toString = typeChecker.getPropertyOfType(type, 'toString'); + const toString = checker.getPropertyOfType(type, 'toString'); const declarations = toString?.getDeclarations(); if (!toString || !declarations || declarations.length === 0) { return Usefulness.Always; @@ -96,7 +93,7 @@ export default util.createRule({ return Usefulness.Always; } - if (ignoredTypeNames.includes(util.getTypeName(typeChecker, type))) { + if (ignoredTypeNames.includes(util.getTypeName(checker, type))) { return Usefulness.Always; } @@ -155,17 +152,13 @@ export default util.createRule({ 'AssignmentExpression[operator = "+="], BinaryExpression[operator = "+"]'( node: TSESTree.AssignmentExpression | TSESTree.BinaryExpression, ): void { - const leftType = typeChecker.getTypeAtLocation( - parserServices.esTreeNodeToTSNodeMap.get(node.left), - ); - const rightType = typeChecker.getTypeAtLocation( - parserServices.esTreeNodeToTSNodeMap.get(node.right), - ); - - if (util.getTypeName(typeChecker, leftType) === 'string') { + const leftType = services.getTypeAtLocation(node.left); + const rightType = services.getTypeAtLocation(node.right); + + if (util.getTypeName(checker, leftType) === 'string') { checkExpression(node.right, rightType); } else if ( - util.getTypeName(typeChecker, rightType) === 'string' && + util.getTypeName(checker, rightType) === 'string' && node.left.type !== AST_NODE_TYPES.PrivateIdentifier ) { checkExpression(node.left, leftType); diff --git a/packages/eslint-plugin/src/rules/no-confusing-void-expression.ts b/packages/eslint-plugin/src/rules/no-confusing-void-expression.ts index 32cc048d6a5..a54bd46a80a 100644 --- a/packages/eslint-plugin/src/rules/no-confusing-void-expression.ts +++ b/packages/eslint-plugin/src/rules/no-confusing-void-expression.ts @@ -80,10 +80,8 @@ export default util.createRule({ | TSESTree.CallExpression | TSESTree.TaggedTemplateExpression, ): void { - const parserServices = util.getParserServices(context); - const checker = parserServices.program.getTypeChecker(); - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); - const type = util.getConstrainedTypeAtLocation(checker, tsNode); + const services = util.getParserServices(context); + const type = util.getConstrainedTypeAtLocation(services, node); if (!tsutils.isTypeFlagSet(type, ts.TypeFlags.VoidLike)) { // not a void expression return; diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index c4ce3db8e1c..d6e61e458d5 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -65,8 +65,8 @@ export default util.createRule({ ], create(context, [options]) { - const parserServices = util.getParserServices(context); - const checker = parserServices.program.getTypeChecker(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); return { ExpressionStatement(node): void { @@ -89,7 +89,7 @@ export default util.createRule({ { messageId: 'floatingFixVoid', fix(fixer): TSESLint.RuleFix | TSESLint.RuleFix[] { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get( + const tsNode = services.esTreeNodeToTSNodeMap.get( node.expression, ); if (isHigherPrecedenceThanUnary(tsNode)) { @@ -124,7 +124,7 @@ export default util.createRule({ 'await', ); } - const tsNode = parserServices.esTreeNodeToTSNodeMap.get( + const tsNode = services.esTreeNodeToTSNodeMap.get( node.expression, ); if (isHigherPrecedenceThanUnary(tsNode)) { @@ -191,9 +191,7 @@ export default util.createRule({ } // Check the type. At this point it can't be unhandled if it isn't a promise - if ( - !isPromiseLike(checker, parserServices.esTreeNodeToTSNodeMap.get(node)) - ) { + if (!isPromiseLike(checker, services.esTreeNodeToTSNodeMap.get(node))) { return false; } diff --git a/packages/eslint-plugin/src/rules/no-for-in-array.ts b/packages/eslint-plugin/src/rules/no-for-in-array.ts index 34590d4e569..927939b8598 100644 --- a/packages/eslint-plugin/src/rules/no-for-in-array.ts +++ b/packages/eslint-plugin/src/rules/no-for-in-array.ts @@ -21,14 +21,10 @@ export default util.createRule({ create(context) { return { ForInStatement(node): void { - const parserServices = util.getParserServices(context); - const checker = parserServices.program.getTypeChecker(); - const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); - const type = util.getConstrainedTypeAtLocation( - checker, - originalNode.expression, - ); + const type = util.getConstrainedTypeAtLocation(services, node.right); if ( util.isTypeArrayTypeOrUnionOfArrayTypes(type, checker) || diff --git a/packages/eslint-plugin/src/rules/no-implied-eval.ts b/packages/eslint-plugin/src/rules/no-implied-eval.ts index d88cd05ff6f..c1cdcfbbd21 100644 --- a/packages/eslint-plugin/src/rules/no-implied-eval.ts +++ b/packages/eslint-plugin/src/rules/no-implied-eval.ts @@ -33,9 +33,8 @@ export default util.createRule({ }, defaultOptions: [], create(context) { - const parserServices = util.getParserServices(context); - const program = parserServices.program; - const checker = parserServices.program.getTypeChecker(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); function getCalleeName( node: TSESTree.LeftHandSideExpression, @@ -65,8 +64,7 @@ export default util.createRule({ } function isFunctionType(node: TSESTree.Node): boolean { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); - const type = checker.getTypeAtLocation(tsNode); + const type = services.getTypeAtLocation(node); const symbol = type.getSymbol(); if ( @@ -83,7 +81,7 @@ export default util.createRule({ const declarations = symbol.getDeclarations() ?? []; for (const declaration of declarations) { const sourceFile = declaration.getSourceFile(); - if (program.isSourceFileDefaultLibrary(sourceFile)) { + if (services.program.isSourceFileDefaultLibrary(sourceFile)) { return true; } } @@ -140,14 +138,13 @@ export default util.createRule({ } if (calleeName === FUNCTION_CONSTRUCTOR) { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node.callee); - const type = checker.getTypeAtLocation(tsNode); + const type = services.getTypeAtLocation(node.callee); const symbol = type.getSymbol(); if (symbol) { const declarations = symbol.getDeclarations() ?? []; for (const declaration of declarations) { const sourceFile = declaration.getSourceFile(); - if (program.isSourceFileDefaultLibrary(sourceFile)) { + if (services.program.isSourceFileDefaultLibrary(sourceFile)) { context.report({ node, messageId: 'noFunctionConstructor' }); return; } diff --git a/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts b/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts index 79d0611e5d0..c585b13f785 100644 --- a/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts +++ b/packages/eslint-plugin/src/rules/no-meaningless-void-operator.ts @@ -47,8 +47,8 @@ export default util.createRule< defaultOptions: [{ checkNever: false }], create(context, [{ checkNever }]) { - const parserServices = ESLintUtils.getParserServices(context); - const checker = parserServices.program.getTypeChecker(); + const services = ESLintUtils.getParserServices(context); + const checker = services.program.getTypeChecker(); const sourceCode = context.getSourceCode(); return { @@ -60,10 +60,7 @@ export default util.createRule< ]); }; - const argTsNode = parserServices.esTreeNodeToTSNodeMap.get( - node.argument, - ); - const argType = checker.getTypeAtLocation(argTsNode); + const argType = services.getTypeAtLocation(node.argument); const unionParts = tsutils.unionTypeParts(argType); if ( unionParts.every( diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 15bf0c501d1..9d0e3b4a639 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -120,8 +120,8 @@ export default util.createRule({ ], create(context, [{ checksConditionals, checksVoidReturn, checksSpreads }]) { - const parserServices = util.getParserServices(context); - const checker = parserServices.program.getTypeChecker(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); const checkedNodes = new Set(); @@ -200,7 +200,7 @@ export default util.createRule({ } return; } - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const tsNode = services.esTreeNodeToTSNodeMap.get(node); if (isAlwaysThenable(checker, tsNode)) { context.report({ messageId: 'conditional', @@ -212,7 +212,7 @@ export default util.createRule({ function checkArguments( node: TSESTree.CallExpression | TSESTree.NewExpression, ): void { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const tsNode = services.esTreeNodeToTSNodeMap.get(node); const voidArgs = voidFunctionArguments(checker, tsNode); if (voidArgs.size === 0) { return; @@ -223,7 +223,7 @@ export default util.createRule({ continue; } - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(argument); + const tsNode = services.esTreeNodeToTSNodeMap.get(argument); if (returnsThenable(checker, tsNode as ts.Expression)) { context.report({ messageId: 'voidReturnArgument', @@ -234,8 +234,8 @@ export default util.createRule({ } function checkAssignment(node: TSESTree.AssignmentExpression): void { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); - const varType = checker.getTypeAtLocation(tsNode.left); + const tsNode = services.esTreeNodeToTSNodeMap.get(node); + const varType = services.getTypeAtLocation(node.left); if (!isVoidReturningFunctionType(checker, tsNode.left, varType)) { return; } @@ -249,11 +249,11 @@ export default util.createRule({ } function checkVariableDeclaration(node: TSESTree.VariableDeclarator): void { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const tsNode = services.esTreeNodeToTSNodeMap.get(node); if (tsNode.initializer === undefined || node.init == null) { return; } - const varType = checker.getTypeAtLocation(tsNode.name); + const varType = services.getTypeAtLocation(node.id); if (!isVoidReturningFunctionType(checker, tsNode.initializer, varType)) { return; } @@ -267,7 +267,7 @@ export default util.createRule({ } function checkProperty(node: TSESTree.Property): void { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const tsNode = services.esTreeNodeToTSNodeMap.get(node); if (ts.isPropertyAssignment(tsNode)) { const contextualType = checker.getContextualType(tsNode.initializer); if ( @@ -343,7 +343,7 @@ export default util.createRule({ } function checkReturnStatement(node: TSESTree.ReturnStatement): void { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const tsNode = services.esTreeNodeToTSNodeMap.get(node); if (tsNode.expression === undefined || node.argument == null) { return; } @@ -365,7 +365,7 @@ export default util.createRule({ } function checkJSXAttribute(node: TSESTree.JSXAttribute): void { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const tsNode = services.esTreeNodeToTSNodeMap.get(node); const value = tsNode.initializer; if ( node.value == null || @@ -389,7 +389,7 @@ export default util.createRule({ } function checkSpread(node: TSESTree.SpreadElement): void { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const tsNode = services.esTreeNodeToTSNodeMap.get(node); if (isSometimesThenable(checker, tsNode.expression)) { context.report({ diff --git a/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts b/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts index 0055c5fe3f6..db015edc839 100644 --- a/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts +++ b/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts @@ -192,7 +192,7 @@ export default util.createRule({ }, defaultOptions: [], create(context) { - const parserServices = util.getParserServices(context); + const services = util.getParserServices(context); const typesCache = new Map(); function getTypeNodeTypePartFlags( @@ -228,9 +228,7 @@ export default util.createRule({ return typeNode.types.flatMap(getTypeNodeTypePartFlags); } - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(typeNode); - const checker = parserServices.program.getTypeChecker(); - const nodeType = checker.getTypeAtLocation(tsNode); + const nodeType = services.getTypeAtLocation(typeNode); const typeParts = unionTypePartsUnlessBoolean(nodeType); return typeParts.map(typePart => ({ diff --git a/packages/eslint-plugin/src/rules/no-throw-literal.ts b/packages/eslint-plugin/src/rules/no-throw-literal.ts index 9f79ea0ff47..55145507d65 100644 --- a/packages/eslint-plugin/src/rules/no-throw-literal.ts +++ b/packages/eslint-plugin/src/rules/no-throw-literal.ts @@ -49,9 +49,8 @@ export default util.createRule({ }, ], create(context, [options]) { - const parserServices = util.getParserServices(context); - const program = parserServices.program; - const checker = program.getTypeChecker(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); function isErrorLike(type: ts.Type): boolean { if (type.isIntersection()) { @@ -70,7 +69,7 @@ export default util.createRule({ const declarations = symbol.getDeclarations() ?? []; for (const declaration of declarations) { const sourceFile = declaration.getSourceFile(); - if (program.isSourceFileDefaultLibrary(sourceFile)) { + if (services.program.isSourceFileDefaultLibrary(sourceFile)) { return true; } } @@ -95,8 +94,7 @@ export default util.createRule({ return; } - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); - const type = checker.getTypeAtLocation(tsNode); + const type = services.getTypeAtLocation(node); if (type.flags & ts.TypeFlags.Undefined) { context.report({ node, messageId: 'undef' }); diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts b/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts index f874905b738..97b05a210ac 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts @@ -80,8 +80,7 @@ export default util.createRule({ }, ], create(context, [options]) { - const parserServices = util.getParserServices(context); - const checker = parserServices.program.getTypeChecker(); + const services = util.getParserServices(context); function getBooleanComparison( node: TSESTree.BinaryExpression, @@ -91,9 +90,7 @@ export default util.createRule({ return undefined; } - const expressionType = checker.getTypeAtLocation( - parserServices.esTreeNodeToTSNodeMap.get(comparison.expression), - ); + const expressionType = services.getTypeAtLocation(comparison.expression); if (isBooleanType(expressionType)) { return { diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 42f12748af9..e3a9e95afbe 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -146,10 +146,10 @@ export default createRule({ }, ], ) { - const service = getParserServices(context); - const checker = service.program.getTypeChecker(); + const services = getParserServices(context); + const checker = services.program.getTypeChecker(); const sourceCode = context.getSourceCode(); - const compilerOptions = service.program.getCompilerOptions(); + const compilerOptions = services.program.getCompilerOptions(); const isStrictNullChecks = isStrictCompilerOptionEnabled( compilerOptions, 'strictNullChecks', @@ -168,17 +168,12 @@ export default createRule({ }); } - function getNodeType(node: TSESTree.Node): ts.Type { - const tsNode = service.esTreeNodeToTSNodeMap.get(node); - return getConstrainedTypeAtLocation(checker, tsNode); - } - function nodeIsArrayType(node: TSESTree.Expression): boolean { - const nodeType = getNodeType(node); + const nodeType = getConstrainedTypeAtLocation(services, node); return checker.isArrayType(nodeType); } function nodeIsTupleType(node: TSESTree.Expression): boolean { - const nodeType = getNodeType(node); + const nodeType = getConstrainedTypeAtLocation(services, node); return checker.isTupleType(nodeType); } @@ -232,7 +227,7 @@ export default createRule({ return checkNode(node.right); } - const type = getNodeType(node); + const type = getConstrainedTypeAtLocation(services, node); // Conditional is always necessary if it involves: // `any` or `unknown` or a naked type parameter @@ -262,7 +257,7 @@ export default createRule({ } function checkNodeForNullish(node: TSESTree.Expression): void { - const type = getNodeType(node); + const type = getConstrainedTypeAtLocation(services, node); // Conditional is always necessary if it involves `any` or `unknown` if (isTypeAnyType(type) || isTypeUnknownType(type)) { return; @@ -320,8 +315,8 @@ export default createRule({ if (!BOOL_OPERATORS.has(node.operator)) { return; } - const leftType = getNodeType(node.left); - const rightType = getNodeType(node.right); + const leftType = getConstrainedTypeAtLocation(services, node.left); + const rightType = getConstrainedTypeAtLocation(services, node.right); if (isLiteral(leftType) && isLiteral(rightType)) { context.report({ node, messageId: 'literalBooleanExpression' }); return; @@ -397,7 +392,10 @@ export default createRule({ */ if ( allowConstantLoopConditions && - isBooleanLiteralType(getNodeType(node.test), true) + isBooleanLiteralType( + getConstrainedTypeAtLocation(services, node.test), + true, + ) ) { return; } @@ -451,9 +449,9 @@ export default createRule({ // (Value to complexity ratio is dubious however) } // Otherwise just do type analysis on the function as a whole. - const returnTypes = getCallSignaturesOfType(getNodeType(callback)).map( - sig => sig.getReturnType(), - ); + const returnTypes = getCallSignaturesOfType( + getConstrainedTypeAtLocation(services, callback), + ).map(sig => sig.getReturnType()); /* istanbul ignore if */ if (returnTypes.length === 0) { // Not a callable function return; @@ -541,12 +539,15 @@ export default createRule({ function isNullableOriginFromPrev( node: TSESTree.MemberExpression, ): boolean { - const prevType = getNodeType(node.object); + const prevType = getConstrainedTypeAtLocation(services, node.object); const property = node.property; if (prevType.isUnion() && isIdentifier(property)) { const isOwnNullable = prevType.types.some(type => { if (node.computed) { - const propertyType = getNodeType(node.property); + const propertyType = getConstrainedTypeAtLocation( + services, + node.property, + ); return isNullablePropertyType(type, propertyType); } const propType = getTypeOfPropertyOfName( @@ -569,7 +570,7 @@ export default createRule({ } function isOptionableExpression(node: TSESTree.Expression): boolean { - const type = getNodeType(node); + const type = getConstrainedTypeAtLocation(services, node); const isOwnNullable = node.type === AST_NODE_TYPES.MemberExpression ? !isNullableOriginFromPrev(node) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts index 632ad6c5ba0..4265dc3d24c 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts @@ -25,10 +25,9 @@ export default util.createRule({ create(context) { const namespacesInScope: ts.Node[] = []; let currentFailedNamespaceExpression: TSESTree.Node | null = null; - const parserServices = util.getParserServices(context); - const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap; - const program = parserServices.program; - const checker = program.getTypeChecker(); + const services = util.getParserServices(context); + const esTreeNodeToTSNodeMap = services.esTreeNodeToTSNodeMap; + const checker = services.program.getTypeChecker(); const sourceCode = context.getSourceCode(); function tryGetAliasedSymbol( @@ -61,7 +60,6 @@ export default util.createRule({ flags: ts.SymbolFlags, name: string, ): ts.Symbol | undefined { - // TODO:PERF `getSymbolsInScope` gets a long list. Is there a better way? const scope = checker.getSymbolsInScope(node, flags); return scope.find(scopeSymbol => scopeSymbol.name === name); @@ -75,10 +73,7 @@ export default util.createRule({ qualifier: TSESTree.EntityName | TSESTree.MemberExpression, name: TSESTree.Identifier, ): boolean { - const tsQualifier = esTreeNodeToTSNodeMap.get(qualifier); - const tsName = esTreeNodeToTSNodeMap.get(name); - - const namespaceSymbol = checker.getSymbolAtLocation(tsQualifier); + const namespaceSymbol = services.getSymbolAtLocation(qualifier); if ( typeof namespaceSymbol === 'undefined' || @@ -87,13 +82,14 @@ export default util.createRule({ return false; } - const accessedSymbol = checker.getSymbolAtLocation(tsName); + const accessedSymbol = services.getSymbolAtLocation(name); if (typeof accessedSymbol === 'undefined') { return false; } // If the symbol in scope is different, the qualifier is necessary. + const tsQualifier = esTreeNodeToTSNodeMap.get(qualifier); const fromScope = getSymbolInScope( tsQualifier, accessedSymbol.flags, diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts index 6d300b36fe1..1860e26a8bd 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts @@ -36,8 +36,8 @@ export default util.createRule<[], MessageIds>({ }, defaultOptions: [], create(context) { - const parserServices = util.getParserServices(context); - const checker = parserServices.program.getTypeChecker(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); function getTypeForComparison(type: ts.Type): { type: ts.Type; @@ -69,8 +69,7 @@ export default util.createRule<[], MessageIds>({ // TODO: would like checker.areTypesEquivalent. https://github.com/Microsoft/TypeScript/issues/13502 const defaultType = checker.getTypeAtLocation(param.default); - const argTsNode = parserServices.esTreeNodeToTSNodeMap.get(arg); - const argType = checker.getTypeAtLocation(argTsNode); + const argType = services.getTypeAtLocation(arg); // this check should handle some of the most simple cases of like strings, numbers, etc if (defaultType !== argType) { // For more complex types (like aliases to generic object types) - TS won't always create a @@ -106,7 +105,7 @@ export default util.createRule<[], MessageIds>({ return { TSTypeParameterInstantiation(node): void { - const expression = parserServices.esTreeNodeToTSNodeMap.get(node); + const expression = services.esTreeNodeToTSNodeMap.get(node); const typeParameters = getTypeParametersFromNode(expression, checker); if (typeParameters) { diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index 38248f31123..87ef3b9ba42 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -53,9 +53,9 @@ export default util.createRule({ defaultOptions: [{}], create(context, [options]) { const sourceCode = context.getSourceCode(); - const parserServices = util.getParserServices(context); - const checker = parserServices.program.getTypeChecker(); - const compilerOptions = parserServices.program.getCompilerOptions(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); + const compilerOptions = services.program.getCompilerOptions(); /** * Sometimes tuple types don't have ObjectFlags.Tuple set, like when they're being matched against an inferred type. @@ -91,8 +91,8 @@ export default util.createRule({ /** * Returns true if there's a chance the variable has been used before a value has been assigned to it */ - function isPossiblyUsedBeforeAssigned(node: ts.Expression): boolean { - const declaration = util.getDeclaration(checker, node); + function isPossiblyUsedBeforeAssigned(node: TSESTree.Expression): boolean { + const declaration = util.getDeclaration(services, node); if (!declaration) { // don't know what the declaration is for some reason, so just assume the worst return true; @@ -111,7 +111,7 @@ export default util.createRule({ ) { // check if the defined variable type has changed since assignment const declarationType = checker.getTypeFromTypeNode(declaration.type); - const type = util.getConstrainedTypeAtLocation(checker, node); + const type = util.getConstrainedTypeAtLocation(services, node); if (declarationType === type) { // possibly used before assigned, so just skip it // better to false negative and skip it, than false positive and fix to compile erroring code @@ -157,15 +157,15 @@ export default util.createRule({ return; } - const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const originalNode = services.esTreeNodeToTSNodeMap.get(node); const type = util.getConstrainedTypeAtLocation( - checker, - originalNode.expression, + services, + node.expression, ); if (!util.isNullableType(type)) { - if (isPossiblyUsedBeforeAssigned(originalNode.expression)) { + if (isPossiblyUsedBeforeAssigned(node.expression)) { return; } @@ -241,8 +241,7 @@ export default util.createRule({ return; } - const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node); - const castType = checker.getTypeAtLocation(originalNode); + const castType = services.getTypeAtLocation(node); if ( isTypeFlagSet(castType, ts.TypeFlags.Literal) || @@ -255,14 +254,14 @@ export default util.createRule({ return; } - const uncastType = checker.getTypeAtLocation(originalNode.expression); + const uncastType = services.getTypeAtLocation(node.expression); if (uncastType === castType) { context.report({ node, messageId: 'unnecessaryAssertion', fix(fixer) { - if (originalNode.kind === ts.SyntaxKind.TypeAssertionExpression) { + if (node.type === AST_NODE_TYPES.TSTypeAssertion) { const closingAngleBracket = sourceCode.getTokenAfter( node.typeAnnotation, ); diff --git a/packages/eslint-plugin/src/rules/no-unsafe-argument.ts b/packages/eslint-plugin/src/rules/no-unsafe-argument.ts index b5aced4d68c..76fe8652966 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-argument.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-argument.ts @@ -152,8 +152,8 @@ export default util.createRule<[], MessageIds>({ }, defaultOptions: [], create(context) { - const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); - const checker = program.getTypeChecker(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); return { 'CallExpression, NewExpression'( @@ -164,15 +164,11 @@ export default util.createRule<[], MessageIds>({ } // ignore any-typed calls as these are caught by no-unsafe-call - if ( - util.isTypeAnyType( - checker.getTypeAtLocation(esTreeNodeToTSNodeMap.get(node.callee)), - ) - ) { + if (util.isTypeAnyType(services.getTypeAtLocation(node.callee))) { return; } - const tsNode = esTreeNodeToTSNodeMap.get(node); + const tsNode = services.esTreeNodeToTSNodeMap.get(node); const signature = FunctionSignature.create(checker, tsNode); if (!signature) { return; @@ -182,8 +178,8 @@ export default util.createRule<[], MessageIds>({ switch (argument.type) { // spreads consume case AST_NODE_TYPES.SpreadElement: { - const spreadArgType = checker.getTypeAtLocation( - esTreeNodeToTSNodeMap.get(argument.argument), + const spreadArgType = services.getTypeAtLocation( + argument.argument, ); if (util.isTypeAnyType(spreadArgType)) { @@ -247,9 +243,7 @@ export default util.createRule<[], MessageIds>({ continue; } - const argumentType = checker.getTypeAtLocation( - esTreeNodeToTSNodeMap.get(argument), - ); + const argumentType = services.getTypeAtLocation(argument); const result = util.isUnsafeAssignment( argumentType, parameterType, diff --git a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts index 4833d84a84c..01cd90b20b8 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts @@ -42,9 +42,9 @@ export default util.createRule({ }, defaultOptions: [], create(context) { - const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); - const checker = program.getTypeChecker(); - const compilerOptions = program.getCompilerOptions(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); + const compilerOptions = services.program.getCompilerOptions(); const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled( compilerOptions, 'noImplicitThis', @@ -59,8 +59,8 @@ export default util.createRule({ return false; } - const senderTsNode = esTreeNodeToTSNodeMap.get(senderNode); - const senderType = checker.getTypeAtLocation(senderTsNode); + const senderTsNode = services.esTreeNodeToTSNodeMap.get(senderNode); + const senderType = services.getTypeAtLocation(senderNode); return checkArrayDestructure(receiverNode, senderType, senderTsNode); } @@ -145,8 +145,8 @@ export default util.createRule({ return false; } - const senderTsNode = esTreeNodeToTSNodeMap.get(senderNode); - const senderType = checker.getTypeAtLocation(senderTsNode); + const senderTsNode = services.esTreeNodeToTSNodeMap.get(senderNode); + const senderType = services.getTypeAtLocation(senderNode); return checkObjectDestructure(receiverNode, senderType, senderTsNode); } @@ -232,15 +232,13 @@ export default util.createRule({ reportingNode: TSESTree.Node, comparisonType: ComparisonType, ): boolean { - const receiverTsNode = esTreeNodeToTSNodeMap.get(receiverNode); + const receiverTsNode = services.esTreeNodeToTSNodeMap.get(receiverNode); const receiverType = comparisonType === ComparisonType.Contextual ? util.getContextualType(checker, receiverTsNode as ts.Expression) ?? - checker.getTypeAtLocation(receiverTsNode) - : checker.getTypeAtLocation(receiverTsNode); - const senderType = checker.getTypeAtLocation( - esTreeNodeToTSNodeMap.get(senderNode), - ); + services.getTypeAtLocation(receiverNode) + : services.getTypeAtLocation(receiverNode); + const senderType = services.getTypeAtLocation(senderNode); if (util.isTypeAnyType(senderType)) { // handle cases when we assign any ==> unknown. @@ -256,10 +254,7 @@ export default util.createRule({ if ( thisExpression && util.isTypeAnyType( - util.getConstrainedTypeAtLocation( - checker, - esTreeNodeToTSNodeMap.get(thisExpression), - ), + util.getConstrainedTypeAtLocation(services, thisExpression), ) ) { messageId = 'anyAssignmentThis'; @@ -372,8 +367,7 @@ export default util.createRule({ checkAssignment(node.key, node.value, node, ComparisonType.Contextual); }, 'ArrayExpression > SpreadElement'(node: TSESTree.SpreadElement): void { - const resetNode = esTreeNodeToTSNodeMap.get(node.argument); - const restType = checker.getTypeAtLocation(resetNode); + const restType = services.getTypeAtLocation(node.argument); if ( util.isTypeAnyType(restType) || util.isTypeAnyArrayType(restType, checker) diff --git a/packages/eslint-plugin/src/rules/no-unsafe-call.ts b/packages/eslint-plugin/src/rules/no-unsafe-call.ts index dfa6fa2fb4c..216339d26b0 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-call.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-call.ts @@ -32,9 +32,8 @@ export default util.createRule<[], MessageIds>({ }, defaultOptions: [], create(context) { - const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); - const checker = program.getTypeChecker(); - const compilerOptions = program.getCompilerOptions(); + const services = util.getParserServices(context); + const compilerOptions = services.program.getCompilerOptions(); const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled( compilerOptions, 'noImplicitThis', @@ -45,8 +44,7 @@ export default util.createRule<[], MessageIds>({ reportingNode: TSESTree.Node, messageId: MessageIds, ): void { - const tsNode = esTreeNodeToTSNodeMap.get(node); - const type = util.getConstrainedTypeAtLocation(checker, tsNode); + const type = util.getConstrainedTypeAtLocation(services, node); if (util.isTypeAnyType(type)) { if (!isNoImplicitThis) { @@ -55,10 +53,7 @@ export default util.createRule<[], MessageIds>({ if ( thisExpression && util.isTypeAnyType( - util.getConstrainedTypeAtLocation( - checker, - esTreeNodeToTSNodeMap.get(thisExpression), - ), + util.getConstrainedTypeAtLocation(services, thisExpression), ) ) { messageId = 'unsafeCallThis'; diff --git a/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts b/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts index 410ff78f545..a0858af39e4 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts @@ -33,9 +33,8 @@ export default util.createRule({ }, defaultOptions: [], create(context) { - const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); - const checker = program.getTypeChecker(); - const compilerOptions = program.getCompilerOptions(); + const services = util.getParserServices(context); + const compilerOptions = services.program.getCompilerOptions(); const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled( compilerOptions, 'noImplicitThis', @@ -60,8 +59,7 @@ export default util.createRule({ } } - const tsNode = esTreeNodeToTSNodeMap.get(node.object); - const type = checker.getTypeAtLocation(tsNode); + const type = services.getTypeAtLocation(node.object); const state = util.isTypeAnyType(type) ? State.Unsafe : State.Safe; stateCache.set(node, state); @@ -78,10 +76,7 @@ export default util.createRule({ if ( thisExpression && util.isTypeAnyType( - util.getConstrainedTypeAtLocation( - checker, - esTreeNodeToTSNodeMap.get(thisExpression), - ), + util.getConstrainedTypeAtLocation(services, thisExpression), ) ) { messageId = 'unsafeThisMemberExpression'; @@ -119,8 +114,7 @@ export default util.createRule({ return; } - const tsNode = esTreeNodeToTSNodeMap.get(node); - const type = checker.getTypeAtLocation(tsNode); + const type = services.getTypeAtLocation(node); if (util.isTypeAnyType(type)) { const propertyName = sourceCode.getText(node); diff --git a/packages/eslint-plugin/src/rules/no-unsafe-return.ts b/packages/eslint-plugin/src/rules/no-unsafe-return.ts index 63d60ff81f8..7843f25ffa1 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-return.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-return.ts @@ -27,9 +27,9 @@ export default util.createRule({ }, defaultOptions: [], create(context) { - const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); - const checker = program.getTypeChecker(); - const compilerOptions = program.getCompilerOptions(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); + const compilerOptions = services.program.getCompilerOptions(); const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled( compilerOptions, 'noImplicitThis', @@ -64,7 +64,7 @@ export default util.createRule({ returnNode: TSESTree.Node, reportingNode: TSESTree.Node = returnNode, ): void { - const tsNode = esTreeNodeToTSNodeMap.get(returnNode); + const tsNode = services.esTreeNodeToTSNodeMap.get(returnNode); const anyType = util.isAnyOrAnyArrayTypeDiscriminated(tsNode, checker); const functionNode = getParentFunctionNode(returnNode); /* istanbul ignore if */ if (!functionNode) { @@ -73,10 +73,10 @@ export default util.createRule({ // function has an explicit return type, so ensure it's a safe return const returnNodeType = util.getConstrainedTypeAtLocation( - checker, - esTreeNodeToTSNodeMap.get(returnNode), + services, + returnNode, ); - const functionTSNode = esTreeNodeToTSNodeMap.get(functionNode); + const functionTSNode = services.esTreeNodeToTSNodeMap.get(functionNode); // function expressions will not have their return type modified based on receiver typing // so we have to use the contextual typing in these cases, i.e. @@ -84,9 +84,9 @@ export default util.createRule({ // the return type of the arrow function is Set even though the variable is typed as Set let functionType = tsutils.isExpression(functionTSNode) ? util.getContextualType(checker, functionTSNode) - : checker.getTypeAtLocation(functionTSNode); + : services.getTypeAtLocation(functionNode); if (!functionType) { - functionType = checker.getTypeAtLocation(functionTSNode); + functionType = services.getTypeAtLocation(functionNode); } // If there is an explicit type annotation *and* that type matches the actual @@ -126,10 +126,7 @@ export default util.createRule({ if ( thisExpression && util.isTypeAnyType( - util.getConstrainedTypeAtLocation( - checker, - esTreeNodeToTSNodeMap.get(thisExpression), - ), + util.getConstrainedTypeAtLocation(services, thisExpression), ) ) { messageId = 'unsafeReturnThis'; diff --git a/packages/eslint-plugin/src/rules/non-nullable-type-assertion-style.ts b/packages/eslint-plugin/src/rules/non-nullable-type-assertion-style.ts index 4953cf8041e..b42a1c06d40 100644 --- a/packages/eslint-plugin/src/rules/non-nullable-type-assertion-style.ts +++ b/packages/eslint-plugin/src/rules/non-nullable-type-assertion-style.ts @@ -24,14 +24,11 @@ export default util.createRule({ defaultOptions: [], create(context) { - const parserServices = util.getParserServices(context); - const checker = parserServices.program.getTypeChecker(); + const services = util.getParserServices(context); const sourceCode = context.getSourceCode(); const getTypesIfNotLoose = (node: TSESTree.Node): ts.Type[] | undefined => { - const type = checker.getTypeAtLocation( - parserServices.esTreeNodeToTSNodeMap.get(node), - ); + const type = services.getTypeAtLocation(node); if ( tsutils.isTypeFlagSet(type, ts.TypeFlags.Any | ts.TypeFlags.Unknown) diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index 720d5fbe809..c2a921fec0d 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -34,7 +34,7 @@ export default createRule({ create(context) { const globalScope = context.getScope(); const services = getParserServices(context); - const types = services.program.getTypeChecker(); + const checker = services.program.getTypeChecker(); function isNumber(node: TSESTree.Node, value: number): boolean { const evaluated = getStaticValue(node, globalScope); @@ -141,9 +141,8 @@ export default createRule({ } // Get the symbol of `indexOf` method. - const tsNode = services.esTreeNodeToTSNodeMap.get(node.property); - const indexofMethodDeclarations = types - .getSymbolAtLocation(tsNode) + const indexofMethodDeclarations = services + .getSymbolAtLocation(node.property) ?.getDeclarations(); if ( indexofMethodDeclarations == null || @@ -156,7 +155,7 @@ export default createRule({ // and the two methods have the same parameters. for (const instanceofMethodDecl of indexofMethodDeclarations) { const typeDecl = instanceofMethodDecl.parent; - const type = types.getTypeAtLocation(typeDecl); + const type = checker.getTypeAtLocation(typeDecl); const includesMethodDecl = type .getProperty('includes') ?.getDeclarations(); @@ -214,8 +213,7 @@ export default createRule({ //check the argument type of test methods const argument = callNode.arguments[0]; - const tsNode = services.esTreeNodeToTSNodeMap.get(argument); - const type = getConstrainedTypeAtLocation(types, tsNode); + const type = getConstrainedTypeAtLocation(services, argument); const includesMethodDecl = type .getProperty('includes') diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index 2becee5ea02..28ec95e5a73 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -80,10 +80,9 @@ export default util.createRule({ }, ], ) { - const parserServices = util.getParserServices(context); - const compilerOptions = parserServices.program.getCompilerOptions(); + const services = util.getParserServices(context); + const compilerOptions = services.program.getCompilerOptions(); const sourceCode = context.getSourceCode(); - const checker = parserServices.program.getTypeChecker(); const isStrictNullChecks = tsutils.isStrictCompilerOptionEnabled( compilerOptions, 'strictNullChecks', @@ -107,8 +106,7 @@ export default util.createRule({ description: string, equals: string, ): void { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); - const type = checker.getTypeAtLocation(tsNode.left); + const type = services.getTypeAtLocation(node.left); const isNullish = util.isNullableType(type, { allowUndefined: true }); if (!isNullish) { return; @@ -278,8 +276,7 @@ export default util.createRule({ return true; } - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(identifier); - const type = checker.getTypeAtLocation(tsNode); + const type = services.getTypeAtLocation(identifier); const flags = util.getTypeFlags(type); if (flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) { diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts index 5e6a28197c2..95b910e7909 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts @@ -51,7 +51,7 @@ export default util.createRule({ defaultOptions: [], create(context) { const sourceCode = context.getSourceCode(); - const parserServices = util.getParserServices(context, true); + const services = util.getParserServices(context, true); return { 'LogicalExpression[operator="||"], LogicalExpression[operator="??"]'( @@ -73,9 +73,9 @@ export default util.createRule({ } function isLeftSideLowerPrecedence(): boolean { - const logicalTsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const logicalTsNode = services.esTreeNodeToTSNodeMap.get(node); - const leftTsNode = parserServices.esTreeNodeToTSNodeMap.get(leftNode); + const leftTsNode = services.esTreeNodeToTSNodeMap.get(leftNode); const operator = isBinaryExpression(logicalTsNode) ? logicalTsNode.operatorToken.kind : ts.SyntaxKind.Unknown; diff --git a/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts b/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts index 4ba01de52b2..02e249a5f7b 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts @@ -51,8 +51,8 @@ export default util.createRule({ context, [{ checkParameterProperties, ignoreInferredTypes, treatMethodsAsReadonly }], ) { - const { esTreeNodeToTSNodeMap, program } = util.getParserServices(context); - const checker = program.getTypeChecker(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); return { [[ @@ -94,8 +94,7 @@ export default util.createRule({ continue; } - const tsNode = esTreeNodeToTSNodeMap.get(actualParam); - const type = checker.getTypeAtLocation(tsNode); + const type = services.getTypeAtLocation(actualParam); const isReadOnly = util.isTypeReadonly(checker, type, { treatMethodsAsReadonly: treatMethodsAsReadonly!, }); diff --git a/packages/eslint-plugin/src/rules/prefer-readonly.ts b/packages/eslint-plugin/src/rules/prefer-readonly.ts index 3a9e6cdca66..64d6adb5e89 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly.ts @@ -49,8 +49,8 @@ export default util.createRule({ }, defaultOptions: [{ onlyInlineLambdas: false }], create(context, [{ onlyInlineLambdas }]) { - const parserServices = util.getParserServices(context); - const checker = parserServices.program.getTypeChecker(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); const classScopeStack: ClassScope[] = []; function handlePropertyAccessExpression( @@ -146,7 +146,7 @@ export default util.createRule({ return false; } - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const tsNode = services.esTreeNodeToTSNodeMap.get(node); if (ts.isConstructorDeclaration(tsNode)) { return false; } @@ -161,16 +161,14 @@ export default util.createRule({ ts.isParameterPropertyDeclaration(violatingNode, violatingNode.parent) ) { return { - esNode: parserServices.tsNodeToESTreeNodeMap.get(violatingNode.name), - nameNode: parserServices.tsNodeToESTreeNodeMap.get( - violatingNode.name, - ), + esNode: services.tsNodeToESTreeNodeMap.get(violatingNode.name), + nameNode: services.tsNodeToESTreeNodeMap.get(violatingNode.name), }; } return { - esNode: parserServices.tsNodeToESTreeNodeMap.get(violatingNode), - nameNode: parserServices.tsNodeToESTreeNodeMap.get(violatingNode.name), + esNode: services.tsNodeToESTreeNodeMap.get(violatingNode), + nameNode: services.tsNodeToESTreeNodeMap.get(violatingNode.name), }; } @@ -181,7 +179,7 @@ export default util.createRule({ classScopeStack.push( new ClassScope( checker, - parserServices.esTreeNodeToTSNodeMap.get(node), + services.esTreeNodeToTSNodeMap.get(node), onlyInlineLambdas, ), ); @@ -205,7 +203,7 @@ export default util.createRule({ }, MemberExpression(node): void { if (classScopeStack.length !== 0 && !node.computed) { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get( + const tsNode = services.esTreeNodeToTSNodeMap.get( node, ) as ts.PropertyAccessExpression; handlePropertyAccessExpression( @@ -224,7 +222,7 @@ export default util.createRule({ ): void { if (ASTUtils.isConstructor(node)) { classScopeStack[classScopeStack.length - 1].enterConstructor( - parserServices.esTreeNodeToTSNodeMap.get(node), + services.esTreeNodeToTSNodeMap.get(node), ); } else if (isFunctionScopeBoundaryInStack(node)) { classScopeStack[classScopeStack.length - 1].enterNonConstructor(); diff --git a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts index 7d27a014653..874455d5d9e 100644 --- a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts +++ b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts @@ -43,8 +43,8 @@ export default util.createRule({ }, defaultOptions: [], create(context) { - const service = util.getParserServices(context); - const checker = service.program.getTypeChecker(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); return { 'CallExpression > MemberExpression.callee'( @@ -64,10 +64,9 @@ export default util.createRule({ } // Get the symbol of the `reduce` method. - const tsNode = service.esTreeNodeToTSNodeMap.get(callee.object); const calleeObjType = util.getConstrainedTypeAtLocation( - checker, - tsNode, + services, + callee.object, ); // Check the owner type of the `reduce` method. diff --git a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts index 60bf310947f..413eae49774 100644 --- a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts +++ b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts @@ -39,8 +39,8 @@ export default createRule({ create(context) { const globalScope = context.getScope(); - const parserServices = getParserServices(context); - const typeChecker = parserServices.program.getTypeChecker(); + const services = getParserServices(context); + const checker = services.program.getTypeChecker(); const sourceCode = context.getSourceCode(); /** @@ -48,7 +48,7 @@ export default createRule({ * @param node The node type to check. */ function isStringType(type: ts.Type): boolean { - return getTypeName(typeChecker, type) === 'string'; + return getTypeName(checker, type) === 'string'; } /** @@ -56,7 +56,7 @@ export default createRule({ * @param node The node type to check. */ function isRegExpType(type: ts.Type): boolean { - return getTypeName(typeChecker, type) === 'RegExp'; + return getTypeName(checker, type) === 'RegExp'; } function collectArgumentTypes(types: ts.Type[]): ArgumentType { @@ -101,13 +101,7 @@ export default createRule({ const [argumentNode] = callNode.arguments; const argumentValue = getStaticValue(argumentNode, globalScope); - if ( - !isStringType( - typeChecker.getTypeAtLocation( - parserServices.esTreeNodeToTSNodeMap.get(objectNode), - ), - ) - ) { + if (!isStringType(services.getTypeAtLocation(objectNode))) { return; } @@ -138,9 +132,7 @@ export default createRule({ }); } - const argumentType = typeChecker.getTypeAtLocation( - parserServices.esTreeNodeToTSNodeMap.get(argumentNode), - ); + const argumentType = services.getTypeAtLocation(argumentNode); const argumentTypes = collectArgumentTypes( tsutils.unionTypeParts(argumentType), ); diff --git a/packages/eslint-plugin/src/rules/prefer-return-this-type.ts b/packages/eslint-plugin/src/rules/prefer-return-this-type.ts index b6ea9d465bd..b0ae1acd8ff 100644 --- a/packages/eslint-plugin/src/rules/prefer-return-this-type.ts +++ b/packages/eslint-plugin/src/rules/prefer-return-this-type.ts @@ -32,8 +32,8 @@ export default createRule({ }, create(context) { - const parserServices = getParserServices(context); - const checker = parserServices.program.getTypeChecker(); + const services = getParserServices(context); + const checker = services.program.getTypeChecker(); function tryGetNameInType( name: string, @@ -76,14 +76,14 @@ export default createRule({ return false; } - const func = parserServices.esTreeNodeToTSNodeMap.get(originalFunc); + const func = services.esTreeNodeToTSNodeMap.get(originalFunc); if (!func.body) { return false; } - const classType = checker.getTypeAtLocation( - parserServices.esTreeNodeToTSNodeMap.get(originalClass), + const classType = services.getTypeAtLocation( + originalClass, ) as ts.InterfaceType; if (func.body.kind !== ts.SyntaxKind.Block) { diff --git a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts index 104637062bb..b35e16c2049 100644 --- a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts +++ b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts @@ -40,18 +40,16 @@ export default createRule({ create(context) { const globalScope = context.getScope(); const sourceCode = context.getSourceCode(); - const service = getParserServices(context); - const typeChecker = service.program.getTypeChecker(); + const services = getParserServices(context); + const checker = services.program.getTypeChecker(); /** * Check if a given node is a string. * @param node The node to check. */ function isStringType(node: TSESTree.Expression): boolean { - const objectType = typeChecker.getTypeAtLocation( - service.esTreeNodeToTSNodeMap.get(node), - ); - return getTypeName(typeChecker, objectType) === 'string'; + const objectType = services.getTypeAtLocation(node); + return getTypeName(checker, objectType) === 'string'; } /** diff --git a/packages/eslint-plugin/src/rules/promise-function-async.ts b/packages/eslint-plugin/src/rules/promise-function-async.ts index 81765bc18b8..4d708f1f2cd 100644 --- a/packages/eslint-plugin/src/rules/promise-function-async.ts +++ b/packages/eslint-plugin/src/rules/promise-function-async.ts @@ -91,8 +91,8 @@ export default util.createRule({ 'Promise', ...allowedPromiseNames!, ]); - const parserServices = util.getParserServices(context); - const checker = parserServices.program.getTypeChecker(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); const sourceCode = context.getSourceCode(); function validateNode( @@ -101,10 +101,7 @@ export default util.createRule({ | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression, ): void { - const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node); - const signatures = checker - .getTypeAtLocation(originalNode) - .getCallSignatures(); + const signatures = services.getTypeAtLocation(node).getCallSignatures(); if (!signatures.length) { return; } diff --git a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts index 713a9e51d6e..403e05ab30f 100644 --- a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts +++ b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts @@ -43,17 +43,16 @@ export default util.createRule({ }, create(context, [options]) { - const service = util.getParserServices(context); - const checker = service.program.getTypeChecker(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); /** * Check if a given node is an array which all elements are string. * @param node */ function isStringArrayNode(node: TSESTree.Expression): boolean { - const type = checker.getTypeAtLocation( - service.esTreeNodeToTSNodeMap.get(node), - ); + const type = services.getTypeAtLocation(node); + if (checker.isArrayType(type) || checker.isTupleType(type)) { const typeArgs = checker.getTypeArguments(type); return typeArgs.every( @@ -67,10 +66,9 @@ export default util.createRule({ "CallExpression[arguments.length=0] > MemberExpression[property.name='sort'][computed=false]"( callee: TSESTree.MemberExpression, ): void { - const tsNode = service.esTreeNodeToTSNodeMap.get(callee.object); const calleeObjType = util.getConstrainedTypeAtLocation( - checker, - tsNode, + services, + callee.object, ); if (options.ignoreStringArrays && isStringArrayNode(callee.object)) { diff --git a/packages/eslint-plugin/src/rules/require-await.ts b/packages/eslint-plugin/src/rules/require-await.ts index 076cd6077a0..c9437b4dc7c 100644 --- a/packages/eslint-plugin/src/rules/require-await.ts +++ b/packages/eslint-plugin/src/rules/require-await.ts @@ -34,8 +34,8 @@ export default util.createRule({ }, defaultOptions: [], create(context) { - const parserServices = util.getParserServices(context); - const checker = parserServices.program.getTypeChecker(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); const sourceCode = context.getSourceCode(); let scopeInfo: ScopeInfo | null = null; @@ -110,14 +110,13 @@ export default util.createRule({ return; } - if (node?.argument?.type === AST_NODE_TYPES.Literal) { + if (node.argument.type === AST_NODE_TYPES.Literal) { // making this `false` as for literals we don't need to check the definition // eg : async function* run() { yield* 1 } scopeInfo.isAsyncYield ||= false; } - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node?.argument); - const type = checker.getTypeAtLocation(tsNode); + const type = services.getTypeAtLocation(node.argument); const typesToCheck = expandUnionOrIntersectionType(type); for (const type of typesToCheck) { const asyncIterator = tsutils.getWellKnownSymbolPropertyOfType( @@ -152,7 +151,7 @@ export default util.createRule({ TSESTree.BlockStatement | TSESTree.AwaitExpression >, ): void { - const expression = parserServices.esTreeNodeToTSNodeMap.get(node); + const expression = services.esTreeNodeToTSNodeMap.get(node); if (expression && isThenableType(expression)) { markAsHasAwait(); } @@ -163,7 +162,7 @@ export default util.createRule({ return; } - const { expression } = parserServices.esTreeNodeToTSNodeMap.get(node); + const { expression } = services.esTreeNodeToTSNodeMap.get(node); if (expression && isThenableType(expression)) { markAsHasAwait(); } diff --git a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts index ad5d0a832b7..b1dccf3a7c9 100644 --- a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts +++ b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts @@ -61,8 +61,8 @@ export default util.createRule({ }, ], create(context, [{ checkCompoundAssignments, allowAny }]) { - const service = util.getParserServices(context); - const typeChecker = service.program.getTypeChecker(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); type BaseLiteral = 'string' | 'number' | 'bigint' | 'invalid' | 'any'; @@ -107,7 +107,7 @@ export default util.createRule({ return 'invalid'; } - const stringType = typeChecker.typeToString(type); + const stringType = checker.typeToString(type); if ( stringType === 'number' || @@ -127,8 +127,7 @@ export default util.createRule({ function getNodeType( node: TSESTree.Expression | TSESTree.PrivateIdentifier, ): BaseLiteral { - const tsNode = service.esTreeNodeToTSNodeMap.get(node); - const type = util.getConstrainedTypeAtLocation(typeChecker, tsNode); + const type = util.getConstrainedTypeAtLocation(services, node); return getBaseTypeOfLiteralType(type); } diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 83ee29d7ea5..7a5b1ba286a 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -68,8 +68,8 @@ export default util.createRule({ }, ], create(context, [options]) { - const service = util.getParserServices(context); - const typeChecker = service.program.getTypeChecker(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); function isUnderlyingTypePrimitive(type: ts.Type): boolean { if (util.isTypeFlagSet(type, ts.TypeFlags.StringLike)) { @@ -97,10 +97,7 @@ export default util.createRule({ return true; } - if ( - options.allowRegExp && - util.getTypeName(typeChecker, type) === 'RegExp' - ) { + if (options.allowRegExp && util.getTypeName(checker, type) === 'RegExp') { return true; } @@ -123,8 +120,8 @@ export default util.createRule({ for (const expression of node.expressions) { const expressionType = util.getConstrainedTypeAtLocation( - typeChecker, - service.esTreeNodeToTSNodeMap.get(expression), + services, + expression, ); if ( @@ -136,7 +133,7 @@ export default util.createRule({ context.report({ node: expression, messageId: 'invalidType', - data: { type: typeChecker.typeToString(expressionType) }, + data: { type: checker.typeToString(expressionType) }, }); } } diff --git a/packages/eslint-plugin/src/rules/return-await.ts b/packages/eslint-plugin/src/rules/return-await.ts index 1797e47e127..cddfc4ba8c7 100644 --- a/packages/eslint-plugin/src/rules/return-await.ts +++ b/packages/eslint-plugin/src/rules/return-await.ts @@ -46,8 +46,8 @@ export default util.createRule({ defaultOptions: ['in-try-catch'], create(context, [option]) { - const parserServices = util.getParserServices(context); - const checker = parserServices.program.getTypeChecker(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); const sourceCode = context.getSourceCode(); const scopeInfoStack: ScopeInfo[] = []; @@ -301,7 +301,7 @@ export default util.createRule({ ): void { if (node.body.type !== AST_NODE_TYPES.BlockStatement) { findPossiblyReturnedNodes(node.body).forEach(node => { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const tsNode = services.esTreeNodeToTSNodeMap.get(node); test(node, tsNode); }); } @@ -312,7 +312,7 @@ export default util.createRule({ return; } findPossiblyReturnedNodes(node.argument).forEach(node => { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const tsNode = services.esTreeNodeToTSNodeMap.get(node); test(node, tsNode); }); }, diff --git a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts index 74e6521eb66..b7a18b87cd5 100644 --- a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts +++ b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts @@ -1,4 +1,7 @@ -import type { ParserServices, TSESTree } from '@typescript-eslint/utils'; +import type { + ParserServicesWithTypeInformation, + TSESTree, +} from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'tsutils'; import * as ts from 'typescript'; @@ -142,9 +145,9 @@ export default util.createRule({ }, ], create(context, [options]) { - const parserServices = util.getParserServices(context); - const typeChecker = parserServices.program.getTypeChecker(); - const compilerOptions = parserServices.program.getCompilerOptions(); + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); + const compilerOptions = services.program.getCompilerOptions(); const sourceCode = context.getSourceCode(); const isStrictNullChecks = tsutils.isStrictCompilerOptionEnabled( compilerOptions, @@ -258,8 +261,7 @@ export default util.createRule({ * It analyzes the type of a node and checks if it is allowed in a boolean context. */ function checkNode(node: TSESTree.Node): void { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); - const type = util.getConstrainedTypeAtLocation(typeChecker, tsNode); + const type = util.getConstrainedTypeAtLocation(services, node); const types = inspectVariantTypes(tsutils.unionTypeParts(type)); const is = (...wantedTypes: readonly VariantType[]): boolean => @@ -507,7 +509,7 @@ export default util.createRule({ // number if (is('number') || is('truthy number')) { if (!options.allowNumber) { - if (isArrayLengthExpression(node, typeChecker, parserServices)) { + if (isArrayLengthExpression(node, checker, services)) { if (isLogicalNegationExpression(node.parent)) { // if (!array.length) context.report({ @@ -867,7 +869,7 @@ function isLogicalNegationExpression( function isArrayLengthExpression( node: TSESTree.Node, typeChecker: ts.TypeChecker, - parserServices: ParserServices, + services: ParserServicesWithTypeInformation, ): node is TSESTree.MemberExpressionNonComputedName { if (node.type !== AST_NODE_TYPES.MemberExpression) { return false; @@ -878,10 +880,6 @@ function isArrayLengthExpression( if (node.property.name !== 'length') { return false; } - const objectTsNode = parserServices.esTreeNodeToTSNodeMap.get(node.object); - const objectType = util.getConstrainedTypeAtLocation( - typeChecker, - objectTsNode, - ); + const objectType = util.getConstrainedTypeAtLocation(services, node.object); return util.isTypeArrayTypeOrUnionOfArrayTypes(objectType, typeChecker); } diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index 43d4913b4ca..76843d12942 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -32,14 +32,9 @@ export default createRule({ defaultOptions: [], create(context) { const sourceCode = context.getSourceCode(); - const service = getParserServices(context); - const checker = service.program.getTypeChecker(); - const compilerOptions = service.program.getCompilerOptions(); - - function getNodeType(node: TSESTree.Node): ts.Type { - const tsNode = service.esTreeNodeToTSNodeMap.get(node); - return getConstrainedTypeAtLocation(checker, tsNode); - } + const services = getParserServices(context); + const checker = services.program.getTypeChecker(); + const compilerOptions = services.program.getCompilerOptions(); function fixSwitch( fixer: TSESLint.RuleFixer, @@ -114,7 +109,10 @@ export default createRule({ } function checkSwitchExhaustive(node: TSESTree.SwitchStatement): void { - const discriminantType = getNodeType(node.discriminant); + const discriminantType = getConstrainedTypeAtLocation( + services, + node.discriminant, + ); const symbolName = discriminantType.getSymbol()?.escapedName; if (discriminantType.isUnion()) { @@ -126,7 +124,9 @@ export default createRule({ return; } - caseTypes.add(getNodeType(switchCase.test)); + caseTypes.add( + getConstrainedTypeAtLocation(services, switchCase.test), + ); } const missingBranchTypes = unionTypes.filter( diff --git a/packages/eslint-plugin/src/rules/unbound-method.ts b/packages/eslint-plugin/src/rules/unbound-method.ts index 6741f4df09f..43971d621b4 100644 --- a/packages/eslint-plugin/src/rules/unbound-method.ts +++ b/packages/eslint-plugin/src/rules/unbound-method.ts @@ -161,9 +161,9 @@ export default util.createRule({ }, ], create(context, [{ ignoreStatic }]) { - const parserServices = util.getParserServices(context); - const checker = parserServices.program.getTypeChecker(); - const currentSourceFile = parserServices.program.getSourceFile( + const services = util.getParserServices(context); + const checker = services.program.getTypeChecker(); + const currentSourceFile = services.program.getSourceFile( context.getFilename(), ); @@ -193,9 +193,7 @@ export default util.createRule({ return; } - const objectSymbol = checker.getSymbolAtLocation( - parserServices.esTreeNodeToTSNodeMap.get(node.object), - ); + const objectSymbol = services.getSymbolAtLocation(node.object); if ( objectSymbol && @@ -205,9 +203,7 @@ export default util.createRule({ return; } - const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node); - - checkMethodAndReport(node, checker.getSymbolAtLocation(originalNode)); + checkMethodAndReport(node, services.getSymbolAtLocation(node)); }, 'VariableDeclarator, AssignmentExpression'( node: TSESTree.VariableDeclarator | TSESTree.AssignmentExpression, @@ -218,9 +214,8 @@ export default util.createRule({ : [node.left, node.right]; if (initNode && idNode.type === AST_NODE_TYPES.ObjectPattern) { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(initNode); - const rightSymbol = checker.getSymbolAtLocation(tsNode); - const initTypes = checker.getTypeAtLocation(tsNode); + const rightSymbol = services.getSymbolAtLocation(initNode); + const initTypes = services.getTypeAtLocation(initNode); const notImported = rightSymbol && isNotImported(rightSymbol, currentSourceFile); diff --git a/packages/type-utils/src/getConstrainedTypeAtLocation.ts b/packages/type-utils/src/getConstrainedTypeAtLocation.ts index 50317b00e56..cbd332f98da 100644 --- a/packages/type-utils/src/getConstrainedTypeAtLocation.ts +++ b/packages/type-utils/src/getConstrainedTypeAtLocation.ts @@ -1,14 +1,20 @@ +import type { + ParserServicesWithTypeInformation, + TSESTree, +} from '@typescript-eslint/typescript-estree'; import type * as ts from 'typescript'; /** * Resolves the given node's type. Will resolve to the type's generic constraint, if it has one. */ export function getConstrainedTypeAtLocation( - checker: ts.TypeChecker, - node: ts.Node, + services: ParserServicesWithTypeInformation, + node: TSESTree.Node, ): ts.Type { - const nodeType = checker.getTypeAtLocation(node); - const constrained = checker.getBaseConstraintOfType(nodeType); + const nodeType = services.getTypeAtLocation(node); + const constrained = services.program + .getTypeChecker() + .getBaseConstraintOfType(nodeType); return constrained ?? nodeType; } diff --git a/packages/type-utils/src/getDeclaration.ts b/packages/type-utils/src/getDeclaration.ts index d0c6c998d33..9e8d8b77024 100644 --- a/packages/type-utils/src/getDeclaration.ts +++ b/packages/type-utils/src/getDeclaration.ts @@ -1,13 +1,17 @@ +import type { + ParserServicesWithTypeInformation, + TSESTree, +} from '@typescript-eslint/typescript-estree'; import type * as ts from 'typescript'; /** * Gets the declaration for the given variable */ export function getDeclaration( - checker: ts.TypeChecker, - node: ts.Expression, + services: ParserServicesWithTypeInformation, + node: TSESTree.Node, ): ts.Declaration | null { - const symbol = checker.getSymbolAtLocation(node); + const symbol = services.getSymbolAtLocation(node); if (!symbol) { return null; } diff --git a/packages/type-utils/tests/isUnsafeAssignment.test.ts b/packages/type-utils/tests/isUnsafeAssignment.test.ts index e7ba11fda28..55e2195f93a 100644 --- a/packages/type-utils/tests/isUnsafeAssignment.test.ts +++ b/packages/type-utils/tests/isUnsafeAssignment.test.ts @@ -22,17 +22,12 @@ describe('isUnsafeAssignment', () => { }); expectToHaveParserServices(services); const checker = services.program.getTypeChecker(); - const esTreeNodeToTSNodeMap = services.esTreeNodeToTSNodeMap; const declaration = ast.body[0] as TSESTree.VariableDeclaration; const declarator = declaration.declarations[0]; return { - receiver: checker.getTypeAtLocation( - esTreeNodeToTSNodeMap.get(declarator.id), - ), - sender: checker.getTypeAtLocation( - esTreeNodeToTSNodeMap.get(declarator.init!), - ), + receiver: services.getTypeAtLocation(declarator.id), + sender: services.getTypeAtLocation(declarator.init!), senderNode: declarator.init!, checker, }; diff --git a/packages/typescript-estree/src/createParserServices.ts b/packages/typescript-estree/src/createParserServices.ts new file mode 100644 index 00000000000..1e62bdbe351 --- /dev/null +++ b/packages/typescript-estree/src/createParserServices.ts @@ -0,0 +1,27 @@ +import type * as ts from 'typescript'; + +import type { ASTMaps } from './convert'; +import type { ParserServices } from './parser-options'; + +export function createParserServices( + astMaps: ASTMaps, + program: ts.Program | null, +): ParserServices { + if (!program) { + // we always return the node maps because + // (a) they don't require type info and + // (b) they can be useful when using some of TS's internal non-type-aware AST utils + return { program, ...astMaps }; + } + + const checker = program.getTypeChecker(); + + return { + program, + ...astMaps, + getSymbolAtLocation: node => + checker.getSymbolAtLocation(astMaps.esTreeNodeToTSNodeMap.get(node)), + getTypeAtLocation: node => + checker.getTypeAtLocation(astMaps.esTreeNodeToTSNodeMap.get(node)), + }; +} diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index 441c3b2c530..7113d76cc23 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -211,6 +211,8 @@ export interface ParserServicesNodeMaps { export interface ParserServicesWithTypeInformation extends ParserServicesNodeMaps { program: ts.Program; + getSymbolAtLocation: (node: TSESTree.Node) => ts.Symbol | undefined; + getTypeAtLocation: (node: TSESTree.Node) => ts.Type; } export interface ParserServicesWithoutTypeInformation extends ParserServicesNodeMaps { diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 084e156fe47..4c08717fbca 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -15,6 +15,7 @@ import { createProgramFromConfigFile, useProvidedPrograms, } from './create-program/useProvidedPrograms'; +import { createParserServices } from './createParserServices'; import type { ParserServices, ParserServicesNodeMaps, @@ -39,12 +40,12 @@ function clearProgramCache(): void { /** * @param parseSettings Internal settings for parsing the file - * @param shouldProvideParserServices True if the program should be attempted to be calculated from provided tsconfig files + * @param hasFullTypeInformation True if the program should be attempted to be calculated from provided tsconfig files * @returns Returns a source file and program corresponding to the linted code */ function getProgramAndAST( parseSettings: ParseSettings, - shouldProvideParserServices: boolean, + hasFullTypeInformation: boolean, ): ASTAndProgram { if (parseSettings.programs) { const fromProvidedPrograms = useProvidedPrograms( @@ -56,7 +57,7 @@ function getProgramAndAST( } } - if (shouldProvideParserServices) { + if (hasFullTypeInformation) { const fromProjectProgram = createProjectProgram(parseSettings); if (fromProjectProgram) { return fromProjectProgram; @@ -197,7 +198,7 @@ function parseAndGenerateServices( /** * Generate a full ts.Program or offer provided instances in order to be able to provide parser services, such as type-checking */ - const shouldProvideParserServices = + const hasFullTypeInformation = parseSettings.programs != null || parseSettings.projects?.length > 0; if (typeof options !== 'undefined') { @@ -211,7 +212,7 @@ function parseAndGenerateServices( if ( parseSettings.errorOnTypeScriptSyntacticAndSemanticIssues && - !shouldProvideParserServices + !hasFullTypeInformation ) { throw new Error( 'Cannot calculate TypeScript semantic issues without a valid project.', @@ -237,7 +238,7 @@ function parseAndGenerateServices( options.filePath && parseAndGenerateServicesCalls[options.filePath] > 1 ? createIsolatedProgram(parseSettings) - : getProgramAndAST(parseSettings, shouldProvideParserServices)!; + : getProgramAndAST(parseSettings, hasFullTypeInformation)!; /** * Convert the TypeScript AST to an ESTree-compatible one, and optionally preserve @@ -270,14 +271,7 @@ function parseAndGenerateServices( */ return { ast: estree as AST, - services: { - program, - // we always return the node maps because - // (a) they don't require type info and - // (b) they can be useful when using some of TS's internal non-type-aware AST utils - esTreeNodeToTSNodeMap: astMaps.esTreeNodeToTSNodeMap, - tsNodeToESTreeNodeMap: astMaps.tsNodeToESTreeNodeMap, - }, + services: createParserServices(astMaps, program), }; } diff --git a/packages/typescript-estree/tests/lib/parse.test.ts b/packages/typescript-estree/tests/lib/parse.test.ts index 600aea3e734..e39bb1bb0a7 100644 --- a/packages/typescript-estree/tests/lib/parse.test.ts +++ b/packages/typescript-estree/tests/lib/parse.test.ts @@ -10,7 +10,6 @@ import * as sharedParserUtilsModule from '../../src/create-program/shared'; import type { TSESTreeOptions } from '../../src/parser-options'; import { clearGlobResolutionCache } from '../../src/parseSettings/resolveProjectList'; import { createSnapshotTestBlock } from '../../tools/test-utils'; -import { expectToHaveParserServices } from './test-utils/expectToHaveParserServices'; const FIXTURES_DIR = join(__dirname, '../fixtures/simpleProject'); diff --git a/packages/typescript-estree/tests/lib/semanticInfo.test.ts b/packages/typescript-estree/tests/lib/semanticInfo.test.ts index 76d7767fcb8..6c50712e0ea 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.test.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.test.ts @@ -80,8 +80,17 @@ describe('semanticInfo', () => { ...options, project: ['./tsconfig.json'], }; - expect(parseAndGenerateServices(code, optionsProjectString)).toEqual( - parseAndGenerateServices(code, optionsProjectArray), + const fromString = parseAndGenerateServices(code, optionsProjectString); + const fromArray = parseAndGenerateServices(code, optionsProjectArray); + + expect(fromString.services.program).toBe(fromArray.services.program); + + expect(fromString.ast).toEqual(fromArray.ast); + expect(fromString.services.esTreeNodeToTSNodeMap).toEqual( + fromArray.services.esTreeNodeToTSNodeMap, + ); + expect(fromString.services.tsNodeToESTreeNodeMap).toEqual( + fromArray.services.tsNodeToESTreeNodeMap, ); }); diff --git a/packages/website/src/components/linter/WebLinter.ts b/packages/website/src/components/linter/WebLinter.ts index 50c1daa2f7e..3ffd4da2c09 100644 --- a/packages/website/src/components/linter/WebLinter.ts +++ b/packages/website/src/components/linter/WebLinter.ts @@ -103,6 +103,7 @@ export class WebLinter { host: this.host, }); const tsAst = program.getSourceFile(fileName)!; + const checker = program.getTypeChecker(); const { estree: ast, astMaps } = this.lintUtils.astConverter( tsAst, @@ -125,6 +126,10 @@ export class WebLinter { program, esTreeNodeToTSNodeMap: astMaps.esTreeNodeToTSNodeMap, tsNodeToESTreeNodeMap: astMaps.tsNodeToESTreeNodeMap, + getSymbolAtLocation: node => + checker.getSymbolAtLocation(astMaps.esTreeNodeToTSNodeMap.get(node)), + getTypeAtLocation: node => + checker.getTypeAtLocation(astMaps.esTreeNodeToTSNodeMap.get(node)), }, scopeManager, visitorKeys: this.lintUtils.visitorKeys,