UNPKG

3.34 kBJavaScriptView Raw
1/* @flow */
2
3import { dirRE, onRE } from './parser/index'
4
5// these keywords should not appear inside expressions, but operators like
6// typeof, instanceof and in are allowed
7const prohibitedKeywordRE = new RegExp('\\b' + (
8 'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +
9 'super,throw,while,yield,delete,export,import,return,switch,default,' +
10 'extends,finally,continue,debugger,function,arguments'
11).split(',').join('\\b|\\b') + '\\b')
12
13// these unary operators should not be used as property/method names
14const unaryOperatorsRE = new RegExp('\\b' + (
15 'delete,typeof,void'
16).split(',').join('\\s*\\([^\\)]*\\)|\\b') + '\\s*\\([^\\)]*\\)')
17
18// check valid identifier for v-for
19const identRE = /[A-Za-z_$][\w$]*/
20
21// strip strings in expressions
22const stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g
23
24// detect problematic expressions in a template
25export function detectErrors (ast: ?ASTNode): Array<string> {
26 const errors: Array<string> = []
27 if (ast) {
28 checkNode(ast, errors)
29 }
30 return errors
31}
32
33function checkNode (node: ASTNode, errors: Array<string>) {
34 if (node.type === 1) {
35 for (const name in node.attrsMap) {
36 if (dirRE.test(name)) {
37 const value = node.attrsMap[name]
38 if (value) {
39 if (name === 'v-for') {
40 checkFor(node, `v-for="${value}"`, errors)
41 } else if (onRE.test(name)) {
42 checkEvent(value, `${name}="${value}"`, errors)
43 } else {
44 checkExpression(value, `${name}="${value}"`, errors)
45 }
46 }
47 }
48 }
49 if (node.children) {
50 for (let i = 0; i < node.children.length; i++) {
51 checkNode(node.children[i], errors)
52 }
53 }
54 } else if (node.type === 2) {
55 checkExpression(node.expression, node.text, errors)
56 }
57}
58
59function checkEvent (exp: string, text: string, errors: Array<string>) {
60 const stipped = exp.replace(stripStringRE, '')
61 const keywordMatch: any = stipped.match(unaryOperatorsRE)
62 if (keywordMatch && stipped.charAt(keywordMatch.index - 1) !== '$') {
63 errors.push(
64 `avoid using JavaScript unary operator as property name: ` +
65 `"${keywordMatch[0]}" in expression ${text.trim()}`
66 )
67 }
68 checkExpression(exp, text, errors)
69}
70
71function checkFor (node: ASTElement, text: string, errors: Array<string>) {
72 checkExpression(node.for || '', text, errors)
73 checkIdentifier(node.alias, 'v-for alias', text, errors)
74 checkIdentifier(node.iterator1, 'v-for iterator', text, errors)
75 checkIdentifier(node.iterator2, 'v-for iterator', text, errors)
76}
77
78function checkIdentifier (ident: ?string, type: string, text: string, errors: Array<string>) {
79 if (typeof ident === 'string' && !identRE.test(ident)) {
80 errors.push(`invalid ${type} "${ident}" in expression: ${text.trim()}`)
81 }
82}
83
84function checkExpression (exp: string, text: string, errors: Array<string>) {
85 try {
86 new Function(`return ${exp}`)
87 } catch (e) {
88 const keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE)
89 if (keywordMatch) {
90 errors.push(
91 `avoid using JavaScript keyword as property name: ` +
92 `"${keywordMatch[0]}" in expression ${text.trim()}`
93 )
94 } else {
95 errors.push(`invalid expression: ${text.trim()}`)
96 }
97 }
98}