refactor(compiler): extract shared ast transform utils

Also improve referenced identifier check using isReferenced from  @babel/types
This commit is contained in:
Evan You 2021-08-22 13:24:16 -04:00
parent 6be6c268e8
commit 62f752552a
5 changed files with 105 additions and 174 deletions

View File

@ -23,14 +23,17 @@ import {
makeMap, makeMap,
babelParserDefaultPlugins, babelParserDefaultPlugins,
hasOwn, hasOwn,
isString isString,
isReferencedIdentifier,
isInDestructureAssignment,
isStaticProperty,
isStaticPropertyKey,
isFunctionType
} from '@vue/shared' } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
import { import {
Node, Node,
Function,
Identifier, Identifier,
ObjectProperty,
AssignmentExpression, AssignmentExpression,
UpdateExpression UpdateExpression
} from '@babel/types' } from '@babel/types'
@ -279,7 +282,7 @@ export function processExpression(
ids.push(node) ids.push(node)
} }
} }
} else if (isFunction(node)) { } else if (isFunctionType(node)) {
// walk function expressions and add its arguments to known identifiers // walk function expressions and add its arguments to known identifiers
// so that we don't prefix them // so that we don't prefix them
node.params.forEach(p => node.params.forEach(p =>
@ -372,97 +375,16 @@ export function processExpression(
return ret return ret
} }
const isFunction = (node: Node): node is Function => {
return /Function(?:Expression|Declaration)$|Method$/.test(node.type)
}
const isStaticProperty = (node: Node): node is ObjectProperty =>
node &&
(node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
!node.computed
const isStaticPropertyKey = (node: Node, parent: Node) =>
isStaticProperty(parent) && parent.key === node
function shouldPrefix(id: Identifier, parent: Node, parentStack: Node[]) { function shouldPrefix(id: Identifier, parent: Node, parentStack: Node[]) {
// declaration id
if (
(parent.type === 'VariableDeclarator' ||
parent.type === 'ClassDeclaration') &&
parent.id === id
) {
return false
}
if (isFunction(parent)) {
// function decalration/expression id
if ((parent as any).id === id) {
return false
}
// params list
if (parent.params.includes(id)) {
return false
}
}
// property key
// this also covers object destructure pattern
if (isStaticPropertyKey(id, parent)) {
return false
}
// non-assignment array destructure pattern
if (
parent.type === 'ArrayPattern' &&
!isInDestructureAssignment(parent, parentStack)
) {
return false
}
// member expression property
if (
(parent.type === 'MemberExpression' ||
parent.type === 'OptionalMemberExpression') &&
parent.property === id &&
!parent.computed
) {
return false
}
// is a special keyword but parsed as identifier
if (id.name === 'arguments') {
return false
}
// skip whitelisted globals // skip whitelisted globals
if (isGloballyWhitelisted(id.name)) { if (isGloballyWhitelisted(id.name)) {
return false return false
} }
// special case for webpack compilation // special case for webpack compilation
if (id.name === 'require') { if (id.name === 'require') {
return false return false
} }
return isReferencedIdentifier(id, parent, parentStack)
return true
}
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 stringifyExpression(exp: ExpressionNode | string): string { function stringifyExpression(exp: ExpressionNode | string): string {

View File

@ -22,6 +22,10 @@ import {
camelize, camelize,
capitalize, capitalize,
generateCodeFrame, generateCodeFrame,
isFunctionType,
isReferencedIdentifier,
isStaticProperty,
isStaticPropertyKey,
makeMap makeMap
} from '@vue/shared' } from '@vue/shared'
import { import {
@ -32,7 +36,6 @@ import {
ArrayPattern, ArrayPattern,
Identifier, Identifier,
ExportSpecifier, ExportSpecifier,
Function as FunctionNode,
TSType, TSType,
TSTypeLiteral, TSTypeLiteral,
TSFunctionType, TSFunctionType,
@ -1028,7 +1031,7 @@ export function compileScript(
) { ) {
;(walk as any)(node, { ;(walk as any)(node, {
enter(child: Node, parent: Node) { enter(child: Node, parent: Node) {
if (isFunction(child)) { if (isFunctionType(child)) {
this.skip() this.skip()
} }
if (child.type === 'AwaitExpression') { if (child.type === 'AwaitExpression') {
@ -1819,11 +1822,11 @@ export function walkIdentifiers(
if (node.type === 'Identifier') { if (node.type === 'Identifier') {
if ( if (
!knownIds[node.name] && !knownIds[node.name] &&
isRefIdentifier(node, parent!, parentStack) isReferencedIdentifier(node, parent!, parentStack)
) { ) {
onIdentifier(node, parent!, parentStack) onIdentifier(node, parent!, parentStack)
} }
} else if (isFunction(node)) { } else if (isFunctionType(node)) {
// #3445 // #3445
// should not rewrite local variables sharing a name with a top-level ref // should not rewrite local variables sharing a name with a top-level ref
if (node.body.type === 'BlockStatement') { if (node.body.type === 'BlockStatement') {
@ -1881,79 +1884,6 @@ export function walkIdentifiers(
}) })
} }
function isRefIdentifier(
id: Identifier,
parent: Node | null,
parentStack: Node[]
) {
if (!parent) {
return true
}
// declaration id
if (
(parent.type === 'VariableDeclarator' ||
parent.type === 'ClassDeclaration') &&
parent.id === id
) {
return false
}
if (isFunction(parent)) {
// function decalration/expression id
if ((parent as any).id === id) {
return false
}
// params list
if (parent.params.includes(id)) {
return false
}
}
// property key
// this also covers object destructure pattern
if (isStaticPropertyKey(id, parent)) {
return false
}
// non-assignment array destructure pattern
if (
parent.type === 'ArrayPattern' &&
!isInDestructureAssignment(parent, parentStack)
) {
return false
}
// member expression property
if (
(parent.type === 'MemberExpression' ||
parent.type === 'OptionalMemberExpression') &&
parent.property === id &&
!parent.computed
) {
return false
}
// is a special keyword but parsed as identifier
if (id.name === 'arguments') {
return false
}
return true
}
const isStaticProperty = (node: Node): node is ObjectProperty =>
node &&
(node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
!node.computed
const isStaticPropertyKey = (node: Node, parent: Node) =>
isStaticProperty(parent) && parent.key === node
function isFunction(node: Node): node is FunctionNode {
return /Function(?:Expression|Declaration)$|Method$/.test(node.type)
}
function isCallOf( function isCallOf(
node: Node | null | undefined, node: Node | null | undefined,
test: string | ((id: string) => boolean) test: string | ((id: string) => boolean)

View File

@ -8,27 +8,29 @@ import {
WritableComputedRef WritableComputedRef
} from '@vue/reactivity' } from '@vue/reactivity'
export function $ref<T>(arg: T | Ref<T>): UnwrapRef<T> declare const RefMarker: unique symbol
type RefValue<T> = T & { [RefMarker]?: any }
export function $ref<T>(arg?: T | Ref<T>): RefValue<UnwrapRef<T>>
export function $ref() {} export function $ref() {}
export function $shallowRef<T>(arg: T): T { export function $shallowRef<T>(arg?: T): RefValue<T>
return arg export function $shallowRef() {}
}
declare const ComputedRefMarker: unique symbol declare const ComputedRefMarker: unique symbol
type ComputedValue<T> = T & { [ComputedRefMarker]?: any } type ComputedRefValue<T> = T & { [ComputedRefMarker]?: any }
declare const WritableComputedRefMarker: unique symbol declare const WritableComputedRefMarker: unique symbol
type WritableComputedValue<T> = T & { [WritableComputedRefMarker]?: any } type WritableComputedRefValue<T> = T & { [WritableComputedRefMarker]?: any }
export function $computed<T>( export function $computed<T>(
getter: () => T, getter: () => T,
debuggerOptions?: DebuggerOptions debuggerOptions?: DebuggerOptions
): ComputedValue<T> ): ComputedRefValue<T>
export function $computed<T>( export function $computed<T>(
options: WritableComputedOptions<T>, options: WritableComputedOptions<T>,
debuggerOptions?: DebuggerOptions debuggerOptions?: DebuggerOptions
): WritableComputedValue<T> ): WritableComputedRefValue<T>
export function $computed() {} export function $computed() {}
export function $fromRefs<T>(source: T): ShallowUnwrapRef<T> export function $fromRefs<T>(source: T): ShallowUnwrapRef<T>
@ -36,9 +38,13 @@ export function $fromRefs() {
return null as any return null as any
} }
export function $raw<T>(value: ComputedValue<T>): ComputedRef<T> export function $raw<T extends ComputedRefValue<any>>(
export function $raw<T>(value: WritableComputedValue<T>): WritableComputedRef<T> value: T
export function $raw<T>(value: T): Ref<T> ): T extends ComputedRefValue<infer V> ? ComputedRef<V> : never
export function $raw<T>(
value: WritableComputedRefValue<T>
): WritableComputedRef<T>
export function $raw<T>(value: RefValue<T>): Ref<T>
export function $raw() { export function $raw() {
return null as any return null as any
} }

View File

@ -0,0 +1,72 @@
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

View File

@ -12,6 +12,7 @@ export * from './domAttrConfig'
export * from './escapeHtml' export * from './escapeHtml'
export * from './looseEqual' export * from './looseEqual'
export * from './toDisplayString' export * from './toDisplayString'
export * from './astUtils'
/** /**
* List of @babel/parser plugins that are used for template expression * List of @babel/parser plugins that are used for template expression