]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler): better warning for invalid expressions in function/browser mode
authorEvan You <yyx990803@gmail.com>
Thu, 11 Jun 2020 20:31:51 +0000 (16:31 -0400)
committerEvan You <yyx990803@gmail.com>
Thu, 11 Jun 2020 20:31:51 +0000 (16:31 -0400)
fix #1266

packages/compiler-core/src/compile.ts
packages/compiler-core/src/transforms/transformExpression.ts
packages/compiler-core/src/transforms/vFor.ts
packages/compiler-core/src/transforms/vIf.ts
packages/compiler-core/src/transforms/vOn.ts
packages/compiler-core/src/transforms/validateExpression.ts [new file with mode: 0644]
packages/compiler-core/src/validateExpression.ts [new file with mode: 0644]

index ae296139742bd446d7300b7d5a26f3812cd383a1..e3b6260bcefa14c620e9bee6dbe110acd3c629a5 100644 (file)
@@ -36,7 +36,9 @@ export function getBaseTransformPreset(
             trackVForSlotScopes,
             transformExpression
           ]
-        : []),
+        : __BROWSER__ && __DEV__
+          ? [transformExpression]
+          : []),
       transformSlotOutlet,
       transformElement,
       trackSlotScopes,
index 9ff5b8025648eab224bcacf543c45ceb4a3bfc67..0d3f145e77bb8be408d73ee579c796a533b83e02 100644 (file)
@@ -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
   }
index a1869ba7f39d840654421061452abec510f4c4b5..ee5c243a80019c4a453e4195fb6f3eaa512bb52d 100644 (file)
@@ -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
index 6109558846f409ec9b5e2dd0cfc9457e6f8bee86..fc6ca5e1f040ea8641831253516e190630b8c797 100644 (file)
@@ -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 = {
index 019fcbd5b92bcb0988173337bb68d51c67e28e2f..d245841004e191b2ff85a569760722489102a6f0 100644 (file)
@@ -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([
diff --git a/packages/compiler-core/src/transforms/validateExpression.ts b/packages/compiler-core/src/transforms/validateExpression.ts
new file mode 100644 (file)
index 0000000..4909625
--- /dev/null
@@ -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
+) {}
diff --git a/packages/compiler-core/src/validateExpression.ts b/packages/compiler-core/src/validateExpression.ts
new file mode 100644 (file)
index 0000000..64f171f
--- /dev/null
@@ -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
+      )
+    )
+  }
+}