-
-
Notifications
You must be signed in to change notification settings - Fork 79
/
Copy pathscope-analyzer.ts
166 lines (153 loc) · 4.94 KB
/
scope-analyzer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* @copyright 2017 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
import type * as escopeTypes from "eslint-scope"
import type { ParserOptions } from "../common/parser-options"
import type {
ESLintIdentifier,
ESLintProgram,
Reference,
Variable,
} from "../ast/index"
import { getFallbackKeys } from "../ast/index"
import { getEslintScope } from "../common/eslint-scope"
import { getEcmaVersionIfUseEspree } from "../common/espree"
import { ANALYZE_SCOPE_DEFAULT_ECMA_VERSION } from "../script-setup/parser-options"
type ParserResult = {
ast: ESLintProgram
scopeManager?: escopeTypes.ScopeManager
}
/**
* Check whether the given reference is unique in the belonging array.
* @param reference The current reference to check.
* @param index The index of the reference.
* @param references The belonging array of the reference.
*/
function isUnique(
reference: escopeTypes.Reference,
index: number,
references: escopeTypes.Reference[],
): boolean {
return (
index === 0 || reference.identifier !== references[index - 1].identifier
)
}
/**
* Check whether a given variable has that definition.
* @param variable The variable to check.
* @returns `true` if the variable has that definition.
*/
function hasDefinition(variable: escopeTypes.Variable): boolean {
return variable.defs.length >= 1
}
/**
* Transform the given reference object.
* @param reference The source reference object.
* @returns The transformed reference object.
*/
function transformReference(reference: escopeTypes.Reference): Reference {
const ret: Reference = {
id: reference.identifier as ESLintIdentifier,
mode: reference.isReadOnly()
? "r"
: reference.isWriteOnly()
? "w"
: /* otherwise */ "rw",
variable: null,
isValueReference: reference.isValueReference,
isTypeReference: reference.isTypeReference,
}
Object.defineProperty(ret, "variable", { enumerable: false })
return ret
}
/**
* Transform the given variable object.
* @param variable The source variable object.
* @returns The transformed variable object.
*/
function transformVariable(
variable: escopeTypes.Variable,
kind: Variable["kind"],
): Variable {
const ret: Variable = {
id: variable.defs[0].name as ESLintIdentifier,
kind,
references: [],
}
Object.defineProperty(ret, "references", { enumerable: false })
return ret
}
/**
* Get the `for` statement scope.
* @param scope The global scope.
* @returns The `for` statement scope.
*/
function getForScope(scope: escopeTypes.Scope): escopeTypes.Scope {
const child = scope.childScopes[0]
return child.block === scope.block ? child.childScopes[0] : child
}
export function analyzeScope(
ast: ESLintProgram,
parserOptions: ParserOptions,
): escopeTypes.ScopeManager {
const ecmaVersion =
getEcmaVersionIfUseEspree(parserOptions) ||
ANALYZE_SCOPE_DEFAULT_ECMA_VERSION
const ecmaFeatures = parserOptions.ecmaFeatures || {}
const sourceType = parserOptions.sourceType || "script"
const result = getEslintScope().analyze(ast, {
ignoreEval: true,
nodejsScope: false,
impliedStrict: ecmaFeatures.impliedStrict,
ecmaVersion,
sourceType,
fallback: getFallbackKeys,
})
return result
}
/**
* Analyze the scope of the given AST.
* @param {ParserResult} parserResult The parser result to analyze.
* @param parserOptions
*/
function analyze(
parserResult: ParserResult,
parserOptions: ParserOptions,
): escopeTypes.Scope {
const scopeManager =
parserResult.scopeManager ||
analyzeScope(parserResult.ast, parserOptions)
return scopeManager.globalScope
}
/**
* Analyze the external references of the given AST.
* @param {ParserResult} parserResult The parser result to analyze.
* @returns {Reference[]} The reference objects of external references.
*/
export function analyzeExternalReferences(
parserResult: ParserResult,
parserOptions: ParserOptions,
): Reference[] {
const scope = analyze(parserResult, parserOptions)
return scope.through.filter(isUnique).map(transformReference)
}
/**
* Analyze the external references of the given AST.
* @param {ParserResult} parserResult The parser result to analyze.
* @returns {Reference[]} The reference objects of external references.
*/
export function analyzeVariablesAndExternalReferences(
parserResult: ParserResult,
kind: Variable["kind"],
parserOptions: ParserOptions,
): { variables: Variable[]; references: Reference[] } {
const scope = analyze(parserResult, parserOptions)
return {
variables: getForScope(scope)
.variables.filter(hasDefinition)
.map((v) => transformVariable(v, kind)),
references: scope.through.filter(isUnique).map(transformReference),
}
}