]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(sfc): accept inMap in compileTemplate()
authorEvan You <yyx990803@gmail.com>
Thu, 19 Dec 2019 21:25:05 +0000 (16:25 -0500)
committerEvan You <yyx990803@gmail.com>
Thu, 19 Dec 2019 21:25:05 +0000 (16:25 -0500)
packages/compiler-core/src/errors.ts
packages/compiler-core/src/parse.ts
packages/compiler-sfc/src/compileTemplate.ts

index 71c19e26169b28b0a1f54d09ba69843f11038d35..6fd71521b5322a0e1cecbc7b007442b0137f298e 100644 (file)
@@ -145,7 +145,7 @@ export const errorMessages: { [code: number]: string } = {
 
   // Vue-specific parse errors
   [ErrorCodes.X_INVALID_END_TAG]: 'Invalid end tag.',
-  [ErrorCodes.X_MISSING_END_TAG]: 'End tag was not found.',
+  [ErrorCodes.X_MISSING_END_TAG]: 'Element is missing end tag.',
   [ErrorCodes.X_MISSING_INTERPOLATION_END]:
     'Interpolation end sign was not found.',
   [ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END]:
index 6c125bb0fe641dc869f2203457b4a54338eefeb0..fa31036ddfe97333f4d3e03f659b9abf8e19430c 100644 (file)
@@ -372,7 +372,7 @@ function parseElement(
   if (startsWithEndTagOpen(context.source, element.tag)) {
     parseTag(context, TagType.End, parent)
   } else {
-    emitError(context, ErrorCodes.X_MISSING_END_TAG)
+    emitError(context, ErrorCodes.X_MISSING_END_TAG, 0, element.loc.start)
     if (context.source.length === 0 && element.tag.toLowerCase() === 'script') {
       const first = children[0]
       if (first && startsWith(first.loc.source, '<!--')) {
@@ -963,9 +963,9 @@ function getNewPosition(
 function emitError(
   context: ParserContext,
   code: ErrorCodes,
-  offset?: number
+  offset?: number,
+  loc: Position = getCursor(context)
 ): void {
-  const loc = getCursor(context)
   if (offset) {
     loc.offset += offset
     loc.column += offset
index ee2297dc2ff45c5b8b23eeda1f2822998bf5c623..e7a4e476cb1680d4b072eb9ed1dbeeb51f260319 100644 (file)
@@ -4,7 +4,7 @@ import {
   CompilerError,
   NodeTransform
 } from '@vue/compiler-core'
-import { RawSourceMap } from 'source-map'
+import { SourceMapConsumer, SourceMapGenerator, RawSourceMap } from 'source-map'
 import {
   transformAssetUrl,
   AssetURLOptions,
@@ -29,6 +29,7 @@ export interface SFCTemplateCompileResults {
 export interface SFCTemplateCompileOptions {
   source: string
   filename: string
+  inMap?: RawSourceMap
   compiler?: TemplateCompiler
   compilerOptions?: CompilerOptions
   preprocessLang?: string
@@ -100,6 +101,7 @@ export function compileTemplate(
 
 function doCompileTemplate({
   filename,
+  inMap,
   source,
   compiler = require('@vue/compiler-dom'),
   compilerOptions = {},
@@ -117,7 +119,7 @@ function doCompileTemplate({
     nodeTransforms = [transformAssetUrl, transformSrcset]
   }
 
-  const { code, map } = compiler.compile(source, {
+  let { code, map } = compiler.compile(source, {
     mode: 'module',
     prefixIdentifiers: true,
     hoistStatic: true,
@@ -128,5 +130,91 @@ function doCompileTemplate({
     sourceMap: true,
     onError: e => errors.push(e)
   })
+
+  // inMap should be the map produced by ./parse.ts which is a simple line-only
+  // mapping. If it is present, we need to adjust the final map and errors to
+  // reflect the original line numbers.
+  if (inMap) {
+    if (map) {
+      map = mapLines(inMap, map)
+    }
+    if (errors.length) {
+      patchErrors(errors, source, inMap)
+    }
+  }
+
   return { code, source, errors, tips: [], map }
 }
+
+function mapLines(oldMap: RawSourceMap, newMap: RawSourceMap): RawSourceMap {
+  if (!oldMap) return newMap
+  if (!newMap) return oldMap
+
+  const oldMapConsumer = new SourceMapConsumer(oldMap)
+  const newMapConsumer = new SourceMapConsumer(newMap)
+  const mergedMapGenerator = new SourceMapGenerator()
+
+  newMapConsumer.eachMapping(m => {
+    if (m.originalLine == null) {
+      return
+    }
+
+    const origPosInOldMap = oldMapConsumer.originalPositionFor({
+      line: m.originalLine,
+      column: m.originalColumn
+    })
+
+    if (origPosInOldMap.source == null) {
+      return
+    }
+
+    mergedMapGenerator.addMapping({
+      generated: {
+        line: m.generatedLine,
+        column: m.generatedColumn
+      },
+      original: {
+        line: origPosInOldMap.line, // map line
+        // use current column, since the oldMap produced by @vue/compiler-sfc
+        // does not
+        column: m.originalColumn
+      },
+      source: origPosInOldMap.source,
+      name: origPosInOldMap.name
+    })
+  })
+
+  // source-map's type definition is incomplete
+  const generator = mergedMapGenerator as any
+  ;(oldMapConsumer as any).sources.forEach((sourceFile: string) => {
+    generator._sources.add(sourceFile)
+    const sourceContent = oldMapConsumer.sourceContentFor(sourceFile)
+    if (sourceContent != null) {
+      mergedMapGenerator.setSourceContent(sourceFile, sourceContent)
+    }
+  })
+
+  generator._sourceRoot = oldMap.sourceRoot
+  generator._file = oldMap.file
+  return generator.toJSON()
+}
+
+function patchErrors(
+  errors: CompilerError[],
+  source: string,
+  inMap: RawSourceMap
+) {
+  const originalSource = inMap.sourcesContent![0]
+  const offset = originalSource.indexOf(source)
+  const lineOffset = originalSource.slice(0, offset).split(/\r?\n/).length - 1
+  errors.forEach(err => {
+    if (err.loc) {
+      err.loc.start.line += lineOffset
+      err.loc.start.offset += offset
+      if (err.loc.end !== err.loc.start) {
+        err.loc.end.line += lineOffset
+        err.loc.end.offset += offset
+      }
+    }
+  })
+}