Skip to content

Commit 73f8cae

Browse files
committed
refactor(compiler): further extract babel ast utilities
1 parent 62f7525 commit 73f8cae

File tree

7 files changed

+284
-324
lines changed

7 files changed

+284
-324
lines changed
+231
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import {
2+
Identifier,
3+
Node,
4+
isReferenced,
5+
Function,
6+
ObjectProperty
7+
} from '@babel/types'
8+
import { walk } from 'estree-walker'
9+
10+
export function walkIdentifiers(
11+
root: Node,
12+
onIdentifier: (
13+
node: Identifier,
14+
parent: Node,
15+
parentStack: Node[],
16+
isReference: boolean,
17+
isLocal: boolean
18+
) => void,
19+
onNode?: (node: Node, parent: Node, parentStack: Node[]) => void | boolean,
20+
parentStack: Node[] = [],
21+
knownIds: Record<string, number> = Object.create(null),
22+
includeAll = false
23+
) {
24+
const rootExp =
25+
root.type === 'Program' &&
26+
root.body[0].type === 'ExpressionStatement' &&
27+
root.body[0].expression
28+
29+
;(walk as any)(root, {
30+
enter(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
31+
parent && parentStack.push(parent)
32+
if (
33+
parent &&
34+
parent.type.startsWith('TS') &&
35+
parent.type !== 'TSAsExpression' &&
36+
parent.type !== 'TSNonNullExpression' &&
37+
parent.type !== 'TSTypeAssertion'
38+
) {
39+
return this.skip()
40+
}
41+
if (onNode && onNode(node, parent!, parentStack) === false) {
42+
return this.skip()
43+
}
44+
if (node.type === 'Identifier') {
45+
const isLocal = !!knownIds[node.name]
46+
const isRefed = isReferencedIdentifier(node, parent!, parentStack)
47+
if (includeAll || (isRefed && !isLocal)) {
48+
onIdentifier(node, parent!, parentStack, isRefed, isLocal)
49+
}
50+
} else if (isFunctionType(node)) {
51+
// walk function expressions and add its arguments to known identifiers
52+
// so that we don't prefix them
53+
for (const p of node.params) {
54+
;(walk as any)(p, {
55+
enter(child: Node, parent: Node) {
56+
if (
57+
child.type === 'Identifier' &&
58+
// do not record as scope variable if is a destructured key
59+
!isStaticPropertyKey(child, parent) &&
60+
// do not record if this is a default value
61+
// assignment of a destructured variable
62+
!(
63+
parent &&
64+
parent.type === 'AssignmentPattern' &&
65+
parent.right === child
66+
)
67+
) {
68+
markScopeIdentifier(node, child, knownIds)
69+
}
70+
}
71+
})
72+
}
73+
} else if (node.type === 'BlockStatement') {
74+
// #3445 record block-level local variables
75+
for (const stmt of node.body) {
76+
if (stmt.type === 'VariableDeclaration') {
77+
for (const decl of stmt.declarations) {
78+
for (const id of extractIdentifiers(decl.id)) {
79+
markScopeIdentifier(node, id, knownIds)
80+
}
81+
}
82+
}
83+
}
84+
} else if (
85+
node.type === 'ObjectProperty' &&
86+
parent!.type === 'ObjectPattern'
87+
) {
88+
// mark property in destructure pattern
89+
;(node as any).inPattern = true
90+
}
91+
},
92+
leave(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
93+
parent && parentStack.pop()
94+
if (node !== rootExp && node.scopeIds) {
95+
node.scopeIds.forEach((id: string) => {
96+
knownIds[id]--
97+
if (knownIds[id] === 0) {
98+
delete knownIds[id]
99+
}
100+
})
101+
}
102+
}
103+
})
104+
}
105+
106+
export function isReferencedIdentifier(
107+
id: Identifier,
108+
parent: Node | null,
109+
parentStack: Node[]
110+
) {
111+
if (!parent) {
112+
return true
113+
}
114+
115+
// is a special keyword but parsed as identifier
116+
if (id.name === 'arguments') {
117+
return false
118+
}
119+
120+
if (isReferenced(id, parent)) {
121+
return true
122+
}
123+
124+
// babel's isReferenced check returns false for ids being assigned to, so we
125+
// need to cover those cases here
126+
switch (parent.type) {
127+
case 'AssignmentExpression':
128+
case 'AssignmentPattern':
129+
return true
130+
case 'ObjectPattern':
131+
case 'ArrayPattern':
132+
return isInDestructureAssignment(parent, parentStack)
133+
}
134+
135+
return false
136+
}
137+
138+
export function isInDestructureAssignment(
139+
parent: Node,
140+
parentStack: Node[]
141+
): boolean {
142+
if (
143+
parent &&
144+
(parent.type === 'ObjectProperty' || parent.type === 'ArrayPattern')
145+
) {
146+
let i = parentStack.length
147+
while (i--) {
148+
const p = parentStack[i]
149+
if (p.type === 'AssignmentExpression') {
150+
return true
151+
} else if (p.type !== 'ObjectProperty' && !p.type.endsWith('Pattern')) {
152+
break
153+
}
154+
}
155+
}
156+
return false
157+
}
158+
159+
function extractIdentifiers(
160+
param: Node,
161+
nodes: Identifier[] = []
162+
): Identifier[] {
163+
switch (param.type) {
164+
case 'Identifier':
165+
nodes.push(param)
166+
break
167+
168+
case 'MemberExpression':
169+
let object: any = param
170+
while (object.type === 'MemberExpression') {
171+
object = object.object
172+
}
173+
nodes.push(object)
174+
break
175+
176+
case 'ObjectPattern':
177+
param.properties.forEach(prop => {
178+
if (prop.type === 'RestElement') {
179+
extractIdentifiers(prop.argument, nodes)
180+
} else {
181+
extractIdentifiers(prop.value, nodes)
182+
}
183+
})
184+
break
185+
186+
case 'ArrayPattern':
187+
param.elements.forEach(element => {
188+
if (element) extractIdentifiers(element, nodes)
189+
})
190+
break
191+
192+
case 'RestElement':
193+
extractIdentifiers(param.argument, nodes)
194+
break
195+
196+
case 'AssignmentPattern':
197+
extractIdentifiers(param.left, nodes)
198+
break
199+
}
200+
201+
return nodes
202+
}
203+
204+
function markScopeIdentifier(
205+
node: Node & { scopeIds?: Set<string> },
206+
child: Identifier,
207+
knownIds: Record<string, number>
208+
) {
209+
const { name } = child
210+
if (node.scopeIds && node.scopeIds.has(name)) {
211+
return
212+
}
213+
if (name in knownIds) {
214+
knownIds[name]++
215+
} else {
216+
knownIds[name] = 1
217+
}
218+
;(node.scopeIds || (node.scopeIds = new Set())).add(name)
219+
}
220+
221+
export const isFunctionType = (node: Node): node is Function => {
222+
return /Function(?:Expression|Declaration)$|Method$/.test(node.type)
223+
}
224+
225+
export const isStaticProperty = (node: Node): node is ObjectProperty =>
226+
node &&
227+
(node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
228+
!node.computed
229+
230+
export const isStaticPropertyKey = (node: Node, parent: Node) =>
231+
isStaticProperty(parent) && parent.key === node

packages/compiler-core/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export {
3131

3232
export * from './ast'
3333
export * from './utils'
34+
export * from './babelUtils'
3435
export * from './runtimeHelpers'
3536

3637
export { getBaseTransformPreset, TransformPreset } from './compile'

0 commit comments

Comments
 (0)