Skip to content

Commit d76bf8c

Browse files
committed
improve template error detector
1 parent a5ccee3 commit d76bf8c

File tree

5 files changed

+47
-15
lines changed

5 files changed

+47
-15
lines changed

flow/compiler.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,13 @@ declare type ASTElement = {
8888
renderMethod?: ?string,
8989
renderArgs?: ?string,
9090

91-
if?: string | null,
91+
if?: string,
92+
ifProcessed?: boolean,
9293
else?: true,
9394
elseBlock?: ASTElement,
9495

95-
for?: string | null,
96+
for?: string,
97+
forProcessed?: boolean,
9698
key?: string,
9799
alias?: string,
98100
iterator1?: string,

src/compiler/codegen.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ export function generate (
3939
}
4040

4141
function genElement (el: ASTElement): string {
42-
if (el.for) {
42+
if (el.for && !el.forProcessed) {
4343
return genFor(el)
44-
} else if (el.if) {
44+
} else if (el.if && !el.ifProcessed) {
4545
return genIf(el)
4646
} else if (el.tag === 'template' && !el.slotTarget) {
4747
return genChildren(el) || 'void 0'
@@ -88,7 +88,7 @@ function genElement (el: ASTElement): string {
8888

8989
function genIf (el: ASTElement): string {
9090
const exp = el.if
91-
el.if = null // avoid recursion
91+
el.ifProcessed = true // avoid recursion
9292
return `(${exp})?${genElement(el)}:${genElse(el)}`
9393
}
9494

@@ -103,7 +103,7 @@ function genFor (el: ASTElement): string {
103103
const alias = el.alias
104104
const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
105105
const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
106-
el.for = null // avoid recursion
106+
el.forProcessed = true // avoid recursion
107107
return `(${exp})&&_l((${exp}),` +
108108
`function(${alias}${iterator1}${iterator2}){` +
109109
`return ${genElement(el)}` +

src/compiler/error-detector.js

+27-7
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22

33
import { dirRE } from './parser/index'
44

5-
const keywordRE = new RegExp('\\b' + (
6-
'do,if,in,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +
7-
'super,throw,while,yield,delete,export,import,return,switch,typeof,default,' +
8-
'extends,finally,continue,debugger,function,arguments,instanceof'
5+
// operators like typeof, instanceof and in are allowed
6+
const prohibitedKeywordRE = new RegExp('\\b' + (
7+
'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +
8+
'super,throw,while,yield,delete,export,import,return,switch,default,' +
9+
'extends,finally,continue,debugger,function,arguments'
910
).split(',').join('\\b|\\b') + '\\b')
11+
// check valid identifier for v-for
12+
const identRE = /[^\w$\.](?:[A-Za-z_$][\w$]*)/
1013

1114
// detect problematic expressions in a template
1215
export function detectErrors (ast: ?ASTNode): Array<string> {
@@ -23,7 +26,11 @@ function checkNode (node: ASTNode, errors: Array<string>) {
2326
if (dirRE.test(name)) {
2427
const value = node.attrsMap[name]
2528
if (value) {
26-
checkExpression(value, `${name}="${value}"`, errors)
29+
if (name === 'v-for') {
30+
checkFor(node, `v-for="${value}"`, errors)
31+
} else {
32+
checkExpression(value, `${name}="${value}"`, errors)
33+
}
2734
}
2835
}
2936
}
@@ -37,17 +44,30 @@ function checkNode (node: ASTNode, errors: Array<string>) {
3744
}
3845
}
3946

47+
function checkFor (node: ASTElement, text: string, errors: Array<string>) {
48+
checkExpression(node.for || '', text, errors)
49+
checkIdentifier(node.alias, 'v-for alias', text, errors)
50+
checkIdentifier(node.iterator1, 'v-for iterator', text, errors)
51+
checkIdentifier(node.iterator2, 'v-for iterator', text, errors)
52+
}
53+
54+
function checkIdentifier (ident: ?string, type: string, text: string, errors: Array<string>) {
55+
if (typeof ident === 'string' && !identRE.test(ident)) {
56+
errors.push(`- invalid ${type} "${ident}" in expression: ${text}`)
57+
}
58+
}
59+
4060
function checkExpression (exp: string, text: string, errors: Array<string>) {
4161
exp = stripToString(exp)
42-
const keywordMatch = exp.match(keywordRE)
62+
const keywordMatch = exp.match(prohibitedKeywordRE)
4363
if (keywordMatch) {
4464
errors.push(
4565
`- avoid using JavaScript keyword as property name: ` +
4666
`"${keywordMatch[0]}" in expression ${text}`
4767
)
4868
} else {
4969
try {
50-
new Function(exp)
70+
new Function(`return ${exp}`)
5171
} catch (e) {
5272
errors.push(`- invalid expression: ${text}`)
5373
}

src/compiler/parser/index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ import {
1717
} from '../helpers'
1818

1919
export const dirRE = /^v-|^@|^:/
20+
export const forAliasRE = /(.*)\s+(?:in|of)\s+(.*)/
21+
export const forIteratorRE = /\(([^,]*),([^,]*)(?:,([^,]*))?\)/
2022
const bindRE = /^:|^v-bind:/
2123
const onRE = /^@|^v-on:/
2224
const argRE = /:(.*)$/
2325
const modifierRE = /\.[^\.]+/g
24-
const forAliasRE = /(.*)\s+(?:in|of)\s+(.*)/
25-
const forIteratorRE = /\(([^,]*),([^,]*)(?:,([^,]*))?\)/
2626
const camelRE = /[a-z\d][A-Z]/
2727

2828
const decodeHTMLCached = cached(decodeHTML)

test/unit/features/options/template.spec.js

+10
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,14 @@ describe('Options template', () => {
6060
expect('invalid expression: {{ a"" }}').toHaveBeenWarned()
6161
expect('avoid using JavaScript keyword as property name: "do" in expression {{ do + 1 }}').toHaveBeenWarned()
6262
})
63+
64+
it('warn error in generated function (v-for)', () => {
65+
new Vue({
66+
template: '<div><div v-for="(1, 2) in a----"></div></div>'
67+
}).$mount()
68+
expect('failed to compile template').toHaveBeenWarned()
69+
expect('invalid v-for alias "1"').toHaveBeenWarned()
70+
expect('invalid v-for iterator "2"').toHaveBeenWarned()
71+
expect('invalid expression: v-for="(1, 2) in a----"').toHaveBeenWarned()
72+
})
6373
})

0 commit comments

Comments
 (0)