44
55import type * as ESTree from '../generated/types.d.ts' ;
66
7- import type { Node } from './types.ts' ;
7+ import {
8+ analyze ,
9+ type AnalyzeOptions ,
10+ type ScopeManager as TSESLintScopeManager ,
11+ } from '@typescript-eslint/scope-manager' ;
12+ import { SOURCE_CODE } from './source_code.js' ;
813
914type Identifier =
1015 | ESTree . IdentifierName
@@ -14,8 +19,65 @@ type Identifier =
1419 | ESTree . TSThisParameter
1520 | ESTree . TSIndexSignatureName ;
1621
22+ /**
23+ * @see https://eslint.org/docs/latest/developer-guide/scope-manager-interface#scopemanager-interface
24+ */
25+ // This is a wrapper class around the @typescript -eslint/scope-manager package.
26+ // We want to control what APIs are exposed to the user to limit breaking changes when we switch our implementation.
1727export class ScopeManager {
18- // TODO
28+ #scopeManager: TSESLintScopeManager ;
29+
30+ constructor ( ast : ESTree . Program ) {
31+ const defaultOptions : AnalyzeOptions = {
32+ globalReturn : false ,
33+ jsxFragmentName : null ,
34+ jsxPragma : 'React' ,
35+ lib : [ 'esnext' ] ,
36+ sourceType : ast . sourceType ,
37+ } ;
38+ // The effectiveness of this assertion depends on our alignment with ESTree.
39+ // It could eventually be removed as we align the remaining corner cases and the typegen.
40+ // @ts -expect-error // TODO: our types don't quite align yet
41+ this . #scopeManager = analyze ( ast , defaultOptions ) ;
42+ }
43+
44+ /**
45+ * All scopes
46+ */
47+ get scopes ( ) : Scope [ ] {
48+ // @ts -expect-error // TODO: our types don't quite align yet
49+ return this . #scopeManager. scopes ;
50+ }
51+
52+ /**
53+ * The root scope
54+ */
55+ get globalScope ( ) : Scope | null {
56+ return this . #scopeManager. globalScope as any ;
57+ }
58+
59+ /**
60+ * Get the variables that a given AST node defines. The gotten variables' `def[].node`/`def[].parent` property is the node.
61+ * If the node does not define any variable, this returns an empty array.
62+ * @param node An AST node to get their variables.
63+ */
64+ getDeclaredVariables ( node : ESTree . Node ) : Variable [ ] {
65+ // @ts -expect-error // TODO: our types don't quite align yet
66+ return this . #scopeManager. getDeclaredVariables ( node ) ;
67+ }
68+
69+ /**
70+ * Get the scope of a given AST node. The gotten scope's `block` property is the node.
71+ * This method never returns `function-expression-name` scope. If the node does not have their scope, this returns `null`.
72+ *
73+ * @param node An AST node to get their scope.
74+ * @param inner If the node has multiple scopes, this returns the outermost scope normally.
75+ * If `inner` is `true` then this returns the innermost scope.
76+ */
77+ acquire ( node : ESTree . Node , inner ?: boolean ) : Scope | null {
78+ // @ts -expect-error // TODO: our types don't quite align yet
79+ return this . #scopeManager. acquire ( node , inner ) ;
80+ }
1981}
2082
2183export interface Scope {
@@ -24,7 +86,7 @@ export interface Scope {
2486 upper : Scope | null ;
2587 childScopes : Scope [ ] ;
2688 variableScope : Scope ;
27- block : Node ;
89+ block : ESTree . Node ;
2890 variables : Variable [ ] ;
2991 set : Map < string , Variable > ;
3092 references : Reference [ ] ;
@@ -74,8 +136,8 @@ export interface Reference {
74136export interface Definition {
75137 type : DefinitionType ;
76138 name : Identifier ;
77- node : Node ;
78- parent : Node | null ;
139+ node : ESTree . Node ;
140+ parent : ESTree . Node | null ;
79141}
80142
81143export type DefinitionType =
@@ -92,9 +154,43 @@ export type DefinitionType =
92154 * @param node - `Identifier` node to check.
93155 * @returns `true` if the identifier is a reference to a global variable.
94156 */
95- // oxlint-disable-next-line no-unused-vars
96- export function isGlobalReference ( node : Node ) : boolean {
97- throw new Error ( '`sourceCode.isGlobalReference` not implemented yet' ) ; // TODO
157+ export function isGlobalReference ( node : ESTree . Node ) : boolean {
158+ // ref: https://github.com/eslint/eslint/blob/e7cda3bdf1bdd664e6033503a3315ad81736b200/lib/languages/js/source-code/source-code.js#L934-L962
159+ if ( ! node ) {
160+ throw new TypeError ( 'Missing required argument: node.' ) ;
161+ }
162+
163+ if ( node . type !== 'Identifier' ) {
164+ return false ;
165+ }
166+
167+ const { name } = node ;
168+ if ( typeof name !== 'string' ) {
169+ return false ;
170+ }
171+
172+ const globalScope = SOURCE_CODE . scopeManager . scopes [ 0 ] ;
173+ if ( ! globalScope ) return false ;
174+
175+ // If the identifier is a reference to a global variable, the global scope should have a variable with the name.
176+ const variable = globalScope . set . get ( name ) ;
177+
178+ // Global variables are not defined by any node, so they should have no definitions.
179+ if ( ! variable || variable . defs . length > 0 ) {
180+ return false ;
181+ }
182+
183+ // If there is a variable by the same name exists in the global scope, we need to check our node is one of its references.
184+ const { references } = variable ;
185+
186+ for ( let i = 0 ; i < references . length ; i ++ ) {
187+ const reference = references [ i ] ;
188+ if ( reference . identifier === node ) {
189+ return true ;
190+ }
191+ }
192+
193+ return false ;
98194}
99195
100196/**
@@ -103,28 +199,37 @@ export function isGlobalReference(node: Node): boolean {
103199 * @param node - The node for which the variables are obtained.
104200 * @returns An array of variable nodes representing the variables that `node` defines.
105201 */
106- // oxlint-disable-next-line no-unused-vars
107- export function getDeclaredVariables ( node : Node ) : Variable [ ] {
108- throw new Error ( '`sourceCode. getDeclaredVariables` not implemented yet' ) ; // TODO
202+ export function getDeclaredVariables ( node : ESTree . Node ) : Variable [ ] {
203+ // ref: https://github.com/eslint/eslint/blob/e7cda3bdf1bdd664e6033503a3315ad81736b200/lib/languages/js/source-code/source-code.js#L904
204+ return SOURCE_CODE . scopeManager . getDeclaredVariables ( node ) ;
109205}
110206
111207/**
112208 * Get the scope for the given node
113209 * @param node - The node to get the scope of.
114210 * @returns The scope information for this node.
115211 */
116- // oxlint-disable-next-line no-unused-vars
117- export function getScope ( node : Node ) : Scope {
118- throw new Error ( '`sourceCode.getScope` not implemented yet' ) ; // TODO
119- }
212+ export function getScope ( node : ESTree . Node ) : Scope {
213+ // ref: https://github.com/eslint/eslint/blob/e7cda3bdf1bdd664e6033503a3315ad81736b200/lib/languages/js/source-code/source-code.js#L862-L892
214+ if ( ! node ) {
215+ throw new TypeError ( 'Missing required argument: node.' ) ;
216+ }
120217
121- /**
122- * Mark a variable as used in the current scope
123- * @param name - The name of the variable to mark as used.
124- * @param refNode? - The closest node to the variable reference.
125- * @returns `true` if the variable was found and marked as used, `false` if not.
126- */
127- // oxlint-disable-next-line no-unused-vars
128- export function markVariableAsUsed ( name : string , refNode : Node ) : boolean {
129- throw new Error ( '`sourceCode.markVariableAsUsed` not implemented yet' ) ; // TODO
218+ const { scopeManager } = SOURCE_CODE ;
219+ const inner = node . type !== 'Program' ;
220+
221+ // Traverse up the AST to find a `Node` whose scope can be acquired.
222+ for ( let current : any = node ; current ; current = current . parent ) {
223+ const scope = scopeManager . acquire ( current , inner ) ;
224+
225+ if ( scope ) {
226+ if ( scope . type === 'function-expression-name' ) {
227+ return scope . childScopes [ 0 ] ;
228+ }
229+
230+ return scope ;
231+ }
232+ }
233+
234+ return scopeManager . scopes [ 0 ] ;
130235}
0 commit comments