]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(compiler): further extract babel ast utilities
authorEvan You <yyx990803@gmail.com>
Sun, 22 Aug 2021 18:51:16 +0000 (14:51 -0400)
committerEvan You <yyx990803@gmail.com>
Sun, 22 Aug 2021 18:51:16 +0000 (14:51 -0400)
packages/compiler-core/src/babelUtils.ts [new file with mode: 0644]
packages/compiler-core/src/index.ts
packages/compiler-core/src/transforms/transformExpression.ts
packages/compiler-sfc/src/compileScript.ts
packages/compiler-sfc/src/index.ts
packages/shared/src/astUtils.ts [deleted file]
packages/shared/src/index.ts

diff --git a/packages/compiler-core/src/babelUtils.ts b/packages/compiler-core/src/babelUtils.ts
new file mode 100644 (file)
index 0000000..49b3d1a
--- /dev/null
@@ -0,0 +1,231 @@
+import {
+  Identifier,
+  Node,
+  isReferenced,
+  Function,
+  ObjectProperty
+} from '@babel/types'
+import { walk } from 'estree-walker'
+
+export function walkIdentifiers(
+  root: Node,
+  onIdentifier: (
+    node: Identifier,
+    parent: Node,
+    parentStack: Node[],
+    isReference: boolean,
+    isLocal: boolean
+  ) => void,
+  onNode?: (node: Node, parent: Node, parentStack: Node[]) => void | boolean,
+  parentStack: Node[] = [],
+  knownIds: Record<string, number> = Object.create(null),
+  includeAll = false
+) {
+  const rootExp =
+    root.type === 'Program' &&
+    root.body[0].type === 'ExpressionStatement' &&
+    root.body[0].expression
+
+  ;(walk as any)(root, {
+    enter(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
+      parent && parentStack.push(parent)
+      if (
+        parent &&
+        parent.type.startsWith('TS') &&
+        parent.type !== 'TSAsExpression' &&
+        parent.type !== 'TSNonNullExpression' &&
+        parent.type !== 'TSTypeAssertion'
+      ) {
+        return this.skip()
+      }
+      if (onNode && onNode(node, parent!, parentStack) === false) {
+        return this.skip()
+      }
+      if (node.type === 'Identifier') {
+        const isLocal = !!knownIds[node.name]
+        const isRefed = isReferencedIdentifier(node, parent!, parentStack)
+        if (includeAll || (isRefed && !isLocal)) {
+          onIdentifier(node, parent!, parentStack, isRefed, isLocal)
+        }
+      } else if (isFunctionType(node)) {
+        // walk function expressions and add its arguments to known identifiers
+        // so that we don't prefix them
+        for (const p of node.params) {
+          ;(walk as any)(p, {
+            enter(child: Node, parent: Node) {
+              if (
+                child.type === 'Identifier' &&
+                // do not record as scope variable if is a destructured key
+                !isStaticPropertyKey(child, parent) &&
+                // do not record if this is a default value
+                // assignment of a destructured variable
+                !(
+                  parent &&
+                  parent.type === 'AssignmentPattern' &&
+                  parent.right === child
+                )
+              ) {
+                markScopeIdentifier(node, child, knownIds)
+              }
+            }
+          })
+        }
+      } else if (node.type === 'BlockStatement') {
+        // #3445 record block-level local variables
+        for (const stmt of node.body) {
+          if (stmt.type === 'VariableDeclaration') {
+            for (const decl of stmt.declarations) {
+              for (const id of extractIdentifiers(decl.id)) {
+                markScopeIdentifier(node, id, knownIds)
+              }
+            }
+          }
+        }
+      } else if (
+        node.type === 'ObjectProperty' &&
+        parent!.type === 'ObjectPattern'
+      ) {
+        // mark property in destructure pattern
+        ;(node as any).inPattern = true
+      }
+    },
+    leave(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
+      parent && parentStack.pop()
+      if (node !== rootExp && node.scopeIds) {
+        node.scopeIds.forEach((id: string) => {
+          knownIds[id]--
+          if (knownIds[id] === 0) {
+            delete knownIds[id]
+          }
+        })
+      }
+    }
+  })
+}
+
+export function isReferencedIdentifier(
+  id: Identifier,
+  parent: Node | null,
+  parentStack: Node[]
+) {
+  if (!parent) {
+    return true
+  }
+
+  // is a special keyword but parsed as identifier
+  if (id.name === 'arguments') {
+    return false
+  }
+
+  if (isReferenced(id, parent)) {
+    return true
+  }
+
+  // babel's isReferenced check returns false for ids being assigned to, so we
+  // need to cover those cases here
+  switch (parent.type) {
+    case 'AssignmentExpression':
+    case 'AssignmentPattern':
+      return true
+    case 'ObjectPattern':
+    case 'ArrayPattern':
+      return isInDestructureAssignment(parent, parentStack)
+  }
+
+  return false
+}
+
+export function isInDestructureAssignment(
+  parent: Node,
+  parentStack: Node[]
+): boolean {
+  if (
+    parent &&
+    (parent.type === 'ObjectProperty' || parent.type === 'ArrayPattern')
+  ) {
+    let i = parentStack.length
+    while (i--) {
+      const p = parentStack[i]
+      if (p.type === 'AssignmentExpression') {
+        return true
+      } else if (p.type !== 'ObjectProperty' && !p.type.endsWith('Pattern')) {
+        break
+      }
+    }
+  }
+  return false
+}
+
+function extractIdentifiers(
+  param: Node,
+  nodes: Identifier[] = []
+): Identifier[] {
+  switch (param.type) {
+    case 'Identifier':
+      nodes.push(param)
+      break
+
+    case 'MemberExpression':
+      let object: any = param
+      while (object.type === 'MemberExpression') {
+        object = object.object
+      }
+      nodes.push(object)
+      break
+
+    case 'ObjectPattern':
+      param.properties.forEach(prop => {
+        if (prop.type === 'RestElement') {
+          extractIdentifiers(prop.argument, nodes)
+        } else {
+          extractIdentifiers(prop.value, nodes)
+        }
+      })
+      break
+
+    case 'ArrayPattern':
+      param.elements.forEach(element => {
+        if (element) extractIdentifiers(element, nodes)
+      })
+      break
+
+    case 'RestElement':
+      extractIdentifiers(param.argument, nodes)
+      break
+
+    case 'AssignmentPattern':
+      extractIdentifiers(param.left, nodes)
+      break
+  }
+
+  return nodes
+}
+
+function markScopeIdentifier(
+  node: Node & { scopeIds?: Set<string> },
+  child: Identifier,
+  knownIds: Record<string, number>
+) {
+  const { name } = child
+  if (node.scopeIds && node.scopeIds.has(name)) {
+    return
+  }
+  if (name in knownIds) {
+    knownIds[name]++
+  } else {
+    knownIds[name] = 1
+  }
+  ;(node.scopeIds || (node.scopeIds = new Set())).add(name)
+}
+
+export const isFunctionType = (node: Node): node is Function => {
+  return /Function(?:Expression|Declaration)$|Method$/.test(node.type)
+}
+
+export const isStaticProperty = (node: Node): node is ObjectProperty =>
+  node &&
+  (node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
+  !node.computed
+
+export const isStaticPropertyKey = (node: Node, parent: Node) =>
+  isStaticProperty(parent) && parent.key === node
index 303de3af73e9ef052aac1be6099e8f0f083cf83b..1233e0ada591bc0a8eb3b6e8e39d84e485bb837c 100644 (file)
@@ -31,6 +31,7 @@ export {
 
 export * from './ast'
 export * from './utils'
+export * from './babelUtils'
 export * from './runtimeHelpers'
 
 export { getBaseTransformPreset, TransformPreset } from './compile'
index 4efd6d4aab90f11fce402cfcd24aacba8679b676..4c30707fcf4fa7f0f0e0fdff3e9e210cf9bd7f65 100644 (file)
@@ -17,18 +17,19 @@ import {
   createCompoundExpression,
   ConstantTypes
 } from '../ast'
+import {
+  isInDestructureAssignment,
+  isStaticProperty,
+  isStaticPropertyKey,
+  walkIdentifiers
+} from '../babelUtils'
 import { advancePositionWithClone, isSimpleIdentifier } from '../utils'
 import {
   isGloballyWhitelisted,
   makeMap,
   babelParserDefaultPlugins,
   hasOwn,
-  isString,
-  isReferencedIdentifier,
-  isInDestructureAssignment,
-  isStaticProperty,
-  isStaticPropertyKey,
-  isFunctionType
+  isString
 } from '@vue/shared'
 import { createCompilerError, ErrorCodes } from '../errors'
 import {
@@ -39,7 +40,6 @@ import {
 } from '@babel/types'
 import { validateBrowserExpression } from '../validateExpression'
 import { parse } from '@babel/parser'
-import { walk } from 'estree-walker'
 import { IS_REF, UNREF } from '../runtimeHelpers'
 import { BindingTypes } from '../options'
 
@@ -245,89 +245,49 @@ export function processExpression(
     return node
   }
 
-  const ids: (Identifier & PrefixMeta)[] = []
-  const knownIds = Object.create(context.identifiers)
-  const isDuplicate = (node: Node & PrefixMeta): boolean =>
-    ids.some(id => id.start === node.start)
+  type QualifiedId = Identifier & PrefixMeta
+
+  const ids: QualifiedId[] = []
   const parentStack: Node[] = []
+  const knownIds: Record<string, number> = Object.create(context.identifiers)
 
-  // walk the AST and look for identifiers that need to be prefixed.
-  ;(walk as any)(ast, {
-    enter(node: Node & PrefixMeta, parent: Node | undefined) {
-      parent && parentStack.push(parent)
-      if (node.type === 'Identifier') {
-        if (!isDuplicate(node)) {
-          // v2 wrapped filter call
-          if (__COMPAT__ && node.name.startsWith('_filter_')) {
-            return
-          }
+  walkIdentifiers(
+    ast,
+    (node, parent, _, isReferenced, isLocal) => {
+      if (isStaticPropertyKey(node, parent!)) {
+        return
+      }
+      // v2 wrapped filter call
+      if (__COMPAT__ && node.name.startsWith('_filter_')) {
+        return
+      }
 
-          const needPrefix = shouldPrefix(node, parent!, parentStack)
-          if (!knownIds[node.name] && needPrefix) {
-            if (isStaticProperty(parent!) && parent.shorthand) {
-              // property shorthand like { foo }, we need to add the key since
-              // we rewrite the value
-              node.prefix = `${node.name}: `
-            }
-            node.name = rewriteIdentifier(node.name, parent, node)
-            ids.push(node)
-          } else if (!isStaticPropertyKey(node, parent!)) {
-            // The identifier is considered constant unless it's pointing to a
-            // scope variable (a v-for alias, or a v-slot prop)
-            if (!(needPrefix && knownIds[node.name]) && !bailConstant) {
-              node.isConstant = true
-            }
-            // also generate sub-expressions for other identifiers for better
-            // source map support. (except for property keys which are static)
-            ids.push(node)
-          }
+      const needPrefix = isReferenced && canPrefix(node)
+      if (needPrefix && !isLocal) {
+        if (isStaticProperty(parent!) && parent.shorthand) {
+          // property shorthand like { foo }, we need to add the key since
+          // we rewrite the value
+          ;(node as QualifiedId).prefix = `${node.name}: `
+        }
+        node.name = rewriteIdentifier(node.name, parent, node)
+        ids.push(node as QualifiedId)
+      } else {
+        // The identifier is considered constant unless it's pointing to a
+        // local scope variable (a v-for alias, or a v-slot prop)
+        if (!(needPrefix && isLocal) && !bailConstant) {
+          ;(node as QualifiedId).isConstant = true
         }
-      } else if (isFunctionType(node)) {
-        // walk function expressions and add its arguments to known identifiers
-        // so that we don't prefix them
-        node.params.forEach(p =>
-          (walk as any)(p, {
-            enter(child: Node, parent: Node) {
-              if (
-                child.type === 'Identifier' &&
-                // do not record as scope variable if is a destructured key
-                !isStaticPropertyKey(child, parent) &&
-                // do not record if this is a default value
-                // assignment of a destructured variable
-                !(
-                  parent &&
-                  parent.type === 'AssignmentPattern' &&
-                  parent.right === child
-                )
-              ) {
-                const { name } = child
-                if (node.scopeIds && node.scopeIds.has(name)) {
-                  return
-                }
-                if (name in knownIds) {
-                  knownIds[name]++
-                } else {
-                  knownIds[name] = 1
-                }
-                ;(node.scopeIds || (node.scopeIds = new Set())).add(name)
-              }
-            }
-          })
-        )
+        // also generate sub-expressions for other identifiers for better
+        // source map support. (except for property keys which are static)
+        ids.push(node as QualifiedId)
       }
     },
-    leave(node: Node & PrefixMeta, parent: Node | undefined) {
-      parent && parentStack.pop()
-      if (node !== ast.body[0].expression && node.scopeIds) {
-        node.scopeIds.forEach((id: string) => {
-          knownIds[id]--
-          if (knownIds[id] === 0) {
-            delete knownIds[id]
-          }
-        })
-      }
-    }
-  })
+    undefined,
+    parentStack,
+    knownIds,
+    // invoke on ALL identifiers
+    true
+  )
 
   // We break up the compound expression into an array of strings and sub
   // expressions (for identifiers that have been prefixed). In codegen, if
@@ -375,7 +335,7 @@ export function processExpression(
   return ret
 }
 
-function shouldPrefix(id: Identifier, parent: Node, parentStack: Node[]) {
+function canPrefix(id: Identifier) {
   // skip whitelisted globals
   if (isGloballyWhitelisted(id.name)) {
     return false
@@ -384,7 +344,7 @@ function shouldPrefix(id: Identifier, parent: Node, parentStack: Node[]) {
   if (id.name === 'require') {
     return false
   }
-  return isReferencedIdentifier(id, parent, parentStack)
+  return true
 }
 
 function stringifyExpression(exp: ExpressionNode | string): string {
index 1e560fe8dacf95431e125c3a705b3dc8f8ad8066..4ed0b2a61410427616a2609a8e711eb00ccbe23a 100644 (file)
@@ -8,7 +8,10 @@ import {
   transform,
   parserOptions,
   UNREF,
-  SimpleExpressionNode
+  SimpleExpressionNode,
+  isFunctionType,
+  isStaticProperty,
+  walkIdentifiers
 } from '@vue/compiler-dom'
 import {
   ScriptSetupTextRanges,
@@ -22,10 +25,6 @@ import {
   camelize,
   capitalize,
   generateCodeFrame,
-  isFunctionType,
-  isReferencedIdentifier,
-  isStaticProperty,
-  isStaticPropertyKey,
   makeMap
 } from '@vue/shared'
 import {
@@ -1154,9 +1153,7 @@ export function compileScript(
     }
 
     for (const node of scriptSetupAst) {
-      if (node.type !== 'ImportDeclaration') {
-        walkIdentifiers(node, onIdent, onNode)
-      }
+      walkIdentifiers(node, onIdent, onNode)
     }
   }
 
@@ -1774,116 +1771,6 @@ function genRuntimeEmits(emits: Set<string>) {
     : ``
 }
 
-function markScopeIdentifier(
-  node: Node & { scopeIds?: Set<string> },
-  child: Identifier,
-  knownIds: Record<string, number>
-) {
-  const { name } = child
-  if (node.scopeIds && node.scopeIds.has(name)) {
-    return
-  }
-  if (name in knownIds) {
-    knownIds[name]++
-  } else {
-    knownIds[name] = 1
-  }
-  ;(node.scopeIds || (node.scopeIds = new Set())).add(name)
-}
-
-/**
- * Walk an AST and find identifiers that are variable references.
- * This is largely the same logic with `transformExpressions` in compiler-core
- * but with some subtle differences as this needs to handle a wider range of
- * possible syntax.
- */
-export function walkIdentifiers(
-  root: Node,
-  onIdentifier: (node: Identifier, parent: Node, parentStack: Node[]) => void,
-  onNode?: (node: Node, parent: Node, parentStack: Node[]) => void | boolean
-) {
-  const parentStack: Node[] = []
-  const knownIds: Record<string, number> = Object.create(null)
-  ;(walk as any)(root, {
-    enter(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
-      parent && parentStack.push(parent)
-      if (
-        parent &&
-        parent.type.startsWith('TS') &&
-        parent.type !== 'TSAsExpression' &&
-        parent.type !== 'TSNonNullExpression' &&
-        parent.type !== 'TSTypeAssertion'
-      ) {
-        return this.skip()
-      }
-      if (onNode && onNode(node, parent!, parentStack) === false) {
-        return this.skip()
-      }
-      if (node.type === 'Identifier') {
-        if (
-          !knownIds[node.name] &&
-          isReferencedIdentifier(node, parent!, parentStack)
-        ) {
-          onIdentifier(node, parent!, parentStack)
-        }
-      } else if (isFunctionType(node)) {
-        // #3445
-        // should not rewrite local variables sharing a name with a top-level ref
-        if (node.body.type === 'BlockStatement') {
-          node.body.body.forEach(p => {
-            if (p.type === 'VariableDeclaration') {
-              for (const decl of p.declarations) {
-                extractIdentifiers(decl.id).forEach(id => {
-                  markScopeIdentifier(node, id, knownIds)
-                })
-              }
-            }
-          })
-        }
-        // walk function expressions and add its arguments to known identifiers
-        // so that we don't prefix them
-        node.params.forEach(p =>
-          (walk as any)(p, {
-            enter(child: Node, parent: Node) {
-              if (
-                child.type === 'Identifier' &&
-                // do not record as scope variable if is a destructured key
-                !isStaticPropertyKey(child, parent) &&
-                // do not record if this is a default value
-                // assignment of a destructured variable
-                !(
-                  parent &&
-                  parent.type === 'AssignmentPattern' &&
-                  parent.right === child
-                )
-              ) {
-                markScopeIdentifier(node, child, knownIds)
-              }
-            }
-          })
-        )
-      } else if (
-        node.type === 'ObjectProperty' &&
-        parent!.type === 'ObjectPattern'
-      ) {
-        // mark property in destructure pattern
-        ;(node as any).inPattern = true
-      }
-    },
-    leave(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
-      parent && parentStack.pop()
-      if (node.scopeIds) {
-        node.scopeIds.forEach((id: string) => {
-          knownIds[id]--
-          if (knownIds[id] === 0) {
-            delete knownIds[id]
-          }
-        })
-      }
-    }
-  })
-}
-
 function isCallOf(
   node: Node | null | undefined,
   test: string | ((id: string) => boolean)
@@ -2077,51 +1964,6 @@ function getObjectOrArrayExpressionKeys(value: Node): string[] {
   return []
 }
 
-function extractIdentifiers(
-  param: Node,
-  nodes: Identifier[] = []
-): Identifier[] {
-  switch (param.type) {
-    case 'Identifier':
-      nodes.push(param)
-      break
-
-    case 'MemberExpression':
-      let object: any = param
-      while (object.type === 'MemberExpression') {
-        object = object.object
-      }
-      nodes.push(object)
-      break
-
-    case 'ObjectPattern':
-      param.properties.forEach(prop => {
-        if (prop.type === 'RestElement') {
-          extractIdentifiers(prop.argument, nodes)
-        } else {
-          extractIdentifiers(prop.value, nodes)
-        }
-      })
-      break
-
-    case 'ArrayPattern':
-      param.elements.forEach(element => {
-        if (element) extractIdentifiers(element, nodes)
-      })
-      break
-
-    case 'RestElement':
-      extractIdentifiers(param.argument, nodes)
-      break
-
-    case 'AssignmentPattern':
-      extractIdentifiers(param.left, nodes)
-      break
-  }
-
-  return nodes
-}
-
 function toTextRange(node: Node): TextRange {
   return {
     start: node.start!,
index 9acc94466d4e4512a88a3e6031ee4384ce9d84ea..9d93832607d3bb0d94d3ba13baecf1b43f332341 100644 (file)
@@ -4,11 +4,10 @@ export { compileTemplate } from './compileTemplate'
 export { compileStyle, compileStyleAsync } from './compileStyle'
 export { compileScript } from './compileScript'
 export { rewriteDefault } from './rewriteDefault'
-export { generateCodeFrame } from '@vue/compiler-core'
+export { generateCodeFrame, walkIdentifiers } from '@vue/compiler-core'
 
 // Utilities
 export { parse as babelParse } from '@babel/parser'
-export { walkIdentifiers } from './compileScript'
 import MagicString from 'magic-string'
 export { MagicString }
 export { walk } from 'estree-walker'
diff --git a/packages/shared/src/astUtils.ts b/packages/shared/src/astUtils.ts
deleted file mode 100644 (file)
index cb01c8e..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-import {
-  Identifier,
-  Node,
-  isReferenced,
-  Function,
-  ObjectProperty
-} from '@babel/types'
-
-export function isReferencedIdentifier(
-  id: Identifier,
-  parent: Node | null,
-  parentStack: Node[]
-) {
-  if (!parent) {
-    return true
-  }
-
-  // is a special keyword but parsed as identifier
-  if (id.name === 'arguments') {
-    return false
-  }
-
-  if (isReferenced(id, parent)) {
-    return true
-  }
-
-  // babel's isReferenced check returns false for ids being assigned to, so we
-  // need to cover those cases here
-  switch (parent.type) {
-    case 'AssignmentExpression':
-    case 'AssignmentPattern':
-      return true
-    case 'ObjectPattern':
-    case 'ArrayPattern':
-      return isInDestructureAssignment(parent, parentStack)
-  }
-
-  return false
-}
-
-export function isInDestructureAssignment(
-  parent: Node,
-  parentStack: Node[]
-): boolean {
-  if (
-    parent &&
-    (parent.type === 'ObjectProperty' || parent.type === 'ArrayPattern')
-  ) {
-    let i = parentStack.length
-    while (i--) {
-      const p = parentStack[i]
-      if (p.type === 'AssignmentExpression') {
-        return true
-      } else if (p.type !== 'ObjectProperty' && !p.type.endsWith('Pattern')) {
-        break
-      }
-    }
-  }
-  return false
-}
-
-export const isFunctionType = (node: Node): node is Function => {
-  return /Function(?:Expression|Declaration)$|Method$/.test(node.type)
-}
-
-export const isStaticProperty = (node: Node): node is ObjectProperty =>
-  node &&
-  (node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
-  !node.computed
-
-export const isStaticPropertyKey = (node: Node, parent: Node) =>
-  isStaticProperty(parent) && parent.key === node
index 0bd2df43802daa3fb0f27c0f4bdad81a675ffbff..8c98b3b6cc4f255ef451b2098640b01a5c69c80b 100644 (file)
@@ -12,7 +12,6 @@ export * from './domAttrConfig'
 export * from './escapeHtml'
 export * from './looseEqual'
 export * from './toDisplayString'
-export * from './astUtils'
 
 /**
  * List of @babel/parser plugins that are used for template expression