feat(compiler): better warning for invalid expressions in function/browser mode
fix #1266
This commit is contained in:
parent
10bb34bb86
commit
e29f0b3fc2
@ -36,6 +36,8 @@ export function getBaseTransformPreset(
|
||||
trackVForSlotScopes,
|
||||
transformExpression
|
||||
]
|
||||
: __BROWSER__ && __DEV__
|
||||
? [transformExpression]
|
||||
: []),
|
||||
transformSlotOutlet,
|
||||
transformElement,
|
||||
|
@ -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')
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
@ -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(
|
||||
@ -243,6 +244,9 @@ export function parseForExpression(
|
||||
context
|
||||
)
|
||||
}
|
||||
if (__DEV__ && __BROWSER__) {
|
||||
validateBrowserExpression(result.source as SimpleExpressionNode, context)
|
||||
}
|
||||
|
||||
let valueContent = LHS.trim()
|
||||
.replace(stripParensRE, '')
|
||||
@ -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]) {
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
} from '../ast'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
import { processExpression } from './transformExpression'
|
||||
import { validateBrowserExpression } from '../validateExpression'
|
||||
import {
|
||||
CREATE_BLOCK,
|
||||
FRAGMENT,
|
||||
@ -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 = {
|
||||
|
@ -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*\(/
|
||||
@ -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([
|
||||
|
49
packages/compiler-core/src/transforms/validateExpression.ts
Normal file
49
packages/compiler-core/src/transforms/validateExpression.ts
Normal file
@ -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
packages/compiler-core/src/validateExpression.ts
Normal file
60
packages/compiler-core/src/validateExpression.ts
Normal file
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user