Skip to content

Commit

Permalink
feat(compiler): better warning for invalid expressions in function/br…
Browse files Browse the repository at this point in the history
…owser mode

fix #1266
  • Loading branch information
yyx990803 committed Jun 11, 2020
1 parent 10bb34b commit e29f0b3
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 1 deletion.
4 changes: 3 additions & 1 deletion packages/compiler-core/src/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ export function getBaseTransformPreset(
trackVForSlotScopes,
transformExpression
]
: []),
: __BROWSER__ && __DEV__
? [transformExpression]
: []),
transformSlotOutlet,
transformElement,
trackSlotScopes,
Expand Down
7 changes: 7 additions & 0 deletions packages/compiler-core/src/transforms/transformExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import { isGloballyWhitelisted, makeMap } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import { Node, Function, Identifier, ObjectProperty } from '@babel/types'
import { validateBrowserExpression } from '../validateExpression'

const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')

Expand Down Expand Up @@ -84,6 +85,12 @@ export function processExpression(
// v-on handler values may contain multiple statements
asRawStatements = false
): ExpressionNode {
if (__DEV__ && __BROWSER__) {
// simple in-browser validation (same logic in 2.x)
validateBrowserExpression(node, context, asParams, asRawStatements)
return node
}

if (!context.prefixIdentifiers || !node.content.trim()) {
return node
}
Expand Down
25 changes: 25 additions & 0 deletions packages/compiler-core/src/transforms/vFor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
FRAGMENT
} from '../runtimeHelpers'
import { processExpression } from './transformExpression'
import { validateBrowserExpression } from '../validateExpression'
import { PatchFlags, PatchFlagNames } from '@vue/shared'

export const transformFor = createStructuralDirectiveTransform(
Expand Down Expand Up @@ -243,6 +244,9 @@ export function parseForExpression(
context
)
}
if (__DEV__ && __BROWSER__) {
validateBrowserExpression(result.source as SimpleExpressionNode, context)
}

let valueContent = LHS.trim()
.replace(stripParensRE, '')
Expand All @@ -261,6 +265,13 @@ export function parseForExpression(
if (!__BROWSER__ && context.prefixIdentifiers) {
result.key = processExpression(result.key, context, true)
}
if (__DEV__ && __BROWSER__) {
validateBrowserExpression(
result.key as SimpleExpressionNode,
context,
true
)
}
}

if (iteratorMatch[2]) {
Expand All @@ -280,6 +291,13 @@ export function parseForExpression(
if (!__BROWSER__ && context.prefixIdentifiers) {
result.index = processExpression(result.index, context, true)
}
if (__DEV__ && __BROWSER__) {
validateBrowserExpression(
result.index as SimpleExpressionNode,
context,
true
)
}
}
}
}
Expand All @@ -289,6 +307,13 @@ export function parseForExpression(
if (!__BROWSER__ && context.prefixIdentifiers) {
result.value = processExpression(result.value, context, true)
}
if (__DEV__ && __BROWSER__) {
validateBrowserExpression(
result.value as SimpleExpressionNode,
context,
true
)
}
}

return result
Expand Down
5 changes: 5 additions & 0 deletions packages/compiler-core/src/transforms/vIf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
import { validateBrowserExpression } from '../validateExpression'
import {
CREATE_BLOCK,
FRAGMENT,
Expand Down Expand Up @@ -93,6 +94,10 @@ export function processIf(
dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
}

if (__DEV__ && __BROWSER__ && dir.exp) {
validateBrowserExpression(dir.exp as SimpleExpressionNode, context)
}

if (dir.name === 'if') {
const branch = createIfBranch(node, dir)
const ifNode: IfNode = {
Expand Down
10 changes: 10 additions & 0 deletions packages/compiler-core/src/transforms/vOn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { capitalize, camelize } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
import { validateBrowserExpression } from '../validateExpression'
import { isMemberExpression, hasScopeRef } from '../utils'

const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function(?:\s+[\w$]+)?\s*\(/
Expand Down Expand Up @@ -89,6 +90,15 @@ export const transformOn: DirectiveTransform = (
}
}

if (__DEV__ && __BROWSER__) {
validateBrowserExpression(
exp as SimpleExpressionNode,
context,
false,
hasMultipleStatements
)
}

if (isInlineStatement || (isCacheable && isMemberExp)) {
// wrap inline statement in a function expression
exp = createCompoundExpression([
Expand Down
49 changes: 49 additions & 0 deletions packages/compiler-core/src/transforms/validateExpression.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { NodeTransform, TransformContext } from '../transform'
import { NodeTypes, SimpleExpressionNode } from '../ast'

/**
* When using the runtime compiler in function mode, some expressions will
* become invalid (e.g. using keyworkds like `class` in expressions) so we need
* to detect them.
*
* This transform is browser-only and dev-only.
*/
export const validateExpression: NodeTransform = (node, context) => {
if (node.type === NodeTypes.INTERPOLATION) {
validateBrowserExpression(node.content as SimpleExpressionNode, context)
} else if (node.type === NodeTypes.ELEMENT) {
// handle directives on element
for (let i = 0; i < node.props.length; i++) {
const dir = node.props[i]
// do not process for v-on & v-for since they are special handled
if (dir.type === NodeTypes.DIRECTIVE && dir.name !== 'for') {
const exp = dir.exp
const arg = dir.arg
// do not process exp if this is v-on:arg - we need special handling
// for wrapping inline statements.
if (
exp &&
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
!(dir.name === 'on' && arg)
) {
validateBrowserExpression(
exp,
context,
// slot args must be processed as function params
dir.name === 'slot'
)
}
if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {
validateBrowserExpression(arg, context)
}
}
}
}
}

export function validateBrowserExpression(
node: SimpleExpressionNode,
context: TransformContext,
asParams = false,
asRawStatements = false
) {}
60 changes: 60 additions & 0 deletions packages/compiler-core/src/validateExpression.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// these keywords should not appear inside expressions, but operators like

import { SimpleExpressionNode } from './ast'
import { TransformContext } from './transform'
import { createCompilerError, ErrorCodes } from './errors'

// typeof, instanceof and in are allowed
const prohibitedKeywordRE = new RegExp(
'\\b' +
(
'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +
'super,throw,while,yield,delete,export,import,return,switch,default,' +
'extends,finally,continue,debugger,function,arguments,typeof,void'
)
.split(',')
.join('\\b|\\b') +
'\\b'
)

// strip strings in expressions
const stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g

/**
* Validate a non-prefixed expression.
* This is only called when using the in-browser runtime compiler since it
* doesn't prefix expressions.
*/
export function validateBrowserExpression(
node: SimpleExpressionNode,
context: TransformContext,
asParams = false,
asRawStatements = false
) {
const exp = node.content
try {
new Function(
asRawStatements
? ` ${exp} `
: `return ${asParams ? `(${exp}) => {}` : `(${exp})`}`
)
} catch (e) {
let message = e.message
const keywordMatch = exp
.replace(stripStringRE, '')
.match(prohibitedKeywordRE)
if (keywordMatch) {
message = `avoid using JavaScript keyword as property name: "${
keywordMatch[0]
}"`
}
context.onError(
createCompilerError(
ErrorCodes.X_INVALID_EXPRESSION,
node.loc,
undefined,
message
)
)
}
}

0 comments on commit e29f0b3

Please sign in to comment.