diff --git a/.eslintignore.all b/.eslintignore.all new file mode 100644 index 000000000000..cdb001c84b6b --- /dev/null +++ b/.eslintignore.all @@ -0,0 +1,17 @@ +node_modules +dist +jest.config.js +fixtures +coverage +__snapshots__ +.docusaurus +build + +# Files copied as part of the build +packages/types/src/generated/**/*.ts + +# Playground types downloaded from the web +packages/website/src/vendor + +# Additionally, JS files +**/*.js diff --git a/.eslintrc.js b/.eslintrc.js index 4e670b5f0358..59a2a594f58b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -41,6 +41,8 @@ module.exports = { allowAutomaticSingleRunInference: true, tsconfigRootDir: __dirname, warnOnUnsupportedTypeScriptVersion: false, + EXPERIMENTAL_memoizeTypeCheckingAPIs: + !!process.env.MEMOIZE_TYPE_CHECKING_APIS, EXPERIMENTAL_useSourceOfProjectReferenceRedirect: false, cacheLifetime: { // we pretty well never create/change tsconfig structure - so need to ever evict the cache diff --git a/.eslintrc.many.js b/.eslintrc.many.js new file mode 100644 index 000000000000..1f080d9ade2c --- /dev/null +++ b/.eslintrc.many.js @@ -0,0 +1,22 @@ +// A version of our config that enables many more rules, for use in perf testing + +// @ts-check +/** @type {import('./packages/utils/src/ts-eslint/Linter.js').Linter.Config} */ +module.exports = { + extends: ['plugin:@typescript-eslint/all', 'prettier'], + overrides: [ + { + files: ['*.js'], + extends: ['plugin:@typescript-eslint/disable-type-checked'], + }, + ], + parser: '@typescript-eslint/parser', + parserOptions: { + EXPERIMENTAL_memoizeTypeCheckingAPIs: + !!process.env.MEMOIZE_TYPE_CHECKING_APIS, + project: ['./tsconfig.eslint.json'], + tsconfigRootDir: __dirname, + }, + plugins: ['@typescript-eslint'], + root: true, +}; diff --git a/docs/architecture/TypeScript-ESTree.mdx b/docs/architecture/TypeScript-ESTree.mdx index 1df714085953..35f156ca0065 100644 --- a/docs/architecture/TypeScript-ESTree.mdx +++ b/docs/architecture/TypeScript-ESTree.mdx @@ -147,6 +147,13 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { */ errorOnTypeScriptSyntacticAndSemanticIssues?: boolean; + /** + * ***EXPERIMENTAL FLAG*** - Use this at your own risk. + * + * Whether to memoize many of TypeScript's type checking APIs. + */ + EXPERIMENTAL__memoizeTypeCheckingAPIs?: boolean; + /** * ***EXPERIMENTAL FLAG*** - Use this at your own risk. * diff --git a/package.json b/package.json index 79e78df462a5..dba7b06b525d 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "generate-website-dts": "nx run website:generate-website-dts", "generate-lib": "nx run scope-manager:generate-lib", "lint-fix": "yarn lint --fix", + "lint-many": "npx eslint --config .eslintrc.many.js --ignore-path .eslintignore.all packages/", "lint-markdown-fix": "yarn lint-markdown --fix", "lint-markdown": "markdownlint \"**/*.md\" --config=.markdownlint.json --ignore-path=.markdownlintignore", "lint": "nx run-many --target=lint --parallel", @@ -82,8 +83,9 @@ "cross-fetch": "^3.1.5", "cspell": "^6.0.0", "downlevel-dts": ">=0.11.0", - "eslint-plugin-deprecation": "^1.3.3", "eslint": "^8.34.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-deprecation": "^1.3.3", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-eslint-plugin": "^5.0.8", "eslint-plugin-import": "^2.27.5", diff --git a/packages/types/src/parser-options.ts b/packages/types/src/parser-options.ts index 815ad4409130..fb5255ef14c9 100644 --- a/packages/types/src/parser-options.ts +++ b/packages/types/src/parser-options.ts @@ -47,6 +47,7 @@ interface ParserOptions { debugLevel?: DebugLevel; errorOnTypeScriptSyntacticAndSemanticIssues?: boolean; errorOnUnknownASTType?: boolean; + EXPERIMENTAL_memoizeTypeCheckingAPIs?: boolean; EXPERIMENTAL_useSourceOfProjectReferenceRedirect?: boolean; // purposely undocumented for now extraFileExtensions?: string[]; filePath?: string; diff --git a/packages/typescript-estree/src/createParserServices.ts b/packages/typescript-estree/src/createParserServices.ts index 1e62bdbe351e..012c2f504508 100644 --- a/packages/typescript-estree/src/createParserServices.ts +++ b/packages/typescript-estree/src/createParserServices.ts @@ -3,9 +3,40 @@ import type * as ts from 'typescript'; import type { ASTMaps } from './convert'; import type { ParserServices } from './parser-options'; +type MethodKeyOf = keyof { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [Key in keyof Container]: Container[Key] extends (parameter: any) => unknown + ? Key + : never; +}; + +function memoizeMethods[]>( + container: Container, + keys: Keys, +): void { + for (const key of keys) { + const originalMethod = ( + container[key] as (parameter: object) => unknown + ).bind(container); + const cache = new WeakMap(); + + container[key] = ((parameter: object): unknown => { + const cached = cache.get(parameter); + if (cached) { + return cached; + } + + const created = originalMethod(parameter); + cache.set(parameter, created); + return created; + }) as Container[typeof key]; + } +} + export function createParserServices( astMaps: ASTMaps, program: ts.Program | null, + memoize: boolean, ): ParserServices { if (!program) { // we always return the node maps because @@ -16,6 +47,10 @@ export function createParserServices( const checker = program.getTypeChecker(); + if (memoize) { + memoizeMethods(checker, ['getSymbolAtLocation', 'getTypeAtLocation']); + } + return { program, ...astMaps, diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index 28943a02c06e..00b94b96ebbe 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -47,6 +47,8 @@ export function createParseSettings( : new Set(), errorOnTypeScriptSyntacticAndSemanticIssues: false, errorOnUnknownASTType: options.errorOnUnknownASTType === true, + EXPERIMENTAL_memoizeTypeCheckingAPIs: + options.EXPERIMENTAL_memoizeTypeCheckingAPIs === true, EXPERIMENTAL_useSourceOfProjectReferenceRedirect: options.EXPERIMENTAL_useSourceOfProjectReferenceRedirect === true, extraFileExtensions: diff --git a/packages/typescript-estree/src/parseSettings/index.ts b/packages/typescript-estree/src/parseSettings/index.ts index 9fc8986ad484..23b761edd428 100644 --- a/packages/typescript-estree/src/parseSettings/index.ts +++ b/packages/typescript-estree/src/parseSettings/index.ts @@ -57,6 +57,11 @@ export interface MutableParseSettings { */ errorOnUnknownASTType: boolean; + /** + * Whether to memoize many of TypeScript's type checking APIs. + */ + EXPERIMENTAL_memoizeTypeCheckingAPIs: boolean; + /** * Whether TS should use the source files for referenced projects instead of the compiled .d.ts files. * diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index da00e007d619..a600f3c170c6 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -94,6 +94,13 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { */ errorOnTypeScriptSyntacticAndSemanticIssues?: boolean; + /** + * ***EXPERIMENTAL FLAG*** - Use this at your own risk. + * + * Whether to memoize many of TypeScript's type checking APIs. + */ + EXPERIMENTAL_memoizeTypeCheckingAPIs?: boolean; + /** * ***EXPERIMENTAL FLAG*** - Use this at your own risk. * diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 4a3e55e038d2..30b1b470f125 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -270,7 +270,11 @@ function parseAndGenerateServices( */ return { ast: estree as AST, - services: createParserServices(astMaps, program), + services: createParserServices( + astMaps, + program, + parseSettings.EXPERIMENTAL_memoizeTypeCheckingAPIs, + ), }; } diff --git a/packages/website/src/components/linter/config.ts b/packages/website/src/components/linter/config.ts index 641adc873c1b..00a91ea5fc66 100644 --- a/packages/website/src/components/linter/config.ts +++ b/packages/website/src/components/linter/config.ts @@ -13,6 +13,7 @@ export const parseSettings: ParseSettings = { DEPRECATED__createDefaultProgram: false, errorOnTypeScriptSyntacticAndSemanticIssues: false, errorOnUnknownASTType: false, + EXPERIMENTAL_memoizeTypeCheckingAPIs: false, EXPERIMENTAL_useSourceOfProjectReferenceRedirect: false, extraFileExtensions: [], filePath: '', diff --git a/yarn.lock b/yarn.lock index 1f1895cfab59..7e7ec180195c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6855,6 +6855,11 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +eslint-config-prettier@^8.8.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz#bfda738d412adc917fd7b038857110efe98c9348" + integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA== + eslint-import-resolver-node@^0.3.7: version "0.3.7" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7"