packages/compiler-sfc/src/compileScript.ts TYPESCRIPT 1,917 lines View on github.com → Search inside
1import MagicString from 'magic-string'2import LRU from 'lru-cache'3import { walkIdentifiers, isFunctionType } from './babelUtils'4import { BindingMetadata, BindingTypes } from './types'5import { SFCDescriptor, SFCScriptBlock } from './parseComponent'6import {7  parse as _parse,8  parseExpression,9  ParserOptions,10  ParserPlugin11} from '@babel/parser'12import { generateCodeFrame } from 'compiler/codeframe'13import { camelize, capitalize, isBuiltInTag, makeMap } from 'shared/util'14import { parseHTML } from 'compiler/parser/html-parser'15import { baseOptions as webCompilerOptions } from 'web/compiler/options'16import {17  Node,18  Declaration,19  ObjectPattern,20  ObjectExpression,21  ArrayPattern,22  Identifier,23  ExportSpecifier,24  TSType,25  TSTypeLiteral,26  TSFunctionType,27  ObjectProperty,28  ArrayExpression,29  Statement,30  CallExpression,31  RestElement,32  TSInterfaceBody,33  Program,34  ObjectMethod,35  LVal,36  Expression37} from '@babel/types'38import { walk } from 'estree-walker'39import { RawSourceMap } from 'source-map'40import { warnOnce } from './warn'41import { isReservedTag } from 'web/util'42import { bindRE, dirRE, onRE, slotRE } from 'compiler/parser'43import { parseText } from 'compiler/parser/text-parser'44import { DEFAULT_FILENAME } from './parseComponent'45import {46  CSS_VARS_HELPER,47  genCssVarsCode,48  genNormalScriptCssVarsCode49} from './cssVars'50import { rewriteDefault } from './rewriteDefault'5152// Special compiler macros53const DEFINE_PROPS = 'defineProps'54const DEFINE_EMITS = 'defineEmits'55const DEFINE_EXPOSE = 'defineExpose'56const WITH_DEFAULTS = 'withDefaults'5758// constants59const DEFAULT_VAR = `__default__`6061const isBuiltInDir = makeMap(62  `once,memo,if,for,else,else-if,slot,text,html,on,bind,model,show,cloak,is`63)6465export interface SFCScriptCompileOptions {66  /**67   * Scope ID for prefixing injected CSS variables.68   * This must be consistent with the `id` passed to `compileStyle`.69   */70  id: string71  /**72   * Production mode. Used to determine whether to generate hashed CSS variables73   */74  isProd?: boolean75  /**76   * Enable/disable source map. Defaults to true.77   */78  sourceMap?: boolean79  /**80   * https://babeljs.io/docs/en/babel-parser#plugins81   */82  babelParserPlugins?: ParserPlugin[]83}8485export interface ImportBinding {86  isType: boolean87  imported: string88  source: string89  isFromSetup: boolean90  isUsedInTemplate: boolean91}9293/**94 * Compile `<script setup>`95 * It requires the whole SFC descriptor because we need to handle and merge96 * normal `<script>` + `<script setup>` if both are present.97 */98export function compileScript(99  sfc: SFCDescriptor,100  options: SFCScriptCompileOptions = { id: '' }101): SFCScriptBlock {102  let { filename, script, scriptSetup, source } = sfc103  const isProd = !!options.isProd104  const genSourceMap = options.sourceMap !== false105  let refBindings: string[] | undefined106107  const cssVars = sfc.cssVars108  const scopeId = options.id ? options.id.replace(/^data-v-/, '') : ''109  const scriptLang = script && script.lang110  const scriptSetupLang = scriptSetup && scriptSetup.lang111  const isTS =112    scriptLang === 'ts' ||113    scriptLang === 'tsx' ||114    scriptSetupLang === 'ts' ||115    scriptSetupLang === 'tsx'116117  // resolve parser plugins118  const plugins: ParserPlugin[] = []119  if (!isTS || scriptLang === 'tsx' || scriptSetupLang === 'tsx') {120    plugins.push('jsx')121  } else {122    // If don't match the case of adding jsx, should remove the jsx from the babelParserPlugins123    if (options.babelParserPlugins)124      options.babelParserPlugins = options.babelParserPlugins.filter(125        n => n !== 'jsx'126      )127  }128  if (options.babelParserPlugins) plugins.push(...options.babelParserPlugins)129  if (isTS) {130    plugins.push('typescript')131    if (!plugins.includes('decorators')) {132      plugins.push('decorators-legacy')133    }134  }135136  if (!scriptSetup) {137    if (!script) {138      throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)139    }140    if (scriptLang && !isTS && scriptLang !== 'jsx') {141      // do not process non js/ts script blocks142      return script143    }144    try {145      let content = script.content146      let map = script.map147      const scriptAst = _parse(content, {148        plugins,149        sourceType: 'module'150      }).program151      const bindings = analyzeScriptBindings(scriptAst.body)152      if (cssVars.length) {153        content = rewriteDefault(content, DEFAULT_VAR, plugins)154        content += genNormalScriptCssVarsCode(155          cssVars,156          bindings,157          scopeId,158          isProd159        )160        content += `\nexport default ${DEFAULT_VAR}`161      }162      return {163        ...script,164        content,165        map,166        bindings,167        scriptAst: scriptAst.body168      }169    } catch (e: any) {170      // silently fallback if parse fails since user may be using custom171      // babel syntax172      return script173    }174  }175176  if (script && scriptLang !== scriptSetupLang) {177    throw new Error(178      `[@vue/compiler-sfc] <script> and <script setup> must have the same ` +179        `language type.`180    )181  }182183  if (scriptSetupLang && !isTS && scriptSetupLang !== 'jsx') {184    // do not process non js/ts script blocks185    return scriptSetup186  }187188  // metadata that needs to be returned189  const bindingMetadata: BindingMetadata = {}190  const helperImports: Set<string> = new Set()191  const userImports: Record<string, ImportBinding> = Object.create(null)192  const userImportAlias: Record<string, string> = Object.create(null)193  const scriptBindings: Record<string, BindingTypes> = Object.create(null)194  const setupBindings: Record<string, BindingTypes> = Object.create(null)195196  let defaultExport: Node | undefined197  let hasDefinePropsCall = false198  let hasDefineEmitCall = false199  let hasDefineExposeCall = false200  let hasDefaultExportName = false201  let propsRuntimeDecl: Node | undefined202  let propsRuntimeDefaults: ObjectExpression | undefined203  let propsDestructureDecl: Node | undefined204  let propsDestructureRestId: string | undefined205  let propsTypeDecl: TSTypeLiteral | TSInterfaceBody | undefined206  let propsTypeDeclRaw: Node | undefined207  let propsIdentifier: string | undefined208  let emitsRuntimeDecl: Node | undefined209  let emitsTypeDecl:210    | TSFunctionType211    | TSTypeLiteral212    | TSInterfaceBody213    | undefined214  let emitsTypeDeclRaw: Node | undefined215  let emitIdentifier: string | undefined216  let hasInlinedSsrRenderFn = false217  // props/emits declared via types218  const typeDeclaredProps: Record<string, PropTypeData> = {}219  const typeDeclaredEmits: Set<string> = new Set()220  // record declared types for runtime props type generation221  const declaredTypes: Record<string, string[]> = {}222  // props destructure data223  const propsDestructuredBindings: Record<224    string, // public prop key225    {226      local: string // local identifier, may be different227      default?: Expression228    }229  > = Object.create(null)230231  // magic-string state232  const s = new MagicString(source)233  const startOffset = scriptSetup.start234  const endOffset = scriptSetup.end235  const scriptStartOffset = script && script.start236  const scriptEndOffset = script && script.end237238  function helper(key: string): string {239    helperImports.add(key)240    return `_${key}`241  }242243  function parse(244    input: string,245    options: ParserOptions,246    offset: number247  ): Program {248    try {249      return _parse(input, options).program250    } catch (e: any) {251      e.message = `[@vue/compiler-sfc] ${252        e.message253      }\n\n${filename}\n${generateCodeFrame(254        source,255        e.pos + offset,256        e.pos + offset + 1257      )}`258      throw e259    }260  }261262  function error(263    msg: string,264    node: Node,265    end: number = node.end! + startOffset266  ): never {267    throw new Error(268      `[@vue/compiler-sfc] ${msg}\n\n${filename}\n${generateCodeFrame(269        source,270        node.start! + startOffset,271        end272      )}`273    )274  }275276  function registerUserImport(277    source: string,278    local: string,279    imported: string | false,280    isType: boolean,281    isFromSetup: boolean282  ) {283    if (source === 'vue' && imported) {284      userImportAlias[imported] = local285    }286287    let isUsedInTemplate = true288    if (sfc.template && !sfc.template.src && !sfc.template.lang) {289      isUsedInTemplate = isImportUsed(local, sfc, isTS)290    }291292    userImports[local] = {293      isType,294      imported: imported || 'default',295      source,296      isFromSetup,297      isUsedInTemplate298    }299  }300301  function processDefineProps(node: Node, declId?: LVal): boolean {302    if (!isCallOf(node, DEFINE_PROPS)) {303      return false304    }305306    if (hasDefinePropsCall) {307      error(`duplicate ${DEFINE_PROPS}() call`, node)308    }309    hasDefinePropsCall = true310311    propsRuntimeDecl = node.arguments[0]312313    // call has type parameters - infer runtime types from it314    if (node.typeParameters) {315      if (propsRuntimeDecl) {316        error(317          `${DEFINE_PROPS}() cannot accept both type and non-type arguments ` +318            `at the same time. Use one or the other.`,319          node320        )321      }322323      propsTypeDeclRaw = node.typeParameters.params[0]324      propsTypeDecl = resolveQualifiedType(325        propsTypeDeclRaw,326        node => node.type === 'TSTypeLiteral'327      ) as TSTypeLiteral | TSInterfaceBody | undefined328329      if (!propsTypeDecl) {330        error(331          `type argument passed to ${DEFINE_PROPS}() must be a literal type, ` +332            `or a reference to an interface or literal type.`,333          propsTypeDeclRaw334        )335      }336    }337338    if (declId) {339      propsIdentifier = scriptSetup!.content.slice(declId.start!, declId.end!)340    }341342    return true343  }344345  function processWithDefaults(node: Node, declId?: LVal): boolean {346    if (!isCallOf(node, WITH_DEFAULTS)) {347      return false348    }349    if (processDefineProps(node.arguments[0], declId)) {350      if (propsRuntimeDecl) {351        error(352          `${WITH_DEFAULTS} can only be used with type-based ` +353            `${DEFINE_PROPS} declaration.`,354          node355        )356      }357      if (propsDestructureDecl) {358        error(359          `${WITH_DEFAULTS}() is unnecessary when using destructure with ${DEFINE_PROPS}().\n` +360            `Prefer using destructure default values, e.g. const { foo = 1 } = defineProps(...).`,361          node.callee362        )363      }364      propsRuntimeDefaults = node.arguments[1] as ObjectExpression365      if (366        !propsRuntimeDefaults ||367        propsRuntimeDefaults.type !== 'ObjectExpression'368      ) {369        error(370          `The 2nd argument of ${WITH_DEFAULTS} must be an object literal.`,371          propsRuntimeDefaults || node372        )373      }374    } else {375      error(376        `${WITH_DEFAULTS}' first argument must be a ${DEFINE_PROPS} call.`,377        node.arguments[0] || node378      )379    }380    return true381  }382383  function processDefineEmits(node: Node, declId?: LVal): boolean {384    if (!isCallOf(node, DEFINE_EMITS)) {385      return false386    }387    if (hasDefineEmitCall) {388      error(`duplicate ${DEFINE_EMITS}() call`, node)389    }390    hasDefineEmitCall = true391    emitsRuntimeDecl = node.arguments[0]392    if (node.typeParameters) {393      if (emitsRuntimeDecl) {394        error(395          `${DEFINE_EMITS}() cannot accept both type and non-type arguments ` +396            `at the same time. Use one or the other.`,397          node398        )399      }400401      emitsTypeDeclRaw = node.typeParameters.params[0]402      emitsTypeDecl = resolveQualifiedType(403        emitsTypeDeclRaw,404        node => node.type === 'TSFunctionType' || node.type === 'TSTypeLiteral'405      ) as TSFunctionType | TSTypeLiteral | TSInterfaceBody | undefined406407      if (!emitsTypeDecl) {408        error(409          `type argument passed to ${DEFINE_EMITS}() must be a function type, ` +410            `a literal type with call signatures, or a reference to the above types.`,411          emitsTypeDeclRaw412        )413      }414    }415416    if (declId) {417      emitIdentifier =418        declId.type === 'Identifier'419          ? declId.name420          : scriptSetup!.content.slice(declId.start!, declId.end!)421    }422423    return true424  }425426  function resolveQualifiedType(427    node: Node,428    qualifier: (node: Node) => boolean429  ) {430    if (qualifier(node)) {431      return node432    }433    if (434      node.type === 'TSTypeReference' &&435      node.typeName.type === 'Identifier'436    ) {437      const refName = node.typeName.name438      const isQualifiedType = (node: Node): Node | undefined => {439        if (440          node.type === 'TSInterfaceDeclaration' &&441          node.id.name === refName442        ) {443          return node.body444        } else if (445          node.type === 'TSTypeAliasDeclaration' &&446          node.id.name === refName &&447          qualifier(node.typeAnnotation)448        ) {449          return node.typeAnnotation450        } else if (node.type === 'ExportNamedDeclaration' && node.declaration) {451          return isQualifiedType(node.declaration)452        }453      }454      const body = scriptAst455        ? [...scriptSetupAst.body, ...scriptAst.body]456        : scriptSetupAst.body457      for (const node of body) {458        const qualified = isQualifiedType(node)459        if (qualified) {460          return qualified461        }462      }463    }464  }465466  function processDefineExpose(node: Node): boolean {467    if (isCallOf(node, DEFINE_EXPOSE)) {468      if (hasDefineExposeCall) {469        error(`duplicate ${DEFINE_EXPOSE}() call`, node)470      }471      hasDefineExposeCall = true472      return true473    }474    return false475  }476477  function checkInvalidScopeReference(node: Node | undefined, method: string) {478    if (!node) return479    walkIdentifiers(node, id => {480      if (setupBindings[id.name]) {481        error(482          `\`${method}()\` in <script setup> cannot reference locally ` +483            `declared variables because it will be hoisted outside of the ` +484            `setup() function. If your component options require initialization ` +485            `in the module scope, use a separate normal <script> to export ` +486            `the options instead.`,487          id488        )489      }490    })491  }492493  /**494   * check defaults. If the default object is an object literal with only495   * static properties, we can directly generate more optimized default496   * declarations. Otherwise we will have to fallback to runtime merging.497   */498  function hasStaticWithDefaults() {499    return (500      propsRuntimeDefaults &&501      propsRuntimeDefaults.type === 'ObjectExpression' &&502      propsRuntimeDefaults.properties.every(503        node =>504          (node.type === 'ObjectProperty' && !node.computed) ||505          node.type === 'ObjectMethod'506      )507    )508  }509510  function genRuntimeProps(props: Record<string, PropTypeData>) {511    const keys = Object.keys(props)512    if (!keys.length) {513      return ``514    }515    const hasStaticDefaults = hasStaticWithDefaults()516    const scriptSetupSource = scriptSetup!.content517    let propsDecls = `{518    ${keys519      .map(key => {520        let defaultString: string | undefined521        const destructured = genDestructuredDefaultValue(key)522        if (destructured) {523          defaultString = `default: ${destructured}`524        } else if (hasStaticDefaults) {525          const prop = propsRuntimeDefaults!.properties.find(526            (node: any) => node.key.name === key527          ) as ObjectProperty | ObjectMethod528          if (prop) {529            if (prop.type === 'ObjectProperty') {530              // prop has corresponding static default value531              defaultString = `default: ${scriptSetupSource.slice(532                prop.value.start!,533                prop.value.end!534              )}`535            } else {536              defaultString = `default() ${scriptSetupSource.slice(537                prop.body.start!,538                prop.body.end!539              )}`540            }541          }542        }543544        const { type, required } = props[key]545        if (!isProd) {546          return `${key}: { type: ${toRuntimeTypeString(547            type548          )}, required: ${required}${549            defaultString ? `, ${defaultString}` : ``550          } }`551        } else if (552          type.some(553            el => el === 'Boolean' || (defaultString && el === 'Function')554          )555        ) {556          // #4783 production: if boolean or defaultString and function exists, should keep the type.557          return `${key}: { type: ${toRuntimeTypeString(type)}${558            defaultString ? `, ${defaultString}` : ``559          } }`560        } else {561          // production: checks are useless562          return `${key}: ${defaultString ? `{ ${defaultString} }` : 'null'}`563        }564      })565      .join(',\n    ')}\n  }`566567    if (propsRuntimeDefaults && !hasStaticDefaults) {568      propsDecls = `${helper('mergeDefaults')}(${propsDecls}, ${source.slice(569        propsRuntimeDefaults.start! + startOffset,570        propsRuntimeDefaults.end! + startOffset571      )})`572    }573574    return `\n  props: ${propsDecls},`575  }576577  function genDestructuredDefaultValue(key: string): string | undefined {578    const destructured = propsDestructuredBindings[key]579    if (destructured && destructured.default) {580      const value = scriptSetup!.content.slice(581        destructured.default.start!,582        destructured.default.end!583      )584      const isLiteral = destructured.default.type.endsWith('Literal')585      return isLiteral ? value : `() => (${value})`586    }587  }588589  function genSetupPropsType(node: TSTypeLiteral | TSInterfaceBody) {590    const scriptSetupSource = scriptSetup!.content591    if (hasStaticWithDefaults()) {592      // if withDefaults() is used, we need to remove the optional flags593      // on props that have default values594      let res = `{ `595      const members = node.type === 'TSTypeLiteral' ? node.members : node.body596      for (const m of members) {597        if (598          (m.type === 'TSPropertySignature' ||599            m.type === 'TSMethodSignature') &&600          m.typeAnnotation &&601          m.key.type === 'Identifier'602        ) {603          if (604            propsRuntimeDefaults!.properties.some(605              (p: any) => p.key.name === (m.key as Identifier).name606            )607          ) {608            res +=609              m.key.name +610              (m.type === 'TSMethodSignature' ? '()' : '') +611              scriptSetupSource.slice(612                m.typeAnnotation.start!,613                m.typeAnnotation.end!614              ) +615              ', '616          } else {617            res +=618              scriptSetupSource.slice(m.start!, m.typeAnnotation.end!) + `, `619          }620        }621      }622      return (res.length ? res.slice(0, -2) : res) + ` }`623    } else {624      return scriptSetupSource.slice(node.start!, node.end!)625    }626  }627628  // 1. process normal <script> first if it exists629  let scriptAst: Program | undefined630  if (script) {631    scriptAst = parse(632      script.content,633      {634        plugins,635        sourceType: 'module'636      },637      scriptStartOffset!638    )639640    for (const node of scriptAst.body) {641      if (node.type === 'ImportDeclaration') {642        // record imports for dedupe643        for (const specifier of node.specifiers) {644          const imported =645            specifier.type === 'ImportSpecifier' &&646            specifier.imported.type === 'Identifier' &&647            specifier.imported.name648          registerUserImport(649            node.source.value,650            specifier.local.name,651            imported,652            node.importKind === 'type' ||653              (specifier.type === 'ImportSpecifier' &&654                specifier.importKind === 'type'),655            false656          )657        }658      } else if (node.type === 'ExportDefaultDeclaration') {659        // export default660        defaultExport = node661662        // check if user has manually specified `name` or 'render` option in663        // export default664        // if has name, skip name inference665        // if has render and no template, generate return object instead of666        // empty render function (#4980)667        let optionProperties668        if (defaultExport.declaration.type === 'ObjectExpression') {669          optionProperties = defaultExport.declaration.properties670        } else if (671          defaultExport.declaration.type === 'CallExpression' &&672          defaultExport.declaration.arguments[0].type === 'ObjectExpression'673        ) {674          optionProperties = defaultExport.declaration.arguments[0].properties675        }676        if (optionProperties) {677          for (const s of optionProperties) {678            if (679              s.type === 'ObjectProperty' &&680              s.key.type === 'Identifier' &&681              s.key.name === 'name'682            ) {683              hasDefaultExportName = true684            }685          }686        }687688        // export default { ... } --> const __default__ = { ... }689        const start = node.start! + scriptStartOffset!690        const end = node.declaration.start! + scriptStartOffset!691        s.overwrite(start, end, `const ${DEFAULT_VAR} = `)692      } else if (node.type === 'ExportNamedDeclaration') {693        const defaultSpecifier = node.specifiers.find(694          s => s.exported.type === 'Identifier' && s.exported.name === 'default'695        ) as ExportSpecifier696        if (defaultSpecifier) {697          defaultExport = node698          // 1. remove specifier699          if (node.specifiers.length > 1) {700            s.remove(701              defaultSpecifier.start! + scriptStartOffset!,702              defaultSpecifier.end! + scriptStartOffset!703            )704          } else {705            s.remove(706              node.start! + scriptStartOffset!,707              node.end! + scriptStartOffset!708            )709          }710          if (node.source) {711            // export { x as default } from './x'712            // rewrite to `import { x as __default__ } from './x'` and713            // add to top714            s.prepend(715              `import { ${defaultSpecifier.local.name} as ${DEFAULT_VAR} } from '${node.source.value}'\n`716            )717          } else {718            // export { x as default }719            // rewrite to `const __default__ = x` and move to end720            s.appendLeft(721              scriptEndOffset!,722              `\nconst ${DEFAULT_VAR} = ${defaultSpecifier.local.name}\n`723            )724          }725        }726        if (node.declaration) {727          walkDeclaration(node.declaration, scriptBindings, userImportAlias)728        }729      } else if (730        (node.type === 'VariableDeclaration' ||731          node.type === 'FunctionDeclaration' ||732          node.type === 'ClassDeclaration' ||733          node.type === 'TSEnumDeclaration') &&734        !node.declare735      ) {736        walkDeclaration(node, scriptBindings, userImportAlias)737      }738    }739740    // apply reactivity transform741    // if (enableReactivityTransform && shouldTransform(script.content)) {742    //   const { rootRefs, importedHelpers } = transformAST(743    //     scriptAst,744    //     s,745    //     scriptStartOffset!746    //   )747    //   refBindings = rootRefs748    //   for (const h of importedHelpers) {749    //     helperImports.add(h)750    //   }751    // }752753    // <script> after <script setup>754    // we need to move the block up so that `const __default__` is755    // declared before being used in the actual component definition756    if (scriptStartOffset! > startOffset) {757      // if content doesn't end with newline, add one758      if (!/\n$/.test(script.content.trim())) {759        s.appendLeft(scriptEndOffset!, `\n`)760      }761      s.move(scriptStartOffset!, scriptEndOffset!, 0)762    }763  }764765  // 2. parse <script setup> and  walk over top level statements766  const scriptSetupAst = parse(767    scriptSetup.content,768    {769      plugins: [770        ...plugins,771        // allow top level await but only inside <script setup>772        'topLevelAwait'773      ],774      sourceType: 'module'775    },776    startOffset777  )778779  for (const node of scriptSetupAst.body) {780    const start = node.start! + startOffset781    let end = node.end! + startOffset782    // locate comment783    if (node.trailingComments && node.trailingComments.length > 0) {784      const lastCommentNode =785        node.trailingComments[node.trailingComments.length - 1]786      end = lastCommentNode.end! + startOffset787    }788    // locate the end of whitespace between this statement and the next789    while (end <= source.length) {790      if (!/\s/.test(source.charAt(end))) {791        break792      }793      end++794    }795796    // (Dropped) `ref: x` bindings797    if (798      node.type === 'LabeledStatement' &&799      node.label.name === 'ref' &&800      node.body.type === 'ExpressionStatement'801    ) {802      error(803        `ref sugar using the label syntax was an experimental proposal and ` +804          `has been dropped based on community feedback. Please check out ` +805          `the new proposal at https://github.com/vuejs/rfcs/discussions/369`,806        node807      )808    }809810    if (node.type === 'ImportDeclaration') {811      // import declarations are moved to top812      s.move(start, end, 0)813814      // dedupe imports815      let removed = 0816      const removeSpecifier = (i: number) => {817        const removeLeft = i > removed818        removed++819        const current = node.specifiers[i]820        const next = node.specifiers[i + 1]821        s.remove(822          removeLeft823            ? node.specifiers[i - 1].end! + startOffset824            : current.start! + startOffset,825          next && !removeLeft826            ? next.start! + startOffset827            : current.end! + startOffset828        )829      }830831      for (let i = 0; i < node.specifiers.length; i++) {832        const specifier = node.specifiers[i]833        const local = specifier.local.name834        let imported =835          specifier.type === 'ImportSpecifier' &&836          specifier.imported.type === 'Identifier' &&837          specifier.imported.name838        if (specifier.type === 'ImportNamespaceSpecifier') {839          imported = '*'840        }841        const source = node.source.value842        const existing = userImports[local]843        if (844          source === 'vue' &&845          (imported === DEFINE_PROPS ||846            imported === DEFINE_EMITS ||847            imported === DEFINE_EXPOSE)848        ) {849          warnOnce(850            `\`${imported}\` is a compiler macro and no longer needs to be imported.`851          )852          removeSpecifier(i)853        } else if (existing) {854          if (existing.source === source && existing.imported === imported) {855            // already imported in <script setup>, dedupe856            removeSpecifier(i)857          } else {858            error(`different imports aliased to same local name.`, specifier)859          }860        } else {861          registerUserImport(862            source,863            local,864            imported,865            node.importKind === 'type' ||866              (specifier.type === 'ImportSpecifier' &&867                specifier.importKind === 'type'),868            true869          )870        }871      }872      if (node.specifiers.length && removed === node.specifiers.length) {873        s.remove(node.start! + startOffset, node.end! + startOffset)874      }875    }876877    if (node.type === 'ExpressionStatement') {878      // process `defineProps` and `defineEmit(s)` calls879      if (880        processDefineProps(node.expression) ||881        processDefineEmits(node.expression) ||882        processWithDefaults(node.expression)883      ) {884        s.remove(node.start! + startOffset, node.end! + startOffset)885      } else if (processDefineExpose(node.expression)) {886        // defineExpose({}) -> expose({})887        const callee = (node.expression as CallExpression).callee888        s.overwrite(889          callee.start! + startOffset,890          callee.end! + startOffset,891          'expose'892        )893      }894    }895896    if (node.type === 'VariableDeclaration' && !node.declare) {897      const total = node.declarations.length898      let left = total899      for (let i = 0; i < total; i++) {900        const decl = node.declarations[i]901        if (decl.init) {902          // defineProps / defineEmits903          const isDefineProps =904            processDefineProps(decl.init, decl.id) ||905            processWithDefaults(decl.init, decl.id)906          const isDefineEmits = processDefineEmits(decl.init, decl.id)907          if (isDefineProps || isDefineEmits) {908            if (left === 1) {909              s.remove(node.start! + startOffset, node.end! + startOffset)910            } else {911              let start = decl.start! + startOffset912              let end = decl.end! + startOffset913              if (i === 0) {914                // first one, locate the start of the next915                end = node.declarations[i + 1].start! + startOffset916              } else {917                // not first one, locate the end of the prev918                start = node.declarations[i - 1].end! + startOffset919              }920              s.remove(start, end)921              left--922            }923          }924        }925      }926    }927928    // walk declarations to record declared bindings929    if (930      (node.type === 'VariableDeclaration' ||931        node.type === 'FunctionDeclaration' ||932        node.type === 'ClassDeclaration') &&933      !node.declare934    ) {935      walkDeclaration(node, setupBindings, userImportAlias)936    }937938    // walk statements & named exports / variable declarations for top level939    // await940    if (941      (node.type === 'VariableDeclaration' && !node.declare) ||942      node.type.endsWith('Statement')943    ) {944      const scope: Statement[][] = [scriptSetupAst.body]945      ;(walk as any)(node, {946        enter(child: Node, parent: Node) {947          if (isFunctionType(child)) {948            this.skip()949          }950          if (child.type === 'BlockStatement') {951            scope.push(child.body)952          }953          if (child.type === 'AwaitExpression') {954            error(955              `Vue 2 does not support top level await in <script setup>.`,956              child957            )958          }959        },960        exit(node: Node) {961          if (node.type === 'BlockStatement') scope.pop()962        }963      })964    }965966    if (967      (node.type === 'ExportNamedDeclaration' && node.exportKind !== 'type') ||968      node.type === 'ExportAllDeclaration' ||969      node.type === 'ExportDefaultDeclaration'970    ) {971      error(972        `<script setup> cannot contain ES module exports. ` +973          `If you are using a previous version of <script setup>, please ` +974          `consult the updated RFC at https://github.com/vuejs/rfcs/pull/227.`,975        node976      )977    }978979    if (isTS) {980      // runtime enum981      if (node.type === 'TSEnumDeclaration') {982        registerBinding(setupBindings, node.id, BindingTypes.SETUP_CONST)983      }984985      // move all Type declarations to outer scope986      if (987        node.type.startsWith('TS') ||988        (node.type === 'ExportNamedDeclaration' &&989          node.exportKind === 'type') ||990        (node.type === 'VariableDeclaration' && node.declare)991      ) {992        recordType(node, declaredTypes)993        s.move(start, end, 0)994      }995    }996  }997998  // 3. Apply reactivity transform999  // if (1000  //   (enableReactivityTransform &&1001  //     // normal <script> had ref bindings that maybe used in <script setup>1002  //     (refBindings || shouldTransform(scriptSetup.content))) ||1003  //   propsDestructureDecl1004  // ) {1005  //   const { rootRefs, importedHelpers } = transformAST(1006  //     scriptSetupAst,1007  //     s,1008  //     startOffset,1009  //     refBindings,1010  //     propsDestructuredBindings1011  //   )1012  //   refBindings = refBindings ? [...refBindings, ...rootRefs] : rootRefs1013  //   for (const h of importedHelpers) {1014  //     helperImports.add(h)1015  //   }1016  // }10171018  // 4. extract runtime props/emits code from setup context type1019  if (propsTypeDecl) {1020    extractRuntimeProps(propsTypeDecl, typeDeclaredProps, declaredTypes, isProd)1021  }1022  if (emitsTypeDecl) {1023    extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits)1024  }10251026  // 5. check useOptions args to make sure it doesn't reference setup scope1027  // variables1028  checkInvalidScopeReference(propsRuntimeDecl, DEFINE_PROPS)1029  checkInvalidScopeReference(propsRuntimeDefaults, DEFINE_PROPS)1030  checkInvalidScopeReference(propsDestructureDecl, DEFINE_PROPS)1031  checkInvalidScopeReference(emitsRuntimeDecl, DEFINE_EMITS)10321033  // 6. remove non-script content1034  if (script) {1035    if (startOffset < scriptStartOffset!) {1036      // <script setup> before <script>1037      s.remove(0, startOffset)1038      s.remove(endOffset, scriptStartOffset!)1039      s.remove(scriptEndOffset!, source.length)1040    } else {1041      // <script> before <script setup>1042      s.remove(0, scriptStartOffset!)1043      s.remove(scriptEndOffset!, startOffset)1044      s.remove(endOffset, source.length)1045    }1046  } else {1047    // only <script setup>1048    s.remove(0, startOffset)1049    s.remove(endOffset, source.length)1050  }10511052  // 7. analyze binding metadata1053  if (scriptAst) {1054    Object.assign(bindingMetadata, analyzeScriptBindings(scriptAst.body))1055  }1056  if (propsRuntimeDecl) {1057    for (const key of getObjectOrArrayExpressionKeys(propsRuntimeDecl)) {1058      bindingMetadata[key] = BindingTypes.PROPS1059    }1060  }1061  for (const key in typeDeclaredProps) {1062    bindingMetadata[key] = BindingTypes.PROPS1063  }1064  // props aliases1065  // if (propsDestructureDecl) {1066  //   if (propsDestructureRestId) {1067  //     bindingMetadata[propsDestructureRestId] =1068  //       BindingTypes.SETUP_REACTIVE_CONST1069  //   }1070  //   for (const key in propsDestructuredBindings) {1071  //     const { local } = propsDestructuredBindings[key]1072  //     if (local !== key) {1073  //       bindingMetadata[local] = BindingTypes.PROPS_ALIASED1074  //       ;(bindingMetadata.__propsAliases ||1075  //         (bindingMetadata.__propsAliases = {}))[local] = key1076  //     }1077  //   }1078  // }1079  for (const [key, { isType, imported, source }] of Object.entries(1080    userImports1081  )) {1082    if (isType) continue1083    bindingMetadata[key] =1084      imported === '*' ||1085      (imported === 'default' && source.endsWith('.vue')) ||1086      source === 'vue'1087        ? BindingTypes.SETUP_CONST1088        : BindingTypes.SETUP_MAYBE_REF1089  }1090  for (const key in scriptBindings) {1091    bindingMetadata[key] = scriptBindings[key]1092  }1093  for (const key in setupBindings) {1094    bindingMetadata[key] = setupBindings[key]1095  }1096  // known ref bindings1097  if (refBindings) {1098    for (const key of refBindings) {1099      bindingMetadata[key] = BindingTypes.SETUP_REF1100    }1101  }11021103  // 8. inject `useCssVars` calls1104  if (cssVars.length) {1105    helperImports.add(CSS_VARS_HELPER)1106    s.prependRight(1107      startOffset,1108      `\n${genCssVarsCode(cssVars, bindingMetadata, scopeId, isProd)}\n`1109    )1110  }11111112  // 9. finalize setup() argument signature1113  let args = `__props`1114  if (propsTypeDecl) {1115    // mark as any and only cast on assignment1116    // since the user defined complex types may be incompatible with the1117    // inferred type from generated runtime declarations1118    args += `: any`1119  }1120  // inject user assignment of props1121  // we use a default __props so that template expressions referencing props1122  // can use it directly1123  if (propsIdentifier) {1124    s.prependLeft(1125      startOffset,1126      `\nconst ${propsIdentifier} = __props${1127        propsTypeDecl ? ` as ${genSetupPropsType(propsTypeDecl)}` : ``1128      };\n`1129    )1130  }1131  if (propsDestructureRestId) {1132    s.prependLeft(1133      startOffset,1134      `\nconst ${propsDestructureRestId} = ${helper(1135        `createPropsRestProxy`1136      )}(__props, ${JSON.stringify(Object.keys(propsDestructuredBindings))});\n`1137    )1138  }11391140  const destructureElements = hasDefineExposeCall ? [`expose`] : []1141  if (emitIdentifier) {1142    destructureElements.push(1143      emitIdentifier === `emit` ? `emit` : `emit: ${emitIdentifier}`1144    )1145  }1146  if (destructureElements.length) {1147    args += `, { ${destructureElements.join(', ')} }`1148    if (emitsTypeDecl) {1149      args += `: { emit: (${scriptSetup.content.slice(1150        emitsTypeDecl.start!,1151        emitsTypeDecl.end!1152      )}), expose: any, slots: any, attrs: any }`1153    }1154  }11551156  // 10. generate return statement1157  const allBindings: Record<string, any> = {1158    ...scriptBindings,1159    ...setupBindings1160  }1161  for (const key in userImports) {1162    if (!userImports[key].isType && userImports[key].isUsedInTemplate) {1163      allBindings[key] = true1164    }1165  }1166  // __sfc marker indicates these bindings are compiled from <script setup>1167  // and should not be proxied on `this`1168  const returned = `{ ${__TEST__ ? `` : `__sfc: true,`}${Object.keys(1169    allBindings1170  ).join(', ')} }`11711172  s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`)11731174  // 11. finalize default export1175  let runtimeOptions = ``1176  if (!hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) {1177    const match = filename.match(/([^/\\]+)\.\w+$/)1178    if (match) {1179      runtimeOptions += `\n  __name: '${match[1]}',`1180    }1181  }1182  if (hasInlinedSsrRenderFn) {1183    runtimeOptions += `\n  __ssrInlineRender: true,`1184  }1185  if (propsRuntimeDecl) {1186    let declCode = scriptSetup.content1187      .slice(propsRuntimeDecl.start!, propsRuntimeDecl.end!)1188      .trim()1189    if (propsDestructureDecl) {1190      const defaults: string[] = []1191      for (const key in propsDestructuredBindings) {1192        const d = genDestructuredDefaultValue(key)1193        if (d) defaults.push(`${key}: ${d}`)1194      }1195      if (defaults.length) {1196        declCode = `${helper(1197          `mergeDefaults`1198        )}(${declCode}, {\n  ${defaults.join(',\n  ')}\n})`1199      }1200    }1201    runtimeOptions += `\n  props: ${declCode},`1202  } else if (propsTypeDecl) {1203    runtimeOptions += genRuntimeProps(typeDeclaredProps)1204  }1205  if (emitsRuntimeDecl) {1206    runtimeOptions += `\n  emits: ${scriptSetup.content1207      .slice(emitsRuntimeDecl.start!, emitsRuntimeDecl.end!)1208      .trim()},`1209  } else if (emitsTypeDecl) {1210    runtimeOptions += genRuntimeEmits(typeDeclaredEmits)1211  }12121213  // wrap setup code with function.1214  if (isTS) {1215    // for TS, make sure the exported type is still valid type with1216    // correct props information1217    // we have to use object spread for types to be merged properly1218    // user's TS setting should compile it down to proper targets1219    // export default defineComponent({ ...__default__, ... })1220    const def = defaultExport ? `\n  ...${DEFAULT_VAR},` : ``1221    s.prependLeft(1222      startOffset,1223      `\nexport default /*#__PURE__*/${helper(1224        `defineComponent`1225      )}({${def}${runtimeOptions}\n  setup(${args}) {\n`1226    )1227    s.appendRight(endOffset, `})`)1228  } else {1229    if (defaultExport) {1230      // without TS, can't rely on rest spread, so we use Object.assign1231      // export default Object.assign(__default__, { ... })1232      s.prependLeft(1233        startOffset,1234        `\nexport default /*#__PURE__*/Object.assign(${DEFAULT_VAR}, {${runtimeOptions}\n  ` +1235          `setup(${args}) {\n`1236      )1237      s.appendRight(endOffset, `})`)1238    } else {1239      s.prependLeft(1240        startOffset,1241        `\nexport default {${runtimeOptions}\n  setup(${args}) {\n`1242      )1243      s.appendRight(endOffset, `}`)1244    }1245  }12461247  // 12. finalize Vue helper imports1248  if (helperImports.size > 0) {1249    s.prepend(1250      `import { ${[...helperImports]1251        .map(h => `${h} as _${h}`)1252        .join(', ')} } from 'vue'\n`1253    )1254  }12551256  s.trim()12571258  return {1259    ...scriptSetup,1260    bindings: bindingMetadata,1261    imports: userImports,1262    content: s.toString(),1263    map: genSourceMap1264      ? (s.generateMap({1265          source: filename,1266          hires: true,1267          includeContent: true1268        }) as unknown as RawSourceMap)1269      : undefined,1270    scriptAst: scriptAst?.body,1271    scriptSetupAst: scriptSetupAst?.body1272  }1273}12741275function registerBinding(1276  bindings: Record<string, BindingTypes>,1277  node: Identifier,1278  type: BindingTypes1279) {1280  bindings[node.name] = type1281}12821283function walkDeclaration(1284  node: Declaration,1285  bindings: Record<string, BindingTypes>,1286  userImportAlias: Record<string, string>1287) {1288  if (node.type === 'VariableDeclaration') {1289    const isConst = node.kind === 'const'1290    // export const foo = ...1291    for (const { id, init } of node.declarations) {1292      const isDefineCall = !!(1293        isConst &&1294        isCallOf(1295          init,1296          c => c === DEFINE_PROPS || c === DEFINE_EMITS || c === WITH_DEFAULTS1297        )1298      )1299      if (id.type === 'Identifier') {1300        let bindingType1301        const userReactiveBinding = userImportAlias['reactive'] || 'reactive'1302        if (isCallOf(init, userReactiveBinding)) {1303          // treat reactive() calls as let since it's meant to be mutable1304          bindingType = isConst1305            ? BindingTypes.SETUP_REACTIVE_CONST1306            : BindingTypes.SETUP_LET1307        } else if (1308          // if a declaration is a const literal, we can mark it so that1309          // the generated render fn code doesn't need to unref() it1310          isDefineCall ||1311          (isConst && canNeverBeRef(init!, userReactiveBinding))1312        ) {1313          bindingType = isCallOf(init, DEFINE_PROPS)1314            ? BindingTypes.SETUP_REACTIVE_CONST1315            : BindingTypes.SETUP_CONST1316        } else if (isConst) {1317          if (isCallOf(init, userImportAlias['ref'] || 'ref')) {1318            bindingType = BindingTypes.SETUP_REF1319          } else {1320            bindingType = BindingTypes.SETUP_MAYBE_REF1321          }1322        } else {1323          bindingType = BindingTypes.SETUP_LET1324        }1325        registerBinding(bindings, id, bindingType)1326      } else {1327        if (isCallOf(init, DEFINE_PROPS)) {1328          // skip walking props destructure1329          return1330        }1331        if (id.type === 'ObjectPattern') {1332          walkObjectPattern(id, bindings, isConst, isDefineCall)1333        } else if (id.type === 'ArrayPattern') {1334          walkArrayPattern(id, bindings, isConst, isDefineCall)1335        }1336      }1337    }1338  } else if (1339    node.type === 'TSEnumDeclaration' ||1340    node.type === 'FunctionDeclaration' ||1341    node.type === 'ClassDeclaration'1342  ) {1343    // export function foo() {} / export class Foo {}1344    // export declarations must be named.1345    bindings[node.id!.name] = BindingTypes.SETUP_CONST1346  }1347}13481349function walkObjectPattern(1350  node: ObjectPattern,1351  bindings: Record<string, BindingTypes>,1352  isConst: boolean,1353  isDefineCall = false1354) {1355  for (const p of node.properties) {1356    if (p.type === 'ObjectProperty') {1357      if (p.key.type === 'Identifier' && p.key === p.value) {1358        // shorthand: const { x } = ...1359        const type = isDefineCall1360          ? BindingTypes.SETUP_CONST1361          : isConst1362          ? BindingTypes.SETUP_MAYBE_REF1363          : BindingTypes.SETUP_LET1364        registerBinding(bindings, p.key, type)1365      } else {1366        walkPattern(p.value, bindings, isConst, isDefineCall)1367      }1368    } else {1369      // ...rest1370      // argument can only be identifier when destructuring1371      const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET1372      registerBinding(bindings, p.argument as Identifier, type)1373    }1374  }1375}13761377function walkArrayPattern(1378  node: ArrayPattern,1379  bindings: Record<string, BindingTypes>,1380  isConst: boolean,1381  isDefineCall = false1382) {1383  for (const e of node.elements) {1384    e && walkPattern(e, bindings, isConst, isDefineCall)1385  }1386}13871388function walkPattern(1389  node: Node,1390  bindings: Record<string, BindingTypes>,1391  isConst: boolean,1392  isDefineCall = false1393) {1394  if (node.type === 'Identifier') {1395    const type = isDefineCall1396      ? BindingTypes.SETUP_CONST1397      : isConst1398      ? BindingTypes.SETUP_MAYBE_REF1399      : BindingTypes.SETUP_LET1400    registerBinding(bindings, node, type)1401  } else if (node.type === 'RestElement') {1402    // argument can only be identifier when destructuring1403    const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET1404    registerBinding(bindings, node.argument as Identifier, type)1405  } else if (node.type === 'ObjectPattern') {1406    walkObjectPattern(node, bindings, isConst)1407  } else if (node.type === 'ArrayPattern') {1408    walkArrayPattern(node, bindings, isConst)1409  } else if (node.type === 'AssignmentPattern') {1410    if (node.left.type === 'Identifier') {1411      const type = isDefineCall1412        ? BindingTypes.SETUP_CONST1413        : isConst1414        ? BindingTypes.SETUP_MAYBE_REF1415        : BindingTypes.SETUP_LET1416      registerBinding(bindings, node.left, type)1417    } else {1418      walkPattern(node.left, bindings, isConst)1419    }1420  }1421}14221423interface PropTypeData {1424  key: string1425  type: string[]1426  required: boolean1427}14281429function recordType(node: Node, declaredTypes: Record<string, string[]>) {1430  if (node.type === 'TSInterfaceDeclaration') {1431    declaredTypes[node.id.name] = [`Object`]1432  } else if (node.type === 'TSTypeAliasDeclaration') {1433    declaredTypes[node.id.name] = inferRuntimeType(1434      node.typeAnnotation,1435      declaredTypes1436    )1437  } else if (node.type === 'ExportNamedDeclaration' && node.declaration) {1438    recordType(node.declaration, declaredTypes)1439  }1440}14411442function extractRuntimeProps(1443  node: TSTypeLiteral | TSInterfaceBody,1444  props: Record<string, PropTypeData>,1445  declaredTypes: Record<string, string[]>,1446  isProd: boolean1447) {1448  const members = node.type === 'TSTypeLiteral' ? node.members : node.body1449  for (const m of members) {1450    if (1451      (m.type === 'TSPropertySignature' || m.type === 'TSMethodSignature') &&1452      m.key.type === 'Identifier'1453    ) {1454      let type1455      if (m.type === 'TSMethodSignature') {1456        type = ['Function']1457      } else if (m.typeAnnotation) {1458        type = inferRuntimeType(m.typeAnnotation.typeAnnotation, declaredTypes)1459      }1460      props[m.key.name] = {1461        key: m.key.name,1462        required: !m.optional,1463        type: type || [`null`]1464      }1465    }1466  }1467}14681469function inferRuntimeType(1470  node: TSType,1471  declaredTypes: Record<string, string[]>1472): string[] {1473  switch (node.type) {1474    case 'TSStringKeyword':1475      return ['String']1476    case 'TSNumberKeyword':1477      return ['Number']1478    case 'TSBooleanKeyword':1479      return ['Boolean']1480    case 'TSObjectKeyword':1481      return ['Object']1482    case 'TSTypeLiteral':1483      // TODO (nice to have) generate runtime property validation1484      return ['Object']1485    case 'TSFunctionType':1486      return ['Function']1487    case 'TSArrayType':1488    case 'TSTupleType':1489      // TODO (nice to have) generate runtime element type/length checks1490      return ['Array']14911492    case 'TSLiteralType':1493      switch (node.literal.type) {1494        case 'StringLiteral':1495          return ['String']1496        case 'BooleanLiteral':1497          return ['Boolean']1498        case 'NumericLiteral':1499        case 'BigIntLiteral':1500          return ['Number']1501        default:1502          return [`null`]1503      }15041505    case 'TSTypeReference':1506      if (node.typeName.type === 'Identifier') {1507        if (declaredTypes[node.typeName.name]) {1508          return declaredTypes[node.typeName.name]1509        }1510        switch (node.typeName.name) {1511          case 'Array':1512          case 'Function':1513          case 'Object':1514          case 'Set':1515          case 'Map':1516          case 'WeakSet':1517          case 'WeakMap':1518          case 'Date':1519          case 'Promise':1520            return [node.typeName.name]1521          case 'Record':1522          case 'Partial':1523          case 'Readonly':1524          case 'Pick':1525          case 'Omit':1526          case 'Exclude':1527          case 'Extract':1528          case 'Required':1529          case 'InstanceType':1530            return ['Object']1531        }1532      }1533      return [`null`]15341535    case 'TSParenthesizedType':1536      return inferRuntimeType(node.typeAnnotation, declaredTypes)1537    case 'TSUnionType':1538      return [1539        ...new Set(1540          [].concat(1541            ...(node.types.map(t => inferRuntimeType(t, declaredTypes)) as any)1542          )1543        )1544      ]1545    case 'TSIntersectionType':1546      return ['Object']15471548    case 'TSSymbolKeyword':1549      return ['Symbol']15501551    default:1552      return [`null`] // no runtime check1553  }1554}15551556function toRuntimeTypeString(types: string[]) {1557  return types.length > 1 ? `[${types.join(', ')}]` : types[0]1558}15591560function extractRuntimeEmits(1561  node: TSFunctionType | TSTypeLiteral | TSInterfaceBody,1562  emits: Set<string>1563) {1564  if (node.type === 'TSTypeLiteral' || node.type === 'TSInterfaceBody') {1565    const members = node.type === 'TSTypeLiteral' ? node.members : node.body1566    for (let t of members) {1567      if (t.type === 'TSCallSignatureDeclaration') {1568        extractEventNames(t.parameters[0], emits)1569      }1570    }1571    return1572  } else {1573    extractEventNames(node.parameters[0], emits)1574  }1575}15761577function extractEventNames(1578  eventName: ArrayPattern | Identifier | ObjectPattern | RestElement,1579  emits: Set<string>1580) {1581  if (1582    eventName.type === 'Identifier' &&1583    eventName.typeAnnotation &&1584    eventName.typeAnnotation.type === 'TSTypeAnnotation'1585  ) {1586    const typeNode = eventName.typeAnnotation.typeAnnotation1587    if (typeNode.type === 'TSLiteralType') {1588      if (1589        typeNode.literal.type !== 'UnaryExpression' &&1590        typeNode.literal.type !== 'TemplateLiteral'1591      ) {1592        emits.add(String(typeNode.literal.value))1593      }1594    } else if (typeNode.type === 'TSUnionType') {1595      for (const t of typeNode.types) {1596        if (1597          t.type === 'TSLiteralType' &&1598          t.literal.type !== 'UnaryExpression' &&1599          t.literal.type !== 'TemplateLiteral'1600        ) {1601          emits.add(String(t.literal.value))1602        }1603      }1604    }1605  }1606}16071608function genRuntimeEmits(emits: Set<string>) {1609  return emits.size1610    ? `\n  emits: [${Array.from(emits)1611        .map(p => JSON.stringify(p))1612        .join(', ')}],`1613    : ``1614}16151616function isCallOf(1617  node: Node | null | undefined,1618  test: string | ((id: string) => boolean)1619): node is CallExpression {1620  return !!(1621    node &&1622    node.type === 'CallExpression' &&1623    node.callee.type === 'Identifier' &&1624    (typeof test === 'string'1625      ? node.callee.name === test1626      : test(node.callee.name))1627  )1628}16291630function canNeverBeRef(node: Node, userReactiveImport: string): boolean {1631  if (isCallOf(node, userReactiveImport)) {1632    return true1633  }1634  switch (node.type) {1635    case 'UnaryExpression':1636    case 'BinaryExpression':1637    case 'ArrayExpression':1638    case 'ObjectExpression':1639    case 'FunctionExpression':1640    case 'ArrowFunctionExpression':1641    case 'UpdateExpression':1642    case 'ClassExpression':1643    case 'TaggedTemplateExpression':1644      return true1645    case 'SequenceExpression':1646      return canNeverBeRef(1647        node.expressions[node.expressions.length - 1],1648        userReactiveImport1649      )1650    default:1651      if (node.type.endsWith('Literal')) {1652        return true1653      }1654      return false1655  }1656}16571658/**1659 * Analyze bindings in normal `<script>`1660 * Note that `compileScriptSetup` already analyzes bindings as part of its1661 * compilation process so this should only be used on single `<script>` SFCs.1662 */1663function analyzeScriptBindings(ast: Statement[]): BindingMetadata {1664  for (const node of ast) {1665    if (1666      node.type === 'ExportDefaultDeclaration' &&1667      node.declaration.type === 'ObjectExpression'1668    ) {1669      return analyzeBindingsFromOptions(node.declaration)1670    }1671  }1672  return {}1673}16741675function analyzeBindingsFromOptions(node: ObjectExpression): BindingMetadata {1676  const bindings: BindingMetadata = {}1677  // #3270, #32751678  // mark non-script-setup so we don't resolve components/directives from these1679  Object.defineProperty(bindings, '__isScriptSetup', {1680    enumerable: false,1681    value: false1682  })1683  for (const property of node.properties) {1684    if (1685      property.type === 'ObjectProperty' &&1686      !property.computed &&1687      property.key.type === 'Identifier'1688    ) {1689      // props1690      if (property.key.name === 'props') {1691        // props: ['foo']1692        // props: { foo: ... }1693        for (const key of getObjectOrArrayExpressionKeys(property.value)) {1694          bindings[key] = BindingTypes.PROPS1695        }1696      }16971698      // inject1699      else if (property.key.name === 'inject') {1700        // inject: ['foo']1701        // inject: { foo: {} }1702        for (const key of getObjectOrArrayExpressionKeys(property.value)) {1703          bindings[key] = BindingTypes.OPTIONS1704        }1705      }17061707      // computed & methods1708      else if (1709        property.value.type === 'ObjectExpression' &&1710        (property.key.name === 'computed' || property.key.name === 'methods')1711      ) {1712        // methods: { foo() {} }1713        // computed: { foo() {} }1714        for (const key of getObjectExpressionKeys(property.value)) {1715          bindings[key] = BindingTypes.OPTIONS1716        }1717      }1718    }17191720    // setup & data1721    else if (1722      property.type === 'ObjectMethod' &&1723      property.key.type === 'Identifier' &&1724      (property.key.name === 'setup' || property.key.name === 'data')1725    ) {1726      for (const bodyItem of property.body.body) {1727        // setup() {1728        //   return {1729        //     foo: null1730        //   }1731        // }1732        if (1733          bodyItem.type === 'ReturnStatement' &&1734          bodyItem.argument &&1735          bodyItem.argument.type === 'ObjectExpression'1736        ) {1737          for (const key of getObjectExpressionKeys(bodyItem.argument)) {1738            bindings[key] =1739              property.key.name === 'setup'1740                ? BindingTypes.SETUP_MAYBE_REF1741                : BindingTypes.DATA1742          }1743        }1744      }1745    }1746  }17471748  return bindings1749}17501751function getObjectExpressionKeys(node: ObjectExpression): string[] {1752  const keys: string[] = []1753  for (const prop of node.properties) {1754    if (1755      (prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') &&1756      !prop.computed1757    ) {1758      if (prop.key.type === 'Identifier') {1759        keys.push(prop.key.name)1760      } else if (prop.key.type === 'StringLiteral') {1761        keys.push(prop.key.value)1762      }1763    }1764  }1765  return keys1766}17671768function getArrayExpressionKeys(node: ArrayExpression): string[] {1769  const keys: string[] = []1770  for (const element of node.elements) {1771    if (element && element.type === 'StringLiteral') {1772      keys.push(element.value)1773    }1774  }1775  return keys1776}17771778function getObjectOrArrayExpressionKeys(value: Node): string[] {1779  if (value.type === 'ArrayExpression') {1780    return getArrayExpressionKeys(value)1781  }1782  if (value.type === 'ObjectExpression') {1783    return getObjectExpressionKeys(value)1784  }1785  return []1786}17871788const templateUsageCheckCache = new LRU<string, string>(512)17891790function resolveTemplateUsageCheckString(sfc: SFCDescriptor, isTS: boolean) {1791  const { content } = sfc.template!1792  const cached = templateUsageCheckCache.get(content)1793  if (cached) {1794    return cached1795  }17961797  let code = ''17981799  parseHTML(content, {1800    ...webCompilerOptions,1801    start(tag, attrs) {1802      if (!isBuiltInTag(tag) && !isReservedTag(tag)) {1803        code += `,${camelize(tag)},${capitalize(camelize(tag))}`1804      }1805      for (let i = 0; i < attrs.length; i++) {1806        const { name, value } = attrs[i]1807        if (dirRE.test(name)) {1808          const baseName = onRE.test(name)1809            ? 'on'1810            : slotRE.test(name)1811            ? 'slot'1812            : bindRE.test(name)1813            ? 'bind'1814            : name.replace(dirRE, '')1815          if (!isBuiltInDir(baseName)) {1816            code += `,v${capitalize(camelize(baseName))}`1817          }1818          if (value) {1819            code += `,${processExp(value, isTS, baseName)}`1820          }1821        } else if (name === 'ref') {1822          code += `,${value}`1823        }1824      }1825    },1826    chars(text) {1827      const res = parseText(text)1828      if (res) {1829        code += `,${processExp(res.expression, isTS)}`1830      }1831    }1832  })18331834  code += ';'1835  templateUsageCheckCache.set(content, code)1836  return code1837}18381839const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/18401841function processExp(exp: string, isTS: boolean, dir?: string): string {1842  if (isTS && / as\s+\w|<.*>|:/.test(exp)) {1843    if (dir === 'slot') {1844      exp = `(${exp})=>{}`1845    } else if (dir === 'on') {1846      exp = `()=>{return ${exp}}`1847    } else if (dir === 'for') {1848      const inMatch = exp.match(forAliasRE)1849      if (inMatch) {1850        const [, LHS, RHS] = inMatch1851        return processExp(`(${LHS})=>{}`, true) + processExp(RHS, true)1852      }1853    }1854    let ret = ''1855    // has potential type cast or generic arguments that uses types1856    const ast = parseExpression(exp, { plugins: ['typescript'] })1857    walkIdentifiers(ast, node => {1858      ret += `,` + node.name1859    })1860    return ret1861  }1862  return stripStrings(exp)1863}18641865function stripStrings(exp: string) {1866  return exp1867    .replace(/'[^']*'|"[^"]*"/g, '')1868    .replace(/`[^`]+`/g, stripTemplateString)1869}18701871function stripTemplateString(str: string): string {1872  const interpMatch = str.match(/\${[^}]+}/g)1873  if (interpMatch) {1874    return interpMatch.map(m => m.slice(2, -1)).join(',')1875  }1876  return ''1877}18781879function isImportUsed(1880  local: string,1881  sfc: SFCDescriptor,1882  isTS: boolean1883): boolean {1884  return new RegExp(1885    // #4274 escape $ since it's a special char in regex1886    // (and is the only regex special char that is valid in identifiers)1887    `[^\\w$_]${local.replace(/\$/g, '\\$')}[^\\w$_]`1888  ).test(resolveTemplateUsageCheckString(sfc, isTS))1889}18901891/**1892 * Note: this comparison assumes the prev/next script are already identical,1893 * and only checks the special case where <script setup> unused import1894 * pruning result changes due to template changes.1895 */1896export function hmrShouldReload(1897  prevImports: Record<string, ImportBinding>,1898  next: SFCDescriptor1899): boolean {1900  if (!next.scriptSetup) {1901    return false1902  }19031904  const isTS = next.scriptSetup.lang === 'ts' || next.scriptSetup.lang === 'tsx'1905  // for each previous import, check if its used status remain the same based on1906  // the next descriptor's template1907  for (const key in prevImports) {1908    // if an import was previous unused, but now is used, we need to force1909    // reload so that the script now includes that import.1910    if (!prevImports[key].isUsedInTemplate && isImportUsed(key, next, isTS)) {1911      return true1912    }1913  }19141915  return false1916}

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.