]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler-core/runtime-core): show codeframe in runtime compile errors (#220)
authorlikui <2218301630@qq.com>
Sat, 12 Oct 2019 23:49:23 +0000 (07:49 +0800)
committerEvan You <yyx990803@gmail.com>
Sat, 12 Oct 2019 23:49:23 +0000 (19:49 -0400)
packages/compiler-core/__tests__/__snapshots__/codeframe.spec.ts.snap [new file with mode: 0644]
packages/compiler-core/__tests__/codeframe.spec.ts [new file with mode: 0644]
packages/compiler-core/src/codeframe.ts [new file with mode: 0644]
packages/compiler-core/src/index.ts
packages/runtime-core/src/component.ts

diff --git a/packages/compiler-core/__tests__/__snapshots__/codeframe.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/codeframe.spec.ts.snap
new file mode 100644 (file)
index 0000000..89a4c26
--- /dev/null
@@ -0,0 +1,37 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`compiler: codeframe line in middle 1`] = `
+"2  |    <template key=\\"one\\"></template>
+3  |    <ul>
+4  |      <li v-for=\\"foobar\\">hi</li>
+   |          ^^^^^^^^^^^^^^
+5  |    </ul>
+6  |    <template key=\\"two\\"></template>"
+`;
+
+exports[`compiler: codeframe line near bottom 1`] = `
+"4  |      <li v-for=\\"foobar\\">hi</li>
+5  |    </ul>
+6  |    <template key=\\"two\\"></template>
+   |              ^^^^^^^^^
+7  |  </div>"
+`;
+
+exports[`compiler: codeframe line near top 1`] = `
+"1  |  <div>
+2  |    <template key=\\"one\\"></template>
+   |              ^^^^^^^^^
+3  |    <ul>
+4  |      <li v-for=\\"foobar\\">hi</li>"
+`;
+
+exports[`compiler: codeframe multi-line highlights 1`] = `
+"1  |  <div attr=\\"some
+   |       ^^^^^^^^^^
+2  |    multiline
+   |  ^^^^^^^^^^^
+3  |  attr
+   |  ^^^^
+4  |  \\">
+   |  ^"
+`;
diff --git a/packages/compiler-core/__tests__/codeframe.spec.ts b/packages/compiler-core/__tests__/codeframe.spec.ts
new file mode 100644 (file)
index 0000000..2b64afa
--- /dev/null
@@ -0,0 +1,46 @@
+import { generateCodeFrame } from '../src'
+
+describe('compiler: codeframe', () => {
+  const source = `
+<div>
+  <template key="one"></template>
+  <ul>
+    <li v-for="foobar">hi</li>
+  </ul>
+  <template key="two"></template>
+</div>
+    `.trim()
+
+  test('line near top', () => {
+    const keyStart = source.indexOf(`key="one"`)
+    const keyEnd = keyStart + `key="one"`.length
+    expect(generateCodeFrame(source, keyStart, keyEnd)).toMatchSnapshot()
+  })
+
+  test('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)).toMatchSnapshot()
+  })
+
+  test('line near bottom', () => {
+    const keyStart = source.indexOf(`key="two"`)
+    const keyEnd = keyStart + `key="two"`.length
+    expect(generateCodeFrame(source, keyStart, keyEnd)).toMatchSnapshot()
+  })
+
+  test('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)).toMatchSnapshot()
+  })
+})
diff --git a/packages/compiler-core/src/codeframe.ts b/packages/compiler-core/src/codeframe.ts
new file mode 100644 (file)
index 0000000..acea12b
--- /dev/null
@@ -0,0 +1,37 @@
+const range: number = 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')
+}
index ab5655d93747a0a451b254bc0a6ec143208b355f..32c7f529ed970bf5143ca4e1ee072c7a0075f6e9 100644 (file)
@@ -99,6 +99,7 @@ export {
 } from './errors'
 export * from './ast'
 export * from './utils'
+export * from './codeframe'
 export { registerRuntimeHelpers } from './runtimeHelpers'
 
 // expose transforms so higher-order compilers can import and extend them
index 77ed68ab52d02010c54d41eaedaff73c7709b3af..08cc7cbee76d31c8d9361c4e07eb2e0ee6c6d8b6 100644 (file)
@@ -24,7 +24,11 @@ import {
   isObject
 } from '@vue/shared'
 import { SuspenseBoundary } from './suspense'
-import { CompilerOptions } from '@vue/compiler-dom'
+import {
+  CompilerError,
+  CompilerOptions,
+  generateCodeFrame
+} from '@vue/compiler-dom'
 
 export type Data = { [key: string]: unknown }
 
@@ -319,10 +323,17 @@ function finishComponentSetup(
     if (Component.template && !Component.render) {
       if (compile) {
         Component.render = compile(Component.template, {
-          onError(err) {
+          onError(err: CompilerError) {
             if (__DEV__) {
-              // TODO use err.loc to provide codeframe like Vue 2
-              warn(`Template compilation error: ${err.message}`)
+              const message = `Template compilation error: ${err.message}`
+              const codeFrame =
+                err.loc &&
+                generateCodeFrame(
+                  Component.template!,
+                  err.loc.start.offset,
+                  err.loc.end.offset
+                )
+              warn(codeFrame ? `${message}\n${codeFrame}` : message)
             }
           }
         })