fix(compiler-sfc): fix template usage check false positives on types

fix #5414
This commit is contained in:
Evan You 2022-05-12 18:23:10 +08:00
parent ba17792b72
commit ccf92564d3
3 changed files with 71 additions and 8 deletions

View File

@ -535,6 +535,23 @@ return { props, a, emit }
}" }"
`; `;
exports[`SFC compile <script setup> dev mode import usage check TS annotations 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
import { Foo, Bar, Baz } from './x'
export default /*#__PURE__*/_defineComponent({
setup(__props, { expose }) {
expose();
const a = 1
function b() {}
return { a, b, Baz }
}
})"
`;
exports[`SFC compile <script setup> dev mode import usage check attribute expressions 1`] = ` exports[`SFC compile <script setup> dev mode import usage check attribute expressions 1`] = `
"import { defineComponent as _defineComponent } from 'vue' "import { defineComponent as _defineComponent } from 'vue'
import { bar, baz } from './x' import { bar, baz } from './x'

View File

@ -432,6 +432,23 @@ defineExpose({ foo: 123 })
expect(content).toMatch(`return { FooBaz, Last }`) expect(content).toMatch(`return { FooBaz, Last }`)
assertCode(content) assertCode(content)
}) })
test('TS annotations', () => {
const { content } = compile(`
<script setup lang="ts">
import { Foo, Bar, Baz } from './x'
const a = 1
function b() {}
</script>
<template>
{{ a as Foo }}
{{ b<Bar>() }}
{{ Baz }}
</template>
`)
expect(content).toMatch(`return { a, b, Baz }`)
assertCode(content)
})
}) })
describe('inlineTemplate mode', () => { describe('inlineTemplate mode', () => {

View File

@ -12,7 +12,12 @@ import {
walkIdentifiers walkIdentifiers
} from '@vue/compiler-dom' } from '@vue/compiler-dom'
import { DEFAULT_FILENAME, SFCDescriptor, SFCScriptBlock } from './parse' import { DEFAULT_FILENAME, SFCDescriptor, SFCScriptBlock } from './parse'
import { parse as _parse, ParserOptions, ParserPlugin } from '@babel/parser' import {
parse as _parse,
parseExpression,
ParserOptions,
ParserPlugin
} from '@babel/parser'
import { camelize, capitalize, generateCodeFrame, makeMap } from '@vue/shared' import { camelize, capitalize, generateCodeFrame, makeMap } from '@vue/shared'
import { import {
Node, Node,
@ -348,14 +353,23 @@ export function compileScript(
local: string, local: string,
imported: string | false, imported: string | false,
isType: boolean, isType: boolean,
isFromSetup: boolean isFromSetup: boolean,
needTemplateUsageCheck: boolean
) { ) {
if (source === 'vue' && imported) { if (source === 'vue' && imported) {
userImportAlias[imported] = local userImportAlias[imported] = local
} }
let isUsedInTemplate = true // template usage check is only needed in non-inline mode, so we can skip
if (isTS && sfc.template && !sfc.template.src && !sfc.template.lang) { // the work if inlineTemplate is true.
let isUsedInTemplate = needTemplateUsageCheck
if (
needTemplateUsageCheck &&
isTS &&
sfc.template &&
!sfc.template.src &&
!sfc.template.lang
) {
isUsedInTemplate = isImportUsed(local, sfc) isUsedInTemplate = isImportUsed(local, sfc)
} }
@ -813,7 +827,8 @@ export function compileScript(
node.importKind === 'type' || node.importKind === 'type' ||
(specifier.type === 'ImportSpecifier' && (specifier.type === 'ImportSpecifier' &&
specifier.importKind === 'type'), specifier.importKind === 'type'),
false false,
!options.inlineTemplate
) )
} }
} else if (node.type === 'ExportDefaultDeclaration') { } else if (node.type === 'ExportDefaultDeclaration') {
@ -1027,7 +1042,8 @@ export function compileScript(
node.importKind === 'type' || node.importKind === 'type' ||
(specifier.type === 'ImportSpecifier' && (specifier.type === 'ImportSpecifier' &&
specifier.importKind === 'type'), specifier.importKind === 'type'),
true true,
!options.inlineTemplate
) )
} }
} }
@ -2051,14 +2067,14 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
code += `,v${capitalize(camelize(prop.name))}` code += `,v${capitalize(camelize(prop.name))}`
} }
if (prop.exp) { if (prop.exp) {
code += `,${stripStrings( code += `,${processExp(
(prop.exp as SimpleExpressionNode).content (prop.exp as SimpleExpressionNode).content
)}` )}`
} }
} }
} }
} else if (node.type === NodeTypes.INTERPOLATION) { } else if (node.type === NodeTypes.INTERPOLATION) {
code += `,${stripStrings( code += `,${processExp(
(node.content as SimpleExpressionNode).content (node.content as SimpleExpressionNode).content
)}` )}`
} }
@ -2071,6 +2087,19 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
return code return code
} }
function processExp(exp: string) {
if (/ as \w|<.*>/.test(exp)) {
let ret = ''
// has potential type cast or generic arguments that uses types
const ast = parseExpression(exp, { plugins: ['typescript'] })
walkIdentifiers(ast, node => {
ret += `,` + node.name
})
return ret
}
return stripStrings(exp)
}
function stripStrings(exp: string) { function stripStrings(exp: string) {
return exp return exp
.replace(/'[^']*'|"[^"]*"/g, '') .replace(/'[^']*'|"[^"]*"/g, '')