2
0
Эх сурвалжийг харах

feat(compiler): output codeframe in browser compiler

Evan You 7 жил өмнө
parent
commit
325fc7693c

+ 48 - 0
src/compiler/codeframe.js

@@ -0,0 +1,48 @@
+/* @flow */
+
+const range = 2
+
+export function generateCodeFrame (
+  source: string,
+  start: number = 0,
+  end: number = source.length
+): string {
+  const lines = source.split(/\r?\n/)
+  let count = 0
+  const res = []
+  for (let i = 0; i < lines.length; i++) {
+    count += lines[i].length + 1
+    if (count >= start) {
+      for (let j = i - range; j <= i + range || end > count; j++) {
+        if (j < 0 || j >= lines.length) continue
+        res.push(`${j + 1}${repeat(` `, 3 - String(j + 1).length)}|  ${lines[j]}`)
+        const lineLength = lines[j].length
+        if (j === i) {
+          // push underline
+          const pad = start - (count - lineLength) + 1
+          const length = end > count ? lineLength - pad : end - start
+          res.push(`   |  ` + repeat(` `, pad) + repeat(`^`, length))
+        } else if (j > i) {
+          if (end > count) {
+            const length = Math.min(end - count, lineLength)
+            res.push(`   |  ` + repeat(`^`, length))
+          }
+          count += lineLength + 1
+        }
+      }
+      break
+    }
+  }
+  return res.join('\n')
+}
+
+function repeat (str, n) {
+  let result = ''
+  while (true) { // eslint-disable-line
+    if (n & 1) result += str
+    n >>>= 1
+    if (n <= 0) break
+    str += str
+  }
+  return result
+}

+ 21 - 6
src/compiler/to-function.js

@@ -2,6 +2,7 @@
 
 import { noop, extend } from 'shared/util'
 import { warn as baseWarn, tip } from 'core/util/debug'
+import { generateCodeFrame } from './codeframe'
 
 type CompiledFunctionResult = {
   render: Function;
@@ -61,14 +62,28 @@ export function createCompileToFunctionFn (compile: Function): Function {
     // check compilation errors/tips
     if (process.env.NODE_ENV !== 'production') {
       if (compiled.errors && compiled.errors.length) {
-        warn(
-          `Error compiling template:\n\n${template}\n\n` +
-          compiled.errors.map(e => `- ${e}`).join('\n') + '\n',
-          vm
-        )
+        if (options.outputSourceRange) {
+          compiled.errors.forEach(e => {
+            warn(
+              `Error compiling template:\n\n${e.msg}\n\n` +
+              generateCodeFrame(template, e.start, e.end),
+              vm
+            )
+          })
+        } else {
+          warn(
+            `Error compiling template:\n\n${template}\n\n` +
+            compiled.errors.map(e => `- ${e}`).join('\n') + '\n',
+            vm
+          )
+        }
       }
       if (compiled.tips && compiled.tips.length) {
-        compiled.tips.forEach(msg => tip(msg, vm))
+        if (options.outputSourceRange) {
+          compiled.tips.forEach(e => tip(e.msg, vm))
+        } else {
+          compiled.tips.forEach(msg => tip(msg, vm))
+        }
       }
     }
 

+ 1 - 0
src/platforms/web/entry-runtime-with-compiler.js

@@ -63,6 +63,7 @@ Vue.prototype.$mount = function (
       }
 
       const { render, staticRenderFns } = compileToFunctions(template, {
+        outputSourceRange: process.env.NODE_ENV !== 'production',
         shouldDecodeNewlines,
         shouldDecodeNewlinesForHref,
         delimiters: options.delimiters,

+ 74 - 0
test/unit/modules/compiler/codeframe.spec.js

@@ -0,0 +1,74 @@
+import { generateCodeFrame } from 'compiler/codeframe'
+
+describe('codeframe', () => {
+  const source = `
+<div>
+  <template key="one"></template>
+  <ul>
+    <li v-for="foobar">hi</li>
+  </ul>
+  <template key="two"></template>
+</div>
+    `.trim()
+
+  it('line near top', () => {
+    const keyStart = source.indexOf(`key="one"`)
+    const keyEnd = keyStart + `key="one"`.length
+    expect(generateCodeFrame(source, keyStart, keyEnd)).toBe(`
+1  |  <div>
+2  |    <template key="one"></template>
+   |              ^^^^^^^^^
+3  |    <ul>
+4  |      <li v-for="foobar">hi</li>
+    `.trim())
+  })
+
+  it('line in middle', () => {
+    // should cover 5 lines
+    const forStart = source.indexOf(`v-for=`)
+    const forEnd = forStart + `v-for="foobar"`.length
+    expect(generateCodeFrame(source, forStart, forEnd)).toBe(`
+2  |    <template key="one"></template>
+3  |    <ul>
+4  |      <li v-for="foobar">hi</li>
+   |          ^^^^^^^^^^^^^^
+5  |    </ul>
+6  |    <template key="two"></template>
+    `.trim())
+  })
+
+  it('line near bottom', () => {
+    const keyStart = source.indexOf(`key="two"`)
+    const keyEnd = keyStart + `key="two"`.length
+    expect(generateCodeFrame(source, keyStart, keyEnd)).toBe(`
+4  |      <li v-for="foobar">hi</li>
+5  |    </ul>
+6  |    <template key="two"></template>
+   |              ^^^^^^^^^
+7  |  </div>
+    `.trim())
+  })
+
+  it('multi-line highlights', () => {
+    const source = `
+<div attr="some
+  multiline
+attr
+">
+</div>
+    `.trim()
+
+    const attrStart = source.indexOf(`attr=`)
+    const attrEnd = source.indexOf(`">`) + 1
+    expect(generateCodeFrame(source, attrStart, attrEnd)).toBe(`
+1  |  <div attr="some
+   |       ^^^^^^^^^^
+2  |    multiline
+   |  ^^^^^^^^^^^
+3  |  attr
+   |  ^^^^
+4  |  ">
+   |  ^
+    `.trim())
+  })
+})