diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 74ab1811..97a91d7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,3 +45,16 @@ jobs: run: npm run build - name: Run tests run: npm run test + are-the-types-wrong: + name: Are the types wrong? + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: 'lts/*' + - name: Install dependencies + run: npm install + - name: Are the types wrong? + run: npm run lint:types diff --git a/eslint.config.js b/eslint.config.js index 433c4f75..9fdba50c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -2,7 +2,9 @@ import { defineConfig, globalIgnores } from "eslint/config"; import eslintConfigESLint from "eslint-config-eslint"; import eslintConfigESLintFormatting from "eslint-config-eslint/formatting"; import eslintPluginChaiFriendly from "eslint-plugin-chai-friendly"; +import * as expectType from "eslint-plugin-expect-type"; import globals from "globals"; +import tsParser from "@typescript-eslint/parser"; export default defineConfig([ globalIgnores([ @@ -11,8 +13,10 @@ export default defineConfig([ "**/coverage/", "packages/espree/tools/create-test-example.js" ]), - eslintConfigESLint, - eslintConfigESLintFormatting, + { + files: ["**/*.{,c}js"], + extends: [eslintConfigESLint, eslintConfigESLintFormatting] + }, { files: ["packages/*/tests/lib/**"], languageOptions: { @@ -22,7 +26,7 @@ export default defineConfig([ } }, { - files: ["packages/eslint-scope/tests/**"], + files: ["packages/eslint-scope/tests/**/*.{,c}js"], languageOptions: { globals: { ...globals.mocha @@ -68,6 +72,21 @@ export default defineConfig([ } } }, + { + files: ["packages/eslint-scope/tests/types/*.{,c}ts"], + languageOptions: { + parser: tsParser, + parserOptions: { + project: ["packages/eslint-scope/tests/types/tsconfig.json"] + } + }, + plugins: { + "expect-type": expectType + }, + rules: { + "expect-type/expect": "error" + } + }, { files: ["**/tools/**"], rules: { diff --git a/package.json b/package.json index 1a47d83c..fbc9d8bc 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "test": "npm test --workspaces --if-present", "build": "npm run build --workspaces --if-present", "lint": "eslint", - "lint:fix": "eslint --fix" + "lint:fix": "eslint --fix", + "lint:types": "npm run lint:types --workspaces --if-present" }, "workspaces": [ "packages/*" @@ -16,15 +17,17 @@ "pre-commit": "lint-staged" }, "lint-staged": { - "*.{js,cjs}": [ + "*.{js,cjs,ts,cts}": [ "eslint --fix" ] }, "devDependencies": { + "@typescript-eslint/parser": "^8.47.0", "c8": "^10.1.3", "eslint": "^9.35.0", "eslint-config-eslint": "^13.0.0", "eslint-plugin-chai-friendly": "^1.0.0", + "eslint-plugin-expect-type": "^0.6.2", "globals": "^16.0.0", "lint-staged": "^15.2.0", "mocha": "^11.1.0", diff --git a/packages/eslint-scope/lib/index.d.cts b/packages/eslint-scope/lib/index.d.cts new file mode 100644 index 00000000..bcd8c305 --- /dev/null +++ b/packages/eslint-scope/lib/index.d.cts @@ -0,0 +1,776 @@ +/** + * @fileoverview This file contains the types for ESLint Scope. + * It was initially extracted from the DefinitelyTyped repository. + */ + +/* + * MIT License + * Copyright (c) Microsoft Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE + */ + +import * as eslint from "eslint"; +import { VisitorKeys } from "eslint-visitor-keys"; +import { Visitor, VisitorOptions } from "esrecurse"; +import * as ESTree from "estree"; + +/** + * Options for scope analysis. + */ +export interface AnalyzeOptions { + /** + * Whether to ignore eval() calls, which normally create scopes. + * @default false + */ + ignoreEval?: boolean; + + /** + * Whether to create a top-level function scope for CommonJS evaluation. + * @default false + */ + nodejsScope?: boolean; + + /** + * Whether to evaluate code in strict mode even outside modules or without "use strict". + * @default false + */ + impliedStrict?: boolean; + + /** + * The ECMAScript version to use for evaluation (e.g., 5, 2015, 2022). + * @default 5 + */ + ecmaVersion?: number; + + /** + * The type of JavaScript file to evaluate. + * @default "script" + */ + sourceType?: "script" | "module" | "commonjs"; + + /** + * Visitor key information for performance enhancement. + * @default null + */ + childVisitorKeys?: VisitorKeys | null; + + /** + * Strategy to use when childVisitorKeys is not specified. + * @default "iteration" + */ + fallback?: "iteration" | ((node: ESTree.Node) => string[]); + + /** + * Whether to enable optimistic scope analysis. + * @default false + */ + optimistic?: boolean; + + /** + * Enables the tracking of JSX components as variable references. + * @default false + */ + jsx?: boolean; +} + +export type PatternVisitorCallback = ( + pattern: ESTree.Identifier, + misc: { + topLevel: boolean; + rest: boolean; + assignments: ESTree.AssignmentPattern[]; + }, +) => void; + +/** + * Manages the scope hierarchy of an AST. + */ +export class ScopeManager implements eslint.Scope.ScopeManager { + /** + * Creates a new ScopeManager instance. + * @param options Options for scope analysis. + */ + constructor(options: AnalyzeOptions); + + /** + * The global scope. + */ + globalScope: GlobalScope; + + /** + * All scopes in the analyzed program. + */ + scopes: Scope[]; + + /** + * Adds variables to the global scope and resolves references to them. + * @param names An array of strings, the names of variables to add to the global scope. + * @returns void + */ + addGlobals(names: ReadonlyArray): void; + + /** + * Acquires the scope for a given node. + * @param node The AST node to get the scope for. + * @param inner Whether to get the innermost scope. + * @returns The scope or null if not found. + */ + acquire(node: ESTree.Node, inner?: boolean): Scope | null; + + /** + * acquire all scopes from node. + * @param node node for the acquired scope. + * @returns Scope array + * @deprecated + */ + acquireAll(node: ESTree.Node): Scope[] | null; + + /** + * Releases a scope, moving to its parent. + * @param node The AST node to release the scope for. + * @param inner Whether to release the innermost scope. + * @returns The parent scope or null if not found. + */ + release(node: ESTree.Node, inner?: boolean): Scope | null; + + /** + * Gets all scopes for a given node, including parents. + * @param node The AST node to get scopes for. + * @param inner Whether to start from the innermost scope. + * @returns Array of scopes or empty array if none found. + */ + getDeclaredVariables(node: ESTree.Node): Variable[]; + + isGlobalReturn(): boolean; + + /** @deprecated */ + isModule(): boolean; + + /** @deprecated */ + isImpliedStrict(): boolean; + + /** @deprecated */ + isStrictModeSupported(): boolean; +} + +/** + * Base export class for all scopes. + */ +export class Scope + implements eslint.Scope.Scope +{ + /** + * Creates a new Scope instance. + * @param scopeManager The scope manager this scope belongs to. + * @param type The type of the scope. + * @param upperScope The parent scope, or null for the global scope. + * @param block The AST node that created this scope. + * @param isMethodDefinition Whether this scope is for a method definition. + */ + constructor( + scopeManager: ScopeManager, + type: string, + upperScope: Scope | null, + block: ESTree.Node, + isMethodDefinition: boolean, + ); + + /** + * The type of the scope (e.g., 'global', 'function'). + */ + type: eslint.Scope.Scope["type"]; + + /** + * Whether the scope is in strict mode. + */ + isStrict: boolean; + + /** + * The parent scope, or null for the global scope. + */ + upper: Scope | null; + + /** + * The scope where variables are declared (same as this for most scopes). + */ + variableScope: Scope; + + /** + * Variables defined in this scope. + */ + variables: TVariable[]; + + /** + * References to variables in this scope. + */ + references: TReference[]; + + /** + * Child scopes. + */ + childScopes: Scope[]; + + /** + * The AST node that created this scope. + */ + block: ESTree.Node; + + /** + * Whether this is a function expression scope. + */ + functionExpressionScope: boolean; + + /** + * Implicit references (e.g., 'arguments' in functions). + */ + implicit: { left: TReference[]; set: Map; variables: Variable[] }; + + /** + * Map of variable names to variables. + */ + set: eslint.Scope.Scope["set"]; + + /** + * The tainted variables of this scope. + * @deprecated + */ + taints: Map; + + /** + * References that pass through this scope to outer scopes. + */ + through: eslint.Scope.Scope["through"]; + + /** + * Dynamic flag for certain scope types. + * @deprecated + */ + dynamic: boolean; + + /** + * Direct call to eval() flag. + * @deprecated + */ + directCallToEvalScope: boolean; + + /** + * This scope flag. + * @deprecated + */ + thisFound: boolean; + + /** + * Resolves a reference in this scope. + * @param ident An AST node to get their reference object. + * @deprecated + */ + resolve(ident: ESTree.Identifier): Reference | null; + + /** + * Whether the reference is static. + * @deprecated + */ + isStatic(): boolean; + + /** + * Returns whether this scope has materialized arguments. + * @deprecated + */ + isArgumentsMaterialized(): boolean; + + /** + * Returns whether this scope has materialized `this` reference. + * @deprecated + */ + isThisMaterialized(): boolean; + + /** @deprecated */ + isUsedName(name: string): boolean; +} + +/** + * Global scope. + */ +export class GlobalScope extends Scope { + /** + * Creates a new GlobalScope instance. + * @param scopeManager The scope manager this scope belongs to. + * @param block The AST node that created this scope. + */ + constructor(scopeManager: ScopeManager, block: ESTree.Node); + + type: "global"; + + functionExpressionScope: false; +} + +/** + * Module scope. + */ +export class ModuleScope extends Scope { + /** + * Creates a new ModuleScope instance. + * @param scopeManager The scope manager this scope belongs to. + * @param upperScope The parent scope. + * @param block The AST node that created this scope. + */ + constructor(scopeManager: ScopeManager, upperScope: Scope, block: ESTree.Node); + + type: "module"; + + functionExpressionScope: false; +} + +/** + * Function expression name scope. + */ +export class FunctionExpressionNameScope extends Scope { + /** + * Creates a new FunctionExpressionNameScope instance. + * @param scopeManager The scope manager this scope belongs to. + * @param upperScope The parent scope. + * @param block The AST node that created this scope. + */ + constructor(scopeManager: ScopeManager, upperScope: Scope, block: ESTree.Node); + + type: "function-expression-name"; + + functionExpressionScope: true; +} + +/** + * Catch scope. + */ +export class CatchScope extends Scope { + /** + * Creates a new CatchScope instance. + * @param scopeManager The scope manager this scope belongs to. + * @param upperScope The parent scope. + * @param block The AST node that created this scope. + */ + constructor(scopeManager: ScopeManager, upperScope: Scope, block: ESTree.Node); + + type: "catch"; + + functionExpressionScope: false; +} + +/** + * With scope. + */ +export class WithScope extends Scope { + /** + * Creates a new WithScope instance. + * @param scopeManager The scope manager this scope belongs to. + * @param upperScope The parent scope. + * @param block The AST node that created this scope. + */ + constructor(scopeManager: ScopeManager, upperScope: Scope, block: ESTree.Node); + + type: "with"; + + functionExpressionScope: false; +} + +/** + * Block scope. + */ +export class BlockScope extends Scope { + /** + * Creates a new BlockScope instance. + * @param scopeManager The scope manager this scope belongs to. + * @param upperScope The parent scope. + * @param block The AST node that created this scope. + */ + constructor(scopeManager: ScopeManager, upperScope: Scope, block: ESTree.Node); + + type: "block"; + + functionExpressionScope: false; +} + +/** + * Switch scope. + */ +export class SwitchScope extends Scope { + /** + * Creates a new SwitchScope instance. + * @param scopeManager The scope manager this scope belongs to. + * @param upperScope The parent scope. + * @param block The AST node that created this scope. + */ + constructor(scopeManager: ScopeManager, upperScope: Scope, block: ESTree.Node); + + type: "switch"; + + functionExpressionScope: false; +} + +/** + * Function scope. + */ +export class FunctionScope extends Scope { + /** + * Creates a new FunctionScope instance. + * @param scopeManager The scope manager this scope belongs to. + * @param upperScope The parent scope. + * @param block The AST node that created this scope. + * @param isMethodDefinition Whether this scope is for a method definition. + */ + constructor( + scopeManager: ScopeManager, + upperScope: Scope, + block: ESTree.Node, + isMethodDefinition: boolean, + ); + + type: "function"; + + functionExpressionScope: false; +} + +/** + * Scope of for, for-in, and for-of statements. + */ +export class ForScope extends Scope { + /** + * Creates a new ForScope instance. + * @param scopeManager The scope manager this scope belongs to. + * @param upperScope The parent scope. + * @param block The AST node that created this scope. + */ + constructor(scopeManager: ScopeManager, upperScope: Scope, block: ESTree.Node); + + type: "for"; + + functionExpressionScope: false; +} + +/** + * Class scope. + */ +export class ClassScope extends Scope { + /** + * Creates a new ClassScope instance. + * @param scopeManager The scope manager this scope belongs to. + * @param upperScope The parent scope. + * @param block The AST node that created this scope. + */ + constructor(scopeManager: ScopeManager, upperScope: Scope, block: ESTree.Node); + + type: "class"; + + functionExpressionScope: false; +} + +/** + * Class field initializer scope. + */ +export class ClassFieldInitializerScope extends Scope { + /** + * Creates a new ClassFieldInitializerScope instance. + * @param scopeManager The scope manager this scope belongs to. + * @param upperScope The parent scope. + * @param block The AST node that created this scope. + */ + constructor(scopeManager: ScopeManager, upperScope: Scope, block: ESTree.Node); + + type: "class-field-initializer"; + + functionExpressionScope: false; +} + +/** + * Class static block scope. + */ +export class ClassStaticBlockScope extends Scope { + /** + * Creates a new ClassStaticBlockScope instance. + * @param scopeManager The scope manager this scope belongs to. + * @param upperScope The parent scope. + * @param block The AST node that created this scope. + */ + constructor(scopeManager: ScopeManager, upperScope: Scope, block: ESTree.Node); + + type: "class-static-block"; + + functionExpressionScope: false; +} + +/** + * Represents a variable in a scope. + */ +export class Variable implements eslint.Scope.Variable { + /** + * Creates a new Variable instance. + * @param name The name of the variable. + * @param scope The scope where the variable is defined. + */ + constructor(name: string, scope: Scope); + + /** + * The name of the variable. + */ + name: string; + + /** + * The scope where the variable is defined. + */ + scope: Scope; + + /** + * Identifiers that declare this variable. + */ + identifiers: ESTree.Identifier[]; + + /** + * References to this variable. + */ + references: TReference[]; + + /** + * Definitions of this variable. + */ + defs: eslint.Scope.Definition[]; + + /** + * Whether the variable is tainted (e.g., potentially modified externally). + * @deprecated + */ + tainted: boolean; + + /** + * Stack flag for certain variable types. + * @deprecated + */ + stack: boolean; +} + +/** + * Represents a reference to a variable. + */ +export class Reference implements eslint.Scope.Reference { + /** + * Creates a new Reference instance. + * @param ident The identifier node of the reference. + * @param scope The scope where the reference occurs. + * @param flag The reference flag (read, write, or read-write). + * @param writeExpr The expression being written, if applicable. + * @param maybeImplicitGlobal Information about a possible global variable, if applicable. + * @param partial Whether this is a partial reference. + * @param init Whether this is an initialization reference. + */ + constructor( + ident: ESTree.Identifier, + scope: Scope, + flag: number, + writeExpr: ESTree.Expression | null, + maybeImplicitGlobal: { pattern: ESTree.Pattern; node: ESTree.Node } | null, + partial: boolean, + init: boolean, + ); + + /** + * The identifier node of the reference. + */ + identifier: ESTree.Identifier; + + /** + * The variable being referenced, or null if unresolved. + */ + resolved: Variable | null; + + /** + * Whether the reference is static. + * @deprecated + */ + isStatic(): boolean; + + /** + * Whether this is a write operation. + */ + isWrite: eslint.Scope.Reference["isWrite"]; + + /** + * Whether this is a read operation. + */ + isRead: eslint.Scope.Reference["isRead"]; + + /** + * The scope where the reference occurs. + */ + from: Scope; + + /** + * Whether the reference comes from a dynamic scope (such as 'eval', + * 'with', etc.), and may be trapped by dynamic scopes. + * @deprecated + */ + tainted: boolean; + + /** + * The expression being written, if applicable. + */ + writeExpr: ESTree.Expression | null; + + /** + * Whether this is a partial reference. + * @deprecated + */ + partial: boolean; + + /** + * Whether this is an initialization reference. + */ + init: boolean; + + /** + * Whether this reference is only read. + * @returns True if the reference is read-only. + */ + isReadOnly(): boolean; + + /** + * Whether this reference is only written. + * @returns True if the reference is write-only. + */ + isWriteOnly(): boolean; + + /** + * Whether this reference is read-write. + * @returns True if the reference is read-write. + */ + isReadWrite(): boolean; + + /** @deprecated */ + flag: 1 | 2 | 3; +} + +/** + * Represents a variable definition. + * @todo extends eslint.Scope.Definition for this class + */ +export class Definition { + /** + * Creates a new Definition instance. + * @param type The type of definition (e.g., 'Variable', 'Parameter'). + * @param name The identifier node of the definition. + * @param node The AST node where the definition occurs. + * @param parent The parent node, if applicable. + * @param index The index of the definition in a pattern, if applicable. + * @param kind The kind of variable (e.g., 'var', 'let', 'const'), if applicable. + */ + constructor( + type: eslint.Scope.Definition["type"], + name: eslint.Scope.Definition["name"], + node: eslint.Scope.Definition["node"], + parent: eslint.Scope.Definition["parent"], + index: number | null, + kind: string | null, + ); + + /** + * The type of definition (e.g., 'Variable', 'Parameter'). + */ + type: eslint.Scope.Definition["type"]; + + /** + * The identifier node of the definition. + */ + name: eslint.Scope.Definition["name"]; + + /** + * The AST node where the definition occurs. + */ + node: eslint.Scope.Definition["node"]; + + /** + * The parent node, if applicable. + */ + parent: eslint.Scope.Definition["parent"]; + + /** + * The index of the definition in a pattern, if applicable. + * @deprecated + */ + index: number | null; + + /** + * The kind of variable (e.g., 'var', 'let', 'const'), if applicable. + * @deprecated + */ + kind: string | null; +} + +/** + * Visitor for destructuring patterns. + */ +export class PatternVisitor extends Visitor { + static isPattern(node: ESTree.Node): node is + | ESTree.Identifier + | ESTree.ObjectPattern + | ESTree.ArrayPattern + | ESTree.SpreadElement + | ESTree.RestElement + | ESTree.AssignmentPattern; + + constructor( + options: VisitorOptions | null | undefined, + rootPattern: ESTree.Pattern, + callback: PatternVisitorCallback, + ); + + rootPattern: ESTree.Pattern; + + callback: PatternVisitorCallback; + + assignments: ESTree.AssignmentPattern[]; + + rightHandNodes: ESTree.Expression[]; + + restElements: ESTree.RestElement[]; + + Identifier(pattern: ESTree.Identifier): void; + + Property(pattern: ESTree.Property): void; + + ArrayPattern(pattern: ESTree.ArrayPattern): void; + + AssignmentPattern(pattern: ESTree.AssignmentPattern): void; + + RestElement(pattern: ESTree.RestElement): void; + + MemberExpression(pattern: ESTree.MemberExpression): void; + + SpreadElement(pattern: ESTree.SpreadElement): void; + + ArrayExpression(pattern: ESTree.SpreadElement): void; + + AssignmentExpression(pattern: ESTree.AssignmentExpression): void; + + CallExpression(pattern: ESTree.CallExpression): void; +} + +/** + * Analyzes the scope of an AST. + * @param ast The ESTree-compliant AST to analyze. + * @param options Options for scope analysis. + * @returns The scope manager for the analyzed AST. + */ +export function analyze(ast: ESTree.Program, options?: AnalyzeOptions): ScopeManager; diff --git a/packages/eslint-scope/lib/index.d.ts b/packages/eslint-scope/lib/index.d.ts new file mode 100644 index 00000000..def35507 --- /dev/null +++ b/packages/eslint-scope/lib/index.d.ts @@ -0,0 +1,6 @@ +/** + * @fileoverview ESLint Scope types in ESM format. + * @author Francesco Trotta + */ + +export * from "./index.cjs"; diff --git a/packages/eslint-scope/package.json b/packages/eslint-scope/package.json index 764de6f1..ca141284 100644 --- a/packages/eslint-scope/package.json +++ b/packages/eslint-scope/package.json @@ -3,11 +3,18 @@ "description": "ECMAScript scope analyzer for ESLint", "homepage": "https://github.com/eslint/js/blob/main/packages/eslint-scope/README.md", "main": "./dist/eslint-scope.cjs", + "types": "./lib/index.d.cts", "type": "module", "exports": { ".": { - "import": "./lib/index.js", - "require": "./dist/eslint-scope.cjs" + "import": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + }, + "require": { + "types": "./lib/index.d.cts", + "default": "./dist/eslint-scope.cjs" + } }, "./package.json": "./package.json" }, @@ -31,9 +38,11 @@ "scripts": { "build": "rollup -c", "build:update-version": "node tools/update-version.js", + "lint:types": "attw --pack", "prepublishOnly": "npm run build:update-version && npm run build", "pretest": "npm run build", - "test": "node Makefile.js test" + "test": "node Makefile.js test && npm run test:types", + "test:types": "tsc -p tests/types/tsconfig.json" }, "files": [ "LICENSE", @@ -42,12 +51,16 @@ "dist/eslint-scope.cjs" ], "dependencies": { + "@types/espree": "*", + "@types/esrecurse": "*", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "devDependencies": { + "@arethetypeswrong/cli": "^0.18.2", "@typescript-eslint/parser": "^8.7.0", "chai": "^6.0.0", + "eslint": ">=10.0.0-alpha.0 <10.0.0 || ^10.0.0", "eslint-visitor-keys": "^5.0.0", "espree": "^11.0.0", "npm-license": "^0.3.3", diff --git a/packages/eslint-scope/tests/types/cjs-import.test.cts b/packages/eslint-scope/tests/types/cjs-import.test.cts new file mode 100644 index 00000000..ffde5079 --- /dev/null +++ b/packages/eslint-scope/tests/types/cjs-import.test.cts @@ -0,0 +1,16 @@ +/** + * @fileoverview CommonJS type import test for ESLint Scope package. + * @author Francesco Trotta + */ + +//----------------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------------- + +import { + Definition, + Reference, + ScopeManager, + Scope, + Variable +} from "eslint-scope"; diff --git a/packages/eslint-scope/tests/types/tsconfig.json b/packages/eslint-scope/tests/types/tsconfig.json new file mode 100644 index 00000000..ddfb4d95 --- /dev/null +++ b/packages/eslint-scope/tests/types/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "nodenext", + "lib": [ + "es6" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "exactOptionalPropertyTypes": true, + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "include": [".", "../../lib"] +} diff --git a/packages/eslint-scope/tests/types/types.test.ts b/packages/eslint-scope/tests/types/types.test.ts new file mode 100644 index 00000000..b7269771 --- /dev/null +++ b/packages/eslint-scope/tests/types/types.test.ts @@ -0,0 +1,474 @@ +/** + * @fileoverview This file contains code to test the ESLint Scope types. + * It was initially extracted from the DefinitelyTyped repository. + */ + +/* + * MIT License + * Copyright (c) Microsoft Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE + */ + +import * as eslintScope from "eslint-scope"; +import type { AnalyzeOptions } from "eslint-scope"; +import * as espree from "espree"; +import * as estree from "estree"; + +const code = ` +function example() { + let x = 1; + console.log(x); +} +`; + +const ast = espree.parse(code, { ecmaVersion: 2022, sourceType: "module" }) as estree.Program; + +// $ExpectType ScopeManager +const scopeManager = eslintScope.analyze( + ast, + { + ecmaVersion: 2022, + sourceType: "module", + ignoreEval: true, + nodejsScope: false, + impliedStrict: false, + childVisitorKeys: null, + fallback: "iteration", + } satisfies AnalyzeOptions, +); + +// $ExpectType GlobalScope +scopeManager.globalScope; +// $ExpectType Scope, Reference>[] +scopeManager.scopes; + +// $ExpectType Scope, Reference> | null +const scope = scopeManager.acquire(ast); + +// $ExpectType Scope, Reference> | null +scopeManager.release(ast); + +if (scope) { + (( + type: + | "function" + | "module" + | "block" + | "catch" + | "class" + | "class-field-initializer" + | "class-static-block" + | "for" + | "function-expression-name" + | "global" + | "switch" + | "with", + ) => type satisfies typeof scope.type); + // $ExpectType boolean + scope.isStrict; + // $ExpectType Scope, Reference> | null + scope.upper; + // $ExpectType Scope, Reference> + scope.variableScope; + // $ExpectType Variable[] + scope.variables; + // $ExpectType Reference[] + scope.references; + // $ExpectType Scope, Reference>[] + scope.childScopes; + // $ExpectType Node + scope.block; + // $ExpectType boolean + scope.functionExpressionScope; + // $ExpectType Reference[] + scope.implicit.left; + // $ExpectType Map> + scope.implicit.set; + // $ExpectType Variable[] + scope.implicit.variables; + // $ExpectType Map + scope.set; + // $ExpectType Reference[] + scope.through; +} + +const variable = scope?.variables[0]; +if (variable) { + // $ExpectType string + variable.name; + // $ExpectType Scope, Reference> + variable.scope; + // $ExpectType Identifier[] + variable.identifiers; + // $ExpectType Reference[] + variable.references; + // $ExpectType Definition[] + variable.defs; +} + +const reference = scope?.references[0]; +if (reference) { + // $ExpectType Identifier + reference.identifier; + // $ExpectType Variable | null + reference.resolved; + // $ExpectType () => boolean + reference.isWrite; + // $ExpectType () => boolean + reference.isRead; + // $ExpectType Scope, Reference> + reference.from; +} + +const definition = variable?.defs[0]; +if (definition) { + (( + type: + | "CatchClause" + | "ClassName" + | "FunctionName" + | "ImplicitGlobalVariable" + | "ImportBinding" + | "Parameter" + | "Variable", + ) => type satisfies typeof definition.type); + // $ExpectType Identifier + definition.name; + // $ExpectType ImportDeclaration | VariableDeclaration | null + definition.parent; +} + +// $ExpectType GlobalScope +const globalScope = scopeManager.globalScope; +// $ExpectType 'global' +globalScope.type; + +// $ExpectType ScopeManager +eslintScope.analyze(ast); + +const identifier: estree.Identifier = { + type: "Identifier", + name: "foo", +}; +const definition2 = new eslintScope.Definition( + "Variable", + identifier, + ast, + null, + null, + "let", +); +(( + type: + | "CatchClause" + | "ClassName" + | "FunctionName" + | "ImplicitGlobalVariable" + | "ImportBinding" + | "Parameter" + | "Variable", +) => type satisfies typeof definition2.type); +// $ExpectType Identifier +definition2.name; + +const blockScope = new eslintScope.BlockScope(scopeManager, scopeManager.globalScope, ast); +// $ExpectType "block" +blockScope.type; +// $ExpectType false +blockScope.functionExpressionScope; + +const catchScope = new eslintScope.CatchScope(scopeManager, scopeManager.globalScope, ast); +// $ExpectType "catch" +catchScope.type; +// $ExpectType false +catchScope.functionExpressionScope; + +const classFieldInitializerScope = new eslintScope.ClassFieldInitializerScope( + scopeManager, + scopeManager.globalScope, + ast, +); +// $ExpectType "class-field-initializer" +classFieldInitializerScope.type; +// $ExpectType false +classFieldInitializerScope.functionExpressionScope; + +const classScope = new eslintScope.ClassScope(scopeManager, scopeManager.globalScope, ast); +// $ExpectType "class" +classScope.type; +// $ExpectType false +classScope.functionExpressionScope; + +const classStaticBlockScope = new eslintScope.ClassStaticBlockScope( + scopeManager, + scopeManager.globalScope, + ast, +); +// $ExpectType "class-static-block" +classStaticBlockScope.type; +// $ExpectType false +classStaticBlockScope.functionExpressionScope; + +const forScope = new eslintScope.ForScope(scopeManager, scopeManager.globalScope, ast); +// $ExpectType "for" +forScope.type; +// $ExpectType false +forScope.functionExpressionScope; + +const functionExpressionNameScope = new eslintScope.FunctionExpressionNameScope( + scopeManager, + scopeManager.globalScope, + ast, +); +// $ExpectType "function-expression-name" +functionExpressionNameScope.type; +// $ExpectType true +functionExpressionNameScope.functionExpressionScope; + +const functionScope = new eslintScope.FunctionScope(scopeManager, scopeManager.globalScope, ast, false); +// $ExpectType "function" +functionScope.type; +// $ExpectType false +functionScope.functionExpressionScope; + +const globalScopeInstance = new eslintScope.GlobalScope(scopeManager, ast); +// $ExpectType "global" +globalScopeInstance.type; +// $ExpectType false +globalScopeInstance.functionExpressionScope; + +const moduleScope = new eslintScope.ModuleScope(scopeManager, scopeManager.globalScope, ast); +// $ExpectType "module" +moduleScope.type; +// $ExpectType false +moduleScope.functionExpressionScope; + +const switchScope = new eslintScope.SwitchScope(scopeManager, scopeManager.globalScope, ast); +// $ExpectType "switch" +switchScope.type; +// $ExpectType false +switchScope.functionExpressionScope; + +const withScope = new eslintScope.WithScope(scopeManager, scopeManager.globalScope, ast); +// $ExpectType "with" +withScope.type; +// $ExpectType false +withScope.functionExpressionScope; + +const ref = new eslintScope.Reference( + identifier, + scopeManager.globalScope, + 0, + null, + null, + false, + false, +); +// $ExpectType Identifier +ref.identifier; +// $ExpectType Scope, Reference> +ref.from; +// $ExpectType boolean +ref.isRead(); +// $ExpectType boolean +ref.isWrite(); +// $ExpectType boolean +ref.isReadOnly(); +// $ExpectType boolean +ref.isWriteOnly(); +// $ExpectType boolean +ref.isReadWrite(); + +const scopeInstance = new eslintScope.Scope( + scopeManager, + "block", + null, + ast, + false, +); +(( + type: + | "function" + | "module" + | "block" + | "catch" + | "class" + | "class-field-initializer" + | "class-static-block" + | "for" + | "function-expression-name" + | "global" + | "switch" + | "with", +) => type satisfies typeof scopeInstance.type); +// $ExpectType boolean +scopeInstance.isStrict; +// $ExpectType Scope, Reference> | null +scopeInstance.upper; +// $ExpectType Scope, Reference> +scopeInstance.variableScope; +// $ExpectType Variable[] +scopeInstance.variables; +// $ExpectType Reference[] +scopeInstance.references; +// $ExpectType Scope, Reference>[] +scopeInstance.childScopes; +// $ExpectType Node +scopeInstance.block; +// $ExpectType boolean +scopeInstance.functionExpressionScope; +// $ExpectType { left: Reference[]; set: Map>; variables: Variable[]; } +scopeInstance.implicit; +// $ExpectType Map +scopeInstance.set; +// $ExpectType Map> +scopeInstance.taints; +// $ExpectType Reference[] +scopeInstance.through; +// $ExpectType boolean +scopeInstance.dynamic; +// $ExpectType boolean +scopeInstance.directCallToEvalScope; +// $ExpectType boolean +scopeInstance.thisFound; +// $ExpectType Reference | null +scopeInstance.resolve(identifier); +// $ExpectType boolean +scopeInstance.isStatic(); +// $ExpectType boolean +scopeInstance.isArgumentsMaterialized(); +// $ExpectType boolean +scopeInstance.isThisMaterialized(); +// $ExpectType boolean +scopeInstance.isUsedName("foo"); + +const scopeManagerInstance = new eslintScope.ScopeManager({ + ecmaVersion: 2022, + sourceType: "module", +}); +// $ExpectType GlobalScope +scopeManagerInstance.globalScope; +// $ExpectType Scope, Reference>[] +scopeManagerInstance.scopes; +// $ExpectType void +scopeManagerInstance.addGlobals(["window", "self"]); +// $ExpectType Scope, Reference> | null +scopeManagerInstance.acquire(ast); +// $ExpectType Scope, Reference>[] | null +scopeManagerInstance.acquireAll(ast); +// $ExpectType Scope, Reference> | null +scopeManagerInstance.release(ast); +// $ExpectType Variable[] +scopeManagerInstance.getDeclaredVariables(ast); +// $ExpectType boolean +scopeManagerInstance.isGlobalReturn(); +// $ExpectType boolean +scopeManagerInstance.isModule(); +// $ExpectType boolean +scopeManagerInstance.isImpliedStrict(); +// $ExpectType boolean +scopeManagerInstance.isStrictModeSupported(); + +const variableInstance = new eslintScope.Variable("foo", scopeInstance); +// $ExpectType string +variableInstance.name; +// $ExpectType Scope, Reference> +variableInstance.scope; +// $ExpectType Identifier[] +variableInstance.identifiers; +// $ExpectType Reference[] +variableInstance.references; +// $ExpectType Definition[] +variableInstance.defs; +// $ExpectType boolean +variableInstance.tainted; +// $ExpectType boolean +variableInstance.stack; + +let node: any; +if (eslintScope.PatternVisitor.isPattern(node)) { + // $ExpectType Identifier | ObjectPattern | ArrayPattern | SpreadElement | RestElement | AssignmentPattern + node; +} + +declare let rootPattern: estree.Pattern; + +// // $ExpectType PatternVisitor +const patternVisitor = new eslintScope.PatternVisitor( + { + fallback: (node: any) => Object.keys(node).filter((key) => key !== "parent"), + childVisitorKeys: { TestExpression: ["argument"] }, + }, + rootPattern, + (pattern, misc) => { + // $ExpectType Identifier + pattern; + // $ExpectType AssignmentPattern[] + misc.assignments; + // $ExpectType boolean + misc.rest; + // $ExpectType boolean + misc.topLevel; + }, +); + +// $ExpectType Pattern +patternVisitor.rootPattern; + +// $ExpectType PatternVisitorCallback +patternVisitor.callback; + +// $ExpectType AssignmentPattern[] +patternVisitor.assignments; + +// $ExpectType Expression[] +patternVisitor.rightHandNodes; + +// $ExpectType RestElement[] +patternVisitor.restElements; + +// $ExpectType (pattern: Identifier) => void +patternVisitor.Identifier; + +// $ExpectType (pattern: Property) => void +patternVisitor.Property; + +// $ExpectType (pattern: ArrayPattern) => void +patternVisitor.ArrayPattern; + +// $ExpectType (pattern: AssignmentPattern) => void +patternVisitor.AssignmentPattern; + +// $ExpectType (pattern: RestElement) => void +patternVisitor.RestElement; + +// $ExpectType (pattern: MemberExpression) => void +patternVisitor.MemberExpression; + +// $ExpectType (pattern: SpreadElement) => void +patternVisitor.SpreadElement; + +// $ExpectType (pattern: SpreadElement) => void +patternVisitor.ArrayExpression; + +// $ExpectType (pattern: AssignmentExpression) => void +patternVisitor.AssignmentExpression; + +// $ExpectType (pattern: CallExpression) => void +patternVisitor.CallExpression;