fix(compiler-sfc): fix template usage check false positives on types
fix #5414
This commit is contained in:
		
							parent
							
								
									ba17792b72
								
							
						
					
					
						commit
						ccf92564d3
					
				@ -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'
 | 
				
			||||||
 | 
				
			|||||||
@ -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', () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -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, '')
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user