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

Commit be5be03

Browse files
committed
fix: improve identifier detection, close #9
1 parent 03b03cf commit be5be03

File tree

13 files changed

+369
-34
lines changed

13 files changed

+369
-34
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"eslint": "^7.32.0",
6262
"eslint-plugin-jest": "^24.4.0",
6363
"esno": "^0.9.1",
64+
"fast-glob": "^3.2.7",
6465
"jest": "^27.0.6",
6566
"rimraf": "^3.0.2",
6667
"ts-jest": "^27.0.5",

pnpm-lock.yaml

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/parse.ts

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Parser as HTMLParser } from 'htmlparser2'
2-
import { parse, ParserOptions } from '@babel/parser'
2+
import { parse } from '@babel/parser'
3+
import { PrivateName, Expression, Statement } from '@babel/types'
34
import { camelize, capitalize, isHTMLTag, isSVGTag, isVoidTag } from '@vue/shared'
4-
import traverse from '@babel/traverse'
55
import { ParseResult, TagMeta } from './types'
66

77
export function parseVueSFC(code: string, id?: string): ParseResult {
@@ -43,8 +43,12 @@ export function parseVueSFC(code: string, id?: string): ParseResult {
4343
Object.entries(attributes).forEach(([key, value]) => {
4444
if (!value)
4545
return
46-
if (key.startsWith('v-') || key.startsWith('@') || key.startsWith(':'))
47-
expressions.add(value)
46+
if (key.startsWith('v-') || key.startsWith('@') || key.startsWith(':')) {
47+
if (key === 'v-if')
48+
expressions.add(`for (let ${value}) {}`)
49+
else
50+
expressions.add(`(${value})`)
51+
}
4852
if (key === 'ref')
4953
identifiers.add(value)
5054
})
@@ -71,7 +75,7 @@ export function parseVueSFC(code: string, id?: string): ParseResult {
7175
ontext(text) {
7276
if (templateLevel > 0) {
7377
Array.from(text.matchAll(/\{\{(.*?)\}\}/g)).forEach(([, expression]) => {
74-
expressions.add(expression)
78+
expressions.add(`(${expression})`)
7579
})
7680
}
7781
},
@@ -102,7 +106,10 @@ export function parseVueSFC(code: string, id?: string): ParseResult {
102106
parser.write(code)
103107
parser.end()
104108

105-
expressions.forEach(exp => getIdentifiersFromCode(exp, identifiers))
109+
expressions.forEach((exp) => {
110+
const nodes = parse(exp).program.body
111+
nodes.forEach(node => getIdentifiersUsage(node, identifiers))
112+
})
106113

107114
return {
108115
id,
@@ -115,12 +122,70 @@ export function parseVueSFC(code: string, id?: string): ParseResult {
115122
}
116123
}
117124

118-
export function getIdentifiersFromCode(code: string, identifiers = new Set<string>(), options: ParserOptions = {}) {
119-
const ast = parse(code, options) as any
120-
traverse(ast, {
121-
Identifier(path) {
122-
identifiers.add(path.node.name)
123-
},
124-
})
125+
export function getIdentifiersDeclaration(nodes: Statement[], identifiers = new Set<string>()) {
126+
for (const node of nodes) {
127+
if (node.type === 'ImportDeclaration') {
128+
for (const specifier of node.specifiers)
129+
identifiers.add(specifier.local.name)
130+
}
131+
else if (node.type === 'VariableDeclaration') {
132+
for (const declarator of node.declarations) {
133+
// @ts-expect-error
134+
identifiers.add(declarator.id.name)
135+
}
136+
}
137+
else if (node.type === 'FunctionDeclaration') {
138+
if (node.id)
139+
identifiers.add(node.id.name)
140+
}
141+
// else {
142+
// console.log(node)
143+
// }
144+
}
145+
return identifiers
146+
}
147+
148+
export function getIdentifiersUsage(node?: Expression | PrivateName | Statement, identifiers = new Set<string>()) {
149+
if (!node)
150+
return identifiers
151+
152+
if (node.type === 'ExpressionStatement') {
153+
getIdentifiersUsage(node.expression, identifiers)
154+
}
155+
else if (node.type === 'Identifier') {
156+
identifiers.add(node.name)
157+
}
158+
else if (node.type === 'MemberExpression') {
159+
getIdentifiersUsage(node.object, identifiers)
160+
}
161+
else if (node.type === 'CallExpression') {
162+
// @ts-expect-error
163+
getIdentifiersUsage(node.callee, identifiers)
164+
node.arguments.forEach((arg) => {
165+
// @ts-expect-error
166+
getIdentifiersUsage(arg, identifiers)
167+
})
168+
}
169+
else if (node.type === 'BinaryExpression' || node.type === 'LogicalExpression') {
170+
getIdentifiersUsage(node.left, identifiers)
171+
getIdentifiersUsage(node.right, identifiers)
172+
}
173+
else if (node.type === 'ForOfStatement' || node.type === 'ForInStatement') {
174+
getIdentifiersUsage(node.right, identifiers)
175+
}
176+
else if (node.type === 'ConditionalExpression') {
177+
getIdentifiersUsage(node.test, identifiers)
178+
getIdentifiersUsage(node.consequent, identifiers)
179+
getIdentifiersUsage(node.alternate, identifiers)
180+
}
181+
else if (node.type === 'ObjectExpression') {
182+
node.properties.forEach((prop) => {
183+
// @ts-expect-error
184+
getIdentifiersUsage(prop.value, identifiers)
185+
})
186+
}
187+
// else {
188+
// console.log(node)
189+
// }
125190
return identifiers
126191
}

src/transform.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,21 @@ export function transform(sfc: string, id?: string) {
1616
.map(([key, value]) => value ? `${key}="${value}"` : key)
1717
.join(' ')
1818

19-
s.remove(result.script.start, result.script.end)
20-
s.overwrite(
21-
result.scriptSetup.start,
22-
result.scriptSetup.end,
23-
`<script ${attr}>\n${code}\n</script>`,
24-
)
19+
if (code) {
20+
const block = `<script ${attr}>\n${code}\n</script>`
21+
22+
s.remove(result.script.start, result.script.end)
23+
if (result.scriptSetup.start !== result.scriptSetup.end) {
24+
s.overwrite(
25+
result.scriptSetup.start,
26+
result.scriptSetup.end,
27+
block,
28+
)
29+
}
30+
else {
31+
s.prependLeft(0, `${block}\n`)
32+
}
33+
}
2534

2635
return {
2736
code: s.toString(),

src/transformScriptSetup.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import traverse from '@babel/traverse'
55
import generate from '@babel/generator'
66
import { ParseResult } from './types'
77
import { applyMacros } from './macros'
8+
import { getIdentifiersDeclaration } from './parse'
89

910
export function transformScriptSetup(result: ParseResult) {
1011
if (result.script.found && result.scriptSetup.found && result.scriptSetup.attrs.lang !== result.script.attrs.lang)
@@ -26,7 +27,7 @@ export function transformScriptSetup(result: ParseResult) {
2627
sourceType: 'module',
2728
plugins,
2829
})
29-
const scriptAst = parse(result.script.content || 'export default {}', {
30+
const scriptAst = parse(result.script.content || '', {
3031
sourceType: 'module',
3132
plugins,
3233
})
@@ -37,12 +38,7 @@ export function transformScriptSetup(result: ParseResult) {
3738
const { nodes: scriptSetupBody, props } = applyMacros(nodes)
3839

3940
// get all identifiers in `<script setup>`
40-
scriptSetupAst.program.body = [...imports, ...nodes]
41-
traverse(scriptSetupAst as any, {
42-
Identifier(path) {
43-
identifiers.add(path.node.name)
44-
},
45-
})
41+
getIdentifiersDeclaration([...imports, ...nodes], identifiers)
4642

4743
const returns = Array.from(identifiers).filter(i => result.template.identifiers.has(i))
4844
const components = Array.from(identifiers).filter(i => result.template.components.has(i)
@@ -56,10 +52,13 @@ export function transformScriptSetup(result: ParseResult) {
5652

5753
const __sfc = t.identifier('__sfc_main')
5854

55+
let hasBody = false
56+
5957
// replace `export default` with a temproray variable
6058
// `const __sfc_main = { ... }`
6159
traverse(scriptAst as any, {
6260
ExportDefaultDeclaration(path) {
61+
hasBody = true
6362
const decl = path.node.declaration
6463
path.replaceWith(
6564
t.variableDeclaration('const', [
@@ -72,9 +71,22 @@ export function transformScriptSetup(result: ParseResult) {
7271
},
7372
})
7473

74+
// inject `const __sfc_main = {}` if `<script>` has default export
75+
if (!hasBody) {
76+
scriptAst.program.body.push(
77+
t.variableDeclaration('const', [
78+
t.variableDeclarator(
79+
__sfc,
80+
t.objectExpression([]),
81+
),
82+
]),
83+
)
84+
}
85+
7586
// inject props function
7687
// `__sfc_main.props = { ... }`
7788
if (props) {
89+
hasBody = true
7890
scriptAst.program.body.push(
7991
t.expressionStatement(
8092
t.assignmentExpression('=',
@@ -88,6 +100,7 @@ export function transformScriptSetup(result: ParseResult) {
88100
// inject setup function
89101
// `__sfc_main.setup = () => {}`
90102
if (nodes.length) {
103+
hasBody = true
91104
const returnStatement = t.returnStatement(
92105
t.objectExpression(
93106
returns.map((i) => {
@@ -116,6 +129,7 @@ export function transformScriptSetup(result: ParseResult) {
116129
// inject components
117130
// `__sfc_main.components = Object.assign({ ... }, __sfc_main.components)`
118131
if (components.length) {
132+
hasBody = true
119133
const componentsObject = t.objectExpression(
120134
components.map((i) => {
121135
const id = t.identifier(i)
@@ -139,6 +153,13 @@ export function transformScriptSetup(result: ParseResult) {
139153
)
140154
}
141155

156+
if (!hasBody) {
157+
return {
158+
ast: null,
159+
code: '',
160+
}
161+
}
162+
142163
// re-export
143164
// `export default __sfc_main`
144165
scriptAst.program.body.push(

0 commit comments

Comments
 (0)