Skip to content
This repository was archived by the owner on Dec 25, 2024. It is now read-only.

Commit eb9c5b2

Browse files
committed
feat: use @babel/traverse to get identifiers
1 parent 8d785a3 commit eb9c5b2

File tree

5 files changed

+124
-111
lines changed

5 files changed

+124
-111
lines changed

src/core/babel.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import * as babel from '@babel/core'
22
import { parse, parseExpression } from '@babel/parser'
33
import g from '@babel/generator'
4+
import * as babel_traverse from '@babel/traverse'
45

56
export const t: typeof babel['types'] = ((babel as any).default || babel).types
67
export const generate: typeof g = ((g as any).default || g)
8+
export const traverse = ((babel_traverse as any)?.default?.default as null) ?? babel_traverse?.default ?? babel_traverse
79
export { parseExpression, parse }

src/core/identifiers.ts

+58-52
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,41 @@
1-
import type { Expression, Node, PrivateName, SpreadElement, Statement, TSType } from '@babel/types'
1+
import type {
2+
Expression,
3+
File,
4+
PrivateName,
5+
SpreadElement,
6+
Statement,
7+
TSType,
8+
} from '@babel/types'
9+
import type { ParseResult } from '@babel/parser'
10+
import { t, traverse } from './babel'
211

3-
export function getIdentifierDeclarations(nodes: Statement[], identifiers = new Set<string>()) {
4-
for (let node of nodes) {
5-
if (node.type === 'ExportNamedDeclaration') {
6-
node = node.declaration!
7-
if (!node)
8-
continue
9-
}
10-
if (node.type === 'ImportDeclaration') {
11-
for (const specifier of node.specifiers)
12-
identifiers.add(specifier.local.name)
13-
}
14-
else if (node.type === 'VariableDeclaration') {
15-
function handleVariableId(node: Node) {
16-
if (node.type === 'Identifier') {
17-
identifiers.add(node.name)
18-
}
19-
else if (node.type === 'ObjectPattern') {
20-
for (const property of node.properties) {
21-
if (property.type === 'ObjectProperty')
22-
handleVariableId(property.value)
23-
else if (property.type === 'RestElement' && property.argument.type === 'Identifier')
24-
identifiers.add(property.argument.name)
25-
}
26-
}
27-
else if (node.type === 'ArrayPattern') {
28-
for (const element of node.elements) {
29-
if (element?.type === 'Identifier')
30-
identifiers.add(element.name)
31-
else if (element?.type === 'RestElement' && element.argument.type === 'Identifier')
32-
identifiers.add(element.argument.name)
33-
else if (element?.type === 'ObjectPattern' || element?.type === 'ArrayPattern')
34-
handleVariableId(element)
35-
}
36-
}
12+
export function getIdentifierDeclarations(nodes: Statement[]) {
13+
let result!: Set<string>
14+
let programScopeUid: number
15+
traverse(t.file(t.program(nodes)), {
16+
Program(path) {
17+
result = new Set(Object.keys(path.scope.bindings))
18+
programScopeUid = (path.scope as any).uid
19+
},
20+
// FIXME: babel bug, temporary add TSEnumDeclaration and TSModuleDeclaration logic
21+
TSEnumDeclaration(path) {
22+
if ((path.scope as any).uid === programScopeUid)
23+
result.add(path.node.id.name)
24+
},
25+
TSModuleDeclaration(path) {
26+
if ((path.scope as any).uid === programScopeUid) {
27+
const id = path.node.id
28+
if (id.type === 'Identifier')
29+
result.add(id.name)
3730
}
38-
39-
for (const declarator of node.declarations)
40-
handleVariableId(declarator.id)
41-
}
42-
else if (node.type === 'FunctionDeclaration' || node.type === 'ClassDeclaration') {
43-
if (node.id)
44-
identifiers.add(node.id.name)
45-
}
46-
else if (node.type === 'TSEnumDeclaration') {
47-
if (node.id)
48-
identifiers.add(node.id.name)
49-
}
50-
// else {
51-
// console.log(node)
52-
// }
53-
}
54-
return identifiers
31+
},
32+
})
33+
return Array.from(result)
5534
}
5635

36+
/**
37+
* @deprecated use `getFileGlobals` instead
38+
*/
5739
export function getIdentifierUsages(node?: Expression | TSType | SpreadElement | PrivateName | Statement | null, identifiers = new Set<string>()) {
5840
if (!node)
5941
return identifiers
@@ -124,3 +106,27 @@ export function getIdentifierUsages(node?: Expression | TSType | SpreadElement |
124106
// }
125107
return identifiers
126108
}
109+
110+
export function getFileGlobals(result: ParseResult<File>) {
111+
let globals!: Set<string>
112+
let programScopeUid: number
113+
traverse(result, {
114+
Program(path) {
115+
globals = new Set(Object.keys((path.scope as any).globals))
116+
programScopeUid = (path.scope as any).uid
117+
},
118+
// FIXME: babel bug, temporary add TSEnumDeclaration and TSModuleDeclaration logic
119+
TSEnumDeclaration(path) {
120+
if ((path.scope as any).uid === programScopeUid)
121+
globals.delete(path.node.id.name)
122+
},
123+
TSModuleDeclaration(path) {
124+
if ((path.scope as any).uid === programScopeUid) {
125+
const id = path.node.id
126+
if (id.type === 'Identifier')
127+
globals.delete(id.name)
128+
}
129+
},
130+
})
131+
return Array.from(globals)
132+
}

src/core/parseSFC.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import type {
1919
ScriptSetupTransformOptions,
2020
ScriptTagMeta,
2121
} from '../types'
22-
import { getIdentifierUsages } from './identifiers'
22+
import { getFileGlobals } from './identifiers'
2323
import { parse } from './babel'
2424
import { exhaustiveCheckReturnUndefined, pascalize } from './utils'
2525

@@ -137,12 +137,8 @@ function getDirectiveNames(node: TemplateChildNode): string[] {
137137
}
138138

139139
function getFreeVariablesForText(input: string): string[] {
140-
const identifiers = new Set<string>()
141140
const inputWithPrefix = input.trimStart()[0] === '{' ? `(${input})` : input
142-
143-
const nodes = parse(inputWithPrefix).program.body
144-
nodes.forEach(node => getIdentifierUsages(node, identifiers))
145-
return [...identifiers.values()]
141+
return getFileGlobals(parse(inputWithPrefix))
146142
}
147143

148144
function getFreeVariablesForPropsNode(

src/core/transformScriptSetup.ts

+61-52
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,31 @@
11
import { capitalize } from '@vue/shared'
22
import type { Node, ObjectExpression, Statement } from '@babel/types'
3-
import { partition } from '@antfu/utils'
3+
import { notNullish, partition, uniq } from '@antfu/utils'
44
import type { ParsedSFC, ScriptSetupTransformOptions } from '../types'
55
import { applyMacros } from './macros'
66
import { getIdentifierDeclarations } from './identifiers'
77
import { generate, t } from './babel'
8-
import { isNotNil, pascalize } from './utils'
8+
import { pascalize } from './utils'
99

10-
function isAsyncImport(node: any) {
11-
if (node.type === 'VariableDeclaration') {
10+
function isAsyncImport(node: Statement) {
11+
if (t.isVariableDeclaration(node)) {
1212
const declaration = node.declarations[0]
1313

14-
return declaration?.init?.callee?.name === 'defineAsyncComponent'
14+
return (
15+
declaration !== undefined
16+
&& t.isCallExpression(declaration.init)
17+
&& t.isIdentifier(declaration.init.callee)
18+
&& declaration.init.callee.name === 'defineAsyncComponent'
19+
)
1520
}
1621

1722
return false
1823
}
1924

20-
export function transformScriptSetup(sfc: ParsedSFC, options?: ScriptSetupTransformOptions) {
25+
export function transformScriptSetup(
26+
sfc: ParsedSFC,
27+
options?: ScriptSetupTransformOptions,
28+
) {
2129
const { scriptSetup, script, template } = sfc
2230

2331
const { nodes: body, props, expose } = applyMacros(scriptSetup.ast.body)
@@ -26,16 +34,17 @@ export function transformScriptSetup(sfc: ParsedSFC, options?: ScriptSetupTransf
2634
body,
2735
n =>
2836
isAsyncImport(n)
29-
|| n.type === 'ImportDeclaration'
30-
|| n.type === 'ExportNamedDeclaration'
31-
|| n.type.startsWith('TS'),
37+
|| t.isImportDeclaration(n)
38+
|| t.isExportNamedDeclaration(n)
39+
|| n.type.startsWith('TS'),
3240
)
3341

3442
// get all identifiers in `<script setup>`
35-
const declarations = new Set<string>()
36-
getIdentifierDeclarations(hoisted, declarations)
37-
getIdentifierDeclarations(setupBody, declarations)
38-
const declarationArray = Array.from(declarations).filter(isNotNil)
43+
const declarations = [
44+
...getIdentifierDeclarations(hoisted),
45+
...getIdentifierDeclarations(setupBody),
46+
]
47+
const declarationArray = uniq(declarations).filter(notNullish)
3948

4049
// filter out identifiers that are used in `<template>`
4150
const returns: ObjectExpression['properties'] = declarationArray
@@ -45,19 +54,24 @@ export function transformScriptSetup(sfc: ParsedSFC, options?: ScriptSetupTransf
4554
return t.objectProperty(id, id, false, true)
4655
})
4756

48-
const components = Array.from(template.components).map(component =>
49-
declarationArray.find(declare => declare === component)
50-
?? declarationArray.find(declare => pascalize(declare) === component),
51-
).filter(isNotNil)
57+
const components = Array.from(template.components)
58+
.map(
59+
component =>
60+
declarationArray.find(declare => declare === component)
61+
?? declarationArray.find(declare => pascalize(declare) === component),
62+
)
63+
.filter(notNullish)
5264

53-
const directiveDeclaration = Array.from(template.directives).map((directive) => {
54-
const identifier = declarationArray.find(declaration => declaration === `v${capitalize(directive)}`)
55-
if (identifier === undefined)
56-
return undefined
65+
const directiveDeclaration = Array.from(template.directives)
66+
.map((directive) => {
67+
const identifier = declarationArray.find(
68+
declaration => declaration === `v${capitalize(directive)}`,
69+
)
70+
if (identifier === undefined) return undefined
5771

58-
return { identifier, directive }
59-
},
60-
).filter(isNotNil)
72+
return { identifier, directive }
73+
})
74+
.filter(notNullish)
6175

6276
// append `<script setup>` imports to `<script>`
6377

@@ -71,10 +85,7 @@ export function transformScriptSetup(sfc: ParsedSFC, options?: ScriptSetupTransf
7185
if (node.type === 'ExportDefaultDeclaration') {
7286
hasBody = true
7387
return t.variableDeclaration('const', [
74-
t.variableDeclarator(
75-
__sfc,
76-
node.declaration as any,
77-
),
88+
t.variableDeclarator(__sfc, node.declaration as any),
7889
])
7990
}
8091
return node
@@ -90,10 +101,7 @@ export function transformScriptSetup(sfc: ParsedSFC, options?: ScriptSetupTransf
90101
if (!hasBody) {
91102
ast.body.push(
92103
t.variableDeclaration('const', [
93-
t.variableDeclarator(
94-
__sfc,
95-
t.objectExpression([]),
96-
),
104+
t.variableDeclarator(__sfc, t.objectExpression([])),
97105
]),
98106
)
99107
}
@@ -104,7 +112,8 @@ export function transformScriptSetup(sfc: ParsedSFC, options?: ScriptSetupTransf
104112
hasBody = true
105113
ast.body.push(
106114
t.expressionStatement(
107-
t.assignmentExpression('=',
115+
t.assignmentExpression(
116+
'=',
108117
t.memberExpression(__sfc, t.identifier('props')),
109118
props as any,
110119
),
@@ -126,15 +135,13 @@ export function transformScriptSetup(sfc: ParsedSFC, options?: ScriptSetupTransf
126135

127136
ast.body.push(
128137
t.expressionStatement(
129-
t.assignmentExpression('=',
138+
t.assignmentExpression(
139+
'=',
130140
t.memberExpression(__sfc, t.identifier('setup')),
131-
t.arrowFunctionExpression([
132-
t.identifier('__props'),
133-
t.identifier('__ctx'),
134-
], t.blockStatement([
135-
...setupBody,
136-
returnStatement as any,
137-
])),
141+
t.arrowFunctionExpression(
142+
[t.identifier('__props'), t.identifier('__ctx')],
143+
t.blockStatement([...setupBody, returnStatement as any]),
144+
),
138145
),
139146
) as any,
140147
)
@@ -153,7 +160,8 @@ export function transformScriptSetup(sfc: ParsedSFC, options?: ScriptSetupTransf
153160

154161
ast.body.push(
155162
t.expressionStatement(
156-
t.assignmentExpression('=',
163+
t.assignmentExpression(
164+
'=',
157165
t.memberExpression(__sfc, t.identifier('components')),
158166
t.callExpression(
159167
t.memberExpression(t.identifier('Object'), t.identifier('assign')),
@@ -172,17 +180,20 @@ export function transformScriptSetup(sfc: ParsedSFC, options?: ScriptSetupTransf
172180
if (directiveDeclaration.length) {
173181
hasBody = true
174182
const directivesObject = t.objectExpression(
175-
directiveDeclaration.map(({ directive, identifier }) => (t.objectProperty(
176-
t.identifier(directive),
177-
t.identifier(identifier),
178-
false,
179-
false,
180-
))),
183+
directiveDeclaration.map(({ directive, identifier }) =>
184+
t.objectProperty(
185+
t.identifier(directive),
186+
t.identifier(identifier),
187+
false,
188+
false,
189+
),
190+
),
181191
)
182192

183193
ast.body.push(
184194
t.expressionStatement(
185-
t.assignmentExpression('=',
195+
t.assignmentExpression(
196+
'=',
186197
t.memberExpression(__sfc, t.identifier('directives')),
187198
t.callExpression(
188199
t.memberExpression(t.identifier('Object'), t.identifier('assign')),
@@ -205,9 +216,7 @@ export function transformScriptSetup(sfc: ParsedSFC, options?: ScriptSetupTransf
205216

206217
// re-export
207218
// `export default __sfc_main`
208-
ast.body.push(
209-
t.exportDefaultDeclaration(__sfc) as any,
210-
)
219+
ast.body.push(t.exportDefaultDeclaration(__sfc) as any)
211220

212221
ast = options?.astTransforms?.post?.(ast, sfc) || ast
213222

test/identifiers.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe('identifiers', () => {
2727
sourceType: 'module',
2828
})
2929

30-
expect(getIdentifierDeclarations(ast.program.body)).toEqual(new Set(output))
30+
expect(new Set(getIdentifierDeclarations(ast.program.body))).toEqual(new Set(output))
3131
})
3232
}
3333
})

0 commit comments

Comments
 (0)