From 5930751b07afbd05d82ce53b2814813109a3dcc2 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Thu, 5 Aug 2021 12:26:31 +0800 Subject: [PATCH 1/3] feat: support `'latest'` as `ecmaVersion` --- packages/parser/README.md | 7 ++++--- packages/parser/tests/lib/parser.ts | 5 +++++ packages/scope-manager/src/ScopeManager.ts | 12 +++++++++--- packages/scope-manager/src/analyze.ts | 4 ++++ .../tests/eslint-scope/map-ecma-version.test.ts | 9 ++++++++- packages/types/src/parser-options.ts | 3 ++- 6 files changed, 32 insertions(+), 8 deletions(-) diff --git a/packages/parser/README.md b/packages/parser/README.md index d76219e1ae9..d352026ddd1 100644 --- a/packages/parser/README.md +++ b/packages/parser/README.md @@ -53,7 +53,7 @@ interface ParserOptions { jsx?: boolean; globalReturn?: boolean; }; - ecmaVersion?: number; + ecmaVersion?: number | 'latest'; jsxPragma?: string | null; jsxFragmentName?: string | null; @@ -97,12 +97,13 @@ This options allows you to tell the parser if you want to allow global `return` Default `2018`. -Accepts any valid ECMAScript version number: +Accepts any valid ECMAScript version number or `'latest'`: - A version: es3, es5, es6, es7, es8, es9, es10, es11, ..., or - A year: es2015, es2016, es2017, es2018, es2019, es2020, ... +- `latest` -The value **must** be a number - so do not include the `es` prefix. +When it's a version or a year, the value **must** be a number - so do not include the `es` prefix. Specifies the version of ECMAScript syntax you want to use. This is used by the parser to determine how to perform scope analysis, and it affects the default diff --git a/packages/parser/tests/lib/parser.ts b/packages/parser/tests/lib/parser.ts index 41e22a911f9..0432c94252e 100644 --- a/packages/parser/tests/lib/parser.ts +++ b/packages/parser/tests/lib/parser.ts @@ -18,6 +18,11 @@ describe('parser', () => { expect(() => parseForESLint(code, null)).not.toThrow(); }); + it("parseForESLint() should work if options.ecmaVersion is `'latest'`", () => { + const code = 'const valid = true;'; + expect(() => parseForESLint(code, { ecmaVersion: 'latest' })).not.toThrow(); + }); + it('parseAndGenerateServices() should be called with options', () => { const code = 'const valid = true;'; const spy = jest.spyOn(typescriptESTree, 'parseAndGenerateServices'); diff --git a/packages/scope-manager/src/ScopeManager.ts b/packages/scope-manager/src/ScopeManager.ts index e4c9b4d3cdc..29d31dff379 100644 --- a/packages/scope-manager/src/ScopeManager.ts +++ b/packages/scope-manager/src/ScopeManager.ts @@ -26,7 +26,7 @@ interface ScopeManagerOptions { globalReturn?: boolean; sourceType?: 'module' | 'script'; impliedStrict?: boolean; - ecmaVersion?: number; + ecmaVersion?: number | 'latest'; } class ScopeManager { @@ -76,11 +76,17 @@ class ScopeManager { return this.#options.impliedStrict === true; } public isStrictModeSupported(): boolean { - return this.#options.ecmaVersion != null && this.#options.ecmaVersion >= 5; + return ( + this.#options.ecmaVersion === 'latest' || + (this.#options.ecmaVersion != null && this.#options.ecmaVersion >= 5) + ); } public isES6(): boolean { - return this.#options.ecmaVersion != null && this.#options.ecmaVersion >= 6; + return ( + this.#options.ecmaVersion === 'latest' || + (this.#options.ecmaVersion != null && this.#options.ecmaVersion >= 6) + ); } /** diff --git a/packages/scope-manager/src/analyze.ts b/packages/scope-manager/src/analyze.ts index 9845a3c1750..38af08fe9ef 100644 --- a/packages/scope-manager/src/analyze.ts +++ b/packages/scope-manager/src/analyze.ts @@ -82,6 +82,10 @@ const DEFAULT_OPTIONS: Required = { }; function mapEcmaVersion(version: EcmaVersion | undefined): Lib { + if (version === 'latest') { + return 'esnext'; + } + if (version == null || version === 3 || version === 5) { return 'es5'; } diff --git a/packages/scope-manager/tests/eslint-scope/map-ecma-version.test.ts b/packages/scope-manager/tests/eslint-scope/map-ecma-version.test.ts index d8646fbb353..b41df7bc171 100644 --- a/packages/scope-manager/tests/eslint-scope/map-ecma-version.test.ts +++ b/packages/scope-manager/tests/eslint-scope/map-ecma-version.test.ts @@ -31,11 +31,18 @@ describe('ecma version mapping', () => { it("should map to 'es2018' when undefined", () => { expectMapping(undefined, 'es2018'); }); + + it("should map to 'es2018' when 'latest'", () => { + expectMapping('latest', 'esnext'); + }); }); const fakeNode = {} as unknown as TSESTree.Node; -function expectMapping(ecmaVersion: number | undefined, lib: Lib): void { +function expectMapping( + ecmaVersion: number | undefined | 'latest', + lib: Lib, +): void { (Referencer as jest.Mock).mockClear(); analyze(fakeNode, { ecmaVersion: ecmaVersion as EcmaVersion }); expect(Referencer).toHaveBeenCalledWith( diff --git a/packages/types/src/parser-options.ts b/packages/types/src/parser-options.ts index 9a761a37b97..36c59742f25 100644 --- a/packages/types/src/parser-options.ts +++ b/packages/types/src/parser-options.ts @@ -17,7 +17,8 @@ type EcmaVersion = | 2017 | 2018 | 2019 - | 2020; + | 2020 + | 'latest'; type SourceType = 'script' | 'module'; From 9bbb090ec56bfad20a990f2b35121a4956b52d4f Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Thu, 5 Aug 2021 12:47:20 +0800 Subject: [PATCH 2/3] fix: test title --- .../scope-manager/tests/eslint-scope/map-ecma-version.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scope-manager/tests/eslint-scope/map-ecma-version.test.ts b/packages/scope-manager/tests/eslint-scope/map-ecma-version.test.ts index b41df7bc171..7f08bdb0a82 100644 --- a/packages/scope-manager/tests/eslint-scope/map-ecma-version.test.ts +++ b/packages/scope-manager/tests/eslint-scope/map-ecma-version.test.ts @@ -32,7 +32,7 @@ describe('ecma version mapping', () => { expectMapping(undefined, 'es2018'); }); - it("should map to 'es2018' when 'latest'", () => { + it("should map to 'esnext' when 'latest'", () => { expectMapping('latest', 'esnext'); }); }); From d93aec04692ffc613b617e2ba2f7673d48cb5687 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Mon, 30 Aug 2021 20:14:00 +0800 Subject: [PATCH 3/3] Add 2021 2022, handle `"latest"` in parser --- packages/parser/README.md | 4 ++-- packages/parser/src/parser.ts | 2 +- packages/scope-manager/src/ScopeManager.ts | 12 +++--------- packages/scope-manager/src/analyze.ts | 4 ++-- packages/types/src/parser-options.ts | 4 ++++ 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/parser/README.md b/packages/parser/README.md index d352026ddd1..4b4d013376b 100644 --- a/packages/parser/README.md +++ b/packages/parser/README.md @@ -99,8 +99,8 @@ Default `2018`. Accepts any valid ECMAScript version number or `'latest'`: -- A version: es3, es5, es6, es7, es8, es9, es10, es11, ..., or -- A year: es2015, es2016, es2017, es2018, es2019, es2020, ... +- A version: es3, es5, es6, es7, es8, es9, es10, es11, es12, es13, ..., or +- A year: es2015, es2016, es2017, es2018, es2019, es2020, es2021, es2022, ... - `latest` When it's a version or a year, the value **must** be a number - so do not include the `es` prefix. diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts index f304a837d8b..bbb0e3a04d7 100644 --- a/packages/parser/src/parser.ts +++ b/packages/parser/src/parser.ts @@ -102,7 +102,7 @@ function parseForESLint( jsx: validateBoolean(options.ecmaFeatures.jsx), }); const analyzeOptions: AnalyzeOptions = { - ecmaVersion: options.ecmaVersion, + ecmaVersion: options.ecmaVersion === 'latest' ? 1e8 : options.ecmaVersion, globalReturn: options.ecmaFeatures.globalReturn, jsxPragma: options.jsxPragma, jsxFragmentName: options.jsxFragmentName, diff --git a/packages/scope-manager/src/ScopeManager.ts b/packages/scope-manager/src/ScopeManager.ts index 29d31dff379..e4c9b4d3cdc 100644 --- a/packages/scope-manager/src/ScopeManager.ts +++ b/packages/scope-manager/src/ScopeManager.ts @@ -26,7 +26,7 @@ interface ScopeManagerOptions { globalReturn?: boolean; sourceType?: 'module' | 'script'; impliedStrict?: boolean; - ecmaVersion?: number | 'latest'; + ecmaVersion?: number; } class ScopeManager { @@ -76,17 +76,11 @@ class ScopeManager { return this.#options.impliedStrict === true; } public isStrictModeSupported(): boolean { - return ( - this.#options.ecmaVersion === 'latest' || - (this.#options.ecmaVersion != null && this.#options.ecmaVersion >= 5) - ); + return this.#options.ecmaVersion != null && this.#options.ecmaVersion >= 5; } public isES6(): boolean { - return ( - this.#options.ecmaVersion === 'latest' || - (this.#options.ecmaVersion != null && this.#options.ecmaVersion >= 6) - ); + return this.#options.ecmaVersion != null && this.#options.ecmaVersion >= 6; } /** diff --git a/packages/scope-manager/src/analyze.ts b/packages/scope-manager/src/analyze.ts index 38af08fe9ef..6026aaa3f62 100644 --- a/packages/scope-manager/src/analyze.ts +++ b/packages/scope-manager/src/analyze.ts @@ -18,7 +18,7 @@ interface AnalyzeOptions { * Which ECMAScript version is considered. * Defaults to `2018`. */ - ecmaVersion?: EcmaVersion; + ecmaVersion?: number; /** * Whether the whole script is executed under node.js environment. @@ -81,7 +81,7 @@ const DEFAULT_OPTIONS: Required = { emitDecoratorMetadata: false, }; -function mapEcmaVersion(version: EcmaVersion | undefined): Lib { +function mapEcmaVersion(version: EcmaVersion | number | undefined): Lib { if (version === 'latest') { return 'esnext'; } diff --git a/packages/types/src/parser-options.ts b/packages/types/src/parser-options.ts index 36c59742f25..16212faff28 100644 --- a/packages/types/src/parser-options.ts +++ b/packages/types/src/parser-options.ts @@ -12,12 +12,16 @@ type EcmaVersion = | 9 | 10 | 11 + | 12 + | 13 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020 + | 2021 + | 2022 | 'latest'; type SourceType = 'script' | 'module';