feat(compiler-core): switch to @babel/parser for expression parsing
This enables default support for parsing bigInt, optional chaining
and nullish coalescing, and also adds the `expressionPlugins`
compiler option for enabling additional parsing plugins listed at
https://babeljs.io/docs/en/next/babel-parser#plugins.
This commit is contained in:
@@ -16,9 +16,13 @@ export function defaultOnError(error: CompilerError) {
|
||||
export function createCompilerError<T extends number>(
|
||||
code: T,
|
||||
loc?: SourceLocation,
|
||||
messages?: { [code: number]: string }
|
||||
messages?: { [code: number]: string },
|
||||
additionalMessage?: string
|
||||
): T extends ErrorCodes ? CoreCompilerError : CompilerError {
|
||||
const msg = __DEV__ || !__BROWSER__ ? (messages || errorMessages)[code] : code
|
||||
const msg =
|
||||
__DEV__ || !__BROWSER__
|
||||
? (messages || errorMessages)[code] + (additionalMessage || ``)
|
||||
: code
|
||||
const error = new SyntaxError(String(msg)) as CompilerError
|
||||
error.code = code
|
||||
error.loc = loc
|
||||
@@ -174,7 +178,7 @@ export const errorMessages: { [code: number]: string } = {
|
||||
[ErrorCodes.X_V_MODEL_NO_EXPRESSION]: `v-model is missing expression.`,
|
||||
[ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION]: `v-model value must be a valid JavaScript member expression.`,
|
||||
[ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE]: `v-model cannot be used on v-for or v-slot scope variables because they are not writable.`,
|
||||
[ErrorCodes.X_INVALID_EXPRESSION]: `Invalid JavaScript expression.`,
|
||||
[ErrorCodes.X_INVALID_EXPRESSION]: `Error parsing JavaScript expression: `,
|
||||
[ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN]: `<KeepAlive> expects exactly one child component.`,
|
||||
|
||||
// generic errors
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
DirectiveTransform,
|
||||
TransformContext
|
||||
} from './transform'
|
||||
import { ParserPlugin } from '@babel/parser'
|
||||
|
||||
export interface ParserOptions {
|
||||
isVoidTag?: (tag: string) => boolean // e.g. img, br, hr
|
||||
@@ -61,6 +62,9 @@ export interface TransformOptions {
|
||||
// analysis to determine if a handler is safe to cache.
|
||||
// - Default: false
|
||||
cacheHandlers?: boolean
|
||||
// a list of parser plugins to enable for @babel/parser
|
||||
// https://babeljs.io/docs/en/next/babel-parser#plugins
|
||||
expressionPlugins?: ParserPlugin[]
|
||||
// SFC scoped styles ID
|
||||
scopeId?: string | null
|
||||
ssr?: boolean
|
||||
|
||||
@@ -117,6 +117,7 @@ export function createTransformContext(
|
||||
directiveTransforms = {},
|
||||
transformHoist = null,
|
||||
isBuiltInComponent = NOOP,
|
||||
expressionPlugins = [],
|
||||
scopeId = null,
|
||||
ssr = false,
|
||||
onError = defaultOnError
|
||||
@@ -131,6 +132,7 @@ export function createTransformContext(
|
||||
directiveTransforms,
|
||||
transformHoist,
|
||||
isBuiltInComponent,
|
||||
expressionPlugins,
|
||||
scopeId,
|
||||
ssr,
|
||||
onError,
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
CompoundExpressionNode,
|
||||
createCompoundExpression
|
||||
} from '../ast'
|
||||
import { Node, Function, Identifier, Property } from 'estree'
|
||||
import {
|
||||
advancePositionWithClone,
|
||||
isSimpleIdentifier,
|
||||
@@ -25,6 +24,7 @@ import {
|
||||
} from '../utils'
|
||||
import { isGloballyWhitelisted, makeMap } from '@vue/shared'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
import { Node, Function, Identifier, ObjectProperty } from '@babel/types'
|
||||
|
||||
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
|
||||
|
||||
@@ -117,22 +117,39 @@ export function processExpression(
|
||||
? ` ${rawExp} `
|
||||
: `(${rawExp})${asParams ? `=>{}` : ``}`
|
||||
try {
|
||||
ast = parseJS(source, { ranges: true })
|
||||
ast = parseJS(source, {
|
||||
plugins: [
|
||||
...context.expressionPlugins,
|
||||
// by default we enable proposals slated for ES2020.
|
||||
// full list at https://babeljs.io/docs/en/next/babel-parser#plugins
|
||||
// this will need to be updated as the spec moves forward.
|
||||
'bigInt',
|
||||
'optionalChaining',
|
||||
'nullishCoalescingOperator'
|
||||
]
|
||||
}).program
|
||||
} catch (e) {
|
||||
context.onError(
|
||||
createCompilerError(ErrorCodes.X_INVALID_EXPRESSION, node.loc)
|
||||
createCompilerError(
|
||||
ErrorCodes.X_INVALID_EXPRESSION,
|
||||
node.loc,
|
||||
undefined,
|
||||
e.message
|
||||
)
|
||||
)
|
||||
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)
|
||||
|
||||
// walk the AST and look for identifiers that need to be prefixed with `_ctx.`.
|
||||
walkJS(ast, {
|
||||
enter(node: Node & PrefixMeta, parent) {
|
||||
if (node.type === 'Identifier') {
|
||||
if (!ids.includes(node)) {
|
||||
if (!isDuplicate(node)) {
|
||||
const needPrefix = shouldPrefix(node, parent)
|
||||
if (!knownIds[node.name] && needPrefix) {
|
||||
if (isPropertyShorthand(node, parent)) {
|
||||
@@ -246,17 +263,20 @@ export function processExpression(
|
||||
const isFunction = (node: Node): node is Function =>
|
||||
/Function(Expression|Declaration)$/.test(node.type)
|
||||
|
||||
const isPropertyKey = (node: Node, parent: Node) =>
|
||||
parent &&
|
||||
parent.type === 'Property' &&
|
||||
parent.key === node &&
|
||||
!parent.computed
|
||||
const isStaticProperty = (node: Node): node is ObjectProperty =>
|
||||
node && node.type === 'ObjectProperty' && !node.computed
|
||||
|
||||
const isPropertyShorthand = (node: Node, parent: Node) =>
|
||||
isPropertyKey(node, parent) && (parent as Property).value === node
|
||||
const isPropertyShorthand = (node: Node, parent: Node) => {
|
||||
return (
|
||||
isStaticProperty(parent) &&
|
||||
parent.value === node &&
|
||||
parent.key.type === 'Identifier' &&
|
||||
parent.key.name === (node as Identifier).name
|
||||
)
|
||||
}
|
||||
|
||||
const isStaticPropertyKey = (node: Node, parent: Node) =>
|
||||
isPropertyKey(node, parent) && (parent as Property).value !== node
|
||||
isStaticProperty(parent) && parent.key === node
|
||||
|
||||
function shouldPrefix(identifier: Identifier, parent: Node) {
|
||||
if (
|
||||
@@ -271,7 +291,8 @@ function shouldPrefix(identifier: Identifier, parent: Node) {
|
||||
!isStaticPropertyKey(identifier, parent) &&
|
||||
// not a property of a MemberExpression
|
||||
!(
|
||||
parent.type === 'MemberExpression' &&
|
||||
(parent.type === 'MemberExpression' ||
|
||||
parent.type === 'OptionalMemberExpression') &&
|
||||
parent.property === identifier &&
|
||||
!parent.computed
|
||||
) &&
|
||||
|
||||
@@ -22,8 +22,6 @@ import {
|
||||
InterpolationNode,
|
||||
VNodeCall
|
||||
} from './ast'
|
||||
import { parse } from 'acorn'
|
||||
import { walk } from 'estree-walker'
|
||||
import { TransformContext } from './transform'
|
||||
import {
|
||||
MERGE_PROPS,
|
||||
@@ -33,6 +31,8 @@ import {
|
||||
BASE_TRANSITION
|
||||
} from './runtimeHelpers'
|
||||
import { isString, isFunction, isObject, hyphenate } from '@vue/shared'
|
||||
import { parse } from '@babel/parser'
|
||||
import { Node } from '@babel/types'
|
||||
|
||||
export const isBuiltInType = (tag: string, expected: string): boolean =>
|
||||
tag === expected || tag === hyphenate(expected)
|
||||
@@ -53,7 +53,7 @@ export function isCoreComponent(tag: string): symbol | void {
|
||||
// lazy require dependencies so that they don't end up in rollup's dep graph
|
||||
// and thus can be tree-shaken in browser builds.
|
||||
let _parse: typeof parse
|
||||
let _walk: typeof walk
|
||||
let _walk: any
|
||||
|
||||
export function loadDep(name: string) {
|
||||
if (!__BROWSER__ && typeof process !== 'undefined' && isFunction(require)) {
|
||||
@@ -70,11 +70,18 @@ export const parseJS: typeof parse = (code, options) => {
|
||||
!__BROWSER__,
|
||||
`Expression AST analysis can only be performed in non-browser builds.`
|
||||
)
|
||||
const parse = _parse || (_parse = loadDep('acorn').parse)
|
||||
return parse(code, options)
|
||||
if (!_parse) {
|
||||
_parse = loadDep('@babel/parser').parse
|
||||
}
|
||||
return _parse(code, options)
|
||||
}
|
||||
|
||||
export const walkJS: typeof walk = (ast, walker) => {
|
||||
interface Walker {
|
||||
enter?(node: Node, parent: Node): void
|
||||
leave?(node: Node): void
|
||||
}
|
||||
|
||||
export const walkJS = (ast: Node, walker: Walker) => {
|
||||
assert(
|
||||
!__BROWSER__,
|
||||
`Expression AST analysis can only be performed in non-browser builds.`
|
||||
|
||||
Reference in New Issue
Block a user