Skip to content

Commit 57f1081

Browse files
committed
fix(compiler-sfc): fix import usage check for lowercase imported components
fix #4358
1 parent 03abc25 commit 57f1081

File tree

3 files changed

+147
-29
lines changed

3 files changed

+147
-29
lines changed

packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap

+76-16
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,82 @@ return { props, a, emit }
126126
}"
127127
`;
128128
129+
exports[`SFC compile <script setup> dev mode import usage check components 1`] = `
130+
"import { defineComponent as _defineComponent } from 'vue'
131+
import { FooBar, FooBaz, FooQux, foo } from './x'
132+
133+
export default _defineComponent({
134+
setup(__props, { expose }) {
135+
expose()
136+
137+
const fooBar: FooBar = 1
138+
139+
return { fooBar, FooBaz, FooQux, foo }
140+
}
141+
142+
})"
143+
`;
144+
145+
exports[`SFC compile <script setup> dev mode import usage check directive 1`] = `
146+
"import { defineComponent as _defineComponent } from 'vue'
147+
import { vMyDir } from './x'
148+
149+
export default _defineComponent({
150+
setup(__props, { expose }) {
151+
expose()
152+
153+
154+
return { vMyDir }
155+
}
156+
157+
})"
158+
`;
159+
160+
exports[`SFC compile <script setup> dev mode import usage check js template string interpolations 1`] = `
161+
"import { defineComponent as _defineComponent } from 'vue'
162+
import { VAR, VAR2, VAR3 } from './x'
163+
164+
export default _defineComponent({
165+
setup(__props, { expose }) {
166+
expose()
167+
168+
169+
return { VAR, VAR3 }
170+
}
171+
172+
})"
173+
`;
174+
175+
exports[`SFC compile <script setup> dev mode import usage check last tag 1`] = `
176+
"import { defineComponent as _defineComponent } from 'vue'
177+
import { FooBaz, Last } from './x'
178+
179+
export default _defineComponent({
180+
setup(__props, { expose }) {
181+
expose()
182+
183+
184+
return { FooBaz, Last }
185+
}
186+
187+
})"
188+
`;
189+
190+
exports[`SFC compile <script setup> dev mode import usage check vue interpolations 1`] = `
191+
"import { defineComponent as _defineComponent } from 'vue'
192+
import { x, y, z, x$y } from './x'
193+
194+
export default _defineComponent({
195+
setup(__props, { expose }) {
196+
expose()
197+
198+
199+
return { x, z, x$y }
200+
}
201+
202+
})"
203+
`;
204+
129205
exports[`SFC compile <script setup> errors should allow defineProps/Emit() referencing imported binding 1`] = `
130206
"import { bar } from './bar'
131207
@@ -204,22 +280,6 @@ return { x }
204280
}"
205281
`;
206282
207-
exports[`SFC compile <script setup> imports imports not used in <template> should not be exposed 1`] = `
208-
"import { defineComponent as _defineComponent } from 'vue'
209-
import { FooBar, FooBaz, FooQux, vMyDir, x, y, z, x$y, VAR, VAR2, VAR3, Last } from './x'
210-
211-
export default _defineComponent({
212-
setup(__props, { expose }) {
213-
expose()
214-
215-
const fooBar: FooBar = 1
216-
217-
return { fooBar, FooBaz, FooQux, vMyDir, x, z, x$y, VAR, VAR3, Last }
218-
}
219-
220-
})"
221-
`;
222-
223283
exports[`SFC compile <script setup> imports should allow defineProps/Emit at the start of imports 1`] = `
224284
"import { ref } from 'vue'
225285

packages/compiler-sfc/__tests__/compileScript.spec.ts

+70-12
Original file line numberDiff line numberDiff line change
@@ -209,32 +209,90 @@ defineExpose({ foo: 123 })
209209
content.lastIndexOf(`import { x }`)
210210
)
211211
})
212+
})
212213

213-
test('imports not used in <template> should not be exposed', () => {
214+
// in dev mode, declared bindings are returned as an object from setup()
215+
// when using TS, users may import types which should not be returned as
216+
// values, so we need to check import usage in the template to determine
217+
// what to be returned.
218+
describe('dev mode import usage check', () => {
219+
test('components', () => {
214220
const { content } = compile(`
215221
<script setup lang="ts">
216-
import { FooBar, FooBaz, FooQux, vMyDir, x, y, z, x$y, VAR, VAR2, VAR3, Last } from './x'
222+
import { FooBar, FooBaz, FooQux, foo } from './x'
217223
const fooBar: FooBar = 1
218224
</script>
219225
<template>
220-
<FooBaz v-my-dir>{{ x }} {{ yy }} {{ x$y }}</FooBaz>
226+
<FooBaz></FooBaz>
221227
<foo-qux/>
222-
<div :id="z + 'y'">FooBar</div>
223-
{{ \`\${VAR}VAR2\${VAR3}\` }}
224-
<Last/>
228+
<foo/>
229+
FooBar
225230
</template>
226231
`)
227-
// FooBar: should not be matched by plain text
232+
// FooBar: should not be matched by plain text or incorrect case
228233
// FooBaz: used as PascalCase component
229234
// FooQux: used as kebab-case component
230-
// vMyDir: used as directive v-my-dir
235+
// foo: lowercase component
236+
expect(content).toMatch(`return { fooBar, FooBaz, FooQux, foo }`)
237+
assertCode(content)
238+
})
239+
240+
test('directive', () => {
241+
const { content } = compile(`
242+
<script setup lang="ts">
243+
import { vMyDir } from './x'
244+
</script>
245+
<template>
246+
<div v-my-dir></div>
247+
</template>
248+
`)
249+
expect(content).toMatch(`return { vMyDir }`)
250+
assertCode(content)
251+
})
252+
253+
test('vue interpolations', () => {
254+
const { content } = compile(`
255+
<script setup lang="ts">
256+
import { x, y, z, x$y } from './x'
257+
</script>
258+
<template>
259+
<div :id="z + 'y'">{{ x }} {{ yy }} {{ x$y }}</div>
260+
</template>
261+
`)
231262
// x: used in interpolation
232263
// y: should not be matched by {{ yy }} or 'y' in binding exps
233264
// x$y: #4274 should escape special chars when creating Regex
234-
// VAR & VAR3: #4340 interpolations in tempalte strings
235-
expect(content).toMatch(
236-
`return { fooBar, FooBaz, FooQux, vMyDir, x, z, x$y, VAR, VAR3, Last }`
237-
)
265+
expect(content).toMatch(`return { x, z, x$y }`)
266+
assertCode(content)
267+
})
268+
269+
// #4340 interpolations in tempalte strings
270+
test('js template string interpolations', () => {
271+
const { content } = compile(`
272+
<script setup lang="ts">
273+
import { VAR, VAR2, VAR3 } from './x'
274+
</script>
275+
<template>
276+
{{ \`\${VAR}VAR2\${VAR3}\` }}
277+
</template>
278+
`)
279+
// VAR2 should not be matched
280+
expect(content).toMatch(`return { VAR, VAR3 }`)
281+
assertCode(content)
282+
})
283+
284+
// edge case: last tag in template
285+
test('last tag', () => {
286+
const { content } = compile(`
287+
<script setup lang="ts">
288+
import { FooBaz, Last } from './x'
289+
</script>
290+
<template>
291+
<FooBaz></FooBaz>
292+
<Last/>
293+
</template>
294+
`)
295+
expect(content).toMatch(`return { FooBaz, Last }`)
238296
assertCode(content)
239297
})
240298
})

packages/compiler-sfc/src/compileScript.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2212,7 +2212,7 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
22122212
!parserOptions.isNativeTag!(node.tag) &&
22132213
!parserOptions.isBuiltInComponent!(node.tag)
22142214
) {
2215-
code += `,${capitalize(camelize(node.tag))}`
2215+
code += `,${camelize(node.tag)},${capitalize(camelize(node.tag))}`
22162216
}
22172217
for (let i = 0; i < node.props.length; i++) {
22182218
const prop = node.props[i]

0 commit comments

Comments
 (0)