Skip to content

Commit 325fc76

Browse files
committed
feat(compiler): output codeframe in browser compiler
1 parent 3883f1f commit 325fc76

File tree

4 files changed

+144
-6
lines changed

4 files changed

+144
-6
lines changed

src/compiler/codeframe.js

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/* @flow */
2+
3+
const range = 2
4+
5+
export function generateCodeFrame (
6+
source: string,
7+
start: number = 0,
8+
end: number = source.length
9+
): string {
10+
const lines = source.split(/\r?\n/)
11+
let count = 0
12+
const res = []
13+
for (let i = 0; i < lines.length; i++) {
14+
count += lines[i].length + 1
15+
if (count >= start) {
16+
for (let j = i - range; j <= i + range || end > count; j++) {
17+
if (j < 0 || j >= lines.length) continue
18+
res.push(`${j + 1}${repeat(` `, 3 - String(j + 1).length)}| ${lines[j]}`)
19+
const lineLength = lines[j].length
20+
if (j === i) {
21+
// push underline
22+
const pad = start - (count - lineLength) + 1
23+
const length = end > count ? lineLength - pad : end - start
24+
res.push(` | ` + repeat(` `, pad) + repeat(`^`, length))
25+
} else if (j > i) {
26+
if (end > count) {
27+
const length = Math.min(end - count, lineLength)
28+
res.push(` | ` + repeat(`^`, length))
29+
}
30+
count += lineLength + 1
31+
}
32+
}
33+
break
34+
}
35+
}
36+
return res.join('\n')
37+
}
38+
39+
function repeat (str, n) {
40+
let result = ''
41+
while (true) { // eslint-disable-line
42+
if (n & 1) result += str
43+
n >>>= 1
44+
if (n <= 0) break
45+
str += str
46+
}
47+
return result
48+
}

src/compiler/to-function.js

+21-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { noop, extend } from 'shared/util'
44
import { warn as baseWarn, tip } from 'core/util/debug'
5+
import { generateCodeFrame } from './codeframe'
56

67
type CompiledFunctionResult = {
78
render: Function;
@@ -61,14 +62,28 @@ export function createCompileToFunctionFn (compile: Function): Function {
6162
// check compilation errors/tips
6263
if (process.env.NODE_ENV !== 'production') {
6364
if (compiled.errors && compiled.errors.length) {
64-
warn(
65-
`Error compiling template:\n\n${template}\n\n` +
66-
compiled.errors.map(e => `- ${e}`).join('\n') + '\n',
67-
vm
68-
)
65+
if (options.outputSourceRange) {
66+
compiled.errors.forEach(e => {
67+
warn(
68+
`Error compiling template:\n\n${e.msg}\n\n` +
69+
generateCodeFrame(template, e.start, e.end),
70+
vm
71+
)
72+
})
73+
} else {
74+
warn(
75+
`Error compiling template:\n\n${template}\n\n` +
76+
compiled.errors.map(e => `- ${e}`).join('\n') + '\n',
77+
vm
78+
)
79+
}
6980
}
7081
if (compiled.tips && compiled.tips.length) {
71-
compiled.tips.forEach(msg => tip(msg, vm))
82+
if (options.outputSourceRange) {
83+
compiled.tips.forEach(e => tip(e.msg, vm))
84+
} else {
85+
compiled.tips.forEach(msg => tip(msg, vm))
86+
}
7287
}
7388
}
7489

src/platforms/web/entry-runtime-with-compiler.js

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Vue.prototype.$mount = function (
6363
}
6464

6565
const { render, staticRenderFns } = compileToFunctions(template, {
66+
outputSourceRange: process.env.NODE_ENV !== 'production',
6667
shouldDecodeNewlines,
6768
shouldDecodeNewlinesForHref,
6869
delimiters: options.delimiters,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { generateCodeFrame } from 'compiler/codeframe'
2+
3+
describe('codeframe', () => {
4+
const source = `
5+
<div>
6+
<template key="one"></template>
7+
<ul>
8+
<li v-for="foobar">hi</li>
9+
</ul>
10+
<template key="two"></template>
11+
</div>
12+
`.trim()
13+
14+
it('line near top', () => {
15+
const keyStart = source.indexOf(`key="one"`)
16+
const keyEnd = keyStart + `key="one"`.length
17+
expect(generateCodeFrame(source, keyStart, keyEnd)).toBe(`
18+
1 | <div>
19+
2 | <template key="one"></template>
20+
| ^^^^^^^^^
21+
3 | <ul>
22+
4 | <li v-for="foobar">hi</li>
23+
`.trim())
24+
})
25+
26+
it('line in middle', () => {
27+
// should cover 5 lines
28+
const forStart = source.indexOf(`v-for=`)
29+
const forEnd = forStart + `v-for="foobar"`.length
30+
expect(generateCodeFrame(source, forStart, forEnd)).toBe(`
31+
2 | <template key="one"></template>
32+
3 | <ul>
33+
4 | <li v-for="foobar">hi</li>
34+
| ^^^^^^^^^^^^^^
35+
5 | </ul>
36+
6 | <template key="two"></template>
37+
`.trim())
38+
})
39+
40+
it('line near bottom', () => {
41+
const keyStart = source.indexOf(`key="two"`)
42+
const keyEnd = keyStart + `key="two"`.length
43+
expect(generateCodeFrame(source, keyStart, keyEnd)).toBe(`
44+
4 | <li v-for="foobar">hi</li>
45+
5 | </ul>
46+
6 | <template key="two"></template>
47+
| ^^^^^^^^^
48+
7 | </div>
49+
`.trim())
50+
})
51+
52+
it('multi-line highlights', () => {
53+
const source = `
54+
<div attr="some
55+
multiline
56+
attr
57+
">
58+
</div>
59+
`.trim()
60+
61+
const attrStart = source.indexOf(`attr=`)
62+
const attrEnd = source.indexOf(`">`) + 1
63+
expect(generateCodeFrame(source, attrStart, attrEnd)).toBe(`
64+
1 | <div attr="some
65+
| ^^^^^^^^^^
66+
2 | multiline
67+
| ^^^^^^^^^^^
68+
3 | attr
69+
| ^^^^
70+
4 | ">
71+
| ^
72+
`.trim())
73+
})
74+
})

0 commit comments

Comments
 (0)