Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Revert "feat(scope-manager): ignore ECMA version"#5888

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletionspackages/parser/src/parser.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,6 +105,7 @@ function parseForESLint(
jsx: validateBoolean(options.ecmaFeatures.jsx),
});
const analyzeOptions: AnalyzeOptions = {
ecmaVersion: options.ecmaVersion === 'latest' ? 1e8 : options.ecmaVersion,
globalReturn: options.ecmaFeatures.globalReturn,
jsxPragma: options.jsxPragma,
jsxFragmentName: options.jsxFragmentName,
Expand Down
8 changes: 8 additions & 0 deletionspackages/parser/tests/lib/parser.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,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');
Expand All@@ -28,6 +33,7 @@ describe('parser', () => {
range: false,
tokens: false,
sourceType: 'module' as const,
ecmaVersion: 2018,
ecmaFeatures: {
globalReturn: false,
jsx: false,
Expand DownExpand Up@@ -78,6 +84,7 @@ describe('parser', () => {
range: false,
tokens: false,
sourceType: 'module' as const,
ecmaVersion: 2018,
ecmaFeatures: {
globalReturn: false,
jsx: false,
Expand All@@ -97,6 +104,7 @@ describe('parser', () => {
parseForESLint(code, config);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenLastCalledWith(expect.anything(), {
ecmaVersion: 2018,
globalReturn: false,
lib: ['dom.iterable'],
jsxPragma: 'Foo',
Expand Down
12 changes: 10 additions & 2 deletionspackages/scope-manager/README.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,13 @@ interface AnalyzeOptions {
*/
childVisitorKeys?: Record<string, string[]> | null;

/**
* Which ECMAScript version is considered.
* Defaults to `2018`.
* `'latest'` is converted to 1e8 at parser.
*/
ecmaVersion?: EcmaVersion | 1e8;

/**
* Whether the whole script is executed under node.js environment.
* When enabled, the scope manager adds a function scope immediately following the global scope.
Expand All@@ -44,7 +51,7 @@ interface AnalyzeOptions {
globalReturn?: boolean;

/**
* Implied strict mode.
* Implied strict mode (if ecmaVersion >= 5).
* Defaults to `false`.
*/
impliedStrict?: boolean;
Expand All@@ -69,7 +76,7 @@ interface AnalyzeOptions {
* This automatically defines a type variable for any types provided by the configured TS libs.
* For more information, see https://www.typescriptlang.org/tsconfig#lib
*
* Defaults to['esnext'].
* Defaults tothe lib for the provided `ecmaVersion`.
*/
lib?: Lib[];

Expand DownExpand Up@@ -98,6 +105,7 @@ const ast = parse(code, {
range: true,
});
const scope = analyze(ast, {
ecmaVersion: 2020,
sourceType: 'module',
});
```
Expand Down
9 changes: 3 additions & 6 deletionspackages/scope-manager/src/ScopeManager.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,11 +28,9 @@ interface ScopeManagerOptions {
globalReturn?: boolean;
sourceType?: 'module' | 'script';
impliedStrict?: boolean;
ecmaVersion?: number;
}

/**
* @see https://eslint.org/docs/latest/developer-guide/scope-manager-interface#scopemanager-interface
*/
class ScopeManager {
public currentScope: Scope | null;
public readonly declaredVariables: WeakMap<TSESTree.Node, Variable[]>;
Expand DownExpand Up@@ -79,13 +77,12 @@ class ScopeManager {
public isImpliedStrict(): boolean {
return this.#options.impliedStrict === true;
}

public isStrictModeSupported(): boolean {
returntrue;
returnthis.#options.ecmaVersion != null && this.#options.ecmaVersion >= 5;
}

public isES6(): boolean {
returntrue;
returnthis.#options.ecmaVersion != null && this.#options.ecmaVersion >= 6;
}

/**
Expand Down
35 changes: 31 additions & 4 deletionspackages/scope-manager/src/analyze.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import type { Lib, TSESTree } from '@typescript-eslint/types';
import type {EcmaVersion,Lib, TSESTree } from '@typescript-eslint/types';
import { visitorKeys } from '@typescript-eslint/visitor-keys';

import { lib as TSLibraries } from './lib';
import type { ReferencerOptions } from './referencer';
import { Referencer } from './referencer';
import { ScopeManager } from './ScopeManager';
Expand All@@ -15,6 +16,13 @@ interface AnalyzeOptions {
*/
childVisitorKeys?: ReferencerOptions['childVisitorKeys'];

/**
* Which ECMAScript version is considered.
* Defaults to `2018`.
* `'latest'` is converted to 1e8 at parser.
*/
ecmaVersion?: EcmaVersion | 1e8;

/**
* Whether the whole script is executed under node.js environment.
* When enabled, the scope manager adds a function scope immediately following the global scope.
Expand All@@ -23,7 +31,7 @@ interface AnalyzeOptions {
globalReturn?: boolean;

/**
* Implied strict mode.
* Implied strict mode (if ecmaVersion >= 5).
* Defaults to `false`.
*/
impliedStrict?: boolean;
Expand All@@ -46,7 +54,7 @@ interface AnalyzeOptions {
/**
* The lib used by the project.
* This automatically defines a type variable for any types provided by the configured TS libs.
* Defaults to['esnext'].
* Defaults tothe lib for the provided `ecmaVersion`.
*
* https://www.typescriptlang.org/tsconfig#lib
*/
Expand All@@ -66,6 +74,7 @@ interface AnalyzeOptions {

const DEFAULT_OPTIONS: Required<AnalyzeOptions> = {
childVisitorKeys: visitorKeys,
ecmaVersion: 2018,
globalReturn: false,
impliedStrict: false,
jsxPragma: 'React',
Expand All@@ -75,16 +84,34 @@ const DEFAULT_OPTIONS: Required<AnalyzeOptions> = {
emitDecoratorMetadata: false,
};

/**
* Convert ecmaVersion to lib.
* `'latest'` is converted to 1e8 at parser.
*/
function mapEcmaVersion(version: EcmaVersion | 1e8 | undefined): Lib {
if (version == null || version === 3 || version === 5) {
return 'es5';
}

const year = version > 2000 ? version : 2015 + (version - 6);
const lib = `es${year}`;

return lib in TSLibraries ? (lib as Lib) : year > 2020 ? 'esnext' : 'es5';
}

/**
* Takes an AST and returns the analyzed scopes.
*/
function analyze(
tree: TSESTree.Node,
providedOptions?: AnalyzeOptions,
): ScopeManager {
const ecmaVersion =
providedOptions?.ecmaVersion ?? DEFAULT_OPTIONS.ecmaVersion;
const options: Required<AnalyzeOptions> = {
childVisitorKeys:
providedOptions?.childVisitorKeys ?? DEFAULT_OPTIONS.childVisitorKeys,
ecmaVersion,
globalReturn: providedOptions?.globalReturn ?? DEFAULT_OPTIONS.globalReturn,
impliedStrict:
providedOptions?.impliedStrict ?? DEFAULT_OPTIONS.impliedStrict,
Expand All@@ -95,7 +122,7 @@ function analyze(
jsxFragmentName:
providedOptions?.jsxFragmentName ?? DEFAULT_OPTIONS.jsxFragmentName,
sourceType: providedOptions?.sourceType ?? DEFAULT_OPTIONS.sourceType,
lib: providedOptions?.lib ?? ['esnext'],
lib: providedOptions?.lib ?? [mapEcmaVersion(ecmaVersion)],
emitDecoratorMetadata:
providedOptions?.emitDecoratorMetadata ??
DEFAULT_OPTIONS.emitDecoratorMetadata,
Expand Down
17 changes: 12 additions & 5 deletionspackages/scope-manager/src/referencer/Referencer.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -371,7 +371,9 @@ class Referencer extends Visitor {
}

protected BlockStatement(node: TSESTree.BlockStatement): void {
this.scopeManager.nestBlockScope(node);
if (this.scopeManager.isES6()) {
this.scopeManager.nestBlockScope(node);
}

this.visitChildren(node);

Expand DownExpand Up@@ -485,7 +487,7 @@ class Referencer extends Visitor {

protected ImportDeclaration(node: TSESTree.ImportDeclaration): void {
assert(
this.scopeManager.isModule(),
this.scopeManager.isES6() && this.scopeManager.isModule(),
'ImportDeclaration should appear when the mode is ES6 and in the module context.',
);

Expand DownExpand Up@@ -577,11 +579,14 @@ class Referencer extends Visitor {
this.scopeManager.nestFunctionScope(node, false);
}

if (this.scopeManager.isModule()) {
if (this.scopeManager.isES6() && this.scopeManager.isModule()) {
this.scopeManager.nestModuleScope(node);
}

if (this.scopeManager.isImpliedStrict()) {
if (
this.scopeManager.isStrictModeSupported() &&
this.scopeManager.isImpliedStrict()
) {
this.currentScope().isStrict = true;
}

Expand All@@ -596,7 +601,9 @@ class Referencer extends Visitor {
protected SwitchStatement(node: TSESTree.SwitchStatement): void {
this.visit(node.discriminant);

this.scopeManager.nestSwitchScope(node);
if (this.scopeManager.isES6()) {
this.scopeManager.nestSwitchScope(node);
}

for (const switchCase of node.cases) {
this.visit(switchCase);
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,7 @@ describe('ScopeManager.prototype.getDeclaredVariables', () => {
expectedNamesList: string[][],
): void {
const scopeManager = analyze(ast, {
ecmaVersion: 6,
sourceType: 'module',
});

Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@ import {
} from '../util';

describe('impliedStrict option', () => {
it('ensures all user scopes are strict', () => {
it('ensures all user scopes are strict if ecmaVersion >= 5', () => {
const { scopeManager } = parseAndAnalyze(
`
function foo() {
Expand All@@ -18,6 +18,7 @@ describe('impliedStrict option', () => {
}
`,
{
ecmaVersion: 5,
impliedStrict: true,
},
);
Expand All@@ -41,12 +42,38 @@ describe('impliedStrict option', () => {
expect(scope.isStrict).toBeTruthy();
});

it('ensures impliedStrict option is only effective when ecmaVersion option >= 5', () => {
const { scopeManager } = parseAndAnalyze(
`
function foo() {}
`,
{
ecmaVersion: 3,
impliedStrict: true,
},
);

expect(scopeManager.scopes).toHaveLength(2);

let scope = scopeManager.scopes[0];

expectToBeGlobalScope(scope);
expect(scope.block.type).toBe(AST_NODE_TYPES.Program);
expect(scope.isStrict).toBeFalsy();

scope = scopeManager.scopes[1];
expectToBeFunctionScope(scope);
expect(scope.block.type).toBe(AST_NODE_TYPES.FunctionDeclaration);
expect(scope.isStrict).toBeFalsy();
});

it('omits a nodejs global scope when ensuring all user scopes are strict', () => {
const { scopeManager } = parseAndAnalyze(
`
function foo() {}
`,
{
ecmaVersion: 5,
globalReturn: true,
impliedStrict: true,
},
Expand All@@ -73,6 +100,7 @@ describe('impliedStrict option', () => {

it('omits a module global scope when ensuring all user scopes are strict', () => {
const { scopeManager } = parseAndAnalyze('function foo() {}', {
ecmaVersion: 6,
impliedStrict: true,
sourceType: 'module',
});
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
import type { EcmaVersion, Lib, TSESTree } from '@typescript-eslint/types';

import { analyze } from '../../src/analyze';
import { Referencer } from '../../src/referencer';

jest.mock('../../src/referencer');
jest.mock('../../src/ScopeManager');

describe('ecma version mapping', () => {
it("should map to 'esnext' when unsuported and new", () => {
expectMapping(2042, 'esnext');
expectMapping(42, 'esnext');
});

it("should map to 'es5' when unsuported and old", () => {
expectMapping(2002, 'es5');
expectMapping(2, 'es5');
});

it("should map to 'es{year}' when supported and >= 6", () => {
expectMapping(2015, 'es2015');
expectMapping(6, 'es2015');
expectMapping(2020, 'es2020');
expectMapping(11, 'es2020');
});

it("should map to 'es5' when 5 or 3", () => {
expectMapping(5, 'es5');
expectMapping(3, 'es5');
});

it("should map to 'es2018' when undefined", () => {
expectMapping(undefined, 'es2018');
});

it("should map to 'esnext' when 'latest'", () => {
// `'latest'` is converted to 1e8 at parser.
expectMapping(1e8, 'esnext');
});
});

const fakeNode = {} as unknown as TSESTree.Node;

function expectMapping(ecmaVersion: number | undefined, lib: Lib): void {
(Referencer as jest.Mock).mockClear();
analyze(fakeNode, { ecmaVersion: ecmaVersion as EcmaVersion });
expect(Referencer).toHaveBeenCalledWith(
expect.objectContaining({ lib: [lib] }),
expect.any(Object),
);
}
1 change: 1 addition & 0 deletionspackages/scope-manager/tests/fixtures.test.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@ const ALLOWED_OPTIONS: Map<string, ALLOWED_VALUE> = new Map<
keyof AnalyzeOptions,
ALLOWED_VALUE
>([
['ecmaVersion', ['number']],
['globalReturn', ['boolean']],
['impliedStrict', ['boolean']],
['jsxPragma', ['string']],
Expand Down
4 changes: 4 additions & 0 deletionspackages/website/src/components/linter/WebLinter.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -111,6 +111,10 @@ export class WebLinter {
);

const scopeManager = this.lintUtils.analyze(ast, {
ecmaVersion:
eslintOptions.ecmaVersion === 'latest'
? 1e8
: eslintOptions.ecmaVersion,
globalReturn: eslintOptions.ecmaFeatures?.globalReturn ?? false,
sourceType: eslintOptions.sourceType ?? 'script',
});
Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp