From 31bc44fe896baccab6c1ab3da8ea08b1d6c20ef4 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 5 Feb 2023 00:15:49 -0500 Subject: [PATCH 1/7] perf(typescript-estree): cache type checking APIs --- .../src/createParserServices.ts | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/typescript-estree/src/createParserServices.ts b/packages/typescript-estree/src/createParserServices.ts index 1e62bdbe351e..1c9585e6488e 100644 --- a/packages/typescript-estree/src/createParserServices.ts +++ b/packages/typescript-estree/src/createParserServices.ts @@ -3,6 +3,23 @@ import type * as ts from 'typescript'; import type { ASTMaps } from './convert'; import type { ParserServices } from './parser-options'; +function memoize( + get: (key: Key) => Return, +): typeof get { + const cache = new WeakMap(); + + return key => { + const cached = cache.get(key); + if (cached) { + return cached; + } + + const created = get(key); + cache.set(key, created); + return created; + }; +} + export function createParserServices( astMaps: ASTMaps, program: ts.Program | null, @@ -19,9 +36,11 @@ export function createParserServices( return { program, ...astMaps, - getSymbolAtLocation: node => + getSymbolAtLocation: memoize(node => checker.getSymbolAtLocation(astMaps.esTreeNodeToTSNodeMap.get(node)), - getTypeAtLocation: node => + ), + getTypeAtLocation: memoize(node => checker.getTypeAtLocation(astMaps.esTreeNodeToTSNodeMap.get(node)), + ), }; } From 7c564eaafd42533968a63b442da61fed4c61967e Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 6 Feb 2023 03:58:51 -0500 Subject: [PATCH 2/7] Try memoizing the checker itself --- .../typescript-estree/src/createParserServices.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/typescript-estree/src/createParserServices.ts b/packages/typescript-estree/src/createParserServices.ts index 1c9585e6488e..6ac5d6681080 100644 --- a/packages/typescript-estree/src/createParserServices.ts +++ b/packages/typescript-estree/src/createParserServices.ts @@ -33,14 +33,17 @@ export function createParserServices( const checker = program.getTypeChecker(); + checker.getSymbolAtLocation = memoize( + checker.getSymbolAtLocation.bind(checker), + ); + checker.getTypeAtLocation = memoize(checker.getTypeAtLocation.bind(checker)); + return { program, ...astMaps, - getSymbolAtLocation: memoize(node => + getSymbolAtLocation: node => checker.getSymbolAtLocation(astMaps.esTreeNodeToTSNodeMap.get(node)), - ), - getTypeAtLocation: memoize(node => + getTypeAtLocation: node => checker.getTypeAtLocation(astMaps.esTreeNodeToTSNodeMap.get(node)), - ), }; } From 1de32340a73311e3d541b01cc99d6d27e3f1758e Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 5 Apr 2023 12:28:54 -0400 Subject: [PATCH 3/7] An option, and more memoization --- .eslintrc.js | 1 + docs/architecture/TypeScript-ESTree.mdx | 7 +++ packages/types/src/parser-options.ts | 1 + .../src/createParserServices.ts | 58 +++++++++++++------ .../src/parseSettings/createParseSettings.ts | 2 + .../src/parseSettings/index.ts | 5 ++ .../typescript-estree/src/parser-options.ts | 7 +++ packages/typescript-estree/src/parser.ts | 6 +- 8 files changed, 67 insertions(+), 20 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 4e670b5f0358..8cc1860e2b39 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -41,6 +41,7 @@ module.exports = { allowAutomaticSingleRunInference: true, tsconfigRootDir: __dirname, warnOnUnsupportedTypeScriptVersion: false, + EXPERIMENTAL_memoizeTypeCheckingAPIs: true, EXPERIMENTAL_useSourceOfProjectReferenceRedirect: false, cacheLifetime: { // we pretty well never create/change tsconfig structure - so need to ever evict the cache 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/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 6ac5d6681080..4a250ada35cb 100644 --- a/packages/typescript-estree/src/createParserServices.ts +++ b/packages/typescript-estree/src/createParserServices.ts @@ -3,26 +3,40 @@ import type * as ts from 'typescript'; import type { ASTMaps } from './convert'; import type { ParserServices } from './parser-options'; -function memoize( - get: (key: Key) => Return, -): typeof get { - const cache = new WeakMap(); - - return key => { - const cached = cache.get(key); - if (cached) { - return cached; - } - - const created = get(key); - cache.set(key, created); - return created; - }; +type MethodKeyOf = keyof { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [Key in keyof Container]: Container[Key] extends (arg: any) => void + ? 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 @@ -33,10 +47,16 @@ export function createParserServices( const checker = program.getTypeChecker(); - checker.getSymbolAtLocation = memoize( - checker.getSymbolAtLocation.bind(checker), - ); - checker.getTypeAtLocation = memoize(checker.getTypeAtLocation.bind(checker)); + if (memoize) { + memoizeMethods(checker, [ + 'getApparentType', + 'getContextualType', + 'getPropertyOfType', + 'getSymbolAtLocation', + 'getTypeAtLocation', + 'getTypeOfSymbolAtLocation', + ]); + } return { program, diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index 28943a02c06e..dfbdc66e3f3d 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..be32e57793b9 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..4174e1ddd644 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..3c9894bd95bc 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, + ), }; } From eee66a5b197e6d645d96b1f2951699859fc0b892 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 5 Apr 2023 12:52:10 -0400 Subject: [PATCH 4/7] One more lil type --- packages/website/src/components/linter/config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/website/src/components/linter/config.ts b/packages/website/src/components/linter/config.ts index 641adc873c1b..14ef8657cf9a 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: '', From 5e1c0e308ef1a6a8ef92c637fdee7a571eea6876 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 5 Apr 2023 13:16:54 -0400 Subject: [PATCH 5/7] Add lint-many --- .eslintignore.all | 17 +++++++++++++++++ .eslintrc.many.js | 22 ++++++++++++++++++++++ package.json | 4 +++- yarn.lock | 5 +++++ 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 .eslintignore.all create mode 100644 .eslintrc.many.js 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.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/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/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" From 8e6dd94c7ef0b648f19c3522e07e96f4fa4cbddc Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 5 Apr 2023 13:39:27 -0400 Subject: [PATCH 6/7] Fix underscore, and simplify type --- .../typescript-estree/src/createParserServices.ts | 11 ++--------- .../src/parseSettings/createParseSettings.ts | 4 ++-- packages/typescript-estree/src/parseSettings/index.ts | 2 +- packages/typescript-estree/src/parser-options.ts | 2 +- packages/typescript-estree/src/parser.ts | 2 +- packages/website/src/components/linter/config.ts | 2 +- 6 files changed, 8 insertions(+), 15 deletions(-) diff --git a/packages/typescript-estree/src/createParserServices.ts b/packages/typescript-estree/src/createParserServices.ts index 4a250ada35cb..012c2f504508 100644 --- a/packages/typescript-estree/src/createParserServices.ts +++ b/packages/typescript-estree/src/createParserServices.ts @@ -5,7 +5,7 @@ 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 (arg: any) => void + [Key in keyof Container]: Container[Key] extends (parameter: any) => unknown ? Key : never; }; @@ -48,14 +48,7 @@ export function createParserServices( const checker = program.getTypeChecker(); if (memoize) { - memoizeMethods(checker, [ - 'getApparentType', - 'getContextualType', - 'getPropertyOfType', - 'getSymbolAtLocation', - 'getTypeAtLocation', - 'getTypeOfSymbolAtLocation', - ]); + memoizeMethods(checker, ['getSymbolAtLocation', 'getTypeAtLocation']); } return { diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index dfbdc66e3f3d..00b94b96ebbe 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -47,8 +47,8 @@ export function createParseSettings( : new Set(), errorOnTypeScriptSyntacticAndSemanticIssues: false, errorOnUnknownASTType: options.errorOnUnknownASTType === true, - EXPERIMENTAL__memoizeTypeCheckingAPIs: - options.EXPERIMENTAL__memoizeTypeCheckingAPIs === 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 be32e57793b9..23b761edd428 100644 --- a/packages/typescript-estree/src/parseSettings/index.ts +++ b/packages/typescript-estree/src/parseSettings/index.ts @@ -60,7 +60,7 @@ export interface MutableParseSettings { /** * Whether to memoize many of TypeScript's type checking APIs. */ - EXPERIMENTAL__memoizeTypeCheckingAPIs: boolean; + 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 4174e1ddd644..a600f3c170c6 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -99,7 +99,7 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { * * Whether to memoize many of TypeScript's type checking APIs. */ - EXPERIMENTAL__memoizeTypeCheckingAPIs?: boolean; + 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 3c9894bd95bc..30b1b470f125 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -273,7 +273,7 @@ function parseAndGenerateServices( services: createParserServices( astMaps, program, - parseSettings.EXPERIMENTAL__memoizeTypeCheckingAPIs, + parseSettings.EXPERIMENTAL_memoizeTypeCheckingAPIs, ), }; } diff --git a/packages/website/src/components/linter/config.ts b/packages/website/src/components/linter/config.ts index 14ef8657cf9a..00a91ea5fc66 100644 --- a/packages/website/src/components/linter/config.ts +++ b/packages/website/src/components/linter/config.ts @@ -13,7 +13,7 @@ export const parseSettings: ParseSettings = { DEPRECATED__createDefaultProgram: false, errorOnTypeScriptSyntacticAndSemanticIssues: false, errorOnUnknownASTType: false, - EXPERIMENTAL__memoizeTypeCheckingAPIs: false, + EXPERIMENTAL_memoizeTypeCheckingAPIs: false, EXPERIMENTAL_useSourceOfProjectReferenceRedirect: false, extraFileExtensions: [], filePath: '', From 3a8b206ba116262d5d289b83c61fd788d366413f Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 5 Apr 2023 13:50:05 -0400 Subject: [PATCH 7/7] Also check process.env in .eslintrc.js --- .eslintrc.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 8cc1860e2b39..59a2a594f58b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -41,7 +41,8 @@ module.exports = { allowAutomaticSingleRunInference: true, tsconfigRootDir: __dirname, warnOnUnsupportedTypeScriptVersion: false, - EXPERIMENTAL_memoizeTypeCheckingAPIs: true, + 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