},
"devDependencies": {
"@microsoft/api-extractor": "^7.3.9",
+ "@rollup/plugin-commonjs": "^11.0.2",
"@rollup/plugin-json": "^4.0.0",
+ "@rollup/plugin-node-resolve": "^7.1.1",
"@rollup/plugin-replace": "^2.2.1",
"@types/jest": "^24.0.21",
"@types/puppeteer": "^2.0.0",
const onError = jest.fn()
parseWithExpressionTransform(`{{ a( }}`, { onError })
expect(onError.mock.calls[0][0].message).toMatch(
- `Invalid JavaScript expression.`
+ `Error parsing JavaScript expression: Unexpected token`
)
})
+
+ describe('ES Proposals support', () => {
+ test('bigInt', () => {
+ const node = parseWithExpressionTransform(
+ `{{ 13000n }}`
+ ) as InterpolationNode
+ expect(node.content).toMatchObject({
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `13000n`,
+ isStatic: false,
+ isConstant: true
+ })
+ })
+
+ test('nullish colescing', () => {
+ const node = parseWithExpressionTransform(
+ `{{ a ?? b }}`
+ ) as InterpolationNode
+ expect(node.content).toMatchObject({
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [{ content: `_ctx.a` }, ` ?? `, { content: `_ctx.b` }]
+ })
+ })
+
+ test('optional chaining', () => {
+ const node = parseWithExpressionTransform(
+ `{{ a?.b?.c }}`
+ ) as InterpolationNode
+ expect(node.content).toMatchObject({
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [
+ { content: `_ctx.a` },
+ `?.`,
+ { content: `b` },
+ `?.`,
+ { content: `c` }
+ ]
+ })
+ })
+
+ test('Enabling additional plugins', () => {
+ // enabling pipeline operator to replace filters:
+ const node = parseWithExpressionTransform(`{{ a |> uppercase }}`, {
+ expressionPlugins: [
+ [
+ 'pipelineOperator',
+ {
+ proposal: 'minimal'
+ }
+ ]
+ ]
+ }) as InterpolationNode
+ expect(node.content).toMatchObject({
+ type: NodeTypes.COMPOUND_EXPRESSION,
+ children: [{ content: `_ctx.a` }, ` |> `, { content: `_ctx.uppercase` }]
+ })
+ })
+ })
})
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/compiler-core#readme",
"dependencies": {
- "acorn": "^7.1.0",
+ "@babel/parser": "^7.8.6",
+ "@babel/types": "^7.8.6",
"estree-walker": "^0.8.1",
"source-map": "^0.6.1"
}
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
[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
DirectiveTransform,
TransformContext
} from './transform'
+import { ParserPlugin } from '@babel/parser'
export interface ParserOptions {
isVoidTag?: (tag: string) => boolean // e.g. img, br, hr
// 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
directiveTransforms = {},
transformHoist = null,
isBuiltInComponent = NOOP,
+ expressionPlugins = [],
scopeId = null,
ssr = false,
onError = defaultOnError
directiveTransforms,
transformHoist,
isBuiltInComponent,
+ expressionPlugins,
scopeId,
ssr,
onError,
CompoundExpressionNode,
createCompoundExpression
} from '../ast'
-import { Node, Function, Identifier, Property } from 'estree'
import {
advancePositionWithClone,
isSimpleIdentifier,
} 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')
? ` ${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)) {
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 (
!isStaticPropertyKey(identifier, parent) &&
// not a property of a MemberExpression
!(
- parent.type === 'MemberExpression' &&
+ (parent.type === 'MemberExpression' ||
+ parent.type === 'OptionalMemberExpression') &&
parent.property === identifier &&
!parent.computed
) &&
InterpolationNode,
VNodeCall
} from './ast'
-import { parse } from 'acorn'
-import { walk } from 'estree-walker'
import { TransformContext } from './transform'
import {
MERGE_PROPS,
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)
// 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)) {
!__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)
+}
+
+interface Walker {
+ enter?(node: Node, parent: Node): void
+ leave?(node: Node): void
}
-export const walkJS: typeof walk = (ast, walker) => {
+export const walkJS = (ast: Node, walker: Walker) => {
assert(
!__BROWSER__,
`Expression AST analysis can only be performed in non-browser builds.`
<div id="source" class="editor"></div>
<div id="output" class="editor"></div>
-<script src="https://unpkg.com/acorn@7.1.0/dist/acorn.js"></script>
<script src="https://unpkg.com/estree-walker@0.8.1/dist/estree-walker.umd.js"></script>
<script src="https://unpkg.com/source-map@0.6.1/dist/source-map.js"></script>
<script src="https://unpkg.com/monaco-editor@0.18.1/min/vs/loader.js"></script>
-<script src="./dist/template-explorer.global.js"></script>
<script>
window._deps = {
- acorn,
'estree-walker': estreeWalker,
'source-map': sourceMap
}
}
})
</script>
+<script src="./dist/template-explorer.global.js"></script>
<script>
require(['vs/editor/editor.main'], init /* injected by build */)
</script>
<div id="source" class="editor"></div>
<div id="output" class="editor"></div>
-<script src="../../node_modules/acorn/dist/acorn.js"></script>
<script src="../../node_modules/estree-walker/dist/estree-walker.umd.js"></script>
<script src="../../node_modules/source-map/dist/source-map.js"></script>
<script src="../../node_modules/monaco-editor/min/vs/loader.js"></script>
-<script src="./dist/template-explorer.global.js"></script>
<script>
window._deps = {
- acorn,
+ // @babel/parser is injected by the bundle
'estree-walker': estreeWalker,
'source-map': sourceMap
}
}
})
</script>
+<script src="./dist/template-explorer.global.js"></script>
<script>
require(['vs/editor/editor.main'], init /* injected by build */)
</script>
import { compilerOptions, initOptions, ssrMode } from './options'
import { watchEffect } from '@vue/runtime-dom'
import { SourceMapConsumer } from 'source-map'
+import { parse } from '@babel/parser'
+
+window._deps['@babel/parser'] = { parse }
declare global {
interface Window {
? []
: knownExternals.concat(Object.keys(pkg.dependencies || []))
+ const nodePlugins = packageOptions.enableNonBrowserBranches
+ ? [
+ require('@rollup/plugin-node-resolve')(),
+ require('@rollup/plugin-commonjs')()
+ ]
+ : []
+
return {
input: resolve(entryFile),
// Global and Browser ESM builds inlines everything so that they can be
isGlobalBuild,
isNodeBuild
),
+ ...nodePlugins,
...plugins
],
output,
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.5.5.tgz#02f077ac8817d3df4a832ef59de67565e71cca4b"
integrity sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==
+"@babel/parser@^7.8.6":
+ version "7.8.6"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.6.tgz#ba5c9910cddb77685a008e3c587af8d27b67962c"
+ integrity sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g==
+
"@babel/plugin-syntax-object-rest-spread@^7.0.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e"
lodash "^4.17.13"
to-fast-properties "^2.0.0"
+"@babel/types@^7.8.6":
+ version "7.8.6"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.6.tgz#629ecc33c2557fcde7126e58053127afdb3e6d01"
+ integrity sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==
+ dependencies:
+ esutils "^2.0.2"
+ lodash "^4.17.13"
+ to-fast-properties "^2.0.0"
+
"@cnakazawa/watch@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef"
"@nodelib/fs.scandir" "2.1.1"
fastq "^1.6.0"
+"@rollup/plugin-commonjs@^11.0.2":
+ version "11.0.2"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-11.0.2.tgz#837cc6950752327cb90177b608f0928a4e60b582"
+ integrity sha512-MPYGZr0qdbV5zZj8/2AuomVpnRVXRU5XKXb3HVniwRoRCreGlf5kOE081isNWeiLIi6IYkwTX9zE0/c7V8g81g==
+ dependencies:
+ "@rollup/pluginutils" "^3.0.0"
+ estree-walker "^1.0.1"
+ is-reference "^1.1.2"
+ magic-string "^0.25.2"
+ resolve "^1.11.0"
+
"@rollup/plugin-json@^4.0.0":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.0.2.tgz#482185ee36ac7dd21c346e2dbcc22ffed0c6f2d6"
dependencies:
"@rollup/pluginutils" "^3.0.4"
+"@rollup/plugin-node-resolve@^7.1.1":
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.1.tgz#8c6e59c4b28baf9d223028d0e450e06a485bb2b7"
+ integrity sha512-14ddhD7TnemeHE97a4rLOhobfYvUVcaYuqTnL8Ti7Jxi9V9Jr5LY7Gko4HZ5k4h4vqQM0gBQt6tsp9xXW94WPA==
+ dependencies:
+ "@rollup/pluginutils" "^3.0.6"
+ "@types/resolve" "0.0.8"
+ builtin-modules "^3.1.0"
+ is-module "^1.0.0"
+ resolve "^1.14.2"
+
"@rollup/plugin-replace@^2.2.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-2.3.1.tgz#16fb0563628f9e6c6ef9e05d48d3608916d466f5"
"@rollup/pluginutils" "^3.0.4"
magic-string "^0.25.5"
-"@rollup/pluginutils@^3.0.4":
+"@rollup/pluginutils@^3.0.0", "@rollup/pluginutils@^3.0.4", "@rollup/pluginutils@^3.0.6":
version "3.0.8"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.0.8.tgz#4e94d128d94b90699e517ef045422960d18c8fde"
integrity sha512-rYGeAc4sxcZ+kPG/Tw4/fwJODC3IXHYDH4qusdN/b6aLw5LPUbzpecYbEJh4sVQGPFJxd2dBU4kc1H3oy9/bnw==
"@types/bluebird" "*"
"@types/node" "*"
-"@types/estree@*":
+"@types/estree@*", "@types/estree@0.0.39":
version "0.0.39"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
dependencies:
"@types/node" "*"
+"@types/resolve@0.0.8":
+ version "0.0.8"
+ resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194"
+ integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==
+ dependencies:
+ "@types/node" "*"
+
"@types/stack-utils@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
+builtin-modules@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484"
+ integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==
+
bytes@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
global-dirs "^0.1.0"
is-path-inside "^1.0.0"
+is-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
+ integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=
+
is-npm@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4"
resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=
+is-reference@^1.1.2:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.1.4.tgz#3f95849886ddb70256a3e6d062b1a68c13c51427"
+ integrity sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==
+ dependencies:
+ "@types/estree" "0.0.39"
+
is-regex@^1.0.3, is-regex@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
dependencies:
yallist "^3.0.2"
-magic-string@^0.25.5:
+magic-string@^0.25.2, magic-string@^0.25.5:
version "0.25.6"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.6.tgz#5586387d1242f919c6d223579cc938bf1420795e"
integrity sha512-3a5LOMSGoCTH5rbqobC2HuDNRtE2glHZ8J7pK+QZYppyWA36yuNpsX994rIY2nCuyP7CZYy7lQq/X2jygiZ89g==
dependencies:
path-parse "^1.0.6"
+resolve@^1.11.0, resolve@^1.14.2:
+ version "1.15.1"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8"
+ integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==
+ dependencies:
+ path-parse "^1.0.6"
+
restore-cursor@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"