--- /dev/null
+// 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 | \\">
+ | ^"
+`;
--- /dev/null
+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()
+ })
+})
--- /dev/null
+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')
+}
} 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
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 }
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)
}
}
})