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/definition.js b/packages/eslint-scope/lib/definition.js index 9744ef48..12c227aa 100644 --- a/packages/eslint-scope/lib/definition.js +++ b/packages/eslint-scope/lib/definition.js @@ -24,8 +24,12 @@ import Variable from "./variable.js"; +/** @import * as types from "eslint-scope" */ + +// Cannot implement `types.Definition` directly because it contains a union. /** * @constructor Definition + * @implements {Omit} */ class Definition { constructor(type, name, node, parent, index, kind) { diff --git a/packages/eslint-scope/lib/index.d.cts b/packages/eslint-scope/lib/index.d.cts new file mode 100644 index 00000000..f873b729 --- /dev/null +++ b/packages/eslint-scope/lib/index.d.cts @@ -0,0 +1,807 @@ +/** + * @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 { 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 { + /** + * Creates a new ScopeManager instance. + * @param options Options for scope analysis. + */ + constructor(options: AnalyzeOptions); + + /** + * The global scope, initially set to `null`. + */ + globalScope: GlobalScope | null; + + /** + * 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 { + /** + * 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: + | "block" + | "catch" + | "class" + | "class-field-initializer" + | "class-static-block" + | "for" + | "function" + | "function-expression-name" + | "global" + | "module" + | "switch" + | "with"; + + /** + * 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; + + /** + * Map of variable names to variables. + */ + set: Map; + + /** + * The tainted variables of this scope. + * @deprecated + */ + taints: Map; + + /** + * References that pass through this scope to outer scopes. + */ + through: TReference[]; + + /** + * 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): TReference | 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; + + /** + * Implicit references (e.g., 'arguments' in functions). + */ + implicit: { left: Reference[]; set: Map; variables: Variable[] }; +} + +/** + * 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 { + /** + * 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: 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 { + /** + * 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(): boolean; + + /** + * Whether this is a read operation. + */ + isRead(): boolean; + + /** + * 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. + */ +export const 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. + */ + new ( + type: Definition["type"], + name: ESTree.Identifier, + node: Definition["node"], + parent: Definition["parent"], + index: number | null, + kind: string | null, + ): Definition; +}; + +export type Definition = ( + | { type: "CatchClause"; node: ESTree.CatchClause; parent: null } + | { + type: "ClassName"; + node: ESTree.ClassDeclaration | ESTree.ClassExpression; + parent: null; + } | { + type: "FunctionName"; + node: ESTree.FunctionDeclaration | ESTree.FunctionExpression; + parent: null; + } | { + type: "ImplicitGlobalVariable"; + node: + | ESTree.AssignmentExpression + | ESTree.ForInStatement + | ESTree.ForOfStatement; + parent: null; + } | { + type: "ImportBinding"; + node: + | ESTree.ImportSpecifier + | ESTree.ImportDefaultSpecifier + | ESTree.ImportNamespaceSpecifier; + parent: ESTree.ImportDeclaration; + } | { + type: "Parameter"; + node: + | ESTree.FunctionDeclaration + | ESTree.FunctionExpression + | ESTree.ArrowFunctionExpression; + parent: null; + } | { + type: "Variable"; + node: ESTree.VariableDeclarator; + parent: ESTree.VariableDeclaration; + }) & { + + /** + * The identifier node of the definition. + */ + name: ESTree.Identifier; + + /** + * 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: Array; + + rightHandNodes: ESTree.Node[]; + + 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.ArrayExpression): 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/lib/index.js b/packages/eslint-scope/lib/index.js index 85f8b510..268ae650 100644 --- a/packages/eslint-scope/lib/index.js +++ b/packages/eslint-scope/lib/index.js @@ -55,6 +55,8 @@ import Variable from "./variable.js"; import eslintScopeVersion from "./version.js"; +/** @import ESTree from "estree" */ + /** * Set the default options * @returns {Object} options @@ -73,9 +75,9 @@ function defaultOptions() { /** * Preform deep update on option object - * @param {Object} target Options - * @param {Object} override Updates - * @returns {Object} Updated options + * @param {Record} target Options + * @param {Record} override Updates + * @returns {Record} Updated options */ function updateDeeply(target, override) { @@ -110,7 +112,7 @@ function updateDeeply(target, override) { * Main interface function. Takes an Espree syntax tree and returns the * analyzed scopes. * @function analyze - * @param {espree.Tree} tree Abstract Syntax Tree + * @param {ESTree.Program} tree Abstract Syntax Tree * @param {Object} providedOptions Options that tailor the scope analysis * @param {boolean} [providedOptions.optimistic=false] the optimistic flag * @param {boolean} [providedOptions.ignoreEval=false] whether to check 'eval()' calls diff --git a/packages/eslint-scope/lib/pattern-visitor.js b/packages/eslint-scope/lib/pattern-visitor.js index 367a3773..f870f6c9 100644 --- a/packages/eslint-scope/lib/pattern-visitor.js +++ b/packages/eslint-scope/lib/pattern-visitor.js @@ -25,6 +25,8 @@ import estraverse from "estraverse"; import esrecurse from "esrecurse"; +/** @import * as types from "eslint-scope" */ + const { Syntax } = estraverse; /** @@ -38,6 +40,7 @@ function getLast(xs) { /** * Visitor for destructuring patterns. + * @implements {types.PatternVisitor} */ class PatternVisitor extends esrecurse.Visitor { static isPattern(node) { diff --git a/packages/eslint-scope/lib/reference.js b/packages/eslint-scope/lib/reference.js index e657d628..3f85b164 100644 --- a/packages/eslint-scope/lib/reference.js +++ b/packages/eslint-scope/lib/reference.js @@ -22,6 +22,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/** @import * as types from "eslint-scope" */ + const READ = 0x1; const WRITE = 0x2; const RW = READ | WRITE; @@ -29,6 +31,7 @@ const RW = READ | WRITE; /** * A Reference represents a single occurrence of an identifier in code. * @constructor Reference + * @implements {types.Reference} */ class Reference { constructor(ident, scope, flag, writeExpr, maybeImplicitGlobal, partial, init) { @@ -62,7 +65,6 @@ class Reference { * The read-write mode of the reference. (Value is one of {@link * Reference.READ}, {@link Reference.RW}, {@link Reference.WRITE}). * @member {number} Reference#flag - * @private */ this.flag = flag; if (this.isWrite()) { @@ -94,7 +96,7 @@ class Reference { * @returns {boolean} static */ isStatic() { - return !this.tainted && this.resolved && this.resolved.scope.isStatic(); + return !this.tainted && !!this.resolved && this.resolved.scope.isStatic(); } /** @@ -145,19 +147,16 @@ class Reference { /** * @constant Reference.READ - * @private */ Reference.READ = READ; /** * @constant Reference.WRITE - * @private */ Reference.WRITE = WRITE; /** * @constant Reference.RW - * @private */ Reference.RW = RW; diff --git a/packages/eslint-scope/lib/referencer.js b/packages/eslint-scope/lib/referencer.js index ad88ea20..971fa0e9 100644 --- a/packages/eslint-scope/lib/referencer.js +++ b/packages/eslint-scope/lib/referencer.js @@ -30,14 +30,17 @@ import PatternVisitor from "./pattern-visitor.js"; import { Definition, ParameterDefinition } from "./definition.js"; import { assert } from "./assert.js"; +/** @import * as types from "eslint-scope" */ +/** @import ESTree from "estree" */ + const { Syntax } = estraverse; /** * Traverse identifier in pattern * @param {Object} options options - * @param {pattern} rootPattern root pattern - * @param {Refencer} referencer referencer - * @param {callback} callback callback + * @param {ESTree.Pattern} rootPattern root pattern + * @param {?Referencer} referencer referencer + * @param {types.PatternVisitorCallback} callback callback * @returns {void} */ function traverseIdentifierInPattern(options, rootPattern, referencer, callback) { @@ -209,7 +212,7 @@ class Referencer extends esrecurse.Visitor { /** * Visit pattern callback - * @param {pattern} pattern pattern + * @param {ESTree.Pattern} pattern pattern * @param {Object} info info * @returns {void} */ diff --git a/packages/eslint-scope/lib/scope-manager.js b/packages/eslint-scope/lib/scope-manager.js index a9f5c943..ca10206c 100644 --- a/packages/eslint-scope/lib/scope-manager.js +++ b/packages/eslint-scope/lib/scope-manager.js @@ -38,8 +38,14 @@ import { } from "./scope.js"; import { assert } from "./assert.js"; +/** @import * as types from "eslint-scope" */ +/** @import ESTree from "estree" */ +/** @import { Scope } from "./scope.js" */ +/** @import Variable from "./variable.js" */ + /** * @constructor ScopeManager + * @implements {types.ScopeManager} */ class ScopeManager { constructor(options) { @@ -72,10 +78,12 @@ class ScopeManager { } isImpliedStrict() { - return this.__options.impliedStrict; + return !!this.__options.impliedStrict; } isStrictModeSupported() { + + // @ts-ignore -- if ecmaVersion is undefined, the comparison returns false. return this.__options.ecmaVersion >= 5; } @@ -90,7 +98,7 @@ class ScopeManager { * "are declared by the node" means the node is same as `Variable.defs[].node` or `Variable.defs[].parent`. * If the node declares nothing, this method returns an empty array. * CAUTION: This API is experimental. See https://github.com/estools/escope/pull/69 for more details. - * @param {Espree.Node} node a node to get. + * @param {ESTree.Node} node a node to get. * @returns {Variable[]} variables that declared by the node. */ getDeclaredVariables(node) { @@ -100,7 +108,7 @@ class ScopeManager { /** * acquire scope from node. * @function ScopeManager#acquire - * @param {Espree.Node} node node for the acquired scope. + * @param {ESTree.Node} node node for the acquired scope. * @param {?boolean} [inner=false] look up the most inner scope, default value is false. * @returns {Scope?} Scope from node */ @@ -154,8 +162,8 @@ class ScopeManager { /** * acquire all scopes from node. * @function ScopeManager#acquireAll - * @param {Espree.Node} node node for the acquired scope. - * @returns {Scopes?} Scope array + * @param {ESTree.Node} node node for the acquired scope. + * @returns {Scope[]?} Scope array */ acquireAll(node) { return this.__get(node); @@ -164,7 +172,7 @@ class ScopeManager { /** * release the node. * @function ScopeManager#release - * @param {Espree.Node} node releasing node. + * @param {ESTree.Node} node releasing node. * @param {?boolean} [inner=false] look up the most inner scope, default value is false. * @returns {Scope?} upper scope for the node. */ @@ -189,6 +197,8 @@ class ScopeManager { * @returns {void} */ addGlobals(names) { + + // @ts-ignore -- globalScope must be set before this method is called. this.globalScope.__addVariables(names); } @@ -254,6 +264,8 @@ class ScopeManager { } __isES6() { + + // @ts-ignore -- if ecmaVersion is undefined, the comparison returns false. return this.__options.ecmaVersion >= 6; } } diff --git a/packages/eslint-scope/lib/scope.js b/packages/eslint-scope/lib/scope.js index 0b7f5890..d1612ea9 100644 --- a/packages/eslint-scope/lib/scope.js +++ b/packages/eslint-scope/lib/scope.js @@ -29,6 +29,12 @@ import Variable from "./variable.js"; import { Definition } from "./definition.js"; import { assert } from "./assert.js"; +/** @import * as types from "eslint-scope" */ +/** @import ESTree from "estree" */ +/** @import ScopeManager from "./scope-manager.js" */ +/** @typedef {ESTree.Function | ESTree.Program | ESTree.StaticBlock} Block */ +/** @typedef {{pattern: any, node: any}} MaybeImplicitGlobal */ + const { Syntax } = estraverse; /** @@ -59,6 +65,8 @@ function isStrictScope(scope, block, isMethodDefinition) { } if (scope.type === "function") { + + // @ts-ignore -- when block is ArrowFunctionExpression if (block.type === Syntax.ArrowFunctionExpression && block.body.type !== Syntax.BlockStatement) { return false; } @@ -79,7 +87,10 @@ function isStrictScope(scope, block, isMethodDefinition) { } // Search for a 'use strict' directive. + // @ts-ignore -- body is a function body for (let i = 0, iz = body.body.length; i < iz; ++i) { + + // @ts-ignore -- body is a function body const stmt = body.body[i]; /* @@ -124,6 +135,7 @@ function registerScope(scopeManager, scope) { /** * @constructor Scope + * @implements {types.Scope} */ class Scope { constructor(scopeManager, type, upperScope, block, isMethodDefinition) { @@ -211,7 +223,7 @@ class Scope { * Whether this scope is created by a FunctionExpression. * @member {boolean} Scope#functionExpressionScope */ - this.functionExpressionScope = false; + this.functionExpressionScope = /** @type {any} */ (false); /** * Whether this is a scope that contains an 'eval()' invocation. @@ -224,6 +236,7 @@ class Scope { */ this.thisFound = false; + /** @type {?Reference[]} */ this.__left = []; /** @@ -285,7 +298,10 @@ class Scope { } // Try Resolving all references in this scope. + // @ts-ignore -- __left should be an array here for (let i = 0, iz = this.__left.length; i < iz; ++i) { + + // @ts-ignore -- __left should be an array here const ref = this.__left[i]; closeRef.call(this, ref); @@ -393,6 +409,8 @@ class Scope { const ref = new Reference(node, this, assign || Reference.READ, writeExpr, maybeImplicitGlobal, !!partial, !!init); this.references.push(ref); + + // @ts-ignore -- __left should be an array here this.__left.push(ref); } @@ -417,8 +435,8 @@ class Scope { /** * returns resolved {Reference} * @function Scope#resolve - * @param {Espree.Identifier} ident identifier to be resolved. - * @returns {Reference} reference + * @param {ESTree.Identifier} ident identifier to be resolved. + * @returns {?Reference} reference */ resolve(ident) { let ref, i, iz; @@ -476,18 +494,22 @@ class Scope { /** * Global scope. + * @implements {types.GlobalScope} */ class GlobalScope extends Scope { constructor(scopeManager, block) { super(scopeManager, "global", null, block, false); this.implicit = { set: new Map(), + + /** @type {Variable[]} */ variables: [], /** * List of {@link Reference}s that are left to be resolved (i.e. which * need to be linked to the variable they refer to). * @member {Reference[]} Scope#implicit#left + * @type {Reference[]} */ left: [] }; @@ -496,7 +518,10 @@ class GlobalScope extends Scope { __close(scopeManager) { const implicit = []; + // @ts-ignore -- __left should be an array here for (let i = 0, iz = this.__left.length; i < iz; ++i) { + + // @ts-ignore -- __left should be an array here const ref = this.__left[i]; if (ref.__maybeImplicitGlobal && !this.set.has(ref.identifier.name)) { @@ -587,6 +612,7 @@ class GlobalScope extends Scope { /** * Module scope. + * @implements {types.ModuleScope} */ class ModuleScope extends Scope { constructor(scopeManager, upperScope, block) { @@ -596,6 +622,7 @@ class ModuleScope extends Scope { /** * Function expression name scope. + * @implements {types.FunctionExpressionNameScope} */ class FunctionExpressionNameScope extends Scope { constructor(scopeManager, upperScope, block) { @@ -609,12 +636,13 @@ class FunctionExpressionNameScope extends Scope { null, null )); - this.functionExpressionScope = true; + this.functionExpressionScope = /** @type {const} */ (true); } } /** * Catch scope. + * @implements {types.CatchScope} */ class CatchScope extends Scope { constructor(scopeManager, upperScope, block) { @@ -624,6 +652,7 @@ class CatchScope extends Scope { /** * With statement scope. + * @implements {types.WithScope} */ class WithScope extends Scope { constructor(scopeManager, upperScope, block) { @@ -635,7 +664,10 @@ class WithScope extends Scope { return super.__close(scopeManager); } + // @ts-ignore -- __left should be an array here for (let i = 0, iz = this.__left.length; i < iz; ++i) { + + // @ts-ignore -- __left should be an array here const ref = this.__left[i]; ref.tainted = true; @@ -649,6 +681,7 @@ class WithScope extends Scope { /** * Block scope. + * @implements {types.BlockScope} */ class BlockScope extends Scope { constructor(scopeManager, upperScope, block) { @@ -658,6 +691,7 @@ class BlockScope extends Scope { /** * Switch scope. + * @implements {types.SwitchScope} */ class SwitchScope extends Scope { constructor(scopeManager, upperScope, block) { @@ -667,6 +701,7 @@ class SwitchScope extends Scope { /** * Function scope. + * @implements {types.FunctionScope} */ class FunctionScope extends Scope { constructor(scopeManager, upperScope, block, isMethodDefinition) { @@ -747,6 +782,7 @@ class FunctionScope extends Scope { /** * Scope of for, for-in, and for-of statements. + * @implements {types.ForScope} */ class ForScope extends Scope { constructor(scopeManager, upperScope, block) { @@ -756,6 +792,7 @@ class ForScope extends Scope { /** * Class scope. + * @implements {types.ClassScope} */ class ClassScope extends Scope { constructor(scopeManager, upperScope, block) { @@ -765,6 +802,7 @@ class ClassScope extends Scope { /** * Class field initializer scope. + * @implements {types.ClassFieldInitializerScope} */ class ClassFieldInitializerScope extends Scope { constructor(scopeManager, upperScope, block) { @@ -774,6 +812,7 @@ class ClassFieldInitializerScope extends Scope { /** * Class static block scope. + * @implements {types.ClassStaticBlockScope} */ class ClassStaticBlockScope extends Scope { constructor(scopeManager, upperScope, block) { diff --git a/packages/eslint-scope/lib/variable.js b/packages/eslint-scope/lib/variable.js index 286202f7..93787b25 100644 --- a/packages/eslint-scope/lib/variable.js +++ b/packages/eslint-scope/lib/variable.js @@ -22,10 +22,14 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/** @import * as types from "eslint-scope" */ +/** @import Reference from "./reference.js" */ + /** * A Variable represents a locally scoped identifier. These include arguments to * functions. * @constructor Variable + * @implements {types.Variable} */ class Variable { constructor(name, scope) { diff --git a/packages/eslint-scope/package.json b/packages/eslint-scope/package.json index 764de6f1..e42d1b08 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 tsconfig.json && tsc -p tests/types/tsconfig.json" }, "files": [ "LICENSE", @@ -42,12 +51,16 @@ "dist/eslint-scope.cjs" ], "dependencies": { + "@types/espree": "^10.1.0", + "@types/esrecurse": "^4.3.1", "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..05b38cc1 --- /dev/null +++ b/packages/eslint-scope/tests/types/cjs-import.test.cts @@ -0,0 +1,17 @@ +/** + * @fileoverview CommonJS type import test for ESLint Scope package. + * @author Francesco Trotta + */ + +//----------------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------------- + +// Make sure the named exports exist. +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..89a4a918 --- /dev/null +++ b/packages/eslint-scope/tests/types/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "checkJs": false, + "noImplicitAny": 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..3cb3bb49 --- /dev/null +++ b/packages/eslint-scope/tests/types/types.test.ts @@ -0,0 +1,484 @@ +/** + * @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 eslint from "eslint"; +import * as eslintScope 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 eslintScope.AnalyzeOptions, +); + +// $ExpectType GlobalScope | null +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 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 Reference[] +globalScope.implicit.left; +// $ExpectType Map> +globalScope.implicit.set; +// $ExpectType Variable[] +globalScope.implicit.variables; + +// $ExpectType ScopeManager +eslintScope.analyze(ast); + +const identifier: estree.Identifier = { + type: "Identifier", + name: "foo", +}; +const definition2 = new eslintScope.Definition( + "Variable", + identifier, + { type: "VariableDeclarator", id: identifier }, + 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; +// $ExpectType { left: Reference[]; set: Map>; variables: Variable[]; } +globalScopeInstance.implicit; + +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 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 | null +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 (AssignmentExpression | AssignmentPattern)[] +patternVisitor.assignments; + +// $ExpectType Node[] +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: ArrayExpression) => void +patternVisitor.ArrayExpression; + +// $ExpectType (pattern: AssignmentExpression) => void +patternVisitor.AssignmentExpression; + +// $ExpectType (pattern: CallExpression) => void +patternVisitor.CallExpression; + +(definition: eslintScope.Definition) => definition satisfies eslint.Scope.Definition; + +(reference: eslintScope.Reference) => reference satisfies eslint.Scope.Reference; + +(scope: eslintScope.Scope) => scope satisfies eslint.Scope.Scope; + +(scopeManager: eslintScope.ScopeManager) => scopeManager satisfies eslint.Scope.ScopeManager; + +(variable: eslintScope.Variable) => variable satisfies eslint.Scope.Variable; diff --git a/packages/eslint-scope/tsconfig.json b/packages/eslint-scope/tsconfig.json new file mode 100644 index 00000000..fd3c19ea --- /dev/null +++ b/packages/eslint-scope/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "checkJs": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "lib": [ + "es2022" + ], + "module": "nodenext", + "noEmit": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "types": [], + }, + "include": ["lib"] +}