packages/compiler-sfc/src/parseComponent.ts TYPESCRIPT 221 lines View on github.com → Search inside
1import deindent from 'de-indent'2import { parseHTML } from 'compiler/parser/html-parser'3import { makeMap } from 'shared/util'4import { ASTAttr, WarningMessage } from 'types/compiler'5import { BindingMetadata, RawSourceMap } from './types'6import type { ImportBinding } from './compileScript'78export const DEFAULT_FILENAME = 'anonymous.vue'910const splitRE = /\r?\n/g11const replaceRE = /./g12const isSpecialTag = makeMap('script,style,template', true)1314export interface SFCCustomBlock {15  type: string16  content: string17  attrs: { [key: string]: string | true }18  start: number19  end: number20  src?: string21  map?: RawSourceMap22}2324export interface SFCBlock extends SFCCustomBlock {25  lang?: string26  scoped?: boolean27  module?: string | boolean28}2930export interface SFCScriptBlock extends SFCBlock {31  type: 'script'32  setup?: string | boolean33  bindings?: BindingMetadata34  imports?: Record<string, ImportBinding>35  /**36   * import('\@babel/types').Statement37   */38  scriptAst?: any[]39  /**40   * import('\@babel/types').Statement41   */42  scriptSetupAst?: any[]43}4445export interface SFCDescriptor {46  source: string47  filename: string48  template: SFCBlock | null49  script: SFCScriptBlock | null50  scriptSetup: SFCScriptBlock | null51  styles: SFCBlock[]52  customBlocks: SFCCustomBlock[]53  cssVars: string[]5455  errors: (string | WarningMessage)[]5657  /**58   * compare with an existing descriptor to determine whether HMR should perform59   * a reload vs. re-render.60   *61   * Note: this comparison assumes the prev/next script are already identical,62   * and only checks the special case where `<script setup lang="ts">` unused63   * import pruning result changes due to template changes.64   */65  shouldForceReload: (prevImports: Record<string, ImportBinding>) => boolean66}6768export interface VueTemplateCompilerParseOptions {69  pad?: 'line' | 'space' | boolean70  deindent?: boolean71  outputSourceRange?: boolean72}7374/**75 * Parse a single-file component (*.vue) file into an SFC Descriptor Object.76 */77export function parseComponent(78  source: string,79  options: VueTemplateCompilerParseOptions = {}80): SFCDescriptor {81  const sfc: SFCDescriptor = {82    source,83    filename: DEFAULT_FILENAME,84    template: null,85    script: null,86    scriptSetup: null, // TODO87    styles: [],88    customBlocks: [],89    cssVars: [],90    errors: [],91    shouldForceReload: null as any // attached in parse() by compiler-sfc92  }93  let depth = 094  let currentBlock: SFCBlock | null = null9596  let warn: any = msg => {97    sfc.errors.push(msg)98  }99100  if (__DEV__ && options.outputSourceRange) {101    warn = (msg, range) => {102      const data: WarningMessage = { msg }103      if (range.start != null) {104        data.start = range.start105      }106      if (range.end != null) {107        data.end = range.end108      }109      sfc.errors.push(data)110    }111  }112113  function start(114    tag: string,115    attrs: ASTAttr[],116    unary: boolean,117    start: number,118    end: number119  ) {120    if (depth === 0) {121      currentBlock = {122        type: tag,123        content: '',124        start: end,125        end: 0, // will be set on tag close126        attrs: attrs.reduce((cumulated, { name, value }) => {127          cumulated[name] = value || true128          return cumulated129        }, {})130      }131132      if (typeof currentBlock.attrs.src === 'string') {133        currentBlock.src = currentBlock.attrs.src134      }135136      if (isSpecialTag(tag)) {137        checkAttrs(currentBlock, attrs)138        if (tag === 'script') {139          const block = currentBlock as SFCScriptBlock140          if (block.attrs.setup) {141            block.setup = currentBlock.attrs.setup142            sfc.scriptSetup = block143          } else {144            sfc.script = block145          }146        } else if (tag === 'style') {147          sfc.styles.push(currentBlock)148        } else {149          sfc[tag] = currentBlock150        }151      } else {152        // custom blocks153        sfc.customBlocks.push(currentBlock)154      }155    }156    if (!unary) {157      depth++158    }159  }160161  function checkAttrs(block: SFCBlock, attrs: ASTAttr[]) {162    for (let i = 0; i < attrs.length; i++) {163      const attr = attrs[i]164      if (attr.name === 'lang') {165        block.lang = attr.value166      }167      if (attr.name === 'scoped') {168        block.scoped = true169      }170      if (attr.name === 'module') {171        block.module = attr.value || true172      }173    }174  }175176  function end(tag: string, start: number) {177    if (depth === 1 && currentBlock) {178      currentBlock.end = start179      let text = source.slice(currentBlock.start, currentBlock.end)180      if (181        options.deindent === true ||182        // by default, deindent unless it's script with default lang or (j/t)sx?183        (options.deindent !== false &&184          !(185            currentBlock.type === 'script' &&186            (!currentBlock.lang || /^(j|t)sx?$/.test(currentBlock.lang))187          ))188      ) {189        text = deindent(text)190      }191      // pad content so that linters and pre-processors can output correct192      // line numbers in errors and warnings193      if (currentBlock.type !== 'template' && options.pad) {194        text = padContent(currentBlock, options.pad) + text195      }196      currentBlock.content = text197      currentBlock = null198    }199    depth--200  }201202  function padContent(block: SFCBlock, pad: true | 'line' | 'space') {203    if (pad === 'space') {204      return source.slice(0, block.start).replace(replaceRE, ' ')205    } else {206      const offset = source.slice(0, block.start).split(splitRE).length207      const padChar = block.type === 'script' && !block.lang ? '//\n' : '\n'208      return Array(offset).join(padChar)209    }210  }211212  parseHTML(source, {213    warn,214    start,215    end,216    outputSourceRange: options.outputSourceRange217  })218219  return sfc220}

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.