Skip to content

Commit ccf9256

Browse files
committed
fix(compiler-sfc): fix template usage check false positives on types
fix #5414
1 parent ba17792 commit ccf9256

File tree

3 files changed

+71
-8
lines changed

3 files changed

+71
-8
lines changed

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

+17
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,23 @@ return { props, a, emit }
535535
}"
536536
`;
537537

538+
exports[`SFC compile <script setup> dev mode import usage check TS annotations 1`] = `
539+
"import { defineComponent as _defineComponent } from 'vue'
540+
import { Foo, Bar, Baz } from './x'
541+
542+
export default /*#__PURE__*/_defineComponent({
543+
setup(__props, { expose }) {
544+
expose();
545+
546+
const a = 1
547+
function b() {}
548+
549+
return { a, b, Baz }
550+
}
551+
552+
})"
553+
`;
554+
538555
exports[`SFC compile <script setup> dev mode import usage check attribute expressions 1`] = `
539556
"import { defineComponent as _defineComponent } from 'vue'
540557
import { bar, baz } from './x'

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

+17
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,23 @@ defineExpose({ foo: 123 })
432432
expect(content).toMatch(`return { FooBaz, Last }`)
433433
assertCode(content)
434434
})
435+
436+
test('TS annotations', () => {
437+
const { content } = compile(`
438+
<script setup lang="ts">
439+
import { Foo, Bar, Baz } from './x'
440+
const a = 1
441+
function b() {}
442+
</script>
443+
<template>
444+
{{ a as Foo }}
445+
{{ b<Bar>() }}
446+
{{ Baz }}
447+
</template>
448+
`)
449+
expect(content).toMatch(`return { a, b, Baz }`)
450+
assertCode(content)
451+
})
435452
})
436453

437454
describe('inlineTemplate mode', () => {

packages/compiler-sfc/src/compileScript.ts

+37-8
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ import {
1212
walkIdentifiers
1313
} from '@vue/compiler-dom'
1414
import { DEFAULT_FILENAME, SFCDescriptor, SFCScriptBlock } from './parse'
15-
import { parse as _parse, ParserOptions, ParserPlugin } from '@babel/parser'
15+
import {
16+
parse as _parse,
17+
parseExpression,
18+
ParserOptions,
19+
ParserPlugin
20+
} from '@babel/parser'
1621
import { camelize, capitalize, generateCodeFrame, makeMap } from '@vue/shared'
1722
import {
1823
Node,
@@ -348,14 +353,23 @@ export function compileScript(
348353
local: string,
349354
imported: string | false,
350355
isType: boolean,
351-
isFromSetup: boolean
356+
isFromSetup: boolean,
357+
needTemplateUsageCheck: boolean
352358
) {
353359
if (source === 'vue' && imported) {
354360
userImportAlias[imported] = local
355361
}
356362

357-
let isUsedInTemplate = true
358-
if (isTS && sfc.template && !sfc.template.src && !sfc.template.lang) {
363+
// template usage check is only needed in non-inline mode, so we can skip
364+
// the work if inlineTemplate is true.
365+
let isUsedInTemplate = needTemplateUsageCheck
366+
if (
367+
needTemplateUsageCheck &&
368+
isTS &&
369+
sfc.template &&
370+
!sfc.template.src &&
371+
!sfc.template.lang
372+
) {
359373
isUsedInTemplate = isImportUsed(local, sfc)
360374
}
361375

@@ -813,7 +827,8 @@ export function compileScript(
813827
node.importKind === 'type' ||
814828
(specifier.type === 'ImportSpecifier' &&
815829
specifier.importKind === 'type'),
816-
false
830+
false,
831+
!options.inlineTemplate
817832
)
818833
}
819834
} else if (node.type === 'ExportDefaultDeclaration') {
@@ -1027,7 +1042,8 @@ export function compileScript(
10271042
node.importKind === 'type' ||
10281043
(specifier.type === 'ImportSpecifier' &&
10291044
specifier.importKind === 'type'),
1030-
true
1045+
true,
1046+
!options.inlineTemplate
10311047
)
10321048
}
10331049
}
@@ -2051,14 +2067,14 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
20512067
code += `,v${capitalize(camelize(prop.name))}`
20522068
}
20532069
if (prop.exp) {
2054-
code += `,${stripStrings(
2070+
code += `,${processExp(
20552071
(prop.exp as SimpleExpressionNode).content
20562072
)}`
20572073
}
20582074
}
20592075
}
20602076
} else if (node.type === NodeTypes.INTERPOLATION) {
2061-
code += `,${stripStrings(
2077+
code += `,${processExp(
20622078
(node.content as SimpleExpressionNode).content
20632079
)}`
20642080
}
@@ -2071,6 +2087,19 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
20712087
return code
20722088
}
20732089

2090+
function processExp(exp: string) {
2091+
if (/ as \w|<.*>/.test(exp)) {
2092+
let ret = ''
2093+
// has potential type cast or generic arguments that uses types
2094+
const ast = parseExpression(exp, { plugins: ['typescript'] })
2095+
walkIdentifiers(ast, node => {
2096+
ret += `,` + node.name
2097+
})
2098+
return ret
2099+
}
2100+
return stripStrings(exp)
2101+
}
2102+
20742103
function stripStrings(exp: string) {
20752104
return exp
20762105
.replace(/'[^']*'|"[^"]*"/g, '')

0 commit comments

Comments
 (0)