]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-sfc): fix template usage check false positives on types
authorEvan You <yyx990803@gmail.com>
Thu, 12 May 2022 10:23:10 +0000 (18:23 +0800)
committerEvan You <yyx990803@gmail.com>
Thu, 12 May 2022 10:23:10 +0000 (18:23 +0800)
fix #5414

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

index cbe274ef9e8f6fdd3cf290c016e732afa5e4431a..757b153411ddc900178f8c94d1c8efd5883945fe 100644 (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`] = `
 "import { defineComponent as _defineComponent } from 'vue'
 import { bar, baz } from './x'
index d7b10f5d735109f9318a3c4e2e11ff17f347f4e8..8b3e918307d140c59a2a6784e8d918ce7165e0b8 100644 (file)
@@ -432,6 +432,23 @@ defineExpose({ foo: 123 })
       expect(content).toMatch(`return { FooBaz, Last }`)
       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', () => {
index 9c9c7ffbf82157b144d685d756c1aa4ca200f323..509934d6d910e113fda518be74babc39bb049256 100644 (file)
@@ -12,7 +12,12 @@ import {
   walkIdentifiers
 } from '@vue/compiler-dom'
 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 {
   Node,
@@ -348,14 +353,23 @@ export function compileScript(
     local: string,
     imported: string | false,
     isType: boolean,
-    isFromSetup: boolean
+    isFromSetup: boolean,
+    needTemplateUsageCheck: boolean
   ) {
     if (source === 'vue' && imported) {
       userImportAlias[imported] = local
     }
 
-    let isUsedInTemplate = true
-    if (isTS && sfc.template && !sfc.template.src && !sfc.template.lang) {
+    // template usage check is only needed in non-inline mode, so we can skip
+    // the work if inlineTemplate is true.
+    let isUsedInTemplate = needTemplateUsageCheck
+    if (
+      needTemplateUsageCheck &&
+      isTS &&
+      sfc.template &&
+      !sfc.template.src &&
+      !sfc.template.lang
+    ) {
       isUsedInTemplate = isImportUsed(local, sfc)
     }
 
@@ -813,7 +827,8 @@ export function compileScript(
             node.importKind === 'type' ||
               (specifier.type === 'ImportSpecifier' &&
                 specifier.importKind === 'type'),
-            false
+            false,
+            !options.inlineTemplate
           )
         }
       } else if (node.type === 'ExportDefaultDeclaration') {
@@ -1027,7 +1042,8 @@ export function compileScript(
             node.importKind === 'type' ||
               (specifier.type === 'ImportSpecifier' &&
                 specifier.importKind === 'type'),
-            true
+            true,
+            !options.inlineTemplate
           )
         }
       }
@@ -2051,14 +2067,14 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
                 code += `,v${capitalize(camelize(prop.name))}`
               }
               if (prop.exp) {
-                code += `,${stripStrings(
+                code += `,${processExp(
                   (prop.exp as SimpleExpressionNode).content
                 )}`
               }
             }
           }
         } else if (node.type === NodeTypes.INTERPOLATION) {
-          code += `,${stripStrings(
+          code += `,${processExp(
             (node.content as SimpleExpressionNode).content
           )}`
         }
@@ -2071,6 +2087,19 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
   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) {
   return exp
     .replace(/'[^']*'|"[^"]*"/g, '')