style: update format & lint config (#9162)

Co-authored-by: 丶远方 <yangpanteng@gmail.com>
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
Co-authored-by: Guo Xingjun <99574369+Plumbiu@users.noreply.github.com>
This commit is contained in:
三咲智子 Kevin Deng 2023-12-26 19:39:47 +08:00 committed by GitHub
parent baf0b7664d
commit bfe6b459d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
464 changed files with 12844 additions and 12385 deletions

View File

@ -1,12 +1,11 @@
/* eslint-disable no-restricted-globals */ const { builtinModules } = require('node:module')
const DOMGlobals = ['window', 'document'] const DOMGlobals = ['window', 'document']
const NodeGlobals = ['module', 'require'] const NodeGlobals = ['module', 'require']
const banConstEnum = { const banConstEnum = {
selector: 'TSEnumDeclaration[const=true]', selector: 'TSEnumDeclaration[const=true]',
message: message:
'Please use non-const enums. This project automatically inlines enums.' 'Please use non-const enums. This project automatically inlines enums.',
} }
/** /**
@ -15,9 +14,9 @@ const banConstEnum = {
module.exports = { module.exports = {
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
parserOptions: { parserOptions: {
sourceType: 'module' sourceType: 'module',
}, },
plugins: ['jest'], plugins: ['jest', 'import', '@typescript-eslint'],
rules: { rules: {
'no-debugger': 'error', 'no-debugger': 'error',
// most of the codebase are expected to be env agnostic // most of the codebase are expected to be env agnostic
@ -32,8 +31,27 @@ module.exports = {
// tsc compiles assignment spread into Object.assign() calls, but esbuild // tsc compiles assignment spread into Object.assign() calls, but esbuild
// still generates verbose helpers, so spread assignment is also prohiboted // still generates verbose helpers, so spread assignment is also prohiboted
'ObjectExpression > SpreadElement', 'ObjectExpression > SpreadElement',
'AwaitExpression' 'AwaitExpression',
] ],
'sort-imports': ['error', { ignoreDeclarationSort: true }],
'import/no-nodejs-modules': [
'error',
{ allow: builtinModules.map(mod => `node:${mod}`) },
],
// This rule enforces the preference for using '@ts-expect-error' comments in TypeScript
// code to indicate intentional type errors, improving code clarity and maintainability.
'@typescript-eslint/prefer-ts-expect-error': 'error',
// Enforce the use of 'import type' for importing types
'@typescript-eslint/consistent-type-imports': [
'error',
{
fixStyle: 'inline-type-imports',
disallowTypeAnnotations: false,
},
],
// Enforce the use of top-level import type qualifier when an import only has specifiers with inline type qualifiers
'@typescript-eslint/no-import-type-side-effects': 'error',
}, },
overrides: [ overrides: [
// tests, no restrictions (runs in Node / jest with jsdom) // tests, no restrictions (runs in Node / jest with jsdom)
@ -43,54 +61,66 @@ module.exports = {
'no-restricted-globals': 'off', 'no-restricted-globals': 'off',
'no-restricted-syntax': 'off', 'no-restricted-syntax': 'off',
'jest/no-disabled-tests': 'error', 'jest/no-disabled-tests': 'error',
'jest/no-focused-tests': 'error' 'jest/no-focused-tests': 'error',
} },
}, },
// shared, may be used in any env // shared, may be used in any env
{ {
files: ['packages/shared/**'], files: ['packages/shared/**', '.eslintrc.cjs'],
rules: { rules: {
'no-restricted-globals': 'off' 'no-restricted-globals': 'off',
} },
}, },
// Packages targeting DOM // Packages targeting DOM
{ {
files: ['packages/{vue,vue-compat,runtime-dom}/**'], files: ['packages/{vue,vue-compat,runtime-dom}/**'],
rules: { rules: {
'no-restricted-globals': ['error', ...NodeGlobals] 'no-restricted-globals': ['error', ...NodeGlobals],
} },
}, },
// Packages targeting Node // Packages targeting Node
{ {
files: ['packages/{compiler-sfc,compiler-ssr,server-renderer}/**'], files: ['packages/{compiler-sfc,compiler-ssr,server-renderer}/**'],
rules: { rules: {
'no-restricted-globals': ['error', ...DOMGlobals], 'no-restricted-globals': ['error', ...DOMGlobals],
'no-restricted-syntax': ['error', banConstEnum] 'no-restricted-syntax': ['error', banConstEnum],
} },
}, },
// Private package, browser only + no syntax restrictions // Private package, browser only + no syntax restrictions
{ {
files: ['packages/template-explorer/**', 'packages/sfc-playground/**'], files: ['packages/template-explorer/**', 'packages/sfc-playground/**'],
rules: { rules: {
'no-restricted-globals': ['error', ...NodeGlobals], 'no-restricted-globals': ['error', ...NodeGlobals],
'no-restricted-syntax': ['error', banConstEnum] 'no-restricted-syntax': ['error', banConstEnum],
} },
}, },
// JavaScript files // JavaScript files
{ {
files: ['*.js', '*.cjs'], files: ['*.js', '*.cjs'],
rules: { rules: {
// We only do `no-unused-vars` checks for js files, TS files are checked by TypeScript itself. // We only do `no-unused-vars` checks for js files, TS files are checked by TypeScript itself.
'no-unused-vars': ['error', { vars: 'all', args: 'none' }] 'no-unused-vars': ['error', { vars: 'all', args: 'none' }],
} },
}, },
// Node scripts // Node scripts
{ {
files: ['scripts/**', '*.{js,ts}', 'packages/**/index.js'], files: [
'scripts/**',
'./*.{js,ts}',
'packages/*/*.js',
'packages/vue/*/*.js',
],
rules: { rules: {
'no-restricted-globals': 'off', 'no-restricted-globals': 'off',
'no-restricted-syntax': ['error', banConstEnum] 'no-restricted-syntax': ['error', banConstEnum],
} },
} },
] // Import nodejs modules in compiler-sfc
{
files: ['packages/compiler-sfc/src/**'],
rules: {
'import/no-nodejs-modules': ['error', { allow: builtinModules }],
},
},
],
} }

View File

@ -7,35 +7,35 @@
packageRules: [ packageRules: [
{ {
depTypeList: ['peerDependencies'], depTypeList: ['peerDependencies'],
enabled: false enabled: false,
}, },
{ {
groupName: 'test', groupName: 'test',
matchPackageNames: ['vitest', 'jsdom', 'puppeteer'], matchPackageNames: ['vitest', 'jsdom', 'puppeteer'],
matchPackagePrefixes: ['@vitest'] matchPackagePrefixes: ['@vitest'],
}, },
{ {
groupName: 'playground', groupName: 'playground',
matchFileNames: [ matchFileNames: [
'packages/sfc-playground/package.json', 'packages/sfc-playground/package.json',
'packages/template-explorer/package.json' 'packages/template-explorer/package.json',
] ],
}, },
{ {
groupName: 'compiler', groupName: 'compiler',
matchPackageNames: ['magic-string'], matchPackageNames: ['magic-string'],
matchPackagePrefixes: ['@babel', 'postcss'] matchPackagePrefixes: ['@babel', 'postcss'],
}, },
{ {
groupName: 'build', groupName: 'build',
matchPackageNames: ['vite', 'terser'], matchPackageNames: ['vite', 'terser'],
matchPackagePrefixes: ['rollup', 'esbuild', '@rollup', '@vitejs'] matchPackagePrefixes: ['rollup', 'esbuild', '@rollup', '@vitejs'],
}, },
{ {
groupName: 'lint', groupName: 'lint',
matchPackageNames: ['simple-git-hooks', 'lint-staged'], matchPackageNames: ['simple-git-hooks', 'lint-staged'],
matchPackagePrefixes: ['@typescript-eslint', 'eslint', 'prettier'] matchPackagePrefixes: ['@typescript-eslint', 'eslint', 'prettier'],
} },
], ],
ignoreDeps: [ ignoreDeps: [
'vue', 'vue',
@ -45,6 +45,6 @@
'typescript', 'typescript',
// ESM only // ESM only
'estree-walker' 'estree-walker',
] ],
} }

View File

@ -1 +1,4 @@
dist dist
*.md
*.html
pnpm-lock.yaml

View File

@ -1,5 +1,5 @@
semi: false {
singleQuote: true "semi": false,
printWidth: 80 "singleQuote": true,
trailingComma: 'none' "arrowParens": "avoid"
arrowParens: 'avoid' }

2
.vscode/launch.json vendored
View File

@ -21,7 +21,7 @@
"console": "integratedTerminal", "console": "integratedTerminal",
"sourceMaps": true, "sourceMaps": true,
"windows": { "windows": {
"program": "${workspaceFolder}/node_modules/jest/bin/jest", "program": "${workspaceFolder}/node_modules/jest/bin/jest"
} }
} }
] ]

View File

@ -13,9 +13,9 @@
"size-esm-runtime": "node scripts/build.js vue -f esm-bundler-runtime", "size-esm-runtime": "node scripts/build.js vue -f esm-bundler-runtime",
"size-esm": "node scripts/build.js runtime-dom runtime-core reactivity shared -f esm-bundler", "size-esm": "node scripts/build.js runtime-dom runtime-core reactivity shared -f esm-bundler",
"check": "tsc --incremental --noEmit", "check": "tsc --incremental --noEmit",
"lint": "eslint --cache --ext .ts packages/*/{src,__tests__}/**.ts", "lint": "eslint --cache --ext .js,.ts,.tsx .",
"format": "prettier --write --cache \"**/*.[tj]s?(x)\"", "format": "prettier --write --cache .",
"format-check": "prettier --check --cache \"**/*.[tj]s?(x)\"", "format-check": "prettier --check --cache .",
"test": "vitest", "test": "vitest",
"test-unit": "vitest -c vitest.unit.config.ts", "test-unit": "vitest -c vitest.unit.config.ts",
"test-e2e": "node scripts/build.js vue -f global -d && vitest -c vitest.e2e.config.ts", "test-e2e": "node scripts/build.js vue -f global -d && vitest -c vitest.e2e.config.ts",
@ -72,6 +72,7 @@
"@types/minimist": "^1.2.5", "@types/minimist": "^1.2.5",
"@types/node": "^20.10.5", "@types/node": "^20.10.5",
"@types/semver": "^7.5.5", "@types/semver": "^7.5.5",
"@typescript-eslint/eslint-plugin": "^6.16.0",
"@typescript-eslint/parser": "^6.15.0", "@typescript-eslint/parser": "^6.15.0",
"@vitest/coverage-istanbul": "^1.1.0", "@vitest/coverage-istanbul": "^1.1.0",
"@vue/consolidate": "0.17.3", "@vue/consolidate": "0.17.3",
@ -81,6 +82,7 @@
"esbuild-plugin-polyfill-node": "^0.3.0", "esbuild-plugin-polyfill-node": "^0.3.0",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-define-config": "^1.24.1", "eslint-define-config": "^1.24.1",
"eslint-plugin-import": "npm:eslint-plugin-i@^2.29.1",
"eslint-plugin-jest": "^27.6.0", "eslint-plugin-jest": "^27.6.0",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"execa": "^8.0.1", "execa": "^8.0.1",

View File

@ -1,38 +1,38 @@
import { import {
locStub, ConstantTypes,
generate, type DirectiveArguments,
type ForCodegenNode,
type IfConditionalExpression,
NodeTypes, NodeTypes,
RootNode, type RootNode,
createSimpleExpression, type VNodeCall,
createArrayExpression,
createAssignmentExpression,
createBlockStatement,
createCacheExpression,
createCallExpression,
createCompoundExpression,
createConditionalExpression,
createIfStatement,
createInterpolation,
createObjectExpression, createObjectExpression,
createObjectProperty, createObjectProperty,
createArrayExpression, createSimpleExpression,
createCompoundExpression,
createInterpolation,
createCallExpression,
createConditionalExpression,
ForCodegenNode,
createCacheExpression,
createTemplateLiteral, createTemplateLiteral,
createBlockStatement,
createIfStatement,
createAssignmentExpression,
IfConditionalExpression,
createVNodeCall, createVNodeCall,
VNodeCall, generate,
DirectiveArguments, locStub,
ConstantTypes
} from '../src' } from '../src'
import { import {
CREATE_VNODE,
TO_DISPLAY_STRING,
RESOLVE_DIRECTIVE,
helperNameMap,
RESOLVE_COMPONENT,
CREATE_COMMENT, CREATE_COMMENT,
CREATE_ELEMENT_VNODE,
CREATE_VNODE,
FRAGMENT, FRAGMENT,
RENDER_LIST, RENDER_LIST,
CREATE_ELEMENT_VNODE RESOLVE_COMPONENT,
RESOLVE_DIRECTIVE,
TO_DISPLAY_STRING,
helperNameMap,
} from '../src/runtimeHelpers' } from '../src/runtimeHelpers'
import { createElementWithCodegen, genFlagText } from './testUtils' import { createElementWithCodegen, genFlagText } from './testUtils'
import { PatchFlags } from '@vue/shared' import { PatchFlags } from '@vue/shared'
@ -51,59 +51,59 @@ function createRoot(options: Partial<RootNode> = {}): RootNode {
temps: 0, temps: 0,
codegenNode: createSimpleExpression(`null`, false), codegenNode: createSimpleExpression(`null`, false),
loc: locStub, loc: locStub,
...options ...options,
} }
} }
describe('compiler: codegen', () => { describe('compiler: codegen', () => {
test('module mode preamble', () => { test('module mode preamble', () => {
const root = createRoot({ const root = createRoot({
helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE]) helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE]),
}) })
const { code } = generate(root, { mode: 'module' }) const { code } = generate(root, { mode: 'module' })
expect(code).toMatch( expect(code).toMatch(
`import { ${helperNameMap[CREATE_VNODE]} as _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]} as _${helperNameMap[RESOLVE_DIRECTIVE]} } from "vue"` `import { ${helperNameMap[CREATE_VNODE]} as _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]} as _${helperNameMap[RESOLVE_DIRECTIVE]} } from "vue"`,
) )
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
test('module mode preamble w/ optimizeImports: true', () => { test('module mode preamble w/ optimizeImports: true', () => {
const root = createRoot({ const root = createRoot({
helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE]) helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE]),
}) })
const { code } = generate(root, { mode: 'module', optimizeImports: true }) const { code } = generate(root, { mode: 'module', optimizeImports: true })
expect(code).toMatch( expect(code).toMatch(
`import { ${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]} } from "vue"` `import { ${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]} } from "vue"`,
) )
expect(code).toMatch( expect(code).toMatch(
`const _${helperNameMap[CREATE_VNODE]} = ${helperNameMap[CREATE_VNODE]}, _${helperNameMap[RESOLVE_DIRECTIVE]} = ${helperNameMap[RESOLVE_DIRECTIVE]}` `const _${helperNameMap[CREATE_VNODE]} = ${helperNameMap[CREATE_VNODE]}, _${helperNameMap[RESOLVE_DIRECTIVE]} = ${helperNameMap[RESOLVE_DIRECTIVE]}`,
) )
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
test('function mode preamble', () => { test('function mode preamble', () => {
const root = createRoot({ const root = createRoot({
helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE]) helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE]),
}) })
const { code } = generate(root, { mode: 'function' }) const { code } = generate(root, { mode: 'function' })
expect(code).toMatch(`const _Vue = Vue`) expect(code).toMatch(`const _Vue = Vue`)
expect(code).toMatch( expect(code).toMatch(
`const { ${helperNameMap[CREATE_VNODE]}: _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]}: _${helperNameMap[RESOLVE_DIRECTIVE]} } = _Vue` `const { ${helperNameMap[CREATE_VNODE]}: _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]}: _${helperNameMap[RESOLVE_DIRECTIVE]} } = _Vue`,
) )
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
test('function mode preamble w/ prefixIdentifiers: true', () => { test('function mode preamble w/ prefixIdentifiers: true', () => {
const root = createRoot({ const root = createRoot({
helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE]) helpers: new Set([CREATE_VNODE, RESOLVE_DIRECTIVE]),
}) })
const { code } = generate(root, { const { code } = generate(root, {
mode: 'function', mode: 'function',
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect(code).not.toMatch(`const _Vue = Vue`) expect(code).not.toMatch(`const _Vue = Vue`)
expect(code).toMatch( expect(code).toMatch(
`const { ${helperNameMap[CREATE_VNODE]}: _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]}: _${helperNameMap[RESOLVE_DIRECTIVE]} } = Vue` `const { ${helperNameMap[CREATE_VNODE]}: _${helperNameMap[CREATE_VNODE]}, ${helperNameMap[RESOLVE_DIRECTIVE]}: _${helperNameMap[RESOLVE_DIRECTIVE]} } = Vue`,
) )
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -112,27 +112,27 @@ describe('compiler: codegen', () => {
const root = createRoot({ const root = createRoot({
components: [`Foo`, `bar-baz`, `barbaz`, `Qux__self`], components: [`Foo`, `bar-baz`, `barbaz`, `Qux__self`],
directives: [`my_dir_0`, `my_dir_1`], directives: [`my_dir_0`, `my_dir_1`],
temps: 3 temps: 3,
}) })
const { code } = generate(root, { mode: 'function' }) const { code } = generate(root, { mode: 'function' })
expect(code).toMatch( expect(code).toMatch(
`const _component_Foo = _${helperNameMap[RESOLVE_COMPONENT]}("Foo")\n` `const _component_Foo = _${helperNameMap[RESOLVE_COMPONENT]}("Foo")\n`,
) )
expect(code).toMatch( expect(code).toMatch(
`const _component_bar_baz = _${helperNameMap[RESOLVE_COMPONENT]}("bar-baz")\n` `const _component_bar_baz = _${helperNameMap[RESOLVE_COMPONENT]}("bar-baz")\n`,
) )
expect(code).toMatch( expect(code).toMatch(
`const _component_barbaz = _${helperNameMap[RESOLVE_COMPONENT]}("barbaz")\n` `const _component_barbaz = _${helperNameMap[RESOLVE_COMPONENT]}("barbaz")\n`,
) )
// implicit self reference from SFC filename // implicit self reference from SFC filename
expect(code).toMatch( expect(code).toMatch(
`const _component_Qux = _${helperNameMap[RESOLVE_COMPONENT]}("Qux", true)\n` `const _component_Qux = _${helperNameMap[RESOLVE_COMPONENT]}("Qux", true)\n`,
) )
expect(code).toMatch( expect(code).toMatch(
`const _directive_my_dir_0 = _${helperNameMap[RESOLVE_DIRECTIVE]}("my_dir_0")\n` `const _directive_my_dir_0 = _${helperNameMap[RESOLVE_DIRECTIVE]}("my_dir_0")\n`,
) )
expect(code).toMatch( expect(code).toMatch(
`const _directive_my_dir_1 = _${helperNameMap[RESOLVE_DIRECTIVE]}("my_dir_1")\n` `const _directive_my_dir_1 = _${helperNameMap[RESOLVE_DIRECTIVE]}("my_dir_1")\n`,
) )
expect(code).toMatch(`let _temp0, _temp1, _temp2`) expect(code).toMatch(`let _temp0, _temp1, _temp2`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -146,12 +146,12 @@ describe('compiler: codegen', () => {
[ [
createObjectProperty( createObjectProperty(
createSimpleExpression(`id`, true, locStub), createSimpleExpression(`id`, true, locStub),
createSimpleExpression(`foo`, true, locStub) createSimpleExpression(`foo`, true, locStub),
) ),
],
locStub,
),
], ],
locStub
)
]
}) })
const { code } = generate(root) const { code } = generate(root)
expect(code).toMatch(`const _hoisted_1 = hello`) expect(code).toMatch(`const _hoisted_1 = hello`)
@ -161,7 +161,7 @@ describe('compiler: codegen', () => {
test('temps', () => { test('temps', () => {
const root = createRoot({ const root = createRoot({
temps: 3 temps: 3,
}) })
const { code } = generate(root) const { code } = generate(root)
expect(code).toMatch(`let _temp0, _temp1, _temp2`) expect(code).toMatch(`let _temp0, _temp1, _temp2`)
@ -174,9 +174,9 @@ describe('compiler: codegen', () => {
codegenNode: { codegenNode: {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: 'hello', content: 'hello',
loc: locStub loc: locStub,
} },
}) }),
) )
expect(code).toMatch(`return "hello"`) expect(code).toMatch(`return "hello"`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -185,8 +185,8 @@ describe('compiler: codegen', () => {
test('interpolation', () => { test('interpolation', () => {
const { code } = generate( const { code } = generate(
createRoot({ createRoot({
codegenNode: createInterpolation(`hello`, locStub) codegenNode: createInterpolation(`hello`, locStub),
}) }),
) )
expect(code).toMatch(`return _${helperNameMap[TO_DISPLAY_STRING]}(hello)`) expect(code).toMatch(`return _${helperNameMap[TO_DISPLAY_STRING]}(hello)`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -198,9 +198,9 @@ describe('compiler: codegen', () => {
codegenNode: { codegenNode: {
type: NodeTypes.COMMENT, type: NodeTypes.COMMENT,
content: 'foo', content: 'foo',
loc: locStub loc: locStub,
} },
}) }),
) )
expect(code).toMatch(`return _${helperNameMap[CREATE_COMMENT]}("foo")`) expect(code).toMatch(`return _${helperNameMap[CREATE_COMMENT]}("foo")`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -216,15 +216,15 @@ describe('compiler: codegen', () => {
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
loc: locStub, loc: locStub,
content: createSimpleExpression(`bar`, false, locStub) content: createSimpleExpression(`bar`, false, locStub),
}, },
// nested compound // nested compound
createCompoundExpression([` + `, `nested`]) createCompoundExpression([` + `, `nested`]),
]) ]),
}) }),
) )
expect(code).toMatch( expect(code).toMatch(
`return _ctx.foo + _${helperNameMap[TO_DISPLAY_STRING]}(bar) + nested` `return _ctx.foo + _${helperNameMap[TO_DISPLAY_STRING]}(bar) + nested`,
) )
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -239,10 +239,10 @@ describe('compiler: codegen', () => {
codegenNode: createConditionalExpression( codegenNode: createConditionalExpression(
createSimpleExpression('foo', false), createSimpleExpression('foo', false),
createSimpleExpression('bar', false), createSimpleExpression('bar', false),
createSimpleExpression('baz', false) createSimpleExpression('baz', false),
) as IfConditionalExpression ) as IfConditionalExpression,
} },
}) }),
) )
expect(code).toMatch(/return foo\s+\? bar\s+: baz/) expect(code).toMatch(/return foo\s+\? bar\s+: baz/)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -270,10 +270,10 @@ describe('compiler: codegen', () => {
patchFlag: '1', patchFlag: '1',
dynamicProps: undefined, dynamicProps: undefined,
directives: undefined, directives: undefined,
loc: locStub loc: locStub,
} as ForCodegenNode } as ForCodegenNode,
} },
}) }),
) )
expect(code).toMatch(`openBlock(true)`) expect(code).toMatch(`openBlock(true)`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -289,7 +289,7 @@ describe('compiler: codegen', () => {
'1 + 2', '1 + 2',
false, false,
locStub, locStub,
ConstantTypes.CAN_STRINGIFY ConstantTypes.CAN_STRINGIFY,
), ),
valueAlias: undefined, valueAlias: undefined,
keyAlias: undefined, keyAlias: undefined,
@ -306,10 +306,10 @@ describe('compiler: codegen', () => {
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT), patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT),
dynamicProps: undefined, dynamicProps: undefined,
directives: undefined, directives: undefined,
loc: locStub loc: locStub,
} as ForCodegenNode } as ForCodegenNode,
} },
}) }),
) )
expect(code).toMatch(`openBlock()`) expect(code).toMatch(`openBlock()`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -326,11 +326,11 @@ describe('compiler: codegen', () => {
[ [
createObjectProperty( createObjectProperty(
createSimpleExpression(`id`, true, locStub), createSimpleExpression(`id`, true, locStub),
createSimpleExpression(`foo`, true, locStub) createSimpleExpression(`foo`, true, locStub),
), ),
createObjectProperty( createObjectProperty(
createSimpleExpression(`prop`, false, locStub), createSimpleExpression(`prop`, false, locStub),
createSimpleExpression(`bar`, false, locStub) createSimpleExpression(`bar`, false, locStub),
), ),
// compound expression as computed key // compound expression as computed key
createObjectProperty( createObjectProperty(
@ -339,13 +339,13 @@ describe('compiler: codegen', () => {
loc: locStub, loc: locStub,
children: [ children: [
`foo + `, `foo + `,
createSimpleExpression(`bar`, false, locStub) createSimpleExpression(`bar`, false, locStub),
]
},
createSimpleExpression(`bar`, false, locStub)
)
], ],
locStub },
createSimpleExpression(`bar`, false, locStub),
),
],
locStub,
), ),
// ChildNode[] // ChildNode[]
[ [
@ -356,17 +356,17 @@ describe('compiler: codegen', () => {
createObjectProperty( createObjectProperty(
// should quote the key! // should quote the key!
createSimpleExpression(`some-key`, true, locStub), createSimpleExpression(`some-key`, true, locStub),
createSimpleExpression(`foo`, true, locStub) createSimpleExpression(`foo`, true, locStub),
) ),
], ],
locStub locStub,
) ),
) ),
], ],
// flag // flag
PatchFlags.FULL_PROPS + '' PatchFlags.FULL_PROPS + '',
) ),
}) }),
) )
expect(code).toMatch(` expect(code).toMatch(`
return _${helperNameMap[CREATE_ELEMENT_VNODE]}("div", { return _${helperNameMap[CREATE_ELEMENT_VNODE]}("div", {
@ -384,9 +384,9 @@ describe('compiler: codegen', () => {
createRoot({ createRoot({
codegenNode: createArrayExpression([ codegenNode: createArrayExpression([
createSimpleExpression(`foo`, false), createSimpleExpression(`foo`, false),
createCallExpression(`bar`, [`baz`]) createCallExpression(`bar`, [`baz`]),
]) ]),
}) }),
) )
expect(code).toMatch(`return [ expect(code).toMatch(`return [
foo, foo,
@ -404,17 +404,17 @@ describe('compiler: codegen', () => {
createConditionalExpression( createConditionalExpression(
createSimpleExpression(`orNot`, false), createSimpleExpression(`orNot`, false),
createCallExpression(`bar`), createCallExpression(`bar`),
createCallExpression(`baz`) createCallExpression(`baz`),
) ),
) ),
}) }),
) )
expect(code).toMatch( expect(code).toMatch(
`return ok `return ok
? foo() ? foo()
: orNot : orNot
? bar() ? bar()
: baz()` : baz()`,
) )
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -425,13 +425,13 @@ describe('compiler: codegen', () => {
cached: 1, cached: 1,
codegenNode: createCacheExpression( codegenNode: createCacheExpression(
1, 1,
createSimpleExpression(`foo`, false) createSimpleExpression(`foo`, false),
) ),
}), }),
{ {
mode: 'module', mode: 'module',
prefixIdentifiers: true prefixIdentifiers: true,
} },
) )
expect(code).toMatch(`_cache[1] || (_cache[1] = foo)`) expect(code).toMatch(`_cache[1] || (_cache[1] = foo)`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -444,13 +444,13 @@ describe('compiler: codegen', () => {
codegenNode: createCacheExpression( codegenNode: createCacheExpression(
1, 1,
createSimpleExpression(`foo`, false), createSimpleExpression(`foo`, false),
true true,
) ),
}), }),
{ {
mode: 'module', mode: 'module',
prefixIdentifiers: true prefixIdentifiers: true,
} },
) )
expect(code).toMatch( expect(code).toMatch(
` `
@ -460,7 +460,7 @@ describe('compiler: codegen', () => {
_setBlockTracking(1), _setBlockTracking(1),
_cache[1] _cache[1]
) )
`.trim() `.trim(),
) )
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -472,11 +472,11 @@ describe('compiler: codegen', () => {
createTemplateLiteral([ createTemplateLiteral([
`foo`, `foo`,
createCallExpression(`_renderAttr`, ['id', 'foo']), createCallExpression(`_renderAttr`, ['id', 'foo']),
`bar` `bar`,
]) ]),
]) ]),
}), }),
{ ssr: true, mode: 'module' } { ssr: true, mode: 'module' },
) )
expect(code).toMatchInlineSnapshot(` expect(code).toMatchInlineSnapshot(`
" "
@ -493,11 +493,11 @@ describe('compiler: codegen', () => {
codegenNode: createBlockStatement([ codegenNode: createBlockStatement([
createIfStatement( createIfStatement(
createSimpleExpression('foo', false), createSimpleExpression('foo', false),
createBlockStatement([createCallExpression(`ok`)]) createBlockStatement([createCallExpression(`ok`)]),
) ),
]) ]),
}), }),
{ ssr: true, mode: 'module' } { ssr: true, mode: 'module' },
) )
expect(code).toMatchInlineSnapshot(` expect(code).toMatchInlineSnapshot(`
" "
@ -516,11 +516,11 @@ describe('compiler: codegen', () => {
createIfStatement( createIfStatement(
createSimpleExpression('foo', false), createSimpleExpression('foo', false),
createBlockStatement([createCallExpression(`foo`)]), createBlockStatement([createCallExpression(`foo`)]),
createBlockStatement([createCallExpression('bar')]) createBlockStatement([createCallExpression('bar')]),
) ),
]) ]),
}), }),
{ ssr: true, mode: 'module' } { ssr: true, mode: 'module' },
) )
expect(code).toMatchInlineSnapshot(` expect(code).toMatchInlineSnapshot(`
" "
@ -543,12 +543,12 @@ describe('compiler: codegen', () => {
createBlockStatement([createCallExpression(`foo`)]), createBlockStatement([createCallExpression(`foo`)]),
createIfStatement( createIfStatement(
createSimpleExpression('bar', false), createSimpleExpression('bar', false),
createBlockStatement([createCallExpression(`bar`)]) createBlockStatement([createCallExpression(`bar`)]),
) ),
) ),
]) ]),
}), }),
{ ssr: true, mode: 'module' } { ssr: true, mode: 'module' },
) )
expect(code).toMatchInlineSnapshot(` expect(code).toMatchInlineSnapshot(`
" "
@ -572,12 +572,12 @@ describe('compiler: codegen', () => {
createIfStatement( createIfStatement(
createSimpleExpression('bar', false), createSimpleExpression('bar', false),
createBlockStatement([createCallExpression(`bar`)]), createBlockStatement([createCallExpression(`bar`)]),
createBlockStatement([createCallExpression('baz')]) createBlockStatement([createCallExpression('baz')]),
) ),
) ),
]) ]),
}), }),
{ ssr: true, mode: 'module' } { ssr: true, mode: 'module' },
) )
expect(code).toMatchInlineSnapshot(` expect(code).toMatchInlineSnapshot(`
" "
@ -599,9 +599,9 @@ describe('compiler: codegen', () => {
createRoot({ createRoot({
codegenNode: createAssignmentExpression( codegenNode: createAssignmentExpression(
createSimpleExpression(`foo`, false), createSimpleExpression(`foo`, false),
createSimpleExpression(`bar`, false) createSimpleExpression(`bar`, false),
) ),
}) }),
) )
expect(code).toMatchInlineSnapshot(` expect(code).toMatchInlineSnapshot(`
" "
@ -617,17 +617,17 @@ describe('compiler: codegen', () => {
function genCode(node: VNodeCall) { function genCode(node: VNodeCall) {
return generate( return generate(
createRoot({ createRoot({
codegenNode: node codegenNode: node,
}) }),
).code.match(/with \(_ctx\) \{\s+([^]+)\s+\}\s+\}$/)![1] ).code.match(/with \(_ctx\) \{\s+([^]+)\s+\}\s+\}$/)![1]
} }
const mockProps = createObjectExpression([ const mockProps = createObjectExpression([
createObjectProperty(`foo`, createSimpleExpression(`bar`, true)) createObjectProperty(`foo`, createSimpleExpression(`bar`, true)),
]) ])
const mockChildren = createCompoundExpression(['children']) const mockChildren = createCompoundExpression(['children'])
const mockDirs = createArrayExpression([ const mockDirs = createArrayExpression([
createArrayExpression([`foo`, createSimpleExpression(`bar`, false)]) createArrayExpression([`foo`, createSimpleExpression(`bar`, false)]),
]) as DirectiveArguments ]) as DirectiveArguments
test('tag only', () => { test('tag only', () => {
@ -684,9 +684,9 @@ describe('compiler: codegen', () => {
undefined, undefined,
undefined, undefined,
undefined, undefined,
true true,
) ),
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"return (_openBlock(), _createElementBlock("div", { foo: "bar" }, children)) "return (_openBlock(), _createElementBlock("div", { foo: "bar" }, children))
" "
@ -705,9 +705,9 @@ describe('compiler: codegen', () => {
undefined, undefined,
undefined, undefined,
true, true,
true true,
) ),
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"return (_openBlock(true), _createElementBlock("div", { foo: "bar" }, children)) "return (_openBlock(true), _createElementBlock("div", { foo: "bar" }, children))
" "
@ -724,9 +724,9 @@ describe('compiler: codegen', () => {
mockChildren, mockChildren,
undefined, undefined,
undefined, undefined,
mockDirs mockDirs,
) ),
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"return _withDirectives(_createElementVNode("div", { foo: "bar" }, children), [ "return _withDirectives(_createElementVNode("div", { foo: "bar" }, children), [
[foo, bar] [foo, bar]
@ -746,9 +746,9 @@ describe('compiler: codegen', () => {
undefined, undefined,
undefined, undefined,
mockDirs, mockDirs,
true true,
) ),
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"return _withDirectives((_openBlock(), _createElementBlock("div", { foo: "bar" }, children)), [ "return _withDirectives((_openBlock(), _createElementBlock("div", { foo: "bar" }, children)), [
[foo, bar] [foo, bar]

View File

@ -1,5 +1,5 @@
import { baseCompile as compile } from '../src' import { baseCompile as compile } from '../src'
import { SourceMapConsumer, RawSourceMap } from 'source-map-js' import { type RawSourceMap, SourceMapConsumer } from 'source-map-js'
describe('compiler: integration tests', () => { describe('compiler: integration tests', () => {
const source = ` const source = `
@ -20,7 +20,7 @@ describe('compiler: integration tests', () => {
function getPositionInCode( function getPositionInCode(
code: string, code: string,
token: string, token: string,
expectName: string | boolean = false expectName: string | boolean = false,
): Pos { ): Pos {
const generatedOffset = code.indexOf(token) const generatedOffset = code.indexOf(token)
let line = 1 let line = 1
@ -36,7 +36,7 @@ describe('compiler: integration tests', () => {
column: column:
lastNewLinePos === -1 lastNewLinePos === -1
? generatedOffset ? generatedOffset
: generatedOffset - lastNewLinePos - 1 : generatedOffset - lastNewLinePos - 1,
} }
if (expectName) { if (expectName) {
res.name = typeof expectName === 'string' ? expectName : token res.name = typeof expectName === 'string' ? expectName : token
@ -47,7 +47,7 @@ describe('compiler: integration tests', () => {
test('function mode', () => { test('function mode', () => {
const { code, map } = compile(source, { const { code, map } = compile(source, {
sourceMap: true, sourceMap: true,
filename: `foo.vue` filename: `foo.vue`,
}) })
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -57,55 +57,55 @@ describe('compiler: integration tests', () => {
const consumer = new SourceMapConsumer(map as RawSourceMap) const consumer = new SourceMapConsumer(map as RawSourceMap)
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `id`)) consumer.originalPositionFor(getPositionInCode(code, `id`)),
).toMatchObject(getPositionInCode(source, `id`)) ).toMatchObject(getPositionInCode(source, `id`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `"foo"`)) consumer.originalPositionFor(getPositionInCode(code, `"foo"`)),
).toMatchObject(getPositionInCode(source, `"foo"`)) ).toMatchObject(getPositionInCode(source, `"foo"`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `class:`)) consumer.originalPositionFor(getPositionInCode(code, `class:`)),
).toMatchObject(getPositionInCode(source, `class=`)) ).toMatchObject(getPositionInCode(source, `class=`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `bar`)) consumer.originalPositionFor(getPositionInCode(code, `bar`)),
).toMatchObject(getPositionInCode(source, `bar`)) ).toMatchObject(getPositionInCode(source, `bar`))
// without prefixIdentifiers: true, identifiers inside compound expressions // without prefixIdentifiers: true, identifiers inside compound expressions
// are mapped to closest parent expression. // are mapped to closest parent expression.
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `baz`)) consumer.originalPositionFor(getPositionInCode(code, `baz`)),
).toMatchObject(getPositionInCode(source, `bar`)) ).toMatchObject(getPositionInCode(source, `bar`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `world`)) consumer.originalPositionFor(getPositionInCode(code, `world`)),
).toMatchObject(getPositionInCode(source, `world`)) ).toMatchObject(getPositionInCode(source, `world`))
// without prefixIdentifiers: true, identifiers inside compound expressions // without prefixIdentifiers: true, identifiers inside compound expressions
// are mapped to closest parent expression. // are mapped to closest parent expression.
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `burn()`)) consumer.originalPositionFor(getPositionInCode(code, `burn()`)),
).toMatchObject(getPositionInCode(source, `world`)) ).toMatchObject(getPositionInCode(source, `world`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `ok`)) consumer.originalPositionFor(getPositionInCode(code, `ok`)),
).toMatchObject(getPositionInCode(source, `ok`)) ).toMatchObject(getPositionInCode(source, `ok`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `list`)) consumer.originalPositionFor(getPositionInCode(code, `list`)),
).toMatchObject(getPositionInCode(source, `list`)) ).toMatchObject(getPositionInCode(source, `list`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `value`)) consumer.originalPositionFor(getPositionInCode(code, `value`)),
).toMatchObject(getPositionInCode(source, `value`)) ).toMatchObject(getPositionInCode(source, `value`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `index`)) consumer.originalPositionFor(getPositionInCode(code, `index`)),
).toMatchObject(getPositionInCode(source, `index`)) ).toMatchObject(getPositionInCode(source, `index`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `value + index`)) consumer.originalPositionFor(getPositionInCode(code, `value + index`)),
).toMatchObject(getPositionInCode(source, `value + index`)) ).toMatchObject(getPositionInCode(source, `value + index`))
}) })
@ -113,7 +113,7 @@ describe('compiler: integration tests', () => {
const { code, map } = compile(source, { const { code, map } = compile(source, {
sourceMap: true, sourceMap: true,
filename: `foo.vue`, filename: `foo.vue`,
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -123,64 +123,66 @@ describe('compiler: integration tests', () => {
const consumer = new SourceMapConsumer(map as RawSourceMap) const consumer = new SourceMapConsumer(map as RawSourceMap)
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `id`)) consumer.originalPositionFor(getPositionInCode(code, `id`)),
).toMatchObject(getPositionInCode(source, `id`)) ).toMatchObject(getPositionInCode(source, `id`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `"foo"`)) consumer.originalPositionFor(getPositionInCode(code, `"foo"`)),
).toMatchObject(getPositionInCode(source, `"foo"`)) ).toMatchObject(getPositionInCode(source, `"foo"`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `class:`)) consumer.originalPositionFor(getPositionInCode(code, `class:`)),
).toMatchObject(getPositionInCode(source, `class=`)) ).toMatchObject(getPositionInCode(source, `class=`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `bar`)) consumer.originalPositionFor(getPositionInCode(code, `bar`)),
).toMatchObject(getPositionInCode(source, `bar`)) ).toMatchObject(getPositionInCode(source, `bar`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `_ctx.bar`, `bar`)) consumer.originalPositionFor(getPositionInCode(code, `_ctx.bar`, `bar`)),
).toMatchObject(getPositionInCode(source, `bar`, true)) ).toMatchObject(getPositionInCode(source, `bar`, true))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `baz`)) consumer.originalPositionFor(getPositionInCode(code, `baz`)),
).toMatchObject(getPositionInCode(source, `baz`)) ).toMatchObject(getPositionInCode(source, `baz`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `world`, true)) consumer.originalPositionFor(getPositionInCode(code, `world`, true)),
).toMatchObject(getPositionInCode(source, `world`, `world`)) ).toMatchObject(getPositionInCode(source, `world`, `world`))
expect( expect(
consumer.originalPositionFor( consumer.originalPositionFor(
getPositionInCode(code, `_ctx.world`, `world`) getPositionInCode(code, `_ctx.world`, `world`),
) ),
).toMatchObject(getPositionInCode(source, `world`, `world`)) ).toMatchObject(getPositionInCode(source, `world`, `world`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `burn()`)) consumer.originalPositionFor(getPositionInCode(code, `burn()`)),
).toMatchObject(getPositionInCode(source, `burn()`)) ).toMatchObject(getPositionInCode(source, `burn()`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `ok`)) consumer.originalPositionFor(getPositionInCode(code, `ok`)),
).toMatchObject(getPositionInCode(source, `ok`)) ).toMatchObject(getPositionInCode(source, `ok`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `_ctx.ok`, `ok`)) consumer.originalPositionFor(getPositionInCode(code, `_ctx.ok`, `ok`)),
).toMatchObject(getPositionInCode(source, `ok`, true)) ).toMatchObject(getPositionInCode(source, `ok`, true))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `list`)) consumer.originalPositionFor(getPositionInCode(code, `list`)),
).toMatchObject(getPositionInCode(source, `list`)) ).toMatchObject(getPositionInCode(source, `list`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `_ctx.list`, `list`)) consumer.originalPositionFor(
getPositionInCode(code, `_ctx.list`, `list`),
),
).toMatchObject(getPositionInCode(source, `list`, true)) ).toMatchObject(getPositionInCode(source, `list`, true))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `value`)) consumer.originalPositionFor(getPositionInCode(code, `value`)),
).toMatchObject(getPositionInCode(source, `value`)) ).toMatchObject(getPositionInCode(source, `value`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `index`)) consumer.originalPositionFor(getPositionInCode(code, `index`)),
).toMatchObject(getPositionInCode(source, `index`)) ).toMatchObject(getPositionInCode(source, `index`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `value + index`)) consumer.originalPositionFor(getPositionInCode(code, `value + index`)),
).toMatchObject(getPositionInCode(source, `value + index`)) ).toMatchObject(getPositionInCode(source, `value + index`))
}) })
@ -188,7 +190,7 @@ describe('compiler: integration tests', () => {
const { code, map } = compile(source, { const { code, map } = compile(source, {
mode: 'module', mode: 'module',
sourceMap: true, sourceMap: true,
filename: `foo.vue` filename: `foo.vue`,
}) })
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -198,64 +200,66 @@ describe('compiler: integration tests', () => {
const consumer = new SourceMapConsumer(map as RawSourceMap) const consumer = new SourceMapConsumer(map as RawSourceMap)
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `id`)) consumer.originalPositionFor(getPositionInCode(code, `id`)),
).toMatchObject(getPositionInCode(source, `id`)) ).toMatchObject(getPositionInCode(source, `id`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `"foo"`)) consumer.originalPositionFor(getPositionInCode(code, `"foo"`)),
).toMatchObject(getPositionInCode(source, `"foo"`)) ).toMatchObject(getPositionInCode(source, `"foo"`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `class:`)) consumer.originalPositionFor(getPositionInCode(code, `class:`)),
).toMatchObject(getPositionInCode(source, `class=`)) ).toMatchObject(getPositionInCode(source, `class=`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `bar`)) consumer.originalPositionFor(getPositionInCode(code, `bar`)),
).toMatchObject(getPositionInCode(source, `bar`)) ).toMatchObject(getPositionInCode(source, `bar`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `_ctx.bar`, `bar`)) consumer.originalPositionFor(getPositionInCode(code, `_ctx.bar`, `bar`)),
).toMatchObject(getPositionInCode(source, `bar`, true)) ).toMatchObject(getPositionInCode(source, `bar`, true))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `baz`)) consumer.originalPositionFor(getPositionInCode(code, `baz`)),
).toMatchObject(getPositionInCode(source, `baz`)) ).toMatchObject(getPositionInCode(source, `baz`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `world`, true)) consumer.originalPositionFor(getPositionInCode(code, `world`, true)),
).toMatchObject(getPositionInCode(source, `world`, `world`)) ).toMatchObject(getPositionInCode(source, `world`, `world`))
expect( expect(
consumer.originalPositionFor( consumer.originalPositionFor(
getPositionInCode(code, `_ctx.world`, `world`) getPositionInCode(code, `_ctx.world`, `world`),
) ),
).toMatchObject(getPositionInCode(source, `world`, `world`)) ).toMatchObject(getPositionInCode(source, `world`, `world`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `burn()`)) consumer.originalPositionFor(getPositionInCode(code, `burn()`)),
).toMatchObject(getPositionInCode(source, `burn()`)) ).toMatchObject(getPositionInCode(source, `burn()`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `ok`)) consumer.originalPositionFor(getPositionInCode(code, `ok`)),
).toMatchObject(getPositionInCode(source, `ok`)) ).toMatchObject(getPositionInCode(source, `ok`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `_ctx.ok`, `ok`)) consumer.originalPositionFor(getPositionInCode(code, `_ctx.ok`, `ok`)),
).toMatchObject(getPositionInCode(source, `ok`, true)) ).toMatchObject(getPositionInCode(source, `ok`, true))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `list`)) consumer.originalPositionFor(getPositionInCode(code, `list`)),
).toMatchObject(getPositionInCode(source, `list`)) ).toMatchObject(getPositionInCode(source, `list`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `_ctx.list`, `list`)) consumer.originalPositionFor(
getPositionInCode(code, `_ctx.list`, `list`),
),
).toMatchObject(getPositionInCode(source, `list`, true)) ).toMatchObject(getPositionInCode(source, `list`, true))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `value`)) consumer.originalPositionFor(getPositionInCode(code, `value`)),
).toMatchObject(getPositionInCode(source, `value`)) ).toMatchObject(getPositionInCode(source, `value`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `index`)) consumer.originalPositionFor(getPositionInCode(code, `index`)),
).toMatchObject(getPositionInCode(source, `index`)) ).toMatchObject(getPositionInCode(source, `index`))
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, `value + index`)) consumer.originalPositionFor(getPositionInCode(code, `value + index`)),
).toMatchObject(getPositionInCode(source, `value + index`)) ).toMatchObject(getPositionInCode(source, `value + index`))
}) })
}) })

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
import { baseCompile } from '../src/compile' import { baseCompile } from '../src/compile'
import { PUSH_SCOPE_ID, POP_SCOPE_ID } from '../src/runtimeHelpers' import { POP_SCOPE_ID, PUSH_SCOPE_ID } from '../src/runtimeHelpers'
import { PatchFlags } from '@vue/shared' import { PatchFlags } from '@vue/shared'
import { genFlagText } from './testUtils' import { genFlagText } from './testUtils'
@ -18,7 +18,7 @@ describe('scopeId compiler support', () => {
test('should wrap default slot', () => { test('should wrap default slot', () => {
const { code } = baseCompile(`<Child><div/></Child>`, { const { code } = baseCompile(`<Child><div/></Child>`, {
mode: 'module', mode: 'module',
scopeId: 'test' scopeId: 'test',
}) })
expect(code).toMatch(`default: _withCtx(() => [`) expect(code).toMatch(`default: _withCtx(() => [`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -33,8 +33,8 @@ describe('scopeId compiler support', () => {
`, `,
{ {
mode: 'module', mode: 'module',
scopeId: 'test' scopeId: 'test',
} },
) )
expect(code).toMatch(`foo: _withCtx(({ msg }) => [`) expect(code).toMatch(`foo: _withCtx(({ msg }) => [`)
expect(code).toMatch(`bar: _withCtx(() => [`) expect(code).toMatch(`bar: _withCtx(() => [`)
@ -50,8 +50,8 @@ describe('scopeId compiler support', () => {
`, `,
{ {
mode: 'module', mode: 'module',
scopeId: 'test' scopeId: 'test',
} },
) )
expect(code).toMatch(/name: "foo",\s+fn: _withCtx\(/) expect(code).toMatch(/name: "foo",\s+fn: _withCtx\(/)
expect(code).toMatch(/name: i,\s+fn: _withCtx\(/) expect(code).toMatch(/name: i,\s+fn: _withCtx\(/)
@ -64,8 +64,8 @@ describe('scopeId compiler support', () => {
{ {
mode: 'module', mode: 'module',
scopeId: 'test', scopeId: 'test',
hoistStatic: true hoistStatic: true,
} },
) )
expect(ast.helpers).toContain(PUSH_SCOPE_ID) expect(ast.helpers).toContain(PUSH_SCOPE_ID)
expect(ast.helpers).toContain(POP_SCOPE_ID) expect(ast.helpers).toContain(POP_SCOPE_ID)
@ -73,11 +73,11 @@ describe('scopeId compiler support', () => {
;[ ;[
`const _withScopeId = n => (_pushScopeId("test"),n=n(),_popScopeId(),n)`, `const _withScopeId = n => (_pushScopeId("test"),n=n(),_popScopeId(),n)`,
`const _hoisted_1 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "hello", ${genFlagText( `const _hoisted_1 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "hello", ${genFlagText(
PatchFlags.HOISTED PatchFlags.HOISTED,
)}))`, )}))`,
`const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "world", ${genFlagText( `const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "world", ${genFlagText(
PatchFlags.HOISTED PatchFlags.HOISTED,
)}))` )}))`,
].forEach(c => expect(code).toMatch(c)) ].forEach(c => expect(code).toMatch(c))
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })

View File

@ -1,17 +1,17 @@
import { import {
NodeTypes, type ElementNode,
ElementNode,
locStub,
Namespaces,
ElementTypes, ElementTypes,
VNodeCall Namespaces,
NodeTypes,
type VNodeCall,
locStub,
} from '../src' } from '../src'
import { import {
isString,
PatchFlags,
PatchFlagNames, PatchFlagNames,
type PatchFlags,
type ShapeFlags,
isArray, isArray,
ShapeFlags isString,
} from '@vue/shared' } from '@vue/shared'
const leadingBracketRE = /^\[/ const leadingBracketRE = /^\[/
@ -30,16 +30,16 @@ export function createObjectMatcher(obj: Record<string, any>) {
key: { key: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: key.replace(bracketsRE, ''), content: key.replace(bracketsRE, ''),
isStatic: !leadingBracketRE.test(key) isStatic: !leadingBracketRE.test(key),
}, },
value: isString(obj[key]) value: isString(obj[key])
? { ? {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: obj[key].replace(bracketsRE, ''), content: obj[key].replace(bracketsRE, ''),
isStatic: !leadingBracketRE.test(obj[key]) isStatic: !leadingBracketRE.test(obj[key]),
} }
: obj[key] : obj[key],
})) })),
} }
} }
@ -48,7 +48,7 @@ export function createElementWithCodegen(
props?: VNodeCall['props'], props?: VNodeCall['props'],
children?: VNodeCall['children'], children?: VNodeCall['children'],
patchFlag?: VNodeCall['patchFlag'], patchFlag?: VNodeCall['patchFlag'],
dynamicProps?: VNodeCall['dynamicProps'] dynamicProps?: VNodeCall['dynamicProps'],
): ElementNode { ): ElementNode {
return { return {
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,
@ -69,15 +69,15 @@ export function createElementWithCodegen(
isBlock: false, isBlock: false,
disableTracking: false, disableTracking: false,
isComponent: false, isComponent: false,
loc: locStub loc: locStub,
} },
} }
} }
type Flags = PatchFlags | ShapeFlags type Flags = PatchFlags | ShapeFlags
export function genFlagText( export function genFlagText(
flag: Flags | Flags[], flag: Flags | Flags[],
names: { [k: number]: string } = PatchFlagNames names: { [k: number]: string } = PatchFlagNames,
) { ) {
if (isArray(flag)) { if (isArray(flag)) {
let f = 0 let f = 0

View File

@ -1,18 +1,18 @@
import { baseParse } from '../src/parser' import { baseParse } from '../src/parser'
import { transform, NodeTransform } from '../src/transform' import { type NodeTransform, transform } from '../src/transform'
import { import {
ElementNode, type DirectiveNode,
type ElementNode,
type ExpressionNode,
NodeTypes, NodeTypes,
DirectiveNode, type VNodeCall,
ExpressionNode,
VNodeCall
} from '../src/ast' } from '../src/ast'
import { ErrorCodes, createCompilerError } from '../src/errors' import { ErrorCodes, createCompilerError } from '../src/errors'
import { import {
TO_DISPLAY_STRING, CREATE_COMMENT,
FRAGMENT, FRAGMENT,
RENDER_SLOT, RENDER_SLOT,
CREATE_COMMENT TO_DISPLAY_STRING,
} from '../src/runtimeHelpers' } from '../src/runtimeHelpers'
import { transformIf } from '../src/transforms/vIf' import { transformIf } from '../src/transforms/vIf'
import { transformFor } from '../src/transforms/vFor' import { transformFor } from '../src/transforms/vFor'
@ -34,7 +34,7 @@ describe('compiler: transform', () => {
} }
transform(ast, { transform(ast, {
nodeTransforms: [plugin] nodeTransforms: [plugin],
}) })
const div = ast.children[0] as ElementNode const div = ast.children[0] as ElementNode
@ -43,29 +43,29 @@ describe('compiler: transform', () => {
ast, ast,
{ {
parent: null, parent: null,
currentNode: ast currentNode: ast,
} },
]) ])
expect(calls[1]).toMatchObject([ expect(calls[1]).toMatchObject([
div, div,
{ {
parent: ast, parent: ast,
currentNode: div currentNode: div,
} },
]) ])
expect(calls[2]).toMatchObject([ expect(calls[2]).toMatchObject([
div.children[0], div.children[0],
{ {
parent: div, parent: div,
currentNode: div.children[0] currentNode: div.children[0],
} },
]) ])
expect(calls[3]).toMatchObject([ expect(calls[3]).toMatchObject([
div.children[1], div.children[1],
{ {
parent: div, parent: div,
currentNode: div.children[1] currentNode: div.children[1],
} },
]) ])
}) })
@ -81,16 +81,16 @@ describe('compiler: transform', () => {
{ {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: 'hello', content: 'hello',
isEmpty: false isEmpty: false,
} },
] ],
}) }),
) )
} }
} }
const spy = vi.fn(plugin) const spy = vi.fn(plugin)
transform(ast, { transform(ast, {
nodeTransforms: [spy] nodeTransforms: [spy],
}) })
expect(ast.children.length).toBe(2) expect(ast.children.length).toBe(2)
@ -115,7 +115,7 @@ describe('compiler: transform', () => {
} }
const spy = vi.fn(plugin) const spy = vi.fn(plugin)
transform(ast, { transform(ast, {
nodeTransforms: [spy] nodeTransforms: [spy],
}) })
expect(ast.children.length).toBe(2) expect(ast.children.length).toBe(2)
@ -143,7 +143,7 @@ describe('compiler: transform', () => {
} }
const spy = vi.fn(plugin) const spy = vi.fn(plugin)
transform(ast, { transform(ast, {
nodeTransforms: [spy] nodeTransforms: [spy],
}) })
expect(ast.children.length).toBe(1) expect(ast.children.length).toBe(1)
@ -170,7 +170,7 @@ describe('compiler: transform', () => {
} }
const spy = vi.fn(plugin) const spy = vi.fn(plugin)
transform(ast, { transform(ast, {
nodeTransforms: [spy] nodeTransforms: [spy],
}) })
expect(ast.children.length).toBe(1) expect(ast.children.length).toBe(1)
@ -194,7 +194,7 @@ describe('compiler: transform', () => {
} }
} }
transform(ast, { transform(ast, {
nodeTransforms: [mock] nodeTransforms: [mock],
}) })
expect(ast.hoists).toMatchObject(hoisted) expect(ast.hoists).toMatchObject(hoisted)
expect((ast as any).children[0].props[0].exp.content).toBe(`_hoisted_1`) expect((ast as any).children[0].props[0].exp.content).toBe(`_hoisted_1`)
@ -211,13 +211,13 @@ describe('compiler: transform', () => {
transform(ast, { transform(ast, {
filename: '/the/fileName.vue', filename: '/the/fileName.vue',
nodeTransforms: [plugin] nodeTransforms: [plugin],
}) })
expect(calls.length).toBe(2) expect(calls.length).toBe(2)
expect(calls[1]).toMatchObject({ expect(calls[1]).toMatchObject({
filename: '/the/fileName.vue', filename: '/the/fileName.vue',
selfName: 'FileName' selfName: 'FileName',
}) })
}) })
@ -226,19 +226,19 @@ describe('compiler: transform', () => {
const loc = ast.children[0].loc const loc = ast.children[0].loc
const plugin: NodeTransform = (node, context) => { const plugin: NodeTransform = (node, context) => {
context.onError( context.onError(
createCompilerError(ErrorCodes.X_INVALID_END_TAG, node.loc) createCompilerError(ErrorCodes.X_INVALID_END_TAG, node.loc),
) )
} }
const spy = vi.fn() const spy = vi.fn()
transform(ast, { transform(ast, {
nodeTransforms: [plugin], nodeTransforms: [plugin],
onError: spy onError: spy,
}) })
expect(spy.mock.calls[0]).toMatchObject([ expect(spy.mock.calls[0]).toMatchObject([
{ {
code: ErrorCodes.X_INVALID_END_TAG, code: ErrorCodes.X_INVALID_END_TAG,
loc loc,
} },
]) ])
}) })
@ -263,8 +263,8 @@ describe('compiler: transform', () => {
transformFor, transformFor,
transformText, transformText,
transformSlotOutlet, transformSlotOutlet,
transformElement transformElement,
] ],
}) })
return ast return ast
} }
@ -273,7 +273,7 @@ describe('compiler: transform', () => {
tag: VNodeCall['tag'], tag: VNodeCall['tag'],
props?: VNodeCall['props'], props?: VNodeCall['props'],
children?: VNodeCall['children'], children?: VNodeCall['children'],
patchFlag?: VNodeCall['patchFlag'] patchFlag?: VNodeCall['patchFlag'],
) { ) {
return { return {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
@ -281,7 +281,7 @@ describe('compiler: transform', () => {
tag, tag,
props, props,
children, children,
patchFlag patchFlag,
} }
} }
@ -295,8 +295,8 @@ describe('compiler: transform', () => {
expect(ast.codegenNode).toMatchObject({ expect(ast.codegenNode).toMatchObject({
codegenNode: { codegenNode: {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT callee: RENDER_SLOT,
} },
}) })
}) })
@ -308,14 +308,14 @@ describe('compiler: transform', () => {
test('root v-if', () => { test('root v-if', () => {
const ast = transformWithCodegen(`<div v-if="ok" />`) const ast = transformWithCodegen(`<div v-if="ok" />`)
expect(ast.codegenNode).toMatchObject({ expect(ast.codegenNode).toMatchObject({
type: NodeTypes.IF type: NodeTypes.IF,
}) })
}) })
test('root v-for', () => { test('root v-for', () => {
const ast = transformWithCodegen(`<div v-for="i in list" />`) const ast = transformWithCodegen(`<div v-for="i in list" />`)
expect(ast.codegenNode).toMatchObject({ expect(ast.codegenNode).toMatchObject({
type: NodeTypes.FOR type: NodeTypes.FOR,
}) })
}) })
@ -323,28 +323,28 @@ describe('compiler: transform', () => {
const ast = transformWithCodegen(`<div v-foo/>`) const ast = transformWithCodegen(`<div v-foo/>`)
expect(ast.codegenNode).toMatchObject({ expect(ast.codegenNode).toMatchObject({
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
directives: { type: NodeTypes.JS_ARRAY_EXPRESSION } directives: { type: NodeTypes.JS_ARRAY_EXPRESSION },
}) })
}) })
test('single text', () => { test('single text', () => {
const ast = transformWithCodegen(`hello`) const ast = transformWithCodegen(`hello`)
expect(ast.codegenNode).toMatchObject({ expect(ast.codegenNode).toMatchObject({
type: NodeTypes.TEXT type: NodeTypes.TEXT,
}) })
}) })
test('single interpolation', () => { test('single interpolation', () => {
const ast = transformWithCodegen(`{{ foo }}`) const ast = transformWithCodegen(`{{ foo }}`)
expect(ast.codegenNode).toMatchObject({ expect(ast.codegenNode).toMatchObject({
type: NodeTypes.INTERPOLATION type: NodeTypes.INTERPOLATION,
}) })
}) })
test('single CompoundExpression', () => { test('single CompoundExpression', () => {
const ast = transformWithCodegen(`{{ foo }} bar baz`) const ast = transformWithCodegen(`{{ foo }} bar baz`)
expect(ast.codegenNode).toMatchObject({ expect(ast.codegenNode).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION type: NodeTypes.COMPOUND_EXPRESSION,
}) })
}) })
@ -356,10 +356,10 @@ describe('compiler: transform', () => {
undefined, undefined,
[ [
{ type: NodeTypes.ELEMENT, tag: `div` }, { type: NodeTypes.ELEMENT, tag: `div` },
{ type: NodeTypes.ELEMENT, tag: `div` } { type: NodeTypes.ELEMENT, tag: `div` },
] as any, ] as any,
genFlagText(PatchFlags.STABLE_FRAGMENT) genFlagText(PatchFlags.STABLE_FRAGMENT),
) ),
) )
}) })
@ -372,13 +372,13 @@ describe('compiler: transform', () => {
[ [
{ type: NodeTypes.COMMENT }, { type: NodeTypes.COMMENT },
{ type: NodeTypes.ELEMENT, tag: `div` }, { type: NodeTypes.ELEMENT, tag: `div` },
{ type: NodeTypes.COMMENT } { type: NodeTypes.COMMENT },
] as any, ] as any,
genFlagText([ genFlagText([
PatchFlags.STABLE_FRAGMENT, PatchFlags.STABLE_FRAGMENT,
PatchFlags.DEV_ROOT_FRAGMENT PatchFlags.DEV_ROOT_FRAGMENT,
]) ]),
) ),
) )
}) })
}) })

View File

@ -1,19 +1,19 @@
import { import {
type CompilerOptions,
ConstantTypes,
type ElementNode,
type ForNode,
type IfNode,
NodeTypes,
type VNodeCall,
generate,
baseParse as parse, baseParse as parse,
transform, transform,
NodeTypes,
generate,
CompilerOptions,
VNodeCall,
IfNode,
ElementNode,
ForNode,
ConstantTypes
} from '../../src' } from '../../src'
import { import {
FRAGMENT, FRAGMENT,
NORMALIZE_CLASS,
RENDER_LIST, RENDER_LIST,
NORMALIZE_CLASS
} from '../../src/runtimeHelpers' } from '../../src/runtimeHelpers'
import { transformElement } from '../../src/transforms/transformElement' import { transformElement } from '../../src/transforms/transformElement'
import { transformExpression } from '../../src/transforms/transformExpression' import { transformExpression } from '../../src/transforms/transformExpression'
@ -31,9 +31,9 @@ const hoistedChildrenArrayMatcher = (startIndex = 1, length = 1) => ({
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,
codegenNode: { codegenNode: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_${startIndex + i}` content: `_hoisted_${startIndex + i}`,
} },
})) })),
}) })
function transformWithHoist(template: string, options: CompilerOptions = {}) { function transformWithHoist(template: string, options: CompilerOptions = {}) {
@ -45,17 +45,17 @@ function transformWithHoist(template: string, options: CompilerOptions = {}) {
transformFor, transformFor,
...(options.prefixIdentifiers ? [transformExpression] : []), ...(options.prefixIdentifiers ? [transformExpression] : []),
transformElement, transformElement,
transformText transformText,
], ],
directiveTransforms: { directiveTransforms: {
on: transformOn, on: transformOn,
bind: transformBind bind: transformBind,
}, },
...options ...options,
}) })
expect(ast.codegenNode).toMatchObject({ expect(ast.codegenNode).toMatchObject({
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
isBlock: true isBlock: true,
}) })
return ast return ast
} }
@ -67,14 +67,14 @@ describe('compiler: hoistStatic transform', () => {
const root = transformWithHoist(`<div/>`) const root = transformWithHoist(`<div/>`)
expect(root.hoists.length).toBe(0) expect(root.hoists.length).toBe(0)
expect(root.codegenNode).toMatchObject({ expect(root.codegenNode).toMatchObject({
tag: `"div"` tag: `"div"`,
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
test('hoist simple element', () => { test('hoist simple element', () => {
const root = transformWithHoist( const root = transformWithHoist(
`<div><span class="inline">hello</span></div>` `<div><span class="inline">hello</span></div>`,
) )
expect(root.hoists).toMatchObject([ expect(root.hoists).toMatchObject([
{ {
@ -83,15 +83,15 @@ describe('compiler: hoistStatic transform', () => {
props: createObjectMatcher({ class: 'inline' }), props: createObjectMatcher({ class: 'inline' }),
children: { children: {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: `hello` content: `hello`,
}
}, },
hoistedChildrenArrayMatcher() },
hoistedChildrenArrayMatcher(),
]) ])
expect(root.codegenNode).toMatchObject({ expect(root.codegenNode).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: undefined, props: undefined,
children: { content: `_hoisted_2` } children: { content: `_hoisted_2` },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -105,13 +105,13 @@ describe('compiler: hoistStatic transform', () => {
props: undefined, props: undefined,
children: [ children: [
{ type: NodeTypes.ELEMENT, tag: `span` }, { type: NodeTypes.ELEMENT, tag: `span` },
{ type: NodeTypes.ELEMENT, tag: `span` } { type: NodeTypes.ELEMENT, tag: `span` },
] ],
}, },
hoistedChildrenArrayMatcher() hoistedChildrenArrayMatcher(),
]) ])
expect((root.codegenNode as VNodeCall).children).toMatchObject({ expect((root.codegenNode as VNodeCall).children).toMatchObject({
content: '_hoisted_2' content: '_hoisted_2',
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -123,12 +123,12 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"`, tag: `"div"`,
props: undefined, props: undefined,
children: [{ type: NodeTypes.COMMENT, content: `comment` }] children: [{ type: NodeTypes.COMMENT, content: `comment` }],
}, },
hoistedChildrenArrayMatcher() hoistedChildrenArrayMatcher(),
]) ])
expect((root.codegenNode as VNodeCall).children).toMatchObject({ expect((root.codegenNode as VNodeCall).children).toMatchObject({
content: `_hoisted_2` content: `_hoisted_2`,
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -138,16 +138,16 @@ describe('compiler: hoistStatic transform', () => {
expect(root.hoists).toMatchObject([ expect(root.hoists).toMatchObject([
{ {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"span"` tag: `"span"`,
}, },
{ {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"` tag: `"div"`,
}, },
hoistedChildrenArrayMatcher(1, 2) hoistedChildrenArrayMatcher(1, 2),
]) ])
expect((root.codegenNode as VNodeCall).children).toMatchObject({ expect((root.codegenNode as VNodeCall).children).toMatchObject({
content: '_hoisted_3' content: '_hoisted_3',
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -160,9 +160,9 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,
codegenNode: { codegenNode: {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `_component_Comp` tag: `_component_Comp`,
} },
} },
]) ])
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -177,17 +177,17 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ props: createObjectMatcher({
id: `[foo]` id: `[foo]`,
}), }),
children: undefined, children: undefined,
patchFlag: genFlagText(PatchFlags.PROPS), patchFlag: genFlagText(PatchFlags.PROPS),
dynamicProps: { dynamicProps: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1`, content: `_hoisted_1`,
isStatic: false isStatic: false,
} },
} },
} },
]) ])
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -199,14 +199,14 @@ describe('compiler: hoistStatic transform', () => {
{ {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ key: 'foo' }) props: createObjectMatcher({ key: 'foo' }),
}, },
hoistedChildrenArrayMatcher() hoistedChildrenArrayMatcher(),
]) ])
expect(root.codegenNode).toMatchObject({ expect(root.codegenNode).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: undefined, props: undefined,
children: { content: `_hoisted_2` } children: { content: `_hoisted_2` },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -221,10 +221,10 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ props: createObjectMatcher({
key: `[foo]` key: `[foo]`,
}) }),
} },
} },
]) ])
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -239,12 +239,12 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ props: createObjectMatcher({
ref: `[foo]` ref: `[foo]`,
}), }),
children: undefined, children: undefined,
patchFlag: genFlagText(PatchFlags.NEED_PATCH) patchFlag: genFlagText(PatchFlags.NEED_PATCH),
} },
} },
]) ])
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -260,22 +260,22 @@ describe('compiler: hoistStatic transform', () => {
tag: `"div"`, tag: `"div"`,
props: { props: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1` content: `_hoisted_1`,
}, },
children: undefined, children: undefined,
patchFlag: genFlagText(PatchFlags.NEED_PATCH), patchFlag: genFlagText(PatchFlags.NEED_PATCH),
directives: { directives: {
type: NodeTypes.JS_ARRAY_EXPRESSION type: NodeTypes.JS_ARRAY_EXPRESSION,
} },
} },
} },
]) ])
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
test('hoist static props for elements with dynamic text children', () => { test('hoist static props for elements with dynamic text children', () => {
const root = transformWithHoist( const root = transformWithHoist(
`<div><div id="foo">{{ hello }}</div></div>` `<div><div id="foo">{{ hello }}</div></div>`,
) )
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })]) expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
expect((root.codegenNode as VNodeCall).children).toMatchObject([ expect((root.codegenNode as VNodeCall).children).toMatchObject([
@ -286,9 +286,9 @@ describe('compiler: hoistStatic transform', () => {
tag: `"div"`, tag: `"div"`,
props: { content: `_hoisted_1` }, props: { content: `_hoisted_1` },
children: { type: NodeTypes.INTERPOLATION }, children: { type: NodeTypes.INTERPOLATION },
patchFlag: genFlagText(PatchFlags.TEXT) patchFlag: genFlagText(PatchFlags.TEXT),
} },
} },
]) ])
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -303,30 +303,30 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"`, tag: `"div"`,
props: { content: `_hoisted_1` }, props: { content: `_hoisted_1` },
children: [{ type: NodeTypes.ELEMENT, tag: `Comp` }] children: [{ type: NodeTypes.ELEMENT, tag: `Comp` }],
} },
} },
]) ])
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
test('should hoist v-if props/children if static', () => { test('should hoist v-if props/children if static', () => {
const root = transformWithHoist( const root = transformWithHoist(
`<div><div v-if="ok" id="foo"><span/></div></div>` `<div><div v-if="ok" id="foo"><span/></div></div>`,
) )
expect(root.hoists).toMatchObject([ expect(root.hoists).toMatchObject([
createObjectMatcher({ createObjectMatcher({
key: `[0]`, // key injected by v-if branch key: `[0]`, // key injected by v-if branch
id: 'foo' id: 'foo',
}), }),
{ {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"span"` tag: `"span"`,
}, },
hoistedChildrenArrayMatcher(2) hoistedChildrenArrayMatcher(2),
]) ])
expect( expect(
((root.children[0] as ElementNode).children[0] as IfNode).codegenNode ((root.children[0] as ElementNode).children[0] as IfNode).codegenNode,
).toMatchObject({ ).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION, type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
consequent: { consequent: {
@ -334,25 +334,25 @@ describe('compiler: hoistStatic transform', () => {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"`, tag: `"div"`,
props: { content: `_hoisted_1` }, props: { content: `_hoisted_1` },
children: { content: `_hoisted_3` } children: { content: `_hoisted_3` },
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
test('should hoist v-for children if static', () => { test('should hoist v-for children if static', () => {
const root = transformWithHoist( const root = transformWithHoist(
`<div><div v-for="i in list" id="foo"><span/></div></div>` `<div><div v-for="i in list" id="foo"><span/></div></div>`,
) )
expect(root.hoists).toMatchObject([ expect(root.hoists).toMatchObject([
createObjectMatcher({ createObjectMatcher({
id: 'foo' id: 'foo',
}), }),
{ {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"span"` tag: `"span"`,
}, },
hoistedChildrenArrayMatcher(2) hoistedChildrenArrayMatcher(2),
]) ])
const forBlockCodegen = ( const forBlockCodegen = (
(root.children[0] as ElementNode).children[0] as ForNode (root.children[0] as ElementNode).children[0] as ForNode
@ -363,16 +363,16 @@ describe('compiler: hoistStatic transform', () => {
props: undefined, props: undefined,
children: { children: {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_LIST callee: RENDER_LIST,
}, },
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT) patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
}) })
const innerBlockCodegen = forBlockCodegen!.children.arguments[1] const innerBlockCodegen = forBlockCodegen!.children.arguments[1]
expect(innerBlockCodegen.returns).toMatchObject({ expect(innerBlockCodegen.returns).toMatchObject({
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"`, tag: `"div"`,
props: { content: `_hoisted_1` }, props: { content: `_hoisted_1` },
children: { content: `_hoisted_3` } children: { content: `_hoisted_3` },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -382,8 +382,8 @@ describe('compiler: hoistStatic transform', () => {
const root = transformWithHoist( const root = transformWithHoist(
`<div><span>foo {{ 1 }} {{ true }}</span></div>`, `<div><span>foo {{ 1 }} {{ true }}</span></div>`,
{ {
prefixIdentifiers: true prefixIdentifiers: true,
} },
) )
expect(root.hoists).toMatchObject([ expect(root.hoists).toMatchObject([
{ {
@ -391,18 +391,18 @@ describe('compiler: hoistStatic transform', () => {
tag: `"span"`, tag: `"span"`,
props: undefined, props: undefined,
children: { children: {
type: NodeTypes.COMPOUND_EXPRESSION type: NodeTypes.COMPOUND_EXPRESSION,
}
}, },
hoistedChildrenArrayMatcher() },
hoistedChildrenArrayMatcher(),
]) ])
expect(root.codegenNode).toMatchObject({ expect(root.codegenNode).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: undefined, props: undefined,
children: { children: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_2` content: `_hoisted_2`,
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -411,8 +411,8 @@ describe('compiler: hoistStatic transform', () => {
const root = transformWithHoist( const root = transformWithHoist(
`<div><span :foo="0">{{ 1 }}</span></div>`, `<div><span :foo="0">{{ 1 }}</span></div>`,
{ {
prefixIdentifiers: true prefixIdentifiers: true,
} },
) )
expect(root.hoists).toMatchObject([ expect(root.hoists).toMatchObject([
@ -425,19 +425,19 @@ describe('compiler: hoistStatic transform', () => {
content: { content: {
content: `1`, content: `1`,
isStatic: false, isStatic: false,
constType: ConstantTypes.CAN_STRINGIFY constType: ConstantTypes.CAN_STRINGIFY,
}
}
}, },
hoistedChildrenArrayMatcher() },
},
hoistedChildrenArrayMatcher(),
]) ])
expect(root.codegenNode).toMatchObject({ expect(root.codegenNode).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: undefined, props: undefined,
children: { children: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_2` content: `_hoisted_2`,
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -446,8 +446,8 @@ describe('compiler: hoistStatic transform', () => {
const root = transformWithHoist( const root = transformWithHoist(
`<div><span :class="{ foo: true }">{{ bar }}</span></div>`, `<div><span :class="{ foo: true }">{{ bar }}</span></div>`,
{ {
prefixIdentifiers: true prefixIdentifiers: true,
} },
) )
expect(root.hoists).toMatchObject([ expect(root.hoists).toMatchObject([
@ -458,7 +458,7 @@ describe('compiler: hoistStatic transform', () => {
key: { key: {
content: `class`, content: `class`,
isStatic: true, isStatic: true,
constType: ConstantTypes.CAN_STRINGIFY constType: ConstantTypes.CAN_STRINGIFY,
}, },
value: { value: {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
@ -467,13 +467,13 @@ describe('compiler: hoistStatic transform', () => {
{ {
content: `{ foo: true }`, content: `{ foo: true }`,
isStatic: false, isStatic: false,
constType: ConstantTypes.CAN_STRINGIFY constType: ConstantTypes.CAN_STRINGIFY,
} },
] ],
} },
} },
] ],
} },
]) ])
expect(root.codegenNode).toMatchObject({ expect(root.codegenNode).toMatchObject({
tag: `"div"`, tag: `"div"`,
@ -486,20 +486,20 @@ describe('compiler: hoistStatic transform', () => {
tag: `"span"`, tag: `"span"`,
props: { props: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_hoisted_1` content: `_hoisted_1`,
}, },
children: { children: {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `_ctx.bar`, content: `_ctx.bar`,
isStatic: false, isStatic: false,
constType: ConstantTypes.NOT_CONSTANT constType: ConstantTypes.NOT_CONSTANT,
}
}, },
patchFlag: `1 /* TEXT */` },
} patchFlag: `1 /* TEXT */`,
} },
] },
],
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -508,8 +508,8 @@ describe('compiler: hoistStatic transform', () => {
const root = transformWithHoist( const root = transformWithHoist(
`<div><p v-for="o in list"><span>{{ o }}</span></p></div>`, `<div><p v-for="o in list"><span>{{ o }}</span></p></div>`,
{ {
prefixIdentifiers: true prefixIdentifiers: true,
} },
) )
expect(root.hoists.length).toBe(0) expect(root.hoists.length).toBe(0)
@ -520,8 +520,8 @@ describe('compiler: hoistStatic transform', () => {
const root = transformWithHoist( const root = transformWithHoist(
`<div><p v-for="o in list"><span>{{ o + 'foo' }}</span></p></div>`, `<div><p v-for="o in list"><span>{{ o + 'foo' }}</span></p></div>`,
{ {
prefixIdentifiers: true prefixIdentifiers: true,
} },
) )
expect(root.hoists.length).toBe(0) expect(root.hoists.length).toBe(0)
@ -532,8 +532,8 @@ describe('compiler: hoistStatic transform', () => {
const root = transformWithHoist( const root = transformWithHoist(
`<Comp v-slot="{ foo }">{{ foo }}</Comp>`, `<Comp v-slot="{ foo }">{{ foo }}</Comp>`,
{ {
prefixIdentifiers: true prefixIdentifiers: true,
} },
) )
expect(root.hoists.length).toBe(0) expect(root.hoists.length).toBe(0)
@ -545,8 +545,8 @@ describe('compiler: hoistStatic transform', () => {
`<div><div><div @click="foo"/></div></div>`, `<div><div><div @click="foo"/></div></div>`,
{ {
prefixIdentifiers: true, prefixIdentifiers: true,
cacheHandlers: true cacheHandlers: true,
} },
) )
expect(root.cached).toBe(1) expect(root.cached).toBe(1)
@ -554,8 +554,8 @@ describe('compiler: hoistStatic transform', () => {
expect( expect(
generate(root, { generate(root, {
mode: 'module', mode: 'module',
prefixIdentifiers: true prefixIdentifiers: true,
}).code }).code,
).toMatchSnapshot() ).toMatchSnapshot()
}) })
@ -564,8 +564,8 @@ describe('compiler: hoistStatic transform', () => {
`<div><div><div :class="{}" @click="foo"/></div></div>`, `<div><div><div :class="{}" @click="foo"/></div></div>`,
{ {
prefixIdentifiers: true, prefixIdentifiers: true,
cacheHandlers: true cacheHandlers: true,
} },
) )
expect(root.cached).toBe(1) expect(root.cached).toBe(1)
@ -573,14 +573,14 @@ describe('compiler: hoistStatic transform', () => {
expect( expect(
generate(root, { generate(root, {
mode: 'module', mode: 'module',
prefixIdentifiers: true prefixIdentifiers: true,
}).code }).code,
).toMatchSnapshot() ).toMatchSnapshot()
}) })
test('should NOT hoist keyed template v-for with plain element child', () => { test('should NOT hoist keyed template v-for with plain element child', () => {
const root = transformWithHoist( const root = transformWithHoist(
`<div><template v-for="item in items" :key="item"><span/></template></div>` `<div><template v-for="item in items" :key="item"><span/></template></div>`,
) )
expect(root.hoists.length).toBe(0) expect(root.hoists.length).toBe(0)
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
@ -588,7 +588,7 @@ describe('compiler: hoistStatic transform', () => {
test('should NOT hoist SVG with directives', () => { test('should NOT hoist SVG with directives', () => {
const root = transformWithHoist( const root = transformWithHoist(
`<div><svg v-foo><path d="M2,3H5.5L12"/></svg></div>` `<div><svg v-foo><path d="M2,3H5.5L12"/></svg></div>`,
) )
expect(root.hoists.length).toBe(2) expect(root.hoists.length).toBe(2)
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
@ -596,13 +596,13 @@ describe('compiler: hoistStatic transform', () => {
test('clone hoisted array children in HMR mode', () => { test('clone hoisted array children in HMR mode', () => {
const root = transformWithHoist(`<div><span class="hi"></span></div>`, { const root = transformWithHoist(`<div><span class="hi"></span></div>`, {
hmr: true hmr: true,
}) })
expect(root.hoists.length).toBe(2) expect(root.hoists.length).toBe(2)
expect(root.codegenNode).toMatchObject({ expect(root.codegenNode).toMatchObject({
children: { children: {
content: '[..._hoisted_2]' content: '[..._hoisted_2]',
} },
}) })
}) })
}) })

View File

@ -1,9 +1,9 @@
import { import {
type ElementNode,
type VNodeCall,
noopDirectiveTransform,
baseParse as parse, baseParse as parse,
transform, transform,
ElementNode,
noopDirectiveTransform,
VNodeCall
} from '../../src' } from '../../src'
import { transformElement } from '../../src/transforms/transformElement' import { transformElement } from '../../src/transforms/transformElement'
@ -13,8 +13,8 @@ describe('compiler: noop directive transform', () => {
transform(ast, { transform(ast, {
nodeTransforms: [transformElement], nodeTransforms: [transformElement],
directiveTransforms: { directiveTransforms: {
noop: noopDirectiveTransform noop: noopDirectiveTransform,
} },
}) })
const node = ast.children[0] as ElementNode const node = ast.children[0] as ElementNode
// As v-noop adds no properties the codegen should be identical to // As v-noop adds no properties the codegen should be identical to

View File

@ -1,14 +1,14 @@
import { import {
BindingTypes,
type CompilerOptions,
ConstantTypes,
type DirectiveNode,
type ElementNode,
type InterpolationNode,
NodeTypes,
baseCompile,
baseParse as parse, baseParse as parse,
transform, transform,
ElementNode,
DirectiveNode,
NodeTypes,
CompilerOptions,
InterpolationNode,
ConstantTypes,
BindingTypes,
baseCompile
} from '../../src' } from '../../src'
import { transformIf } from '../../src/transforms/vIf' import { transformIf } from '../../src/transforms/vIf'
import { transformExpression } from '../../src/transforms/transformExpression' import { transformExpression } from '../../src/transforms/transformExpression'
@ -16,13 +16,13 @@ import { PatchFlagNames, PatchFlags } from '../../../shared/src'
function parseWithExpressionTransform( function parseWithExpressionTransform(
template: string, template: string,
options: CompilerOptions = {} options: CompilerOptions = {},
) { ) {
const ast = parse(template, options) const ast = parse(template, options)
transform(ast, { transform(ast, {
prefixIdentifiers: true, prefixIdentifiers: true,
nodeTransforms: [transformIf, transformExpression], nodeTransforms: [transformIf, transformExpression],
...options ...options,
}) })
return ast.children[0] return ast.children[0]
} }
@ -32,7 +32,7 @@ describe('compiler: expression transform', () => {
const node = parseWithExpressionTransform(`{{ foo }}`) as InterpolationNode const node = parseWithExpressionTransform(`{{ foo }}`) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.foo` content: `_ctx.foo`,
}) })
}) })
@ -40,34 +40,34 @@ describe('compiler: expression transform', () => {
const node = parseWithExpressionTransform(`{{}}`) as InterpolationNode const node = parseWithExpressionTransform(`{{}}`) as InterpolationNode
const node2 = parseWithExpressionTransform(`{{ }}`) as InterpolationNode const node2 = parseWithExpressionTransform(`{{ }}`) as InterpolationNode
const node3 = parseWithExpressionTransform( const node3 = parseWithExpressionTransform(
`<div>{{ }}</div>` `<div>{{ }}</div>`,
) as ElementNode ) as ElementNode
const objectToBeMatched = { const objectToBeMatched = {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `` content: ``,
} }
expect(node.content).toMatchObject(objectToBeMatched) expect(node.content).toMatchObject(objectToBeMatched)
expect(node2.content).toMatchObject(objectToBeMatched) expect(node2.content).toMatchObject(objectToBeMatched)
expect((node3.children[0] as InterpolationNode).content).toMatchObject( expect((node3.children[0] as InterpolationNode).content).toMatchObject(
objectToBeMatched objectToBeMatched,
) )
}) })
test('interpolation (children)', () => { test('interpolation (children)', () => {
const el = parseWithExpressionTransform( const el = parseWithExpressionTransform(
`<div>{{ foo }}</div>` `<div>{{ foo }}</div>`,
) as ElementNode ) as ElementNode
const node = el.children[0] as InterpolationNode const node = el.children[0] as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.foo` content: `_ctx.foo`,
}) })
}) })
test('interpolation (complex)', () => { test('interpolation (complex)', () => {
const el = parseWithExpressionTransform( const el = parseWithExpressionTransform(
`<div>{{ foo + bar(baz.qux) }}</div>` `<div>{{ foo + bar(baz.qux) }}</div>`,
) as ElementNode ) as ElementNode
const node = el.children[0] as InterpolationNode const node = el.children[0] as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
@ -80,46 +80,46 @@ describe('compiler: expression transform', () => {
{ content: `_ctx.baz` }, { content: `_ctx.baz` },
`.`, `.`,
{ content: `qux` }, { content: `qux` },
`)` `)`,
] ],
}) })
}) })
test('directive value', () => { test('directive value', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`<div v-foo:arg="baz"/>` `<div v-foo:arg="baz"/>`,
) as ElementNode ) as ElementNode
const arg = (node.props[0] as DirectiveNode).arg! const arg = (node.props[0] as DirectiveNode).arg!
expect(arg).toMatchObject({ expect(arg).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `arg` content: `arg`,
}) })
const exp = (node.props[0] as DirectiveNode).exp! const exp = (node.props[0] as DirectiveNode).exp!
expect(exp).toMatchObject({ expect(exp).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.baz` content: `_ctx.baz`,
}) })
}) })
test('dynamic directive arg', () => { test('dynamic directive arg', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`<div v-foo:[arg]="baz"/>` `<div v-foo:[arg]="baz"/>`,
) as ElementNode ) as ElementNode
const arg = (node.props[0] as DirectiveNode).arg! const arg = (node.props[0] as DirectiveNode).arg!
expect(arg).toMatchObject({ expect(arg).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.arg` content: `_ctx.arg`,
}) })
const exp = (node.props[0] as DirectiveNode).exp! const exp = (node.props[0] as DirectiveNode).exp!
expect(exp).toMatchObject({ expect(exp).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.baz` content: `_ctx.baz`,
}) })
}) })
test('should prefix complex expressions', () => { test('should prefix complex expressions', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ foo(baz + 1, { key: kuz }) }}` `{{ foo(baz + 1, { key: kuz }) }}`,
) as InterpolationNode ) as InterpolationNode
// should parse into compound expression // should parse into compound expression
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
@ -129,56 +129,56 @@ describe('compiler: expression transform', () => {
content: `_ctx.foo`, content: `_ctx.foo`,
loc: { loc: {
start: { offset: 3, line: 1, column: 4 }, start: { offset: 3, line: 1, column: 4 },
end: { offset: 6, line: 1, column: 7 } end: { offset: 6, line: 1, column: 7 },
} },
}, },
`(`, `(`,
{ {
content: `_ctx.baz`, content: `_ctx.baz`,
loc: { loc: {
start: { offset: 7, line: 1, column: 8 }, start: { offset: 7, line: 1, column: 8 },
end: { offset: 10, line: 1, column: 11 } end: { offset: 10, line: 1, column: 11 },
} },
}, },
` + 1, { key: `, ` + 1, { key: `,
{ {
content: `_ctx.kuz`, content: `_ctx.kuz`,
loc: { loc: {
start: { offset: 23, line: 1, column: 24 }, start: { offset: 23, line: 1, column: 24 },
end: { offset: 26, line: 1, column: 27 } end: { offset: 26, line: 1, column: 27 },
}
}, },
` })` },
] ` })`,
],
}) })
}) })
test('should not prefix whitelisted globals', () => { test('should not prefix whitelisted globals', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ Math.max(1, 2) }}` `{{ Math.max(1, 2) }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [{ content: `Math` }, `.`, { content: `max` }, `(1, 2)`] children: [{ content: `Math` }, `.`, { content: `max` }, `(1, 2)`],
}) })
expect( expect(
(parseWithExpressionTransform(`{{ new Error() }}`) as InterpolationNode) (parseWithExpressionTransform(`{{ new Error() }}`) as InterpolationNode)
.content .content,
).toMatchObject({ ).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: ['new ', { content: 'Error' }, '()'] children: ['new ', { content: 'Error' }, '()'],
}) })
}) })
test('should not prefix reserved literals', () => { test('should not prefix reserved literals', () => {
function assert(exp: string) { function assert(exp: string) {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ ${exp} }}` `{{ ${exp} }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: exp content: exp,
}) })
} }
assert(`true`) assert(`true`)
@ -189,7 +189,7 @@ describe('compiler: expression transform', () => {
test('should not prefix id of a function declaration', () => { test('should not prefix id of a function declaration', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ function foo() { return bar } }}` `{{ function foo() { return bar } }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
@ -198,14 +198,14 @@ describe('compiler: expression transform', () => {
{ content: `foo` }, { content: `foo` },
`() { return `, `() { return `,
{ content: `_ctx.bar` }, { content: `_ctx.bar` },
` }` ` }`,
] ],
}) })
}) })
test('should not prefix params of a function expression', () => { test('should not prefix params of a function expression', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ foo => foo + bar }}` `{{ foo => foo + bar }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
@ -214,14 +214,14 @@ describe('compiler: expression transform', () => {
` => `, ` => `,
{ content: `foo` }, { content: `foo` },
` + `, ` + `,
{ content: `_ctx.bar` } { content: `_ctx.bar` },
] ],
}) })
}) })
test('should prefix default value of a function expression param', () => { test('should prefix default value of a function expression param', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ (foo = baz) => foo + bar }}` `{{ (foo = baz) => foo + bar }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
@ -233,14 +233,14 @@ describe('compiler: expression transform', () => {
`) => `, `) => `,
{ content: `foo` }, { content: `foo` },
` + `, ` + `,
{ content: `_ctx.bar` } { content: `_ctx.bar` },
] ],
}) })
}) })
test('should not prefix function param destructuring', () => { test('should not prefix function param destructuring', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ ({ foo }) => foo + bar }}` `{{ ({ foo }) => foo + bar }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
@ -250,14 +250,14 @@ describe('compiler: expression transform', () => {
` }) => `, ` }) => `,
{ content: `foo` }, { content: `foo` },
` + `, ` + `,
{ content: `_ctx.bar` } { content: `_ctx.bar` },
] ],
}) })
}) })
test('function params should not affect out of scope identifiers', () => { test('function params should not affect out of scope identifiers', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ { a: foo => foo, b: foo } }}` `{{ { a: foo => foo, b: foo } }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
@ -268,14 +268,14 @@ describe('compiler: expression transform', () => {
{ content: `foo` }, { content: `foo` },
`, b: `, `, b: `,
{ content: `_ctx.foo` }, { content: `_ctx.foo` },
` }` ` }`,
] ],
}) })
}) })
test('should prefix default value of function param destructuring', () => { test('should prefix default value of function param destructuring', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ ({ foo = bar }) => foo + bar }}` `{{ ({ foo = bar }) => foo + bar }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
@ -287,13 +287,13 @@ describe('compiler: expression transform', () => {
` }) => `, ` }) => `,
{ content: `foo` }, { content: `foo` },
` + `, ` + `,
{ content: `_ctx.bar` } { content: `_ctx.bar` },
] ],
}) })
}) })
test('should not prefix an object property key', () => { test('should not prefix an object property key', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ { foo() { baz() }, value: bar } }}` `{{ { foo() { baz() }, value: bar } }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
@ -302,24 +302,24 @@ describe('compiler: expression transform', () => {
{ content: `_ctx.baz` }, { content: `_ctx.baz` },
`() }, value: `, `() }, value: `,
{ content: `_ctx.bar` }, { content: `_ctx.bar` },
` }` ` }`,
] ],
}) })
}) })
test('should not duplicate object key with same name as value', () => { test('should not duplicate object key with same name as value', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ { foo: foo } }}` `{{ { foo: foo } }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ foo: `, { content: `_ctx.foo` }, ` }`] children: [`{ foo: `, { content: `_ctx.foo` }, ` }`],
}) })
}) })
test('should prefix a computed object property key', () => { test('should prefix a computed object property key', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ { [foo]: bar } }}` `{{ { [foo]: bar } }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
@ -328,24 +328,24 @@ describe('compiler: expression transform', () => {
{ content: `_ctx.foo` }, { content: `_ctx.foo` },
`]: `, `]: `,
{ content: `_ctx.bar` }, { content: `_ctx.bar` },
` }` ` }`,
] ],
}) })
}) })
test('should prefix object property shorthand value', () => { test('should prefix object property shorthand value', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ { foo } }}` `{{ { foo } }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ foo: `, { content: `_ctx.foo` }, ` }`] children: [`{ foo: `, { content: `_ctx.foo` }, ` }`],
}) })
}) })
test('should not prefix id in a member expression', () => { test('should not prefix id in a member expression', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ foo.bar.baz }}` `{{ foo.bar.baz }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
@ -354,14 +354,14 @@ describe('compiler: expression transform', () => {
`.`, `.`,
{ content: `bar` }, { content: `bar` },
`.`, `.`,
{ content: `baz` } { content: `baz` },
] ],
}) })
}) })
test('should prefix computed id in a member expression', () => { test('should prefix computed id in a member expression', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ foo[bar][baz] }}` `{{ foo[bar][baz] }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
@ -371,8 +371,8 @@ describe('compiler: expression transform', () => {
{ content: `_ctx.bar` }, { content: `_ctx.bar` },
`][`, `][`,
{ content: '_ctx.baz' }, { content: '_ctx.baz' },
`]` `]`,
] ],
}) })
}) })
@ -380,23 +380,23 @@ describe('compiler: expression transform', () => {
const onError = vi.fn() const onError = vi.fn()
parseWithExpressionTransform(`{{ a( }}`, { onError }) parseWithExpressionTransform(`{{ a( }}`, { onError })
expect(onError.mock.calls[0][0].message).toMatch( expect(onError.mock.calls[0][0].message).toMatch(
`Error parsing JavaScript expression: Unexpected token` `Error parsing JavaScript expression: Unexpected token`,
) )
}) })
test('should prefix in assignment', () => { test('should prefix in assignment', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ x = 1 }}` `{{ x = 1 }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [{ content: `_ctx.x` }, ` = 1`] children: [{ content: `_ctx.x` }, ` = 1`],
}) })
}) })
test('should prefix in assignment pattern', () => { test('should prefix in assignment pattern', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ { x, y: [z] } = obj }}` `{{ { x, y: [z] } = obj }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
@ -406,47 +406,47 @@ describe('compiler: expression transform', () => {
`, y: [`, `, y: [`,
{ content: `_ctx.z` }, { content: `_ctx.z` },
`] } = `, `] } = `,
{ content: `_ctx.obj` } { content: `_ctx.obj` },
] ],
}) })
}) })
// #8295 // #8295
test('should treat floating point number literals as constant', () => { test('should treat floating point number literals as constant', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ [1, 2.1] }}` `{{ [1, 2.1] }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
constType: ConstantTypes.CAN_STRINGIFY constType: ConstantTypes.CAN_STRINGIFY,
}) })
}) })
describe('ES Proposals support', () => { describe('ES Proposals support', () => {
test('bigInt', () => { test('bigInt', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ 13000n }}` `{{ 13000n }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `13000n`, content: `13000n`,
isStatic: false, isStatic: false,
constType: ConstantTypes.CAN_STRINGIFY constType: ConstantTypes.CAN_STRINGIFY,
}) })
}) })
test('nullish coalescing', () => { test('nullish coalescing', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ a ?? b }}` `{{ a ?? b }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [{ content: `_ctx.a` }, ` ?? `, { content: `_ctx.b` }] children: [{ content: `_ctx.a` }, ` ?? `, { content: `_ctx.b` }],
}) })
}) })
test('optional chaining', () => { test('optional chaining', () => {
const node = parseWithExpressionTransform( const node = parseWithExpressionTransform(
`{{ a?.b?.c }}` `{{ a?.b?.c }}`,
) as InterpolationNode ) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
@ -455,8 +455,8 @@ describe('compiler: expression transform', () => {
`?.`, `?.`,
{ content: `b` }, { content: `b` },
`?.`, `?.`,
{ content: `c` } { content: `c` },
] ],
}) })
}) })
@ -467,14 +467,18 @@ describe('compiler: expression transform', () => {
[ [
'pipelineOperator', 'pipelineOperator',
{ {
proposal: 'minimal' proposal: 'minimal',
} },
] ],
] ],
}) as InterpolationNode }) as InterpolationNode
expect(node.content).toMatchObject({ expect(node.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [{ content: `_ctx.a` }, ` |> `, { content: `_ctx.uppercase` }] children: [
{ content: `_ctx.a` },
` |> `,
{ content: `_ctx.uppercase` },
],
}) })
}) })
}) })
@ -488,23 +492,23 @@ describe('compiler: expression transform', () => {
options: BindingTypes.OPTIONS, options: BindingTypes.OPTIONS,
reactive: BindingTypes.SETUP_REACTIVE_CONST, reactive: BindingTypes.SETUP_REACTIVE_CONST,
literal: BindingTypes.LITERAL_CONST, literal: BindingTypes.LITERAL_CONST,
isNaN: BindingTypes.SETUP_REF isNaN: BindingTypes.SETUP_REF,
} }
function compileWithBindingMetadata( function compileWithBindingMetadata(
template: string, template: string,
options?: CompilerOptions options?: CompilerOptions,
) { ) {
return baseCompile(template, { return baseCompile(template, {
prefixIdentifiers: true, prefixIdentifiers: true,
bindingMetadata, bindingMetadata,
...options ...options,
}) })
} }
test('non-inline mode', () => { test('non-inline mode', () => {
const { code } = compileWithBindingMetadata( const { code } = compileWithBindingMetadata(
`<div>{{ props }} {{ setup }} {{ data }} {{ options }} {{ isNaN }}</div>` `<div>{{ props }} {{ setup }} {{ data }} {{ options }} {{ isNaN }}</div>`,
) )
expect(code).toMatch(`$props.props`) expect(code).toMatch(`$props.props`)
expect(code).toMatch(`$setup.setup`) expect(code).toMatch(`$setup.setup`)
@ -521,7 +525,7 @@ describe('compiler: expression transform', () => {
for (const x in list) { for (const x in list) {
log(x) log(x)
} }
}"/>` }"/>`,
) )
expect(code).not.toMatch(`_ctx.x`) expect(code).not.toMatch(`_ctx.x`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -533,7 +537,7 @@ describe('compiler: expression transform', () => {
for (const x of list) { for (const x of list) {
log(x) log(x)
} }
}"/>` }"/>`,
) )
expect(code).not.toMatch(`_ctx.x`) expect(code).not.toMatch(`_ctx.x`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -545,7 +549,7 @@ describe('compiler: expression transform', () => {
for (let i = 0; i < list.length; i++) { for (let i = 0; i < list.length; i++) {
log(i) log(i)
} }
}"/>` }"/>`,
) )
expect(code).not.toMatch(`_ctx.i`) expect(code).not.toMatch(`_ctx.i`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -554,7 +558,7 @@ describe('compiler: expression transform', () => {
test('inline mode', () => { test('inline mode', () => {
const { code } = compileWithBindingMetadata( const { code } = compileWithBindingMetadata(
`<div>{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }} {{ isNaN }}</div>`, `<div>{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }} {{ isNaN }}</div>`,
{ inline: true } { inline: true },
) )
expect(code).toMatch(`__props.props`) expect(code).toMatch(`__props.props`)
expect(code).toMatch(`_unref(setup)`) expect(code).toMatch(`_unref(setup)`)
@ -567,12 +571,12 @@ describe('compiler: expression transform', () => {
test('literal const handling', () => { test('literal const handling', () => {
const { code } = compileWithBindingMetadata(`<div>{{ literal }}</div>`, { const { code } = compileWithBindingMetadata(`<div>{{ literal }}</div>`, {
inline: true inline: true,
}) })
expect(code).toMatch(`toDisplayString(literal)`) expect(code).toMatch(`toDisplayString(literal)`)
// #7973 should skip patch for literal const // #7973 should skip patch for literal const
expect(code).not.toMatch( expect(code).not.toMatch(
`${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */` `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`,
) )
}) })
@ -581,17 +585,17 @@ describe('compiler: expression transform', () => {
expect(code).toMatch(`toDisplayString($setup.literal)`) expect(code).toMatch(`toDisplayString($setup.literal)`)
// #7973 should skip patch for literal const // #7973 should skip patch for literal const
expect(code).not.toMatch( expect(code).not.toMatch(
`${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */` `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`,
) )
}) })
test('reactive const handling', () => { test('reactive const handling', () => {
const { code } = compileWithBindingMetadata(`<div>{{ reactive }}</div>`, { const { code } = compileWithBindingMetadata(`<div>{{ reactive }}</div>`, {
inline: true inline: true,
}) })
// #7973 should not skip patch for reactive const // #7973 should not skip patch for reactive const
expect(code).toMatch( expect(code).toMatch(
`${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */` `${PatchFlags.TEXT} /* ${PatchFlagNames[PatchFlags.TEXT]} */`,
) )
}) })
}) })

View File

@ -1,10 +1,10 @@
import { import {
CompilerOptions, type CompilerOptions,
type ElementNode,
ErrorCodes,
NodeTypes,
baseParse as parse, baseParse as parse,
transform, transform,
ElementNode,
NodeTypes,
ErrorCodes
} from '../../src' } from '../../src'
import { transformElement } from '../../src/transforms/transformElement' import { transformElement } from '../../src/transforms/transformElement'
import { transformOn } from '../../src/transforms/vOn' import { transformOn } from '../../src/transforms/vOn'
@ -19,13 +19,13 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
nodeTransforms: [ nodeTransforms: [
...(options.prefixIdentifiers ? [transformExpression] : []), ...(options.prefixIdentifiers ? [transformExpression] : []),
transformSlotOutlet, transformSlotOutlet,
transformElement transformElement,
], ],
directiveTransforms: { directiveTransforms: {
on: transformOn, on: transformOn,
bind: transformBind bind: transformBind,
}, },
...options ...options,
}) })
return ast return ast
} }
@ -36,7 +36,7 @@ describe('compiler: transform <slot> outlets', () => {
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({ expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT, callee: RENDER_SLOT,
arguments: [`$slots`, `"default"`] arguments: [`$slots`, `"default"`],
}) })
}) })
@ -45,7 +45,7 @@ describe('compiler: transform <slot> outlets', () => {
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({ expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT, callee: RENDER_SLOT,
arguments: [`$slots`, `"foo"`] arguments: [`$slots`, `"foo"`],
}) })
}) })
@ -59,15 +59,15 @@ describe('compiler: transform <slot> outlets', () => {
{ {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `foo`, content: `foo`,
isStatic: false isStatic: false,
} },
] ],
}) })
}) })
test('dynamically named slot outlet w/ prefixIdentifiers: true', () => { test('dynamically named slot outlet w/ prefixIdentifiers: true', () => {
const ast = parseWithSlots(`<slot :name="foo + bar" />`, { const ast = parseWithSlots(`<slot :name="foo + bar" />`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({ expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
@ -80,23 +80,23 @@ describe('compiler: transform <slot> outlets', () => {
{ {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.foo`, content: `_ctx.foo`,
isStatic: false isStatic: false,
}, },
` + `, ` + `,
{ {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.bar`, content: `_ctx.bar`,
isStatic: false isStatic: false,
} },
] ],
} },
] ],
}) })
}) })
test('default slot outlet with props', () => { test('default slot outlet with props', () => {
const ast = parseWithSlots( const ast = parseWithSlots(
`<slot foo="bar" :baz="qux" :foo-bar="foo-bar" />` `<slot foo="bar" :baz="qux" :foo-bar="foo-bar" />`,
) )
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({ expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
@ -110,36 +110,36 @@ describe('compiler: transform <slot> outlets', () => {
{ {
key: { key: {
content: `foo`, content: `foo`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `bar`, content: `bar`,
isStatic: true isStatic: true,
} },
}, },
{ {
key: { key: {
content: `baz`, content: `baz`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `qux`, content: `qux`,
isStatic: false isStatic: false,
} },
}, },
{ {
key: { key: {
content: `fooBar`, content: `fooBar`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `foo-bar`, content: `foo-bar`,
isStatic: false isStatic: false,
} },
} },
] ],
} },
] ],
}) })
}) })
@ -158,26 +158,26 @@ describe('compiler: transform <slot> outlets', () => {
{ {
key: { key: {
content: `foo`, content: `foo`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `bar`, content: `bar`,
isStatic: true isStatic: true,
} },
}, },
{ {
key: { key: {
content: `baz`, content: `baz`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `qux`, content: `qux`,
isStatic: false isStatic: false,
} },
} },
] ],
} },
] ],
}) })
}) })
@ -196,26 +196,26 @@ describe('compiler: transform <slot> outlets', () => {
{ {
key: { key: {
content: `foo`, content: `foo`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `bar`, content: `bar`,
isStatic: true isStatic: true,
} },
}, },
{ {
key: { key: {
content: `baz`, content: `baz`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `qux`, content: `qux`,
isStatic: false isStatic: false,
} },
} },
] ],
} },
] ],
}) })
}) })
@ -234,11 +234,11 @@ describe('compiler: transform <slot> outlets', () => {
returns: [ returns: [
{ {
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,
tag: `div` tag: `div`,
} },
] ],
} },
] ],
}) })
}) })
@ -257,11 +257,11 @@ describe('compiler: transform <slot> outlets', () => {
returns: [ returns: [
{ {
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,
tag: `div` tag: `div`,
} },
] ],
} },
] ],
}) })
}) })
@ -279,14 +279,14 @@ describe('compiler: transform <slot> outlets', () => {
{ {
key: { key: {
content: `foo`, content: `foo`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `bar`, content: `bar`,
isStatic: false isStatic: false,
} },
} },
] ],
}, },
{ {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
@ -294,11 +294,11 @@ describe('compiler: transform <slot> outlets', () => {
returns: [ returns: [
{ {
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,
tag: `div` tag: `div`,
} },
] ],
} },
] ],
}) })
}) })
@ -316,14 +316,14 @@ describe('compiler: transform <slot> outlets', () => {
{ {
key: { key: {
content: `foo`, content: `foo`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `bar`, content: `bar`,
isStatic: false isStatic: false,
} },
} },
] ],
}, },
{ {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
@ -331,11 +331,11 @@ describe('compiler: transform <slot> outlets', () => {
returns: [ returns: [
{ {
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,
tag: `div` tag: `div`,
} },
] ],
} },
] ],
}) })
}) })
@ -344,11 +344,11 @@ describe('compiler: transform <slot> outlets', () => {
expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({ expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT, callee: RENDER_SLOT,
arguments: [`$slots`, `"default"`, `{}`, `undefined`, `true`] arguments: [`$slots`, `"default"`, `{}`, `undefined`, `true`],
}) })
const fallback = parseWithSlots(`<slot>fallback</slot>`, { const fallback = parseWithSlots(`<slot>fallback</slot>`, {
slotted: false, slotted: false,
scopeId: 'foo' scopeId: 'foo',
}) })
const child = { const child = {
@ -357,14 +357,14 @@ describe('compiler: transform <slot> outlets', () => {
returns: [ returns: [
{ {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: `fallback` content: `fallback`,
} },
] ],
} }
expect((fallback.children[0] as ElementNode).codegenNode).toMatchObject({ expect((fallback.children[0] as ElementNode).codegenNode).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT, callee: RENDER_SLOT,
arguments: [`$slots`, `"default"`, `{}`, child, `true`] arguments: [`$slots`, `"default"`, `{}`, child, `true`],
}) })
}) })
@ -379,14 +379,14 @@ describe('compiler: transform <slot> outlets', () => {
start: { start: {
offset: index, offset: index,
line: 1, line: 1,
column: index + 1 column: index + 1,
}, },
end: { end: {
offset: index + 5, offset: index + 5,
line: 1, line: 1,
column: index + 6 column: index + 6,
} },
} },
}) })
}) })
}) })

View File

@ -1,11 +1,11 @@
import { import {
CompilerOptions, type CompilerOptions,
baseParse as parse, type ElementNode,
transform, type ForNode,
NodeTypes, NodeTypes,
generate, generate,
ForNode, baseParse as parse,
ElementNode transform,
} from '../../src' } from '../../src'
import { transformFor } from '../../src/transforms/vFor' import { transformFor } from '../../src/transforms/vFor'
import { transformText } from '../../src/transforms/transformText' import { transformText } from '../../src/transforms/transformText'
@ -22,9 +22,9 @@ function transformWithTextOpt(template: string, options: CompilerOptions = {}) {
transformFor, transformFor,
...(options.prefixIdentifiers ? [transformExpression] : []), ...(options.prefixIdentifiers ? [transformExpression] : []),
transformElement, transformElement,
transformText transformText,
], ],
...options ...options,
}) })
return ast return ast
} }
@ -35,8 +35,8 @@ describe('compiler: transform text', () => {
expect(root.children[0]).toMatchObject({ expect(root.children[0]).toMatchObject({
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `foo` content: `foo`,
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -51,8 +51,8 @@ describe('compiler: transform text', () => {
` + `, ` + `,
{ type: NodeTypes.TEXT, content: ` bar ` }, { type: NodeTypes.TEXT, content: ` bar ` },
` + `, ` + `,
{ type: NodeTypes.INTERPOLATION, content: { content: `baz` } } { type: NodeTypes.INTERPOLATION, content: { content: `baz` } },
] ],
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -75,12 +75,12 @@ describe('compiler: transform text', () => {
` + `, ` + `,
{ type: NodeTypes.TEXT, content: ` bar ` }, { type: NodeTypes.TEXT, content: ` bar ` },
` + `, ` + `,
{ type: NodeTypes.INTERPOLATION, content: { content: `baz` } } { type: NodeTypes.INTERPOLATION, content: { content: `baz` } },
] ],
},
genFlagText(PatchFlags.TEXT),
],
}, },
genFlagText(PatchFlags.TEXT)
]
}
}) })
expect(root.children[2].type).toBe(NodeTypes.ELEMENT) expect(root.children[2].type).toBe(NodeTypes.ELEMENT)
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
@ -99,11 +99,11 @@ describe('compiler: transform text', () => {
arguments: [ arguments: [
{ {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: `hello` content: `hello`,
} },
// should have no flag // should have no flag
] ],
} },
}) })
expect(root.children[2].type).toBe(NodeTypes.ELEMENT) expect(root.children[2].type).toBe(NodeTypes.ELEMENT)
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
@ -111,7 +111,7 @@ describe('compiler: transform text', () => {
test('consecutive text mixed with elements', () => { test('consecutive text mixed with elements', () => {
const root = transformWithTextOpt( const root = transformWithTextOpt(
`<div/>{{ foo }} bar {{ baz }}<div/>hello<div/>` `<div/>{{ foo }} bar {{ baz }}<div/>hello<div/>`,
) )
expect(root.children.length).toBe(5) expect(root.children.length).toBe(5)
expect(root.children[0].type).toBe(NodeTypes.ELEMENT) expect(root.children[0].type).toBe(NodeTypes.ELEMENT)
@ -128,12 +128,12 @@ describe('compiler: transform text', () => {
` + `, ` + `,
{ type: NodeTypes.TEXT, content: ` bar ` }, { type: NodeTypes.TEXT, content: ` bar ` },
` + `, ` + `,
{ type: NodeTypes.INTERPOLATION, content: { content: `baz` } } { type: NodeTypes.INTERPOLATION, content: { content: `baz` } },
] ],
},
genFlagText(PatchFlags.TEXT),
],
}, },
genFlagText(PatchFlags.TEXT)
]
}
}) })
expect(root.children[2].type).toBe(NodeTypes.ELEMENT) expect(root.children[2].type).toBe(NodeTypes.ELEMENT)
expect(root.children[3]).toMatchObject({ expect(root.children[3]).toMatchObject({
@ -144,10 +144,10 @@ describe('compiler: transform text', () => {
arguments: [ arguments: [
{ {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: `hello` content: `hello`,
} },
] ],
} },
}) })
expect(root.children[4].type).toBe(NodeTypes.ELEMENT) expect(root.children[4].type).toBe(NodeTypes.ELEMENT)
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
@ -155,21 +155,21 @@ describe('compiler: transform text', () => {
test('<template v-for>', () => { test('<template v-for>', () => {
const root = transformWithTextOpt( const root = transformWithTextOpt(
`<template v-for="i in list">foo</template>` `<template v-for="i in list">foo</template>`,
) )
expect(root.children[0].type).toBe(NodeTypes.FOR) expect(root.children[0].type).toBe(NodeTypes.FOR)
const forNode = root.children[0] as ForNode const forNode = root.children[0] as ForNode
// should convert template v-for text children because they are inside // should convert template v-for text children because they are inside
// fragments // fragments
expect(forNode.children[0]).toMatchObject({ expect(forNode.children[0]).toMatchObject({
type: NodeTypes.TEXT_CALL type: NodeTypes.TEXT_CALL,
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
test('with prefixIdentifiers: true', () => { test('with prefixIdentifiers: true', () => {
const root = transformWithTextOpt(`{{ foo }} bar {{ baz + qux }}`, { const root = transformWithTextOpt(`{{ foo }} bar {{ baz + qux }}`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect(root.children.length).toBe(1) expect(root.children.length).toBe(1)
expect(root.children[0]).toMatchObject({ expect(root.children[0]).toMatchObject({
@ -183,15 +183,15 @@ describe('compiler: transform text', () => {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [{ content: `_ctx.baz` }, ` + `, { content: `_ctx.qux` }] children: [{ content: `_ctx.baz` }, ` + `, { content: `_ctx.qux` }],
} },
} },
] ],
}) })
expect( expect(
generate(root, { generate(root, {
prefixIdentifiers: true prefixIdentifiers: true,
}).code }).code,
).toMatchSnapshot() ).toMatchSnapshot()
}) })
@ -210,12 +210,12 @@ describe('compiler: transform text', () => {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo' content: 'foo',
} },
},
genFlagText(PatchFlags.TEXT),
],
}, },
genFlagText(PatchFlags.TEXT)
]
}
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })

View File

@ -1,37 +1,37 @@
import { import {
type CallExpression,
type CompilerOptions,
type ElementNode,
ErrorCodes,
NodeTypes,
type ObjectExpression,
type VNodeCall,
baseParse as parse, baseParse as parse,
transform, transform,
ElementNode,
ObjectExpression,
CompilerOptions,
ErrorCodes,
VNodeCall,
NodeTypes,
CallExpression
} from '../../src' } from '../../src'
import { transformBind } from '../../src/transforms/vBind' import { transformBind } from '../../src/transforms/vBind'
import { transformElement } from '../../src/transforms/transformElement' import { transformElement } from '../../src/transforms/transformElement'
import { import {
CAMELIZE, CAMELIZE,
NORMALIZE_PROPS,
helperNameMap, helperNameMap,
NORMALIZE_PROPS
} from '../../src/runtimeHelpers' } from '../../src/runtimeHelpers'
import { transformExpression } from '../../src/transforms/transformExpression' import { transformExpression } from '../../src/transforms/transformExpression'
function parseWithVBind( function parseWithVBind(
template: string, template: string,
options: CompilerOptions = {} options: CompilerOptions = {},
): ElementNode { ): ElementNode {
const ast = parse(template) const ast = parse(template)
transform(ast, { transform(ast, {
nodeTransforms: [ nodeTransforms: [
...(options.prefixIdentifiers ? [transformExpression] : []), ...(options.prefixIdentifiers ? [transformExpression] : []),
transformElement transformElement,
], ],
directiveTransforms: { directiveTransforms: {
bind: transformBind bind: transformBind,
}, },
...options ...options,
}) })
return ast.children[0] as ElementNode return ast.children[0] as ElementNode
} }
@ -47,13 +47,13 @@ describe('compiler: transform v-bind', () => {
loc: { loc: {
start: { start: {
line: 1, line: 1,
column: 13 column: 13,
}, },
end: { end: {
line: 1, line: 1,
column: 15 column: 15,
} },
} },
}, },
value: { value: {
content: `id`, content: `id`,
@ -61,14 +61,14 @@ describe('compiler: transform v-bind', () => {
loc: { loc: {
start: { start: {
line: 1, line: 1,
column: 17 column: 17,
}, },
end: { end: {
line: 1, line: 1,
column: 19 column: 19,
} },
} },
} },
}) })
}) })
@ -81,17 +81,17 @@ describe('compiler: transform v-bind', () => {
isStatic: true, isStatic: true,
loc: { loc: {
start: { line: 1, column: 13, offset: 12 }, start: { line: 1, column: 13, offset: 12 },
end: { line: 1, column: 15, offset: 14 } end: { line: 1, column: 15, offset: 14 },
} },
}, },
value: { value: {
content: `id`, content: `id`,
isStatic: false, isStatic: false,
loc: { loc: {
start: { line: 1, column: 13, offset: 12 }, start: { line: 1, column: 13, offset: 12 },
end: { line: 1, column: 15, offset: 14 } end: { line: 1, column: 15, offset: 14 },
} },
} },
}) })
}) })
@ -101,12 +101,12 @@ describe('compiler: transform v-bind', () => {
expect(props.properties[0]).toMatchObject({ expect(props.properties[0]).toMatchObject({
key: { key: {
content: `id`, content: `id`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `id`, content: `id`,
isStatic: false isStatic: false,
} },
}) })
}) })
@ -123,16 +123,16 @@ describe('compiler: transform v-bind', () => {
{ {
key: { key: {
content: `id || ""`, content: `id || ""`,
isStatic: false isStatic: false,
}, },
value: { value: {
content: `id`, content: `id`,
isStatic: false isStatic: false,
} },
} },
] ],
} },
] ],
}) })
}) })
@ -145,23 +145,23 @@ describe('compiler: transform v-bind', () => {
loc: { loc: {
start: { start: {
line: 1, line: 1,
column: 6 column: 6,
}, },
end: { end: {
line: 1, line: 1,
column: 19 column: 19,
} },
} },
}) })
expect(props.properties[0]).toMatchObject({ expect(props.properties[0]).toMatchObject({
key: { key: {
content: `arg`, content: `arg`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: ``, content: ``,
isStatic: true isStatic: true,
} },
}) })
}) })
@ -171,12 +171,12 @@ describe('compiler: transform v-bind', () => {
expect(props.properties[0]).toMatchObject({ expect(props.properties[0]).toMatchObject({
key: { key: {
content: `fooBar`, content: `fooBar`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `id`, content: `id`,
isStatic: false isStatic: false,
} },
}) })
}) })
@ -186,12 +186,12 @@ describe('compiler: transform v-bind', () => {
expect(props.properties[0]).toMatchObject({ expect(props.properties[0]).toMatchObject({
key: { key: {
content: `fooBar`, content: `fooBar`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `fooBar`, content: `fooBar`,
isStatic: false isStatic: false,
} },
}) })
}) })
@ -208,22 +208,22 @@ describe('compiler: transform v-bind', () => {
{ {
key: { key: {
content: `_${helperNameMap[CAMELIZE]}(foo || "")`, content: `_${helperNameMap[CAMELIZE]}(foo || "")`,
isStatic: false isStatic: false,
}, },
value: { value: {
content: `id`, content: `id`,
isStatic: false isStatic: false,
} },
} },
] ],
} },
] ],
}) })
}) })
test('.camel modifier w/ dynamic arg + prefixIdentifiers', () => { test('.camel modifier w/ dynamic arg + prefixIdentifiers', () => {
const node = parseWithVBind(`<div v-bind:[foo(bar)].camel="id"/>`, { const node = parseWithVBind(`<div v-bind:[foo(bar)].camel="id"/>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
const props = (node.codegenNode as VNodeCall).props as CallExpression const props = (node.codegenNode as VNodeCall).props as CallExpression
expect(props).toMatchObject({ expect(props).toMatchObject({
@ -243,17 +243,17 @@ describe('compiler: transform v-bind', () => {
{ content: `_ctx.bar` }, { content: `_ctx.bar` },
`)`, `)`,
`) || ""`, `) || ""`,
`)` `)`,
] ],
}, },
value: { value: {
content: `_ctx.id`, content: `_ctx.id`,
isStatic: false isStatic: false,
} },
} },
] ],
} },
] ],
}) })
}) })
@ -263,12 +263,12 @@ describe('compiler: transform v-bind', () => {
expect(props.properties[0]).toMatchObject({ expect(props.properties[0]).toMatchObject({
key: { key: {
content: `.fooBar`, content: `.fooBar`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `id`, content: `id`,
isStatic: false isStatic: false,
} },
}) })
}) })
@ -278,12 +278,12 @@ describe('compiler: transform v-bind', () => {
expect(props.properties[0]).toMatchObject({ expect(props.properties[0]).toMatchObject({
key: { key: {
content: `.fooBar`, content: `.fooBar`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `fooBar`, content: `fooBar`,
isStatic: false isStatic: false,
} },
}) })
}) })
@ -300,22 +300,22 @@ describe('compiler: transform v-bind', () => {
{ {
key: { key: {
content: '`.${fooBar || ""}`', content: '`.${fooBar || ""}`',
isStatic: false isStatic: false,
}, },
value: { value: {
content: `id`, content: `id`,
isStatic: false isStatic: false,
} },
} },
] ],
} },
] ],
}) })
}) })
test('.prop modifier w/ dynamic arg + prefixIdentifiers', () => { test('.prop modifier w/ dynamic arg + prefixIdentifiers', () => {
const node = parseWithVBind(`<div v-bind:[foo(bar)].prop="id"/>`, { const node = parseWithVBind(`<div v-bind:[foo(bar)].prop="id"/>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
const props = (node.codegenNode as VNodeCall).props as CallExpression const props = (node.codegenNode as VNodeCall).props as CallExpression
expect(props).toMatchObject({ expect(props).toMatchObject({
@ -335,17 +335,17 @@ describe('compiler: transform v-bind', () => {
{ content: `_ctx.bar` }, { content: `_ctx.bar` },
`)`, `)`,
`) || ""`, `) || ""`,
`)` `)`,
] ],
}, },
value: { value: {
content: `_ctx.id`, content: `_ctx.id`,
isStatic: false isStatic: false,
} },
} },
] ],
} },
] ],
}) })
}) })
@ -355,12 +355,12 @@ describe('compiler: transform v-bind', () => {
expect(props.properties[0]).toMatchObject({ expect(props.properties[0]).toMatchObject({
key: { key: {
content: `.fooBar`, content: `.fooBar`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `id`, content: `id`,
isStatic: false isStatic: false,
} },
}) })
}) })
@ -370,12 +370,12 @@ describe('compiler: transform v-bind', () => {
expect(props.properties[0]).toMatchObject({ expect(props.properties[0]).toMatchObject({
key: { key: {
content: `.fooBar`, content: `.fooBar`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `fooBar`, content: `fooBar`,
isStatic: false isStatic: false,
} },
}) })
}) })
@ -385,12 +385,12 @@ describe('compiler: transform v-bind', () => {
expect(props.properties[0]).toMatchObject({ expect(props.properties[0]).toMatchObject({
key: { key: {
content: `^foo-bar`, content: `^foo-bar`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `id`, content: `id`,
isStatic: false isStatic: false,
} },
}) })
}) })
@ -400,12 +400,12 @@ describe('compiler: transform v-bind', () => {
expect(props.properties[0]).toMatchObject({ expect(props.properties[0]).toMatchObject({
key: { key: {
content: `^foo-bar`, content: `^foo-bar`,
isStatic: true isStatic: true,
}, },
value: { value: {
content: `fooBar`, content: `fooBar`,
isStatic: false isStatic: false,
} },
}) })
}) })
}) })

View File

@ -7,23 +7,23 @@ import { transformElement } from '../../src/transforms/transformElement'
import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet' import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet'
import { transformExpression } from '../../src/transforms/transformExpression' import { transformExpression } from '../../src/transforms/transformExpression'
import { import {
ForNode, ConstantTypes,
type ElementNode,
type ForCodegenNode,
type ForNode,
type InterpolationNode,
NodeTypes, NodeTypes,
SimpleExpressionNode, type SimpleExpressionNode,
ElementNode,
InterpolationNode,
ForCodegenNode,
ConstantTypes
} from '../../src/ast' } from '../../src/ast'
import { ErrorCodes } from '../../src/errors' import { ErrorCodes } from '../../src/errors'
import { CompilerOptions, generate } from '../../src' import { type CompilerOptions, generate } from '../../src'
import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers' import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
import { PatchFlags } from '@vue/shared' import { PatchFlags } from '@vue/shared'
import { createObjectMatcher, genFlagText } from '../testUtils' import { createObjectMatcher, genFlagText } from '../testUtils'
function parseWithForTransform( function parseWithForTransform(
template: string, template: string,
options: CompilerOptions = {} options: CompilerOptions = {},
) { ) {
const ast = parse(template, options) const ast = parse(template, options)
transform(ast, { transform(ast, {
@ -32,16 +32,16 @@ function parseWithForTransform(
transformFor, transformFor,
...(options.prefixIdentifiers ? [transformExpression] : []), ...(options.prefixIdentifiers ? [transformExpression] : []),
transformSlotOutlet, transformSlotOutlet,
transformElement transformElement,
], ],
directiveTransforms: { directiveTransforms: {
bind: transformBind bind: transformBind,
}, },
...options ...options,
}) })
return { return {
root: ast, root: ast,
node: ast.children[0] as ForNode & { codegenNode: ForCodegenNode } node: ast.children[0] as ForNode & { codegenNode: ForCodegenNode },
} }
} }
@ -49,7 +49,7 @@ describe('compiler: v-for', () => {
describe('transform', () => { describe('transform', () => {
test('number expression', () => { test('number expression', () => {
const { node: forNode } = parseWithForTransform( const { node: forNode } = parseWithForTransform(
'<span v-for="index in 5" />' '<span v-for="index in 5" />',
) )
expect(forNode.keyAlias).toBeUndefined() expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined()
@ -59,7 +59,7 @@ describe('compiler: v-for', () => {
test('value', () => { test('value', () => {
const { node: forNode } = parseWithForTransform( const { node: forNode } = parseWithForTransform(
'<span v-for="(item) in items" />' '<span v-for="(item) in items" />',
) )
expect(forNode.keyAlias).toBeUndefined() expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined()
@ -69,31 +69,31 @@ describe('compiler: v-for', () => {
test('object de-structured value', () => { test('object de-structured value', () => {
const { node: forNode } = parseWithForTransform( const { node: forNode } = parseWithForTransform(
'<span v-for="({ id, value }) in items" />' '<span v-for="({ id, value }) in items" />',
) )
expect(forNode.keyAlias).toBeUndefined() expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined()
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe( expect((forNode.valueAlias as SimpleExpressionNode).content).toBe(
'{ id, value }' '{ id, value }',
) )
expect((forNode.source as SimpleExpressionNode).content).toBe('items') expect((forNode.source as SimpleExpressionNode).content).toBe('items')
}) })
test('array de-structured value', () => { test('array de-structured value', () => {
const { node: forNode } = parseWithForTransform( const { node: forNode } = parseWithForTransform(
'<span v-for="([ id, value ]) in items" />' '<span v-for="([ id, value ]) in items" />',
) )
expect(forNode.keyAlias).toBeUndefined() expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined()
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe( expect((forNode.valueAlias as SimpleExpressionNode).content).toBe(
'[ id, value ]' '[ id, value ]',
) )
expect((forNode.source as SimpleExpressionNode).content).toBe('items') expect((forNode.source as SimpleExpressionNode).content).toBe('items')
}) })
test('value and key', () => { test('value and key', () => {
const { node: forNode } = parseWithForTransform( const { node: forNode } = parseWithForTransform(
'<span v-for="(item, key) in items" />' '<span v-for="(item, key) in items" />',
) )
expect(forNode.keyAlias).not.toBeUndefined() expect(forNode.keyAlias).not.toBeUndefined()
expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key') expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key')
@ -104,13 +104,13 @@ describe('compiler: v-for', () => {
test('value, key and index', () => { test('value, key and index', () => {
const { node: forNode } = parseWithForTransform( const { node: forNode } = parseWithForTransform(
'<span v-for="(value, key, index) in items" />' '<span v-for="(value, key, index) in items" />',
) )
expect(forNode.keyAlias).not.toBeUndefined() expect(forNode.keyAlias).not.toBeUndefined()
expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key') expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key')
expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe( expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
'index' 'index',
) )
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value') expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value')
expect((forNode.source as SimpleExpressionNode).content).toBe('items') expect((forNode.source as SimpleExpressionNode).content).toBe('items')
@ -118,12 +118,12 @@ describe('compiler: v-for', () => {
test('skipped key', () => { test('skipped key', () => {
const { node: forNode } = parseWithForTransform( const { node: forNode } = parseWithForTransform(
'<span v-for="(value,,index) in items" />' '<span v-for="(value,,index) in items" />',
) )
expect(forNode.keyAlias).toBeUndefined() expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe( expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
'index' 'index',
) )
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value') expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value')
expect((forNode.source as SimpleExpressionNode).content).toBe('items') expect((forNode.source as SimpleExpressionNode).content).toBe('items')
@ -131,12 +131,12 @@ describe('compiler: v-for', () => {
test('skipped value and key', () => { test('skipped value and key', () => {
const { node: forNode } = parseWithForTransform( const { node: forNode } = parseWithForTransform(
'<span v-for="(,,index) in items" />' '<span v-for="(,,index) in items" />',
) )
expect(forNode.keyAlias).toBeUndefined() expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe( expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
'index' 'index',
) )
expect(forNode.valueAlias).toBeUndefined() expect(forNode.valueAlias).toBeUndefined()
expect((forNode.source as SimpleExpressionNode).content).toBe('items') expect((forNode.source as SimpleExpressionNode).content).toBe('items')
@ -144,7 +144,7 @@ describe('compiler: v-for', () => {
test('unbracketed value', () => { test('unbracketed value', () => {
const { node: forNode } = parseWithForTransform( const { node: forNode } = parseWithForTransform(
'<span v-for="item in items" />' '<span v-for="item in items" />',
) )
expect(forNode.keyAlias).toBeUndefined() expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined() expect(forNode.objectIndexAlias).toBeUndefined()
@ -154,7 +154,7 @@ describe('compiler: v-for', () => {
test('unbracketed value and key', () => { test('unbracketed value and key', () => {
const { node: forNode } = parseWithForTransform( const { node: forNode } = parseWithForTransform(
'<span v-for="item, key in items" />' '<span v-for="item, key in items" />',
) )
expect(forNode.keyAlias).not.toBeUndefined() expect(forNode.keyAlias).not.toBeUndefined()
expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key') expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key')
@ -165,13 +165,13 @@ describe('compiler: v-for', () => {
test('unbracketed value, key and index', () => { test('unbracketed value, key and index', () => {
const { node: forNode } = parseWithForTransform( const { node: forNode } = parseWithForTransform(
'<span v-for="value, key, index in items" />' '<span v-for="value, key, index in items" />',
) )
expect(forNode.keyAlias).not.toBeUndefined() expect(forNode.keyAlias).not.toBeUndefined()
expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key') expect((forNode.keyAlias as SimpleExpressionNode).content).toBe('key')
expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe( expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
'index' 'index',
) )
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value') expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value')
expect((forNode.source as SimpleExpressionNode).content).toBe('items') expect((forNode.source as SimpleExpressionNode).content).toBe('items')
@ -179,12 +179,12 @@ describe('compiler: v-for', () => {
test('unbracketed skipped key', () => { test('unbracketed skipped key', () => {
const { node: forNode } = parseWithForTransform( const { node: forNode } = parseWithForTransform(
'<span v-for="value, , index in items" />' '<span v-for="value, , index in items" />',
) )
expect(forNode.keyAlias).toBeUndefined() expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe( expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
'index' 'index',
) )
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value') expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('value')
expect((forNode.source as SimpleExpressionNode).content).toBe('items') expect((forNode.source as SimpleExpressionNode).content).toBe('items')
@ -192,12 +192,12 @@ describe('compiler: v-for', () => {
test('unbracketed skipped value and key', () => { test('unbracketed skipped value and key', () => {
const { node: forNode } = parseWithForTransform( const { node: forNode } = parseWithForTransform(
'<span v-for=", , index in items" />' '<span v-for=", , index in items" />',
) )
expect(forNode.keyAlias).toBeUndefined() expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).not.toBeUndefined() expect(forNode.objectIndexAlias).not.toBeUndefined()
expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe( expect((forNode.objectIndexAlias as SimpleExpressionNode).content).toBe(
'index' 'index',
) )
expect(forNode.valueAlias).toBeUndefined() expect(forNode.valueAlias).toBeUndefined()
expect((forNode.source as SimpleExpressionNode).content).toBe('items') expect((forNode.source as SimpleExpressionNode).content).toBe('items')
@ -212,8 +212,8 @@ describe('compiler: v-for', () => {
expect(onError).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith( expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
code: ErrorCodes.X_V_FOR_NO_EXPRESSION code: ErrorCodes.X_V_FOR_NO_EXPRESSION,
}) }),
) )
}) })
@ -224,8 +224,8 @@ describe('compiler: v-for', () => {
expect(onError).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith( expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
}) }),
) )
}) })
@ -236,8 +236,8 @@ describe('compiler: v-for', () => {
expect(onError).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith( expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
}) }),
) )
}) })
@ -248,8 +248,8 @@ describe('compiler: v-for', () => {
expect(onError).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith( expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
}) }),
) )
}) })
@ -260,8 +260,8 @@ describe('compiler: v-for', () => {
expect(onError).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith( expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
}) }),
) )
}) })
@ -272,14 +272,14 @@ describe('compiler: v-for', () => {
<template v-for="item in items"> <template v-for="item in items">
<div :key="item.id"/> <div :key="item.id"/>
</template>`, </template>`,
{ onError } { onError },
) )
expect(onError).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith( expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
code: ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT code: ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT,
}) }),
) )
// should not warn on nested v-for keys // should not warn on nested v-for keys
@ -288,7 +288,7 @@ describe('compiler: v-for', () => {
<template v-for="item in items"> <template v-for="item in items">
<div v-for="c in item.children" :key="c.id"/> <div v-for="c in item.children" :key="c.id"/>
</template>`, </template>`,
{ onError } { onError },
) )
expect(onError).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(1)
}) })
@ -315,7 +315,7 @@ describe('compiler: v-for', () => {
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1) expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1) expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe( expect(forNode.source.loc.end.column).toBe(
itemsOffset + 1 + `items`.length itemsOffset + 1 + `items`.length,
) )
}) })
@ -339,7 +339,7 @@ describe('compiler: v-for', () => {
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1) expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1) expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe( expect(forNode.source.loc.end.column).toBe(
itemsOffset + 1 + `items`.length itemsOffset + 1 + `items`.length,
) )
}) })
@ -363,7 +363,7 @@ describe('compiler: v-for', () => {
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1) expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1) expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe( expect(forNode.source.loc.end.column).toBe(
itemsOffset + 1 + `items`.length itemsOffset + 1 + `items`.length,
) )
}) })
@ -405,7 +405,7 @@ describe('compiler: v-for', () => {
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1) expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1) expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe( expect(forNode.source.loc.end.column).toBe(
itemsOffset + 1 + `items`.length itemsOffset + 1 + `items`.length,
) )
}) })
@ -438,7 +438,7 @@ describe('compiler: v-for', () => {
expect(forNode.source.loc.start.column).toBe(itemsOffset + 1) expect(forNode.source.loc.start.column).toBe(itemsOffset + 1)
expect(forNode.source.loc.end.line).toBe(1) expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe( expect(forNode.source.loc.end.column).toBe(
itemsOffset + 1 + `items`.length itemsOffset + 1 + `items`.length,
) )
}) })
}) })
@ -446,18 +446,18 @@ describe('compiler: v-for', () => {
describe('prefixIdentifiers: true', () => { describe('prefixIdentifiers: true', () => {
test('should prefix v-for source', () => { test('should prefix v-for source', () => {
const { node } = parseWithForTransform(`<div v-for="i in list"/>`, { const { node } = parseWithForTransform(`<div v-for="i in list"/>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect(node.source).toMatchObject({ expect(node.source).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.list` content: `_ctx.list`,
}) })
}) })
test('should prefix v-for source w/ complex expression', () => { test('should prefix v-for source w/ complex expression', () => {
const { node } = parseWithForTransform( const { node } = parseWithForTransform(
`<div v-for="i in list.concat([foo])"/>`, `<div v-for="i in list.concat([foo])"/>`,
{ prefixIdentifiers: true } { prefixIdentifiers: true },
) )
expect(node.source).toMatchObject({ expect(node.source).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
@ -467,31 +467,31 @@ describe('compiler: v-for', () => {
{ content: `concat` }, { content: `concat` },
`([`, `([`,
{ content: `_ctx.foo` }, { content: `_ctx.foo` },
`])` `])`,
] ],
}) })
}) })
test('should not prefix v-for alias', () => { test('should not prefix v-for alias', () => {
const { node } = parseWithForTransform( const { node } = parseWithForTransform(
`<div v-for="i in list">{{ i }}{{ j }}</div>`, `<div v-for="i in list">{{ i }}{{ j }}</div>`,
{ prefixIdentifiers: true } { prefixIdentifiers: true },
) )
const div = node.children[0] as ElementNode const div = node.children[0] as ElementNode
expect((div.children[0] as InterpolationNode).content).toMatchObject({ expect((div.children[0] as InterpolationNode).content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `i` content: `i`,
}) })
expect((div.children[1] as InterpolationNode).content).toMatchObject({ expect((div.children[1] as InterpolationNode).content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.j` content: `_ctx.j`,
}) })
}) })
test('should not prefix v-for aliases (multiple)', () => { test('should not prefix v-for aliases (multiple)', () => {
const { node } = parseWithForTransform( const { node } = parseWithForTransform(
`<div v-for="(i, j, k) in list">{{ i + j + k }}{{ l }}</div>`, `<div v-for="(i, j, k) in list">{{ i + j + k }}{{ l }}</div>`,
{ prefixIdentifiers: true } { prefixIdentifiers: true },
) )
const div = node.children[0] as ElementNode const div = node.children[0] as ElementNode
expect((div.children[0] as InterpolationNode).content).toMatchObject({ expect((div.children[0] as InterpolationNode).content).toMatchObject({
@ -501,23 +501,23 @@ describe('compiler: v-for', () => {
` + `, ` + `,
{ content: `j` }, { content: `j` },
` + `, ` + `,
{ content: `k` } { content: `k` },
] ],
}) })
expect((div.children[1] as InterpolationNode).content).toMatchObject({ expect((div.children[1] as InterpolationNode).content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.l` content: `_ctx.l`,
}) })
}) })
test('should prefix id outside of v-for', () => { test('should prefix id outside of v-for', () => {
const { node } = parseWithForTransform( const { node } = parseWithForTransform(
`<div><div v-for="i in list" />{{ i }}</div>`, `<div><div v-for="i in list" />{{ i }}</div>`,
{ prefixIdentifiers: true } { prefixIdentifiers: true },
) )
expect((node.children[1] as InterpolationNode).content).toMatchObject({ expect((node.children[1] as InterpolationNode).content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.i` content: `_ctx.i`,
}) })
}) })
@ -526,7 +526,7 @@ describe('compiler: v-for', () => {
`<div v-for="i in list"> `<div v-for="i in list">
<div v-for="i in list">{{ i + j }}</div>{{ i }} <div v-for="i in list">{{ i + j }}</div>{{ i }}
</div>`, </div>`,
{ prefixIdentifiers: true } { prefixIdentifiers: true },
) )
const outerDiv = node.children[0] as ElementNode const outerDiv = node.children[0] as ElementNode
const innerFor = outerDiv.children[0] as ForNode const innerFor = outerDiv.children[0] as ForNode
@ -534,7 +534,7 @@ describe('compiler: v-for', () => {
.children[0] as InterpolationNode .children[0] as InterpolationNode
expect(innerExp.content).toMatchObject({ expect(innerExp.content).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [{ content: 'i' }, ` + `, { content: `_ctx.j` }] children: [{ content: 'i' }, ` + `, { content: `_ctx.j` }],
}) })
// when an inner v-for shadows a variable of an outer v-for and exit, // when an inner v-for shadows a variable of an outer v-for and exit,
@ -542,7 +542,7 @@ describe('compiler: v-for', () => {
const outerExp = outerDiv.children[1] as InterpolationNode const outerExp = outerDiv.children[1] as InterpolationNode
expect(outerExp.content).toMatchObject({ expect(outerExp.content).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `i` content: `i`,
}) })
}) })
@ -551,7 +551,7 @@ describe('compiler: v-for', () => {
`<div v-for="({ foo = bar, baz: [qux = quux] }) in list"> `<div v-for="({ foo = bar, baz: [qux = quux] }) in list">
{{ foo + bar + baz + qux + quux }} {{ foo + bar + baz + qux + quux }}
</div>`, </div>`,
{ prefixIdentifiers: true } { prefixIdentifiers: true },
) )
expect(node.valueAlias!).toMatchObject({ expect(node.valueAlias!).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
@ -564,8 +564,8 @@ describe('compiler: v-for', () => {
{ content: `qux` }, { content: `qux` },
` = `, ` = `,
{ content: `_ctx.quux` }, { content: `_ctx.quux` },
`] }` `] }`,
] ],
}) })
const div = node.children[0] as ElementNode const div = node.children[0] as ElementNode
expect((div.children[0] as InterpolationNode).content).toMatchObject({ expect((div.children[0] as InterpolationNode).content).toMatchObject({
@ -579,17 +579,17 @@ describe('compiler: v-for', () => {
` + `, ` + `,
{ content: `qux` }, { content: `qux` },
` + `, ` + `,
{ content: `_ctx.quux` } { content: `_ctx.quux` },
] ],
}) })
}) })
test('element v-for key expression prefixing', () => { test('element v-for key expression prefixing', () => {
const { const {
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform( } = parseWithForTransform(
'<div v-for="item in items" :key="itemKey(item)">test</div>', '<div v-for="item in items" :key="itemKey(item)">test</div>',
{ prefixIdentifiers: true } { prefixIdentifiers: true },
) )
const innerBlock = codegenNode.children.arguments[1].returns const innerBlock = codegenNode.children.arguments[1].returns
expect(innerBlock).toMatchObject({ expect(innerBlock).toMatchObject({
@ -604,20 +604,20 @@ describe('compiler: v-for', () => {
`(`, `(`,
// should NOT prefix in scope variables // should NOT prefix in scope variables
{ content: `item` }, { content: `item` },
`)` `)`,
] ],
} },
}) }),
}) })
}) })
// #2085 // #2085
test('template v-for key expression prefixing', () => { test('template v-for key expression prefixing', () => {
const { const {
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform( } = parseWithForTransform(
'<template v-for="item in items" :key="itemKey(item)">test</template>', '<template v-for="item in items" :key="itemKey(item)">test</template>',
{ prefixIdentifiers: true } { prefixIdentifiers: true },
) )
const innerBlock = codegenNode.children.arguments[1].returns const innerBlock = codegenNode.children.arguments[1].returns
expect(innerBlock).toMatchObject({ expect(innerBlock).toMatchObject({
@ -632,19 +632,19 @@ describe('compiler: v-for', () => {
`(`, `(`,
// should NOT prefix in scope variables // should NOT prefix in scope variables
{ content: `item` }, { content: `item` },
`)` `)`,
] ],
} },
}) }),
}) })
}) })
test('template v-for key no prefixing on attribute key', () => { test('template v-for key no prefixing on attribute key', () => {
const { const {
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform( } = parseWithForTransform(
'<template v-for="item in items" key="key">test</template>', '<template v-for="item in items" key="key">test</template>',
{ prefixIdentifiers: true } { prefixIdentifiers: true },
) )
const innerBlock = codegenNode.children.arguments[1].returns const innerBlock = codegenNode.children.arguments[1].returns
expect(innerBlock).toMatchObject({ expect(innerBlock).toMatchObject({
@ -653,9 +653,9 @@ describe('compiler: v-for', () => {
props: createObjectMatcher({ props: createObjectMatcher({
key: { key: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: 'key' content: 'key',
} },
}) }),
}) })
}) })
}) })
@ -665,7 +665,7 @@ describe('compiler: v-for', () => {
node: ForCodegenNode, node: ForCodegenNode,
keyed: boolean = false, keyed: boolean = false,
customReturn: boolean = false, customReturn: boolean = false,
disableTracking: boolean = true disableTracking: boolean = true,
) { ) {
expect(node).toMatchObject({ expect(node).toMatchObject({
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
@ -687,32 +687,34 @@ describe('compiler: v-for', () => {
? {} ? {}
: { : {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
isBlock: disableTracking isBlock: disableTracking,
} },
} },
] ],
} },
}) })
const renderListArgs = node.children.arguments const renderListArgs = node.children.arguments
return { return {
source: renderListArgs[0] as SimpleExpressionNode, source: renderListArgs[0] as SimpleExpressionNode,
params: (renderListArgs[1] as any).params, params: (renderListArgs[1] as any).params,
returns: (renderListArgs[1] as any).returns, returns: (renderListArgs[1] as any).returns,
innerVNodeCall: customReturn ? null : (renderListArgs[1] as any).returns innerVNodeCall: customReturn
? null
: (renderListArgs[1] as any).returns,
} }
} }
test('basic v-for', () => { test('basic v-for', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform('<span v-for="(item) in items" />') } = parseWithForTransform('<span v-for="(item) in items" />')
expect(assertSharedCodegen(codegenNode)).toMatchObject({ expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` }, source: { content: `items` },
params: [{ content: `item` }], params: [{ content: `item` }],
innerVNodeCall: { innerVNodeCall: {
tag: `"span"` tag: `"span"`,
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -720,11 +722,11 @@ describe('compiler: v-for', () => {
test('value + key + index', () => { test('value + key + index', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform('<span v-for="(item, key, index) in items" />') } = parseWithForTransform('<span v-for="(item, key, index) in items" />')
expect(assertSharedCodegen(codegenNode)).toMatchObject({ expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` }, source: { content: `items` },
params: [{ content: `item` }, { content: `key` }, { content: `index` }] params: [{ content: `item` }, { content: `key` }, { content: `index` }],
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -732,11 +734,11 @@ describe('compiler: v-for', () => {
test('skipped value', () => { test('skipped value', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform('<span v-for="(, key, index) in items" />') } = parseWithForTransform('<span v-for="(, key, index) in items" />')
expect(assertSharedCodegen(codegenNode)).toMatchObject({ expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` }, source: { content: `items` },
params: [{ content: `_` }, { content: `key` }, { content: `index` }] params: [{ content: `_` }, { content: `key` }, { content: `index` }],
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -744,11 +746,11 @@ describe('compiler: v-for', () => {
test('skipped key', () => { test('skipped key', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform('<span v-for="(item,,index) in items" />') } = parseWithForTransform('<span v-for="(item,,index) in items" />')
expect(assertSharedCodegen(codegenNode)).toMatchObject({ expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` }, source: { content: `items` },
params: [{ content: `item` }, { content: `__` }, { content: `index` }] params: [{ content: `item` }, { content: `__` }, { content: `index` }],
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -756,11 +758,11 @@ describe('compiler: v-for', () => {
test('skipped value & key', () => { test('skipped value & key', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform('<span v-for="(,,index) in items" />') } = parseWithForTransform('<span v-for="(,,index) in items" />')
expect(assertSharedCodegen(codegenNode)).toMatchObject({ expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` }, source: { content: `items` },
params: [{ content: `_` }, { content: `__` }, { content: `index` }] params: [{ content: `_` }, { content: `__` }, { content: `index` }],
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -768,9 +770,9 @@ describe('compiler: v-for', () => {
test('v-for with constant expression', () => { test('v-for with constant expression', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform('<p v-for="item in 10">{{item}}</p>', { } = parseWithForTransform('<p v-for="item in 10">{{item}}</p>', {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect( expect(
@ -778,8 +780,8 @@ describe('compiler: v-for', () => {
codegenNode, codegenNode,
false /* keyed */, false /* keyed */,
false /* customReturn */, false /* customReturn */,
false /* disableTracking */ false /* disableTracking */,
) ),
).toMatchObject({ ).toMatchObject({
source: { content: `10`, constType: ConstantTypes.CAN_STRINGIFY }, source: { content: `10`, constType: ConstantTypes.CAN_STRINGIFY },
params: [{ content: `item` }], params: [{ content: `item` }],
@ -793,11 +795,11 @@ describe('compiler: v-for', () => {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: 'item', content: 'item',
isStatic: false, isStatic: false,
constType: ConstantTypes.NOT_CONSTANT constType: ConstantTypes.NOT_CONSTANT,
} },
},
patchFlag: genFlagText(PatchFlags.TEXT),
}, },
patchFlag: genFlagText(PatchFlags.TEXT)
}
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -805,9 +807,9 @@ describe('compiler: v-for', () => {
test('template v-for', () => { test('template v-for', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform( } = parseWithForTransform(
'<template v-for="item in items">hello<span/></template>' '<template v-for="item in items">hello<span/></template>',
) )
expect(assertSharedCodegen(codegenNode)).toMatchObject({ expect(assertSharedCodegen(codegenNode)).toMatchObject({
source: { content: `items` }, source: { content: `items` },
@ -818,10 +820,10 @@ describe('compiler: v-for', () => {
isBlock: true, isBlock: true,
children: [ children: [
{ type: NodeTypes.TEXT, content: `hello` }, { type: NodeTypes.TEXT, content: `hello` },
{ type: NodeTypes.ELEMENT, tag: `span` } { type: NodeTypes.ELEMENT, tag: `span` },
], ],
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT) patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT),
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -829,19 +831,19 @@ describe('compiler: v-for', () => {
test('template v-for w/ <slot/>', () => { test('template v-for w/ <slot/>', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform( } = parseWithForTransform(
'<template v-for="item in items"><slot/></template>' '<template v-for="item in items"><slot/></template>',
) )
expect( expect(
assertSharedCodegen(codegenNode, false, true /* custom return */) assertSharedCodegen(codegenNode, false, true /* custom return */),
).toMatchObject({ ).toMatchObject({
source: { content: `items` }, source: { content: `items` },
params: [{ content: `item` }], params: [{ content: `item` }],
returns: { returns: {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT callee: RENDER_SLOT,
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -850,9 +852,9 @@ describe('compiler: v-for', () => {
test('template v-for key injection with single child', () => { test('template v-for key injection with single child', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform( } = parseWithForTransform(
'<template v-for="item in items" :key="item.id"><span :id="item.id" /></template>' '<template v-for="item in items" :key="item.id"><span :id="item.id" /></template>',
) )
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({ expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
source: { content: `items` }, source: { content: `items` },
@ -862,9 +864,9 @@ describe('compiler: v-for', () => {
tag: `"span"`, tag: `"span"`,
props: createObjectMatcher({ props: createObjectMatcher({
key: '[item.id]', key: '[item.id]',
id: '[item.id]' id: '[item.id]',
}) }),
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -872,17 +874,17 @@ describe('compiler: v-for', () => {
test('v-for on <slot/>', () => { test('v-for on <slot/>', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform('<slot v-for="item in items"></slot>') } = parseWithForTransform('<slot v-for="item in items"></slot>')
expect( expect(
assertSharedCodegen(codegenNode, false, true /* custom return */) assertSharedCodegen(codegenNode, false, true /* custom return */),
).toMatchObject({ ).toMatchObject({
source: { content: `items` }, source: { content: `items` },
params: [{ content: `item` }], params: [{ content: `item` }],
returns: { returns: {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT callee: RENDER_SLOT,
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -890,7 +892,7 @@ describe('compiler: v-for', () => {
test('keyed v-for', () => { test('keyed v-for', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform('<span v-for="(item) in items" :key="item" />') } = parseWithForTransform('<span v-for="(item) in items" :key="item" />')
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({ expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
source: { content: `items` }, source: { content: `items` },
@ -898,9 +900,9 @@ describe('compiler: v-for', () => {
innerVNodeCall: { innerVNodeCall: {
tag: `"span"`, tag: `"span"`,
props: createObjectMatcher({ props: createObjectMatcher({
key: `[item]` key: `[item]`,
}) }),
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -908,9 +910,9 @@ describe('compiler: v-for', () => {
test('keyed template v-for', () => { test('keyed template v-for', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform( } = parseWithForTransform(
'<template v-for="item in items" :key="item">hello<span/></template>' '<template v-for="item in items" :key="item">hello<span/></template>',
) )
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({ expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
source: { content: `items` }, source: { content: `items` },
@ -918,14 +920,14 @@ describe('compiler: v-for', () => {
innerVNodeCall: { innerVNodeCall: {
tag: FRAGMENT, tag: FRAGMENT,
props: createObjectMatcher({ props: createObjectMatcher({
key: `[item]` key: `[item]`,
}), }),
children: [ children: [
{ type: NodeTypes.TEXT, content: `hello` }, { type: NodeTypes.TEXT, content: `hello` },
{ type: NodeTypes.ELEMENT, tag: `span` } { type: NodeTypes.ELEMENT, tag: `span` },
], ],
patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT) patchFlag: genFlagText(PatchFlags.STABLE_FRAGMENT),
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -933,7 +935,7 @@ describe('compiler: v-for', () => {
test('v-if + v-for', () => { test('v-if + v-for', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform(`<div v-if="ok" v-for="i in list"/>`) } = parseWithForTransform(`<div v-if="ok" v-for="i in list"/>`)
expect(codegenNode).toMatchObject({ expect(codegenNode).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION, type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
@ -941,7 +943,7 @@ describe('compiler: v-for', () => {
consequent: { consequent: {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
props: createObjectMatcher({ props: createObjectMatcher({
key: `[0]` key: `[0]`,
}), }),
isBlock: true, isBlock: true,
disableTracking: true, disableTracking: true,
@ -957,12 +959,12 @@ describe('compiler: v-for', () => {
returns: { returns: {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"`, tag: `"div"`,
isBlock: true isBlock: true,
} },
} },
] ],
} },
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -971,7 +973,7 @@ describe('compiler: v-for', () => {
test('v-if + v-for on <template>', () => { test('v-if + v-for on <template>', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform(`<template v-if="ok" v-for="i in list"/>`) } = parseWithForTransform(`<template v-if="ok" v-for="i in list"/>`)
expect(codegenNode).toMatchObject({ expect(codegenNode).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION, type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
@ -979,7 +981,7 @@ describe('compiler: v-for', () => {
consequent: { consequent: {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
props: createObjectMatcher({ props: createObjectMatcher({
key: `[0]` key: `[0]`,
}), }),
isBlock: true, isBlock: true,
disableTracking: true, disableTracking: true,
@ -995,12 +997,12 @@ describe('compiler: v-for', () => {
returns: { returns: {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: FRAGMENT, tag: FRAGMENT,
isBlock: true isBlock: true,
} },
} },
] ],
} },
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -1008,12 +1010,12 @@ describe('compiler: v-for', () => {
test('v-for on element with custom directive', () => { test('v-for on element with custom directive', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithForTransform('<div v-for="i in list" v-foo/>') } = parseWithForTransform('<div v-for="i in list" v-foo/>')
const { returns } = assertSharedCodegen(codegenNode, false, true) const { returns } = assertSharedCodegen(codegenNode, false, true)
expect(returns).toMatchObject({ expect(returns).toMatchObject({
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
directives: { type: NodeTypes.JS_ARRAY_EXPRESSION } directives: { type: NodeTypes.JS_ARRAY_EXPRESSION },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })

View File

@ -4,26 +4,26 @@ import { transformIf } from '../../src/transforms/vIf'
import { transformElement } from '../../src/transforms/transformElement' import { transformElement } from '../../src/transforms/transformElement'
import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet' import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet'
import { import {
CommentNode, type CommentNode,
ConditionalExpression, type ConditionalExpression,
ElementNode, type ElementNode,
ElementTypes, ElementTypes,
IfBranchNode, type IfBranchNode,
IfConditionalExpression, type IfConditionalExpression,
IfNode, type IfNode,
NodeTypes, NodeTypes,
SimpleExpressionNode, type SimpleExpressionNode,
TextNode, type TextNode,
VNodeCall type VNodeCall,
} from '../../src/ast' } from '../../src/ast'
import { ErrorCodes } from '../../src/errors' import { ErrorCodes } from '../../src/errors'
import { CompilerOptions, generate, TO_HANDLERS } from '../../src' import { type CompilerOptions, TO_HANDLERS, generate } from '../../src'
import { import {
CREATE_COMMENT, CREATE_COMMENT,
FRAGMENT, FRAGMENT,
MERGE_PROPS, MERGE_PROPS,
NORMALIZE_PROPS, NORMALIZE_PROPS,
RENDER_SLOT RENDER_SLOT,
} from '../../src/runtimeHelpers' } from '../../src/runtimeHelpers'
import { createObjectMatcher } from '../testUtils' import { createObjectMatcher } from '../testUtils'
@ -31,12 +31,12 @@ function parseWithIfTransform(
template: string, template: string,
options: CompilerOptions = {}, options: CompilerOptions = {},
returnIndex: number = 0, returnIndex: number = 0,
childrenLen: number = 1 childrenLen: number = 1,
) { ) {
const ast = parse(template, options) const ast = parse(template, options)
transform(ast, { transform(ast, {
nodeTransforms: [transformIf, transformSlotOutlet, transformElement], nodeTransforms: [transformIf, transformSlotOutlet, transformElement],
...options ...options,
}) })
if (!options.onError) { if (!options.onError) {
expect(ast.children.length).toBe(childrenLen) expect(ast.children.length).toBe(childrenLen)
@ -48,7 +48,7 @@ function parseWithIfTransform(
root: ast, root: ast,
node: ast.children[returnIndex] as IfNode & { node: ast.children[returnIndex] as IfNode & {
codegenNode: IfConditionalExpression codegenNode: IfConditionalExpression
} },
} }
} }
@ -59,7 +59,7 @@ describe('compiler: v-if', () => {
expect(node.type).toBe(NodeTypes.IF) expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(1) expect(node.branches.length).toBe(1)
expect((node.branches[0].condition as SimpleExpressionNode).content).toBe( expect((node.branches[0].condition as SimpleExpressionNode).content).toBe(
`ok` `ok`,
) )
expect(node.branches[0].children.length).toBe(1) expect(node.branches[0].children.length).toBe(1)
expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT) expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT)
@ -68,12 +68,12 @@ describe('compiler: v-if', () => {
test('template v-if', () => { test('template v-if', () => {
const { node } = parseWithIfTransform( const { node } = parseWithIfTransform(
`<template v-if="ok"><div/>hello<p/></template>` `<template v-if="ok"><div/>hello<p/></template>`,
) )
expect(node.type).toBe(NodeTypes.IF) expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(1) expect(node.branches.length).toBe(1)
expect((node.branches[0].condition as SimpleExpressionNode).content).toBe( expect((node.branches[0].condition as SimpleExpressionNode).content).toBe(
`ok` `ok`,
) )
expect(node.branches[0].children.length).toBe(3) expect(node.branches[0].children.length).toBe(3)
expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT) expect(node.branches[0].children[0].type).toBe(NodeTypes.ELEMENT)
@ -89,16 +89,16 @@ describe('compiler: v-if', () => {
expect(node.type).toBe(NodeTypes.IF) expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(1) expect(node.branches.length).toBe(1)
expect((node.branches[0].children[0] as ElementNode).tag).toBe( expect((node.branches[0].children[0] as ElementNode).tag).toBe(
`Component` `Component`,
) )
expect((node.branches[0].children[0] as ElementNode).tagType).toBe( expect((node.branches[0].children[0] as ElementNode).tagType).toBe(
ElementTypes.COMPONENT ElementTypes.COMPONENT,
) )
// #2058 since a component may fail to resolve and fallback to a plain // #2058 since a component may fail to resolve and fallback to a plain
// element, it still needs to be made a block // element, it still needs to be made a block
expect( expect(
((node.branches[0].children[0] as ElementNode)! ((node.branches[0].children[0] as ElementNode)!
.codegenNode as VNodeCall)!.isBlock .codegenNode as VNodeCall)!.isBlock,
).toBe(true) ).toBe(true)
}) })
@ -122,7 +122,7 @@ describe('compiler: v-if', () => {
test('v-if + v-else-if', () => { test('v-if + v-else-if', () => {
const { node } = parseWithIfTransform( const { node } = parseWithIfTransform(
`<div v-if="ok"/><p v-else-if="orNot"/>` `<div v-if="ok"/><p v-else-if="orNot"/>`,
) )
expect(node.type).toBe(NodeTypes.IF) expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(2) expect(node.branches.length).toBe(2)
@ -142,7 +142,7 @@ describe('compiler: v-if', () => {
test('v-if + v-else-if + v-else', () => { test('v-if + v-else-if + v-else', () => {
const { node } = parseWithIfTransform( const { node } = parseWithIfTransform(
`<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>` `<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`,
) )
expect(node.type).toBe(NodeTypes.IF) expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(3) expect(node.branches.length).toBe(3)
@ -202,11 +202,11 @@ describe('compiler: v-if', () => {
test('should prefix v-if condition', () => { test('should prefix v-if condition', () => {
const { node } = parseWithIfTransform(`<div v-if="ok"/>`, { const { node } = parseWithIfTransform(`<div v-if="ok"/>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect(node.branches[0].condition).toMatchObject({ expect(node.branches[0].condition).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.ok` content: `_ctx.ok`,
}) })
}) })
}) })
@ -219,32 +219,32 @@ describe('compiler: v-if', () => {
expect(onError.mock.calls[0]).toMatchObject([ expect(onError.mock.calls[0]).toMatchObject([
{ {
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
loc: node1.loc loc: node1.loc,
} },
]) ])
const { node: node2 } = parseWithIfTransform( const { node: node2 } = parseWithIfTransform(
`<div/><div v-else/>`, `<div/><div v-else/>`,
{ onError }, { onError },
1 1,
) )
expect(onError.mock.calls[1]).toMatchObject([ expect(onError.mock.calls[1]).toMatchObject([
{ {
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
loc: node2.loc loc: node2.loc,
} },
]) ])
const { node: node3 } = parseWithIfTransform( const { node: node3 } = parseWithIfTransform(
`<div/>foo<div v-else/>`, `<div/>foo<div v-else/>`,
{ onError }, { onError },
2 2,
) )
expect(onError.mock.calls[2]).toMatchObject([ expect(onError.mock.calls[2]).toMatchObject([
{ {
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
loc: node3.loc loc: node3.loc,
} },
]) ])
}) })
@ -252,52 +252,52 @@ describe('compiler: v-if', () => {
const onError = vi.fn() const onError = vi.fn()
const { node: node1 } = parseWithIfTransform(`<div v-else-if="foo"/>`, { const { node: node1 } = parseWithIfTransform(`<div v-else-if="foo"/>`, {
onError onError,
}) })
expect(onError.mock.calls[0]).toMatchObject([ expect(onError.mock.calls[0]).toMatchObject([
{ {
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
loc: node1.loc loc: node1.loc,
} },
]) ])
const { node: node2 } = parseWithIfTransform( const { node: node2 } = parseWithIfTransform(
`<div/><div v-else-if="foo"/>`, `<div/><div v-else-if="foo"/>`,
{ onError }, { onError },
1 1,
) )
expect(onError.mock.calls[1]).toMatchObject([ expect(onError.mock.calls[1]).toMatchObject([
{ {
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
loc: node2.loc loc: node2.loc,
} },
]) ])
const { node: node3 } = parseWithIfTransform( const { node: node3 } = parseWithIfTransform(
`<div/>foo<div v-else-if="foo"/>`, `<div/>foo<div v-else-if="foo"/>`,
{ onError }, { onError },
2 2,
) )
expect(onError.mock.calls[2]).toMatchObject([ expect(onError.mock.calls[2]).toMatchObject([
{ {
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
loc: node3.loc loc: node3.loc,
} },
]) ])
const { const {
node: { branches } node: { branches },
} = parseWithIfTransform( } = parseWithIfTransform(
`<div v-if="notOk"/><div v-else/><div v-else-if="ok"/>`, `<div v-if="notOk"/><div v-else/><div v-else-if="ok"/>`,
{ onError }, { onError },
0 0,
) )
expect(onError.mock.calls[3]).toMatchObject([ expect(onError.mock.calls[3]).toMatchObject([
{ {
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
loc: branches[branches.length - 1].loc loc: branches[branches.length - 1].loc,
} },
]) ])
}) })
@ -306,21 +306,21 @@ describe('compiler: v-if', () => {
// dynamic // dynamic
parseWithIfTransform( parseWithIfTransform(
`<div v-if="ok" :key="a + 1" /><div v-else :key="a + 1" />`, `<div v-if="ok" :key="a + 1" /><div v-else :key="a + 1" />`,
{ onError } { onError },
) )
expect(onError.mock.calls[0]).toMatchObject([ expect(onError.mock.calls[0]).toMatchObject([
{ {
code: ErrorCodes.X_V_IF_SAME_KEY code: ErrorCodes.X_V_IF_SAME_KEY,
} },
]) ])
// static // static
parseWithIfTransform(`<div v-if="ok" key="1" /><div v-else key="1" />`, { parseWithIfTransform(`<div v-if="ok" key="1" /><div v-else key="1" />`, {
onError onError,
}) })
expect(onError.mock.calls[1]).toMatchObject([ expect(onError.mock.calls[1]).toMatchObject([
{ {
code: ErrorCodes.X_V_IF_SAME_KEY code: ErrorCodes.X_V_IF_SAME_KEY,
} },
]) ])
}) })
}) })
@ -329,63 +329,63 @@ describe('compiler: v-if', () => {
function assertSharedCodegen( function assertSharedCodegen(
node: IfConditionalExpression, node: IfConditionalExpression,
depth: number = 0, depth: number = 0,
hasElse: boolean = false hasElse: boolean = false,
) { ) {
expect(node).toMatchObject({ expect(node).toMatchObject({
type: NodeTypes.JS_CONDITIONAL_EXPRESSION, type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test: { test: {
content: `ok` content: `ok`,
}, },
consequent: { consequent: {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
isBlock: true isBlock: true,
}, },
alternate: alternate:
depth < 1 depth < 1
? hasElse ? hasElse
? { ? {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
isBlock: true isBlock: true,
} }
: { : {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_COMMENT callee: CREATE_COMMENT,
} }
: { : {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION, type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test: { test: {
content: `orNot` content: `orNot`,
}, },
consequent: { consequent: {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
isBlock: true isBlock: true,
}, },
alternate: hasElse alternate: hasElse
? { ? {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
isBlock: true isBlock: true,
} }
: { : {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_COMMENT callee: CREATE_COMMENT,
} },
} },
}) })
} }
test('basic v-if', () => { test('basic v-if', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithIfTransform(`<div v-if="ok"/>`) } = parseWithIfTransform(`<div v-if="ok"/>`)
assertSharedCodegen(codegenNode) assertSharedCodegen(codegenNode)
expect(codegenNode.consequent).toMatchObject({ expect(codegenNode.consequent).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ key: `[0]` }) props: createObjectMatcher({ key: `[0]` }),
}) })
expect(codegenNode.alternate).toMatchObject({ expect(codegenNode.alternate).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_COMMENT callee: CREATE_COMMENT,
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -393,7 +393,7 @@ describe('compiler: v-if', () => {
test('template v-if', () => { test('template v-if', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithIfTransform(`<template v-if="ok"><div/>hello<p/></template>`) } = parseWithIfTransform(`<template v-if="ok"><div/>hello<p/></template>`)
assertSharedCodegen(codegenNode) assertSharedCodegen(codegenNode)
expect(codegenNode.consequent).toMatchObject({ expect(codegenNode.consequent).toMatchObject({
@ -402,12 +402,12 @@ describe('compiler: v-if', () => {
children: [ children: [
{ type: NodeTypes.ELEMENT, tag: 'div' }, { type: NodeTypes.ELEMENT, tag: 'div' },
{ type: NodeTypes.TEXT, content: `hello` }, { type: NodeTypes.TEXT, content: `hello` },
{ type: NodeTypes.ELEMENT, tag: 'p' } { type: NodeTypes.ELEMENT, tag: 'p' },
] ],
}) })
expect(codegenNode.alternate).toMatchObject({ expect(codegenNode.alternate).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_COMMENT callee: CREATE_COMMENT,
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -415,12 +415,12 @@ describe('compiler: v-if', () => {
test('template v-if w/ single <slot/> child', () => { test('template v-if w/ single <slot/> child', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithIfTransform(`<template v-if="ok"><slot/></template>`) } = parseWithIfTransform(`<template v-if="ok"><slot/></template>`)
expect(codegenNode.consequent).toMatchObject({ expect(codegenNode.consequent).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT, callee: RENDER_SLOT,
arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })] arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })],
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -428,12 +428,12 @@ describe('compiler: v-if', () => {
test('v-if on <slot/>', () => { test('v-if on <slot/>', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithIfTransform(`<slot v-if="ok"></slot>`) } = parseWithIfTransform(`<slot v-if="ok"></slot>`)
expect(codegenNode.consequent).toMatchObject({ expect(codegenNode.consequent).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT, callee: RENDER_SLOT,
arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })] arguments: ['$slots', '"default"', createObjectMatcher({ key: `[0]` })],
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -441,16 +441,16 @@ describe('compiler: v-if', () => {
test('v-if + v-else', () => { test('v-if + v-else', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithIfTransform(`<div v-if="ok"/><p v-else/>`) } = parseWithIfTransform(`<div v-if="ok"/><p v-else/>`)
assertSharedCodegen(codegenNode, 0, true) assertSharedCodegen(codegenNode, 0, true)
expect(codegenNode.consequent).toMatchObject({ expect(codegenNode.consequent).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ key: `[0]` }) props: createObjectMatcher({ key: `[0]` }),
}) })
expect(codegenNode.alternate).toMatchObject({ expect(codegenNode.alternate).toMatchObject({
tag: `"p"`, tag: `"p"`,
props: createObjectMatcher({ key: `[1]` }) props: createObjectMatcher({ key: `[1]` }),
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -458,17 +458,17 @@ describe('compiler: v-if', () => {
test('v-if + v-else-if', () => { test('v-if + v-else-if', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithIfTransform(`<div v-if="ok"/><p v-else-if="orNot" />`) } = parseWithIfTransform(`<div v-if="ok"/><p v-else-if="orNot" />`)
assertSharedCodegen(codegenNode, 1) assertSharedCodegen(codegenNode, 1)
expect(codegenNode.consequent).toMatchObject({ expect(codegenNode.consequent).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ key: `[0]` }) props: createObjectMatcher({ key: `[0]` }),
}) })
const branch2 = codegenNode.alternate as ConditionalExpression const branch2 = codegenNode.alternate as ConditionalExpression
expect(branch2.consequent).toMatchObject({ expect(branch2.consequent).toMatchObject({
tag: `"p"`, tag: `"p"`,
props: createObjectMatcher({ key: `[1]` }) props: createObjectMatcher({ key: `[1]` }),
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -476,19 +476,19 @@ describe('compiler: v-if', () => {
test('v-if + v-else-if + v-else', () => { test('v-if + v-else-if + v-else', () => {
const { const {
root, root,
node: { codegenNode } node: { codegenNode },
} = parseWithIfTransform( } = parseWithIfTransform(
`<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>` `<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`,
) )
assertSharedCodegen(codegenNode, 1, true) assertSharedCodegen(codegenNode, 1, true)
expect(codegenNode.consequent).toMatchObject({ expect(codegenNode.consequent).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ key: `[0]` }) props: createObjectMatcher({ key: `[0]` }),
}) })
const branch2 = codegenNode.alternate as ConditionalExpression const branch2 = codegenNode.alternate as ConditionalExpression
expect(branch2.consequent).toMatchObject({ expect(branch2.consequent).toMatchObject({
tag: `"p"`, tag: `"p"`,
props: createObjectMatcher({ key: `[1]` }) props: createObjectMatcher({ key: `[1]` }),
}) })
expect(branch2.alternate).toMatchObject({ expect(branch2.alternate).toMatchObject({
tag: FRAGMENT, tag: FRAGMENT,
@ -496,9 +496,9 @@ describe('compiler: v-if', () => {
children: [ children: [
{ {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: `fine` content: `fine`,
} },
] ],
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -508,7 +508,7 @@ describe('compiler: v-if', () => {
`<div v-if="ok"/><p v-if="orNot"/>`, `<div v-if="ok"/><p v-if="orNot"/>`,
{}, {},
0 /* returnIndex, just give the default value */, 0 /* returnIndex, just give the default value */,
2 /* childrenLen */ 2 /* childrenLen */,
) )
const ifNode = root.children[0] as IfNode & { const ifNode = root.children[0] as IfNode & {
@ -516,14 +516,14 @@ describe('compiler: v-if', () => {
} }
expect(ifNode.codegenNode.consequent).toMatchObject({ expect(ifNode.codegenNode.consequent).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ key: `[0]` }) props: createObjectMatcher({ key: `[0]` }),
}) })
const ifNode2 = root.children[1] as IfNode & { const ifNode2 = root.children[1] as IfNode & {
codegenNode: IfConditionalExpression codegenNode: IfConditionalExpression
} }
expect(ifNode2.codegenNode.consequent).toMatchObject({ expect(ifNode2.codegenNode.consequent).toMatchObject({
tag: `"p"`, tag: `"p"`,
props: createObjectMatcher({ key: `[1]` }) props: createObjectMatcher({ key: `[1]` }),
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -533,41 +533,41 @@ describe('compiler: v-if', () => {
`<div v-if="ok"/><p v-else/><div v-if="another"/><p v-else-if="orNot"/><p v-else/>`, `<div v-if="ok"/><p v-else/><div v-if="another"/><p v-else-if="orNot"/><p v-else/>`,
{}, {},
0 /* returnIndex, just give the default value */, 0 /* returnIndex, just give the default value */,
2 /* childrenLen */ 2 /* childrenLen */,
) )
const ifNode = root.children[0] as IfNode & { const ifNode = root.children[0] as IfNode & {
codegenNode: IfConditionalExpression codegenNode: IfConditionalExpression
} }
expect(ifNode.codegenNode.consequent).toMatchObject({ expect(ifNode.codegenNode.consequent).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ key: `[0]` }) props: createObjectMatcher({ key: `[0]` }),
}) })
expect(ifNode.codegenNode.alternate).toMatchObject({ expect(ifNode.codegenNode.alternate).toMatchObject({
tag: `"p"`, tag: `"p"`,
props: createObjectMatcher({ key: `[1]` }) props: createObjectMatcher({ key: `[1]` }),
}) })
const ifNode2 = root.children[1] as IfNode & { const ifNode2 = root.children[1] as IfNode & {
codegenNode: IfConditionalExpression codegenNode: IfConditionalExpression
} }
expect(ifNode2.codegenNode.consequent).toMatchObject({ expect(ifNode2.codegenNode.consequent).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ key: `[2]` }) props: createObjectMatcher({ key: `[2]` }),
}) })
const branch = ifNode2.codegenNode.alternate as IfConditionalExpression const branch = ifNode2.codegenNode.alternate as IfConditionalExpression
expect(branch.consequent).toMatchObject({ expect(branch.consequent).toMatchObject({
tag: `"p"`, tag: `"p"`,
props: createObjectMatcher({ key: `[3]` }) props: createObjectMatcher({ key: `[3]` }),
}) })
expect(branch.alternate).toMatchObject({ expect(branch.alternate).toMatchObject({
tag: `"p"`, tag: `"p"`,
props: createObjectMatcher({ key: `[4]` }) props: createObjectMatcher({ key: `[4]` }),
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
test('key injection (only v-bind)', () => { test('key injection (only v-bind)', () => {
const { const {
node: { codegenNode } node: { codegenNode },
} = parseWithIfTransform(`<div v-if="ok" v-bind="obj"/>`) } = parseWithIfTransform(`<div v-if="ok" v-bind="obj"/>`)
const branch1 = codegenNode.consequent as VNodeCall const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({ expect(branch1.props).toMatchObject({
@ -577,15 +577,18 @@ describe('compiler: v-if', () => {
{ {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: MERGE_PROPS, callee: MERGE_PROPS,
arguments: [createObjectMatcher({ key: `[0]` }), { content: `obj` }] arguments: [
} createObjectMatcher({ key: `[0]` }),
] { content: `obj` },
],
},
],
}) })
}) })
test('key injection (before v-bind)', () => { test('key injection (before v-bind)', () => {
const { const {
node: { codegenNode } node: { codegenNode },
} = parseWithIfTransform(`<div v-if="ok" id="foo" v-bind="obj"/>`) } = parseWithIfTransform(`<div v-if="ok" id="foo" v-bind="obj"/>`)
const branch1 = codegenNode.consequent as VNodeCall const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({ expect(branch1.props).toMatchObject({
@ -594,16 +597,16 @@ describe('compiler: v-if', () => {
arguments: [ arguments: [
createObjectMatcher({ createObjectMatcher({
key: '[0]', key: '[0]',
id: 'foo' id: 'foo',
}), }),
{ content: `obj` } { content: `obj` },
] ],
}) })
}) })
test('key injection (after v-bind)', () => { test('key injection (after v-bind)', () => {
const { const {
node: { codegenNode } node: { codegenNode },
} = parseWithIfTransform(`<div v-if="ok" v-bind="obj" id="foo"/>`) } = parseWithIfTransform(`<div v-if="ok" v-bind="obj" id="foo"/>`)
const branch1 = codegenNode.consequent as VNodeCall const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({ expect(branch1.props).toMatchObject({
@ -613,15 +616,15 @@ describe('compiler: v-if', () => {
createObjectMatcher({ key: `[0]` }), createObjectMatcher({ key: `[0]` }),
{ content: `obj` }, { content: `obj` },
createObjectMatcher({ createObjectMatcher({
id: 'foo' id: 'foo',
}) }),
] ],
}) })
}) })
test('key injection (w/ custom directive)', () => { test('key injection (w/ custom directive)', () => {
const { const {
node: { codegenNode } node: { codegenNode },
} = parseWithIfTransform(`<div v-if="ok" v-foo />`) } = parseWithIfTransform(`<div v-if="ok" v-foo />`)
const branch1 = codegenNode.consequent as VNodeCall const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.directives).not.toBeUndefined() expect(branch1.directives).not.toBeUndefined()
@ -631,7 +634,7 @@ describe('compiler: v-if', () => {
// #6631 // #6631
test('avoid duplicate keys', () => { test('avoid duplicate keys', () => {
const { const {
node: { codegenNode } node: { codegenNode },
} = parseWithIfTransform(`<div v-if="ok" key="custom_key" v-bind="obj"/>`) } = parseWithIfTransform(`<div v-if="ok" key="custom_key" v-bind="obj"/>`)
const branch1 = codegenNode.consequent as VNodeCall const branch1 = codegenNode.consequent as VNodeCall
expect(branch1.props).toMatchObject({ expect(branch1.props).toMatchObject({
@ -639,31 +642,31 @@ describe('compiler: v-if', () => {
callee: MERGE_PROPS, callee: MERGE_PROPS,
arguments: [ arguments: [
createObjectMatcher({ createObjectMatcher({
key: 'custom_key' key: 'custom_key',
}), }),
{ content: `obj` } { content: `obj` },
] ],
}) })
}) })
test('with spaces between branches', () => { test('with spaces between branches', () => {
const { const {
node: { codegenNode } node: { codegenNode },
} = parseWithIfTransform( } = parseWithIfTransform(
`<div v-if="ok"/> <div v-else-if="no"/> <div v-else/>` `<div v-if="ok"/> <div v-else-if="no"/> <div v-else/>`,
) )
expect(codegenNode.consequent).toMatchObject({ expect(codegenNode.consequent).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ key: `[0]` }) props: createObjectMatcher({ key: `[0]` }),
}) })
const branch = codegenNode.alternate as ConditionalExpression const branch = codegenNode.alternate as ConditionalExpression
expect(branch.consequent).toMatchObject({ expect(branch.consequent).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ key: `[1]` }) props: createObjectMatcher({ key: `[1]` }),
}) })
expect(branch.alternate).toMatchObject({ expect(branch.alternate).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ key: `[2]` }) props: createObjectMatcher({ key: `[2]` }),
}) })
}) })
@ -729,7 +732,7 @@ describe('compiler: v-if', () => {
<p/> <p/>
</template> </template>
`, `,
{ comments: true } { comments: true },
) )
__DEV__ = true __DEV__ = true
}) })
@ -737,21 +740,21 @@ describe('compiler: v-if', () => {
test('v-on with v-if', () => { test('v-on with v-if', () => {
const { const {
node: { codegenNode } node: { codegenNode },
} = parseWithIfTransform( } = parseWithIfTransform(
`<button v-on="{ click: clickEvent }" v-if="true">w/ v-if</button>` `<button v-on="{ click: clickEvent }" v-if="true">w/ v-if</button>`,
) )
expect((codegenNode.consequent as any).props.type).toBe( expect((codegenNode.consequent as any).props.type).toBe(
NodeTypes.JS_CALL_EXPRESSION NodeTypes.JS_CALL_EXPRESSION,
) )
expect((codegenNode.consequent as any).props.callee).toBe(MERGE_PROPS) expect((codegenNode.consequent as any).props.callee).toBe(MERGE_PROPS)
expect( expect(
(codegenNode.consequent as any).props.arguments[0].properties[0].value (codegenNode.consequent as any).props.arguments[0].properties[0].value
.content .content,
).toBe('0') ).toBe('0')
expect((codegenNode.consequent as any).props.arguments[1].callee).toBe( expect((codegenNode.consequent as any).props.arguments[1].callee).toBe(
TO_HANDLERS TO_HANDLERS,
) )
}) })
}) })

View File

@ -4,7 +4,7 @@ describe('compiler: v-memo transform', () => {
function compile(content: string) { function compile(content: string) {
return baseCompile(`<div>${content}</div>`, { return baseCompile(`<div>${content}</div>`, {
mode: 'module', mode: 'module',
prefixIdentifiers: true prefixIdentifiers: true,
}).code }).code
} }
@ -12,8 +12,8 @@ describe('compiler: v-memo transform', () => {
expect( expect(
baseCompile(`<div v-memo="[x]"></div>`, { baseCompile(`<div v-memo="[x]"></div>`, {
mode: 'module', mode: 'module',
prefixIdentifiers: true prefixIdentifiers: true,
}).code }).code,
).toMatchSnapshot() ).toMatchSnapshot()
}) })
@ -29,8 +29,8 @@ describe('compiler: v-memo transform', () => {
expect( expect(
compile( compile(
`<div v-if="ok" v-memo="[x]"><span>foo</span>bar</div> `<div v-if="ok" v-memo="[x]"><span>foo</span>bar</div>
<Comp v-else v-memo="[x]"></Comp>` <Comp v-else v-memo="[x]"></Comp>`,
) ),
).toMatchSnapshot() ).toMatchSnapshot()
}) })
@ -39,8 +39,8 @@ describe('compiler: v-memo transform', () => {
compile( compile(
`<div v-for="{ x, y } in list" :key="x" v-memo="[x, y === z]"> `<div v-for="{ x, y } in list" :key="x" v-memo="[x, y === z]">
<span>foobar</span> <span>foobar</span>
</div>` </div>`,
) ),
).toMatchSnapshot() ).toMatchSnapshot()
}) })
@ -49,8 +49,8 @@ describe('compiler: v-memo transform', () => {
compile( compile(
`<template v-for="{ x, y } in list" :key="x" v-memo="[x, y === z]"> `<template v-for="{ x, y } in list" :key="x" v-memo="[x, y === z]">
<span>foobar</span> <span>foobar</span>
</template>` </template>`,
) ),
).toMatchSnapshot() ).toMatchSnapshot()
}) })
}) })

View File

@ -1,17 +1,17 @@
import { import {
BindingTypes,
type CompilerOptions,
type ComponentNode,
type ElementNode,
type ForNode,
NORMALIZE_PROPS,
NodeTypes,
type ObjectExpression,
type PlainElementNode,
type VNodeCall,
generate,
baseParse as parse, baseParse as parse,
transform, transform,
generate,
ElementNode,
ObjectExpression,
CompilerOptions,
ForNode,
PlainElementNode,
ComponentNode,
NodeTypes,
VNodeCall,
NORMALIZE_PROPS,
BindingTypes
} from '../../src' } from '../../src'
import { ErrorCodes } from '../../src/errors' import { ErrorCodes } from '../../src/errors'
import { transformModel } from '../../src/transforms/vModel' import { transformModel } from '../../src/transforms/vModel'
@ -19,7 +19,7 @@ import { transformElement } from '../../src/transforms/transformElement'
import { transformExpression } from '../../src/transforms/transformExpression' import { transformExpression } from '../../src/transforms/transformExpression'
import { transformFor } from '../../src/transforms/vFor' import { transformFor } from '../../src/transforms/vFor'
import { trackSlotScopes } from '../../src/transforms/vSlot' import { trackSlotScopes } from '../../src/transforms/vSlot'
import { CallExpression } from '@babel/types' import type { CallExpression } from '@babel/types'
function parseWithVModel(template: string, options: CompilerOptions = {}) { function parseWithVModel(template: string, options: CompilerOptions = {}) {
const ast = parse(template) const ast = parse(template)
@ -29,13 +29,13 @@ function parseWithVModel(template: string, options: CompilerOptions = {}) {
transformFor, transformFor,
transformExpression, transformExpression,
transformElement, transformElement,
trackSlotScopes trackSlotScopes,
], ],
directiveTransforms: { directiveTransforms: {
...options.directiveTransforms, ...options.directiveTransforms,
model: transformModel model: transformModel,
}, },
...options ...options,
}) })
return ast return ast
@ -51,29 +51,29 @@ describe('compiler: transform v-model', () => {
expect(props[0]).toMatchObject({ expect(props[0]).toMatchObject({
key: { key: {
content: 'modelValue', content: 'modelValue',
isStatic: true isStatic: true,
}, },
value: { value: {
content: 'model', content: 'model',
isStatic: false isStatic: false,
} },
}) })
expect(props[1]).toMatchObject({ expect(props[1]).toMatchObject({
key: { key: {
content: 'onUpdate:modelValue', content: 'onUpdate:modelValue',
isStatic: true isStatic: true,
}, },
value: { value: {
children: [ children: [
'$event => ((', '$event => ((',
{ {
content: 'model', content: 'model',
isStatic: false isStatic: false,
},
') = $event)',
],
}, },
') = $event)'
]
}
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
@ -81,7 +81,7 @@ describe('compiler: transform v-model', () => {
test('simple expression (with prefixIdentifiers)', () => { test('simple expression (with prefixIdentifiers)', () => {
const root = parseWithVModel('<input v-model="model" />', { const root = parseWithVModel('<input v-model="model" />', {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
const node = root.children[0] as ElementNode const node = root.children[0] as ElementNode
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression) const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
@ -90,29 +90,29 @@ describe('compiler: transform v-model', () => {
expect(props[0]).toMatchObject({ expect(props[0]).toMatchObject({
key: { key: {
content: 'modelValue', content: 'modelValue',
isStatic: true isStatic: true,
}, },
value: { value: {
content: '_ctx.model', content: '_ctx.model',
isStatic: false isStatic: false,
} },
}) })
expect(props[1]).toMatchObject({ expect(props[1]).toMatchObject({
key: { key: {
content: 'onUpdate:modelValue', content: 'onUpdate:modelValue',
isStatic: true isStatic: true,
}, },
value: { value: {
children: [ children: [
'$event => ((', '$event => ((',
{ {
content: '_ctx.model', content: '_ctx.model',
isStatic: false isStatic: false,
},
') = $event)',
],
}, },
') = $event)'
]
}
}) })
expect(generate(root, { mode: 'module' }).code).toMatchSnapshot() expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
@ -128,29 +128,29 @@ describe('compiler: transform v-model', () => {
expect(props[0]).toMatchObject({ expect(props[0]).toMatchObject({
key: { key: {
content: 'modelValue', content: 'modelValue',
isStatic: true isStatic: true,
}, },
value: { value: {
content: '\n model\n.\nfoo \n', content: '\n model\n.\nfoo \n',
isStatic: false isStatic: false,
} },
}) })
expect(props[1]).toMatchObject({ expect(props[1]).toMatchObject({
key: { key: {
content: 'onUpdate:modelValue', content: 'onUpdate:modelValue',
isStatic: true isStatic: true,
}, },
value: { value: {
children: [ children: [
'$event => ((', '$event => ((',
{ {
content: '\n model\n.\nfoo \n', content: '\n model\n.\nfoo \n',
isStatic: false isStatic: false,
},
') = $event)',
],
}, },
') = $event)'
]
}
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
@ -165,29 +165,29 @@ describe('compiler: transform v-model', () => {
expect(props[0]).toMatchObject({ expect(props[0]).toMatchObject({
key: { key: {
content: 'modelValue', content: 'modelValue',
isStatic: true isStatic: true,
}, },
value: { value: {
content: 'model[index]', content: 'model[index]',
isStatic: false isStatic: false,
} },
}) })
expect(props[1]).toMatchObject({ expect(props[1]).toMatchObject({
key: { key: {
content: 'onUpdate:modelValue', content: 'onUpdate:modelValue',
isStatic: true isStatic: true,
}, },
value: { value: {
children: [ children: [
'$event => ((', '$event => ((',
{ {
content: 'model[index]', content: 'model[index]',
isStatic: false isStatic: false,
},
') = $event)',
],
}, },
') = $event)'
]
}
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
@ -195,7 +195,7 @@ describe('compiler: transform v-model', () => {
test('compound expression (with prefixIdentifiers)', () => { test('compound expression (with prefixIdentifiers)', () => {
const root = parseWithVModel('<input v-model="model[index]" />', { const root = parseWithVModel('<input v-model="model[index]" />', {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
const node = root.children[0] as ElementNode const node = root.children[0] as ElementNode
const props = ((node.codegenNode as VNodeCall).props as ObjectExpression) const props = ((node.codegenNode as VNodeCall).props as ObjectExpression)
@ -204,28 +204,28 @@ describe('compiler: transform v-model', () => {
expect(props[0]).toMatchObject({ expect(props[0]).toMatchObject({
key: { key: {
content: 'modelValue', content: 'modelValue',
isStatic: true isStatic: true,
}, },
value: { value: {
children: [ children: [
{ {
content: '_ctx.model', content: '_ctx.model',
isStatic: false isStatic: false,
}, },
'[', '[',
{ {
content: '_ctx.index', content: '_ctx.index',
isStatic: false isStatic: false,
},
']',
],
}, },
']'
]
}
}) })
expect(props[1]).toMatchObject({ expect(props[1]).toMatchObject({
key: { key: {
content: 'onUpdate:modelValue', content: 'onUpdate:modelValue',
isStatic: true isStatic: true,
}, },
value: { value: {
children: [ children: [
@ -234,19 +234,19 @@ describe('compiler: transform v-model', () => {
children: [ children: [
{ {
content: '_ctx.model', content: '_ctx.model',
isStatic: false isStatic: false,
}, },
'[', '[',
{ {
content: '_ctx.index', content: '_ctx.index',
isStatic: false isStatic: false,
}, },
']' ']',
] ],
},
') = $event)',
],
}, },
') = $event)'
]
}
}) })
expect(generate(root, { mode: 'module' }).code).toMatchSnapshot() expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
@ -260,29 +260,29 @@ describe('compiler: transform v-model', () => {
expect(props[0]).toMatchObject({ expect(props[0]).toMatchObject({
key: { key: {
content: 'foo-value', content: 'foo-value',
isStatic: true isStatic: true,
}, },
value: { value: {
content: 'model', content: 'model',
isStatic: false isStatic: false,
} },
}) })
expect(props[1]).toMatchObject({ expect(props[1]).toMatchObject({
key: { key: {
content: 'onUpdate:fooValue', content: 'onUpdate:fooValue',
isStatic: true isStatic: true,
}, },
value: { value: {
children: [ children: [
'$event => ((', '$event => ((',
{ {
content: 'model', content: 'model',
isStatic: false isStatic: false,
},
') = $event)',
],
}, },
') = $event)'
]
}
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
@ -304,12 +304,12 @@ describe('compiler: transform v-model', () => {
{ {
key: { key: {
content: 'value', content: 'value',
isStatic: false isStatic: false,
}, },
value: { value: {
content: 'model', content: 'model',
isStatic: false isStatic: false,
} },
}, },
{ {
key: { key: {
@ -317,24 +317,24 @@ describe('compiler: transform v-model', () => {
'"onUpdate:" + ', '"onUpdate:" + ',
{ {
content: 'value', content: 'value',
isStatic: false isStatic: false,
} },
] ],
}, },
value: { value: {
children: [ children: [
'$event => ((', '$event => ((',
{ {
content: 'model', content: 'model',
isStatic: false isStatic: false,
}, },
') = $event)' ') = $event)',
] ],
} },
} },
] ],
} },
] ],
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
@ -342,7 +342,7 @@ describe('compiler: transform v-model', () => {
test('with dynamic argument (with prefixIdentifiers)', () => { test('with dynamic argument (with prefixIdentifiers)', () => {
const root = parseWithVModel('<input v-model:[value]="model" />', { const root = parseWithVModel('<input v-model:[value]="model" />', {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
const node = root.children[0] as ElementNode const node = root.children[0] as ElementNode
const props = (node.codegenNode as VNodeCall) const props = (node.codegenNode as VNodeCall)
@ -358,12 +358,12 @@ describe('compiler: transform v-model', () => {
{ {
key: { key: {
content: '_ctx.value', content: '_ctx.value',
isStatic: false isStatic: false,
}, },
value: { value: {
content: '_ctx.model', content: '_ctx.model',
isStatic: false isStatic: false,
} },
}, },
{ {
key: { key: {
@ -371,24 +371,24 @@ describe('compiler: transform v-model', () => {
'"onUpdate:" + ', '"onUpdate:" + ',
{ {
content: '_ctx.value', content: '_ctx.value',
isStatic: false isStatic: false,
} },
] ],
}, },
value: { value: {
children: [ children: [
'$event => ((', '$event => ((',
{ {
content: '_ctx.model', content: '_ctx.model',
isStatic: false isStatic: false,
}, },
') = $event)' ') = $event)',
] ],
} },
} },
] ],
} },
] ],
}) })
expect(generate(root, { mode: 'module' }).code).toMatchSnapshot() expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
@ -397,7 +397,7 @@ describe('compiler: transform v-model', () => {
test('should cache update handler w/ cacheHandlers: true', () => { test('should cache update handler w/ cacheHandlers: true', () => {
const root = parseWithVModel('<input v-model="foo" />', { const root = parseWithVModel('<input v-model="foo" />', {
prefixIdentifiers: true, prefixIdentifiers: true,
cacheHandlers: true cacheHandlers: true,
}) })
expect(root.cached).toBe(1) expect(root.cached).toBe(1)
const codegen = (root.children[0] as PlainElementNode) const codegen = (root.children[0] as PlainElementNode)
@ -405,7 +405,7 @@ describe('compiler: transform v-model', () => {
// should not list cached prop in dynamicProps // should not list cached prop in dynamicProps
expect(codegen.dynamicProps).toBe(`["modelValue"]`) expect(codegen.dynamicProps).toBe(`["modelValue"]`)
expect((codegen.props as ObjectExpression).properties[1].value.type).toBe( expect((codegen.props as ObjectExpression).properties[1].value.type).toBe(
NodeTypes.JS_CACHE_EXPRESSION NodeTypes.JS_CACHE_EXPRESSION,
) )
}) })
@ -414,8 +414,8 @@ describe('compiler: transform v-model', () => {
'<input v-for="i in list" v-model="foo[i]" />', '<input v-for="i in list" v-model="foo[i]" />',
{ {
prefixIdentifiers: true, prefixIdentifiers: true,
cacheHandlers: true cacheHandlers: true,
} },
) )
expect(root.cached).toBe(0) expect(root.cached).toBe(0)
const codegen = ( const codegen = (
@ -423,14 +423,14 @@ describe('compiler: transform v-model', () => {
).codegenNode as VNodeCall ).codegenNode as VNodeCall
expect(codegen.dynamicProps).toBe(`["modelValue", "onUpdate:modelValue"]`) expect(codegen.dynamicProps).toBe(`["modelValue", "onUpdate:modelValue"]`)
expect( expect(
(codegen.props as ObjectExpression).properties[1].value.type (codegen.props as ObjectExpression).properties[1].value.type,
).not.toBe(NodeTypes.JS_CACHE_EXPRESSION) ).not.toBe(NodeTypes.JS_CACHE_EXPRESSION)
}) })
test('should not cache update handler if it inside v-once', () => { test('should not cache update handler if it inside v-once', () => {
const root = parseWithVModel('<div v-once><input v-model="foo" /></div>', { const root = parseWithVModel('<div v-once><input v-model="foo" /></div>', {
prefixIdentifiers: true, prefixIdentifiers: true,
cacheHandlers: true cacheHandlers: true,
}) })
expect(root.cached).not.toBe(2) expect(root.cached).not.toBe(2)
expect(root.cached).toBe(1) expect(root.cached).toBe(1)
@ -440,8 +440,8 @@ describe('compiler: transform v-model', () => {
const root = parseWithVModel( const root = parseWithVModel(
'<Comp v-slot="{ foo }"><input v-model="foo.bar"/></Comp>', '<Comp v-slot="{ foo }"><input v-model="foo.bar"/></Comp>',
{ {
prefixIdentifiers: true prefixIdentifiers: true,
} },
) )
const codegen = ( const codegen = (
(root.children[0] as ComponentNode).children[0] as PlainElementNode (root.children[0] as ComponentNode).children[0] as PlainElementNode
@ -451,7 +451,7 @@ describe('compiler: transform v-model', () => {
test('should generate modelModifiers for component v-model', () => { test('should generate modelModifiers for component v-model', () => {
const root = parseWithVModel('<Comp v-model.trim.bar-baz="foo" />', { const root = parseWithVModel('<Comp v-model.trim.bar-baz="foo" />', {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
const vnodeCall = (root.children[0] as ComponentNode) const vnodeCall = (root.children[0] as ComponentNode)
.codegenNode as VNodeCall .codegenNode as VNodeCall
@ -462,9 +462,12 @@ describe('compiler: transform v-model', () => {
{ key: { content: `onUpdate:modelValue` } }, { key: { content: `onUpdate:modelValue` } },
{ {
key: { content: 'modelModifiers' }, key: { content: 'modelModifiers' },
value: { content: `{ trim: true, "bar-baz": true }`, isStatic: false } value: {
} content: `{ trim: true, "bar-baz": true }`,
] isStatic: false,
},
},
],
}) })
// should NOT include modelModifiers in dynamicPropNames because it's never // should NOT include modelModifiers in dynamicPropNames because it's never
// gonna change // gonna change
@ -475,8 +478,8 @@ describe('compiler: transform v-model', () => {
const root = parseWithVModel( const root = parseWithVModel(
'<Comp v-model:foo.trim="foo" v-model:bar.number="bar" />', '<Comp v-model:foo.trim="foo" v-model:bar.number="bar" />',
{ {
prefixIdentifiers: true prefixIdentifiers: true,
} },
) )
const vnodeCall = (root.children[0] as ComponentNode) const vnodeCall = (root.children[0] as ComponentNode)
.codegenNode as VNodeCall .codegenNode as VNodeCall
@ -487,20 +490,20 @@ describe('compiler: transform v-model', () => {
{ key: { content: `onUpdate:foo` } }, { key: { content: `onUpdate:foo` } },
{ {
key: { content: 'fooModifiers' }, key: { content: 'fooModifiers' },
value: { content: `{ trim: true }`, isStatic: false } value: { content: `{ trim: true }`, isStatic: false },
}, },
{ key: { content: `bar` } }, { key: { content: `bar` } },
{ key: { content: `onUpdate:bar` } }, { key: { content: `onUpdate:bar` } },
{ {
key: { content: 'barModifiers' }, key: { content: 'barModifiers' },
value: { content: `{ number: true }`, isStatic: false } value: { content: `{ number: true }`, isStatic: false },
} },
] ],
}) })
// should NOT include modelModifiers in dynamicPropNames because it's never // should NOT include modelModifiers in dynamicPropNames because it's never
// gonna change // gonna change
expect(vnodeCall.dynamicProps).toBe( expect(vnodeCall.dynamicProps).toBe(
`["foo", "onUpdate:foo", "bar", "onUpdate:bar"]` `["foo", "onUpdate:foo", "bar", "onUpdate:bar"]`,
) )
}) })
@ -512,8 +515,8 @@ describe('compiler: transform v-model', () => {
expect(onError).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith( expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
code: ErrorCodes.X_V_MODEL_NO_EXPRESSION code: ErrorCodes.X_V_MODEL_NO_EXPRESSION,
}) }),
) )
}) })
@ -524,8 +527,8 @@ describe('compiler: transform v-model', () => {
expect(onError).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith( expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION,
}) }),
) )
}) })
@ -536,8 +539,8 @@ describe('compiler: transform v-model', () => {
expect(onError).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith( expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION code: ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION,
}) }),
) )
}) })
@ -552,14 +555,14 @@ describe('compiler: transform v-model', () => {
const onError = vi.fn() const onError = vi.fn()
parseWithVModel('<span v-for="i in list" v-model="i" />', { parseWithVModel('<span v-for="i in list" v-model="i" />', {
onError, onError,
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect(onError).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith( expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
code: ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE code: ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE,
}) }),
) )
}) })
@ -568,15 +571,15 @@ describe('compiler: transform v-model', () => {
parseWithVModel('<div v-model="p" />', { parseWithVModel('<div v-model="p" />', {
onError, onError,
bindingMetadata: { bindingMetadata: {
p: BindingTypes.PROPS p: BindingTypes.PROPS,
} },
}) })
expect(onError).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith( expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
code: ErrorCodes.X_V_MODEL_ON_PROPS code: ErrorCodes.X_V_MODEL_ON_PROPS,
}) }),
) )
}) })
}) })

View File

@ -1,14 +1,14 @@
import { import {
baseParse as parse, type CompilerOptions,
CompilerOptions, type ElementNode,
ElementNode,
ErrorCodes, ErrorCodes,
TO_HANDLER_KEY,
helperNameMap,
NodeTypes, NodeTypes,
ObjectExpression, type ObjectExpression,
TO_HANDLER_KEY,
type VNodeCall,
helperNameMap,
baseParse as parse,
transform, transform,
VNodeCall
} from '../../src' } from '../../src'
import { transformOn } from '../../src/transforms/vOn' import { transformOn } from '../../src/transforms/vOn'
import { transformElement } from '../../src/transforms/transformElement' import { transformElement } from '../../src/transforms/transformElement'
@ -19,13 +19,13 @@ function parseWithVOn(template: string, options: CompilerOptions = {}) {
transform(ast, { transform(ast, {
nodeTransforms: [transformExpression, transformElement], nodeTransforms: [transformExpression, transformElement],
directiveTransforms: { directiveTransforms: {
on: transformOn on: transformOn,
}, },
...options ...options,
}) })
return { return {
root: ast, root: ast,
node: ast.children[0] as ElementNode node: ast.children[0] as ElementNode,
} }
} }
@ -41,13 +41,13 @@ describe('compiler: transform v-on', () => {
loc: { loc: {
start: { start: {
line: 1, line: 1,
column: 11 column: 11,
}, },
end: { end: {
line: 1, line: 1,
column: 16 column: 16,
} },
} },
}, },
value: { value: {
content: `onClick`, content: `onClick`,
@ -55,16 +55,16 @@ describe('compiler: transform v-on', () => {
loc: { loc: {
start: { start: {
line: 1, line: 1,
column: 18 column: 18,
}, },
end: { end: {
line: 1, line: 1,
column: 25 column: 25,
} },
} },
} },
} },
] ],
}) })
}) })
@ -78,22 +78,22 @@ describe('compiler: transform v-on', () => {
children: [ children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`, `_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: `event` }, { content: `event` },
`)` `)`,
] ],
}, },
value: { value: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `handler`, content: `handler`,
isStatic: false isStatic: false,
} },
} },
] ],
}) })
}) })
test('dynamic arg with prefixing', () => { test('dynamic arg with prefixing', () => {
const { node } = parseWithVOn(`<div v-on:[event]="handler"/>`, { const { node } = parseWithVOn(`<div v-on:[event]="handler"/>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect((node.codegenNode as VNodeCall).props).toMatchObject({ expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [ properties: [
@ -103,22 +103,22 @@ describe('compiler: transform v-on', () => {
children: [ children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`, `_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: `_ctx.event` }, { content: `_ctx.event` },
`)` `)`,
] ],
}, },
value: { value: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.handler`, content: `_ctx.handler`,
isStatic: false isStatic: false,
} },
} },
] ],
}) })
}) })
test('dynamic arg with complex exp prefixing', () => { test('dynamic arg with complex exp prefixing', () => {
const { node } = parseWithVOn(`<div v-on:[event(foo)]="handler"/>`, { const { node } = parseWithVOn(`<div v-on:[event(foo)]="handler"/>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect((node.codegenNode as VNodeCall).props).toMatchObject({ expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [ properties: [
@ -131,16 +131,16 @@ describe('compiler: transform v-on', () => {
`(`, `(`,
{ content: `_ctx.foo` }, { content: `_ctx.foo` },
`)`, `)`,
`)` `)`,
] ],
}, },
value: { value: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.handler`, content: `_ctx.handler`,
isStatic: false isStatic: false,
} },
} },
] ],
}) })
}) })
@ -152,10 +152,10 @@ describe('compiler: transform v-on', () => {
key: { content: `onClick` }, key: { content: `onClick` },
value: { value: {
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [`$event => (`, { content: `i++` }, `)`] children: [`$event => (`, { content: `i++` }, `)`],
} },
} },
] ],
}) })
}) })
@ -170,10 +170,10 @@ describe('compiler: transform v-on', () => {
// should wrap with `{` for multiple statements // should wrap with `{` for multiple statements
// in this case the return value is discarded and the behavior is // in this case the return value is discarded and the behavior is
// consistent with 2.x // consistent with 2.x
children: [`$event => {`, { content: `foo();bar()` }, `}`] children: [`$event => {`, { content: `foo();bar()` }, `}`],
} },
} },
] ],
}) })
}) })
@ -188,16 +188,16 @@ describe('compiler: transform v-on', () => {
// should wrap with `{` for multiple statements // should wrap with `{` for multiple statements
// in this case the return value is discarded and the behavior is // in this case the return value is discarded and the behavior is
// consistent with 2.x // consistent with 2.x
children: [`$event => {`, { content: `\nfoo();\nbar()\n` }, `}`] children: [`$event => {`, { content: `\nfoo();\nbar()\n` }, `}`],
} },
} },
] ],
}) })
}) })
test('inline statement w/ prefixIdentifiers: true', () => { test('inline statement w/ prefixIdentifiers: true', () => {
const { node } = parseWithVOn(`<div @click="foo($event)"/>`, { const { node } = parseWithVOn(`<div @click="foo($event)"/>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect((node.codegenNode as VNodeCall).props).toMatchObject({ expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [ properties: [
@ -214,20 +214,20 @@ describe('compiler: transform v-on', () => {
`(`, `(`,
// should NOT prefix $event // should NOT prefix $event
{ content: `$event` }, { content: `$event` },
`)` `)`,
] ],
}, },
`)` `)`,
] ],
} },
} },
] ],
}) })
}) })
test('multiple inline statements w/ prefixIdentifiers: true', () => { test('multiple inline statements w/ prefixIdentifiers: true', () => {
const { node } = parseWithVOn(`<div @click="foo($event);bar()"/>`, { const { node } = parseWithVOn(`<div @click="foo($event);bar()"/>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect((node.codegenNode as VNodeCall).props).toMatchObject({ expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [ properties: [
@ -245,14 +245,14 @@ describe('compiler: transform v-on', () => {
{ content: `$event` }, { content: `$event` },
`);`, `);`,
{ content: `_ctx.bar` }, { content: `_ctx.bar` },
`()` `()`,
] ],
}, },
`}` `}`,
] ],
} },
} },
] ],
}) })
}) })
@ -264,10 +264,10 @@ describe('compiler: transform v-on', () => {
key: { content: `onClick` }, key: { content: `onClick` },
value: { value: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `$event => foo($event)` content: `$event => foo($event)`,
} },
} },
] ],
}) })
}) })
@ -279,10 +279,10 @@ describe('compiler: transform v-on', () => {
key: { content: `onClick` }, key: { content: `onClick` },
value: { value: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `(e: any): any => foo(e)` content: `(e: any): any => foo(e)`,
} },
} },
] ],
}) })
}) })
@ -292,7 +292,7 @@ describe('compiler: transform v-on', () => {
$event => { $event => {
foo($event) foo($event)
} }
"/>` "/>`,
) )
expect((node.codegenNode as VNodeCall).props).toMatchObject({ expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [ properties: [
@ -304,10 +304,10 @@ describe('compiler: transform v-on', () => {
$event => { $event => {
foo($event) foo($event)
} }
` `,
} },
} },
] ],
}) })
}) })
@ -317,7 +317,7 @@ describe('compiler: transform v-on', () => {
function($event) { function($event) {
foo($event) foo($event)
} }
"/>` "/>`,
) )
expect((node.codegenNode as VNodeCall).props).toMatchObject({ expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [ properties: [
@ -329,10 +329,10 @@ describe('compiler: transform v-on', () => {
function($event) { function($event) {
foo($event) foo($event)
} }
` `,
} },
} },
] ],
}) })
}) })
@ -344,16 +344,16 @@ describe('compiler: transform v-on', () => {
key: { content: `onClick` }, key: { content: `onClick` },
value: { value: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `a['b' + c]` content: `a['b' + c]`,
} },
} },
] ],
}) })
}) })
test('complex member expression w/ prefixIdentifiers: true', () => { test('complex member expression w/ prefixIdentifiers: true', () => {
const { node } = parseWithVOn(`<div @click="a['b' + c]"/>`, { const { node } = parseWithVOn(`<div @click="a['b' + c]"/>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect((node.codegenNode as VNodeCall).props).toMatchObject({ expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [ properties: [
@ -365,17 +365,17 @@ describe('compiler: transform v-on', () => {
{ content: `_ctx.a` }, { content: `_ctx.a` },
`['b' + `, `['b' + `,
{ content: `_ctx.c` }, { content: `_ctx.c` },
`]` `]`,
] ],
} },
} },
] ],
}) })
}) })
test('function expression w/ prefixIdentifiers: true', () => { test('function expression w/ prefixIdentifiers: true', () => {
const { node } = parseWithVOn(`<div @click="e => foo(e)"/>`, { const { node } = parseWithVOn(`<div @click="e => foo(e)"/>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect((node.codegenNode as VNodeCall).props).toMatchObject({ expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [ properties: [
@ -389,11 +389,11 @@ describe('compiler: transform v-on', () => {
{ content: `_ctx.foo` }, { content: `_ctx.foo` },
`(`, `(`,
{ content: `e` }, { content: `e` },
`)` `)`,
] ],
} },
} },
] ],
}) })
}) })
@ -405,13 +405,13 @@ describe('compiler: transform v-on', () => {
loc: { loc: {
start: { start: {
line: 1, line: 1,
column: 6 column: 6,
}, },
end: { end: {
line: 1, line: 1,
column: 16 column: 16,
} },
} },
}) })
}) })
@ -427,13 +427,13 @@ describe('compiler: transform v-on', () => {
properties: [ properties: [
{ {
key: { key: {
content: `onFooBar` content: `onFooBar`,
}, },
value: { value: {
content: `onMount` content: `onMount`,
} },
} },
] ],
}) })
}) })
@ -445,39 +445,39 @@ describe('compiler: transform v-on', () => {
loc: { loc: {
start: { start: {
line: 1, line: 1,
column: 11 column: 11,
}, },
end: { end: {
line: 1, line: 1,
column: 24 column: 24,
} },
} },
}) })
}) })
test('vue: prefixed events', () => { test('vue: prefixed events', () => {
const { node } = parseWithVOn( const { node } = parseWithVOn(
`<div v-on:vue:mounted="onMount" @vue:before-update="onBeforeUpdate" />` `<div v-on:vue:mounted="onMount" @vue:before-update="onBeforeUpdate" />`,
) )
expect((node.codegenNode as VNodeCall).props).toMatchObject({ expect((node.codegenNode as VNodeCall).props).toMatchObject({
properties: [ properties: [
{ {
key: { key: {
content: `onVnodeMounted` content: `onVnodeMounted`,
}, },
value: { value: {
content: `onMount` content: `onMount`,
} },
}, },
{ {
key: { key: {
content: `onVnodeBeforeUpdate` content: `onVnodeBeforeUpdate`,
}, },
value: { value: {
content: `onBeforeUpdate` content: `onBeforeUpdate`,
} },
} },
] ],
}) })
}) })
@ -485,35 +485,35 @@ describe('compiler: transform v-on', () => {
test('empty handler', () => { test('empty handler', () => {
const { root, node } = parseWithVOn(`<div v-on:click.prevent />`, { const { root, node } = parseWithVOn(`<div v-on:click.prevent />`, {
prefixIdentifiers: true, prefixIdentifiers: true,
cacheHandlers: true cacheHandlers: true,
}) })
expect(root.cached).toBe(1) expect(root.cached).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags // should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined() expect(vnodeCall.patchFlag).toBeUndefined()
expect( expect(
(vnodeCall.props as ObjectExpression).properties[0].value (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({ ).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION, type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0, index: 0,
value: { value: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `() => {}` content: `() => {}`,
} },
}) })
}) })
test('member expression handler', () => { test('member expression handler', () => {
const { root, node } = parseWithVOn(`<div v-on:click="foo" />`, { const { root, node } = parseWithVOn(`<div v-on:click="foo" />`, {
prefixIdentifiers: true, prefixIdentifiers: true,
cacheHandlers: true cacheHandlers: true,
}) })
expect(root.cached).toBe(1) expect(root.cached).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags // should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined() expect(vnodeCall.patchFlag).toBeUndefined()
expect( expect(
(vnodeCall.props as ObjectExpression).properties[0].value (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({ ).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION, type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0, index: 0,
@ -522,23 +522,23 @@ describe('compiler: transform v-on', () => {
children: [ children: [
`(...args) => (`, `(...args) => (`,
{ content: `_ctx.foo && _ctx.foo(...args)` }, { content: `_ctx.foo && _ctx.foo(...args)` },
`)` `)`,
] ],
} },
}) })
}) })
test('compound member expression handler', () => { test('compound member expression handler', () => {
const { root, node } = parseWithVOn(`<div v-on:click="foo.bar" />`, { const { root, node } = parseWithVOn(`<div v-on:click="foo.bar" />`, {
prefixIdentifiers: true, prefixIdentifiers: true,
cacheHandlers: true cacheHandlers: true,
}) })
expect(root.cached).toBe(1) expect(root.cached).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags // should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined() expect(vnodeCall.patchFlag).toBeUndefined()
expect( expect(
(vnodeCall.props as ObjectExpression).properties[0].value (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({ ).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION, type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0, index: 0,
@ -555,12 +555,12 @@ describe('compiler: transform v-on', () => {
{ content: `_ctx.foo` }, { content: `_ctx.foo` },
`.`, `.`,
{ content: `bar` }, { content: `bar` },
`(...args)` `(...args)`,
] ],
},
`)`,
],
}, },
`)`
]
}
}) })
}) })
@ -568,7 +568,7 @@ describe('compiler: transform v-on', () => {
const { root } = parseWithVOn(`<comp v-on:click="foo" />`, { const { root } = parseWithVOn(`<comp v-on:click="foo" />`, {
prefixIdentifiers: true, prefixIdentifiers: true,
cacheHandlers: true, cacheHandlers: true,
isNativeTag: tag => tag === 'div' isNativeTag: tag => tag === 'div',
}) })
expect(root.cached).toBe(0) expect(root.cached).toBe(0)
}) })
@ -578,8 +578,8 @@ describe('compiler: transform v-on', () => {
`<div v-once><div v-on:click="foo"/></div>`, `<div v-once><div v-on:click="foo"/></div>`,
{ {
prefixIdentifiers: true, prefixIdentifiers: true,
cacheHandlers: true cacheHandlers: true,
} },
) )
expect(root.cached).not.toBe(2) expect(root.cached).not.toBe(2)
expect(root.cached).toBe(1) expect(root.cached).toBe(1)
@ -588,21 +588,21 @@ describe('compiler: transform v-on', () => {
test('inline function expression handler', () => { test('inline function expression handler', () => {
const { root, node } = parseWithVOn(`<div v-on:click="() => foo()" />`, { const { root, node } = parseWithVOn(`<div v-on:click="() => foo()" />`, {
prefixIdentifiers: true, prefixIdentifiers: true,
cacheHandlers: true cacheHandlers: true,
}) })
expect(root.cached).toBe(1) expect(root.cached).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags // should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined() expect(vnodeCall.patchFlag).toBeUndefined()
expect( expect(
(vnodeCall.props as ObjectExpression).properties[0].value (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({ ).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION, type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0, index: 0,
value: { value: {
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [`() => `, { content: `_ctx.foo` }, `()`] children: [`() => `, { content: `_ctx.foo` }, `()`],
} },
}) })
}) })
@ -611,22 +611,22 @@ describe('compiler: transform v-on', () => {
`<div v-on:click="async () => await foo()" />`, `<div v-on:click="async () => await foo()" />`,
{ {
prefixIdentifiers: true, prefixIdentifiers: true,
cacheHandlers: true cacheHandlers: true,
} },
) )
expect(root.cached).toBe(1) expect(root.cached).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags // should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined() expect(vnodeCall.patchFlag).toBeUndefined()
expect( expect(
(vnodeCall.props as ObjectExpression).properties[0].value (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({ ).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION, type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0, index: 0,
value: { value: {
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [`async () => await `, { content: `_ctx.foo` }, `()`] children: [`async () => await `, { content: `_ctx.foo` }, `()`],
} },
}) })
}) })
@ -635,15 +635,15 @@ describe('compiler: transform v-on', () => {
`<div v-on:click="async function () { await foo() } " />`, `<div v-on:click="async function () { await foo() } " />`,
{ {
prefixIdentifiers: true, prefixIdentifiers: true,
cacheHandlers: true cacheHandlers: true,
} },
) )
expect(root.cached).toBe(1) expect(root.cached).toBe(1)
const vnodeCall = node.codegenNode as VNodeCall const vnodeCall = node.codegenNode as VNodeCall
// should not treat cached handler as dynamicProp, so no flags // should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined() expect(vnodeCall.patchFlag).toBeUndefined()
expect( expect(
(vnodeCall.props as ObjectExpression).properties[0].value (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({ ).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION, type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0, index: 0,
@ -652,16 +652,16 @@ describe('compiler: transform v-on', () => {
children: [ children: [
`async function () { await `, `async function () { await `,
{ content: `_ctx.foo` }, { content: `_ctx.foo` },
`() } ` `() } `,
] ],
} },
}) })
}) })
test('inline statement handler', () => { test('inline statement handler', () => {
const { root, node } = parseWithVOn(`<div v-on:click="foo++" />`, { const { root, node } = parseWithVOn(`<div v-on:click="foo++" />`, {
prefixIdentifiers: true, prefixIdentifiers: true,
cacheHandlers: true cacheHandlers: true,
}) })
expect(root.cached).toBe(1) expect(root.cached).toBe(1)
expect(root.cached).toBe(1) expect(root.cached).toBe(1)
@ -669,7 +669,7 @@ describe('compiler: transform v-on', () => {
// should not treat cached handler as dynamicProp, so no flags // should not treat cached handler as dynamicProp, so no flags
expect(vnodeCall.patchFlag).toBeUndefined() expect(vnodeCall.patchFlag).toBeUndefined()
expect( expect(
(vnodeCall.props as ObjectExpression).properties[0].value (vnodeCall.props as ObjectExpression).properties[0].value,
).toMatchObject({ ).toMatchObject({
type: NodeTypes.JS_CACHE_EXPRESSION, type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0, index: 0,
@ -678,9 +678,9 @@ describe('compiler: transform v-on', () => {
children: [ children: [
`$event => (`, `$event => (`,
{ children: [{ content: `_ctx.foo` }, `++`] }, { children: [{ content: `_ctx.foo` }, `++`] },
`)` `)`,
] ],
} },
}) })
}) })
}) })

View File

@ -1,10 +1,10 @@
import { import {
baseParse as parse, type CompilerOptions,
transform,
NodeTypes, NodeTypes,
generate, generate,
CompilerOptions, getBaseTransformPreset,
getBaseTransformPreset baseParse as parse,
transform,
} from '../../src' } from '../../src'
import { RENDER_SLOT, SET_BLOCK_TRACKING } from '../../src/runtimeHelpers' import { RENDER_SLOT, SET_BLOCK_TRACKING } from '../../src/runtimeHelpers'
@ -14,7 +14,7 @@ function transformWithOnce(template: string, options: CompilerOptions = {}) {
transform(ast, { transform(ast, {
nodeTransforms, nodeTransforms,
directiveTransforms, directiveTransforms,
...options ...options,
}) })
return ast return ast
} }
@ -29,8 +29,8 @@ describe('compiler: v-once transform', () => {
index: 0, index: 0,
value: { value: {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"` tag: `"div"`,
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -44,8 +44,8 @@ describe('compiler: v-once transform', () => {
index: 0, index: 0,
value: { value: {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"` tag: `"div"`,
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -59,8 +59,8 @@ describe('compiler: v-once transform', () => {
index: 0, index: 0,
value: { value: {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `_component_Comp` tag: `_component_Comp`,
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -74,8 +74,8 @@ describe('compiler: v-once transform', () => {
index: 0, index: 0,
value: { value: {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: RENDER_SLOT callee: RENDER_SLOT,
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -90,7 +90,7 @@ describe('compiler: v-once transform', () => {
// cached nodes should be ignored by hoistStatic transform // cached nodes should be ignored by hoistStatic transform
test('with hoistStatic: true', () => { test('with hoistStatic: true', () => {
const root = transformWithOnce(`<div><div v-once /></div>`, { const root = transformWithOnce(`<div><div v-once /></div>`, {
hoistStatic: true hoistStatic: true,
}) })
expect(root.cached).toBe(1) expect(root.cached).toBe(1)
expect(root.helpers).toContain(SET_BLOCK_TRACKING) expect(root.helpers).toContain(SET_BLOCK_TRACKING)
@ -100,8 +100,8 @@ describe('compiler: v-once transform', () => {
index: 0, index: 0,
value: { value: {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"` tag: `"div"`,
} },
}) })
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -119,14 +119,14 @@ describe('compiler: v-once transform', () => {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION, type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
consequent: { consequent: {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"div"` tag: `"div"`,
}, },
alternate: { alternate: {
type: NodeTypes.VNODE_CALL, type: NodeTypes.VNODE_CALL,
tag: `"p"` tag: `"p"`,
} },
} },
} },
}) })
}) })
@ -138,8 +138,8 @@ describe('compiler: v-once transform', () => {
type: NodeTypes.FOR, type: NodeTypes.FOR,
// should cache the entire v-for expression, not just a single branch // should cache the entire v-for expression, not just a single branch
codegenNode: { codegenNode: {
type: NodeTypes.JS_CACHE_EXPRESSION type: NodeTypes.JS_CACHE_EXPRESSION,
} },
}) })
}) })
}) })

View File

@ -1,18 +1,18 @@
import { import {
CompilerOptions, type CompilerOptions,
type ComponentNode,
type ElementNode,
ErrorCodes,
type ForNode,
NodeTypes,
type ObjectExpression,
type RenderSlotCall,
type SimpleExpressionNode,
type SlotsExpression,
type VNodeCall,
generate,
baseParse as parse, baseParse as parse,
transform, transform,
generate,
ElementNode,
NodeTypes,
ErrorCodes,
ForNode,
ComponentNode,
VNodeCall,
SlotsExpression,
ObjectExpression,
SimpleExpressionNode,
RenderSlotCall
} from '../../src' } from '../../src'
import { transformElement } from '../../src/transforms/transformElement' import { transformElement } from '../../src/transforms/transformElement'
import { transformOn } from '../../src/transforms/vOn' import { transformOn } from '../../src/transforms/vOn'
@ -21,7 +21,7 @@ import { transformExpression } from '../../src/transforms/transformExpression'
import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet' import { transformSlotOutlet } from '../../src/transforms/transformSlotOutlet'
import { import {
trackSlotScopes, trackSlotScopes,
trackVForSlotScopes trackVForSlotScopes,
} from '../../src/transforms/vSlot' } from '../../src/transforms/vSlot'
import { CREATE_SLOTS, RENDER_LIST } from '../../src/runtimeHelpers' import { CREATE_SLOTS, RENDER_LIST } from '../../src/runtimeHelpers'
import { createObjectMatcher, genFlagText } from '../testUtils' import { createObjectMatcher, genFlagText } from '../testUtils'
@ -31,7 +31,7 @@ import { transformIf } from '../../src/transforms/vIf'
function parseWithSlots(template: string, options: CompilerOptions = {}) { function parseWithSlots(template: string, options: CompilerOptions = {}) {
const ast = parse(template, { const ast = parse(template, {
whitespace: options.whitespace whitespace: options.whitespace,
}) })
transform(ast, { transform(ast, {
nodeTransforms: [ nodeTransforms: [
@ -42,13 +42,13 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
: []), : []),
transformSlotOutlet, transformSlotOutlet,
transformElement, transformElement,
trackSlotScopes trackSlotScopes,
], ],
directiveTransforms: { directiveTransforms: {
on: transformOn, on: transformOn,
bind: transformBind bind: transformBind,
}, },
...options ...options,
}) })
return { return {
root: ast, root: ast,
@ -56,7 +56,7 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
ast.children[0].type === NodeTypes.ELEMENT ast.children[0].type === NodeTypes.ELEMENT
? ((ast.children[0].codegenNode as VNodeCall) ? ((ast.children[0].codegenNode as VNodeCall)
.children as SlotsExpression) .children as SlotsExpression)
: null : null,
} }
} }
@ -70,25 +70,25 @@ function createSlotMatcher(obj: Record<string, any>, isDynamic = false) {
key: { key: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: !/^\[/.test(key), isStatic: !/^\[/.test(key),
content: key.replace(/^\[|\]$/g, '') content: key.replace(/^\[|\]$/g, ''),
}, },
value: obj[key] value: obj[key],
} as any } as any
}) })
.concat({ .concat({
key: { content: `_` }, key: { content: `_` },
value: { value: {
content: isDynamic ? `2 /* DYNAMIC */` : `1 /* STABLE */`, content: isDynamic ? `2 /* DYNAMIC */` : `1 /* STABLE */`,
isStatic: false isStatic: false,
} },
}) }),
} }
} }
describe('compiler: transform component slots', () => { describe('compiler: transform component slots', () => {
test('implicit default slot', () => { test('implicit default slot', () => {
const { root, slots } = parseWithSlots(`<Comp><div/></Comp>`, { const { root, slots } = parseWithSlots(`<Comp><div/></Comp>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect(slots).toMatchObject( expect(slots).toMatchObject(
createSlotMatcher({ createSlotMatcher({
@ -98,11 +98,11 @@ describe('compiler: transform component slots', () => {
returns: [ returns: [
{ {
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,
tag: `div` tag: `div`,
} },
] ],
} },
}) }),
) )
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
}) })
@ -110,7 +110,7 @@ describe('compiler: transform component slots', () => {
test('on-component default slot', () => { test('on-component default slot', () => {
const { root, slots } = parseWithSlots( const { root, slots } = parseWithSlots(
`<Comp v-slot="{ foo }">{{ foo }}{{ bar }}</Comp>`, `<Comp v-slot="{ foo }">{{ foo }}{{ bar }}</Comp>`,
{ prefixIdentifiers: true } { prefixIdentifiers: true },
) )
expect(slots).toMatchObject( expect(slots).toMatchObject(
createSlotMatcher({ createSlotMatcher({
@ -118,24 +118,24 @@ describe('compiler: transform component slots', () => {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: { params: {
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ `, { content: `foo` }, ` }`] children: [`{ `, { content: `foo` }, ` }`],
}, },
returns: [ returns: [
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `foo` content: `foo`,
} },
}, },
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `_ctx.bar` content: `_ctx.bar`,
} },
} },
] ],
} },
}) }),
) )
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
}) })
@ -143,7 +143,7 @@ describe('compiler: transform component slots', () => {
test('on component named slot', () => { test('on component named slot', () => {
const { root, slots } = parseWithSlots( const { root, slots } = parseWithSlots(
`<Comp v-slot:named="{ foo }">{{ foo }}{{ bar }}</Comp>`, `<Comp v-slot:named="{ foo }">{{ foo }}{{ bar }}</Comp>`,
{ prefixIdentifiers: true } { prefixIdentifiers: true },
) )
expect(slots).toMatchObject( expect(slots).toMatchObject(
createSlotMatcher({ createSlotMatcher({
@ -151,24 +151,24 @@ describe('compiler: transform component slots', () => {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: { params: {
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ `, { content: `foo` }, ` }`] children: [`{ `, { content: `foo` }, ` }`],
}, },
returns: [ returns: [
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `foo` content: `foo`,
} },
}, },
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `_ctx.bar` content: `_ctx.bar`,
} },
} },
] ],
} },
}) }),
) )
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
}) })
@ -183,7 +183,7 @@ describe('compiler: transform component slots', () => {
{{ foo }}{{ bar }} {{ foo }}{{ bar }}
</template> </template>
</Comp>`, </Comp>`,
{ prefixIdentifiers: true } { prefixIdentifiers: true },
) )
expect(slots).toMatchObject( expect(slots).toMatchObject(
createSlotMatcher({ createSlotMatcher({
@ -191,45 +191,45 @@ describe('compiler: transform component slots', () => {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: { params: {
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ `, { content: `foo` }, ` }`] children: [`{ `, { content: `foo` }, ` }`],
}, },
returns: [ returns: [
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `foo` content: `foo`,
} },
}, },
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `_ctx.bar` content: `_ctx.bar`,
} },
} },
] ],
}, },
two: { two: {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: { params: {
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ `, { content: `bar` }, ` }`] children: [`{ `, { content: `bar` }, ` }`],
}, },
returns: [ returns: [
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `_ctx.foo` content: `_ctx.foo`,
} },
}, },
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `bar` content: `bar`,
} },
} },
] ],
} },
}) }),
) )
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
}) })
@ -237,7 +237,7 @@ describe('compiler: transform component slots', () => {
test('on component dynamically named slot', () => { test('on component dynamically named slot', () => {
const { root, slots } = parseWithSlots( const { root, slots } = parseWithSlots(
`<Comp v-slot:[named]="{ foo }">{{ foo }}{{ bar }}</Comp>`, `<Comp v-slot:[named]="{ foo }">{{ foo }}{{ bar }}</Comp>`,
{ prefixIdentifiers: true } { prefixIdentifiers: true },
) )
expect(slots).toMatchObject( expect(slots).toMatchObject(
createSlotMatcher( createSlotMatcher(
@ -246,26 +246,26 @@ describe('compiler: transform component slots', () => {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: { params: {
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ `, { content: `foo` }, ` }`] children: [`{ `, { content: `foo` }, ` }`],
}, },
returns: [ returns: [
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `foo` content: `foo`,
} },
}, },
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `_ctx.bar` content: `_ctx.bar`,
}
}
]
}
}, },
true },
) ],
},
},
true,
),
) )
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
}) })
@ -274,7 +274,7 @@ describe('compiler: transform component slots', () => {
const { root, slots } = parseWithSlots( const { root, slots } = parseWithSlots(
`<Comp> `<Comp>
<template #one>foo</template>bar<span/> <template #one>foo</template>bar<span/>
</Comp>` </Comp>`,
) )
expect(slots).toMatchObject( expect(slots).toMatchObject(
createSlotMatcher({ createSlotMatcher({
@ -284,9 +284,9 @@ describe('compiler: transform component slots', () => {
returns: [ returns: [
{ {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: `foo` content: `foo`,
} },
] ],
}, },
default: { default: {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
@ -294,15 +294,15 @@ describe('compiler: transform component slots', () => {
returns: [ returns: [
{ {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: `bar` content: `bar`,
}, },
{ {
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,
tag: `span` tag: `span`,
} },
] ],
} },
}) }),
) )
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -317,7 +317,7 @@ describe('compiler: transform component slots', () => {
{{ foo }}{{ bar }} {{ foo }}{{ bar }}
</template> </template>
</Comp>`, </Comp>`,
{ prefixIdentifiers: true } { prefixIdentifiers: true },
) )
expect(slots).toMatchObject( expect(slots).toMatchObject(
createSlotMatcher( createSlotMatcher(
@ -326,47 +326,47 @@ describe('compiler: transform component slots', () => {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: { params: {
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ `, { content: `foo` }, ` }`] children: [`{ `, { content: `foo` }, ` }`],
}, },
returns: [ returns: [
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `foo` content: `foo`,
} },
}, },
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `_ctx.bar` content: `_ctx.bar`,
} },
} },
] ],
}, },
'[_ctx.two]': { '[_ctx.two]': {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: { params: {
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ `, { content: `bar` }, ` }`] children: [`{ `, { content: `bar` }, ` }`],
}, },
returns: [ returns: [
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `_ctx.foo` content: `_ctx.foo`,
} },
}, },
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `bar` content: `bar`,
}
}
]
}
}, },
true },
) ],
},
},
true,
),
) )
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
}) })
@ -381,7 +381,7 @@ describe('compiler: transform component slots', () => {
{{ foo }}{{ bar }}{{ baz }} {{ foo }}{{ bar }}{{ baz }}
</template> </template>
</Comp>`, </Comp>`,
{ prefixIdentifiers: true } { prefixIdentifiers: true },
) )
expect(slots).toMatchObject( expect(slots).toMatchObject(
createSlotMatcher({ createSlotMatcher({
@ -389,7 +389,7 @@ describe('compiler: transform component slots', () => {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: { params: {
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ `, { content: `foo` }, ` }`] children: [`{ `, { content: `foo` }, ` }`],
}, },
returns: [ returns: [
{ {
@ -404,63 +404,63 @@ describe('compiler: transform component slots', () => {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: { params: {
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
children: [`{ `, { content: `bar` }, ` }`] children: [`{ `, { content: `bar` }, ` }`],
}, },
returns: [ returns: [
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `foo` content: `foo`,
} },
}, },
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `bar` content: `bar`,
} },
}, },
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `_ctx.baz` content: `_ctx.baz`,
}
}
]
}
}, },
true },
],
},
},
true,
), ),
// nested slot should be forced dynamic, since scope variables // nested slot should be forced dynamic, since scope variables
// are not tracked as dependencies of the slot. // are not tracked as dependencies of the slot.
patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS) patchFlag: genFlagText(PatchFlags.DYNAMIC_SLOTS),
} },
}, },
// test scope // test scope
{ {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: ` ` content: ` `,
}, },
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `foo` content: `foo`,
} },
}, },
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `_ctx.bar` content: `_ctx.bar`,
} },
}, },
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: {
content: `_ctx.baz` content: `_ctx.baz`,
} },
} },
] ],
} },
}) }),
) )
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
}) })
@ -469,13 +469,13 @@ describe('compiler: transform component slots', () => {
const { root } = parseWithSlots( const { root } = parseWithSlots(
`<div v-for="i in list"> `<div v-for="i in list">
<Comp v-slot="bar">foo</Comp> <Comp v-slot="bar">foo</Comp>
</div>` </div>`,
) )
const div = ((root.children[0] as ForNode).children[0] as ElementNode) const div = ((root.children[0] as ForNode).children[0] as ElementNode)
.codegenNode as any .codegenNode as any
const comp = div.children[0] const comp = div.children[0]
expect(comp.codegenNode.patchFlag).toBe( expect(comp.codegenNode.patchFlag).toBe(
genFlagText(PatchFlags.DYNAMIC_SLOTS) genFlagText(PatchFlags.DYNAMIC_SLOTS),
) )
}) })
@ -504,14 +504,14 @@ describe('compiler: transform component slots', () => {
`<div v-for="i in list"> `<div v-for="i in list">
<Comp v-slot="bar">foo</Comp> <Comp v-slot="bar">foo</Comp>
</div>`, </div>`,
false false,
) )
assertDynamicSlots( assertDynamicSlots(
`<div v-for="i in list"> `<div v-for="i in list">
<Comp v-slot="bar">{{ i }}</Comp> <Comp v-slot="bar">{{ i }}</Comp>
</div>`, </div>`,
true true,
) )
// reference the component's own slot variable should not force dynamic slots // reference the component's own slot variable should not force dynamic slots
@ -519,14 +519,14 @@ describe('compiler: transform component slots', () => {
`<Comp v-slot="foo"> `<Comp v-slot="foo">
<Comp v-slot="bar">{{ bar }}</Comp> <Comp v-slot="bar">{{ bar }}</Comp>
</Comp>`, </Comp>`,
false false,
) )
assertDynamicSlots( assertDynamicSlots(
`<Comp v-slot="foo"> `<Comp v-slot="foo">
<Comp v-slot="bar">{{ foo }}</Comp> <Comp v-slot="bar">{{ foo }}</Comp>
</Comp>`, </Comp>`,
true true,
) )
// #2564 // #2564
@ -534,14 +534,14 @@ describe('compiler: transform component slots', () => {
`<div v-for="i in list"> `<div v-for="i in list">
<Comp v-slot="bar"><button @click="fn(i)" /></Comp> <Comp v-slot="bar"><button @click="fn(i)" /></Comp>
</div>`, </div>`,
true true,
) )
assertDynamicSlots( assertDynamicSlots(
`<div v-for="i in list"> `<div v-for="i in list">
<Comp v-slot="bar"><button @click="fn()" /></Comp> <Comp v-slot="bar"><button @click="fn()" /></Comp>
</div>`, </div>`,
false false,
) )
}) })
@ -549,14 +549,14 @@ describe('compiler: transform component slots', () => {
const { root, slots } = parseWithSlots( const { root, slots } = parseWithSlots(
`<Comp> `<Comp>
<template #one v-if="ok">hello</template> <template #one v-if="ok">hello</template>
</Comp>` </Comp>`,
) )
expect(slots).toMatchObject({ expect(slots).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_SLOTS, callee: CREATE_SLOTS,
arguments: [ arguments: [
createObjectMatcher({ createObjectMatcher({
_: `[2 /* DYNAMIC */]` _: `[2 /* DYNAMIC */]`,
}), }),
{ {
type: NodeTypes.JS_ARRAY_EXPRESSION, type: NodeTypes.JS_ARRAY_EXPRESSION,
@ -568,21 +568,21 @@ describe('compiler: transform component slots', () => {
name: `one`, name: `one`,
fn: { fn: {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
returns: [{ type: NodeTypes.TEXT, content: `hello` }] returns: [{ type: NodeTypes.TEXT, content: `hello` }],
}, },
key: `0` key: `0`,
}), }),
alternate: { alternate: {
content: `undefined`, content: `undefined`,
isStatic: false isStatic: false,
} },
} },
] ],
} },
] ],
}) })
expect((root as any).children[0].codegenNode.patchFlag).toMatch( expect((root as any).children[0].codegenNode.patchFlag).toMatch(
PatchFlags.DYNAMIC_SLOTS + '' PatchFlags.DYNAMIC_SLOTS + '',
) )
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -592,14 +592,14 @@ describe('compiler: transform component slots', () => {
`<Comp> `<Comp>
<template #one="props" v-if="ok">{{ props }}</template> <template #one="props" v-if="ok">{{ props }}</template>
</Comp>`, </Comp>`,
{ prefixIdentifiers: true } { prefixIdentifiers: true },
) )
expect(slots).toMatchObject({ expect(slots).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_SLOTS, callee: CREATE_SLOTS,
arguments: [ arguments: [
createObjectMatcher({ createObjectMatcher({
_: `[2 /* DYNAMIC */]` _: `[2 /* DYNAMIC */]`,
}), }),
{ {
type: NodeTypes.JS_ARRAY_EXPRESSION, type: NodeTypes.JS_ARRAY_EXPRESSION,
@ -615,23 +615,23 @@ describe('compiler: transform component slots', () => {
returns: [ returns: [
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: `props` } content: { content: `props` },
}
]
}, },
key: `0` ],
},
key: `0`,
}), }),
alternate: { alternate: {
content: `undefined`, content: `undefined`,
isStatic: false isStatic: false,
} },
} },
] ],
} },
] ],
}) })
expect((root as any).children[0].codegenNode.patchFlag).toMatch( expect((root as any).children[0].codegenNode.patchFlag).toMatch(
PatchFlags.DYNAMIC_SLOTS + '' PatchFlags.DYNAMIC_SLOTS + '',
) )
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
}) })
@ -642,14 +642,14 @@ describe('compiler: transform component slots', () => {
<template #one v-if="ok">foo</template> <template #one v-if="ok">foo</template>
<template #two="props" v-else-if="orNot">bar</template> <template #two="props" v-else-if="orNot">bar</template>
<template #one v-else>baz</template> <template #one v-else>baz</template>
</Comp>` </Comp>`,
) )
expect(slots).toMatchObject({ expect(slots).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_SLOTS, callee: CREATE_SLOTS,
arguments: [ arguments: [
createObjectMatcher({ createObjectMatcher({
_: `[2 /* DYNAMIC */]` _: `[2 /* DYNAMIC */]`,
}), }),
{ {
type: NodeTypes.JS_ARRAY_EXPRESSION, type: NodeTypes.JS_ARRAY_EXPRESSION,
@ -662,9 +662,9 @@ describe('compiler: transform component slots', () => {
fn: { fn: {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: undefined, params: undefined,
returns: [{ type: NodeTypes.TEXT, content: `foo` }] returns: [{ type: NodeTypes.TEXT, content: `foo` }],
}, },
key: `0` key: `0`,
}), }),
alternate: { alternate: {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION, type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
@ -674,27 +674,27 @@ describe('compiler: transform component slots', () => {
fn: { fn: {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: { content: `props` }, params: { content: `props` },
returns: [{ type: NodeTypes.TEXT, content: `bar` }] returns: [{ type: NodeTypes.TEXT, content: `bar` }],
}, },
key: `1` key: `1`,
}), }),
alternate: createObjectMatcher({ alternate: createObjectMatcher({
name: `one`, name: `one`,
fn: { fn: {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
params: undefined, params: undefined,
returns: [{ type: NodeTypes.TEXT, content: `baz` }] returns: [{ type: NodeTypes.TEXT, content: `baz` }],
}, },
key: `2` key: `2`,
}) }),
} },
} },
] ],
} },
] ],
}) })
expect((root as any).children[0].codegenNode.patchFlag).toMatch( expect((root as any).children[0].codegenNode.patchFlag).toMatch(
PatchFlags.DYNAMIC_SLOTS + '' PatchFlags.DYNAMIC_SLOTS + '',
) )
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
@ -704,14 +704,14 @@ describe('compiler: transform component slots', () => {
`<Comp> `<Comp>
<template v-for="name in list" #[name]>{{ name }}</template> <template v-for="name in list" #[name]>{{ name }}</template>
</Comp>`, </Comp>`,
{ prefixIdentifiers: true } { prefixIdentifiers: true },
) )
expect(slots).toMatchObject({ expect(slots).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: CREATE_SLOTS, callee: CREATE_SLOTS,
arguments: [ arguments: [
createObjectMatcher({ createObjectMatcher({
_: `[2 /* DYNAMIC */]` _: `[2 /* DYNAMIC */]`,
}), }),
{ {
type: NodeTypes.JS_ARRAY_EXPRESSION, type: NodeTypes.JS_ARRAY_EXPRESSION,
@ -731,20 +731,20 @@ describe('compiler: transform component slots', () => {
returns: [ returns: [
{ {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: { content: `name`, isStatic: false } content: { content: `name`, isStatic: false },
} },
] ],
} },
}) }),
} },
] ],
} },
] ],
} },
] ],
}) })
expect((root as any).children[0].codegenNode.patchFlag).toMatch( expect((root as any).children[0].codegenNode.patchFlag).toMatch(
PatchFlags.DYNAMIC_SLOTS + '' PatchFlags.DYNAMIC_SLOTS + '',
) )
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
}) })
@ -755,13 +755,13 @@ describe('compiler: transform component slots', () => {
properties: [ properties: [
{ {
key: { content: `default` }, key: { content: `default` },
value: { type: NodeTypes.JS_FUNCTION_EXPRESSION } value: { type: NodeTypes.JS_FUNCTION_EXPRESSION },
}, },
{ {
key: { content: `_` }, key: { content: `_` },
value: { content: `3 /* FORWARDED */` } value: { content: `3 /* FORWARDED */` },
} },
] ],
} }
test('<slot> tag only', () => { test('<slot> tag only', () => {
const { slots } = parseWithSlots(`<Comp><slot/></Comp>`) const { slots } = parseWithSlots(`<Comp><slot/></Comp>`)
@ -780,7 +780,7 @@ describe('compiler: transform component slots', () => {
test('<slot> tag w/ template', () => { test('<slot> tag w/ template', () => {
const { slots } = parseWithSlots( const { slots } = parseWithSlots(
`<Comp><template #default><slot/></template></Comp>` `<Comp><template #default><slot/></template></Comp>`,
) )
expect(slots).toMatchObject(toMatch) expect(slots).toMatchObject(toMatch)
}) })
@ -793,7 +793,7 @@ describe('compiler: transform component slots', () => {
// # fix: #6900 // # fix: #6900
test('consistent behavior of @xxx:modelValue and @xxx:model-value', () => { test('consistent behavior of @xxx:modelValue and @xxx:model-value', () => {
const { root: rootUpper } = parseWithSlots( const { root: rootUpper } = parseWithSlots(
`<div><slot @foo:modelValue="handler" /></div>` `<div><slot @foo:modelValue="handler" /></div>`,
) )
const slotNodeUpper = (rootUpper.codegenNode! as VNodeCall) const slotNodeUpper = (rootUpper.codegenNode! as VNodeCall)
.children as ElementNode[] .children as ElementNode[]
@ -805,19 +805,19 @@ describe('compiler: transform component slots', () => {
{ {
key: { key: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: 'onFoo:modelValue' content: 'onFoo:modelValue',
}, },
value: { value: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `handler`, content: `handler`,
isStatic: false isStatic: false,
} },
} },
] ],
}) })
const { root } = parseWithSlots( const { root } = parseWithSlots(
`<div><slot @foo:model-Value="handler" /></div>` `<div><slot @foo:model-Value="handler" /></div>`,
) )
const slotNode = (root.codegenNode! as VNodeCall) const slotNode = (root.codegenNode! as VNodeCall)
.children as ElementNode[] .children as ElementNode[]
@ -828,15 +828,15 @@ describe('compiler: transform component slots', () => {
{ {
key: { key: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: 'onFoo:modelValue' content: 'onFoo:modelValue',
}, },
value: { value: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `handler`, content: `handler`,
isStatic: false isStatic: false,
} },
} },
] ],
}) })
}) })
}) })
@ -853,14 +853,14 @@ describe('compiler: transform component slots', () => {
start: { start: {
offset: index, offset: index,
line: 1, line: 1,
column: index + 1 column: index + 1,
}, },
end: { end: {
offset: index + 3, offset: index + 3,
line: 1, line: 1,
column: index + 4 column: index + 4,
} },
} },
}) })
}) })
@ -875,14 +875,14 @@ describe('compiler: transform component slots', () => {
start: { start: {
offset: index, offset: index,
line: 1, line: 1,
column: index + 1 column: index + 1,
}, },
end: { end: {
offset: index + 4, offset: index + 4,
line: 1, line: 1,
column: index + 5 column: index + 5,
} },
} },
}) })
}) })
@ -897,14 +897,14 @@ describe('compiler: transform component slots', () => {
start: { start: {
offset: index, offset: index,
line: 1, line: 1,
column: index + 1 column: index + 1,
}, },
end: { end: {
offset: index + 4, offset: index + 4,
line: 1, line: 1,
column: index + 5 column: index + 5,
} },
} },
}) })
}) })
@ -919,14 +919,14 @@ describe('compiler: transform component slots', () => {
start: { start: {
offset: index, offset: index,
line: 1, line: 1,
column: index + 1 column: index + 1,
}, },
end: { end: {
offset: index + 6, offset: index + 6,
line: 1, line: 1,
column: index + 7 column: index + 7,
} },
} },
}) })
}) })
}) })
@ -940,11 +940,11 @@ describe('compiler: transform component slots', () => {
</Comp> </Comp>
` `
const { root } = parseWithSlots(source, { const { root } = parseWithSlots(source, {
whitespace: 'preserve' whitespace: 'preserve',
}) })
expect( expect(
`Extraneous children found when component already has explicitly named default slot.` `Extraneous children found when component already has explicitly named default slot.`,
).not.toHaveBeenWarned() ).not.toHaveBeenWarned()
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
}) })
@ -957,11 +957,11 @@ describe('compiler: transform component slots', () => {
</Comp> </Comp>
` `
const { root } = parseWithSlots(source, { const { root } = parseWithSlots(source, {
whitespace: 'preserve' whitespace: 'preserve',
}) })
expect( expect(
`Extraneous children found when component already has explicitly named default slot.` `Extraneous children found when component already has explicitly named default slot.`,
).not.toHaveBeenWarned() ).not.toHaveBeenWarned()
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
}) })
@ -974,7 +974,7 @@ describe('compiler: transform component slots', () => {
</Comp> </Comp>
` `
const { root } = parseWithSlots(source, { const { root } = parseWithSlots(source, {
whitespace: 'preserve' whitespace: 'preserve',
}) })
// slots is vnodeCall's children as an ObjectExpression // slots is vnodeCall's children as an ObjectExpression
@ -984,7 +984,7 @@ describe('compiler: transform component slots', () => {
// should be: header, footer, _ (no default) // should be: header, footer, _ (no default)
expect(slots.length).toBe(3) expect(slots.length).toBe(3)
expect( expect(
slots.some(p => (p.key as SimpleExpressionNode).content === 'default') slots.some(p => (p.key as SimpleExpressionNode).content === 'default'),
).toBe(false) ).toBe(false)
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()

View File

@ -1,10 +1,10 @@
import { TransformContext } from '../src' import type { TransformContext } from '../src'
import { Position } from '../src/ast' import type { Position } from '../src/ast'
import { import {
advancePositionWithClone, advancePositionWithClone,
isMemberExpressionNode,
isMemberExpressionBrowser, isMemberExpressionBrowser,
toValidAssetId isMemberExpressionNode,
toValidAssetId,
} from '../src/utils' } from '../src/utils'
function p(line: number, column: number, offset: number): Position { function p(line: number, column: number, offset: number): Position {
@ -108,6 +108,6 @@ test('toValidAssetId', () => {
expect(toValidAssetId('div', 'filter')).toBe('_filter_div') expect(toValidAssetId('div', 'filter')).toBe('_filter_div')
expect(toValidAssetId('foo-bar', 'component')).toBe('_component_foo_bar') expect(toValidAssetId('foo-bar', 'component')).toBe('_component_foo_bar')
expect(toValidAssetId('test-测试-1', 'component')).toBe( expect(toValidAssetId('test-测试-1', 'component')).toBe(
'_component_test_2797935797_1' '_component_test_2797935797_1',
) )
}) })

View File

@ -1,20 +1,20 @@
import { isString } from '@vue/shared' import { isString } from '@vue/shared'
import { import {
RENDER_SLOT,
CREATE_SLOTS,
RENDER_LIST,
OPEN_BLOCK,
FRAGMENT,
WITH_DIRECTIVES,
WITH_MEMO,
CREATE_VNODE,
CREATE_ELEMENT_VNODE,
CREATE_BLOCK, CREATE_BLOCK,
CREATE_ELEMENT_BLOCK CREATE_ELEMENT_BLOCK,
CREATE_ELEMENT_VNODE,
type CREATE_SLOTS,
CREATE_VNODE,
type FRAGMENT,
OPEN_BLOCK,
type RENDER_LIST,
type RENDER_SLOT,
WITH_DIRECTIVES,
type WITH_MEMO,
} from './runtimeHelpers' } from './runtimeHelpers'
import { PropsExpression } from './transforms/transformElement' import type { PropsExpression } from './transforms/transformElement'
import { ImportItem, TransformContext } from './transform' import type { ImportItem, TransformContext } from './transform'
import { Node as BabelNode } from '@babel/types' import type { Node as BabelNode } from '@babel/types'
// Vue template is a platform-agnostic superset of HTML (syntax only). // Vue template is a platform-agnostic superset of HTML (syntax only).
// More namespaces can be declared by platform specific compilers. // More namespaces can be declared by platform specific compilers.
@ -23,7 +23,7 @@ export type Namespace = number
export enum Namespaces { export enum Namespaces {
HTML, HTML,
SVG, SVG,
MATH_ML MATH_ML,
} }
export enum NodeTypes { export enum NodeTypes {
@ -57,14 +57,14 @@ export enum NodeTypes {
JS_IF_STATEMENT, JS_IF_STATEMENT,
JS_ASSIGNMENT_EXPRESSION, JS_ASSIGNMENT_EXPRESSION,
JS_SEQUENCE_EXPRESSION, JS_SEQUENCE_EXPRESSION,
JS_RETURN_STATEMENT JS_RETURN_STATEMENT,
} }
export enum ElementTypes { export enum ElementTypes {
ELEMENT, ELEMENT,
COMPONENT, COMPONENT,
SLOT, SLOT,
TEMPLATE TEMPLATE,
} }
export interface Node { export interface Node {
@ -219,7 +219,7 @@ export enum ConstantTypes {
NOT_CONSTANT = 0, NOT_CONSTANT = 0,
CAN_SKIP_PATCH, CAN_SKIP_PATCH,
CAN_HOIST, CAN_HOIST,
CAN_STRINGIFY CAN_STRINGIFY,
} }
export interface SimpleExpressionNode extends Node { export interface SimpleExpressionNode extends Node {
@ -495,7 +495,7 @@ export interface RenderSlotCall extends CallExpression {
string, string,
string | ExpressionNode, string | ExpressionNode,
PropsExpression | '{}', PropsExpression | '{}',
TemplateChildNode[] TemplateChildNode[],
] ]
} }
@ -582,12 +582,12 @@ export interface ForIteratorExpression extends FunctionExpression {
export const locStub: SourceLocation = { export const locStub: SourceLocation = {
start: { line: 1, column: 1, offset: 0 }, start: { line: 1, column: 1, offset: 0 },
end: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 },
source: '' source: '',
} }
export function createRoot( export function createRoot(
children: TemplateChildNode[], children: TemplateChildNode[],
source = '' source = '',
): RootNode { ): RootNode {
return { return {
type: NodeTypes.ROOT, type: NodeTypes.ROOT,
@ -601,7 +601,7 @@ export function createRoot(
cached: 0, cached: 0,
temps: 0, temps: 0,
codegenNode: undefined, codegenNode: undefined,
loc: locStub loc: locStub,
} }
} }
@ -616,7 +616,7 @@ export function createVNodeCall(
isBlock: VNodeCall['isBlock'] = false, isBlock: VNodeCall['isBlock'] = false,
disableTracking: VNodeCall['disableTracking'] = false, disableTracking: VNodeCall['disableTracking'] = false,
isComponent: VNodeCall['isComponent'] = false, isComponent: VNodeCall['isComponent'] = false,
loc = locStub loc = locStub,
): VNodeCall { ): VNodeCall {
if (context) { if (context) {
if (isBlock) { if (isBlock) {
@ -641,41 +641,41 @@ export function createVNodeCall(
isBlock, isBlock,
disableTracking, disableTracking,
isComponent, isComponent,
loc loc,
} }
} }
export function createArrayExpression( export function createArrayExpression(
elements: ArrayExpression['elements'], elements: ArrayExpression['elements'],
loc: SourceLocation = locStub loc: SourceLocation = locStub,
): ArrayExpression { ): ArrayExpression {
return { return {
type: NodeTypes.JS_ARRAY_EXPRESSION, type: NodeTypes.JS_ARRAY_EXPRESSION,
loc, loc,
elements elements,
} }
} }
export function createObjectExpression( export function createObjectExpression(
properties: ObjectExpression['properties'], properties: ObjectExpression['properties'],
loc: SourceLocation = locStub loc: SourceLocation = locStub,
): ObjectExpression { ): ObjectExpression {
return { return {
type: NodeTypes.JS_OBJECT_EXPRESSION, type: NodeTypes.JS_OBJECT_EXPRESSION,
loc, loc,
properties properties,
} }
} }
export function createObjectProperty( export function createObjectProperty(
key: Property['key'] | string, key: Property['key'] | string,
value: Property['value'] value: Property['value'],
): Property { ): Property {
return { return {
type: NodeTypes.JS_PROPERTY, type: NodeTypes.JS_PROPERTY,
loc: locStub, loc: locStub,
key: isString(key) ? createSimpleExpression(key, true) : key, key: isString(key) ? createSimpleExpression(key, true) : key,
value value,
} }
} }
@ -683,38 +683,38 @@ export function createSimpleExpression(
content: SimpleExpressionNode['content'], content: SimpleExpressionNode['content'],
isStatic: SimpleExpressionNode['isStatic'] = false, isStatic: SimpleExpressionNode['isStatic'] = false,
loc: SourceLocation = locStub, loc: SourceLocation = locStub,
constType: ConstantTypes = ConstantTypes.NOT_CONSTANT constType: ConstantTypes = ConstantTypes.NOT_CONSTANT,
): SimpleExpressionNode { ): SimpleExpressionNode {
return { return {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
loc, loc,
content, content,
isStatic, isStatic,
constType: isStatic ? ConstantTypes.CAN_STRINGIFY : constType constType: isStatic ? ConstantTypes.CAN_STRINGIFY : constType,
} }
} }
export function createInterpolation( export function createInterpolation(
content: InterpolationNode['content'] | string, content: InterpolationNode['content'] | string,
loc: SourceLocation loc: SourceLocation,
): InterpolationNode { ): InterpolationNode {
return { return {
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
loc, loc,
content: isString(content) content: isString(content)
? createSimpleExpression(content, false, loc) ? createSimpleExpression(content, false, loc)
: content : content,
} }
} }
export function createCompoundExpression( export function createCompoundExpression(
children: CompoundExpressionNode['children'], children: CompoundExpressionNode['children'],
loc: SourceLocation = locStub loc: SourceLocation = locStub,
): CompoundExpressionNode { ): CompoundExpressionNode {
return { return {
type: NodeTypes.COMPOUND_EXPRESSION, type: NodeTypes.COMPOUND_EXPRESSION,
loc, loc,
children children,
} }
} }
@ -725,13 +725,13 @@ type InferCodegenNodeType<T> = T extends typeof RENDER_SLOT
export function createCallExpression<T extends CallExpression['callee']>( export function createCallExpression<T extends CallExpression['callee']>(
callee: T, callee: T,
args: CallExpression['arguments'] = [], args: CallExpression['arguments'] = [],
loc: SourceLocation = locStub loc: SourceLocation = locStub,
): InferCodegenNodeType<T> { ): InferCodegenNodeType<T> {
return { return {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
loc, loc,
callee, callee,
arguments: args arguments: args,
} as InferCodegenNodeType<T> } as InferCodegenNodeType<T>
} }
@ -740,7 +740,7 @@ export function createFunctionExpression(
returns: FunctionExpression['returns'] = undefined, returns: FunctionExpression['returns'] = undefined,
newline: boolean = false, newline: boolean = false,
isSlot: boolean = false, isSlot: boolean = false,
loc: SourceLocation = locStub loc: SourceLocation = locStub,
): FunctionExpression { ): FunctionExpression {
return { return {
type: NodeTypes.JS_FUNCTION_EXPRESSION, type: NodeTypes.JS_FUNCTION_EXPRESSION,
@ -748,7 +748,7 @@ export function createFunctionExpression(
returns, returns,
newline, newline,
isSlot, isSlot,
loc loc,
} }
} }
@ -756,7 +756,7 @@ export function createConditionalExpression(
test: ConditionalExpression['test'], test: ConditionalExpression['test'],
consequent: ConditionalExpression['consequent'], consequent: ConditionalExpression['consequent'],
alternate: ConditionalExpression['alternate'], alternate: ConditionalExpression['alternate'],
newline = true newline = true,
): ConditionalExpression { ): ConditionalExpression {
return { return {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION, type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
@ -764,87 +764,87 @@ export function createConditionalExpression(
consequent, consequent,
alternate, alternate,
newline, newline,
loc: locStub loc: locStub,
} }
} }
export function createCacheExpression( export function createCacheExpression(
index: number, index: number,
value: JSChildNode, value: JSChildNode,
isVNode: boolean = false isVNode: boolean = false,
): CacheExpression { ): CacheExpression {
return { return {
type: NodeTypes.JS_CACHE_EXPRESSION, type: NodeTypes.JS_CACHE_EXPRESSION,
index, index,
value, value,
isVNode, isVNode,
loc: locStub loc: locStub,
} }
} }
export function createBlockStatement( export function createBlockStatement(
body: BlockStatement['body'] body: BlockStatement['body'],
): BlockStatement { ): BlockStatement {
return { return {
type: NodeTypes.JS_BLOCK_STATEMENT, type: NodeTypes.JS_BLOCK_STATEMENT,
body, body,
loc: locStub loc: locStub,
} }
} }
export function createTemplateLiteral( export function createTemplateLiteral(
elements: TemplateLiteral['elements'] elements: TemplateLiteral['elements'],
): TemplateLiteral { ): TemplateLiteral {
return { return {
type: NodeTypes.JS_TEMPLATE_LITERAL, type: NodeTypes.JS_TEMPLATE_LITERAL,
elements, elements,
loc: locStub loc: locStub,
} }
} }
export function createIfStatement( export function createIfStatement(
test: IfStatement['test'], test: IfStatement['test'],
consequent: IfStatement['consequent'], consequent: IfStatement['consequent'],
alternate?: IfStatement['alternate'] alternate?: IfStatement['alternate'],
): IfStatement { ): IfStatement {
return { return {
type: NodeTypes.JS_IF_STATEMENT, type: NodeTypes.JS_IF_STATEMENT,
test, test,
consequent, consequent,
alternate, alternate,
loc: locStub loc: locStub,
} }
} }
export function createAssignmentExpression( export function createAssignmentExpression(
left: AssignmentExpression['left'], left: AssignmentExpression['left'],
right: AssignmentExpression['right'] right: AssignmentExpression['right'],
): AssignmentExpression { ): AssignmentExpression {
return { return {
type: NodeTypes.JS_ASSIGNMENT_EXPRESSION, type: NodeTypes.JS_ASSIGNMENT_EXPRESSION,
left, left,
right, right,
loc: locStub loc: locStub,
} }
} }
export function createSequenceExpression( export function createSequenceExpression(
expressions: SequenceExpression['expressions'] expressions: SequenceExpression['expressions'],
): SequenceExpression { ): SequenceExpression {
return { return {
type: NodeTypes.JS_SEQUENCE_EXPRESSION, type: NodeTypes.JS_SEQUENCE_EXPRESSION,
expressions, expressions,
loc: locStub loc: locStub,
} }
} }
export function createReturnStatement( export function createReturnStatement(
returns: ReturnStatement['returns'] returns: ReturnStatement['returns'],
): ReturnStatement { ): ReturnStatement {
return { return {
type: NodeTypes.JS_RETURN_STATEMENT, type: NodeTypes.JS_RETURN_STATEMENT,
returns, returns,
loc: locStub loc: locStub,
} }
} }
@ -858,7 +858,7 @@ export function getVNodeBlockHelper(ssr: boolean, isComponent: boolean) {
export function convertToBlock( export function convertToBlock(
node: VNodeCall, node: VNodeCall,
{ helper, removeHelper, inSSR }: TransformContext { helper, removeHelper, inSSR }: TransformContext,
) { ) {
if (!node.isBlock) { if (!node.isBlock) {
node.isBlock = true node.isBlock = true

View File

@ -1,12 +1,12 @@
// should only use types from @babel/types // should only use types from @babel/types
// do not import runtime methods // do not import runtime methods
import type { import type {
BlockStatement,
Function,
Identifier, Identifier,
Node, Node,
Function,
ObjectProperty, ObjectProperty,
BlockStatement, Program,
Program
} from '@babel/types' } from '@babel/types'
import { walk } from 'estree-walker' import { walk } from 'estree-walker'
@ -17,11 +17,11 @@ export function walkIdentifiers(
parent: Node, parent: Node,
parentStack: Node[], parentStack: Node[],
isReference: boolean, isReference: boolean,
isLocal: boolean isLocal: boolean,
) => void, ) => void,
includeAll = false, includeAll = false,
parentStack: Node[] = [], parentStack: Node[] = [],
knownIds: Record<string, number> = Object.create(null) knownIds: Record<string, number> = Object.create(null),
) { ) {
if (__BROWSER__) { if (__BROWSER__) {
return return
@ -61,7 +61,7 @@ export function walkIdentifiers(
// 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
walkFunctionParams(node, id => walkFunctionParams(node, id =>
markScopeIdentifier(node, id, knownIds) markScopeIdentifier(node, id, knownIds),
) )
} }
} else if (node.type === 'BlockStatement') { } else if (node.type === 'BlockStatement') {
@ -70,7 +70,7 @@ export function walkIdentifiers(
} else { } else {
// #3445 record block-level local variables // #3445 record block-level local variables
walkBlockDeclarations(node, id => walkBlockDeclarations(node, id =>
markScopeIdentifier(node, id, knownIds) markScopeIdentifier(node, id, knownIds),
) )
} }
} }
@ -85,14 +85,14 @@ export function walkIdentifiers(
} }
} }
} }
} },
}) })
} }
export function isReferencedIdentifier( export function isReferencedIdentifier(
id: Identifier, id: Identifier,
parent: Node | null, parent: Node | null,
parentStack: Node[] parentStack: Node[],
) { ) {
if (__BROWSER__) { if (__BROWSER__) {
return false return false
@ -127,7 +127,7 @@ export function isReferencedIdentifier(
export function isInDestructureAssignment( export function isInDestructureAssignment(
parent: Node, parent: Node,
parentStack: Node[] parentStack: Node[],
): boolean { ): boolean {
if ( if (
parent && parent &&
@ -148,7 +148,7 @@ export function isInDestructureAssignment(
export function walkFunctionParams( export function walkFunctionParams(
node: Function, node: Function,
onIdent: (id: Identifier) => void onIdent: (id: Identifier) => void,
) { ) {
for (const p of node.params) { for (const p of node.params) {
for (const id of extractIdentifiers(p)) { for (const id of extractIdentifiers(p)) {
@ -159,7 +159,7 @@ export function walkFunctionParams(
export function walkBlockDeclarations( export function walkBlockDeclarations(
block: BlockStatement | Program, block: BlockStatement | Program,
onIdent: (node: Identifier) => void onIdent: (node: Identifier) => void,
) { ) {
for (const stmt of block.body) { for (const stmt of block.body) {
if (stmt.type === 'VariableDeclaration') { if (stmt.type === 'VariableDeclaration') {
@ -194,7 +194,7 @@ export function walkBlockDeclarations(
export function extractIdentifiers( export function extractIdentifiers(
param: Node, param: Node,
nodes: Identifier[] = [] nodes: Identifier[] = [],
): Identifier[] { ): Identifier[] {
switch (param.type) { switch (param.type) {
case 'Identifier': case 'Identifier':
@ -248,7 +248,7 @@ function markKnownIds(name: string, knownIds: Record<string, number>) {
function markScopeIdentifier( function markScopeIdentifier(
node: Node & { scopeIds?: Set<string> }, node: Node & { scopeIds?: Set<string> },
child: Identifier, child: Identifier,
knownIds: Record<string, number> knownIds: Record<string, number>,
) { ) {
const { name } = child const { name } = child
if (node.scopeIds && node.scopeIds.has(name)) { if (node.scopeIds && node.scopeIds.has(name)) {
@ -453,7 +453,7 @@ export const TS_NODE_TYPES = [
'TSTypeAssertion', // (<number>foo) 'TSTypeAssertion', // (<number>foo)
'TSNonNullExpression', // foo! 'TSNonNullExpression', // foo!
'TSInstantiationExpression', // foo<string> 'TSInstantiationExpression', // foo<string>
'TSSatisfiesExpression' // foo satisfies T 'TSSatisfiesExpression', // foo satisfies T
] ]
export function unwrapTSNode(node: Node): Node { export function unwrapTSNode(node: Node): Node {

View File

@ -1,60 +1,60 @@
import { CodegenOptions } from './options' import type { CodegenOptions } from './options'
import { import {
RootNode, type ArrayExpression,
TemplateChildNode, type AssignmentExpression,
TextNode, type CacheExpression,
CommentNode, type CallExpression,
ExpressionNode, type CommentNode,
type CompoundExpressionNode,
type ConditionalExpression,
type ExpressionNode,
type FunctionExpression,
type IfStatement,
type InterpolationNode,
type JSChildNode,
NodeTypes, NodeTypes,
JSChildNode, type ObjectExpression,
CallExpression, type Position,
ArrayExpression, type ReturnStatement,
ObjectExpression, type RootNode,
Position, type SSRCodegenNode,
InterpolationNode, type SequenceExpression,
CompoundExpressionNode, type SimpleExpressionNode,
SimpleExpressionNode, type TemplateChildNode,
FunctionExpression, type TemplateLiteral,
ConditionalExpression, type TextNode,
CacheExpression, type VNodeCall,
locStub,
SSRCodegenNode,
TemplateLiteral,
IfStatement,
AssignmentExpression,
ReturnStatement,
VNodeCall,
SequenceExpression,
getVNodeBlockHelper, getVNodeBlockHelper,
getVNodeHelper getVNodeHelper,
locStub,
} from './ast' } from './ast'
import { SourceMapGenerator, RawSourceMap } from 'source-map-js' import { type RawSourceMap, SourceMapGenerator } from 'source-map-js'
import { import {
advancePositionWithMutation, advancePositionWithMutation,
assert, assert,
isSimpleIdentifier, isSimpleIdentifier,
toValidAssetId toValidAssetId,
} from './utils' } from './utils'
import { isString, isArray, isSymbol } from '@vue/shared' import { isArray, isString, isSymbol } from '@vue/shared'
import { import {
helperNameMap, CREATE_COMMENT,
TO_DISPLAY_STRING, CREATE_ELEMENT_VNODE,
CREATE_STATIC,
CREATE_TEXT,
CREATE_VNODE, CREATE_VNODE,
OPEN_BLOCK,
POP_SCOPE_ID,
PUSH_SCOPE_ID,
RESOLVE_COMPONENT, RESOLVE_COMPONENT,
RESOLVE_DIRECTIVE, RESOLVE_DIRECTIVE,
RESOLVE_FILTER,
SET_BLOCK_TRACKING, SET_BLOCK_TRACKING,
CREATE_COMMENT, TO_DISPLAY_STRING,
CREATE_TEXT,
PUSH_SCOPE_ID,
POP_SCOPE_ID,
WITH_DIRECTIVES,
CREATE_ELEMENT_VNODE,
OPEN_BLOCK,
CREATE_STATIC,
WITH_CTX, WITH_CTX,
RESOLVE_FILTER WITH_DIRECTIVES,
helperNameMap,
} from './runtimeHelpers' } from './runtimeHelpers'
import { ImportItem } from './transform' import type { ImportItem } from './transform'
const PURE_ANNOTATION = `/*#__PURE__*/` const PURE_ANNOTATION = `/*#__PURE__*/`
@ -73,7 +73,7 @@ enum NewlineType {
Start = 0, Start = 0,
End = -1, End = -1,
None = -2, None = -2,
Unknown = -3 Unknown = -3,
} }
export interface CodegenContext export interface CodegenContext
@ -107,8 +107,8 @@ function createCodegenContext(
ssrRuntimeModuleName = 'vue/server-renderer', ssrRuntimeModuleName = 'vue/server-renderer',
ssr = false, ssr = false,
isTS = false, isTS = false,
inSSR = false inSSR = false,
}: CodegenOptions }: CodegenOptions,
): CodegenContext { ): CodegenContext {
const context: CodegenContext = { const context: CodegenContext = {
mode, mode,
@ -158,7 +158,7 @@ function createCodegenContext(
if (__TEST__ && code.includes('\n')) { if (__TEST__ && code.includes('\n')) {
throw new Error( throw new Error(
`CodegenContext.push() called newlineIndex: none, but contains` + `CodegenContext.push() called newlineIndex: none, but contains` +
`newlines: ${code.replace(/\n/g, '\\n')}` `newlines: ${code.replace(/\n/g, '\\n')}`,
) )
} }
context.column += code.length context.column += code.length
@ -175,7 +175,7 @@ function createCodegenContext(
) { ) {
throw new Error( throw new Error(
`CodegenContext.push() called with newlineIndex: ${newlineIndex} ` + `CodegenContext.push() called with newlineIndex: ${newlineIndex} ` +
`but does not conform: ${code.replace(/\n/g, '\\n')}` `but does not conform: ${code.replace(/\n/g, '\\n')}`,
) )
} }
context.line++ context.line++
@ -199,7 +199,7 @@ function createCodegenContext(
}, },
newline() { newline() {
newline(context.indentLevel) newline(context.indentLevel)
} },
} }
function newline(n: number) { function newline(n: number) {
@ -218,8 +218,8 @@ function createCodegenContext(
generatedLine: context.line, generatedLine: context.line,
generatedColumn: context.column - 1, generatedColumn: context.column - 1,
source: filename, source: filename,
// @ts-ignore it is possible to be null // @ts-expect-error it is possible to be null
name name,
}) })
} }
@ -237,7 +237,7 @@ export function generate(
ast: RootNode, ast: RootNode,
options: CodegenOptions & { options: CodegenOptions & {
onContextCreated?: (context: CodegenContext) => void onContextCreated?: (context: CodegenContext) => void
} = {} } = {},
): CodegenResult { ): CodegenResult {
const context = createCodegenContext(ast, options) const context = createCodegenContext(ast, options)
if (options.onContextCreated) options.onContextCreated(context) if (options.onContextCreated) options.onContextCreated(context)
@ -249,7 +249,7 @@ export function generate(
deindent, deindent,
newline, newline,
scopeId, scopeId,
ssr ssr,
} = context } = context
const helpers = Array.from(ast.helpers) const helpers = Array.from(ast.helpers)
@ -296,7 +296,7 @@ export function generate(
if (hasHelpers) { if (hasHelpers) {
push( push(
`const { ${helpers.map(aliasHelper).join(', ')} } = _Vue\n`, `const { ${helpers.map(aliasHelper).join(', ')} } = _Vue\n`,
NewlineType.End NewlineType.End,
) )
newline() newline()
} }
@ -354,7 +354,7 @@ export function generate(
ast, ast,
code: context.code, code: context.code,
preamble: isSetupInlined ? preambleContext.code : ``, preamble: isSetupInlined ? preambleContext.code : ``,
map: context.map ? context.map.toJSON() : undefined map: context.map ? context.map.toJSON() : undefined,
} }
} }
@ -366,7 +366,7 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
newline, newline,
runtimeModuleName, runtimeModuleName,
runtimeGlobalName, runtimeGlobalName,
ssrRuntimeModuleName ssrRuntimeModuleName,
} = context } = context
const VueBinding = const VueBinding =
!__BROWSER__ && ssr !__BROWSER__ && ssr
@ -381,7 +381,7 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
if (!__BROWSER__ && prefixIdentifiers) { if (!__BROWSER__ && prefixIdentifiers) {
push( push(
`const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`, `const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`,
NewlineType.End NewlineType.End,
) )
} else { } else {
// "with" mode. // "with" mode.
@ -396,7 +396,7 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
CREATE_ELEMENT_VNODE, CREATE_ELEMENT_VNODE,
CREATE_COMMENT, CREATE_COMMENT,
CREATE_TEXT, CREATE_TEXT,
CREATE_STATIC CREATE_STATIC,
] ]
.filter(helper => helpers.includes(helper)) .filter(helper => helpers.includes(helper))
.map(aliasHelper) .map(aliasHelper)
@ -412,7 +412,7 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
`const { ${ast.ssrHelpers `const { ${ast.ssrHelpers
.map(aliasHelper) .map(aliasHelper)
.join(', ')} } = require("${ssrRuntimeModuleName}")\n`, .join(', ')} } = require("${ssrRuntimeModuleName}")\n`,
NewlineType.End NewlineType.End,
) )
} }
genHoists(ast.hoists, context) genHoists(ast.hoists, context)
@ -424,14 +424,14 @@ function genModulePreamble(
ast: RootNode, ast: RootNode,
context: CodegenContext, context: CodegenContext,
genScopeId: boolean, genScopeId: boolean,
inline?: boolean inline?: boolean,
) { ) {
const { const {
push, push,
newline, newline,
optimizeImports, optimizeImports,
runtimeModuleName, runtimeModuleName,
ssrRuntimeModuleName ssrRuntimeModuleName,
} = context } = context
if (genScopeId && ast.hoists.length) { if (genScopeId && ast.hoists.length) {
@ -452,20 +452,20 @@ function genModulePreamble(
`import { ${helpers `import { ${helpers
.map(s => helperNameMap[s]) .map(s => helperNameMap[s])
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`, .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`,
NewlineType.End NewlineType.End,
) )
push( push(
`\n// Binding optimization for webpack code-split\nconst ${helpers `\n// Binding optimization for webpack code-split\nconst ${helpers
.map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`) .map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`)
.join(', ')}\n`, .join(', ')}\n`,
NewlineType.End NewlineType.End,
) )
} else { } else {
push( push(
`import { ${helpers `import { ${helpers
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`) .map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`, .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`,
NewlineType.End NewlineType.End,
) )
} }
} }
@ -475,7 +475,7 @@ function genModulePreamble(
`import { ${ast.ssrHelpers `import { ${ast.ssrHelpers
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`) .map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
.join(', ')} } from "${ssrRuntimeModuleName}"\n`, .join(', ')} } from "${ssrRuntimeModuleName}"\n`,
NewlineType.End NewlineType.End,
) )
} }
@ -495,14 +495,14 @@ function genModulePreamble(
function genAssets( function genAssets(
assets: string[], assets: string[],
type: 'component' | 'directive' | 'filter', type: 'component' | 'directive' | 'filter',
{ helper, push, newline, isTS }: CodegenContext { helper, push, newline, isTS }: CodegenContext,
) { ) {
const resolver = helper( const resolver = helper(
__COMPAT__ && type === 'filter' __COMPAT__ && type === 'filter'
? RESOLVE_FILTER ? RESOLVE_FILTER
: type === 'component' : type === 'component'
? RESOLVE_COMPONENT ? RESOLVE_COMPONENT
: RESOLVE_DIRECTIVE : RESOLVE_DIRECTIVE,
) )
for (let i = 0; i < assets.length; i++) { for (let i = 0; i < assets.length; i++) {
let id = assets[i] let id = assets[i]
@ -514,7 +514,7 @@ function genAssets(
push( push(
`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)}${ `const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)}${
maybeSelfReference ? `, true` : `` maybeSelfReference ? `, true` : ``
})${isTS ? `!` : ``}` })${isTS ? `!` : ``}`,
) )
if (i < assets.length - 1) { if (i < assets.length - 1) {
newline() newline()
@ -535,8 +535,8 @@ function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
if (genScopeId) { if (genScopeId) {
push( push(
`const _withScopeId = n => (${helper( `const _withScopeId = n => (${helper(
PUSH_SCOPE_ID PUSH_SCOPE_ID,
)}("${scopeId}"),n=n(),${helper(POP_SCOPE_ID)}(),n)` )}("${scopeId}"),n=n(),${helper(POP_SCOPE_ID)}(),n)`,
) )
newline() newline()
} }
@ -548,7 +548,7 @@ function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
push( push(
`const _hoisted_${i + 1} = ${ `const _hoisted_${i + 1} = ${
needScopeIdWrapper ? `${PURE_ANNOTATION} _withScopeId(() => ` : `` needScopeIdWrapper ? `${PURE_ANNOTATION} _withScopeId(() => ` : ``
}` }`,
) )
genNode(exp, context) genNode(exp, context)
if (needScopeIdWrapper) { if (needScopeIdWrapper) {
@ -585,7 +585,7 @@ function isText(n: string | CodegenNode) {
function genNodeListAsArray( function genNodeListAsArray(
nodes: (string | CodegenNode | TemplateChildNode[])[], nodes: (string | CodegenNode | TemplateChildNode[])[],
context: CodegenContext context: CodegenContext,
) { ) {
const multilines = const multilines =
nodes.length > 3 || nodes.length > 3 ||
@ -601,7 +601,7 @@ function genNodeList(
nodes: (string | symbol | CodegenNode | TemplateChildNode[])[], nodes: (string | symbol | CodegenNode | TemplateChildNode[])[],
context: CodegenContext, context: CodegenContext,
multilines: boolean = false, multilines: boolean = false,
comma: boolean = true comma: boolean = true,
) { ) {
const { push, newline } = context const { push, newline } = context
for (let i = 0; i < nodes.length; i++) { for (let i = 0; i < nodes.length; i++) {
@ -641,7 +641,7 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
assert( assert(
node.codegenNode != null, node.codegenNode != null,
`Codegen node is missing for element/if/for node. ` + `Codegen node is missing for element/if/for node. ` +
`Apply appropriate transforms first.` `Apply appropriate transforms first.`,
) )
genNode(node.codegenNode!, context) genNode(node.codegenNode!, context)
break break
@ -722,7 +722,7 @@ function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
function genText( function genText(
node: TextNode | SimpleExpressionNode, node: TextNode | SimpleExpressionNode,
context: CodegenContext context: CodegenContext,
) { ) {
context.push(JSON.stringify(node.content), NewlineType.Unknown, node) context.push(JSON.stringify(node.content), NewlineType.Unknown, node)
} }
@ -732,7 +732,7 @@ function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
context.push( context.push(
isStatic ? JSON.stringify(content) : content, isStatic ? JSON.stringify(content) : content,
NewlineType.Unknown, NewlineType.Unknown,
node node,
) )
} }
@ -746,7 +746,7 @@ function genInterpolation(node: InterpolationNode, context: CodegenContext) {
function genCompoundExpression( function genCompoundExpression(
node: CompoundExpressionNode, node: CompoundExpressionNode,
context: CodegenContext context: CodegenContext,
) { ) {
for (let i = 0; i < node.children!.length; i++) { for (let i = 0; i < node.children!.length; i++) {
const child = node.children![i] const child = node.children![i]
@ -760,7 +760,7 @@ function genCompoundExpression(
function genExpressionAsPropertyKey( function genExpressionAsPropertyKey(
node: ExpressionNode, node: ExpressionNode,
context: CodegenContext context: CodegenContext,
) { ) {
const { push } = context const { push } = context
if (node.type === NodeTypes.COMPOUND_EXPRESSION) { if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
@ -786,7 +786,7 @@ function genComment(node: CommentNode, context: CodegenContext) {
push( push(
`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, `${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`,
NewlineType.Unknown, NewlineType.Unknown,
node node,
) )
} }
@ -801,7 +801,7 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) {
directives, directives,
isBlock, isBlock,
disableTracking, disableTracking,
isComponent isComponent,
} = node } = node
if (directives) { if (directives) {
push(helper(WITH_DIRECTIVES) + `(`) push(helper(WITH_DIRECTIVES) + `(`)
@ -818,7 +818,7 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) {
push(helper(callHelper) + `(`, NewlineType.None, node) push(helper(callHelper) + `(`, NewlineType.None, node)
genNodeList( genNodeList(
genNullableArgs([tag, props, children, patchFlag, dynamicProps]), genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
context context,
) )
push(`)`) push(`)`)
if (isBlock) { if (isBlock) {
@ -887,7 +887,7 @@ function genArrayExpression(node: ArrayExpression, context: CodegenContext) {
function genFunctionExpression( function genFunctionExpression(
node: FunctionExpression, node: FunctionExpression,
context: CodegenContext context: CodegenContext,
) { ) {
const { push, indent, deindent } = context const { push, indent, deindent } = context
const { params, returns, body, newline, isSlot } = node const { params, returns, body, newline, isSlot } = node
@ -932,7 +932,7 @@ function genFunctionExpression(
function genConditionalExpression( function genConditionalExpression(
node: ConditionalExpression, node: ConditionalExpression,
context: CodegenContext context: CodegenContext,
) { ) {
const { test, consequent, alternate, newline: needNewline } = node const { test, consequent, alternate, newline: needNewline } = node
const { push, indent, deindent, newline } = context const { push, indent, deindent, newline } = context
@ -1033,7 +1033,7 @@ function genIfStatement(node: IfStatement, context: CodegenContext) {
function genAssignmentExpression( function genAssignmentExpression(
node: AssignmentExpression, node: AssignmentExpression,
context: CodegenContext context: CodegenContext,
) { ) {
genNode(node.left, context) genNode(node.left, context)
context.push(` = `) context.push(` = `)
@ -1042,7 +1042,7 @@ function genAssignmentExpression(
function genSequenceExpression( function genSequenceExpression(
node: SequenceExpression, node: SequenceExpression,
context: CodegenContext context: CodegenContext,
) { ) {
context.push(`(`) context.push(`(`)
genNodeList(node.expressions, context) genNodeList(node.expressions, context)
@ -1051,7 +1051,7 @@ function genSequenceExpression(
function genReturnStatement( function genReturnStatement(
{ returns }: ReturnStatement, { returns }: ReturnStatement,
context: CodegenContext context: CodegenContext,
) { ) {
context.push(`return `) context.push(`return `)
if (isArray(returns)) { if (isArray(returns)) {

View File

@ -1,7 +1,7 @@
import { SourceLocation } from '../ast' import type { SourceLocation } from '../ast'
import { CompilerError } from '../errors' import type { CompilerError } from '../errors'
import { MergedParserOptions } from '../parser' import type { MergedParserOptions } from '../parser'
import { TransformContext } from '../transform' import type { TransformContext } from '../transform'
export type CompilerCompatConfig = Partial< export type CompilerCompatConfig = Partial<
Record<CompilerDeprecationTypes, boolean | 'suppress-warning'> Record<CompilerDeprecationTypes, boolean | 'suppress-warning'>
@ -21,7 +21,7 @@ export enum CompilerDeprecationTypes {
COMPILER_V_IF_V_FOR_PRECEDENCE = 'COMPILER_V_IF_V_FOR_PRECEDENCE', COMPILER_V_IF_V_FOR_PRECEDENCE = 'COMPILER_V_IF_V_FOR_PRECEDENCE',
COMPILER_NATIVE_TEMPLATE = 'COMPILER_NATIVE_TEMPLATE', COMPILER_NATIVE_TEMPLATE = 'COMPILER_NATIVE_TEMPLATE',
COMPILER_INLINE_TEMPLATE = 'COMPILER_INLINE_TEMPLATE', COMPILER_INLINE_TEMPLATE = 'COMPILER_INLINE_TEMPLATE',
COMPILER_FILTERS = 'COMPILER_FILTER' COMPILER_FILTERS = 'COMPILER_FILTER',
} }
type DeprecationData = { type DeprecationData = {
@ -35,7 +35,7 @@ const deprecationData: Record<CompilerDeprecationTypes, DeprecationData> = {
`Platform-native elements with "is" prop will no longer be ` + `Platform-native elements with "is" prop will no longer be ` +
`treated as components in Vue 3 unless the "is" value is explicitly ` + `treated as components in Vue 3 unless the "is" value is explicitly ` +
`prefixed with "vue:".`, `prefixed with "vue:".`,
link: `https://v3-migration.vuejs.org/breaking-changes/custom-elements-interop.html` link: `https://v3-migration.vuejs.org/breaking-changes/custom-elements-interop.html`,
}, },
[CompilerDeprecationTypes.COMPILER_V_BIND_SYNC]: { [CompilerDeprecationTypes.COMPILER_V_BIND_SYNC]: {
@ -43,7 +43,7 @@ const deprecationData: Record<CompilerDeprecationTypes, DeprecationData> = {
`.sync modifier for v-bind has been removed. Use v-model with ` + `.sync modifier for v-bind has been removed. Use v-model with ` +
`argument instead. \`v-bind:${key}.sync\` should be changed to ` + `argument instead. \`v-bind:${key}.sync\` should be changed to ` +
`\`v-model:${key}\`.`, `\`v-model:${key}\`.`,
link: `https://v3-migration.vuejs.org/breaking-changes/v-model.html` link: `https://v3-migration.vuejs.org/breaking-changes/v-model.html`,
}, },
[CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER]: { [CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER]: {
@ -53,12 +53,12 @@ const deprecationData: Record<CompilerDeprecationTypes, DeprecationData> = {
`that appears before v-bind in the case of conflict. ` + `that appears before v-bind in the case of conflict. ` +
`To retain 2.x behavior, move v-bind to make it the first attribute. ` + `To retain 2.x behavior, move v-bind to make it the first attribute. ` +
`You can also suppress this warning if the usage is intended.`, `You can also suppress this warning if the usage is intended.`,
link: `https://v3-migration.vuejs.org/breaking-changes/v-bind.html` link: `https://v3-migration.vuejs.org/breaking-changes/v-bind.html`,
}, },
[CompilerDeprecationTypes.COMPILER_V_ON_NATIVE]: { [CompilerDeprecationTypes.COMPILER_V_ON_NATIVE]: {
message: `.native modifier for v-on has been removed as is no longer necessary.`, message: `.native modifier for v-on has been removed as is no longer necessary.`,
link: `https://v3-migration.vuejs.org/breaking-changes/v-on-native-modifier-removed.html` link: `https://v3-migration.vuejs.org/breaking-changes/v-on-native-modifier-removed.html`,
}, },
[CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE]: { [CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE]: {
@ -68,18 +68,18 @@ const deprecationData: Record<CompilerDeprecationTypes, DeprecationData> = {
`access to v-for scope variables. It is best to avoid the ambiguity ` + `access to v-for scope variables. It is best to avoid the ambiguity ` +
`with <template> tags or use a computed property that filters v-for ` + `with <template> tags or use a computed property that filters v-for ` +
`data source.`, `data source.`,
link: `https://v3-migration.vuejs.org/breaking-changes/v-if-v-for.html` link: `https://v3-migration.vuejs.org/breaking-changes/v-if-v-for.html`,
}, },
[CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE]: { [CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE]: {
message: message:
`<template> with no special directives will render as a native template ` + `<template> with no special directives will render as a native template ` +
`element instead of its inner content in Vue 3.` `element instead of its inner content in Vue 3.`,
}, },
[CompilerDeprecationTypes.COMPILER_INLINE_TEMPLATE]: { [CompilerDeprecationTypes.COMPILER_INLINE_TEMPLATE]: {
message: `"inline-template" has been removed in Vue 3.`, message: `"inline-template" has been removed in Vue 3.`,
link: `https://v3-migration.vuejs.org/breaking-changes/inline-template-attribute.html` link: `https://v3-migration.vuejs.org/breaking-changes/inline-template-attribute.html`,
}, },
[CompilerDeprecationTypes.COMPILER_FILTERS]: { [CompilerDeprecationTypes.COMPILER_FILTERS]: {
@ -87,13 +87,13 @@ const deprecationData: Record<CompilerDeprecationTypes, DeprecationData> = {
`filters have been removed in Vue 3. ` + `filters have been removed in Vue 3. ` +
`The "|" symbol will be treated as native JavaScript bitwise OR operator. ` + `The "|" symbol will be treated as native JavaScript bitwise OR operator. ` +
`Use method calls or computed properties instead.`, `Use method calls or computed properties instead.`,
link: `https://v3-migration.vuejs.org/breaking-changes/filters.html` link: `https://v3-migration.vuejs.org/breaking-changes/filters.html`,
} },
} }
function getCompatValue( function getCompatValue(
key: CompilerDeprecationTypes | 'MODE', key: CompilerDeprecationTypes | 'MODE',
{ compatConfig }: MergedParserOptions | TransformContext { compatConfig }: MergedParserOptions | TransformContext,
) { ) {
const value = compatConfig && compatConfig[key] const value = compatConfig && compatConfig[key]
if (key === 'MODE') { if (key === 'MODE') {
@ -105,7 +105,7 @@ function getCompatValue(
export function isCompatEnabled( export function isCompatEnabled(
key: CompilerDeprecationTypes, key: CompilerDeprecationTypes,
context: MergedParserOptions | TransformContext context: MergedParserOptions | TransformContext,
) { ) {
const mode = getCompatValue('MODE', context) const mode = getCompatValue('MODE', context)
const value = getCompatValue(key, context) const value = getCompatValue(key, context)

View File

@ -1,17 +1,17 @@
import { RESOLVE_FILTER } from '../runtimeHelpers' import { RESOLVE_FILTER } from '../runtimeHelpers'
import { import {
ExpressionNode, type AttributeNode,
AttributeNode, type DirectiveNode,
DirectiveNode, type ExpressionNode,
NodeTypes, NodeTypes,
SimpleExpressionNode type SimpleExpressionNode,
} from '../ast' } from '../ast'
import { import {
CompilerDeprecationTypes, CompilerDeprecationTypes,
isCompatEnabled, isCompatEnabled,
warnDeprecation warnDeprecation,
} from './compatConfig' } from './compatConfig'
import { NodeTransform, TransformContext } from '../transform' import type { NodeTransform, TransformContext } from '../transform'
import { toValidAssetId } from '../utils' import { toValidAssetId } from '../utils'
const validDivisionCharRE = /[\w).+\-_$\]]/ const validDivisionCharRE = /[\w).+\-_$\]]/
@ -162,7 +162,7 @@ function parseFilter(node: SimpleExpressionNode, context: TransformContext) {
warnDeprecation( warnDeprecation(
CompilerDeprecationTypes.COMPILER_FILTERS, CompilerDeprecationTypes.COMPILER_FILTERS,
context, context,
node.loc node.loc,
) )
for (i = 0; i < filters.length; i++) { for (i = 0; i < filters.length; i++) {
expression = wrapFilter(expression, filters[i], context) expression = wrapFilter(expression, filters[i], context)
@ -174,7 +174,7 @@ function parseFilter(node: SimpleExpressionNode, context: TransformContext) {
function wrapFilter( function wrapFilter(
exp: string, exp: string,
filter: string, filter: string,
context: TransformContext context: TransformContext,
): string { ): string {
context.helper(RESOLVE_FILTER) context.helper(RESOLVE_FILTER)
const i = filter.indexOf('(') const i = filter.indexOf('(')

View File

@ -1,9 +1,13 @@
import { CompilerOptions } from './options' import type { CompilerOptions } from './options'
import { baseParse } from './parser' import { baseParse } from './parser'
import { transform, NodeTransform, DirectiveTransform } from './transform' import {
import { generate, CodegenResult } from './codegen' type DirectiveTransform,
import { RootNode } from './ast' type NodeTransform,
import { isString, extend } from '@vue/shared' transform,
} from './transform'
import { type CodegenResult, generate } from './codegen'
import type { RootNode } from './ast'
import { extend, isString } from '@vue/shared'
import { transformIf } from './transforms/vIf' import { transformIf } from './transforms/vIf'
import { transformFor } from './transforms/vFor' import { transformFor } from './transforms/vFor'
import { transformExpression } from './transforms/transformExpression' import { transformExpression } from './transforms/transformExpression'
@ -16,16 +20,16 @@ import { transformText } from './transforms/transformText'
import { transformOnce } from './transforms/vOnce' import { transformOnce } from './transforms/vOnce'
import { transformModel } from './transforms/vModel' import { transformModel } from './transforms/vModel'
import { transformFilter } from './compat/transformFilter' import { transformFilter } from './compat/transformFilter'
import { defaultOnError, createCompilerError, ErrorCodes } from './errors' import { ErrorCodes, createCompilerError, defaultOnError } from './errors'
import { transformMemo } from './transforms/vMemo' import { transformMemo } from './transforms/vMemo'
export type TransformPreset = [ export type TransformPreset = [
NodeTransform[], NodeTransform[],
Record<string, DirectiveTransform> Record<string, DirectiveTransform>,
] ]
export function getBaseTransformPreset( export function getBaseTransformPreset(
prefixIdentifiers?: boolean prefixIdentifiers?: boolean,
): TransformPreset { ): TransformPreset {
return [ return [
[ [
@ -38,7 +42,7 @@ export function getBaseTransformPreset(
? [ ? [
// order is important // order is important
trackVForSlotScopes, trackVForSlotScopes,
transformExpression transformExpression,
] ]
: __BROWSER__ && __DEV__ : __BROWSER__ && __DEV__
? [transformExpression] ? [transformExpression]
@ -46,13 +50,13 @@ export function getBaseTransformPreset(
transformSlotOutlet, transformSlotOutlet,
transformElement, transformElement,
trackSlotScopes, trackSlotScopes,
transformText transformText,
], ],
{ {
on: transformOn, on: transformOn,
bind: transformBind, bind: transformBind,
model: transformModel model: transformModel,
} },
] ]
} }
@ -60,7 +64,7 @@ export function getBaseTransformPreset(
// @vue/compiler-dom can export `compile` while re-exporting everything else. // @vue/compiler-dom can export `compile` while re-exporting everything else.
export function baseCompile( export function baseCompile(
source: string | RootNode, source: string | RootNode,
options: CompilerOptions = {} options: CompilerOptions = {},
): CodegenResult { ): CodegenResult {
const onError = options.onError || defaultOnError const onError = options.onError || defaultOnError
const isModuleMode = options.mode === 'module' const isModuleMode = options.mode === 'module'
@ -83,7 +87,7 @@ export function baseCompile(
} }
const resolvedOptions = extend({}, options, { const resolvedOptions = extend({}, options, {
prefixIdentifiers prefixIdentifiers,
}) })
const ast = isString(source) ? baseParse(source, resolvedOptions) : source const ast = isString(source) ? baseParse(source, resolvedOptions) : source
const [nodeTransforms, directiveTransforms] = const [nodeTransforms, directiveTransforms] =
@ -101,14 +105,14 @@ export function baseCompile(
extend({}, resolvedOptions, { extend({}, resolvedOptions, {
nodeTransforms: [ nodeTransforms: [
...nodeTransforms, ...nodeTransforms,
...(options.nodeTransforms || []) // user transforms ...(options.nodeTransforms || []), // user transforms
], ],
directiveTransforms: extend( directiveTransforms: extend(
{}, {},
directiveTransforms, directiveTransforms,
options.directiveTransforms || {} // user transforms options.directiveTransforms || {}, // user transforms
) ),
}) }),
) )
return generate(ast, resolvedOptions) return generate(ast, resolvedOptions)

View File

@ -1,4 +1,4 @@
import { SourceLocation } from './ast' import type { SourceLocation } from './ast'
export interface CompilerError extends SyntaxError { export interface CompilerError extends SyntaxError {
code: number | string code: number | string
@ -25,7 +25,7 @@ export function createCompilerError<T extends number>(
code: T, code: T,
loc?: SourceLocation, loc?: SourceLocation,
messages?: { [code: number]: string }, messages?: { [code: number]: string },
additionalMessage?: string additionalMessage?: string,
): InferCompilerError<T> { ): InferCompilerError<T> {
const msg = const msg =
__DEV__ || !__BROWSER__ __DEV__ || !__BROWSER__
@ -101,7 +101,7 @@ export enum ErrorCodes {
// Special value for higher-order compilers to pick up the last code // Special value for higher-order compilers to pick up the last code
// to avoid collision of error codes. This should always be kept as the last // to avoid collision of error codes. This should always be kept as the last
// item. // item.
__EXTEND_POINT__ __EXTEND_POINT__,
} }
export const errorMessages: Record<ErrorCodes, string> = { export const errorMessages: Record<ErrorCodes, string> = {
@ -182,5 +182,5 @@ export const errorMessages: Record<ErrorCodes, string> = {
[ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED]: `"scopeId" option is only supported in module mode.`, [ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED]: `"scopeId" option is only supported in module mode.`,
// just to fulfill types // just to fulfill types
[ErrorCodes.__EXTEND_POINT__]: `` [ErrorCodes.__EXTEND_POINT__]: ``,
} }

View File

@ -8,7 +8,7 @@ export {
type CodegenOptions, type CodegenOptions,
type HoistTransform, type HoistTransform,
type BindingMetadata, type BindingMetadata,
BindingTypes BindingTypes,
} from './options' } from './options'
export { baseParse } from './parser' export { baseParse } from './parser'
export { export {
@ -19,7 +19,7 @@ export {
createStructuralDirectiveTransform, createStructuralDirectiveTransform,
type NodeTransform, type NodeTransform,
type StructuralDirectiveTransform, type StructuralDirectiveTransform,
type DirectiveTransform type DirectiveTransform,
} from './transform' } from './transform'
export { generate, type CodegenContext, type CodegenResult } from './codegen' export { generate, type CodegenContext, type CodegenResult } from './codegen'
export { export {
@ -27,7 +27,7 @@ export {
errorMessages, errorMessages,
createCompilerError, createCompilerError,
type CoreCompilerError, type CoreCompilerError,
type CompilerError type CompilerError,
} from './errors' } from './errors'
export * from './ast' export * from './ast'
@ -45,20 +45,20 @@ export { processFor, createForLoopParams } from './transforms/vFor'
export { export {
transformExpression, transformExpression,
processExpression, processExpression,
stringifyExpression stringifyExpression,
} from './transforms/transformExpression' } from './transforms/transformExpression'
export { export {
buildSlots, buildSlots,
type SlotFnBuilder, type SlotFnBuilder,
trackVForSlotScopes, trackVForSlotScopes,
trackSlotScopes trackSlotScopes,
} from './transforms/vSlot' } from './transforms/vSlot'
export { export {
transformElement, transformElement,
resolveComponentType, resolveComponentType,
buildProps, buildProps,
buildDirectiveArgs, buildDirectiveArgs,
type PropsExpression type PropsExpression,
} from './transforms/transformElement' } from './transforms/transformElement'
export { processSlotOutlet } from './transforms/transformSlotOutlet' export { processSlotOutlet } from './transforms/transformSlotOutlet'
export { getConstantType } from './transforms/hoistStatic' export { getConstantType } from './transforms/hoistStatic'
@ -68,5 +68,5 @@ export { generateCodeFrame } from '@vue/shared'
export { export {
checkCompatEnabled, checkCompatEnabled,
warnDeprecation, warnDeprecation,
CompilerDeprecationTypes CompilerDeprecationTypes,
} from './compat/compatConfig' } from './compat/compatConfig'

View File

@ -1,18 +1,18 @@
import { import type {
ElementNode, ElementNode,
Namespace, Namespace,
TemplateChildNode, Namespaces,
ParentNode, ParentNode,
Namespaces TemplateChildNode,
} from './ast' } from './ast'
import { CompilerError } from './errors' import type { CompilerError } from './errors'
import { import type {
NodeTransform,
DirectiveTransform, DirectiveTransform,
TransformContext NodeTransform,
TransformContext,
} from './transform' } from './transform'
import { CompilerCompatOptions } from './compat/compatConfig' import type { CompilerCompatOptions } from './compat/compatConfig'
import { ParserPlugin } from '@babel/parser' import type { ParserPlugin } from '@babel/parser'
export interface ErrorHandlingOptions { export interface ErrorHandlingOptions {
onWarn?: (warning: CompilerError) => void onWarn?: (warning: CompilerError) => void
@ -66,7 +66,7 @@ export interface ParserOptions
getNamespace?: ( getNamespace?: (
tag: string, tag: string,
parent: ElementNode | undefined, parent: ElementNode | undefined,
rootNamespace: Namespace rootNamespace: Namespace,
) => Namespace ) => Namespace
/** /**
* @default ['{{', '}}'] * @default ['{{', '}}']
@ -102,7 +102,7 @@ export interface ParserOptions
export type HoistTransform = ( export type HoistTransform = (
children: TemplateChildNode[], children: TemplateChildNode[],
context: TransformContext, context: TransformContext,
parent: ParentNode parent: ParentNode,
) => void ) => void
export enum BindingTypes { export enum BindingTypes {
@ -148,7 +148,7 @@ export enum BindingTypes {
/** /**
* a literal constant, e.g. 'foo', 1, true * a literal constant, e.g. 'foo', 1, true
*/ */
LITERAL_CONST = 'literal-const' LITERAL_CONST = 'literal-const',
} }
export type BindingMetadata = { export type BindingMetadata = {

View File

@ -1,20 +1,20 @@
import { import {
AttributeNode, type AttributeNode,
ConstantTypes, ConstantTypes,
DirectiveNode, type DirectiveNode,
ElementNode, type ElementNode,
ElementTypes, ElementTypes,
ForParseResult, type ForParseResult,
Namespaces, Namespaces,
NodeTypes, NodeTypes,
RootNode, type RootNode,
SimpleExpressionNode, type SimpleExpressionNode,
SourceLocation, type SourceLocation,
TemplateChildNode, type TemplateChildNode,
createRoot, createRoot,
createSimpleExpression createSimpleExpression,
} from './ast' } from './ast'
import { ParserOptions } from './options' import type { ParserOptions } from './options'
import Tokenizer, { import Tokenizer, {
CharCodes, CharCodes,
ParseMode, ParseMode,
@ -22,33 +22,33 @@ import Tokenizer, {
Sequences, Sequences,
State, State,
isWhitespace, isWhitespace,
toCharCodes toCharCodes,
} from './tokenizer' } from './tokenizer'
import { import {
CompilerCompatOptions, type CompilerCompatOptions,
CompilerDeprecationTypes, CompilerDeprecationTypes,
checkCompatEnabled, checkCompatEnabled,
isCompatEnabled, isCompatEnabled,
warnDeprecation warnDeprecation,
} from './compat/compatConfig' } from './compat/compatConfig'
import { NO, extend } from '@vue/shared' import { NO, extend } from '@vue/shared'
import { import {
ErrorCodes, ErrorCodes,
createCompilerError, createCompilerError,
defaultOnError, defaultOnError,
defaultOnWarn defaultOnWarn,
} from './errors' } from './errors'
import { import {
forAliasRE, forAliasRE,
isCoreComponent, isCoreComponent,
isSimpleIdentifier, isSimpleIdentifier,
isStaticArgOf isStaticArgOf,
} from './utils' } from './utils'
import { decodeHTML } from 'entities/lib/decode.js' import { decodeHTML } from 'entities/lib/decode.js'
import { import {
type ParserOptions as BabelOptions,
parse, parse,
parseExpression, parseExpression,
type ParserOptions as BabelOptions
} from '@babel/parser' } from '@babel/parser'
type OptionalOptions = type OptionalOptions =
@ -76,7 +76,7 @@ export const defaultParserOptions: MergedParserOptions = {
onError: defaultOnError, onError: defaultOnError,
onWarn: defaultOnWarn, onWarn: defaultOnWarn,
comments: __DEV__, comments: __DEV__,
prefixIdentifiers: false prefixIdentifiers: false,
} }
let currentOptions: MergedParserOptions = defaultParserOptions let currentOptions: MergedParserOptions = defaultParserOptions
@ -129,7 +129,7 @@ const tokenizer = new Tokenizer(stack, {
addNode({ addNode({
type: NodeTypes.INTERPOLATION, type: NodeTypes.INTERPOLATION,
content: createExp(exp, false, getLoc(innerStart, innerEnd)), content: createExp(exp, false, getLoc(innerStart, innerEnd)),
loc: getLoc(start, end) loc: getLoc(start, end),
}) })
}, },
@ -143,7 +143,7 @@ const tokenizer = new Tokenizer(stack, {
props: [], props: [],
children: [], children: [],
loc: getLoc(start - 1, end), loc: getLoc(start - 1, end),
codegenNode: undefined codegenNode: undefined,
} }
}, },
@ -191,7 +191,7 @@ const tokenizer = new Tokenizer(stack, {
name: getSlice(start, end), name: getSlice(start, end),
nameLoc: getLoc(start, end), nameLoc: getLoc(start, end),
value: undefined, value: undefined,
loc: getLoc(start) loc: getLoc(start),
} }
}, },
@ -216,7 +216,7 @@ const tokenizer = new Tokenizer(stack, {
name: raw, name: raw,
nameLoc: getLoc(start, end), nameLoc: getLoc(start, end),
value: undefined, value: undefined,
loc: getLoc(start) loc: getLoc(start),
} }
} else { } else {
currentProp = { currentProp = {
@ -226,7 +226,7 @@ const tokenizer = new Tokenizer(stack, {
exp: undefined, exp: undefined,
arg: undefined, arg: undefined,
modifiers: raw === '.' ? ['prop'] : [], modifiers: raw === '.' ? ['prop'] : [],
loc: getLoc(start) loc: getLoc(start),
} }
if (name === 'pre') { if (name === 'pre') {
inVPre = tokenizer.inVPre = true inVPre = tokenizer.inVPre = true
@ -254,7 +254,7 @@ const tokenizer = new Tokenizer(stack, {
isStatic ? arg : arg.slice(1, -1), isStatic ? arg : arg.slice(1, -1),
isStatic, isStatic,
getLoc(start, end), getLoc(start, end),
isStatic ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT isStatic ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT,
) )
} }
}, },
@ -298,7 +298,7 @@ const tokenizer = new Tokenizer(stack, {
// check duplicate attrs // check duplicate attrs
if ( if (
currentOpenTag!.props.some( currentOpenTag!.props.some(
p => (p.type === NodeTypes.DIRECTIVE ? p.rawName : p.name) === name p => (p.type === NodeTypes.DIRECTIVE ? p.rawName : p.name) === name,
) )
) { ) {
emitError(ErrorCodes.DUPLICATE_ATTRIBUTE, start) emitError(ErrorCodes.DUPLICATE_ATTRIBUTE, start)
@ -314,7 +314,7 @@ const tokenizer = new Tokenizer(stack, {
if (__BROWSER__ && currentAttrValue.includes('&')) { if (__BROWSER__ && currentAttrValue.includes('&')) {
currentAttrValue = currentOptions.decodeEntities!( currentAttrValue = currentOptions.decodeEntities!(
currentAttrValue, currentAttrValue,
true true,
) )
} }
@ -336,7 +336,7 @@ const tokenizer = new Tokenizer(stack, {
loc: loc:
quote === QuoteType.Unquoted quote === QuoteType.Unquoted
? getLoc(currentAttrStartIndex, currentAttrEndIndex) ? getLoc(currentAttrStartIndex, currentAttrEndIndex)
: getLoc(currentAttrStartIndex - 1, currentAttrEndIndex + 1) : getLoc(currentAttrStartIndex - 1, currentAttrEndIndex + 1),
} }
if ( if (
tokenizer.inSFCRoot && tokenizer.inSFCRoot &&
@ -369,7 +369,7 @@ const tokenizer = new Tokenizer(stack, {
false, false,
getLoc(currentAttrStartIndex, currentAttrEndIndex), getLoc(currentAttrStartIndex, currentAttrEndIndex),
ConstantTypes.NOT_CONSTANT, ConstantTypes.NOT_CONSTANT,
expParseMode expParseMode,
) )
if (currentProp.name === 'for') { if (currentProp.name === 'for') {
currentProp.forParseResult = parseForExpression(currentProp.exp) currentProp.forParseResult = parseForExpression(currentProp.exp)
@ -384,7 +384,7 @@ const tokenizer = new Tokenizer(stack, {
CompilerDeprecationTypes.COMPILER_V_BIND_SYNC, CompilerDeprecationTypes.COMPILER_V_BIND_SYNC,
currentOptions, currentOptions,
currentProp.loc, currentProp.loc,
currentProp.rawName currentProp.rawName,
) )
) { ) {
currentProp.name = 'model' currentProp.name = 'model'
@ -408,7 +408,7 @@ const tokenizer = new Tokenizer(stack, {
addNode({ addNode({
type: NodeTypes.COMMENT, type: NodeTypes.COMMENT,
content: getSlice(start, end), content: getSlice(start, end),
loc: getLoc(start - 4, end + 3) loc: getLoc(start - 4, end + 3),
}) })
} }
}, },
@ -426,7 +426,7 @@ const tokenizer = new Tokenizer(stack, {
case State.InterpolationClose: case State.InterpolationClose:
emitError( emitError(
ErrorCodes.X_MISSING_INTERPOLATION_END, ErrorCodes.X_MISSING_INTERPOLATION_END,
tokenizer.sectionStart tokenizer.sectionStart,
) )
break break
case State.InCommentLike: case State.InCommentLike:
@ -476,10 +476,10 @@ const tokenizer = new Tokenizer(stack, {
if ((stack[0] ? stack[0].ns : currentOptions.ns) === Namespaces.HTML) { if ((stack[0] ? stack[0].ns : currentOptions.ns) === Namespaces.HTML) {
emitError( emitError(
ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME, ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
start - 1 start - 1,
) )
} }
} },
}) })
// This regex doesn't cover the case if key or index aliases have destructuring, // This regex doesn't cover the case if key or index aliases have destructuring,
@ -488,7 +488,7 @@ const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
const stripParensRE = /^\(|\)$/g const stripParensRE = /^\(|\)$/g
function parseForExpression( function parseForExpression(
input: SimpleExpressionNode input: SimpleExpressionNode,
): ForParseResult | undefined { ): ForParseResult | undefined {
const loc = input.loc const loc = input.loc
const exp = input.content const exp = input.content
@ -500,7 +500,7 @@ function parseForExpression(
const createAliasExpression = ( const createAliasExpression = (
content: string, content: string,
offset: number, offset: number,
asParam = false asParam = false,
) => { ) => {
const start = loc.start.offset + offset const start = loc.start.offset + offset
const end = start + content.length const end = start + content.length
@ -509,7 +509,7 @@ function parseForExpression(
false, false,
getLoc(start, end), getLoc(start, end),
ConstantTypes.NOT_CONSTANT, ConstantTypes.NOT_CONSTANT,
asParam ? ExpParseMode.Params : ExpParseMode.Normal asParam ? ExpParseMode.Params : ExpParseMode.Normal,
) )
} }
@ -518,7 +518,7 @@ function parseForExpression(
value: undefined, value: undefined,
key: undefined, key: undefined,
index: undefined, index: undefined,
finalized: false finalized: false,
} }
let valueContent = LHS.trim().replace(stripParensRE, '').trim() let valueContent = LHS.trim().replace(stripParensRE, '').trim()
@ -545,9 +545,9 @@ function parseForExpression(
indexContent, indexContent,
result.key result.key
? keyOffset! + keyContent.length ? keyOffset! + keyContent.length
: trimmedOffset + valueContent.length : trimmedOffset + valueContent.length,
), ),
true true,
) )
} }
} }
@ -602,7 +602,7 @@ function onText(content: string, start: number, end: number) {
parent.children.push({ parent.children.push({
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content, content,
loc: getLoc(start, end) loc: getLoc(start, end),
}) })
} }
} }
@ -625,7 +625,7 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
} }
el.innerLoc!.source = getSlice( el.innerLoc!.source = getSlice(
el.innerLoc!.start.offset, el.innerLoc!.start.offset,
el.innerLoc!.end.offset el.innerLoc!.end.offset,
) )
} }
@ -666,7 +666,7 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
__DEV__ && __DEV__ &&
isCompatEnabled( isCompatEnabled(
CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE, CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE,
currentOptions currentOptions,
) )
) { ) {
let hasIf = false let hasIf = false
@ -684,7 +684,7 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
warnDeprecation( warnDeprecation(
CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE, CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE,
currentOptions, currentOptions,
el.loc el.loc,
) )
break break
} }
@ -694,7 +694,7 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
if ( if (
isCompatEnabled( isCompatEnabled(
CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE, CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
currentOptions currentOptions,
) && ) &&
el.tag === 'template' && el.tag === 'template' &&
!isFragmentTemplate(el) !isFragmentTemplate(el)
@ -703,7 +703,7 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
warnDeprecation( warnDeprecation(
CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE, CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
currentOptions, currentOptions,
el.loc el.loc,
) )
// unwrap // unwrap
const parent = stack[0] || currentRoot const parent = stack[0] || currentRoot
@ -712,14 +712,14 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
} }
const inlineTemplateProp = props.find( const inlineTemplateProp = props.find(
p => p.type === NodeTypes.ATTRIBUTE && p.name === 'inline-template' p => p.type === NodeTypes.ATTRIBUTE && p.name === 'inline-template',
) as AttributeNode ) as AttributeNode
if ( if (
inlineTemplateProp && inlineTemplateProp &&
checkCompatEnabled( checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_INLINE_TEMPLATE, CompilerDeprecationTypes.COMPILER_INLINE_TEMPLATE,
currentOptions, currentOptions,
inlineTemplateProp.loc inlineTemplateProp.loc,
) && ) &&
el.children.length el.children.length
) { ) {
@ -727,9 +727,9 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: getSlice( content: getSlice(
el.children[0].loc.start.offset, el.children[0].loc.start.offset,
el.children[el.children.length - 1].loc.end.offset el.children[el.children.length - 1].loc.end.offset,
), ),
loc: inlineTemplateProp.loc loc: inlineTemplateProp.loc,
} }
} }
} }
@ -782,7 +782,7 @@ function isComponent({ tag, props }: ElementNode): boolean {
checkCompatEnabled( checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT, CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
currentOptions, currentOptions,
p.loc p.loc,
) )
) { ) {
return true return true
@ -796,7 +796,7 @@ function isComponent({ tag, props }: ElementNode): boolean {
checkCompatEnabled( checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT, CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
currentOptions, currentOptions,
p.loc p.loc,
) )
) { ) {
return true return true
@ -812,7 +812,7 @@ function isUpperCase(c: number) {
const windowsNewlineRE = /\r\n/g const windowsNewlineRE = /\r\n/g
function condenseWhitespace( function condenseWhitespace(
nodes: TemplateChildNode[], nodes: TemplateChildNode[],
tag?: string tag?: string,
): TemplateChildNode[] { ): TemplateChildNode[] {
const shouldCondense = currentOptions.whitespace !== 'preserve' const shouldCondense = currentOptions.whitespace !== 'preserve'
let removedWhitespace = false let removedWhitespace = false
@ -915,7 +915,7 @@ function getLoc(start: number, end?: number): SourceLocation {
// @ts-expect-error allow late attachment // @ts-expect-error allow late attachment
end: end == null ? end : tokenizer.getPos(end), end: end == null ? end : tokenizer.getPos(end),
// @ts-expect-error allow late attachment // @ts-expect-error allow late attachment
source: end == null ? end : getSlice(start, end) source: end == null ? end : getSlice(start, end),
} }
} }
@ -930,10 +930,10 @@ function dirToAttr(dir: DirectiveNode): AttributeNode {
name: dir.rawName!, name: dir.rawName!,
nameLoc: getLoc( nameLoc: getLoc(
dir.loc.start.offset, dir.loc.start.offset,
dir.loc.start.offset + dir.rawName!.length dir.loc.start.offset + dir.rawName!.length,
), ),
value: undefined, value: undefined,
loc: dir.loc loc: dir.loc,
} }
if (dir.exp) { if (dir.exp) {
// account for quotes // account for quotes
@ -947,7 +947,7 @@ function dirToAttr(dir: DirectiveNode): AttributeNode {
attr.value = { attr.value = {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: (dir.exp as SimpleExpressionNode).content, content: (dir.exp as SimpleExpressionNode).content,
loc loc,
} }
} }
return attr return attr
@ -957,7 +957,7 @@ enum ExpParseMode {
Normal, Normal,
Params, Params,
Statements, Statements,
Skip Skip,
} }
function createExp( function createExp(
@ -965,7 +965,7 @@ function createExp(
isStatic: SimpleExpressionNode['isStatic'] = false, isStatic: SimpleExpressionNode['isStatic'] = false,
loc: SourceLocation, loc: SourceLocation,
constType: ConstantTypes = ConstantTypes.NOT_CONSTANT, constType: ConstantTypes = ConstantTypes.NOT_CONSTANT,
parseMode = ExpParseMode.Normal parseMode = ExpParseMode.Normal,
) { ) {
const exp = createSimpleExpression(content, isStatic, loc, constType) const exp = createSimpleExpression(content, isStatic, loc, constType)
if ( if (
@ -982,7 +982,7 @@ function createExp(
try { try {
const plugins = currentOptions.expressionPlugins const plugins = currentOptions.expressionPlugins
const options: BabelOptions = { const options: BabelOptions = {
plugins: plugins ? [...plugins, 'typescript'] : ['typescript'] plugins: plugins ? [...plugins, 'typescript'] : ['typescript'],
} }
if (parseMode === ExpParseMode.Statements) { if (parseMode === ExpParseMode.Statements) {
// v-on with multi-inline-statements, pad 1 char // v-on with multi-inline-statements, pad 1 char
@ -1003,7 +1003,7 @@ function createExp(
function emitError(code: ErrorCodes, index: number, message?: string) { function emitError(code: ErrorCodes, index: number, message?: string) {
currentOptions.onError( currentOptions.onError(
createCompilerError(code, getLoc(index, index), undefined, message) createCompilerError(code, getLoc(index, index), undefined, message),
) )
} }
@ -1026,7 +1026,7 @@ export function baseParse(input: string, options?: ParserOptions): RootNode {
let key: keyof ParserOptions let key: keyof ParserOptions
for (key in options) { for (key in options) {
if (options[key] != null) { if (options[key] != null) {
// @ts-ignore // @ts-expect-error
currentOptions[key] = options[key] currentOptions[key] = options[key]
} }
} }
@ -1036,11 +1036,11 @@ export function baseParse(input: string, options?: ParserOptions): RootNode {
if (!__BROWSER__ && currentOptions.decodeEntities) { if (!__BROWSER__ && currentOptions.decodeEntities) {
console.warn( console.warn(
`[@vue/compiler-core] decodeEntities option is passed but will be ` + `[@vue/compiler-core] decodeEntities option is passed but will be ` +
`ignored in non-browser builds.` `ignored in non-browser builds.`,
) )
} else if (__BROWSER__ && !currentOptions.decodeEntities) { } else if (__BROWSER__ && !currentOptions.decodeEntities) {
throw new Error( throw new Error(
`[@vue/compiler-core] decodeEntities option is required in browser builds.` `[@vue/compiler-core] decodeEntities option is required in browser builds.`,
) )
} }
} }

View File

@ -13,7 +13,7 @@ export const CREATE_TEXT = Symbol(__DEV__ ? `createTextVNode` : ``)
export const CREATE_STATIC = Symbol(__DEV__ ? `createStaticVNode` : ``) export const CREATE_STATIC = Symbol(__DEV__ ? `createStaticVNode` : ``)
export const RESOLVE_COMPONENT = Symbol(__DEV__ ? `resolveComponent` : ``) export const RESOLVE_COMPONENT = Symbol(__DEV__ ? `resolveComponent` : ``)
export const RESOLVE_DYNAMIC_COMPONENT = Symbol( export const RESOLVE_DYNAMIC_COMPONENT = Symbol(
__DEV__ ? `resolveDynamicComponent` : `` __DEV__ ? `resolveDynamicComponent` : ``,
) )
export const RESOLVE_DIRECTIVE = Symbol(__DEV__ ? `resolveDirective` : ``) export const RESOLVE_DIRECTIVE = Symbol(__DEV__ ? `resolveDirective` : ``)
export const RESOLVE_FILTER = Symbol(__DEV__ ? `resolveFilter` : ``) export const RESOLVE_FILTER = Symbol(__DEV__ ? `resolveFilter` : ``)
@ -81,7 +81,7 @@ export const helperNameMap: Record<symbol, string> = {
[UNREF]: `unref`, [UNREF]: `unref`,
[IS_REF]: `isRef`, [IS_REF]: `isRef`,
[WITH_MEMO]: `withMemo`, [WITH_MEMO]: `withMemo`,
[IS_MEMO_SAME]: `isMemoSame` [IS_MEMO_SAME]: `isMemoSame`,
} }
export function registerRuntimeHelpers(helpers: Record<symbol, string>) { export function registerRuntimeHelpers(helpers: Record<symbol, string>) {

View File

@ -23,7 +23,7 @@ IN THE SOFTWARE.
*/ */
import { ErrorCodes } from './errors' import { ErrorCodes } from './errors'
import { ElementNode, Position } from './ast' import type { ElementNode, Position } from './ast'
/** /**
* Note: entities is a non-browser-build-only dependency. * Note: entities is a non-browser-build-only dependency.
@ -32,16 +32,16 @@ import { ElementNode, Position } from './ast'
* so that it can be properly treeshaken. * so that it can be properly treeshaken.
*/ */
import { import {
EntityDecoder,
DecodingMode, DecodingMode,
EntityDecoder,
fromCodePoint,
htmlDecodeTree, htmlDecodeTree,
fromCodePoint
} from 'entities/lib/decode.js' } from 'entities/lib/decode.js'
export enum ParseMode { export enum ParseMode {
BASE, BASE,
HTML, HTML,
SFC SFC,
} }
export enum CharCodes { export enum CharCodes {
@ -77,7 +77,7 @@ export enum CharCodes {
Colon = 0x3a, // ":" Colon = 0x3a, // ":"
At = 0x40, // "@" At = 0x40, // "@"
LeftSquare = 91, // "[" LeftSquare = 91, // "["
RightSquare = 93 // "]" RightSquare = 93, // "]"
} }
const defaultDelimitersOpen = new Uint8Array([123, 123]) // "{{" const defaultDelimitersOpen = new Uint8Array([123, 123]) // "{{"
@ -134,7 +134,7 @@ export enum State {
InEntity, InEntity,
InSFCRootTagName InSFCRootTagName,
} }
/** /**
@ -174,7 +174,7 @@ export enum QuoteType {
NoValue = 0, NoValue = 0,
Unquoted = 1, Unquoted = 1,
Single = 2, Single = 2,
Double = 3 Double = 3,
} }
export interface Callbacks { export interface Callbacks {
@ -221,8 +221,8 @@ export const Sequences = {
StyleEnd: new Uint8Array([0x3c, 0x2f, 0x73, 0x74, 0x79, 0x6c, 0x65]), // `</style` StyleEnd: new Uint8Array([0x3c, 0x2f, 0x73, 0x74, 0x79, 0x6c, 0x65]), // `</style`
TitleEnd: new Uint8Array([0x3c, 0x2f, 0x74, 0x69, 0x74, 0x6c, 0x65]), // `</title` TitleEnd: new Uint8Array([0x3c, 0x2f, 0x74, 0x69, 0x74, 0x6c, 0x65]), // `</title`
TextareaEnd: new Uint8Array([ TextareaEnd: new Uint8Array([
0x3c, 0x2f, 116, 101, 120, 116, 97, 114, 101, 97 0x3c, 0x2f, 116, 101, 120, 116, 97, 114, 101, 97,
]) // `</textarea ]), // `</textarea
} }
export default class Tokenizer { export default class Tokenizer {
@ -256,11 +256,11 @@ export default class Tokenizer {
constructor( constructor(
private readonly stack: ElementNode[], private readonly stack: ElementNode[],
private readonly cbs: Callbacks private readonly cbs: Callbacks,
) { ) {
if (!__BROWSER__) { if (!__BROWSER__) {
this.entityDecoder = new EntityDecoder(htmlDecodeTree, (cp, consumed) => this.entityDecoder = new EntityDecoder(htmlDecodeTree, (cp, consumed) =>
this.emitCodePoint(cp, consumed) this.emitCodePoint(cp, consumed),
) )
} }
} }
@ -299,7 +299,7 @@ export default class Tokenizer {
return { return {
column, column,
line, line,
offset: index offset: index,
} }
} }
@ -647,7 +647,7 @@ export default class Tokenizer {
if ((__DEV__ || !__BROWSER__) && c === CharCodes.Eq) { if ((__DEV__ || !__BROWSER__) && c === CharCodes.Eq) {
this.cbs.onerr( this.cbs.onerr(
ErrorCodes.UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME, ErrorCodes.UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME,
this.index this.index,
) )
} }
this.handleAttrStart(c) this.handleAttrStart(c)
@ -694,7 +694,7 @@ export default class Tokenizer {
) { ) {
this.cbs.onerr( this.cbs.onerr(
ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME, ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
this.index this.index,
) )
} }
} }
@ -733,7 +733,7 @@ export default class Tokenizer {
if (__DEV__ || !__BROWSER__) { if (__DEV__ || !__BROWSER__) {
this.cbs.onerr( this.cbs.onerr(
ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END, ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END,
this.index this.index,
) )
} }
} }
@ -785,7 +785,7 @@ export default class Tokenizer {
this.sectionStart = -1 this.sectionStart = -1
this.cbs.onattribend( this.cbs.onattribend(
quote === CharCodes.DoubleQuote ? QuoteType.Double : QuoteType.Single, quote === CharCodes.DoubleQuote ? QuoteType.Double : QuoteType.Single,
this.index + 1 this.index + 1,
) )
this.state = State.BeforeAttrName this.state = State.BeforeAttrName
} else if (!__BROWSER__ && c === CharCodes.Amp) { } else if (!__BROWSER__ && c === CharCodes.Amp) {
@ -814,7 +814,7 @@ export default class Tokenizer {
) { ) {
this.cbs.onerr( this.cbs.onerr(
ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE, ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
this.index this.index,
) )
} else if (!__BROWSER__ && c === CharCodes.Amp) { } else if (!__BROWSER__ && c === CharCodes.Amp) {
this.startEntity() this.startEntity()
@ -892,7 +892,7 @@ export default class Tokenizer {
this.entityDecoder!.startEntity( this.entityDecoder!.startEntity(
this.baseState === State.Text || this.baseState === State.InRCDATA this.baseState === State.Text || this.baseState === State.InRCDATA
? DecodingMode.Legacy ? DecodingMode.Legacy
: DecodingMode.Attribute : DecodingMode.Attribute,
) )
} }
} }
@ -1156,7 +1156,7 @@ export default class Tokenizer {
this.cbs.onattribentity( this.cbs.onattribentity(
fromCodePoint(cp), fromCodePoint(cp),
this.entityStart, this.entityStart,
this.sectionStart this.sectionStart,
) )
} else { } else {
if (this.sectionStart < this.entityStart) { if (this.sectionStart < this.entityStart) {
@ -1168,7 +1168,7 @@ export default class Tokenizer {
this.cbs.ontextentity( this.cbs.ontextentity(
fromCodePoint(cp), fromCodePoint(cp),
this.entityStart, this.entityStart,
this.sectionStart this.sectionStart,
) )
} }
} }

View File

@ -1,45 +1,45 @@
import { TransformOptions } from './options' import type { TransformOptions } from './options'
import { import {
RootNode, type ArrayExpression,
NodeTypes, type CacheExpression,
ParentNode,
TemplateChildNode,
ElementNode,
DirectiveNode,
Property,
ExpressionNode,
createSimpleExpression,
JSChildNode,
SimpleExpressionNode,
ElementTypes,
CacheExpression,
createCacheExpression,
TemplateLiteral,
createVNodeCall,
ConstantTypes, ConstantTypes,
ArrayExpression, type DirectiveNode,
convertToBlock type ElementNode,
ElementTypes,
type ExpressionNode,
type JSChildNode,
NodeTypes,
type ParentNode,
type Property,
type RootNode,
type SimpleExpressionNode,
type TemplateChildNode,
type TemplateLiteral,
convertToBlock,
createCacheExpression,
createSimpleExpression,
createVNodeCall,
} from './ast' } from './ast'
import { import {
isString,
isArray,
NOOP,
PatchFlags,
PatchFlagNames,
EMPTY_OBJ, EMPTY_OBJ,
NOOP,
PatchFlagNames,
PatchFlags,
camelize,
capitalize, capitalize,
camelize isArray,
isString,
} from '@vue/shared' } from '@vue/shared'
import { defaultOnError, defaultOnWarn } from './errors' import { defaultOnError, defaultOnWarn } from './errors'
import { import {
TO_DISPLAY_STRING, CREATE_COMMENT,
FRAGMENT, FRAGMENT,
TO_DISPLAY_STRING,
helperNameMap, helperNameMap,
CREATE_COMMENT
} from './runtimeHelpers' } from './runtimeHelpers'
import { isVSlot } from './utils' import { isVSlot } from './utils'
import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic' import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
import { CompilerCompatOptions } from './compat/compatConfig' import type { CompilerCompatOptions } from './compat/compatConfig'
// There are two types of transforms: // There are two types of transforms:
// //
@ -48,7 +48,7 @@ import { CompilerCompatOptions } from './compat/compatConfig'
// replace or remove the node being processed. // replace or remove the node being processed.
export type NodeTransform = ( export type NodeTransform = (
node: RootNode | TemplateChildNode, node: RootNode | TemplateChildNode,
context: TransformContext context: TransformContext,
) => void | (() => void) | (() => void)[] ) => void | (() => void) | (() => void)[]
// - DirectiveTransform: // - DirectiveTransform:
@ -60,7 +60,7 @@ export type DirectiveTransform = (
context: TransformContext, context: TransformContext,
// a platform specific compiler can import the base transform and augment // a platform specific compiler can import the base transform and augment
// it by passing in this optional argument. // it by passing in this optional argument.
augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,
) => DirectiveTransformResult ) => DirectiveTransformResult
export interface DirectiveTransformResult { export interface DirectiveTransformResult {
@ -74,7 +74,7 @@ export interface DirectiveTransformResult {
export type StructuralDirectiveTransform = ( export type StructuralDirectiveTransform = (
node: ElementNode, node: ElementNode,
dir: DirectiveNode, dir: DirectiveNode,
context: TransformContext context: TransformContext,
) => void | (() => void) ) => void | (() => void)
export interface ImportItem { export interface ImportItem {
@ -145,8 +145,8 @@ export function createTransformContext(
isTS = false, isTS = false,
onError = defaultOnError, onError = defaultOnError,
onWarn = defaultOnWarn, onWarn = defaultOnWarn,
compatConfig compatConfig,
}: TransformOptions }: TransformOptions,
): TransformContext { ): TransformContext {
const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/) const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/)
const context: TransformContext = { const context: TransformContext = {
@ -190,7 +190,7 @@ export function createTransformContext(
vFor: 0, vFor: 0,
vSlot: 0, vSlot: 0,
vPre: 0, vPre: 0,
vOnce: 0 vOnce: 0,
}, },
parent: null, parent: null,
currentNode: root, currentNode: root,
@ -287,14 +287,14 @@ export function createTransformContext(
`_hoisted_${context.hoists.length}`, `_hoisted_${context.hoists.length}`,
false, false,
exp.loc, exp.loc,
ConstantTypes.CAN_HOIST ConstantTypes.CAN_HOIST,
) )
identifier.hoisted = exp identifier.hoisted = exp
return identifier return identifier
}, },
cache(exp, isVNode = false) { cache(exp, isVNode = false) {
return createCacheExpression(context.cached++, exp, isVNode) return createCacheExpression(context.cached++, exp, isVNode)
} },
} }
if (__COMPAT__) { if (__COMPAT__) {
@ -383,7 +383,7 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
undefined, undefined,
true, true,
undefined, undefined,
false /* isComponent */ false /* isComponent */,
) )
} else { } else {
// no children = noop. codegen will return null. // no children = noop. codegen will return null.
@ -392,7 +392,7 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
export function traverseChildren( export function traverseChildren(
parent: ParentNode, parent: ParentNode,
context: TransformContext context: TransformContext,
) { ) {
let i = 0 let i = 0
const nodeRemoved = () => { const nodeRemoved = () => {
@ -410,7 +410,7 @@ export function traverseChildren(
export function traverseNode( export function traverseNode(
node: RootNode | TemplateChildNode, node: RootNode | TemplateChildNode,
context: TransformContext context: TransformContext,
) { ) {
context.currentNode = node context.currentNode = node
// apply transform plugins // apply transform plugins
@ -473,7 +473,7 @@ export function traverseNode(
export function createStructuralDirectiveTransform( export function createStructuralDirectiveTransform(
name: string | RegExp, name: string | RegExp,
fn: StructuralDirectiveTransform fn: StructuralDirectiveTransform,
): NodeTransform { ): NodeTransform {
const matches = isString(name) const matches = isString(name)
? (n: string) => n === name ? (n: string) => n === name

View File

@ -1,30 +1,30 @@
import { import {
type CallExpression,
type ComponentNode,
ConstantTypes, ConstantTypes,
RootNode,
NodeTypes,
TemplateChildNode,
SimpleExpressionNode,
ElementTypes, ElementTypes,
PlainElementNode, type JSChildNode,
ComponentNode, NodeTypes,
TemplateNode, type ParentNode,
VNodeCall, type PlainElementNode,
ParentNode, type RootNode,
JSChildNode, type SimpleExpressionNode,
CallExpression, type TemplateChildNode,
type TemplateNode,
type VNodeCall,
createArrayExpression, createArrayExpression,
getVNodeBlockHelper, getVNodeBlockHelper,
getVNodeHelper getVNodeHelper,
} from '../ast' } from '../ast'
import { TransformContext } from '../transform' import type { TransformContext } from '../transform'
import { PatchFlags, isString, isSymbol, isArray } from '@vue/shared' import { PatchFlags, isArray, isString, isSymbol } from '@vue/shared'
import { isSlotOutlet } from '../utils' import { isSlotOutlet } from '../utils'
import { import {
OPEN_BLOCK,
GUARD_REACTIVE_PROPS, GUARD_REACTIVE_PROPS,
NORMALIZE_CLASS, NORMALIZE_CLASS,
NORMALIZE_PROPS, NORMALIZE_PROPS,
NORMALIZE_STYLE NORMALIZE_STYLE,
OPEN_BLOCK,
} from '../runtimeHelpers' } from '../runtimeHelpers'
export function hoistStatic(root: RootNode, context: TransformContext) { export function hoistStatic(root: RootNode, context: TransformContext) {
@ -33,13 +33,13 @@ export function hoistStatic(root: RootNode, context: TransformContext) {
context, context,
// Root node is unfortunately non-hoistable due to potential parent // Root node is unfortunately non-hoistable due to potential parent
// fallthrough attributes. // fallthrough attributes.
isSingleElementRoot(root, root.children[0]) isSingleElementRoot(root, root.children[0]),
) )
} }
export function isSingleElementRoot( export function isSingleElementRoot(
root: RootNode, root: RootNode,
child: TemplateChildNode child: TemplateChildNode,
): child is PlainElementNode | ComponentNode | TemplateNode { ): child is PlainElementNode | ComponentNode | TemplateNode {
const { children } = root const { children } = root
return ( return (
@ -52,7 +52,7 @@ export function isSingleElementRoot(
function walk( function walk(
node: ParentNode, node: ParentNode,
context: TransformContext, context: TransformContext,
doNotHoistNode: boolean = false doNotHoistNode: boolean = false,
) { ) {
const { children } = node const { children } = node
const originalCount = children.length const originalCount = children.length
@ -120,7 +120,7 @@ function walk(
walk( walk(
child.branches[i], child.branches[i],
context, context,
child.branches[i].children.length === 1 child.branches[i].children.length === 1,
) )
} }
} }
@ -141,7 +141,7 @@ function walk(
isArray(node.codegenNode.children) isArray(node.codegenNode.children)
) { ) {
const hoisted = context.hoist( const hoisted = context.hoist(
createArrayExpression(node.codegenNode.children) createArrayExpression(node.codegenNode.children),
) )
// #6978, #7138, #7114 // #6978, #7138, #7114
// a hoisted children array inside v-for can caused HMR errors since // a hoisted children array inside v-for can caused HMR errors since
@ -155,7 +155,7 @@ function walk(
export function getConstantType( export function getConstantType(
node: TemplateChildNode | SimpleExpressionNode, node: TemplateChildNode | SimpleExpressionNode,
context: TransformContext context: TransformContext,
): ConstantTypes { ): ConstantTypes {
const { constantCache } = context const { constantCache } = context
switch (node.type) { switch (node.type) {
@ -244,7 +244,7 @@ export function getConstantType(
context.removeHelper(OPEN_BLOCK) context.removeHelper(OPEN_BLOCK)
context.removeHelper( context.removeHelper(
getVNodeBlockHelper(context.inSSR, codegenNode.isComponent) getVNodeBlockHelper(context.inSSR, codegenNode.isComponent),
) )
codegenNode.isBlock = false codegenNode.isBlock = false
context.helper(getVNodeHelper(context.inSSR, codegenNode.isComponent)) context.helper(getVNodeHelper(context.inSSR, codegenNode.isComponent))
@ -296,12 +296,12 @@ const allowHoistedHelperSet = new Set([
NORMALIZE_CLASS, NORMALIZE_CLASS,
NORMALIZE_STYLE, NORMALIZE_STYLE,
NORMALIZE_PROPS, NORMALIZE_PROPS,
GUARD_REACTIVE_PROPS GUARD_REACTIVE_PROPS,
]) ])
function getConstantTypeOfHelperCall( function getConstantTypeOfHelperCall(
value: CallExpression, value: CallExpression,
context: TransformContext context: TransformContext,
): ConstantTypes { ): ConstantTypes {
if ( if (
value.type === NodeTypes.JS_CALL_EXPRESSION && value.type === NodeTypes.JS_CALL_EXPRESSION &&
@ -321,7 +321,7 @@ function getConstantTypeOfHelperCall(
function getGeneratedPropsConstantType( function getGeneratedPropsConstantType(
node: PlainElementNode, node: PlainElementNode,
context: TransformContext context: TransformContext,
): ConstantTypes { ): ConstantTypes {
let returnType = ConstantTypes.CAN_STRINGIFY let returnType = ConstantTypes.CAN_STRINGIFY
const props = getNodeProps(node) const props = getNodeProps(node)

View File

@ -1,3 +1,3 @@
import { DirectiveTransform } from '../transform' import type { DirectiveTransform } from '../transform'
export const noopDirectiveTransform: DirectiveTransform = () => ({ props: [] }) export const noopDirectiveTransform: DirectiveTransform = () => ({ props: [] })

View File

@ -1,68 +1,68 @@
import { NodeTransform, TransformContext } from '../transform' import type { NodeTransform, TransformContext } from '../transform'
import { import {
NodeTypes, type ArrayExpression,
type CallExpression,
type ComponentNode,
ConstantTypes,
type DirectiveArguments,
type DirectiveNode,
type ElementNode,
ElementTypes, ElementTypes,
CallExpression, type ExpressionNode,
ObjectExpression, type JSChildNode,
ElementNode, NodeTypes,
DirectiveNode, type ObjectExpression,
ExpressionNode, type Property,
ArrayExpression, type TemplateTextChildNode,
createCallExpression, type VNodeCall,
createArrayExpression, createArrayExpression,
createCallExpression,
createObjectExpression,
createObjectProperty, createObjectProperty,
createSimpleExpression, createSimpleExpression,
createObjectExpression,
Property,
ComponentNode,
VNodeCall,
TemplateTextChildNode,
DirectiveArguments,
createVNodeCall, createVNodeCall,
ConstantTypes,
JSChildNode
} from '../ast' } from '../ast'
import { import {
PatchFlags,
PatchFlagNames, PatchFlagNames,
isSymbol, PatchFlags,
isOn,
isObject,
isReservedProp,
capitalize,
camelize, camelize,
isBuiltInDirective capitalize,
isBuiltInDirective,
isObject,
isOn,
isReservedProp,
isSymbol,
} from '@vue/shared' } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors' import { ErrorCodes, createCompilerError } from '../errors'
import { import {
RESOLVE_DIRECTIVE, GUARD_REACTIVE_PROPS,
RESOLVE_COMPONENT, KEEP_ALIVE,
RESOLVE_DYNAMIC_COMPONENT,
MERGE_PROPS, MERGE_PROPS,
NORMALIZE_CLASS, NORMALIZE_CLASS,
NORMALIZE_STYLE,
NORMALIZE_PROPS, NORMALIZE_PROPS,
TO_HANDLERS, NORMALIZE_STYLE,
TELEPORT, RESOLVE_COMPONENT,
KEEP_ALIVE, RESOLVE_DIRECTIVE,
RESOLVE_DYNAMIC_COMPONENT,
SUSPENSE, SUSPENSE,
TELEPORT,
TO_HANDLERS,
UNREF, UNREF,
GUARD_REACTIVE_PROPS
} from '../runtimeHelpers' } from '../runtimeHelpers'
import { import {
toValidAssetId,
findProp, findProp,
isCoreComponent, isCoreComponent,
isStaticArgOf, isStaticArgOf,
isStaticExp isStaticExp,
toValidAssetId,
} from '../utils' } from '../utils'
import { buildSlots } from './vSlot' import { buildSlots } from './vSlot'
import { getConstantType } from './hoistStatic' import { getConstantType } from './hoistStatic'
import { BindingTypes } from '../options' import { BindingTypes } from '../options'
import { import {
checkCompatEnabled,
CompilerDeprecationTypes, CompilerDeprecationTypes,
isCompatEnabled checkCompatEnabled,
isCompatEnabled,
} from '../compat/compatConfig' } from '../compat/compatConfig'
// some directive transforms (e.g. v-model) may return a symbol for runtime // some directive transforms (e.g. v-model) may return a symbol for runtime
@ -125,7 +125,7 @@ export const transformElement: NodeTransform = (node, context) => {
context, context,
undefined, undefined,
isComponent, isComponent,
isDynamicComponent isDynamicComponent,
) )
vnodeProps = propsBuildResult.props vnodeProps = propsBuildResult.props
patchFlag = propsBuildResult.patchFlag patchFlag = propsBuildResult.patchFlag
@ -134,7 +134,7 @@ export const transformElement: NodeTransform = (node, context) => {
vnodeDirectives = vnodeDirectives =
directives && directives.length directives && directives.length
? (createArrayExpression( ? (createArrayExpression(
directives.map(dir => buildDirectiveArgs(dir, context)) directives.map(dir => buildDirectiveArgs(dir, context)),
) as DirectiveArguments) ) as DirectiveArguments)
: undefined : undefined
@ -160,8 +160,8 @@ export const transformElement: NodeTransform = (node, context) => {
createCompilerError(ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN, { createCompilerError(ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN, {
start: node.children[0].loc.start, start: node.children[0].loc.start,
end: node.children[node.children.length - 1].loc.end, end: node.children[node.children.length - 1].loc.end,
source: '' source: '',
}) }),
) )
} }
} }
@ -239,7 +239,7 @@ export const transformElement: NodeTransform = (node, context) => {
!!shouldUseBlock, !!shouldUseBlock,
false /* disableTracking */, false /* disableTracking */,
isComponent, isComponent,
node.loc node.loc,
) )
} }
} }
@ -247,7 +247,7 @@ export const transformElement: NodeTransform = (node, context) => {
export function resolveComponentType( export function resolveComponentType(
node: ComponentNode, node: ComponentNode,
context: TransformContext, context: TransformContext,
ssr = false ssr = false,
) { ) {
let { tag } = node let { tag } = node
@ -260,7 +260,7 @@ export function resolveComponentType(
(__COMPAT__ && (__COMPAT__ &&
isCompatEnabled( isCompatEnabled(
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT, CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
context context,
)) ))
) { ) {
const exp = const exp =
@ -269,7 +269,7 @@ export function resolveComponentType(
: isProp.exp : isProp.exp
if (exp) { if (exp) {
return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [ return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
exp exp,
]) ])
} }
} else if ( } else if (
@ -388,7 +388,7 @@ export function buildProps(
props: ElementNode['props'] = node.props, props: ElementNode['props'] = node.props,
isComponent: boolean, isComponent: boolean,
isDynamicComponent: boolean, isDynamicComponent: boolean,
ssr = false ssr = false,
): { ): {
props: PropsExpression | undefined props: PropsExpression | undefined
directives: DirectiveNode[] directives: DirectiveNode[]
@ -416,7 +416,7 @@ export function buildProps(
const pushMergeArg = (arg?: PropsExpression) => { const pushMergeArg = (arg?: PropsExpression) => {
if (properties.length) { if (properties.length) {
mergeArgs.push( mergeArgs.push(
createObjectExpression(dedupeProperties(properties), elementLoc) createObjectExpression(dedupeProperties(properties), elementLoc),
) )
properties = [] properties = []
} }
@ -496,8 +496,8 @@ export function buildProps(
properties.push( properties.push(
createObjectProperty( createObjectProperty(
createSimpleExpression('ref_for', true), createSimpleExpression('ref_for', true),
createSimpleExpression('true') createSimpleExpression('true'),
) ),
) )
} }
// in inline mode there is no setupState object, so we can't use string // in inline mode there is no setupState object, so we can't use string
@ -514,8 +514,8 @@ export function buildProps(
properties.push( properties.push(
createObjectProperty( createObjectProperty(
createSimpleExpression('ref_key', true), createSimpleExpression('ref_key', true),
createSimpleExpression(value.content, true, value.loc) createSimpleExpression(value.content, true, value.loc),
) ),
) )
} }
} }
@ -528,7 +528,7 @@ export function buildProps(
(__COMPAT__ && (__COMPAT__ &&
isCompatEnabled( isCompatEnabled(
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT, CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
context context,
))) )))
) { ) {
continue continue
@ -539,9 +539,9 @@ export function buildProps(
createSimpleExpression( createSimpleExpression(
value ? value.content : '', value ? value.content : '',
isStatic, isStatic,
value ? value.loc : loc value ? value.loc : loc,
) ),
) ),
) )
} else { } else {
// directives // directives
@ -553,7 +553,7 @@ export function buildProps(
if (name === 'slot') { if (name === 'slot') {
if (!isComponent) { if (!isComponent) {
context.onError( context.onError(
createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, loc) createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, loc),
) )
} }
continue continue
@ -571,7 +571,7 @@ export function buildProps(
(__COMPAT__ && (__COMPAT__ &&
isCompatEnabled( isCompatEnabled(
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT, CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
context context,
)))) ))))
) { ) {
continue continue
@ -595,8 +595,8 @@ export function buildProps(
properties.push( properties.push(
createObjectProperty( createObjectProperty(
createSimpleExpression('ref_for', true), createSimpleExpression('ref_for', true),
createSimpleExpression('true') createSimpleExpression('true'),
) ),
) )
} }
@ -634,7 +634,7 @@ export function buildProps(
checkCompatEnabled( checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER, CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER,
context, context,
loc loc,
) )
} }
} }
@ -642,7 +642,7 @@ export function buildProps(
if ( if (
isCompatEnabled( isCompatEnabled(
CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER, CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER,
context context,
) )
) { ) {
mergeArgs.unshift(exp) mergeArgs.unshift(exp)
@ -657,7 +657,7 @@ export function buildProps(
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
loc, loc,
callee: context.helper(TO_HANDLERS), callee: context.helper(TO_HANDLERS),
arguments: isComponent ? [exp] : [exp, `true`] arguments: isComponent ? [exp] : [exp, `true`],
}) })
} }
} else { } else {
@ -666,8 +666,8 @@ export function buildProps(
isVBind isVBind
? ErrorCodes.X_V_BIND_NO_EXPRESSION ? ErrorCodes.X_V_BIND_NO_EXPRESSION
: ErrorCodes.X_V_ON_NO_EXPRESSION, : ErrorCodes.X_V_ON_NO_EXPRESSION,
loc loc,
) ),
) )
} }
continue continue
@ -716,7 +716,7 @@ export function buildProps(
propsExpression = createCallExpression( propsExpression = createCallExpression(
context.helper(MERGE_PROPS), context.helper(MERGE_PROPS),
mergeArgs, mergeArgs,
elementLoc elementLoc,
) )
} else { } else {
// single v-bind with nothing else - no need for a mergeProps call // single v-bind with nothing else - no need for a mergeProps call
@ -725,7 +725,7 @@ export function buildProps(
} else if (properties.length) { } else if (properties.length) {
propsExpression = createObjectExpression( propsExpression = createObjectExpression(
dedupeProperties(properties), dedupeProperties(properties),
elementLoc elementLoc,
) )
} }
@ -785,7 +785,7 @@ export function buildProps(
if (classProp && !isStaticExp(classProp.value)) { if (classProp && !isStaticExp(classProp.value)) {
classProp.value = createCallExpression( classProp.value = createCallExpression(
context.helper(NORMALIZE_CLASS), context.helper(NORMALIZE_CLASS),
[classProp.value] [classProp.value],
) )
} }
if ( if (
@ -801,14 +801,14 @@ export function buildProps(
) { ) {
styleProp.value = createCallExpression( styleProp.value = createCallExpression(
context.helper(NORMALIZE_STYLE), context.helper(NORMALIZE_STYLE),
[styleProp.value] [styleProp.value],
) )
} }
} else { } else {
// dynamic key binding, wrap with `normalizeProps` // dynamic key binding, wrap with `normalizeProps`
propsExpression = createCallExpression( propsExpression = createCallExpression(
context.helper(NORMALIZE_PROPS), context.helper(NORMALIZE_PROPS),
[propsExpression] [propsExpression],
) )
} }
break break
@ -821,9 +821,9 @@ export function buildProps(
context.helper(NORMALIZE_PROPS), context.helper(NORMALIZE_PROPS),
[ [
createCallExpression(context.helper(GUARD_REACTIVE_PROPS), [ createCallExpression(context.helper(GUARD_REACTIVE_PROPS), [
propsExpression propsExpression,
]) ]),
] ],
) )
break break
} }
@ -834,7 +834,7 @@ export function buildProps(
directives: runtimeDirectives, directives: runtimeDirectives,
patchFlag, patchFlag,
dynamicPropNames, dynamicPropNames,
shouldUseBlock shouldUseBlock,
} }
} }
@ -875,14 +875,14 @@ function mergeAsArray(existing: Property, incoming: Property) {
} else { } else {
existing.value = createArrayExpression( existing.value = createArrayExpression(
[existing.value, incoming.value], [existing.value, incoming.value],
existing.loc existing.loc,
) )
} }
} }
export function buildDirectiveArgs( export function buildDirectiveArgs(
dir: DirectiveNode, dir: DirectiveNode,
context: TransformContext context: TransformContext,
): ArrayExpression { ): ArrayExpression {
const dirArgs: ArrayExpression['elements'] = [] const dirArgs: ArrayExpression['elements'] = []
const runtime = directiveImportMap.get(dir) const runtime = directiveImportMap.get(dir)
@ -922,10 +922,10 @@ export function buildDirectiveArgs(
dirArgs.push( dirArgs.push(
createObjectExpression( createObjectExpression(
dir.modifiers.map(modifier => dir.modifiers.map(modifier =>
createObjectProperty(modifier, trueExpression) createObjectProperty(modifier, trueExpression),
),
loc,
), ),
loc
)
) )
} }
return createArrayExpression(dirArgs, dir.loc) return createArrayExpression(dirArgs, dir.loc)

View File

@ -7,36 +7,36 @@
// - This transform is only applied in non-browser builds because it relies on // - This transform is only applied in non-browser builds because it relies on
// an additional JavaScript parser. In the browser, there is no source-map // an additional JavaScript parser. In the browser, there is no source-map
// support and the code is wrapped in `with (this) { ... }`. // support and the code is wrapped in `with (this) { ... }`.
import { NodeTransform, TransformContext } from '../transform' import type { NodeTransform, TransformContext } from '../transform'
import { import {
type CompoundExpressionNode,
ConstantTypes,
type ExpressionNode,
NodeTypes, NodeTypes,
createSimpleExpression, type SimpleExpressionNode,
ExpressionNode,
SimpleExpressionNode,
CompoundExpressionNode,
createCompoundExpression, createCompoundExpression,
ConstantTypes createSimpleExpression,
} from '../ast' } from '../ast'
import { import {
isInDestructureAssignment, isInDestructureAssignment,
isStaticProperty, isStaticProperty,
isStaticPropertyKey, isStaticPropertyKey,
walkIdentifiers walkIdentifiers,
} from '../babelUtils' } from '../babelUtils'
import { advancePositionWithClone, isSimpleIdentifier } from '../utils' import { advancePositionWithClone, isSimpleIdentifier } from '../utils'
import { import {
isGloballyAllowed, genPropsAccessExp,
makeMap,
hasOwn, hasOwn,
isGloballyAllowed,
isString, isString,
genPropsAccessExp makeMap,
} from '@vue/shared' } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors' import { ErrorCodes, createCompilerError } from '../errors'
import { import type {
Node,
Identifier,
AssignmentExpression, AssignmentExpression,
UpdateExpression Identifier,
Node,
UpdateExpression,
} from '@babel/types' } from '@babel/types'
import { validateBrowserExpression } from '../validateExpression' import { validateBrowserExpression } from '../validateExpression'
import { parse } from '@babel/parser' import { parse } from '@babel/parser'
@ -53,7 +53,7 @@ export const transformExpression: NodeTransform = (node, context) => {
if (node.type === NodeTypes.INTERPOLATION) { if (node.type === NodeTypes.INTERPOLATION) {
node.content = processExpression( node.content = processExpression(
node.content as SimpleExpressionNode, node.content as SimpleExpressionNode,
context context,
) )
} else if (node.type === NodeTypes.ELEMENT) { } else if (node.type === NodeTypes.ELEMENT) {
// handle directives on element // handle directives on element
@ -74,7 +74,7 @@ export const transformExpression: NodeTransform = (node, context) => {
exp, exp,
context, context,
// slot args must be processed as function params // slot args must be processed as function params
dir.name === 'slot' dir.name === 'slot',
) )
} }
if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) { if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {
@ -104,7 +104,7 @@ export function processExpression(
asParams = false, asParams = false,
// v-on handler values may contain multiple statements // v-on handler values may contain multiple statements
asRawStatements = false, asRawStatements = false,
localVars: Record<string, number> = Object.create(context.identifiers) localVars: Record<string, number> = Object.create(context.identifiers),
): ExpressionNode { ): ExpressionNode {
if (__BROWSER__) { if (__BROWSER__) {
if (__DEV__) { if (__DEV__) {
@ -163,8 +163,8 @@ export function processExpression(
context, context,
false, false,
false, false,
knownIds knownIds,
) ),
) )
return `${context.helperString(IS_REF)}(${raw})${ return `${context.helperString(IS_REF)}(${raw})${
context.isTS ? ` //@ts-ignore\n` : `` context.isTS ? ` //@ts-ignore\n` : ``
@ -267,7 +267,7 @@ export function processExpression(
: `(${rawExp})${asParams ? `=>{}` : ``}` : `(${rawExp})${asParams ? `=>{}` : ``}`
try { try {
ast = parse(source, { ast = parse(source, {
plugins: context.expressionPlugins plugins: context.expressionPlugins,
}).program }).program
} catch (e: any) { } catch (e: any) {
context.onError( context.onError(
@ -275,8 +275,8 @@ export function processExpression(
ErrorCodes.X_INVALID_EXPRESSION, ErrorCodes.X_INVALID_EXPRESSION,
node.loc, node.loc,
undefined, undefined,
e.message e.message,
) ),
) )
return node return node
} }
@ -320,7 +320,7 @@ export function processExpression(
}, },
true, // invoke on ALL identifiers true, // invoke on ALL identifiers
parentStack, parentStack,
knownIds knownIds,
) )
// We break up the compound expression into an array of strings and sub // We break up the compound expression into an array of strings and sub
@ -346,10 +346,12 @@ export function processExpression(
{ {
start: advancePositionWithClone(node.loc.start, source, start), start: advancePositionWithClone(node.loc.start, source, start),
end: advancePositionWithClone(node.loc.start, source, end), end: advancePositionWithClone(node.loc.start, source, end),
source source,
}, },
id.isConstant ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT id.isConstant
) ? ConstantTypes.CAN_STRINGIFY
: ConstantTypes.NOT_CONSTANT,
),
) )
if (i === ids.length - 1 && end < rawExp.length) { if (i === ids.length - 1 && end < rawExp.length) {
children.push(rawExp.slice(end)) children.push(rawExp.slice(end))

View File

@ -1,15 +1,15 @@
import { NodeTransform, TransformContext } from '../transform' import type { NodeTransform, TransformContext } from '../transform'
import { import {
type CallExpression,
type ExpressionNode,
NodeTypes, NodeTypes,
CallExpression, type SlotOutletNode,
createCallExpression, createCallExpression,
ExpressionNode, createFunctionExpression,
SlotOutletNode,
createFunctionExpression
} from '../ast' } from '../ast'
import { isSlotOutlet, isStaticArgOf, isStaticExp } from '../utils' import { isSlotOutlet, isStaticArgOf, isStaticExp } from '../utils'
import { buildProps, PropsExpression } from './transformElement' import { type PropsExpression, buildProps } from './transformElement'
import { createCompilerError, ErrorCodes } from '../errors' import { ErrorCodes, createCompilerError } from '../errors'
import { RENDER_SLOT } from '../runtimeHelpers' import { RENDER_SLOT } from '../runtimeHelpers'
import { camelize } from '@vue/shared' import { camelize } from '@vue/shared'
@ -23,7 +23,7 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
slotName, slotName,
'{}', '{}',
'undefined', 'undefined',
'true' 'true',
] ]
let expectedLen = 2 let expectedLen = 2
@ -45,7 +45,7 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
node.codegenNode = createCallExpression( node.codegenNode = createCallExpression(
context.helper(RENDER_SLOT), context.helper(RENDER_SLOT),
slotArgs, slotArgs,
loc loc,
) )
} }
} }
@ -57,7 +57,7 @@ interface SlotOutletProcessResult {
export function processSlotOutlet( export function processSlotOutlet(
node: SlotOutletNode, node: SlotOutletNode,
context: TransformContext context: TransformContext,
): SlotOutletProcessResult { ): SlotOutletProcessResult {
let slotName: string | ExpressionNode = `"default"` let slotName: string | ExpressionNode = `"default"`
let slotProps: PropsExpression | undefined = undefined let slotProps: PropsExpression | undefined = undefined
@ -92,7 +92,7 @@ export function processSlotOutlet(
context, context,
nonNameProps, nonNameProps,
false, false,
false false,
) )
slotProps = props slotProps = props
@ -100,14 +100,14 @@ export function processSlotOutlet(
context.onError( context.onError(
createCompilerError( createCompilerError(
ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET, ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
directives[0].loc directives[0].loc,
) ),
) )
} }
} }
return { return {
slotName, slotName,
slotProps slotProps,
} }
} }

View File

@ -1,16 +1,16 @@
import { NodeTransform } from '../transform' import type { NodeTransform } from '../transform'
import { import {
NodeTypes, type CallExpression,
CompoundExpressionNode, type CompoundExpressionNode,
createCallExpression,
CallExpression,
ElementTypes,
ConstantTypes, ConstantTypes,
createCompoundExpression ElementTypes,
NodeTypes,
createCallExpression,
createCompoundExpression,
} from '../ast' } from '../ast'
import { isText } from '../utils' import { isText } from '../utils'
import { CREATE_TEXT } from '../runtimeHelpers' import { CREATE_TEXT } from '../runtimeHelpers'
import { PatchFlags, PatchFlagNames } from '@vue/shared' import { PatchFlagNames, PatchFlags } from '@vue/shared'
import { getConstantType } from './hoistStatic' import { getConstantType } from './hoistStatic'
// Merge adjacent text nodes and expressions into a single expression // Merge adjacent text nodes and expressions into a single expression
@ -39,7 +39,7 @@ export const transformText: NodeTransform = (node, context) => {
if (!currentContainer) { if (!currentContainer) {
currentContainer = children[i] = createCompoundExpression( currentContainer = children[i] = createCompoundExpression(
[child], [child],
child.loc child.loc,
) )
} }
// merge adjacent text node into current // merge adjacent text node into current
@ -72,7 +72,7 @@ export const transformText: NodeTransform = (node, context) => {
!node.props.find( !node.props.find(
p => p =>
p.type === NodeTypes.DIRECTIVE && p.type === NodeTypes.DIRECTIVE &&
!context.directiveTransforms[p.name] !context.directiveTransforms[p.name],
) && ) &&
// in compat mode, <template> tags with no special directives // in compat mode, <template> tags with no special directives
// will be rendered as a fragment so its children must be // will be rendered as a fragment so its children must be
@ -100,7 +100,7 @@ export const transformText: NodeTransform = (node, context) => {
) { ) {
callArgs.push( callArgs.push(
PatchFlags.TEXT + PatchFlags.TEXT +
(__DEV__ ? ` /* ${PatchFlagNames[PatchFlags.TEXT]} */` : ``) (__DEV__ ? ` /* ${PatchFlagNames[PatchFlags.TEXT]} */` : ``),
) )
} }
children[i] = { children[i] = {
@ -109,8 +109,8 @@ export const transformText: NodeTransform = (node, context) => {
loc: child.loc, loc: child.loc,
codegenNode: createCallExpression( codegenNode: createCallExpression(
context.helper(CREATE_TEXT), context.helper(CREATE_TEXT),
callArgs callArgs,
) ),
} }
} }
} }

View File

@ -1,11 +1,11 @@
import { DirectiveTransform } from '../transform' import type { DirectiveTransform } from '../transform'
import { import {
type ExpressionNode,
NodeTypes,
createObjectProperty, createObjectProperty,
createSimpleExpression, createSimpleExpression,
ExpressionNode,
NodeTypes
} from '../ast' } from '../ast'
import { createCompilerError, ErrorCodes } from '../errors' import { ErrorCodes, createCompilerError } from '../errors'
import { camelize } from '@vue/shared' import { camelize } from '@vue/shared'
import { CAMELIZE } from '../runtimeHelpers' import { CAMELIZE } from '../runtimeHelpers'
import { processExpression } from './transformExpression' import { processExpression } from './transformExpression'
@ -63,12 +63,12 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
) { ) {
context.onError(createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc)) context.onError(createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc))
return { return {
props: [createObjectProperty(arg, createSimpleExpression('', true, loc))] props: [createObjectProperty(arg, createSimpleExpression('', true, loc))],
} }
} }
return { return {
props: [createObjectProperty(arg, exp)] props: [createObjectProperty(arg, exp)],
} }
} }

View File

@ -1,52 +1,52 @@
import { import {
type TransformContext,
createStructuralDirectiveTransform, createStructuralDirectiveTransform,
TransformContext
} from '../transform' } from '../transform'
import { import {
type BlockCodegenNode,
ConstantTypes,
type DirectiveNode,
type ElementNode,
type ExpressionNode,
type ForCodegenNode,
type ForIteratorExpression,
type ForNode,
type ForParseResult,
type ForRenderListExpression,
NodeTypes, NodeTypes,
ExpressionNode, type PlainElementNode,
createSimpleExpression, type RenderSlotCall,
SimpleExpressionNode, type SimpleExpressionNode,
type SlotOutletNode,
type VNodeCall,
createBlockStatement,
createCallExpression, createCallExpression,
createCompoundExpression,
createFunctionExpression, createFunctionExpression,
createObjectExpression, createObjectExpression,
createObjectProperty, createObjectProperty,
ForCodegenNode, createSimpleExpression,
RenderSlotCall,
SlotOutletNode,
ElementNode,
DirectiveNode,
ForNode,
PlainElementNode,
createVNodeCall, createVNodeCall,
VNodeCall,
ForRenderListExpression,
BlockCodegenNode,
ForIteratorExpression,
ConstantTypes,
createBlockStatement,
createCompoundExpression,
getVNodeBlockHelper, getVNodeBlockHelper,
getVNodeHelper, getVNodeHelper,
ForParseResult
} from '../ast' } from '../ast'
import { createCompilerError, ErrorCodes } from '../errors' import { ErrorCodes, createCompilerError } from '../errors'
import { import {
findDir,
findProp, findProp,
isTemplateNode,
isSlotOutlet,
injectProp, injectProp,
findDir isSlotOutlet,
isTemplateNode,
} from '../utils' } from '../utils'
import { import {
RENDER_LIST,
OPEN_BLOCK,
FRAGMENT, FRAGMENT,
IS_MEMO_SAME IS_MEMO_SAME,
OPEN_BLOCK,
RENDER_LIST,
} from '../runtimeHelpers' } from '../runtimeHelpers'
import { processExpression } from './transformExpression' import { processExpression } from './transformExpression'
import { validateBrowserExpression } from '../validateExpression' import { validateBrowserExpression } from '../validateExpression'
import { PatchFlags, PatchFlagNames } from '@vue/shared' import { PatchFlagNames, PatchFlags } from '@vue/shared'
export const transformFor = createStructuralDirectiveTransform( export const transformFor = createStructuralDirectiveTransform(
'for', 'for',
@ -56,7 +56,7 @@ export const transformFor = createStructuralDirectiveTransform(
// create the loop render function expression now, and add the // create the loop render function expression now, and add the
// iterator on exit after all children have been traversed // iterator on exit after all children have been traversed
const renderExp = createCallExpression(helper(RENDER_LIST), [ const renderExp = createCallExpression(helper(RENDER_LIST), [
forNode.source forNode.source,
]) as ForRenderListExpression ]) as ForRenderListExpression
const isTemplate = isTemplateNode(node) const isTemplate = isTemplateNode(node)
const memo = findDir(node, 'memo') const memo = findDir(node, 'memo')
@ -76,13 +76,13 @@ export const transformFor = createStructuralDirectiveTransform(
if (memo) { if (memo) {
memo.exp = processExpression( memo.exp = processExpression(
memo.exp! as SimpleExpressionNode, memo.exp! as SimpleExpressionNode,
context context,
) )
} }
if (keyProperty && keyProp!.type !== NodeTypes.ATTRIBUTE) { if (keyProperty && keyProp!.type !== NodeTypes.ATTRIBUTE) {
keyProperty.value = processExpression( keyProperty.value = processExpression(
keyProperty.value as SimpleExpressionNode, keyProperty.value as SimpleExpressionNode,
context context,
) )
} }
} }
@ -108,7 +108,7 @@ export const transformFor = createStructuralDirectiveTransform(
true /* isBlock */, true /* isBlock */,
!isStableFragment /* disableTracking */, !isStableFragment /* disableTracking */,
false /* isComponent */, false /* isComponent */,
node.loc node.loc,
) as ForCodegenNode ) as ForCodegenNode
return () => { return () => {
@ -125,8 +125,8 @@ export const transformFor = createStructuralDirectiveTransform(
context.onError( context.onError(
createCompilerError( createCompilerError(
ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT, ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT,
key.loc key.loc,
) ),
) )
return true return true
} }
@ -169,7 +169,7 @@ export const transformFor = createStructuralDirectiveTransform(
undefined, undefined,
true, true,
undefined, undefined,
false /* isComponent */ false /* isComponent */,
) )
} else { } else {
// Normal element v-for. Directly use the child's codegenNode // Normal element v-for. Directly use the child's codegenNode
@ -184,12 +184,12 @@ export const transformFor = createStructuralDirectiveTransform(
// switch from block to vnode // switch from block to vnode
removeHelper(OPEN_BLOCK) removeHelper(OPEN_BLOCK)
removeHelper( removeHelper(
getVNodeBlockHelper(context.inSSR, childBlock.isComponent) getVNodeBlockHelper(context.inSSR, childBlock.isComponent),
) )
} else { } else {
// switch from vnode to block // switch from vnode to block
removeHelper( removeHelper(
getVNodeHelper(context.inSSR, childBlock.isComponent) getVNodeHelper(context.inSSR, childBlock.isComponent),
) )
} }
} }
@ -205,8 +205,8 @@ export const transformFor = createStructuralDirectiveTransform(
if (memo) { if (memo) {
const loop = createFunctionExpression( const loop = createFunctionExpression(
createForLoopParams(forNode.parseResult, [ createForLoopParams(forNode.parseResult, [
createSimpleExpression(`_cached`) createSimpleExpression(`_cached`),
]) ]),
) )
loop.body = createBlockStatement([ loop.body = createBlockStatement([
createCompoundExpression([`const _memo = (`, memo.exp!, `)`]), createCompoundExpression([`const _memo = (`, memo.exp!, `)`]),
@ -214,30 +214,30 @@ export const transformFor = createStructuralDirectiveTransform(
`if (_cached`, `if (_cached`,
...(keyExp ? [` && _cached.key === `, keyExp] : []), ...(keyExp ? [` && _cached.key === `, keyExp] : []),
` && ${context.helperString( ` && ${context.helperString(
IS_MEMO_SAME IS_MEMO_SAME,
)}(_cached, _memo)) return _cached` )}(_cached, _memo)) return _cached`,
]), ]),
createCompoundExpression([`const _item = `, childBlock as any]), createCompoundExpression([`const _item = `, childBlock as any]),
createSimpleExpression(`_item.memo = _memo`), createSimpleExpression(`_item.memo = _memo`),
createSimpleExpression(`return _item`) createSimpleExpression(`return _item`),
]) ])
renderExp.arguments.push( renderExp.arguments.push(
loop as ForIteratorExpression, loop as ForIteratorExpression,
createSimpleExpression(`_cache`), createSimpleExpression(`_cache`),
createSimpleExpression(String(context.cached++)) createSimpleExpression(String(context.cached++)),
) )
} else { } else {
renderExp.arguments.push( renderExp.arguments.push(
createFunctionExpression( createFunctionExpression(
createForLoopParams(forNode.parseResult), createForLoopParams(forNode.parseResult),
childBlock, childBlock,
true /* force newline */ true /* force newline */,
) as ForIteratorExpression ) as ForIteratorExpression,
) )
} }
} }
}) })
} },
) )
// target-agnostic transform used for both Client and SSR // target-agnostic transform used for both Client and SSR
@ -245,11 +245,11 @@ export function processFor(
node: ElementNode, node: ElementNode,
dir: DirectiveNode, dir: DirectiveNode,
context: TransformContext, context: TransformContext,
processCodegen?: (forNode: ForNode) => (() => void) | undefined processCodegen?: (forNode: ForNode) => (() => void) | undefined,
) { ) {
if (!dir.exp) { if (!dir.exp) {
context.onError( context.onError(
createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc) createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc),
) )
return return
} }
@ -258,7 +258,7 @@ export function processFor(
if (!parseResult) { if (!parseResult) {
context.onError( context.onError(
createCompilerError(ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION, dir.loc) createCompilerError(ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION, dir.loc),
) )
return return
} }
@ -276,7 +276,7 @@ export function processFor(
keyAlias: key, keyAlias: key,
objectIndexAlias: index, objectIndexAlias: index,
parseResult, parseResult,
children: isTemplateNode(node) ? node.children : [node] children: isTemplateNode(node) ? node.children : [node],
} }
context.replaceNode(forNode) context.replaceNode(forNode)
@ -306,34 +306,34 @@ export function processFor(
export function finalizeForParseResult( export function finalizeForParseResult(
result: ForParseResult, result: ForParseResult,
context: TransformContext context: TransformContext,
) { ) {
if (result.finalized) return if (result.finalized) return
if (!__BROWSER__ && context.prefixIdentifiers) { if (!__BROWSER__ && context.prefixIdentifiers) {
result.source = processExpression( result.source = processExpression(
result.source as SimpleExpressionNode, result.source as SimpleExpressionNode,
context context,
) )
if (result.key) { if (result.key) {
result.key = processExpression( result.key = processExpression(
result.key as SimpleExpressionNode, result.key as SimpleExpressionNode,
context, context,
true true,
) )
} }
if (result.index) { if (result.index) {
result.index = processExpression( result.index = processExpression(
result.index as SimpleExpressionNode, result.index as SimpleExpressionNode,
context, context,
true true,
) )
} }
if (result.value) { if (result.value) {
result.value = processExpression( result.value = processExpression(
result.value as SimpleExpressionNode, result.value as SimpleExpressionNode,
context, context,
true true,
) )
} }
} }
@ -343,21 +343,21 @@ export function finalizeForParseResult(
validateBrowserExpression( validateBrowserExpression(
result.key as SimpleExpressionNode, result.key as SimpleExpressionNode,
context, context,
true true,
) )
} }
if (result.index) { if (result.index) {
validateBrowserExpression( validateBrowserExpression(
result.index as SimpleExpressionNode, result.index as SimpleExpressionNode,
context, context,
true true,
) )
} }
if (result.value) { if (result.value) {
validateBrowserExpression( validateBrowserExpression(
result.value as SimpleExpressionNode, result.value as SimpleExpressionNode,
context, context,
true true,
) )
} }
} }
@ -366,13 +366,13 @@ export function finalizeForParseResult(
export function createForLoopParams( export function createForLoopParams(
{ value, key, index }: ForParseResult, { value, key, index }: ForParseResult,
memoArgs: ExpressionNode[] = [] memoArgs: ExpressionNode[] = [],
): ExpressionNode[] { ): ExpressionNode[] {
return createParamsList([value, key, index, ...memoArgs]) return createParamsList([value, key, index, ...memoArgs])
} }
function createParamsList( function createParamsList(
args: (ExpressionNode | undefined)[] args: (ExpressionNode | undefined)[],
): ExpressionNode[] { ): ExpressionNode[] {
let i = args.length let i = args.length
while (i--) { while (i--) {

View File

@ -1,37 +1,37 @@
import { import {
type TransformContext,
createStructuralDirectiveTransform, createStructuralDirectiveTransform,
TransformContext, traverseNode,
traverseNode
} from '../transform' } from '../transform'
import { import {
NodeTypes, type AttributeNode,
type BlockCodegenNode,
type CacheExpression,
ConstantTypes,
type DirectiveNode,
type ElementNode,
ElementTypes, ElementTypes,
ElementNode, type IfBranchNode,
DirectiveNode, type IfConditionalExpression,
IfBranchNode, type IfNode,
SimpleExpressionNode, type MemoExpression,
NodeTypes,
type SimpleExpressionNode,
convertToBlock,
createCallExpression, createCallExpression,
createConditionalExpression, createConditionalExpression,
createSimpleExpression,
createObjectProperty,
createObjectExpression, createObjectExpression,
IfConditionalExpression, createObjectProperty,
BlockCodegenNode, createSimpleExpression,
IfNode,
createVNodeCall, createVNodeCall,
AttributeNode,
locStub, locStub,
CacheExpression,
ConstantTypes,
MemoExpression,
convertToBlock
} from '../ast' } from '../ast'
import { createCompilerError, ErrorCodes } from '../errors' import { ErrorCodes, createCompilerError } from '../errors'
import { processExpression } from './transformExpression' import { processExpression } from './transformExpression'
import { validateBrowserExpression } from '../validateExpression' import { validateBrowserExpression } from '../validateExpression'
import { FRAGMENT, CREATE_COMMENT } from '../runtimeHelpers' import { CREATE_COMMENT, FRAGMENT } from '../runtimeHelpers'
import { injectProp, findDir, findProp, getMemoedVNodeCall } from '../utils' import { findDir, findProp, getMemoedVNodeCall, injectProp } from '../utils'
import { PatchFlags, PatchFlagNames } from '@vue/shared' import { PatchFlagNames, PatchFlags } from '@vue/shared'
export const transformIf = createStructuralDirectiveTransform( export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/, /^(if|else|else-if)$/,
@ -57,7 +57,7 @@ export const transformIf = createStructuralDirectiveTransform(
ifNode.codegenNode = createCodegenNodeForBranch( ifNode.codegenNode = createCodegenNodeForBranch(
branch, branch,
key, key,
context context,
) as IfConditionalExpression ) as IfConditionalExpression
} else { } else {
// attach this branch's codegen node to the v-if root. // attach this branch's codegen node to the v-if root.
@ -65,12 +65,12 @@ export const transformIf = createStructuralDirectiveTransform(
parentCondition.alternate = createCodegenNodeForBranch( parentCondition.alternate = createCodegenNodeForBranch(
branch, branch,
key + ifNode.branches.length - 1, key + ifNode.branches.length - 1,
context context,
) )
} }
} }
}) })
} },
) )
// target-agnostic transform used for both Client and SSR // target-agnostic transform used for both Client and SSR
@ -81,8 +81,8 @@ export function processIf(
processCodegen?: ( processCodegen?: (
node: IfNode, node: IfNode,
branch: IfBranchNode, branch: IfBranchNode,
isRoot: boolean isRoot: boolean,
) => (() => void) | undefined ) => (() => void) | undefined,
) { ) {
if ( if (
dir.name !== 'else' && dir.name !== 'else' &&
@ -90,7 +90,7 @@ export function processIf(
) { ) {
const loc = dir.exp ? dir.exp.loc : node.loc const loc = dir.exp ? dir.exp.loc : node.loc
context.onError( context.onError(
createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc) createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc),
) )
dir.exp = createSimpleExpression(`true`, false, loc) dir.exp = createSimpleExpression(`true`, false, loc)
} }
@ -110,7 +110,7 @@ export function processIf(
const ifNode: IfNode = { const ifNode: IfNode = {
type: NodeTypes.IF, type: NodeTypes.IF,
loc: node.loc, loc: node.loc,
branches: [branch] branches: [branch],
} }
context.replaceNode(ifNode) context.replaceNode(ifNode)
if (processCodegen) { if (processCodegen) {
@ -145,7 +145,7 @@ export function processIf(
sibling.branches[sibling.branches.length - 1].condition === undefined sibling.branches[sibling.branches.length - 1].condition === undefined
) { ) {
context.onError( context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc) createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc),
) )
} }
@ -175,8 +175,8 @@ export function processIf(
context.onError( context.onError(
createCompilerError( createCompilerError(
ErrorCodes.X_V_IF_SAME_KEY, ErrorCodes.X_V_IF_SAME_KEY,
branch.userKey!.loc branch.userKey!.loc,
) ),
) )
} }
}) })
@ -195,7 +195,7 @@ export function processIf(
context.currentNode = null context.currentNode = null
} else { } else {
context.onError( context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc) createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc),
) )
} }
break break
@ -211,14 +211,14 @@ function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
condition: dir.name === 'else' ? undefined : dir.exp, condition: dir.name === 'else' ? undefined : dir.exp,
children: isTemplateIf && !findDir(node, 'for') ? node.children : [node], children: isTemplateIf && !findDir(node, 'for') ? node.children : [node],
userKey: findProp(node, `key`), userKey: findProp(node, `key`),
isTemplateIf isTemplateIf,
} }
} }
function createCodegenNodeForBranch( function createCodegenNodeForBranch(
branch: IfBranchNode, branch: IfBranchNode,
keyIndex: number, keyIndex: number,
context: TransformContext context: TransformContext,
): IfConditionalExpression | BlockCodegenNode | MemoExpression { ): IfConditionalExpression | BlockCodegenNode | MemoExpression {
if (branch.condition) { if (branch.condition) {
return createConditionalExpression( return createConditionalExpression(
@ -228,8 +228,8 @@ function createCodegenNodeForBranch(
// closes the current block. // closes the current block.
createCallExpression(context.helper(CREATE_COMMENT), [ createCallExpression(context.helper(CREATE_COMMENT), [
__DEV__ ? '"v-if"' : '""', __DEV__ ? '"v-if"' : '""',
'true' 'true',
]) ]),
) as IfConditionalExpression ) as IfConditionalExpression
} else { } else {
return createChildrenCodegenNode(branch, keyIndex, context) return createChildrenCodegenNode(branch, keyIndex, context)
@ -239,7 +239,7 @@ function createCodegenNodeForBranch(
function createChildrenCodegenNode( function createChildrenCodegenNode(
branch: IfBranchNode, branch: IfBranchNode,
keyIndex: number, keyIndex: number,
context: TransformContext context: TransformContext,
): BlockCodegenNode | MemoExpression { ): BlockCodegenNode | MemoExpression {
const { helper } = context const { helper } = context
const keyProperty = createObjectProperty( const keyProperty = createObjectProperty(
@ -248,8 +248,8 @@ function createChildrenCodegenNode(
`${keyIndex}`, `${keyIndex}`,
false, false,
locStub, locStub,
ConstantTypes.CAN_HOIST ConstantTypes.CAN_HOIST,
) ),
) )
const { children } = branch const { children } = branch
const firstChild = children[0] const firstChild = children[0]
@ -286,7 +286,7 @@ function createChildrenCodegenNode(
true, true,
false, false,
false /* isComponent */, false /* isComponent */,
branch.loc branch.loc,
) )
} }
} else { } else {
@ -306,7 +306,7 @@ function createChildrenCodegenNode(
function isSameKey( function isSameKey(
a: AttributeNode | DirectiveNode | undefined, a: AttributeNode | DirectiveNode | undefined,
b: AttributeNode | DirectiveNode b: AttributeNode | DirectiveNode,
): boolean { ): boolean {
if (!a || a.type !== b.type) { if (!a || a.type !== b.type) {
return false return false
@ -334,7 +334,7 @@ function isSameKey(
} }
function getParentCondition( function getParentCondition(
node: IfConditionalExpression | CacheExpression node: IfConditionalExpression | CacheExpression,
): IfConditionalExpression { ): IfConditionalExpression {
while (true) { while (true) {
if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) { if (node.type === NodeTypes.JS_CONDITIONAL_EXPRESSION) {

View File

@ -1,13 +1,13 @@
import { NodeTransform } from '../transform' import type { NodeTransform } from '../transform'
import { findDir } from '../utils' import { findDir } from '../utils'
import { import {
ElementTypes,
type MemoExpression,
NodeTypes,
type PlainElementNode,
convertToBlock, convertToBlock,
createCallExpression, createCallExpression,
createFunctionExpression, createFunctionExpression,
ElementTypes,
MemoExpression,
NodeTypes,
PlainElementNode
} from '../ast' } from '../ast'
import { WITH_MEMO } from '../runtimeHelpers' import { WITH_MEMO } from '../runtimeHelpers'
@ -33,7 +33,7 @@ export const transformMemo: NodeTransform = (node, context) => {
dir.exp!, dir.exp!,
createFunctionExpression(undefined, codegenNode), createFunctionExpression(undefined, codegenNode),
`_cache`, `_cache`,
String(context.cached++) String(context.cached++),
]) as MemoExpression ]) as MemoExpression
} }
} }

View File

@ -1,20 +1,20 @@
import { DirectiveTransform } from '../transform' import type { DirectiveTransform } from '../transform'
import { import {
createSimpleExpression, ConstantTypes,
createObjectProperty,
createCompoundExpression,
NodeTypes,
Property,
ElementTypes, ElementTypes,
ExpressionNode, type ExpressionNode,
ConstantTypes NodeTypes,
type Property,
createCompoundExpression,
createObjectProperty,
createSimpleExpression,
} from '../ast' } from '../ast'
import { createCompilerError, ErrorCodes } from '../errors' import { ErrorCodes, createCompilerError } from '../errors'
import { import {
hasScopeRef,
isMemberExpression, isMemberExpression,
isSimpleIdentifier, isSimpleIdentifier,
hasScopeRef, isStaticExp,
isStaticExp
} from '../utils' } from '../utils'
import { IS_REF } from '../runtimeHelpers' import { IS_REF } from '../runtimeHelpers'
import { BindingTypes } from '../options' import { BindingTypes } from '../options'
@ -24,7 +24,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
const { exp, arg } = dir const { exp, arg } = dir
if (!exp) { if (!exp) {
context.onError( context.onError(
createCompilerError(ErrorCodes.X_V_MODEL_NO_EXPRESSION, dir.loc) createCompilerError(ErrorCodes.X_V_MODEL_NO_EXPRESSION, dir.loc),
) )
return createTransformProps() return createTransformProps()
} }
@ -60,7 +60,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
(!isMemberExpression(expString, context) && !maybeRef) (!isMemberExpression(expString, context) && !maybeRef)
) { ) {
context.onError( context.onError(
createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION, exp.loc) createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION, exp.loc),
) )
return createTransformProps() return createTransformProps()
} }
@ -72,7 +72,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
context.identifiers[expString] context.identifiers[expString]
) { ) {
context.onError( context.onError(
createCompilerError(ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE, exp.loc) createCompilerError(ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE, exp.loc),
) )
return createTransformProps() return createTransformProps()
} }
@ -92,7 +92,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
assignmentExp = createCompoundExpression([ assignmentExp = createCompoundExpression([
`${eventArg} => ((`, `${eventArg} => ((`,
createSimpleExpression(rawExp, false, exp.loc), createSimpleExpression(rawExp, false, exp.loc),
`).value = $event)` `).value = $event)`,
]) ])
} else { } else {
// v-model used on a potentially ref binding in <script setup> inline mode. // v-model used on a potentially ref binding in <script setup> inline mode.
@ -102,14 +102,14 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
assignmentExp = createCompoundExpression([ assignmentExp = createCompoundExpression([
`${eventArg} => (${context.helperString(IS_REF)}(${rawExp}) ? (`, `${eventArg} => (${context.helperString(IS_REF)}(${rawExp}) ? (`,
createSimpleExpression(rawExp, false, exp.loc), createSimpleExpression(rawExp, false, exp.loc),
`).value = $event : ${altAssignment})` `).value = $event : ${altAssignment})`,
]) ])
} }
} else { } else {
assignmentExp = createCompoundExpression([ assignmentExp = createCompoundExpression([
`${eventArg} => ((`, `${eventArg} => ((`,
exp, exp,
`) = $event)` `) = $event)`,
]) ])
} }
@ -117,7 +117,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
// modelValue: foo // modelValue: foo
createObjectProperty(propName, dir.exp!), createObjectProperty(propName, dir.exp!),
// "onUpdate:modelValue": $event => (foo = $event) // "onUpdate:modelValue": $event => (foo = $event)
createObjectProperty(eventName, assignmentExp) createObjectProperty(eventName, assignmentExp),
] ]
// cache v-model handler if applicable (when it doesn't refer any scope vars) // cache v-model handler if applicable (when it doesn't refer any scope vars)
@ -148,9 +148,9 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
`{ ${modifiers} }`, `{ ${modifiers} }`,
false, false,
dir.loc, dir.loc,
ConstantTypes.CAN_HOIST ConstantTypes.CAN_HOIST,
) ),
) ),
) )
} }

View File

@ -1,16 +1,16 @@
import { DirectiveTransform, DirectiveTransformResult } from '../transform' import type { DirectiveTransform, DirectiveTransformResult } from '../transform'
import { import {
type DirectiveNode,
ElementTypes,
type ExpressionNode,
NodeTypes,
type SimpleExpressionNode,
createCompoundExpression, createCompoundExpression,
createObjectProperty, createObjectProperty,
createSimpleExpression, createSimpleExpression,
DirectiveNode,
ElementTypes,
ExpressionNode,
NodeTypes,
SimpleExpressionNode
} from '../ast' } from '../ast'
import { camelize, toHandlerKey } from '@vue/shared' import { camelize, toHandlerKey } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors' import { ErrorCodes, createCompilerError } from '../errors'
import { processExpression } from './transformExpression' import { processExpression } from './transformExpression'
import { validateBrowserExpression } from '../validateExpression' import { validateBrowserExpression } from '../validateExpression'
import { hasScopeRef, isMemberExpression } from '../utils' import { hasScopeRef, isMemberExpression } from '../utils'
@ -33,7 +33,7 @@ export const transformOn: DirectiveTransform = (
dir, dir,
node, node,
context, context,
augmentor augmentor,
) => { ) => {
const { loc, modifiers, arg } = dir as VOnDirectiveNode const { loc, modifiers, arg } = dir as VOnDirectiveNode
if (!dir.exp && !modifiers.length) { if (!dir.exp && !modifiers.length) {
@ -65,7 +65,7 @@ export const transformOn: DirectiveTransform = (
eventName = createCompoundExpression([ eventName = createCompoundExpression([
`${context.helperString(TO_HANDLER_KEY)}(`, `${context.helperString(TO_HANDLER_KEY)}(`,
arg, arg,
`)` `)`,
]) ])
} }
} else { } else {
@ -95,7 +95,7 @@ export const transformOn: DirectiveTransform = (
exp, exp,
context, context,
false, false,
hasMultipleStatements hasMultipleStatements,
) )
isInlineStatement && context.removeIdentifiers(`$event`) isInlineStatement && context.removeIdentifiers(`$event`)
// with scope analysis, the function is hoistable if it has no reference // with scope analysis, the function is hoistable if it has no reference
@ -134,7 +134,7 @@ export const transformOn: DirectiveTransform = (
exp as SimpleExpressionNode, exp as SimpleExpressionNode,
context, context,
false, false,
hasMultipleStatements hasMultipleStatements,
) )
} }
@ -151,7 +151,7 @@ export const transformOn: DirectiveTransform = (
}(...args)` }(...args)`
} => ${hasMultipleStatements ? `{` : `(`}`, } => ${hasMultipleStatements ? `{` : `(`}`,
exp, exp,
hasMultipleStatements ? `}` : `)` hasMultipleStatements ? `}` : `)`,
]) ])
} }
} }
@ -160,9 +160,9 @@ export const transformOn: DirectiveTransform = (
props: [ props: [
createObjectProperty( createObjectProperty(
eventName, eventName,
exp || createSimpleExpression(`() => {}`, false, loc) exp || createSimpleExpression(`() => {}`, false, loc),
) ),
] ],
} }
// apply extended compiler augmentor // apply extended compiler augmentor

View File

@ -1,6 +1,6 @@
import { NodeTransform } from '../transform' import type { NodeTransform } from '../transform'
import { findDir } from '../utils' import { findDir } from '../utils'
import { ElementNode, ForNode, IfNode, NodeTypes } from '../ast' import { type ElementNode, type ForNode, type IfNode, NodeTypes } from '../ast'
import { SET_BLOCK_TRACKING } from '../runtimeHelpers' import { SET_BLOCK_TRACKING } from '../runtimeHelpers'
const seen = new WeakSet() const seen = new WeakSet()

View File

@ -1,34 +1,34 @@
import { import {
ElementNode, type CallExpression,
ObjectExpression, type ConditionalExpression,
createObjectExpression, type DirectiveNode,
type ElementNode,
ElementTypes,
type ExpressionNode,
type FunctionExpression,
NodeTypes, NodeTypes,
type ObjectExpression,
type Property,
type SlotsExpression,
type SourceLocation,
type TemplateChildNode,
createArrayExpression,
createCallExpression,
createConditionalExpression,
createFunctionExpression,
createObjectExpression,
createObjectProperty, createObjectProperty,
createSimpleExpression, createSimpleExpression,
createFunctionExpression,
DirectiveNode,
ElementTypes,
ExpressionNode,
Property,
TemplateChildNode,
SourceLocation,
createConditionalExpression,
ConditionalExpression,
FunctionExpression,
CallExpression,
createCallExpression,
createArrayExpression,
SlotsExpression
} from '../ast' } from '../ast'
import { TransformContext, NodeTransform } from '../transform' import type { NodeTransform, TransformContext } from '../transform'
import { createCompilerError, ErrorCodes } from '../errors' import { ErrorCodes, createCompilerError } from '../errors'
import { import {
findDir,
isTemplateNode,
assert, assert,
isVSlot, findDir,
hasScopeRef, hasScopeRef,
isStaticExp isStaticExp,
isTemplateNode,
isVSlot,
} from '../utils' } from '../utils'
import { CREATE_SLOTS, RENDER_LIST, WITH_CTX } from '../runtimeHelpers' import { CREATE_SLOTS, RENDER_LIST, WITH_CTX } from '../runtimeHelpers'
import { createForLoopParams, finalizeForParseResult } from './vFor' import { createForLoopParams, finalizeForParseResult } from './vFor'
@ -99,7 +99,7 @@ export type SlotFnBuilder = (
slotProps: ExpressionNode | undefined, slotProps: ExpressionNode | undefined,
vFor: DirectiveNode | undefined, vFor: DirectiveNode | undefined,
slotChildren: TemplateChildNode[], slotChildren: TemplateChildNode[],
loc: SourceLocation loc: SourceLocation,
) => FunctionExpression ) => FunctionExpression
const buildClientSlotFn: SlotFnBuilder = (props, _vForExp, children, loc) => const buildClientSlotFn: SlotFnBuilder = (props, _vForExp, children, loc) =>
@ -108,7 +108,7 @@ const buildClientSlotFn: SlotFnBuilder = (props, _vForExp, children, loc) =>
children, children,
false /* newline */, false /* newline */,
true /* isSlot */, true /* isSlot */,
children.length ? children[0].loc : loc children.length ? children[0].loc : loc,
) )
// Instead of being a DirectiveTransform, v-slot processing is called during // Instead of being a DirectiveTransform, v-slot processing is called during
@ -116,7 +116,7 @@ const buildClientSlotFn: SlotFnBuilder = (props, _vForExp, children, loc) =>
export function buildSlots( export function buildSlots(
node: ElementNode, node: ElementNode,
context: TransformContext, context: TransformContext,
buildSlotFn: SlotFnBuilder = buildClientSlotFn buildSlotFn: SlotFnBuilder = buildClientSlotFn,
): { ): {
slots: SlotsExpression slots: SlotsExpression
hasDynamicSlots: boolean hasDynamicSlots: boolean
@ -147,8 +147,8 @@ export function buildSlots(
slotsProperties.push( slotsProperties.push(
createObjectProperty( createObjectProperty(
arg || createSimpleExpression('default', true), arg || createSimpleExpression('default', true),
buildSlotFn(exp, undefined, children, loc) buildSlotFn(exp, undefined, children, loc),
) ),
) )
} }
@ -178,7 +178,7 @@ export function buildSlots(
if (onComponentSlot) { if (onComponentSlot) {
// already has on-component slot - this is incorrect usage. // already has on-component slot - this is incorrect usage.
context.onError( context.onError(
createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, slotDir.loc) createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, slotDir.loc),
) )
break break
} }
@ -188,7 +188,7 @@ export function buildSlots(
const { const {
arg: slotName = createSimpleExpression(`default`, true), arg: slotName = createSimpleExpression(`default`, true),
exp: slotProps, exp: slotProps,
loc: dirLoc loc: dirLoc,
} = slotDir } = slotDir
// check if name is dynamic. // check if name is dynamic.
@ -211,8 +211,8 @@ export function buildSlots(
createConditionalExpression( createConditionalExpression(
vIf.exp!, vIf.exp!,
buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++), buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++),
defaultFallback defaultFallback,
) ),
) )
} else if ( } else if (
(vElse = findDir(slotElement, /^else(-if)?$/, true /* allowEmpty */)) (vElse = findDir(slotElement, /^else(-if)?$/, true /* allowEmpty */))
@ -246,14 +246,14 @@ export function buildSlots(
buildDynamicSlot( buildDynamicSlot(
slotName, slotName,
slotFunction, slotFunction,
conditionalBranchIndex++ conditionalBranchIndex++,
), ),
defaultFallback defaultFallback,
) )
: buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++) : buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++)
} else { } else {
context.onError( context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, vElse.loc) createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, vElse.loc),
) )
} }
} else if (vFor) { } else if (vFor) {
@ -269,13 +269,16 @@ export function buildSlots(
createFunctionExpression( createFunctionExpression(
createForLoopParams(parseResult), createForLoopParams(parseResult),
buildDynamicSlot(slotName, slotFunction), buildDynamicSlot(slotName, slotFunction),
true /* force newline */ true /* force newline */,
) ),
]) ]),
) )
} else { } else {
context.onError( context.onError(
createCompilerError(ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION, vFor.loc) createCompilerError(
ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
vFor.loc,
),
) )
} }
} else { } else {
@ -285,8 +288,8 @@ export function buildSlots(
context.onError( context.onError(
createCompilerError( createCompilerError(
ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES, ErrorCodes.X_V_SLOT_DUPLICATE_SLOT_NAMES,
dirLoc dirLoc,
) ),
) )
continue continue
} }
@ -302,7 +305,7 @@ export function buildSlots(
if (!onComponentSlot) { if (!onComponentSlot) {
const buildDefaultSlotProperty = ( const buildDefaultSlotProperty = (
props: ExpressionNode | undefined, props: ExpressionNode | undefined,
children: TemplateChildNode[] children: TemplateChildNode[],
) => { ) => {
const fn = buildSlotFn(props, undefined, children, loc) const fn = buildSlotFn(props, undefined, children, loc)
if (__COMPAT__ && context.compatConfig) { if (__COMPAT__ && context.compatConfig) {
@ -326,12 +329,12 @@ export function buildSlots(
context.onError( context.onError(
createCompilerError( createCompilerError(
ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN, ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
implicitDefaultChildren[0].loc implicitDefaultChildren[0].loc,
) ),
) )
} else { } else {
slotsProperties.push( slotsProperties.push(
buildDefaultSlotProperty(undefined, implicitDefaultChildren) buildDefaultSlotProperty(undefined, implicitDefaultChildren),
) )
} }
} }
@ -351,37 +354,37 @@ export function buildSlots(
// 1 = compiled and static = can skip normalization AND diff as optimized // 1 = compiled and static = can skip normalization AND diff as optimized
createSimpleExpression( createSimpleExpression(
slotFlag + (__DEV__ ? ` /* ${slotFlagsText[slotFlag]} */` : ``), slotFlag + (__DEV__ ? ` /* ${slotFlagsText[slotFlag]} */` : ``),
false false,
)
)
), ),
loc ),
),
loc,
) as SlotsExpression ) as SlotsExpression
if (dynamicSlots.length) { if (dynamicSlots.length) {
slots = createCallExpression(context.helper(CREATE_SLOTS), [ slots = createCallExpression(context.helper(CREATE_SLOTS), [
slots, slots,
createArrayExpression(dynamicSlots) createArrayExpression(dynamicSlots),
]) as SlotsExpression ]) as SlotsExpression
} }
return { return {
slots, slots,
hasDynamicSlots hasDynamicSlots,
} }
} }
function buildDynamicSlot( function buildDynamicSlot(
name: ExpressionNode, name: ExpressionNode,
fn: FunctionExpression, fn: FunctionExpression,
index?: number index?: number,
): ObjectExpression { ): ObjectExpression {
const props = [ const props = [
createObjectProperty(`name`, name), createObjectProperty(`name`, name),
createObjectProperty(`fn`, fn) createObjectProperty(`fn`, fn),
] ]
if (index != null) { if (index != null) {
props.push( props.push(
createObjectProperty(`key`, createSimpleExpression(String(index), true)) createObjectProperty(`key`, createSimpleExpression(String(index), true)),
) )
} }
return createObjectExpression(props) return createObjectExpression(props)

View File

@ -1,45 +1,45 @@
import { import {
Position, type BlockCodegenNode,
ElementNode, type CallExpression,
NodeTypes, type DirectiveNode,
CallExpression, type ElementNode,
createCallExpression,
DirectiveNode,
ElementTypes, ElementTypes,
TemplateChildNode, type ExpressionNode,
RootNode, type IfBranchNode,
ObjectExpression, type InterpolationNode,
Property, type JSChildNode,
JSChildNode, type MemoExpression,
NodeTypes,
type ObjectExpression,
type Position,
type Property,
type RenderSlotCall,
type RootNode,
type SimpleExpressionNode,
type SlotOutletNode,
type TemplateChildNode,
type TemplateNode,
type TextNode,
type VNodeCall,
createCallExpression,
createObjectExpression, createObjectExpression,
SlotOutletNode,
TemplateNode,
RenderSlotCall,
ExpressionNode,
IfBranchNode,
TextNode,
InterpolationNode,
VNodeCall,
SimpleExpressionNode,
BlockCodegenNode,
MemoExpression
} from './ast' } from './ast'
import { TransformContext } from './transform' import type { TransformContext } from './transform'
import { import {
MERGE_PROPS,
TELEPORT,
SUSPENSE,
KEEP_ALIVE,
BASE_TRANSITION, BASE_TRANSITION,
TO_HANDLERS,
NORMALIZE_PROPS,
GUARD_REACTIVE_PROPS, GUARD_REACTIVE_PROPS,
WITH_MEMO KEEP_ALIVE,
MERGE_PROPS,
NORMALIZE_PROPS,
SUSPENSE,
TELEPORT,
TO_HANDLERS,
WITH_MEMO,
} from './runtimeHelpers' } from './runtimeHelpers'
import { isString, isObject, NOOP } from '@vue/shared' import { NOOP, isObject, isString } from '@vue/shared'
import { PropsExpression } from './transforms/transformElement' import type { PropsExpression } from './transforms/transformElement'
import { parseExpression } from '@babel/parser' import { parseExpression } from '@babel/parser'
import { Expression } from '@babel/types' import type { Expression } from '@babel/types'
import { unwrapTSNode } from './babelUtils' import { unwrapTSNode } from './babelUtils'
export const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode => export const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
@ -70,7 +70,7 @@ enum MemberExpLexState {
inMemberExp, inMemberExp,
inBrackets, inBrackets,
inParens, inParens,
inString inString,
} }
const validFirstIdentCharRE = /[A-Za-z_$\xA0-\uFFFF]/ const validFirstIdentCharRE = /[A-Za-z_$\xA0-\uFFFF]/
@ -157,7 +157,7 @@ export const isMemberExpressionNode = __BROWSER__
: (path: string, context: TransformContext): boolean => { : (path: string, context: TransformContext): boolean => {
try { try {
let ret: Expression = parseExpression(path, { let ret: Expression = parseExpression(path, {
plugins: context.expressionPlugins plugins: context.expressionPlugins,
}) })
ret = unwrapTSNode(ret) as Expression ret = unwrapTSNode(ret) as Expression
return ( return (
@ -177,16 +177,16 @@ export const isMemberExpression = __BROWSER__
export function advancePositionWithClone( export function advancePositionWithClone(
pos: Position, pos: Position,
source: string, source: string,
numberOfCharacters: number = source.length numberOfCharacters: number = source.length,
): Position { ): Position {
return advancePositionWithMutation( return advancePositionWithMutation(
{ {
offset: pos.offset, offset: pos.offset,
line: pos.line, line: pos.line,
column: pos.column column: pos.column,
}, },
source, source,
numberOfCharacters numberOfCharacters,
) )
} }
@ -195,7 +195,7 @@ export function advancePositionWithClone(
export function advancePositionWithMutation( export function advancePositionWithMutation(
pos: Position, pos: Position,
source: string, source: string,
numberOfCharacters: number = source.length numberOfCharacters: number = source.length,
): Position { ): Position {
let linesCount = 0 let linesCount = 0
let lastNewLinePos = -1 let lastNewLinePos = -1
@ -226,7 +226,7 @@ export function assert(condition: boolean, msg?: string) {
export function findDir( export function findDir(
node: ElementNode, node: ElementNode,
name: string | RegExp, name: string | RegExp,
allowEmpty: boolean = false allowEmpty: boolean = false,
): DirectiveNode | undefined { ): DirectiveNode | undefined {
for (let i = 0; i < node.props.length; i++) { for (let i = 0; i < node.props.length; i++) {
const p = node.props[i] const p = node.props[i]
@ -244,7 +244,7 @@ export function findProp(
node: ElementNode, node: ElementNode,
name: string, name: string,
dynamicOnly: boolean = false, dynamicOnly: boolean = false,
allowEmpty: boolean = false allowEmpty: boolean = false,
): ElementNode['props'][0] | undefined { ): ElementNode['props'][0] | undefined {
for (let i = 0; i < node.props.length; i++) { for (let i = 0; i < node.props.length; i++) {
const p = node.props[i] const p = node.props[i]
@ -265,7 +265,7 @@ export function findProp(
export function isStaticArgOf( export function isStaticArgOf(
arg: DirectiveNode['arg'], arg: DirectiveNode['arg'],
name: string name: string,
): boolean { ): boolean {
return !!(arg && isStaticExp(arg) && arg.content === name) return !!(arg && isStaticExp(arg) && arg.content === name)
} }
@ -277,12 +277,12 @@ export function hasDynamicKeyVBind(node: ElementNode): boolean {
p.name === 'bind' && p.name === 'bind' &&
(!p.arg || // v-bind="obj" (!p.arg || // v-bind="obj"
p.arg.type !== NodeTypes.SIMPLE_EXPRESSION || // v-bind:[_ctx.foo] p.arg.type !== NodeTypes.SIMPLE_EXPRESSION || // v-bind:[_ctx.foo]
!p.arg.isStatic) // v-bind:[foo] !p.arg.isStatic), // v-bind:[foo]
) )
} }
export function isText( export function isText(
node: TemplateChildNode node: TemplateChildNode,
): node is TextNode | InterpolationNode { ): node is TextNode | InterpolationNode {
return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
} }
@ -292,7 +292,7 @@ export function isVSlot(p: ElementNode['props'][0]): p is DirectiveNode {
} }
export function isTemplateNode( export function isTemplateNode(
node: RootNode | TemplateChildNode node: RootNode | TemplateChildNode,
): node is TemplateNode { ): node is TemplateNode {
return ( return (
node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.TEMPLATE node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.TEMPLATE
@ -300,7 +300,7 @@ export function isTemplateNode(
} }
export function isSlotOutlet( export function isSlotOutlet(
node: RootNode | TemplateChildNode node: RootNode | TemplateChildNode,
): node is SlotOutletNode { ): node is SlotOutletNode {
return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT
} }
@ -309,7 +309,7 @@ const propsHelperSet = new Set([NORMALIZE_PROPS, GUARD_REACTIVE_PROPS])
function getUnnormalizedProps( function getUnnormalizedProps(
props: PropsExpression | '{}', props: PropsExpression | '{}',
callPath: CallExpression[] = [] callPath: CallExpression[] = [],
): [PropsExpression | '{}', CallExpression[]] { ): [PropsExpression | '{}', CallExpression[]] {
if ( if (
props && props &&
@ -320,7 +320,7 @@ function getUnnormalizedProps(
if (!isString(callee) && propsHelperSet.has(callee)) { if (!isString(callee) && propsHelperSet.has(callee)) {
return getUnnormalizedProps( return getUnnormalizedProps(
props.arguments[0] as PropsExpression, props.arguments[0] as PropsExpression,
callPath.concat(props) callPath.concat(props),
) )
} }
} }
@ -329,7 +329,7 @@ function getUnnormalizedProps(
export function injectProp( export function injectProp(
node: VNodeCall | RenderSlotCall, node: VNodeCall | RenderSlotCall,
prop: Property, prop: Property,
context: TransformContext context: TransformContext,
) { ) {
let propsWithInjection: ObjectExpression | CallExpression | undefined let propsWithInjection: ObjectExpression | CallExpression | undefined
/** /**
@ -372,7 +372,7 @@ export function injectProp(
// #2366 // #2366
propsWithInjection = createCallExpression(context.helper(MERGE_PROPS), [ propsWithInjection = createCallExpression(context.helper(MERGE_PROPS), [
createObjectExpression([prop]), createObjectExpression([prop]),
props props,
]) ])
} else { } else {
props.arguments.unshift(createObjectExpression([prop])) props.arguments.unshift(createObjectExpression([prop]))
@ -388,7 +388,7 @@ export function injectProp(
// single v-bind with expression, return a merged replacement // single v-bind with expression, return a merged replacement
propsWithInjection = createCallExpression(context.helper(MERGE_PROPS), [ propsWithInjection = createCallExpression(context.helper(MERGE_PROPS), [
createObjectExpression([prop]), createObjectExpression([prop]),
props props,
]) ])
// in the case of nested helper call, e.g. `normalizeProps(guardReactiveProps(props))`, // in the case of nested helper call, e.g. `normalizeProps(guardReactiveProps(props))`,
// it will be rewritten as `normalizeProps(mergeProps({ key: 0 }, props))`, // it will be rewritten as `normalizeProps(mergeProps({ key: 0 }, props))`,
@ -420,7 +420,7 @@ function hasProp(prop: Property, props: ObjectExpression) {
result = props.properties.some( result = props.properties.some(
p => p =>
p.key.type === NodeTypes.SIMPLE_EXPRESSION && p.key.type === NodeTypes.SIMPLE_EXPRESSION &&
p.key.content === propKeyName p.key.content === propKeyName,
) )
} }
return result return result
@ -428,7 +428,7 @@ function hasProp(prop: Property, props: ObjectExpression) {
export function toValidAssetId( export function toValidAssetId(
name: string, name: string,
type: 'component' | 'directive' | 'filter' type: 'component' | 'directive' | 'filter',
): string { ): string {
// see issue#4422, we need adding identifier on validAssetId if variable `name` has specific character // see issue#4422, we need adding identifier on validAssetId if variable `name` has specific character
return `_${type}_${name.replace(/[^\w]/g, (searchValue, replaceValue) => { return `_${type}_${name.replace(/[^\w]/g, (searchValue, replaceValue) => {
@ -439,7 +439,7 @@ export function toValidAssetId(
// Check if a node contains expressions that reference current context scope ids // Check if a node contains expressions that reference current context scope ids
export function hasScopeRef( export function hasScopeRef(
node: TemplateChildNode | IfBranchNode | ExpressionNode | undefined, node: TemplateChildNode | IfBranchNode | ExpressionNode | undefined,
ids: TransformContext['identifiers'] ids: TransformContext['identifiers'],
): boolean { ): boolean {
if (!node || Object.keys(ids).length === 0) { if (!node || Object.keys(ids).length === 0) {
return false return false

View File

@ -1,6 +1,6 @@
import { SimpleExpressionNode } from './ast' import type { SimpleExpressionNode } from './ast'
import { TransformContext } from './transform' import type { TransformContext } from './transform'
import { createCompilerError, ErrorCodes } from './errors' import { ErrorCodes, createCompilerError } from './errors'
// these keywords should not appear inside expressions, but operators like // these keywords should not appear inside expressions, but operators like
// 'typeof', 'instanceof', and 'in' are allowed // 'typeof', 'instanceof', and 'in' are allowed
@ -13,7 +13,7 @@ const prohibitedKeywordRE = new RegExp(
) )
.split(',') .split(',')
.join('\\b|\\b') + .join('\\b|\\b') +
'\\b' '\\b',
) )
// strip strings in expressions // strip strings in expressions
@ -29,7 +29,7 @@ export function validateBrowserExpression(
node: SimpleExpressionNode, node: SimpleExpressionNode,
context: TransformContext, context: TransformContext,
asParams = false, asParams = false,
asRawStatements = false asRawStatements = false,
) { ) {
const exp = node.content const exp = node.content
@ -43,7 +43,7 @@ export function validateBrowserExpression(
new Function( new Function(
asRawStatements asRawStatements
? ` ${exp} ` ? ` ${exp} `
: `return ${asParams ? `(${exp}) => {}` : `(${exp})`}` : `return ${asParams ? `(${exp}) => {}` : `(${exp})`}`,
) )
} catch (e: any) { } catch (e: any) {
let message = e.message let message = e.message
@ -58,8 +58,8 @@ export function validateBrowserExpression(
ErrorCodes.X_INVALID_EXPRESSION, ErrorCodes.X_INVALID_EXPRESSION,
node.loc, node.loc,
undefined, undefined,
message message,
) ),
) )
} }
} }

View File

@ -29,16 +29,16 @@ describe('decodeHtmlBrowser', () => {
// #3001 html tags inside attribute values // #3001 html tags inside attribute values
expect(decodeHtmlBrowser('<strong>Text</strong>', true)).toBe( expect(decodeHtmlBrowser('<strong>Text</strong>', true)).toBe(
'<strong>Text</strong>' '<strong>Text</strong>',
) )
expect(decodeHtmlBrowser('<strong>&amp;</strong>', true)).toBe( expect(decodeHtmlBrowser('<strong>&amp;</strong>', true)).toBe(
'<strong>&</strong>' '<strong>&</strong>',
) )
expect( expect(
decodeHtmlBrowser( decodeHtmlBrowser(
'<strong>&lt;strong&gt;&amp;&lt;/strong&gt;</strong>', '<strong>&lt;strong&gt;&amp;&lt;/strong&gt;</strong>',
true true,
) ),
).toBe('<strong><strong>&</strong></strong>') ).toBe('<strong><strong>&</strong></strong>')
}) })
}) })

View File

@ -1,13 +1,13 @@
import { import {
baseParse as parse, type AttributeNode,
NodeTypes,
ElementNode,
TextNode,
ElementTypes,
InterpolationNode,
AttributeNode,
ConstantTypes, ConstantTypes,
Namespaces type ElementNode,
ElementTypes,
type InterpolationNode,
Namespaces,
NodeTypes,
type TextNode,
baseParse as parse,
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { parserOptions } from '../src/parserOptions' import { parserOptions } from '../src/parserOptions'
@ -16,7 +16,7 @@ describe('DOM parser', () => {
test('textarea handles comments/elements as just text', () => { test('textarea handles comments/elements as just text', () => {
const ast = parse( const ast = parse(
'<textarea>some<div>text</div>and<!--comment--></textarea>', '<textarea>some<div>text</div>and<!--comment--></textarea>',
parserOptions parserOptions,
) )
const element = ast.children[0] as ElementNode const element = ast.children[0] as ElementNode
const text = element.children[0] as TextNode const text = element.children[0] as TextNode
@ -27,8 +27,8 @@ describe('DOM parser', () => {
loc: { loc: {
start: { offset: 10, line: 1, column: 11 }, start: { offset: 10, line: 1, column: 11 },
end: { offset: 46, line: 1, column: 47 }, end: { offset: 46, line: 1, column: 47 },
source: 'some<div>text</div>and<!--comment-->' source: 'some<div>text</div>and<!--comment-->',
} },
}) })
}) })
@ -43,8 +43,8 @@ describe('DOM parser', () => {
loc: { loc: {
start: { offset: 10, line: 1, column: 11 }, start: { offset: 10, line: 1, column: 11 },
end: { offset: 15, line: 1, column: 16 }, end: { offset: 15, line: 1, column: 16 },
source: '&amp;' source: '&amp;',
} },
}) })
}) })
@ -58,16 +58,16 @@ describe('DOM parser', () => {
content: { content: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `foo`, content: `foo`,
isStatic: false isStatic: false,
} },
} },
]) ])
}) })
test('style handles comments/elements as just a text', () => { test('style handles comments/elements as just a text', () => {
const ast = parse( const ast = parse(
'<style>some<div>text</div>and<!--comment--></style>', '<style>some<div>text</div>and<!--comment--></style>',
parserOptions parserOptions,
) )
const element = ast.children[0] as ElementNode const element = ast.children[0] as ElementNode
const text = element.children[0] as TextNode const text = element.children[0] as TextNode
@ -78,8 +78,8 @@ describe('DOM parser', () => {
loc: { loc: {
start: { offset: 7, line: 1, column: 8 }, start: { offset: 7, line: 1, column: 8 },
end: { offset: 43, line: 1, column: 44 }, end: { offset: 43, line: 1, column: 44 },
source: 'some<div>text</div>and<!--comment-->' source: 'some<div>text</div>and<!--comment-->',
} },
}) })
}) })
@ -94,8 +94,8 @@ describe('DOM parser', () => {
loc: { loc: {
start: { offset: 7, line: 1, column: 8 }, start: { offset: 7, line: 1, column: 8 },
end: { offset: 12, line: 1, column: 13 }, end: { offset: 12, line: 1, column: 13 },
source: '&amp;' source: '&amp;',
} },
}) })
}) })
@ -109,8 +109,8 @@ describe('DOM parser', () => {
loc: { loc: {
start: { offset: 14, line: 1, column: 15 }, start: { offset: 14, line: 1, column: 15 },
end: { offset: 23, line: 1, column: 24 }, end: { offset: 23, line: 1, column: 24 },
source: 'some text' source: 'some text',
} },
}) })
}) })
@ -120,21 +120,21 @@ describe('DOM parser', () => {
expect((ast.children[0] as ElementNode).children).toMatchObject([ expect((ast.children[0] as ElementNode).children).toMatchObject([
{ {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: ` \na ` content: ` \na `,
}, },
{ {
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,
children: [ children: [
{ {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: `foo \n bar` content: `foo \n bar`,
} },
] ],
}, },
{ {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: ` \n c` content: ` \n c`,
} },
]) ])
}) })
@ -145,7 +145,7 @@ describe('DOM parser', () => {
expect((ast.children[0] as ElementNode).children).toMatchObject([ expect((ast.children[0] as ElementNode).children).toMatchObject([
{ {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: `hello` content: `hello`,
}, },
{ {
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,
@ -153,10 +153,10 @@ describe('DOM parser', () => {
{ {
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
// should not remove the leading newline for nested elements // should not remove the leading newline for nested elements
content: `\nbye` content: `\nbye`,
} },
] ],
} },
]) ])
}) })
@ -166,7 +166,7 @@ describe('DOM parser', () => {
const ast = parse(`foo&nbsp;&nbsp;bar`, parserOptions) const ast = parse(`foo&nbsp;&nbsp;bar`, parserOptions)
expect(ast.children[0]).toMatchObject({ expect(ast.children[0]).toMatchObject({
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
content: `foo${nbsp}${nbsp}bar` content: `foo${nbsp}${nbsp}bar`,
}) })
}) })
@ -181,8 +181,8 @@ describe('DOM parser', () => {
loc: { loc: {
start: { offset: 0, line: 1, column: 1 }, start: { offset: 0, line: 1, column: 1 },
end: { offset: 11, line: 1, column: 12 }, end: { offset: 11, line: 1, column: 12 },
source: '&ampersand;' source: '&ampersand;',
} },
}) })
}) })
@ -190,7 +190,7 @@ describe('DOM parser', () => {
test('HTML entities compatibility in attribute', () => { test('HTML entities compatibility in attribute', () => {
const ast = parse( const ast = parse(
'<div a="&ampersand;" b="&amp;ersand;" c="&amp!"></div>', '<div a="&ampersand;" b="&amp;ersand;" c="&amp!"></div>',
parserOptions parserOptions,
) )
const element = ast.children[0] as ElementNode const element = ast.children[0] as ElementNode
const text1 = (element.props[0] as AttributeNode).value const text1 = (element.props[0] as AttributeNode).value
@ -203,8 +203,8 @@ describe('DOM parser', () => {
loc: { loc: {
start: { offset: 7, line: 1, column: 8 }, start: { offset: 7, line: 1, column: 8 },
end: { offset: 20, line: 1, column: 21 }, end: { offset: 20, line: 1, column: 21 },
source: '"&ampersand;"' source: '"&ampersand;"',
} },
}) })
expect(text2).toStrictEqual({ expect(text2).toStrictEqual({
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
@ -212,8 +212,8 @@ describe('DOM parser', () => {
loc: { loc: {
start: { offset: 23, line: 1, column: 24 }, start: { offset: 23, line: 1, column: 24 },
end: { offset: 37, line: 1, column: 38 }, end: { offset: 37, line: 1, column: 38 },
source: '"&amp;ersand;"' source: '"&amp;ersand;"',
} },
}) })
expect(text3).toStrictEqual({ expect(text3).toStrictEqual({
type: NodeTypes.TEXT, type: NodeTypes.TEXT,
@ -221,8 +221,8 @@ describe('DOM parser', () => {
loc: { loc: {
start: { offset: 40, line: 1, column: 41 }, start: { offset: 40, line: 1, column: 41 },
end: { offset: 47, line: 1, column: 48 }, end: { offset: 47, line: 1, column: 48 },
source: '"&amp!"' source: '"&amp!"',
} },
}) })
}) })
@ -236,8 +236,8 @@ describe('DOM parser', () => {
loc: { loc: {
start: { offset: 0, line: 1, column: 1 }, start: { offset: 0, line: 1, column: 1 },
end: { offset: 6, line: 1, column: 7 }, end: { offset: 6, line: 1, column: 7 },
source: '&#x86;' source: '&#x86;',
} },
}) })
}) })
}) })
@ -258,14 +258,14 @@ describe('DOM parser', () => {
loc: { loc: {
start: { offset: 8, line: 1, column: 9 }, start: { offset: 8, line: 1, column: 9 },
end: { offset: 16, line: 1, column: 17 }, end: { offset: 16, line: 1, column: 17 },
source: 'a &lt; b' source: 'a &lt; b',
} },
}, },
loc: { loc: {
start: { offset: 5, line: 1, column: 6 }, start: { offset: 5, line: 1, column: 6 },
end: { offset: 19, line: 1, column: 20 }, end: { offset: 19, line: 1, column: 20 },
source: '{{ a &lt; b }}' source: '{{ a &lt; b }}',
} },
}) })
}) })
}) })
@ -285,9 +285,9 @@ describe('DOM parser', () => {
loc: { loc: {
start: { offset: 0, line: 1, column: 1 }, start: { offset: 0, line: 1, column: 1 },
end: { offset: 5, line: 1, column: 6 }, end: { offset: 5, line: 1, column: 6 },
source: '<img>' source: '<img>',
}, },
codegenNode: undefined codegenNode: undefined,
}) })
}) })
@ -297,26 +297,26 @@ describe('DOM parser', () => {
expect(ast.children[0]).toMatchObject({ expect(ast.children[0]).toMatchObject({
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,
tag: 'div', tag: 'div',
tagType: ElementTypes.ELEMENT tagType: ElementTypes.ELEMENT,
}) })
expect(ast.children[1]).toMatchObject({ expect(ast.children[1]).toMatchObject({
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,
tag: 'comp', tag: 'comp',
tagType: ElementTypes.COMPONENT tagType: ElementTypes.COMPONENT,
}) })
expect(ast.children[2]).toMatchObject({ expect(ast.children[2]).toMatchObject({
type: NodeTypes.ELEMENT, type: NodeTypes.ELEMENT,
tag: 'Comp', tag: 'Comp',
tagType: ElementTypes.COMPONENT tagType: ElementTypes.COMPONENT,
}) })
}) })
test('Strict end tag detection for textarea.', () => { test('Strict end tag detection for textarea.', () => {
const ast = parse( const ast = parse(
'<textarea>hello</textarea</textarea0></texTArea>', '<textarea>hello</textarea</textarea0></texTArea>',
parserOptions parserOptions,
) )
const element = ast.children[0] as ElementNode const element = ast.children[0] as ElementNode
const text = element.children[0] as TextNode const text = element.children[0] as TextNode
@ -328,8 +328,8 @@ describe('DOM parser', () => {
loc: { loc: {
start: { offset: 10, line: 1, column: 11 }, start: { offset: 10, line: 1, column: 11 },
end: { offset: 37, line: 1, column: 38 }, end: { offset: 37, line: 1, column: 38 },
source: 'hello</textarea</textarea0>' source: 'hello</textarea</textarea0>',
} },
}) })
}) })
}) })
@ -359,7 +359,7 @@ describe('DOM parser', () => {
test('SVG in MATH_ML namespace', () => { test('SVG in MATH_ML namespace', () => {
const ast = parse( const ast = parse(
'<math><annotation-xml><svg></svg></annotation-xml></math>', '<math><annotation-xml><svg></svg></annotation-xml></math>',
parserOptions parserOptions,
) )
const elementMath = ast.children[0] as ElementNode const elementMath = ast.children[0] as ElementNode
const elementAnnotation = elementMath.children[0] as ElementNode const elementAnnotation = elementMath.children[0] as ElementNode
@ -372,7 +372,7 @@ describe('DOM parser', () => {
test('html text/html in MATH_ML namespace', () => { test('html text/html in MATH_ML namespace', () => {
const ast = parse( const ast = parse(
'<math><annotation-xml encoding="text/html"><test/></annotation-xml></math>', '<math><annotation-xml encoding="text/html"><test/></annotation-xml></math>',
parserOptions parserOptions,
) )
const elementMath = ast.children[0] as ElementNode const elementMath = ast.children[0] as ElementNode
@ -386,7 +386,7 @@ describe('DOM parser', () => {
test('html application/xhtml+xml in MATH_ML namespace', () => { test('html application/xhtml+xml in MATH_ML namespace', () => {
const ast = parse( const ast = parse(
'<math><annotation-xml encoding="application/xhtml+xml"><test/></annotation-xml></math>', '<math><annotation-xml encoding="application/xhtml+xml"><test/></annotation-xml></math>',
parserOptions parserOptions,
) )
const elementMath = ast.children[0] as ElementNode const elementMath = ast.children[0] as ElementNode
const elementAnnotation = elementMath.children[0] as ElementNode const elementAnnotation = elementMath.children[0] as ElementNode
@ -399,7 +399,7 @@ describe('DOM parser', () => {
test('mtext malignmark in MATH_ML namespace', () => { test('mtext malignmark in MATH_ML namespace', () => {
const ast = parse( const ast = parse(
'<math><mtext><malignmark/></mtext></math>', '<math><mtext><malignmark/></mtext></math>',
parserOptions parserOptions,
) )
const elementMath = ast.children[0] as ElementNode const elementMath = ast.children[0] as ElementNode
const elementText = elementMath.children[0] as ElementNode const elementText = elementMath.children[0] as ElementNode
@ -422,7 +422,7 @@ describe('DOM parser', () => {
test('foreignObject tag in SVG namespace', () => { test('foreignObject tag in SVG namespace', () => {
const ast = parse( const ast = parse(
'<svg><foreignObject><test/></foreignObject></svg>', '<svg><foreignObject><test/></foreignObject></svg>',
parserOptions parserOptions,
) )
const elementSvg = ast.children[0] as ElementNode const elementSvg = ast.children[0] as ElementNode
const elementForeignObject = elementSvg.children[0] as ElementNode const elementForeignObject = elementSvg.children[0] as ElementNode
@ -473,7 +473,7 @@ describe('DOM parser', () => {
test('root ns', () => { test('root ns', () => {
const ast = parse('<foreignObject><test/></foreignObject>', { const ast = parse('<foreignObject><test/></foreignObject>', {
...parserOptions, ...parserOptions,
ns: Namespaces.SVG ns: Namespaces.SVG,
}) })
const elementForieng = ast.children[0] as ElementNode const elementForieng = ast.children[0] as ElementNode
const element = elementForieng.children[0] as ElementNode const element = elementForieng.children[0] as ElementNode
@ -487,13 +487,13 @@ describe('DOM parser', () => {
// treatment for <script>, <style>, <textarea> etc. // treatment for <script>, <style>, <textarea> etc.
const ast = parse('<script><g/><g/></script>', { const ast = parse('<script><g/><g/></script>', {
...parserOptions, ...parserOptions,
ns: Namespaces.SVG ns: Namespaces.SVG,
}) })
const elementSvg = ast.children[0] as ElementNode const elementSvg = ast.children[0] as ElementNode
// should parse as nodes instead of text // should parse as nodes instead of text
expect(elementSvg.children).toMatchObject([ expect(elementSvg.children).toMatchObject([
{ type: NodeTypes.ELEMENT, tag: 'g' }, { type: NodeTypes.ELEMENT, tag: 'g' },
{ type: NodeTypes.ELEMENT, tag: 'g' } { type: NodeTypes.ELEMENT, tag: 'g' },
]) ])
}) })
}) })

View File

@ -4,7 +4,7 @@ describe('Transition multi children warnings', () => {
function checkWarning( function checkWarning(
template: string, template: string,
shouldWarn: boolean, shouldWarn: boolean,
message = `<Transition> expects exactly one child element or component.` message = `<Transition> expects exactly one child element or component.`,
) { ) {
const spy = vi.fn() const spy = vi.fn()
compile(template.trim(), { compile(template.trim(), {
@ -12,7 +12,7 @@ describe('Transition multi children warnings', () => {
transformHoist: null, transformHoist: null,
onError: err => { onError: err => {
spy(err.message) spy(err.message)
} },
}) })
if (shouldWarn) expect(spy).toHaveBeenCalledWith(message) if (shouldWarn) expect(spy).toHaveBeenCalledWith(message)
@ -27,7 +27,7 @@ describe('Transition multi children warnings', () => {
<div>hey</div> <div>hey</div>
</transition> </transition>
`, `,
true true,
) )
}) })
@ -38,7 +38,7 @@ describe('Transition multi children warnings', () => {
<div v-for="i in items">hey</div> <div v-for="i in items">hey</div>
</transition> </transition>
`, `,
true true,
) )
}) })
@ -50,7 +50,7 @@ describe('Transition multi children warnings', () => {
<div v-else v-for="i in items">hey</div> <div v-else v-for="i in items">hey</div>
</transition> </transition>
`, `,
true true,
) )
}) })
@ -61,7 +61,7 @@ describe('Transition multi children warnings', () => {
<template v-if="ok"></template> <template v-if="ok"></template>
</transition> </transition>
`, `,
true true,
) )
}) })
@ -73,7 +73,7 @@ describe('Transition multi children warnings', () => {
<template v-else></template> <template v-else></template>
</transition> </transition>
`, `,
true true,
) )
}) })
@ -85,7 +85,7 @@ describe('Transition multi children warnings', () => {
<div v-if="other">hey</div> <div v-if="other">hey</div>
</transition> </transition>
`, `,
true true,
) )
}) })
@ -96,7 +96,7 @@ describe('Transition multi children warnings', () => {
<div>hey</div> <div>hey</div>
</transition> </transition>
`, `,
false false,
) )
}) })
@ -107,7 +107,7 @@ describe('Transition multi children warnings', () => {
<div v-if="a">hey</div> <div v-if="a">hey</div>
</transition> </transition>
`, `,
false false,
) )
}) })
@ -120,7 +120,7 @@ describe('Transition multi children warnings', () => {
<div v-else>hey</div> <div v-else>hey</div>
</transition> </transition>
`, `,
false false,
) )
}) })
@ -132,7 +132,7 @@ describe('Transition multi children warnings', () => {
<div v-else>hey</div> <div v-else>hey</div>
</transition> </transition>
`, `,
false false,
) )
}) })
}) })
@ -143,7 +143,7 @@ test('inject persisted when child has v-show', () => {
<transition> <transition>
<div v-show="ok" /> <div v-show="ok" />
</transition> </transition>
`).code `).code,
).toMatchSnapshot() ).toMatchSnapshot()
}) })
@ -161,6 +161,6 @@ test('the v-if/else-if/else branches in Transition should ignore comments', () =
<p v-else/> <p v-else/>
</div> </div>
</transition> </transition>
`).code `).code,
).toMatchSnapshot() ).toMatchSnapshot()
}) })

View File

@ -1,4 +1,4 @@
import { compile, CompilerError } from '../../src' import { type CompilerError, compile } from '../../src'
describe('compiler: ignore side effect tags', () => { describe('compiler: ignore side effect tags', () => {
it('should ignore script', () => { it('should ignore script', () => {
@ -6,7 +6,7 @@ describe('compiler: ignore side effect tags', () => {
const { code } = compile(`<script>console.log(1)</script>`, { const { code } = compile(`<script>console.log(1)</script>`, {
onError(e) { onError(e) {
err = e err = e
} },
}) })
expect(code).not.toMatch('script') expect(code).not.toMatch('script')
expect(err).toBeDefined() expect(err).toBeDefined()
@ -18,7 +18,7 @@ describe('compiler: ignore side effect tags', () => {
const { code } = compile(`<style>h1 { color: red }</style>`, { const { code } = compile(`<style>h1 { color: red }</style>`, {
onError(e) { onError(e) {
err = e err = e
} },
}) })
expect(code).not.toMatch('style') expect(code).not.toMatch('style')
expect(err).toBeDefined() expect(err).toBeDefined()

View File

@ -1,13 +1,13 @@
import { import {
compile,
NodeTypes,
CREATE_STATIC, CREATE_STATIC,
ConstantTypes,
NodeTypes,
compile,
createSimpleExpression, createSimpleExpression,
ConstantTypes
} from '../../src' } from '../../src'
import { import {
StringifyThresholds,
stringifyStatic, stringifyStatic,
StringifyThresholds
} from '../../src/transforms/stringifyStatic' } from '../../src/transforms/stringifyStatic'
describe('stringify static html', () => { describe('stringify static html', () => {
@ -15,7 +15,7 @@ describe('stringify static html', () => {
return compile(template, { return compile(template, {
hoistStatic: true, hoistStatic: true,
prefixIdentifiers: true, prefixIdentifiers: true,
transformHoist: stringifyStatic transformHoist: stringifyStatic,
}) })
} }
@ -25,7 +25,7 @@ describe('stringify static html', () => {
test('should bail on non-eligible static trees', () => { test('should bail on non-eligible static trees', () => {
const { ast } = compileWithStringify( const { ast } = compileWithStringify(
`<div><div><div>hello</div><div>hello</div></div></div>` `<div><div><div>hello</div><div>hello</div></div></div>`,
) )
// should be a normal vnode call // should be a normal vnode call
expect(ast.hoists[0]!.type).toBe(NodeTypes.VNODE_CALL) expect(ast.hoists[0]!.type).toBe(NodeTypes.VNODE_CALL)
@ -35,8 +35,8 @@ describe('stringify static html', () => {
const { ast } = compileWithStringify( const { ast } = compileWithStringify(
`<div><div>${repeat( `<div><div>${repeat(
`<span class="foo"/>`, `<span class="foo"/>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div></div>` )}</div></div>`,
) )
// should be optimized now // should be optimized now
expect(ast.hoists).toMatchObject([ expect(ast.hoists).toMatchObject([
@ -47,15 +47,15 @@ describe('stringify static html', () => {
JSON.stringify( JSON.stringify(
`<div>${repeat( `<div>${repeat(
`<span class="foo"></span>`, `<span class="foo"></span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div>` )}</div>`,
), ),
'1' '1',
] ],
}, // the children array is hoisted as well }, // the children array is hoisted as well
{ {
type: NodeTypes.JS_ARRAY_EXPRESSION type: NodeTypes.JS_ARRAY_EXPRESSION,
} },
]) ])
}) })
@ -63,8 +63,8 @@ describe('stringify static html', () => {
const { ast } = compileWithStringify( const { ast } = compileWithStringify(
`<div><div>${repeat( `<div><div>${repeat(
`<span/>`, `<span/>`,
StringifyThresholds.NODE_COUNT StringifyThresholds.NODE_COUNT,
)}</div></div>` )}</div></div>`,
) )
// should be optimized now // should be optimized now
expect(ast.hoists).toMatchObject([ expect(ast.hoists).toMatchObject([
@ -75,16 +75,16 @@ describe('stringify static html', () => {
JSON.stringify( JSON.stringify(
`<div>${repeat( `<div>${repeat(
`<span></span>`, `<span></span>`,
StringifyThresholds.NODE_COUNT StringifyThresholds.NODE_COUNT,
)}</div>` )}</div>`,
), ),
'1' '1',
] ],
}, },
// the children array is hoisted as well // the children array is hoisted as well
{ {
type: NodeTypes.JS_ARRAY_EXPRESSION type: NodeTypes.JS_ARRAY_EXPRESSION,
} },
]) ])
}) })
@ -92,8 +92,8 @@ describe('stringify static html', () => {
const { ast } = compileWithStringify( const { ast } = compileWithStringify(
`<div>${repeat( `<div>${repeat(
`<span class="foo"/>`, `<span class="foo"/>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div>` )}</div>`,
) )
// should have 6 hoisted nodes (including the entire array), // should have 6 hoisted nodes (including the entire array),
// but 2~5 should be null because they are merged into 1 // but 2~5 should be null because they are merged into 1
@ -105,19 +105,19 @@ describe('stringify static html', () => {
JSON.stringify( JSON.stringify(
repeat( repeat(
`<span class="foo"></span>`, `<span class="foo"></span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)
), ),
'5' ),
] '5',
],
}, },
null, null,
null, null,
null, null,
null, null,
{ {
type: NodeTypes.JS_ARRAY_EXPRESSION type: NodeTypes.JS_ARRAY_EXPRESSION,
} },
]) ])
}) })
@ -125,8 +125,8 @@ describe('stringify static html', () => {
const { ast } = compileWithStringify( const { ast } = compileWithStringify(
`<div><div :style="{ color: 'red' }">${repeat( `<div><div :style="{ color: 'red' }">${repeat(
`<span :class="[{ foo: true }, { bar: true }]">{{ 1 }} + {{ false }}</span>`, `<span :class="[{ foo: true }, { bar: true }]">{{ 1 }} + {{ false }}</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div></div>` )}</div></div>`,
) )
// should be optimized now // should be optimized now
expect(ast.hoists).toMatchObject([ expect(ast.hoists).toMatchObject([
@ -137,15 +137,15 @@ describe('stringify static html', () => {
JSON.stringify( JSON.stringify(
`<div style="color:red;">${repeat( `<div style="color:red;">${repeat(
`<span class="foo bar">1 + false</span>`, `<span class="foo bar">1 + false</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div>` )}</div>`,
), ),
'1' '1',
] ],
}, },
{ {
type: NodeTypes.JS_ARRAY_EXPRESSION type: NodeTypes.JS_ARRAY_EXPRESSION,
} },
]) ])
}) })
@ -154,8 +154,8 @@ describe('stringify static html', () => {
`<div><div>${repeat( `<div><div>${repeat(
`<span :class="'foo' + '&gt;ar'">{{ 1 }} + {{ '<' }}</span>` + `<span :class="'foo' + '&gt;ar'">{{ 1 }} + {{ '<' }}</span>` +
`<span>&amp;</span>`, `<span>&amp;</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div></div>` )}</div></div>`,
) )
// should be optimized now // should be optimized now
expect(ast.hoists).toMatchObject([ expect(ast.hoists).toMatchObject([
@ -166,15 +166,15 @@ describe('stringify static html', () => {
JSON.stringify( JSON.stringify(
`<div>${repeat( `<div>${repeat(
`<span class="foo&gt;ar">1 + &lt;</span>` + `<span>&amp;</span>`, `<span class="foo&gt;ar">1 + &lt;</span>` + `<span>&amp;</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div>` )}</div>`,
), ),
'1' '1',
] ],
}, },
{ {
type: NodeTypes.JS_ARRAY_EXPRESSION type: NodeTypes.JS_ARRAY_EXPRESSION,
} },
]) ])
}) })
@ -182,7 +182,7 @@ describe('stringify static html', () => {
const { ast, code } = compile( const { ast, code } = compile(
`<div><div>${repeat( `<div><div>${repeat(
`<span class="foo">foo</span>`, `<span class="foo">foo</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}<img src="./foo" /></div></div>`, )}<img src="./foo" /></div></div>`,
{ {
hoistStatic: true, hoistStatic: true,
@ -195,7 +195,7 @@ describe('stringify static html', () => {
'_imports_0_', '_imports_0_',
false, false,
node.loc, node.loc,
ConstantTypes.CAN_HOIST ConstantTypes.CAN_HOIST,
) )
node.props[0] = { node.props[0] = {
type: NodeTypes.DIRECTIVE, type: NodeTypes.DIRECTIVE,
@ -203,23 +203,23 @@ describe('stringify static html', () => {
arg: createSimpleExpression('src', true), arg: createSimpleExpression('src', true),
exp, exp,
modifiers: [], modifiers: [],
loc: node.loc loc: node.loc,
} }
} }
} },
] ],
} },
) )
expect(ast.hoists).toMatchObject([ expect(ast.hoists).toMatchObject([
{ {
// the expression and the tree are still hoistable // the expression and the tree are still hoistable
// but should stay NodeTypes.VNODE_CALL // but should stay NodeTypes.VNODE_CALL
// if it's stringified it will be NodeTypes.JS_CALL_EXPRESSION // if it's stringified it will be NodeTypes.JS_CALL_EXPRESSION
type: NodeTypes.VNODE_CALL type: NodeTypes.VNODE_CALL,
}, },
{ {
type: NodeTypes.JS_ARRAY_EXPRESSION type: NodeTypes.JS_ARRAY_EXPRESSION,
} },
]) ])
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -230,7 +230,7 @@ describe('stringify static html', () => {
const { ast, code } = compile( const { ast, code } = compile(
`<div><div>${repeat( `<div><div>${repeat(
`<span class="foo">foo</span>`, `<span class="foo">foo</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}<img src="./foo" /></div></div>`, )}<img src="./foo" /></div></div>`,
{ {
hoistStatic: true, hoistStatic: true,
@ -243,7 +243,7 @@ describe('stringify static html', () => {
'_imports_0_', '_imports_0_',
false, false,
node.loc, node.loc,
ConstantTypes.CAN_STRINGIFY ConstantTypes.CAN_STRINGIFY,
) )
node.props[0] = { node.props[0] = {
type: NodeTypes.DIRECTIVE, type: NodeTypes.DIRECTIVE,
@ -251,22 +251,22 @@ describe('stringify static html', () => {
arg: createSimpleExpression('src', true), arg: createSimpleExpression('src', true),
exp, exp,
modifiers: [], modifiers: [],
loc: node.loc loc: node.loc,
} }
} }
} },
] ],
} },
) )
expect(ast.hoists).toMatchObject([ expect(ast.hoists).toMatchObject([
{ {
// the hoisted node should be NodeTypes.JS_CALL_EXPRESSION // the hoisted node should be NodeTypes.JS_CALL_EXPRESSION
// of `createStaticVNode()` instead of dynamic NodeTypes.VNODE_CALL // of `createStaticVNode()` instead of dynamic NodeTypes.VNODE_CALL
type: NodeTypes.JS_CALL_EXPRESSION type: NodeTypes.JS_CALL_EXPRESSION,
}, },
{ {
type: NodeTypes.JS_ARRAY_EXPRESSION type: NodeTypes.JS_ARRAY_EXPRESSION,
} },
]) ])
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -276,31 +276,31 @@ describe('stringify static html', () => {
const { ast } = compileWithStringify( const { ast } = compileWithStringify(
`<div><div><input indeterminate>${repeat( `<div><div><input indeterminate>${repeat(
`<span class="foo">foo</span>`, `<span class="foo">foo</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div></div>` )}</div></div>`,
) )
expect(ast.hoists).toMatchObject([ expect(ast.hoists).toMatchObject([
{ {
type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
}, },
{ {
type: NodeTypes.JS_ARRAY_EXPRESSION type: NodeTypes.JS_ARRAY_EXPRESSION,
} },
]) ])
const { ast: ast2 } = compileWithStringify( const { ast: ast2 } = compileWithStringify(
`<div><div><input :indeterminate="true">${repeat( `<div><div><input :indeterminate="true">${repeat(
`<span class="foo">foo</span>`, `<span class="foo">foo</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div></div>` )}</div></div>`,
) )
expect(ast2.hoists).toMatchObject([ expect(ast2.hoists).toMatchObject([
{ {
type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
}, },
{ {
type: NodeTypes.JS_ARRAY_EXPRESSION type: NodeTypes.JS_ARRAY_EXPRESSION,
} },
]) ])
}) })
@ -308,31 +308,31 @@ describe('stringify static html', () => {
const { ast } = compileWithStringify( const { ast } = compileWithStringify(
`<div><div>${repeat( `<div><div>${repeat(
`<span class="foo">foo</span>`, `<span class="foo">foo</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}<input indeterminate></div></div>` )}<input indeterminate></div></div>`,
) )
expect(ast.hoists).toMatchObject([ expect(ast.hoists).toMatchObject([
{ {
type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
}, },
{ {
type: NodeTypes.JS_ARRAY_EXPRESSION type: NodeTypes.JS_ARRAY_EXPRESSION,
} },
]) ])
const { ast: ast2 } = compileWithStringify( const { ast: ast2 } = compileWithStringify(
`<div><div>${repeat( `<div><div>${repeat(
`<span class="foo">foo</span>`, `<span class="foo">foo</span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}<input :indeterminate="true"></div></div>` )}<input :indeterminate="true"></div></div>`,
) )
expect(ast2.hoists).toMatchObject([ expect(ast2.hoists).toMatchObject([
{ {
type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
}, },
{ {
type: NodeTypes.JS_ARRAY_EXPRESSION type: NodeTypes.JS_ARRAY_EXPRESSION,
} },
]) ])
}) })
@ -340,16 +340,16 @@ describe('stringify static html', () => {
const { ast } = compileWithStringify( const { ast } = compileWithStringify(
`<table><tbody>${repeat( `<table><tbody>${repeat(
`<tr class="foo"><td>foo</td></tr>`, `<tr class="foo"><td>foo</td></tr>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</tbody></table>` )}</tbody></table>`,
) )
expect(ast.hoists).toMatchObject([ expect(ast.hoists).toMatchObject([
{ {
type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
}, },
{ {
type: NodeTypes.JS_ARRAY_EXPRESSION type: NodeTypes.JS_ARRAY_EXPRESSION,
} },
]) ])
}) })
@ -357,30 +357,30 @@ describe('stringify static html', () => {
const { ast } = compileWithStringify( const { ast } = compileWithStringify(
`<foo>${repeat( `<foo>${repeat(
`<div class="foo"></div>`, `<div class="foo"></div>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</foo>` )}</foo>`,
) )
expect(ast.hoists.length).toBe( expect(ast.hoists.length).toBe(
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
) )
ast.hoists.forEach(node => { ast.hoists.forEach(node => {
expect(node).toMatchObject({ expect(node).toMatchObject({
type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
}) })
}) })
const { ast: ast2 } = compileWithStringify( const { ast: ast2 } = compileWithStringify(
`<foo><template #foo>${repeat( `<foo><template #foo>${repeat(
`<div class="foo"></div>`, `<div class="foo"></div>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</template></foo>` )}</template></foo>`,
) )
expect(ast2.hoists.length).toBe( expect(ast2.hoists.length).toBe(
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
) )
ast2.hoists.forEach(node => { ast2.hoists.forEach(node => {
expect(node).toMatchObject({ expect(node).toMatchObject({
type: NodeTypes.VNODE_CALL // not CALL_EXPRESSION type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
}) })
}) })
}) })
@ -389,8 +389,8 @@ describe('stringify static html', () => {
const { ast } = compileWithStringify( const { ast } = compileWithStringify(
`<div>${repeat( `<div>${repeat(
`<span :title="null"></span>`, `<span :title="null"></span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div>` )}</div>`,
) )
expect(ast.hoists[0]).toMatchObject({ expect(ast.hoists[0]).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
@ -399,11 +399,11 @@ describe('stringify static html', () => {
JSON.stringify( JSON.stringify(
`${repeat( `${repeat(
`<span></span>`, `<span></span>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}` )}`,
), ),
'5' '5',
] ],
}) })
}) })
@ -412,8 +412,8 @@ describe('stringify static html', () => {
const { ast } = compileWithStringify( const { ast } = compileWithStringify(
`<button :disabled="false">enable</button>${repeat( `<button :disabled="false">enable</button>${repeat(
`<div></div>`, `<div></div>`,
StringifyThresholds.NODE_COUNT StringifyThresholds.NODE_COUNT,
)}` )}`,
) )
expect(ast.hoists[0]).toMatchObject({ expect(ast.hoists[0]).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
@ -422,11 +422,11 @@ describe('stringify static html', () => {
JSON.stringify( JSON.stringify(
`<button>enable</button>${repeat( `<button>enable</button>${repeat(
`<div></div>`, `<div></div>`,
StringifyThresholds.NODE_COUNT StringifyThresholds.NODE_COUNT,
)}` )}`,
), ),
'21' '21',
] ],
}) })
}) })
@ -436,8 +436,8 @@ describe('stringify static html', () => {
const { ast } = compileWithStringify( const { ast } = compileWithStringify(
`<div>${svg}${repeat( `<div>${svg}${repeat(
repeated, repeated,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</svg></div>` )}</svg></div>`,
) )
expect(ast.hoists[0]).toMatchObject({ expect(ast.hoists[0]).toMatchObject({
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
@ -446,11 +446,11 @@ describe('stringify static html', () => {
JSON.stringify( JSON.stringify(
`${svg}${repeat( `${svg}${repeat(
repeated, repeated,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</svg>` )}</svg>`,
), ),
'1' '1',
] ],
}) })
}) })

View File

@ -1,10 +1,10 @@
import { import {
type CompilerOptions,
type ElementNode,
NodeTypes,
type VNodeCall,
baseParse as parse, baseParse as parse,
transform, transform,
CompilerOptions,
ElementNode,
NodeTypes,
VNodeCall
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { transformBind } from '../../../compiler-core/src/transforms/vBind' import { transformBind } from '../../../compiler-core/src/transforms/vBind'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement' import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
@ -12,16 +12,16 @@ import { transformStyle } from '../../src/transforms/transformStyle'
function transformWithStyleTransform( function transformWithStyleTransform(
template: string, template: string,
options: CompilerOptions = {} options: CompilerOptions = {},
) { ) {
const ast = parse(template) const ast = parse(template)
transform(ast, { transform(ast, {
nodeTransforms: [transformStyle], nodeTransforms: [transformStyle],
...options ...options,
}) })
return { return {
root: ast, root: ast,
node: ast.children[0] as ElementNode node: ast.children[0] as ElementNode,
} }
} }
@ -34,13 +34,13 @@ describe('compiler: style transform', () => {
arg: { arg: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `style`, content: `style`,
isStatic: true isStatic: true,
}, },
exp: { exp: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `{"color":"red"}`, content: `{"color":"red"}`,
isStatic: false isStatic: false,
} },
}) })
}) })
@ -48,8 +48,8 @@ describe('compiler: style transform', () => {
const { node } = transformWithStyleTransform(`<div style="color: red"/>`, { const { node } = transformWithStyleTransform(`<div style="color: red"/>`, {
nodeTransforms: [transformStyle, transformElement], nodeTransforms: [transformStyle, transformElement],
directiveTransforms: { directiveTransforms: {
bind: transformBind bind: transformBind,
} },
}) })
expect((node.codegenNode as VNodeCall).props).toMatchObject({ expect((node.codegenNode as VNodeCall).props).toMatchObject({
type: NodeTypes.JS_OBJECT_EXPRESSION, type: NodeTypes.JS_OBJECT_EXPRESSION,
@ -58,15 +58,15 @@ describe('compiler: style transform', () => {
key: { key: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `style`, content: `style`,
isStatic: true isStatic: true,
}, },
value: { value: {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `{"color":"red"}`, content: `{"color":"red"}`,
isStatic: false isStatic: false,
} },
} },
] ],
}) })
// should not cause the STYLE patchFlag to be attached // should not cause the STYLE patchFlag to be attached
expect((node.codegenNode as VNodeCall).patchFlag).toBeUndefined() expect((node.codegenNode as VNodeCall).patchFlag).toBeUndefined()

View File

@ -1,14 +1,14 @@
import { import {
type CompilerOptions,
type PlainElementNode,
baseParse as parse, baseParse as parse,
transform, transform,
PlainElementNode,
CompilerOptions
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { transformVHtml } from '../../src/transforms/vHtml' import { transformVHtml } from '../../src/transforms/vHtml'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement' import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
import { import {
createObjectMatcher, createObjectMatcher,
genFlagText genFlagText,
} from '../../../compiler-core/__tests__/testUtils' } from '../../../compiler-core/__tests__/testUtils'
import { PatchFlags } from '@vue/shared' import { PatchFlags } from '@vue/shared'
import { DOMErrorCodes } from '../../src/errors' import { DOMErrorCodes } from '../../src/errors'
@ -18,9 +18,9 @@ function transformWithVHtml(template: string, options: CompilerOptions = {}) {
transform(ast, { transform(ast, {
nodeTransforms: [transformElement], nodeTransforms: [transformElement],
directiveTransforms: { directiveTransforms: {
html: transformVHtml html: transformVHtml,
}, },
...options ...options,
}) })
return ast return ast
} }
@ -31,40 +31,40 @@ describe('compiler: v-html transform', () => {
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({ expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ props: createObjectMatcher({
innerHTML: `[test]` innerHTML: `[test]`,
}), }),
children: undefined, children: undefined,
patchFlag: genFlagText(PatchFlags.PROPS), patchFlag: genFlagText(PatchFlags.PROPS),
dynamicProps: `["innerHTML"]` dynamicProps: `["innerHTML"]`,
}) })
}) })
it('should raise error and ignore children when v-html is present', () => { it('should raise error and ignore children when v-html is present', () => {
const onError = vi.fn() const onError = vi.fn()
const ast = transformWithVHtml(`<div v-html="test">hello</div>`, { const ast = transformWithVHtml(`<div v-html="test">hello</div>`, {
onError onError,
}) })
expect(onError.mock.calls).toMatchObject([ expect(onError.mock.calls).toMatchObject([
[{ code: DOMErrorCodes.X_V_HTML_WITH_CHILDREN }] [{ code: DOMErrorCodes.X_V_HTML_WITH_CHILDREN }],
]) ])
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({ expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ props: createObjectMatcher({
innerHTML: `[test]` innerHTML: `[test]`,
}), }),
children: undefined, // <-- children should have been removed children: undefined, // <-- children should have been removed
patchFlag: genFlagText(PatchFlags.PROPS), patchFlag: genFlagText(PatchFlags.PROPS),
dynamicProps: `["innerHTML"]` dynamicProps: `["innerHTML"]`,
}) })
}) })
it('should raise error if has no expression', () => { it('should raise error if has no expression', () => {
const onError = vi.fn() const onError = vi.fn()
transformWithVHtml(`<div v-html></div>`, { transformWithVHtml(`<div v-html></div>`, {
onError onError,
}) })
expect(onError.mock.calls).toMatchObject([ expect(onError.mock.calls).toMatchObject([
[{ code: DOMErrorCodes.X_V_HTML_NO_EXPRESSION }] [{ code: DOMErrorCodes.X_V_HTML_NO_EXPRESSION }],
]) ])
}) })
}) })

View File

@ -1,8 +1,8 @@
import { import {
type CompilerOptions,
generate,
baseParse as parse, baseParse as parse,
transform, transform,
CompilerOptions,
generate
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { transformModel } from '../../src/transforms/vModel' import { transformModel } from '../../src/transforms/vModel'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement' import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
@ -12,7 +12,7 @@ import {
V_MODEL_DYNAMIC, V_MODEL_DYNAMIC,
V_MODEL_RADIO, V_MODEL_RADIO,
V_MODEL_SELECT, V_MODEL_SELECT,
V_MODEL_TEXT V_MODEL_TEXT,
} from '../../src/runtimeHelpers' } from '../../src/runtimeHelpers'
function transformWithModel(template: string, options: CompilerOptions = {}) { function transformWithModel(template: string, options: CompilerOptions = {}) {
@ -20,9 +20,9 @@ function transformWithModel(template: string, options: CompilerOptions = {}) {
transform(ast, { transform(ast, {
nodeTransforms: [transformElement], nodeTransforms: [transformElement],
directiveTransforms: { directiveTransforms: {
model: transformModel model: transformModel,
}, },
...options ...options,
}) })
return ast return ast
} }
@ -70,7 +70,7 @@ describe('compiler: transform v-model', () => {
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
const root2 = transformWithModel( const root2 = transformWithModel(
'<input v-bind:[key]="val" v-model="model" />' '<input v-bind:[key]="val" v-model="model" />',
) )
expect(root2.helpers).toContain(V_MODEL_DYNAMIC) expect(root2.helpers).toContain(V_MODEL_DYNAMIC)
expect(generate(root2).code).toMatchSnapshot() expect(generate(root2).code).toMatchSnapshot()
@ -98,8 +98,8 @@ describe('compiler: transform v-model', () => {
expect(onError).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith( expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
code: DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT code: DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
}) }),
) )
}) })
@ -110,8 +110,8 @@ describe('compiler: transform v-model', () => {
expect(onError).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith( expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
code: DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT code: DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
}) }),
) )
}) })
@ -119,7 +119,7 @@ describe('compiler: transform v-model', () => {
const onError = vi.fn() const onError = vi.fn()
const root = transformWithModel('<my-input v-model="model" />', { const root = transformWithModel('<my-input v-model="model" />', {
onError, onError,
isCustomElement: tag => tag.startsWith('my-') isCustomElement: tag => tag.startsWith('my-'),
}) })
expect(root.helpers).toContain(V_MODEL_TEXT) expect(root.helpers).toContain(V_MODEL_TEXT)
expect(onError).not.toHaveBeenCalled() expect(onError).not.toHaveBeenCalled()
@ -129,24 +129,24 @@ describe('compiler: transform v-model', () => {
test('should raise error if used file input element', () => { test('should raise error if used file input element', () => {
const onError = vi.fn() const onError = vi.fn()
transformWithModel(`<input type="file" v-model="test"/>`, { transformWithModel(`<input type="file" v-model="test"/>`, {
onError onError,
}) })
expect(onError).toHaveBeenCalledWith( expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
code: DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT code: DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
}) }),
) )
}) })
test('should error on dynamic value binding alongside v-model', () => { test('should error on dynamic value binding alongside v-model', () => {
const onError = vi.fn() const onError = vi.fn()
transformWithModel(`<input v-model="test" :value="test" />`, { transformWithModel(`<input v-model="test" :value="test" />`, {
onError onError,
}) })
expect(onError).toHaveBeenCalledWith( expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
code: DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE code: DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE,
}) }),
) )
}) })
@ -154,7 +154,7 @@ describe('compiler: transform v-model', () => {
test('should NOT error on static value binding alongside v-model', () => { test('should NOT error on static value binding alongside v-model', () => {
const onError = vi.fn() const onError = vi.fn()
transformWithModel(`<input v-model="test" value="test" />`, { transformWithModel(`<input v-model="test" value="test" />`, {
onError onError,
}) })
expect(onError).not.toHaveBeenCalled() expect(onError).not.toHaveBeenCalled()
}) })

View File

@ -1,14 +1,14 @@
import { import {
baseParse as parse, BindingTypes,
CompilerOptions, type CompilerOptions,
ElementNode, type ElementNode,
TO_HANDLER_KEY,
helperNameMap,
NodeTypes, NodeTypes,
ObjectExpression, type ObjectExpression,
TO_HANDLER_KEY,
type VNodeCall,
helperNameMap,
baseParse as parse,
transform, transform,
VNodeCall,
BindingTypes
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { transformOn } from '../../src/transforms/vOn' import { transformOn } from '../../src/transforms/vOn'
import { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from '../../src/runtimeHelpers' import { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from '../../src/runtimeHelpers'
@ -22,31 +22,31 @@ function parseWithVOn(template: string, options: CompilerOptions = {}) {
transform(ast, { transform(ast, {
nodeTransforms: [transformExpression, transformElement], nodeTransforms: [transformExpression, transformElement],
directiveTransforms: { directiveTransforms: {
on: transformOn on: transformOn,
}, },
...options ...options,
}) })
const node = (ast.children[0] as ElementNode).codegenNode as VNodeCall const node = (ast.children[0] as ElementNode).codegenNode as VNodeCall
return { return {
root: ast, root: ast,
node, node,
props: (node.props as ObjectExpression).properties props: (node.props as ObjectExpression).properties,
} }
} }
describe('compiler-dom: transform v-on', () => { describe('compiler-dom: transform v-on', () => {
it('should support multiple modifiers w/ prefixIdentifiers: true', () => { it('should support multiple modifiers w/ prefixIdentifiers: true', () => {
const { const {
props: [prop] props: [prop],
} = parseWithVOn(`<div @click.stop.prevent="test"/>`, { } = parseWithVOn(`<div @click.stop.prevent="test"/>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect(prop).toMatchObject({ expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY, type: NodeTypes.JS_PROPERTY,
value: { value: {
callee: V_ON_WITH_MODIFIERS, callee: V_ON_WITH_MODIFIERS,
arguments: [{ content: '_ctx.test' }, '["stop","prevent"]'] arguments: [{ content: '_ctx.test' }, '["stop","prevent"]'],
} },
}) })
}) })
@ -54,8 +54,8 @@ describe('compiler-dom: transform v-on', () => {
const { props } = parseWithVOn( const { props } = parseWithVOn(
`<div @click.stop="test" @keyup.enter="test" />`, `<div @click.stop="test" @keyup.enter="test" />`,
{ {
prefixIdentifiers: true prefixIdentifiers: true,
} },
) )
const [clickProp, keyUpProp] = props const [clickProp, keyUpProp] = props
@ -64,95 +64,95 @@ describe('compiler-dom: transform v-on', () => {
type: NodeTypes.JS_PROPERTY, type: NodeTypes.JS_PROPERTY,
value: { value: {
callee: V_ON_WITH_MODIFIERS, callee: V_ON_WITH_MODIFIERS,
arguments: [{ content: '_ctx.test' }, '["stop"]'] arguments: [{ content: '_ctx.test' }, '["stop"]'],
} },
}) })
expect(keyUpProp).toMatchObject({ expect(keyUpProp).toMatchObject({
type: NodeTypes.JS_PROPERTY, type: NodeTypes.JS_PROPERTY,
value: { value: {
callee: V_ON_WITH_KEYS, callee: V_ON_WITH_KEYS,
arguments: [{ content: '_ctx.test' }, '["enter"]'] arguments: [{ content: '_ctx.test' }, '["enter"]'],
} },
}) })
}) })
it('should support multiple modifiers and event options w/ prefixIdentifiers: true', () => { it('should support multiple modifiers and event options w/ prefixIdentifiers: true', () => {
const { const {
props: [prop] props: [prop],
} = parseWithVOn(`<div @click.stop.capture.once="test"/>`, { } = parseWithVOn(`<div @click.stop.capture.once="test"/>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect(prop).toMatchObject({ expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY, type: NodeTypes.JS_PROPERTY,
key: { key: {
content: `onClickCaptureOnce` content: `onClickCaptureOnce`,
}, },
value: { value: {
callee: V_ON_WITH_MODIFIERS, callee: V_ON_WITH_MODIFIERS,
arguments: [{ content: '_ctx.test' }, '["stop"]'] arguments: [{ content: '_ctx.test' }, '["stop"]'],
} },
}) })
}) })
it('should wrap keys guard for keyboard events or dynamic events', () => { it('should wrap keys guard for keyboard events or dynamic events', () => {
const { const {
props: [prop] props: [prop],
} = parseWithVOn(`<div @keydown.stop.capture.ctrl.a="test"/>`, { } = parseWithVOn(`<div @keydown.stop.capture.ctrl.a="test"/>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect(prop).toMatchObject({ expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY, type: NodeTypes.JS_PROPERTY,
key: { key: {
content: `onKeydownCapture` content: `onKeydownCapture`,
}, },
value: { value: {
callee: V_ON_WITH_KEYS, callee: V_ON_WITH_KEYS,
arguments: [ arguments: [
{ {
callee: V_ON_WITH_MODIFIERS, callee: V_ON_WITH_MODIFIERS,
arguments: [{ content: '_ctx.test' }, '["stop","ctrl"]'] arguments: [{ content: '_ctx.test' }, '["stop","ctrl"]'],
},
'["a"]',
],
}, },
'["a"]'
]
}
}) })
}) })
it('should not wrap keys guard if no key modifier is present', () => { it('should not wrap keys guard if no key modifier is present', () => {
const { const {
props: [prop] props: [prop],
} = parseWithVOn(`<div @keyup.exact="test"/>`, { } = parseWithVOn(`<div @keyup.exact="test"/>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect(prop).toMatchObject({ expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY, type: NodeTypes.JS_PROPERTY,
value: { value: {
callee: V_ON_WITH_MODIFIERS, callee: V_ON_WITH_MODIFIERS,
arguments: [{ content: '_ctx.test' }, '["exact"]'] arguments: [{ content: '_ctx.test' }, '["exact"]'],
} },
}) })
}) })
it('should wrap keys guard for static key event w/ left/right modifiers', () => { it('should wrap keys guard for static key event w/ left/right modifiers', () => {
const { const {
props: [prop] props: [prop],
} = parseWithVOn(`<div @keyup.left="test"/>`, { } = parseWithVOn(`<div @keyup.left="test"/>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect(prop).toMatchObject({ expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY, type: NodeTypes.JS_PROPERTY,
value: { value: {
callee: V_ON_WITH_KEYS, callee: V_ON_WITH_KEYS,
arguments: [{ content: '_ctx.test' }, '["left"]'] arguments: [{ content: '_ctx.test' }, '["left"]'],
} },
}) })
}) })
it('should wrap both for dynamic key event w/ left/right modifiers', () => { it('should wrap both for dynamic key event w/ left/right modifiers', () => {
const { const {
props: [prop] props: [prop],
} = parseWithVOn(`<div @[e].left="test"/>`, { } = parseWithVOn(`<div @[e].left="test"/>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect(prop).toMatchObject({ expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY, type: NodeTypes.JS_PROPERTY,
@ -161,41 +161,41 @@ describe('compiler-dom: transform v-on', () => {
arguments: [ arguments: [
{ {
callee: V_ON_WITH_MODIFIERS, callee: V_ON_WITH_MODIFIERS,
arguments: [{ content: `_ctx.test` }, `["left"]`] arguments: [{ content: `_ctx.test` }, `["left"]`],
},
'["left"]',
],
}, },
'["left"]'
]
}
}) })
}) })
it('should not wrap normal guard if there is only keys guard', () => { it('should not wrap normal guard if there is only keys guard', () => {
const { const {
props: [prop] props: [prop],
} = parseWithVOn(`<div @keyup.enter="test"/>`, { } = parseWithVOn(`<div @keyup.enter="test"/>`, {
prefixIdentifiers: true prefixIdentifiers: true,
}) })
expect(prop).toMatchObject({ expect(prop).toMatchObject({
type: NodeTypes.JS_PROPERTY, type: NodeTypes.JS_PROPERTY,
value: { value: {
callee: V_ON_WITH_KEYS, callee: V_ON_WITH_KEYS,
arguments: [{ content: '_ctx.test' }, '["enter"]'] arguments: [{ content: '_ctx.test' }, '["enter"]'],
} },
}) })
}) })
test('should transform click.right', () => { test('should transform click.right', () => {
const { const {
props: [prop] props: [prop],
} = parseWithVOn(`<div @click.right="test"/>`) } = parseWithVOn(`<div @click.right="test"/>`)
expect(prop.key).toMatchObject({ expect(prop.key).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `onContextmenu` content: `onContextmenu`,
}) })
// dynamic // dynamic
const { const {
props: [prop2] props: [prop2],
} = parseWithVOn(`<div @[event].right="test"/>`) } = parseWithVOn(`<div @[event].right="test"/>`)
// (_toHandlerKey(event)).toLowerCase() === "onclick" ? "onContextmenu" : (_toHandlerKey(event)) // (_toHandlerKey(event)).toLowerCase() === "onclick" ? "onContextmenu" : (_toHandlerKey(event))
expect(prop2.key).toMatchObject({ expect(prop2.key).toMatchObject({
@ -206,34 +206,34 @@ describe('compiler-dom: transform v-on', () => {
children: [ children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`, `_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: 'event' }, { content: 'event' },
`)` `)`,
] ],
}, },
`) === "onClick" ? "onContextmenu" : (`, `) === "onClick" ? "onContextmenu" : (`,
{ {
children: [ children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`, `_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: 'event' }, { content: 'event' },
`)` `)`,
] ],
}, },
`)` `)`,
] ],
}) })
}) })
test('should transform click.middle', () => { test('should transform click.middle', () => {
const { const {
props: [prop] props: [prop],
} = parseWithVOn(`<div @click.middle="test"/>`) } = parseWithVOn(`<div @click.middle="test"/>`)
expect(prop.key).toMatchObject({ expect(prop.key).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `onMouseup` content: `onMouseup`,
}) })
// dynamic // dynamic
const { const {
props: [prop2] props: [prop2],
} = parseWithVOn(`<div @[event].middle="test"/>`) } = parseWithVOn(`<div @[event].middle="test"/>`)
// (_eventNaming(event)).toLowerCase() === "onclick" ? "onMouseup" : (_eventNaming(event)) // (_eventNaming(event)).toLowerCase() === "onclick" ? "onMouseup" : (_eventNaming(event))
expect(prop2.key).toMatchObject({ expect(prop2.key).toMatchObject({
@ -244,48 +244,48 @@ describe('compiler-dom: transform v-on', () => {
children: [ children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`, `_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: 'event' }, { content: 'event' },
`)` `)`,
] ],
}, },
`) === "onClick" ? "onMouseup" : (`, `) === "onClick" ? "onMouseup" : (`,
{ {
children: [ children: [
`_${helperNameMap[TO_HANDLER_KEY]}(`, `_${helperNameMap[TO_HANDLER_KEY]}(`,
{ content: 'event' }, { content: 'event' },
`)` `)`,
] ],
}, },
`)` `)`,
] ],
}) })
}) })
test('cache handler w/ modifiers', () => { test('cache handler w/ modifiers', () => {
const { const {
root, root,
props: [prop] props: [prop],
} = parseWithVOn(`<div @keyup.enter.capture="foo" />`, { } = parseWithVOn(`<div @keyup.enter.capture="foo" />`, {
prefixIdentifiers: true, prefixIdentifiers: true,
cacheHandlers: true cacheHandlers: true,
}) })
expect(root.cached).toBe(1) expect(root.cached).toBe(1)
// should not treat cached handler as dynamicProp, so it should have no // should not treat cached handler as dynamicProp, so it should have no
// dynamicProps flags and only the hydration flag // dynamicProps flags and only the hydration flag
expect((root as any).children[0].codegenNode.patchFlag).toBe( expect((root as any).children[0].codegenNode.patchFlag).toBe(
genFlagText(PatchFlags.NEED_HYDRATION) genFlagText(PatchFlags.NEED_HYDRATION),
) )
expect(prop).toMatchObject({ expect(prop).toMatchObject({
key: { key: {
content: `onKeyupCapture` content: `onKeyupCapture`,
}, },
value: { value: {
type: NodeTypes.JS_CACHE_EXPRESSION, type: NodeTypes.JS_CACHE_EXPRESSION,
index: 0, index: 0,
value: { value: {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: V_ON_WITH_KEYS callee: V_ON_WITH_KEYS,
} },
} },
}) })
}) })
@ -293,11 +293,11 @@ describe('compiler-dom: transform v-on', () => {
const { node } = parseWithVOn(`<div @keydown.up="foo" />`, { const { node } = parseWithVOn(`<div @keydown.up="foo" />`, {
prefixIdentifiers: true, prefixIdentifiers: true,
bindingMetadata: { bindingMetadata: {
foo: BindingTypes.SETUP_CONST foo: BindingTypes.SETUP_CONST,
}, },
directiveTransforms: { directiveTransforms: {
on: transformOn on: transformOn,
} },
}) })
// should only have hydration flag // should only have hydration flag
expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_HYDRATION)) expect(node.patchFlag).toBe(genFlagText(PatchFlags.NEED_HYDRATION))

View File

@ -1,8 +1,8 @@
import { import {
type CompilerOptions,
generate,
baseParse as parse, baseParse as parse,
transform, transform,
generate,
CompilerOptions
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement' import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
import { transformShow } from '../../src/transforms/vShow' import { transformShow } from '../../src/transforms/vShow'
@ -13,9 +13,9 @@ function transformWithShow(template: string, options: CompilerOptions = {}) {
transform(ast, { transform(ast, {
nodeTransforms: [transformElement], nodeTransforms: [transformElement],
directiveTransforms: { directiveTransforms: {
show: transformShow show: transformShow,
}, },
...options ...options,
}) })
return ast return ast
} }
@ -34,8 +34,8 @@ describe('compiler: v-show transform', () => {
expect(onError).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith( expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
code: DOMErrorCodes.X_V_SHOW_NO_EXPRESSION code: DOMErrorCodes.X_V_SHOW_NO_EXPRESSION,
}) }),
) )
}) })
}) })

View File

@ -1,14 +1,14 @@
import { import {
type CompilerOptions,
type PlainElementNode,
baseParse as parse, baseParse as parse,
transform, transform,
PlainElementNode,
CompilerOptions
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { transformVText } from '../../src/transforms/vText' import { transformVText } from '../../src/transforms/vText'
import { transformElement } from '../../../compiler-core/src/transforms/transformElement' import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
import { import {
createObjectMatcher, createObjectMatcher,
genFlagText genFlagText,
} from '../../../compiler-core/__tests__/testUtils' } from '../../../compiler-core/__tests__/testUtils'
import { PatchFlags } from '@vue/shared' import { PatchFlags } from '@vue/shared'
import { DOMErrorCodes } from '../../src/errors' import { DOMErrorCodes } from '../../src/errors'
@ -18,9 +18,9 @@ function transformWithVText(template: string, options: CompilerOptions = {}) {
transform(ast, { transform(ast, {
nodeTransforms: [transformElement], nodeTransforms: [transformElement],
directiveTransforms: { directiveTransforms: {
text: transformVText text: transformVText,
}, },
...options ...options,
}) })
return ast return ast
} }
@ -32,43 +32,43 @@ describe('compiler: v-text transform', () => {
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ props: createObjectMatcher({
textContent: { textContent: {
arguments: [{ content: 'test' }] arguments: [{ content: 'test' }],
} },
}), }),
children: undefined, children: undefined,
patchFlag: genFlagText(PatchFlags.PROPS), patchFlag: genFlagText(PatchFlags.PROPS),
dynamicProps: `["textContent"]` dynamicProps: `["textContent"]`,
}) })
}) })
it('should raise error and ignore children when v-text is present', () => { it('should raise error and ignore children when v-text is present', () => {
const onError = vi.fn() const onError = vi.fn()
const ast = transformWithVText(`<div v-text="test">hello</div>`, { const ast = transformWithVText(`<div v-text="test">hello</div>`, {
onError onError,
}) })
expect(onError.mock.calls).toMatchObject([ expect(onError.mock.calls).toMatchObject([
[{ code: DOMErrorCodes.X_V_TEXT_WITH_CHILDREN }] [{ code: DOMErrorCodes.X_V_TEXT_WITH_CHILDREN }],
]) ])
expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({ expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
tag: `"div"`, tag: `"div"`,
props: createObjectMatcher({ props: createObjectMatcher({
textContent: { textContent: {
arguments: [{ content: 'test' }] arguments: [{ content: 'test' }],
} },
}), }),
children: undefined, // <-- children should have been removed children: undefined, // <-- children should have been removed
patchFlag: genFlagText(PatchFlags.PROPS), patchFlag: genFlagText(PatchFlags.PROPS),
dynamicProps: `["textContent"]` dynamicProps: `["textContent"]`,
}) })
}) })
it('should raise error if has no expression', () => { it('should raise error if has no expression', () => {
const onError = vi.fn() const onError = vi.fn()
transformWithVText(`<div v-text></div>`, { transformWithVText(`<div v-text></div>`, {
onError onError,
}) })
expect(onError.mock.calls).toMatchObject([ expect(onError.mock.calls).toMatchObject([
[{ code: DOMErrorCodes.X_V_TEXT_NO_EXPRESSION }] [{ code: DOMErrorCodes.X_V_TEXT_NO_EXPRESSION }],
]) ])
}) })
}) })

View File

@ -1,8 +1,8 @@
import { import {
SourceLocation, type CompilerError,
CompilerError, ErrorCodes,
type SourceLocation,
createCompilerError, createCompilerError,
ErrorCodes
} from '@vue/compiler-core' } from '@vue/compiler-core'
export interface DOMCompilerError extends CompilerError { export interface DOMCompilerError extends CompilerError {
@ -11,12 +11,12 @@ export interface DOMCompilerError extends CompilerError {
export function createDOMCompilerError( export function createDOMCompilerError(
code: DOMErrorCodes, code: DOMErrorCodes,
loc?: SourceLocation loc?: SourceLocation,
) { ) {
return createCompilerError( return createCompilerError(
code, code,
loc, loc,
__DEV__ || !__BROWSER__ ? DOMErrorMessages : undefined __DEV__ || !__BROWSER__ ? DOMErrorMessages : undefined,
) as DOMCompilerError ) as DOMCompilerError
} }
@ -32,7 +32,7 @@ export enum DOMErrorCodes {
X_V_SHOW_NO_EXPRESSION, X_V_SHOW_NO_EXPRESSION,
X_TRANSITION_INVALID_CHILDREN, X_TRANSITION_INVALID_CHILDREN,
X_IGNORED_SIDE_EFFECT_TAG, X_IGNORED_SIDE_EFFECT_TAG,
__EXTEND_POINT__ __EXTEND_POINT__,
} }
if (__TEST__) { if (__TEST__) {
@ -43,7 +43,7 @@ if (__TEST__) {
throw new Error( throw new Error(
`DOMErrorCodes need to be updated to ${ `DOMErrorCodes need to be updated to ${
ErrorCodes.__EXTEND_POINT__ + 1 ErrorCodes.__EXTEND_POINT__ + 1
} to match extension point from core ErrorCodes.` } to match extension point from core ErrorCodes.`,
) )
} }
} }
@ -59,5 +59,5 @@ export const DOMErrorMessages: { [code: number]: string } = {
[DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE]: `Unnecessary value binding used alongside v-model. It will interfere with v-model's behavior.`, [DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE]: `Unnecessary value binding used alongside v-model. It will interfere with v-model's behavior.`,
[DOMErrorCodes.X_V_SHOW_NO_EXPRESSION]: `v-show is missing expression.`, [DOMErrorCodes.X_V_SHOW_NO_EXPRESSION]: `v-show is missing expression.`,
[DOMErrorCodes.X_TRANSITION_INVALID_CHILDREN]: `<Transition> expects exactly one child element or component.`, [DOMErrorCodes.X_TRANSITION_INVALID_CHILDREN]: `<Transition> expects exactly one child element or component.`,
[DOMErrorCodes.X_IGNORED_SIDE_EFFECT_TAG]: `Tags with side effect (<script> and <style>) are ignored in client component templates.` [DOMErrorCodes.X_IGNORED_SIDE_EFFECT_TAG]: `Tags with side effect (<script> and <style>) are ignored in client component templates.`,
} }

View File

@ -1,13 +1,13 @@
import { import {
type CodegenResult,
type CompilerOptions,
type DirectiveTransform,
type NodeTransform,
type ParserOptions,
type RootNode,
baseCompile, baseCompile,
baseParse, baseParse,
CompilerOptions,
CodegenResult,
ParserOptions,
RootNode,
noopDirectiveTransform, noopDirectiveTransform,
NodeTransform,
DirectiveTransform
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { parserOptions } from './parserOptions' import { parserOptions } from './parserOptions'
import { transformStyle } from './transforms/transformStyle' import { transformStyle } from './transforms/transformStyle'
@ -25,7 +25,7 @@ export { parserOptions }
export const DOMNodeTransforms: NodeTransform[] = [ export const DOMNodeTransforms: NodeTransform[] = [
transformStyle, transformStyle,
...(__DEV__ ? [transformTransition] : []) ...(__DEV__ ? [transformTransition] : []),
] ]
export const DOMDirectiveTransforms: Record<string, DirectiveTransform> = { export const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {
@ -34,12 +34,12 @@ export const DOMDirectiveTransforms: Record<string, DirectiveTransform> = {
text: transformVText, text: transformVText,
model: transformModel, // override compiler-core model: transformModel, // override compiler-core
on: transformOn, // override compiler-core on: transformOn, // override compiler-core
show: transformShow show: transformShow,
} }
export function compile( export function compile(
src: string | RootNode, src: string | RootNode,
options: CompilerOptions = {} options: CompilerOptions = {},
): CodegenResult { ): CodegenResult {
return baseCompile( return baseCompile(
src, src,
@ -50,15 +50,15 @@ export function compile(
// by compiler-ssr to generate vnode fallback branches // by compiler-ssr to generate vnode fallback branches
ignoreSideEffectTags, ignoreSideEffectTags,
...DOMNodeTransforms, ...DOMNodeTransforms,
...(options.nodeTransforms || []) ...(options.nodeTransforms || []),
], ],
directiveTransforms: extend( directiveTransforms: extend(
{}, {},
DOMDirectiveTransforms, DOMDirectiveTransforms,
options.directiveTransforms || {} options.directiveTransforms || {},
), ),
transformHoist: __BROWSER__ ? null : stringifyStatic transformHoist: __BROWSER__ ? null : stringifyStatic,
}) }),
) )
} }
@ -71,6 +71,6 @@ export { transformStyle } from './transforms/transformStyle'
export { export {
createDOMCompilerError, createDOMCompilerError,
DOMErrorCodes, DOMErrorCodes,
DOMErrorMessages DOMErrorMessages,
} from './errors' } from './errors'
export * from '@vue/compiler-core' export * from '@vue/compiler-core'

View File

@ -1,5 +1,5 @@
import { ParserOptions, NodeTypes, Namespaces } from '@vue/compiler-core' import { Namespaces, NodeTypes, type ParserOptions } from '@vue/compiler-core'
import { isVoidTag, isHTMLTag, isSVGTag, isMathMLTag } from '@vue/shared' import { isHTMLTag, isMathMLTag, isSVGTag, isVoidTag } from '@vue/shared'
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers' import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
import { decodeHtmlBrowser } from './decodeHtmlBrowser' import { decodeHtmlBrowser } from './decodeHtmlBrowser'
@ -33,7 +33,7 @@ export const parserOptions: ParserOptions = {
a.name === 'encoding' && a.name === 'encoding' &&
a.value != null && a.value != null &&
(a.value.content === 'text/html' || (a.value.content === 'text/html' ||
a.value.content === 'application/xhtml+xml') a.value.content === 'application/xhtml+xml'),
) )
) { ) {
ns = Namespaces.HTML ns = Namespaces.HTML
@ -64,5 +64,5 @@ export const parserOptions: ParserOptions = {
} }
} }
return ns return ns
} },
} }

View File

@ -24,5 +24,5 @@ registerRuntimeHelpers({
[V_ON_WITH_KEYS]: `withKeys`, [V_ON_WITH_KEYS]: `withKeys`,
[V_SHOW]: `vShow`, [V_SHOW]: `vShow`,
[TRANSITION]: `Transition`, [TRANSITION]: `Transition`,
[TRANSITION_GROUP]: `TransitionGroup` [TRANSITION_GROUP]: `TransitionGroup`,
}) })

View File

@ -1,12 +1,12 @@
import { import {
NodeTransform, type ComponentNode,
NodeTypes,
ElementTypes, ElementTypes,
ComponentNode, type IfBranchNode,
IfBranchNode type NodeTransform,
NodeTypes,
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { TRANSITION } from '../runtimeHelpers' import { TRANSITION } from '../runtimeHelpers'
import { createDOMCompilerError, DOMErrorCodes } from '../errors' import { DOMErrorCodes, createDOMCompilerError } from '../errors'
export const transformTransition: NodeTransform = (node, context) => { export const transformTransition: NodeTransform = (node, context) => {
if ( if (
@ -28,9 +28,9 @@ export const transformTransition: NodeTransform = (node, context) => {
{ {
start: node.children[0].loc.start, start: node.children[0].loc.start,
end: node.children[node.children.length - 1].loc.end, end: node.children[node.children.length - 1].loc.end,
source: '' source: '',
} },
) ),
) )
} }
@ -45,7 +45,7 @@ export const transformTransition: NodeTransform = (node, context) => {
name: 'persisted', name: 'persisted',
nameLoc: node.loc, nameLoc: node.loc,
value: undefined, value: undefined,
loc: node.loc loc: node.loc,
}) })
} }
} }
@ -60,7 +60,7 @@ function hasMultipleChildren(node: ComponentNode | IfBranchNode): boolean {
const children = (node.children = node.children.filter( const children = (node.children = node.children.filter(
c => c =>
c.type !== NodeTypes.COMMENT && c.type !== NodeTypes.COMMENT &&
!(c.type === NodeTypes.TEXT && !c.content.trim()) !(c.type === NodeTypes.TEXT && !c.content.trim()),
)) ))
const child = children[0] const child = children[0]
return ( return (

View File

@ -1,4 +1,4 @@
import { NodeTransform, NodeTypes, ElementTypes } from '@vue/compiler-core' import { ElementTypes, type NodeTransform, NodeTypes } from '@vue/compiler-core'
import { DOMErrorCodes, createDOMCompilerError } from '../errors' import { DOMErrorCodes, createDOMCompilerError } from '../errors'
export const ignoreSideEffectTags: NodeTransform = (node, context) => { export const ignoreSideEffectTags: NodeTransform = (node, context) => {
@ -11,8 +11,8 @@ export const ignoreSideEffectTags: NodeTransform = (node, context) => {
context.onError( context.onError(
createDOMCompilerError( createDOMCompilerError(
DOMErrorCodes.X_IGNORED_SIDE_EFFECT_TAG, DOMErrorCodes.X_IGNORED_SIDE_EFFECT_TAG,
node.loc node.loc,
) ),
) )
context.removeNode() context.removeNode()
} }

View File

@ -2,40 +2,40 @@
* This module is Node-only. * This module is Node-only.
*/ */
import { import {
NodeTypes,
ElementNode,
TransformContext,
TemplateChildNode,
SimpleExpressionNode,
createCallExpression,
HoistTransform,
CREATE_STATIC, CREATE_STATIC,
ExpressionNode,
ElementTypes,
PlainElementNode,
JSChildNode,
TextCallNode,
ConstantTypes, ConstantTypes,
Namespaces type ElementNode,
ElementTypes,
type ExpressionNode,
type HoistTransform,
type JSChildNode,
Namespaces,
NodeTypes,
type PlainElementNode,
type SimpleExpressionNode,
type TemplateChildNode,
type TextCallNode,
type TransformContext,
createCallExpression,
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { import {
isVoidTag, escapeHtml,
isBooleanAttr,
isKnownHtmlAttr,
isKnownSvgAttr,
isString, isString,
isSymbol, isSymbol,
isKnownHtmlAttr, isVoidTag,
escapeHtml, makeMap,
toDisplayString,
normalizeClass, normalizeClass,
normalizeStyle, normalizeStyle,
stringifyStyle, stringifyStyle,
makeMap, toDisplayString,
isKnownSvgAttr,
isBooleanAttr
} from '@vue/shared' } from '@vue/shared'
export enum StringifyThresholds { export enum StringifyThresholds {
ELEMENT_WITH_BINDING_COUNT = 5, ELEMENT_WITH_BINDING_COUNT = 5,
NODE_COUNT = 20 NODE_COUNT = 20,
} }
type StringifiableNode = PlainElementNode | TextCallNode type StringifiableNode = PlainElementNode | TextCallNode
@ -87,11 +87,11 @@ export const stringifyStatic: HoistTransform = (children, context, parent) => {
// combine all currently eligible nodes into a single static vnode call // combine all currently eligible nodes into a single static vnode call
const staticCall = createCallExpression(context.helper(CREATE_STATIC), [ const staticCall = createCallExpression(context.helper(CREATE_STATIC), [
JSON.stringify( JSON.stringify(
currentChunk.map(node => stringifyNode(node, context)).join('') currentChunk.map(node => stringifyNode(node, context)).join(''),
).replace(expReplaceRE, `" + $1 + "`), ).replace(expReplaceRE, `" + $1 + "`),
// the 2nd argument indicates the number of DOM nodes this static vnode // the 2nd argument indicates the number of DOM nodes this static vnode
// will insert / hydrate // will insert / hydrate
String(currentChunk.length) String(currentChunk.length),
]) ])
// replace the first node's hoisted expression with the static vnode call // replace the first node's hoisted expression with the static vnode call
replaceHoist(currentChunk[0], staticCall, context) replaceHoist(currentChunk[0], staticCall, context)
@ -161,14 +161,14 @@ const isStringifiableAttr = (name: string, ns: Namespaces) => {
const replaceHoist = ( const replaceHoist = (
node: StringifiableNode, node: StringifiableNode,
replacement: JSChildNode | null, replacement: JSChildNode | null,
context: TransformContext context: TransformContext,
) => { ) => {
const hoistToReplace = (node.codegenNode as SimpleExpressionNode).hoisted! const hoistToReplace = (node.codegenNode as SimpleExpressionNode).hoisted!
context.hoists[context.hoists.indexOf(hoistToReplace)] = replacement context.hoists[context.hoists.indexOf(hoistToReplace)] = replacement
} }
const isNonStringifiable = /*#__PURE__*/ makeMap( const isNonStringifiable = /*#__PURE__*/ makeMap(
`caption,thead,tr,th,tbody,td,tfoot,colgroup,col` `caption,thead,tr,th,tbody,td,tfoot,colgroup,col`,
) )
/** /**
@ -248,7 +248,7 @@ function analyzeNode(node: StringifiableNode): [number, number] | false {
function stringifyNode( function stringifyNode(
node: string | TemplateChildNode, node: string | TemplateChildNode,
context: TransformContext context: TransformContext,
): string { ): string {
if (isString(node)) { if (isString(node)) {
return node return node
@ -277,7 +277,7 @@ function stringifyNode(
function stringifyElement( function stringifyElement(
node: ElementNode, node: ElementNode,
context: TransformContext context: TransformContext,
): string { ): string {
let res = `<${node.tag}` let res = `<${node.tag}`
let innerHTML = '' let innerHTML = ''
@ -316,7 +316,7 @@ function stringifyElement(
evaluated = stringifyStyle(normalizeStyle(evaluated)) evaluated = stringifyStyle(normalizeStyle(evaluated))
} }
res += ` ${(p.arg as SimpleExpressionNode).content}="${escapeHtml( res += ` ${(p.arg as SimpleExpressionNode).content}="${escapeHtml(
evaluated evaluated,
)}"` )}"`
} }
} else if (p.name === 'html') { } else if (p.name === 'html') {
@ -325,7 +325,7 @@ function stringifyElement(
innerHTML = evaluateConstant(p.exp as SimpleExpressionNode) innerHTML = evaluateConstant(p.exp as SimpleExpressionNode)
} else if (p.name === 'text') { } else if (p.name === 'text') {
innerHTML = escapeHtml( innerHTML = escapeHtml(
toDisplayString(evaluateConstant(p.exp as SimpleExpressionNode)) toDisplayString(evaluateConstant(p.exp as SimpleExpressionNode)),
) )
} }
} }

View File

@ -1,10 +1,10 @@
import { import {
NodeTransform, ConstantTypes,
type NodeTransform,
NodeTypes, NodeTypes,
type SimpleExpressionNode,
type SourceLocation,
createSimpleExpression, createSimpleExpression,
SimpleExpressionNode,
SourceLocation,
ConstantTypes
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { parseStringStyle } from '@vue/shared' import { parseStringStyle } from '@vue/shared'
@ -25,7 +25,7 @@ export const transformStyle: NodeTransform = node => {
arg: createSimpleExpression(`style`, true, p.loc), arg: createSimpleExpression(`style`, true, p.loc),
exp: parseInlineCSS(p.value.content, p.loc), exp: parseInlineCSS(p.value.content, p.loc),
modifiers: [], modifiers: [],
loc: p.loc loc: p.loc,
} }
} }
}) })
@ -34,13 +34,13 @@ export const transformStyle: NodeTransform = node => {
const parseInlineCSS = ( const parseInlineCSS = (
cssText: string, cssText: string,
loc: SourceLocation loc: SourceLocation,
): SimpleExpressionNode => { ): SimpleExpressionNode => {
const normalized = parseStringStyle(cssText) const normalized = parseStringStyle(cssText)
return createSimpleExpression( return createSimpleExpression(
JSON.stringify(normalized), JSON.stringify(normalized),
false, false,
loc, loc,
ConstantTypes.CAN_STRINGIFY ConstantTypes.CAN_STRINGIFY,
) )
} }

View File

@ -1,20 +1,20 @@
import { import {
DirectiveTransform, type DirectiveTransform,
createObjectProperty, createObjectProperty,
createSimpleExpression createSimpleExpression,
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { createDOMCompilerError, DOMErrorCodes } from '../errors' import { DOMErrorCodes, createDOMCompilerError } from '../errors'
export const transformVHtml: DirectiveTransform = (dir, node, context) => { export const transformVHtml: DirectiveTransform = (dir, node, context) => {
const { exp, loc } = dir const { exp, loc } = dir
if (!exp) { if (!exp) {
context.onError( context.onError(
createDOMCompilerError(DOMErrorCodes.X_V_HTML_NO_EXPRESSION, loc) createDOMCompilerError(DOMErrorCodes.X_V_HTML_NO_EXPRESSION, loc),
) )
} }
if (node.children.length) { if (node.children.length) {
context.onError( context.onError(
createDOMCompilerError(DOMErrorCodes.X_V_HTML_WITH_CHILDREN, loc) createDOMCompilerError(DOMErrorCodes.X_V_HTML_WITH_CHILDREN, loc),
) )
node.children.length = 0 node.children.length = 0
} }
@ -22,8 +22,8 @@ export const transformVHtml: DirectiveTransform = (dir, node, context) => {
props: [ props: [
createObjectProperty( createObjectProperty(
createSimpleExpression(`innerHTML`, true, loc), createSimpleExpression(`innerHTML`, true, loc),
exp || createSimpleExpression('', true) exp || createSimpleExpression('', true),
) ),
] ],
} }
} }

View File

@ -1,20 +1,20 @@
import { import {
transformModel as baseTransform, type DirectiveTransform,
DirectiveTransform,
ElementTypes, ElementTypes,
findProp,
NodeTypes, NodeTypes,
hasDynamicKeyVBind, transformModel as baseTransform,
findDir, findDir,
isStaticArgOf findProp,
hasDynamicKeyVBind,
isStaticArgOf,
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { createDOMCompilerError, DOMErrorCodes } from '../errors' import { DOMErrorCodes, createDOMCompilerError } from '../errors'
import { import {
V_MODEL_CHECKBOX, V_MODEL_CHECKBOX,
V_MODEL_DYNAMIC,
V_MODEL_RADIO, V_MODEL_RADIO,
V_MODEL_SELECT, V_MODEL_SELECT,
V_MODEL_TEXT, V_MODEL_TEXT,
V_MODEL_DYNAMIC
} from '../runtimeHelpers' } from '../runtimeHelpers'
export const transformModel: DirectiveTransform = (dir, node, context) => { export const transformModel: DirectiveTransform = (dir, node, context) => {
@ -28,8 +28,8 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
context.onError( context.onError(
createDOMCompilerError( createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT, DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
dir.arg.loc dir.arg.loc,
) ),
) )
} }
@ -39,8 +39,8 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
context.onError( context.onError(
createDOMCompilerError( createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE, DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE,
value.loc value.loc,
) ),
) )
} }
} }
@ -74,8 +74,8 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
context.onError( context.onError(
createDOMCompilerError( createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT, DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
dir.loc dir.loc,
) ),
) )
break break
default: default:
@ -108,8 +108,8 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
context.onError( context.onError(
createDOMCompilerError( createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT, DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
dir.loc dir.loc,
) ),
) )
} }
@ -120,7 +120,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
!( !(
p.key.type === NodeTypes.SIMPLE_EXPRESSION && p.key.type === NodeTypes.SIMPLE_EXPRESSION &&
p.key.content === 'modelValue' p.key.content === 'modelValue'
) ),
) )
return baseResult return baseResult

View File

@ -1,21 +1,21 @@
import { import {
transformOn as baseTransform,
DirectiveTransform,
createObjectProperty,
createCallExpression,
createSimpleExpression,
NodeTypes,
createCompoundExpression,
ExpressionNode,
SimpleExpressionNode,
isStaticExp,
CompilerDeprecationTypes, CompilerDeprecationTypes,
TransformContext, type DirectiveTransform,
SourceLocation, type ExpressionNode,
checkCompatEnabled NodeTypes,
type SimpleExpressionNode,
type SourceLocation,
type TransformContext,
transformOn as baseTransform,
checkCompatEnabled,
createCallExpression,
createCompoundExpression,
createObjectProperty,
createSimpleExpression,
isStaticExp,
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../runtimeHelpers' import { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from '../runtimeHelpers'
import { makeMap, capitalize } from '@vue/shared' import { capitalize, makeMap } from '@vue/shared'
const isEventOptionModifier = /*#__PURE__*/ makeMap(`passive,once,capture`) const isEventOptionModifier = /*#__PURE__*/ makeMap(`passive,once,capture`)
const isNonKeyModifier = /*#__PURE__*/ makeMap( const isNonKeyModifier = /*#__PURE__*/ makeMap(
@ -24,20 +24,20 @@ const isNonKeyModifier = /*#__PURE__*/ makeMap(
// system modifiers + exact // system modifiers + exact
`ctrl,shift,alt,meta,exact,` + `ctrl,shift,alt,meta,exact,` +
// mouse // mouse
`middle` `middle`,
) )
// left & right could be mouse or key modifiers based on event type // left & right could be mouse or key modifiers based on event type
const maybeKeyModifier = /*#__PURE__*/ makeMap('left,right') const maybeKeyModifier = /*#__PURE__*/ makeMap('left,right')
const isKeyboardEvent = /*#__PURE__*/ makeMap( const isKeyboardEvent = /*#__PURE__*/ makeMap(
`onkeyup,onkeydown,onkeypress`, `onkeyup,onkeydown,onkeypress`,
true true,
) )
const resolveModifiers = ( const resolveModifiers = (
key: ExpressionNode, key: ExpressionNode,
modifiers: string[], modifiers: string[],
context: TransformContext, context: TransformContext,
loc: SourceLocation loc: SourceLocation,
) => { ) => {
const keyModifiers = [] const keyModifiers = []
const nonKeyModifiers = [] const nonKeyModifiers = []
@ -52,7 +52,7 @@ const resolveModifiers = (
checkCompatEnabled( checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_V_ON_NATIVE, CompilerDeprecationTypes.COMPILER_V_ON_NATIVE,
context, context,
loc loc,
) )
) { ) {
eventOptionModifiers.push(modifier) eventOptionModifiers.push(modifier)
@ -86,7 +86,7 @@ const resolveModifiers = (
return { return {
keyModifiers, keyModifiers,
nonKeyModifiers, nonKeyModifiers,
eventOptionModifiers eventOptionModifiers,
} }
} }
@ -101,7 +101,7 @@ const transformClick = (key: ExpressionNode, event: string) => {
key, key,
`) === "onClick" ? "${event}" : (`, `) === "onClick" ? "${event}" : (`,
key, key,
`)` `)`,
]) ])
: key : key
} }
@ -126,7 +126,7 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
if (nonKeyModifiers.length) { if (nonKeyModifiers.length) {
handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [ handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [
handlerExp, handlerExp,
JSON.stringify(nonKeyModifiers) JSON.stringify(nonKeyModifiers),
]) ])
} }
@ -137,7 +137,7 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
) { ) {
handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [ handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [
handlerExp, handlerExp,
JSON.stringify(keyModifiers) JSON.stringify(keyModifiers),
]) ])
} }
@ -149,7 +149,7 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
} }
return { return {
props: [createObjectProperty(key, handlerExp)] props: [createObjectProperty(key, handlerExp)],
} }
}) })
} }

View File

@ -1,17 +1,17 @@
import { DirectiveTransform } from '@vue/compiler-core' import type { DirectiveTransform } from '@vue/compiler-core'
import { createDOMCompilerError, DOMErrorCodes } from '../errors' import { DOMErrorCodes, createDOMCompilerError } from '../errors'
import { V_SHOW } from '../runtimeHelpers' import { V_SHOW } from '../runtimeHelpers'
export const transformShow: DirectiveTransform = (dir, node, context) => { export const transformShow: DirectiveTransform = (dir, node, context) => {
const { exp, loc } = dir const { exp, loc } = dir
if (!exp) { if (!exp) {
context.onError( context.onError(
createDOMCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION, loc) createDOMCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION, loc),
) )
} }
return { return {
props: [], props: [],
needRuntime: context.helper(V_SHOW) needRuntime: context.helper(V_SHOW),
} }
} }

View File

@ -1,23 +1,23 @@
import { import {
DirectiveTransform, type DirectiveTransform,
createObjectProperty,
createSimpleExpression,
TO_DISPLAY_STRING, TO_DISPLAY_STRING,
createCallExpression, createCallExpression,
getConstantType createObjectProperty,
createSimpleExpression,
getConstantType,
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { createDOMCompilerError, DOMErrorCodes } from '../errors' import { DOMErrorCodes, createDOMCompilerError } from '../errors'
export const transformVText: DirectiveTransform = (dir, node, context) => { export const transformVText: DirectiveTransform = (dir, node, context) => {
const { exp, loc } = dir const { exp, loc } = dir
if (!exp) { if (!exp) {
context.onError( context.onError(
createDOMCompilerError(DOMErrorCodes.X_V_TEXT_NO_EXPRESSION, loc) createDOMCompilerError(DOMErrorCodes.X_V_TEXT_NO_EXPRESSION, loc),
) )
} }
if (node.children.length) { if (node.children.length) {
context.onError( context.onError(
createDOMCompilerError(DOMErrorCodes.X_V_TEXT_WITH_CHILDREN, loc) createDOMCompilerError(DOMErrorCodes.X_V_TEXT_WITH_CHILDREN, loc),
) )
node.children.length = 0 node.children.length = 0
} }
@ -31,10 +31,10 @@ export const transformVText: DirectiveTransform = (dir, node, context) => {
: createCallExpression( : createCallExpression(
context.helperString(TO_DISPLAY_STRING), context.helperString(TO_DISPLAY_STRING),
[exp], [exp],
loc loc,
) )
: createSimpleExpression('', true) : createSimpleExpression('', true),
) ),
] ],
} }
} }

View File

@ -1,5 +1,5 @@
import { BindingTypes } from '@vue/compiler-core' import { BindingTypes } from '@vue/compiler-core'
import { compileSFCScript as compile, assertCode, mockId } from './utils' import { assertCode, compileSFCScript as compile, mockId } from './utils'
describe('SFC compile <script setup>', () => { describe('SFC compile <script setup>', () => {
test('should compile JS syntax', () => { test('should compile JS syntax', () => {
@ -34,7 +34,7 @@ describe('SFC compile <script setup>', () => {
expect(content).toMatch( expect(content).toMatch(
`return { get aa() { return aa }, set aa(v) { aa = v }, ` + `return { get aa() { return aa }, set aa(v) { aa = v }, ` +
`bb, cc, dd, get a() { return a }, set a(v) { a = v }, b, c, d, ` + `bb, cc, dd, get a() { return a }, set a(v) { a = v }, b, c, d, ` +
`get xx() { return xx }, get x() { return x } }` `get xx() { return xx }, get x() { return x } }`,
) )
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
x: BindingTypes.SETUP_MAYBE_REF, x: BindingTypes.SETUP_MAYBE_REF,
@ -46,7 +46,7 @@ describe('SFC compile <script setup>', () => {
aa: BindingTypes.SETUP_LET, aa: BindingTypes.SETUP_LET,
bb: BindingTypes.LITERAL_CONST, bb: BindingTypes.LITERAL_CONST,
cc: BindingTypes.SETUP_CONST, cc: BindingTypes.SETUP_CONST,
dd: BindingTypes.SETUP_CONST dd: BindingTypes.SETUP_CONST,
}) })
assertCode(content) assertCode(content)
}) })
@ -63,7 +63,7 @@ describe('SFC compile <script setup>', () => {
bar: BindingTypes.SETUP_MAYBE_REF, bar: BindingTypes.SETUP_MAYBE_REF,
baz: BindingTypes.SETUP_MAYBE_REF, baz: BindingTypes.SETUP_MAYBE_REF,
y: BindingTypes.SETUP_MAYBE_REF, y: BindingTypes.SETUP_MAYBE_REF,
z: BindingTypes.SETUP_MAYBE_REF z: BindingTypes.SETUP_MAYBE_REF,
}) })
assertCode(content) assertCode(content)
}) })
@ -209,7 +209,7 @@ describe('SFC compile <script setup>', () => {
compile(`<script setup> compile(`<script setup>
import { ref } from 'vue' import { ref } from 'vue'
import 'foo/css' import 'foo/css'
</script>`).content </script>`).content,
) )
}) })
@ -220,7 +220,7 @@ describe('SFC compile <script setup>', () => {
import a from 'a' // comment import a from 'a' // comment
import b from 'b' import b from 'b'
</script> </script>
`).content `).content,
) )
}) })
@ -232,7 +232,7 @@ describe('SFC compile <script setup>', () => {
defineProps(['foo']) defineProps(['foo'])
defineEmits(['bar']) defineEmits(['bar'])
const r = ref(0) const r = ref(0)
</script>`).content </script>`).content,
) )
}) })
@ -249,11 +249,11 @@ describe('SFC compile <script setup>', () => {
color: v-bind(msg) color: v-bind(msg)
} }
</style> </style>
` `,
) )
assertCode(content) assertCode(content)
expect(content).toMatch( expect(content).toMatch(
`import { useCssVars as _useCssVars, unref as _unref } from 'vue'` `import { useCssVars as _useCssVars, unref as _unref } from 'vue'`,
) )
expect(content).toMatch(`import { useCssVars, ref } from 'vue'`) expect(content).toMatch(`import { useCssVars, ref } from 'vue'`)
}) })
@ -270,7 +270,7 @@ describe('SFC compile <script setup>', () => {
`) `)
assertCode(content) assertCode(content)
expect(content.indexOf(`import { x }`)).toEqual( expect(content.indexOf(`import { x }`)).toEqual(
content.lastIndexOf(`import { x }`) content.lastIndexOf(`import { x }`),
) )
}) })
@ -288,7 +288,7 @@ describe('SFC compile <script setup>', () => {
ref: BindingTypes.SETUP_MAYBE_REF, ref: BindingTypes.SETUP_MAYBE_REF,
reactive: BindingTypes.SETUP_MAYBE_REF, reactive: BindingTypes.SETUP_MAYBE_REF,
foo: BindingTypes.SETUP_MAYBE_REF, foo: BindingTypes.SETUP_MAYBE_REF,
bar: BindingTypes.SETUP_MAYBE_REF bar: BindingTypes.SETUP_MAYBE_REF,
}) })
}) })
@ -305,7 +305,7 @@ describe('SFC compile <script setup>', () => {
_reactive: BindingTypes.SETUP_MAYBE_REF, _reactive: BindingTypes.SETUP_MAYBE_REF,
_ref: BindingTypes.SETUP_MAYBE_REF, _ref: BindingTypes.SETUP_MAYBE_REF,
foo: BindingTypes.SETUP_MAYBE_REF, foo: BindingTypes.SETUP_MAYBE_REF,
bar: BindingTypes.SETUP_MAYBE_REF bar: BindingTypes.SETUP_MAYBE_REF,
}) })
}) })
@ -318,7 +318,7 @@ describe('SFC compile <script setup>', () => {
`) `)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
bar: BindingTypes.SETUP_REACTIVE_CONST, bar: BindingTypes.SETUP_REACTIVE_CONST,
x: BindingTypes.SETUP_CONST x: BindingTypes.SETUP_CONST,
}) })
}) })
}) })
@ -334,7 +334,7 @@ describe('SFC compile <script setup>', () => {
`) `)
assertCode(content) assertCode(content)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.SETUP_MAYBE_REF foo: BindingTypes.SETUP_MAYBE_REF,
}) })
}) })
}) })
@ -363,7 +363,7 @@ describe('SFC compile <script setup>', () => {
// foo: lowercase component // foo: lowercase component
expect(content).toMatch( expect(content).toMatch(
`return { fooBar, get FooBaz() { return FooBaz }, ` + `return { fooBar, get FooBaz() { return FooBaz }, ` +
`get FooQux() { return FooQux }, get foo() { return foo } }` `get FooQux() { return FooQux }, get foo() { return foo } }`,
) )
assertCode(content) assertCode(content)
}) })
@ -396,7 +396,7 @@ describe('SFC compile <script setup>', () => {
`) `)
expect(content).toMatch( expect(content).toMatch(
`return { get FooBar() { return FooBar }, get foo() { return foo }, ` + `return { get FooBar() { return FooBar }, get foo() { return foo }, ` +
`get bar() { return bar }, get baz() { return baz } }` `get bar() { return bar }, get baz() { return baz } }`,
) )
assertCode(content) assertCode(content)
}) })
@ -413,7 +413,7 @@ describe('SFC compile <script setup>', () => {
</template> </template>
`) `)
expect(content).toMatch( expect(content).toMatch(
`return { cond, get bar() { return bar }, get baz() { return baz } }` `return { cond, get bar() { return bar }, get baz() { return baz } }`,
) )
assertCode(content) assertCode(content)
}) })
@ -431,7 +431,7 @@ describe('SFC compile <script setup>', () => {
// y: should not be matched by {{ yy }} or 'y' in binding exps // y: should not be matched by {{ yy }} or 'y' in binding exps
// x$y: #4274 should escape special chars when creating Regex // x$y: #4274 should escape special chars when creating Regex
expect(content).toMatch( expect(content).toMatch(
`return { get x() { return x }, get z() { return z }, get x$y() { return x$y } }` `return { get x() { return x }, get z() { return z }, get x$y() { return x$y } }`,
) )
assertCode(content) assertCode(content)
}) })
@ -448,7 +448,7 @@ describe('SFC compile <script setup>', () => {
`) `)
// VAR2 should not be matched // VAR2 should not be matched
expect(content).toMatch( expect(content).toMatch(
`return { get VAR() { return VAR }, get VAR3() { return VAR3 } }` `return { get VAR() { return VAR }, get VAR3() { return VAR3 } }`,
) )
assertCode(content) assertCode(content)
}) })
@ -465,7 +465,7 @@ describe('SFC compile <script setup>', () => {
</template> </template>
`) `)
expect(content).toMatch( expect(content).toMatch(
`return { get FooBaz() { return FooBaz }, get Last() { return Last } }` `return { get FooBaz() { return FooBaz }, get Last() { return Last } }`,
) )
assertCode(content) assertCode(content)
}) })
@ -514,7 +514,7 @@ describe('SFC compile <script setup>', () => {
</template> </template>
`) `)
expect(content).toMatch( expect(content).toMatch(
'return { get foo() { return foo }, get bar() { return bar }, get Baz() { return Baz } }' 'return { get foo() { return foo }, get bar() { return bar }, get Baz() { return Baz } }',
) )
assertCode(content) assertCode(content)
}) })
@ -573,7 +573,7 @@ describe('SFC compile <script setup>', () => {
<div>static</div> <div>static</div>
</template> </template>
`, `,
{ inlineTemplate: true } { inlineTemplate: true },
) )
// check snapshot and make sure helper imports and // check snapshot and make sure helper imports and
// hoists are placed correctly. // hoists are placed correctly.
@ -591,7 +591,7 @@ describe('SFC compile <script setup>', () => {
defineExpose({ count }) defineExpose({ count })
</script> </script>
`, `,
{ inlineTemplate: true } { inlineTemplate: true },
) )
assertCode(content) assertCode(content)
expect(content).toMatch(`setup(__props, { expose: __expose })`) expect(content).toMatch(`setup(__props, { expose: __expose })`)
@ -612,7 +612,7 @@ describe('SFC compile <script setup>', () => {
<some-other-comp/> <some-other-comp/>
</template> </template>
`, `,
{ inlineTemplate: true } { inlineTemplate: true },
) )
expect(content).toMatch('[_unref(vMyDir)]') expect(content).toMatch('[_unref(vMyDir)]')
expect(content).toMatch('_createVNode(ChildComp)') expect(content).toMatch('_createVNode(ChildComp)')
@ -641,7 +641,7 @@ describe('SFC compile <script setup>', () => {
{{ tree.foo() }} {{ tree.foo() }}
</template> </template>
`, `,
{ inlineTemplate: true } { inlineTemplate: true },
) )
// no need to unref vue component import // no need to unref vue component import
expect(content).toMatch(`createVNode(Foo,`) expect(content).toMatch(`createVNode(Foo,`)
@ -680,7 +680,7 @@ describe('SFC compile <script setup>', () => {
<input v-model="lett"> <input v-model="lett">
</template> </template>
`, `,
{ inlineTemplate: true } { inlineTemplate: true },
) )
// known const ref: set value // known const ref: set value
expect(content).toMatch(`(count).value = $event`) expect(content).toMatch(`(count).value = $event`)
@ -688,7 +688,7 @@ describe('SFC compile <script setup>', () => {
expect(content).toMatch(`_isRef(maybe) ? (maybe).value = $event : null`) expect(content).toMatch(`_isRef(maybe) ? (maybe).value = $event : null`)
// let: handle both cases // let: handle both cases
expect(content).toMatch( expect(content).toMatch(
`_isRef(lett) ? (lett).value = $event : lett = $event` `_isRef(lett) ? (lett).value = $event : lett = $event`,
) )
assertCode(content) assertCode(content)
}) })
@ -708,7 +708,7 @@ describe('SFC compile <script setup>', () => {
<input v-model="foo"> <input v-model="foo">
</template> </template>
`, `,
{ inlineTemplate: true } { inlineTemplate: true },
) )
expect(content).not.toMatch(`_isRef(foo)`) expect(content).not.toMatch(`_isRef(foo)`)
}) })
@ -746,7 +746,7 @@ describe('SFC compile <script setup>', () => {
}"/> }"/>
</template> </template>
`, `,
{ inlineTemplate: true } { inlineTemplate: true },
) )
// known const ref: set value // known const ref: set value
expect(content).toMatch(`count.value = 1`) expect(content).toMatch(`count.value = 1`)
@ -754,7 +754,7 @@ describe('SFC compile <script setup>', () => {
expect(content).toMatch(`maybe.value = count.value`) expect(content).toMatch(`maybe.value = count.value`)
// let: handle both cases // let: handle both cases
expect(content).toMatch( expect(content).toMatch(
`_isRef(lett) ? lett.value = count.value : lett = count.value` `_isRef(lett) ? lett.value = count.value : lett = count.value`,
) )
expect(content).toMatch(`_isRef(v) ? v.value += 1 : v += 1`) expect(content).toMatch(`_isRef(v) ? v.value += 1 : v += 1`)
expect(content).toMatch(`_isRef(v) ? v.value -= 1 : v -= 1`) expect(content).toMatch(`_isRef(v) ? v.value -= 1 : v -= 1`)
@ -780,7 +780,7 @@ describe('SFC compile <script setup>', () => {
<div @click="--lett"/> <div @click="--lett"/>
</template> </template>
`, `,
{ inlineTemplate: true } { inlineTemplate: true },
) )
// known const ref: set value // known const ref: set value
expect(content).toMatch(`count.value++`) expect(content).toMatch(`count.value++`)
@ -809,7 +809,7 @@ describe('SFC compile <script setup>', () => {
<div @click="({ lett } = val)"/> <div @click="({ lett } = val)"/>
</template> </template>
`, `,
{ inlineTemplate: true } { inlineTemplate: true },
) )
// known const ref: set value // known const ref: set value
expect(content).toMatch(`({ count: count.value } = val)`) expect(content).toMatch(`({ count: count.value } = val)`)
@ -840,9 +840,9 @@ describe('SFC compile <script setup>', () => {
{ {
inlineTemplate: true, inlineTemplate: true,
templateOptions: { templateOptions: {
ssr: true ssr: true,
} },
} },
) )
expect(content).toMatch(`\n __ssrInlineRender: true,\n`) expect(content).toMatch(`\n __ssrInlineRender: true,\n`)
expect(content).toMatch(`return (_ctx, _push`) expect(content).toMatch(`return (_ctx, _push`)
@ -866,9 +866,9 @@ describe('SFC compile <script setup>', () => {
</template> </template>
`, `,
{ {
inlineTemplate: false inlineTemplate: false,
} },
) ),
).not.toThrowError() ).not.toThrowError()
}) })
}) })
@ -887,11 +887,11 @@ describe('SFC compile <script setup>', () => {
const { content, bindings } = compile( const { content, bindings } = compile(
`<script setup lang="ts"> `<script setup lang="ts">
enum Foo { A = 123 } enum Foo { A = 123 }
</script>` </script>`,
) )
assertCode(content) assertCode(content)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
Foo: BindingTypes.LITERAL_CONST Foo: BindingTypes.LITERAL_CONST,
}) })
}) })
@ -904,14 +904,14 @@ describe('SFC compile <script setup>', () => {
</script> </script>
<script setup lang="ts"> <script setup lang="ts">
enum Foo { A = 123 } enum Foo { A = 123 }
</script>` </script>`,
) )
assertCode(content) assertCode(content)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
D: BindingTypes.LITERAL_CONST, D: BindingTypes.LITERAL_CONST,
C: BindingTypes.LITERAL_CONST, C: BindingTypes.LITERAL_CONST,
B: BindingTypes.LITERAL_CONST, B: BindingTypes.LITERAL_CONST,
Foo: BindingTypes.LITERAL_CONST Foo: BindingTypes.LITERAL_CONST,
}) })
}) })
@ -920,11 +920,11 @@ describe('SFC compile <script setup>', () => {
`<script setup lang="ts"> `<script setup lang="ts">
const enum Foo { A = 123 } const enum Foo { A = 123 }
</script>`, </script>`,
{ hoistStatic: true } { hoistStatic: true },
) )
assertCode(content) assertCode(content)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
Foo: BindingTypes.LITERAL_CONST Foo: BindingTypes.LITERAL_CONST,
}) })
}) })
@ -933,7 +933,7 @@ describe('SFC compile <script setup>', () => {
`<script setup lang="ts"> `<script setup lang="ts">
import type { Foo } from './main.ts' import type { Foo } from './main.ts'
import { type Bar, Baz } from './main.ts' import { type Bar, Baz } from './main.ts'
</script>` </script>`,
) )
expect(content).toMatch(`return { get Baz() { return Baz } }`) expect(content).toMatch(`return { get Baz() { return Baz } }`)
assertCode(content) assertCode(content)
@ -1057,7 +1057,7 @@ describe('SFC compile <script setup>', () => {
// class method // class method
assertAwaitDetection( assertAwaitDetection(
`const cls = class Foo { async method() { await bar }}`, `const cls = class Foo { async method() { await bar }}`,
false false,
) )
}) })
}) })
@ -1065,7 +1065,7 @@ describe('SFC compile <script setup>', () => {
describe('errors', () => { describe('errors', () => {
test('<script> and <script setup> must have same lang', () => { test('<script> and <script setup> must have same lang', () => {
expect(() => expect(() =>
compile(`<script>foo()</script><script setup lang="ts">bar()</script>`) compile(`<script>foo()</script><script setup lang="ts">bar()</script>`),
).toThrow(`<script> and <script setup> must have the same language type`) ).toThrow(`<script> and <script setup> must have the same language type`)
}) })
@ -1075,20 +1075,20 @@ describe('SFC compile <script setup>', () => {
expect(() => expect(() =>
compile(`<script setup> compile(`<script setup>
export const a = 1 export const a = 1
</script>`) </script>`),
).toThrow(moduleErrorMsg) ).toThrow(moduleErrorMsg)
expect(() => expect(() =>
compile(`<script setup> compile(`<script setup>
export * from './foo' export * from './foo'
</script>`) </script>`),
).toThrow(moduleErrorMsg) ).toThrow(moduleErrorMsg)
expect(() => expect(() =>
compile(`<script setup> compile(`<script setup>
const bar = 1 const bar = 1
export { bar as default } export { bar as default }
</script>`) </script>`),
).toThrow(moduleErrorMsg) ).toThrow(moduleErrorMsg)
}) })
@ -1101,14 +1101,14 @@ describe('SFC compile <script setup>', () => {
default: () => bar default: () => bar
} }
}) })
</script>`) </script>`),
).toThrow(`cannot reference locally declared variables`) ).toThrow(`cannot reference locally declared variables`)
expect(() => expect(() =>
compile(`<script setup> compile(`<script setup>
let bar = 'hello' let bar = 'hello'
defineEmits([bar]) defineEmits([bar])
</script>`) </script>`),
).toThrow(`cannot reference locally declared variables`) ).toThrow(`cannot reference locally declared variables`)
// #4644 // #4644
@ -1121,7 +1121,7 @@ describe('SFC compile <script setup>', () => {
default: () => bar default: () => bar
} }
}) })
</script>`) </script>`),
).not.toThrow(`cannot reference locally declared variables`) ).not.toThrow(`cannot reference locally declared variables`)
}) })
@ -1137,7 +1137,7 @@ describe('SFC compile <script setup>', () => {
defineEmits({ defineEmits({
foo: bar => bar > 1 foo: bar => bar > 1
}) })
</script>`).content </script>`).content,
) )
}) })
@ -1153,7 +1153,7 @@ describe('SFC compile <script setup>', () => {
defineEmits({ defineEmits({
foo: () => bar > 1 foo: () => bar > 1
}) })
</script>`).content </script>`).content,
) )
}) })
}) })
@ -1187,7 +1187,7 @@ describe('SFC analyze <script> bindings', () => {
`) `)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS, foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS bar: BindingTypes.PROPS,
}) })
expect(bindings!.__isScriptSetup).toBe(false) expect(bindings!.__isScriptSetup).toBe(false)
}) })
@ -1211,7 +1211,7 @@ describe('SFC analyze <script> bindings', () => {
foo: BindingTypes.PROPS, foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS, bar: BindingTypes.PROPS,
baz: BindingTypes.PROPS, baz: BindingTypes.PROPS,
qux: BindingTypes.PROPS qux: BindingTypes.PROPS,
}) })
expect(bindings!.__isScriptSetup).toBe(false) expect(bindings!.__isScriptSetup).toBe(false)
}) })
@ -1232,7 +1232,7 @@ describe('SFC analyze <script> bindings', () => {
`) `)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.SETUP_MAYBE_REF, foo: BindingTypes.SETUP_MAYBE_REF,
bar: BindingTypes.SETUP_MAYBE_REF bar: BindingTypes.SETUP_MAYBE_REF,
}) })
expect(bindings!.__isScriptSetup).toBe(false) expect(bindings!.__isScriptSetup).toBe(false)
}) })
@ -1247,7 +1247,7 @@ describe('SFC analyze <script> bindings', () => {
</script> </script>
`) `)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.LITERAL_CONST foo: BindingTypes.LITERAL_CONST,
}) })
}) })
@ -1267,7 +1267,7 @@ describe('SFC analyze <script> bindings', () => {
`) `)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.SETUP_MAYBE_REF, foo: BindingTypes.SETUP_MAYBE_REF,
bar: BindingTypes.SETUP_MAYBE_REF bar: BindingTypes.SETUP_MAYBE_REF,
}) })
expect(bindings!.__isScriptSetup).toBe(false) expect(bindings!.__isScriptSetup).toBe(false)
}) })
@ -1288,7 +1288,7 @@ describe('SFC analyze <script> bindings', () => {
`) `)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.DATA, foo: BindingTypes.DATA,
bar: BindingTypes.DATA bar: BindingTypes.DATA,
}) })
}) })
@ -1321,7 +1321,7 @@ describe('SFC analyze <script> bindings', () => {
`) `)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.OPTIONS, foo: BindingTypes.OPTIONS,
bar: BindingTypes.OPTIONS bar: BindingTypes.OPTIONS,
}) })
}) })
@ -1335,7 +1335,7 @@ describe('SFC analyze <script> bindings', () => {
`) `)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.OPTIONS, foo: BindingTypes.OPTIONS,
bar: BindingTypes.OPTIONS bar: BindingTypes.OPTIONS,
}) })
}) })
@ -1352,7 +1352,7 @@ describe('SFC analyze <script> bindings', () => {
`) `)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.OPTIONS, foo: BindingTypes.OPTIONS,
bar: BindingTypes.OPTIONS bar: BindingTypes.OPTIONS,
}) })
}) })
@ -1389,7 +1389,7 @@ describe('SFC analyze <script> bindings', () => {
baz: BindingTypes.SETUP_MAYBE_REF, baz: BindingTypes.SETUP_MAYBE_REF,
qux: BindingTypes.DATA, qux: BindingTypes.DATA,
quux: BindingTypes.OPTIONS, quux: BindingTypes.OPTIONS,
quuz: BindingTypes.OPTIONS quuz: BindingTypes.OPTIONS,
}) })
}) })
@ -1416,7 +1416,7 @@ describe('SFC analyze <script> bindings', () => {
c: BindingTypes.LITERAL_CONST, c: BindingTypes.LITERAL_CONST,
d: BindingTypes.SETUP_MAYBE_REF, d: BindingTypes.SETUP_MAYBE_REF,
e: BindingTypes.SETUP_LET, e: BindingTypes.SETUP_LET,
foo: BindingTypes.PROPS foo: BindingTypes.PROPS,
}) })
}) })
@ -1427,8 +1427,8 @@ describe('SFC analyze <script> bindings', () => {
<template>{{ a }}</template>`, <template>{{ a }}</template>`,
undefined, undefined,
{ {
filename: 'FooBar.vue' filename: 'FooBar.vue',
} },
) )
expect(content).toMatch(`export default { expect(content).toMatch(`export default {
__name: 'FooBar'`) __name: 'FooBar'`)
@ -1446,8 +1446,8 @@ describe('SFC analyze <script> bindings', () => {
<template>{{ a }}</template>`, <template>{{ a }}</template>`,
undefined, undefined,
{ {
filename: 'FooBar.vue' filename: 'FooBar.vue',
} },
) )
expect(content).not.toMatch(`name: 'FooBar'`) expect(content).not.toMatch(`name: 'FooBar'`)
expect(content).toMatch(`name: 'Baz'`) expect(content).toMatch(`name: 'Baz'`)
@ -1466,8 +1466,8 @@ describe('SFC analyze <script> bindings', () => {
<template>{{ a }}</template>`, <template>{{ a }}</template>`,
undefined, undefined,
{ {
filename: 'FooBar.vue' filename: 'FooBar.vue',
} },
) )
expect(content).not.toMatch(`name: 'FooBar'`) expect(content).not.toMatch(`name: 'FooBar'`)
expect(content).toMatch(`name: 'Baz'`) expect(content).toMatch(`name: 'Baz'`)
@ -1483,8 +1483,8 @@ describe('SFC genDefaultAs', () => {
export default {} export default {}
</script>`, </script>`,
{ {
genDefaultAs: '_sfc_' genDefaultAs: '_sfc_',
} },
) )
expect(content).not.toMatch('export default') expect(content).not.toMatch('export default')
expect(content).toMatch(`const _sfc_ = {}`) expect(content).toMatch(`const _sfc_ = {}`)
@ -1500,8 +1500,8 @@ describe('SFC genDefaultAs', () => {
.foo { color: v-bind(x) } .foo { color: v-bind(x) }
</style>`, </style>`,
{ {
genDefaultAs: '_sfc_' genDefaultAs: '_sfc_',
} },
) )
expect(content).not.toMatch('export default') expect(content).not.toMatch('export default')
expect(content).not.toMatch('__default__') expect(content).not.toMatch('__default__')
@ -1518,12 +1518,12 @@ describe('SFC genDefaultAs', () => {
const a = 1 const a = 1
</script>`, </script>`,
{ {
genDefaultAs: '_sfc_' genDefaultAs: '_sfc_',
} },
) )
expect(content).not.toMatch('export default') expect(content).not.toMatch('export default')
expect(content).toMatch( expect(content).toMatch(
`const _sfc_ = /*#__PURE__*/Object.assign(__default__` `const _sfc_ = /*#__PURE__*/Object.assign(__default__`,
) )
assertCode(content) assertCode(content)
}) })
@ -1537,12 +1537,12 @@ describe('SFC genDefaultAs', () => {
const a = 1 const a = 1
</script>`, </script>`,
{ {
genDefaultAs: '_sfc_' genDefaultAs: '_sfc_',
} },
) )
expect(content).not.toMatch('export default') expect(content).not.toMatch('export default')
expect(content).toMatch( expect(content).toMatch(
`const _sfc_ = /*#__PURE__*/Object.assign(__default__` `const _sfc_ = /*#__PURE__*/Object.assign(__default__`,
) )
assertCode(content) assertCode(content)
}) })
@ -1553,8 +1553,8 @@ describe('SFC genDefaultAs', () => {
const a = 1 const a = 1
</script>`, </script>`,
{ {
genDefaultAs: '_sfc_' genDefaultAs: '_sfc_',
} },
) )
expect(content).not.toMatch('export default') expect(content).not.toMatch('export default')
expect(content).toMatch(`const _sfc_ = {\n setup`) expect(content).toMatch(`const _sfc_ = {\n setup`)
@ -1567,8 +1567,8 @@ describe('SFC genDefaultAs', () => {
const a = 1 const a = 1
</script>`, </script>`,
{ {
genDefaultAs: '_sfc_' genDefaultAs: '_sfc_',
} },
) )
expect(content).not.toMatch('export default') expect(content).not.toMatch('export default')
expect(content).toMatch(`const _sfc_ = /*#__PURE__*/_defineComponent(`) expect(content).toMatch(`const _sfc_ = /*#__PURE__*/_defineComponent(`)
@ -1584,12 +1584,12 @@ describe('SFC genDefaultAs', () => {
const a = 1 const a = 1
</script>`, </script>`,
{ {
genDefaultAs: '_sfc_' genDefaultAs: '_sfc_',
} },
) )
expect(content).not.toMatch('export default') expect(content).not.toMatch('export default')
expect(content).toMatch( expect(content).toMatch(
`const _sfc_ = /*#__PURE__*/_defineComponent({\n ...__default__` `const _sfc_ = /*#__PURE__*/_defineComponent({\n ...__default__`,
) )
assertCode(content) assertCode(content)
}) })
@ -1600,12 +1600,12 @@ describe('SFC genDefaultAs', () => {
import { toRef } from 'vue' import { toRef } from 'vue'
const props = defineProps<{foo: string}>() const props = defineProps<{foo: string}>()
const foo = toRef(() => props.foo) const foo = toRef(() => props.foo)
</script>` </script>`,
) )
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
toRef: BindingTypes.SETUP_CONST, toRef: BindingTypes.SETUP_CONST,
props: BindingTypes.SETUP_REACTIVE_CONST, props: BindingTypes.SETUP_REACTIVE_CONST,
foo: BindingTypes.SETUP_REF foo: BindingTypes.SETUP_REF,
}) })
}) })
@ -1622,7 +1622,7 @@ describe('SFC genDefaultAs', () => {
compile(` compile(`
<script setup> <script setup>
import { foo } from './foo.js' assert { type: 'foobar' } import { foo } from './foo.js' assert { type: 'foobar' }
</script>`) </script>`),
).toThrow() ).toThrow()
}) })
@ -1635,9 +1635,9 @@ describe('SFC genDefaultAs', () => {
`, `,
{ {
babelParserPlugins: [ babelParserPlugins: [
['importAttributes', { deprecatedAssertSyntax: true }] ['importAttributes', { deprecatedAssertSyntax: true }],
] ],
} },
) )
assertCode(content) assertCode(content)
}) })

View File

@ -1,5 +1,5 @@
import { BindingTypes } from '@vue/compiler-core' import { BindingTypes } from '@vue/compiler-core'
import { compileSFCScript as compile, assertCode } from '../utils' import { assertCode, compileSFCScript as compile } from '../utils'
describe('defineEmits', () => { describe('defineEmits', () => {
test('basic usage', () => { test('basic usage', () => {
@ -10,13 +10,13 @@ const myEmit = defineEmits(['foo', 'bar'])
`) `)
assertCode(content) assertCode(content)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
myEmit: BindingTypes.SETUP_CONST myEmit: BindingTypes.SETUP_CONST,
}) })
// should remove defineEmits import and call // should remove defineEmits import and call
expect(content).not.toMatch('defineEmits') expect(content).not.toMatch('defineEmits')
// should generate correct setup signature // should generate correct setup signature
expect(content).toMatch( expect(content).toMatch(
`setup(__props, { expose: __expose, emit: __emit }) {` `setup(__props, { expose: __expose, emit: __emit }) {`,
) )
expect(content).toMatch('const myEmit = __emit') expect(content).toMatch('const myEmit = __emit')
// should include context options in default export // should include context options in default export
@ -226,9 +226,9 @@ const emit = defineEmits(['a', 'b'])
foo: [] foo: []
(e: 'hi'): void (e: 'hi'): void
}>() }>()
</script>`) </script>`),
).toThrow( ).toThrow(
`defineEmits() type cannot mixed call signature and property syntax.` `defineEmits() type cannot mixed call signature and property syntax.`,
) )
}) })
}) })

View File

@ -1,4 +1,4 @@
import { compileSFCScript as compile, assertCode } from '../utils' import { assertCode, compileSFCScript as compile } from '../utils'
test('defineExpose()', () => { test('defineExpose()', () => {
const { content } = compile(` const { content } = compile(`

View File

@ -1,5 +1,5 @@
import { BindingTypes } from '@vue/compiler-core' import { BindingTypes } from '@vue/compiler-core'
import { compileSFCScript as compile, assertCode } from '../utils' import { assertCode, compileSFCScript as compile } from '../utils'
describe('defineModel()', () => { describe('defineModel()', () => {
test('basic usage', () => { test('basic usage', () => {
@ -10,7 +10,7 @@ describe('defineModel()', () => {
const c = defineModel('count') const c = defineModel('count')
const toString = defineModel('toString', { type: Function }) const toString = defineModel('toString', { type: Function })
</script> </script>
` `,
) )
assertCode(content) assertCode(content)
expect(content).toMatch('props: {') expect(content).toMatch('props: {')
@ -18,10 +18,10 @@ describe('defineModel()', () => {
expect(content).toMatch('"count": {},') expect(content).toMatch('"count": {},')
expect(content).toMatch('"toString": { type: Function },') expect(content).toMatch('"toString": { type: Function },')
expect(content).toMatch( expect(content).toMatch(
'emits: ["update:modelValue", "update:count", "update:toString"],' 'emits: ["update:modelValue", "update:count", "update:toString"],',
) )
expect(content).toMatch( expect(content).toMatch(
`const modelValue = _useModel(__props, "modelValue")` `const modelValue = _useModel(__props, "modelValue")`,
) )
expect(content).toMatch(`const c = _useModel(__props, "count")`) expect(content).toMatch(`const c = _useModel(__props, "count")`)
expect(content).toMatch(`return { modelValue, c, toString }`) expect(content).toMatch(`return { modelValue, c, toString }`)
@ -31,7 +31,7 @@ describe('defineModel()', () => {
modelValue: BindingTypes.SETUP_REF, modelValue: BindingTypes.SETUP_REF,
count: BindingTypes.PROPS, count: BindingTypes.PROPS,
c: BindingTypes.SETUP_REF, c: BindingTypes.SETUP_REF,
toString: BindingTypes.SETUP_REF toString: BindingTypes.SETUP_REF,
}) })
}) })
@ -43,7 +43,7 @@ describe('defineModel()', () => {
defineEmits(['change']) defineEmits(['change'])
const count = defineModel({ default: 0 }) const count = defineModel({ default: 0 })
</script> </script>
` `,
) )
assertCode(content) assertCode(content)
expect(content).toMatch(`props: /*#__PURE__*/_mergeModels({ foo: String }`) expect(content).toMatch(`props: /*#__PURE__*/_mergeModels({ foo: String }`)
@ -53,7 +53,7 @@ describe('defineModel()', () => {
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
count: BindingTypes.SETUP_REF, count: BindingTypes.SETUP_REF,
foo: BindingTypes.PROPS, foo: BindingTypes.PROPS,
modelValue: BindingTypes.PROPS modelValue: BindingTypes.PROPS,
}) })
}) })
@ -64,7 +64,7 @@ describe('defineModel()', () => {
defineProps(['foo', 'bar']) defineProps(['foo', 'bar'])
const count = defineModel('count') const count = defineModel('count')
</script> </script>
` `,
) )
assertCode(content) assertCode(content)
expect(content).toMatch(`props: /*#__PURE__*/_mergeModels(['foo', 'bar'], { expect(content).toMatch(`props: /*#__PURE__*/_mergeModels(['foo', 'bar'], {
@ -75,7 +75,7 @@ describe('defineModel()', () => {
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS, foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS, bar: BindingTypes.PROPS,
count: BindingTypes.SETUP_REF count: BindingTypes.SETUP_REF,
}) })
}) })
@ -91,7 +91,7 @@ describe('defineModel()', () => {
const local = true const local = true
const hoist = defineModel('hoist', { local }) const hoist = defineModel('hoist', { local })
</script>` </script>`,
) )
assertCode(content) assertCode(content)
expect(content).toMatch(`_useModel(__props, "modelValue", { local: true })`) expect(content).toMatch(`_useModel(__props, "modelValue", { local: true })`)
@ -111,21 +111,21 @@ describe('defineModel()', () => {
const disabled = defineModel<number>('disabled', { required: false }) const disabled = defineModel<number>('disabled', { required: false })
const any = defineModel<any | boolean>('any') const any = defineModel<any | boolean>('any')
</script> </script>
` `,
) )
assertCode(content) assertCode(content)
expect(content).toMatch('"modelValue": { type: [Boolean, String] }') expect(content).toMatch('"modelValue": { type: [Boolean, String] }')
expect(content).toMatch('"count": { type: Number }') expect(content).toMatch('"count": { type: Number }')
expect(content).toMatch( expect(content).toMatch(
'"disabled": { type: Number, ...{ required: false } }' '"disabled": { type: Number, ...{ required: false } }',
) )
expect(content).toMatch('"any": { type: Boolean, skipCheck: true }') expect(content).toMatch('"any": { type: Boolean, skipCheck: true }')
expect(content).toMatch( expect(content).toMatch(
'emits: ["update:modelValue", "update:count", "update:disabled", "update:any"]' 'emits: ["update:modelValue", "update:count", "update:disabled", "update:any"]',
) )
expect(content).toMatch( expect(content).toMatch(
`const modelValue = _useModel(__props, "modelValue")` `const modelValue = _useModel(__props, "modelValue")`,
) )
expect(content).toMatch(`const count = _useModel(__props, "count")`) expect(content).toMatch(`const count = _useModel(__props, "count")`)
expect(content).toMatch(`const disabled = _useModel(__props, "disabled")`) expect(content).toMatch(`const disabled = _useModel(__props, "disabled")`)
@ -135,7 +135,7 @@ describe('defineModel()', () => {
modelValue: BindingTypes.SETUP_REF, modelValue: BindingTypes.SETUP_REF,
count: BindingTypes.SETUP_REF, count: BindingTypes.SETUP_REF,
disabled: BindingTypes.SETUP_REF, disabled: BindingTypes.SETUP_REF,
any: BindingTypes.SETUP_REF any: BindingTypes.SETUP_REF,
}) })
}) })
@ -150,21 +150,21 @@ describe('defineModel()', () => {
const optional = defineModel<string>('optional', { required: false }) const optional = defineModel<string>('optional', { required: false })
</script> </script>
`, `,
{ isProd: true } { isProd: true },
) )
assertCode(content) assertCode(content)
expect(content).toMatch('"modelValue": { type: Boolean }') expect(content).toMatch('"modelValue": { type: Boolean }')
expect(content).toMatch('"fn": {}') expect(content).toMatch('"fn": {}')
expect(content).toMatch( expect(content).toMatch(
'"fnWithDefault": { type: Function, ...{ default: () => null } },' '"fnWithDefault": { type: Function, ...{ default: () => null } },',
) )
expect(content).toMatch('"str": {}') expect(content).toMatch('"str": {}')
expect(content).toMatch('"optional": { required: false }') expect(content).toMatch('"optional": { required: false }')
expect(content).toMatch( expect(content).toMatch(
'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]' 'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]',
) )
expect(content).toMatch( expect(content).toMatch(
`const modelValue = _useModel(__props, "modelValue")` `const modelValue = _useModel(__props, "modelValue")`,
) )
expect(content).toMatch(`const fn = _useModel(__props, "fn")`) expect(content).toMatch(`const fn = _useModel(__props, "fn")`)
expect(content).toMatch(`const str = _useModel(__props, "str")`) expect(content).toMatch(`const str = _useModel(__props, "str")`)
@ -173,7 +173,7 @@ describe('defineModel()', () => {
fn: BindingTypes.SETUP_REF, fn: BindingTypes.SETUP_REF,
fnWithDefault: BindingTypes.SETUP_REF, fnWithDefault: BindingTypes.SETUP_REF,
str: BindingTypes.SETUP_REF, str: BindingTypes.SETUP_REF,
optional: BindingTypes.SETUP_REF optional: BindingTypes.SETUP_REF,
}) })
}) })
}) })

View File

@ -1,4 +1,4 @@
import { compileSFCScript as compile, assertCode } from '../utils' import { assertCode, compileSFCScript as compile } from '../utils'
describe('defineOptions()', () => { describe('defineOptions()', () => {
test('basic usage', () => { test('basic usage', () => {
@ -12,7 +12,7 @@ describe('defineOptions()', () => {
expect(content).not.toMatch('defineOptions') expect(content).not.toMatch('defineOptions')
// should include context options in default export // should include context options in default export
expect(content).toMatch( expect(content).toMatch(
`export default /*#__PURE__*/Object.assign({ name: 'FooApp' }, ` `export default /*#__PURE__*/Object.assign({ name: 'FooApp' }, `,
) )
}) })
@ -35,7 +35,7 @@ describe('defineOptions()', () => {
defineOptions({ name: 'FooApp' }) defineOptions({ name: 'FooApp' })
defineOptions({ name: 'BarApp' }) defineOptions({ name: 'BarApp' })
</script> </script>
`) `),
).toThrowError('[@vue/compiler-sfc] duplicate defineOptions() call') ).toThrowError('[@vue/compiler-sfc] duplicate defineOptions() call')
}) })
@ -45,9 +45,9 @@ describe('defineOptions()', () => {
<script setup> <script setup>
defineOptions({ props: { foo: String } }) defineOptions({ props: { foo: String } })
</script> </script>
`) `),
).toThrowError( ).toThrowError(
'[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.' '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.',
) )
expect(() => expect(() =>
@ -55,9 +55,9 @@ describe('defineOptions()', () => {
<script setup> <script setup>
defineOptions({ emits: ['update'] }) defineOptions({ emits: ['update'] })
</script> </script>
`) `),
).toThrowError( ).toThrowError(
'[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead.' '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead.',
) )
expect(() => expect(() =>
@ -65,9 +65,9 @@ describe('defineOptions()', () => {
<script setup> <script setup>
defineOptions({ expose: ['foo'] }) defineOptions({ expose: ['foo'] })
</script> </script>
`) `),
).toThrowError( ).toThrowError(
'[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead.' '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead.',
) )
expect(() => expect(() =>
@ -75,9 +75,9 @@ describe('defineOptions()', () => {
<script setup> <script setup>
defineOptions({ slots: ['foo'] }) defineOptions({ slots: ['foo'] })
</script> </script>
`) `),
).toThrowError( ).toThrowError(
'[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead.' '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead.',
) )
}) })
@ -87,9 +87,9 @@ describe('defineOptions()', () => {
<script setup lang="ts"> <script setup lang="ts">
defineOptions<{ name: 'FooApp' }>() defineOptions<{ name: 'FooApp' }>()
</script> </script>
`) `),
).toThrowError( ).toThrowError(
'[@vue/compiler-sfc] defineOptions() cannot accept type arguments' '[@vue/compiler-sfc] defineOptions() cannot accept type arguments',
) )
}) })
@ -99,9 +99,9 @@ describe('defineOptions()', () => {
<script setup lang="ts"> <script setup lang="ts">
defineOptions({ props: [] } as any) defineOptions({ props: [] } as any)
</script> </script>
`) `),
).toThrowError( ).toThrowError(
'[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.' '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.',
) )
}) })
@ -111,9 +111,9 @@ describe('defineOptions()', () => {
<script setup> <script setup>
defineOptions({ props: ['foo'] }) defineOptions({ props: ['foo'] })
</script> </script>
`) `),
).toThrowError( ).toThrowError(
'[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead' '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead',
) )
expect(() => expect(() =>
@ -121,9 +121,9 @@ describe('defineOptions()', () => {
<script setup> <script setup>
defineOptions({ emits: ['update'] }) defineOptions({ emits: ['update'] })
</script> </script>
`) `),
).toThrowError( ).toThrowError(
'[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead' '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead',
) )
expect(() => expect(() =>
@ -131,9 +131,9 @@ describe('defineOptions()', () => {
<script setup> <script setup>
defineOptions({ expose: ['foo'] }) defineOptions({ expose: ['foo'] })
</script> </script>
`) `),
).toThrowError( ).toThrowError(
'[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead' '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead',
) )
expect(() => expect(() =>
@ -141,9 +141,9 @@ describe('defineOptions()', () => {
<script setup lang="ts"> <script setup lang="ts">
defineOptions({ slots: Object }) defineOptions({ slots: Object })
</script> </script>
`) `),
).toThrowError( ).toThrowError(
'[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead' '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead',
) )
}) })
}) })

View File

@ -1,5 +1,5 @@
import { BindingTypes } from '@vue/compiler-core' import { BindingTypes } from '@vue/compiler-core'
import { compileSFCScript as compile, assertCode } from '../utils' import { assertCode, compileSFCScript as compile } from '../utils'
describe('defineProps', () => { describe('defineProps', () => {
test('basic usage', () => { test('basic usage', () => {
@ -17,7 +17,7 @@ const bar = 1
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS, foo: BindingTypes.PROPS,
bar: BindingTypes.LITERAL_CONST, bar: BindingTypes.LITERAL_CONST,
props: BindingTypes.SETUP_REACTIVE_CONST props: BindingTypes.SETUP_REACTIVE_CONST,
}) })
// should remove defineOptions import and call // should remove defineOptions import and call
@ -146,11 +146,11 @@ const props = defineProps({ foo: String })
expect(content).toMatch(`symbol: { type: Symbol, required: true }`) expect(content).toMatch(`symbol: { type: Symbol, required: true }`)
expect(content).toMatch(`error: { type: Error, required: true }`) expect(content).toMatch(`error: { type: Error, required: true }`)
expect(content).toMatch( expect(content).toMatch(
`objectOrFn: { type: [Function, Object], required: true },` `objectOrFn: { type: [Function, Object], required: true },`,
) )
expect(content).toMatch(`extract: { type: Number, required: true }`) expect(content).toMatch(`extract: { type: Number, required: true }`)
expect(content).toMatch( expect(content).toMatch(
`exclude: { type: [Number, Boolean], required: true }` `exclude: { type: [Number, Boolean], required: true }`,
) )
expect(content).toMatch(`uppercase: { type: String, required: true }`) expect(content).toMatch(`uppercase: { type: String, required: true }`)
expect(content).toMatch(`params: { type: Array, required: true }`) expect(content).toMatch(`params: { type: Array, required: true }`)
@ -158,10 +158,10 @@ const props = defineProps({ foo: String })
expect(content).toMatch(`union: { type: [String, Number], required: true }`) expect(content).toMatch(`union: { type: [String, Number], required: true }`)
expect(content).toMatch(`literalUnion: { type: String, required: true }`) expect(content).toMatch(`literalUnion: { type: String, required: true }`)
expect(content).toMatch( expect(content).toMatch(
`literalUnionNumber: { type: Number, required: true }` `literalUnionNumber: { type: Number, required: true }`,
) )
expect(content).toMatch( expect(content).toMatch(
`literalUnionMixed: { type: [String, Number, Boolean], required: true }` `literalUnionMixed: { type: [String, Number, Boolean], required: true }`,
) )
expect(content).toMatch(`intersection: { type: Object, required: true }`) expect(content).toMatch(`intersection: { type: Object, required: true }`)
expect(content).toMatch(`intersection2: { type: String, required: true }`) expect(content).toMatch(`intersection2: { type: String, required: true }`)
@ -171,13 +171,13 @@ const props = defineProps({ foo: String })
expect(content).toMatch(`unknownUnion: { type: null, required: true }`) expect(content).toMatch(`unknownUnion: { type: null, required: true }`)
// intersection containing unknown type: narrow to the known types // intersection containing unknown type: narrow to the known types
expect(content).toMatch( expect(content).toMatch(
`unknownIntersection: { type: Object, required: true },` `unknownIntersection: { type: Object, required: true },`,
) )
expect(content).toMatch( expect(content).toMatch(
`unknownUnionWithBoolean: { type: Boolean, required: true, skipCheck: true },` `unknownUnionWithBoolean: { type: Boolean, required: true, skipCheck: true },`,
) )
expect(content).toMatch( expect(content).toMatch(
`unknownUnionWithFunction: { type: Function, required: true, skipCheck: true }` `unknownUnionWithFunction: { type: Function, required: true, skipCheck: true }`,
) )
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
string: BindingTypes.PROPS, string: BindingTypes.PROPS,
@ -218,7 +218,7 @@ const props = defineProps({ foo: String })
unknownUnion: BindingTypes.PROPS, unknownUnion: BindingTypes.PROPS,
unknownIntersection: BindingTypes.PROPS, unknownIntersection: BindingTypes.PROPS,
unknownUnionWithBoolean: BindingTypes.PROPS, unknownUnionWithBoolean: BindingTypes.PROPS,
unknownUnionWithFunction: BindingTypes.PROPS unknownUnionWithFunction: BindingTypes.PROPS,
}) })
}) })
@ -232,7 +232,7 @@ const props = defineProps({ foo: String })
assertCode(content) assertCode(content)
expect(content).toMatch(`x: { type: Number, required: false }`) expect(content).toMatch(`x: { type: Number, required: false }`)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
x: BindingTypes.PROPS x: BindingTypes.PROPS,
}) })
}) })
@ -257,7 +257,7 @@ const props = defineProps({ foo: String })
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
x: BindingTypes.PROPS, x: BindingTypes.PROPS,
y: BindingTypes.PROPS, y: BindingTypes.PROPS,
z: BindingTypes.PROPS z: BindingTypes.PROPS,
}) })
}) })
@ -271,7 +271,7 @@ const props = defineProps({ foo: String })
assertCode(content) assertCode(content)
expect(content).toMatch(`x: { type: Number, required: false }`) expect(content).toMatch(`x: { type: Number, required: false }`)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
x: BindingTypes.PROPS x: BindingTypes.PROPS,
}) })
}) })
@ -287,7 +287,7 @@ const props = defineProps({ foo: String })
assertCode(content) assertCode(content)
expect(content).toMatch(`x: { type: Number, required: false }`) expect(content).toMatch(`x: { type: Number, required: false }`)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
x: BindingTypes.PROPS x: BindingTypes.PROPS,
}) })
}) })
@ -301,7 +301,7 @@ const props = defineProps({ foo: String })
assertCode(content) assertCode(content)
expect(content).toMatch(`x: { type: Number, required: false }`) expect(content).toMatch(`x: { type: Number, required: false }`)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
x: BindingTypes.PROPS x: BindingTypes.PROPS,
}) })
}) })
@ -315,7 +315,7 @@ const props = defineProps({ foo: String })
assertCode(content) assertCode(content)
expect(content).toMatch(`x: { type: Number, required: false }`) expect(content).toMatch(`x: { type: Number, required: false }`)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
x: BindingTypes.PROPS x: BindingTypes.PROPS,
}) })
}) })
@ -328,7 +328,7 @@ const props = defineProps({ foo: String })
expect(content).toMatch(`props: ['foo']`) expect(content).toMatch(`props: ['foo']`)
assertCode(content) assertCode(content)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS foo: BindingTypes.PROPS,
}) })
}) })
@ -354,21 +354,21 @@ const props = defineProps({ foo: String })
`) `)
assertCode(content) assertCode(content)
expect(content).toMatch( expect(content).toMatch(
`foo: { type: String, required: false, default: 'hi' }` `foo: { type: String, required: false, default: 'hi' }`,
) )
expect(content).toMatch(`bar: { type: Number, required: false }`) expect(content).toMatch(`bar: { type: Number, required: false }`)
expect(content).toMatch(`baz: { type: Boolean, required: true }`) expect(content).toMatch(`baz: { type: Boolean, required: true }`)
expect(content).toMatch( expect(content).toMatch(
`qux: { type: Function, required: false, default() { return 1 } }` `qux: { type: Function, required: false, default() { return 1 } }`,
) )
expect(content).toMatch( expect(content).toMatch(
`quux: { type: Function, required: false, default() { } }` `quux: { type: Function, required: false, default() { } }`,
) )
expect(content).toMatch( expect(content).toMatch(
`quuxx: { type: Promise, required: false, async default() { return await Promise.resolve('hi') } }` `quuxx: { type: Promise, required: false, async default() { return await Promise.resolve('hi') } }`,
) )
expect(content).toMatch( expect(content).toMatch(
`fred: { type: String, required: false, get default() { return 'fred' } }` `fred: { type: String, required: false, get default() { return 'fred' } }`,
) )
expect(content).toMatch(`const props = __props`) expect(content).toMatch(`const props = __props`)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
@ -379,7 +379,7 @@ const props = defineProps({ foo: String })
quux: BindingTypes.PROPS, quux: BindingTypes.PROPS,
quuxx: BindingTypes.PROPS, quuxx: BindingTypes.PROPS,
fred: BindingTypes.PROPS, fred: BindingTypes.PROPS,
props: BindingTypes.SETUP_CONST props: BindingTypes.SETUP_CONST,
}) })
}) })
@ -415,7 +415,7 @@ const props = defineProps({ foo: String })
}) })
</script> </script>
`, `,
{ isProd: true } { isProd: true },
) )
assertCode(content) assertCode(content)
expect(content).toMatch(`const props = __props`) expect(content).toMatch(`const props = __props`)
@ -446,7 +446,7 @@ const props = defineProps({ foo: String })
foo: { type: String, required: false }, foo: { type: String, required: false },
bar: { type: Number, required: false }, bar: { type: Number, required: false },
baz: { type: Boolean, required: true } baz: { type: Boolean, required: true }
}, { ...defaults })`.trim() }, { ...defaults })`.trim(),
) )
}) })
@ -469,7 +469,7 @@ const props = defineProps({ foo: String })
foo: { type: String, required: false }, foo: { type: String, required: false },
bar: { type: Number, required: false }, bar: { type: Number, required: false },
baz: { type: Boolean, required: true } baz: { type: Boolean, required: true }
}, defaults)`.trim() }, defaults)`.trim(),
) )
}) })
@ -487,7 +487,7 @@ const props = defineProps({ foo: String })
}>(), { ...defaults }) }>(), { ...defaults })
</script> </script>
`, `,
{ isProd: true } { isProd: true },
) )
assertCode(content) assertCode(content)
expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`) expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
@ -498,7 +498,7 @@ const props = defineProps({ foo: String })
bar: { type: Boolean }, bar: { type: Boolean },
baz: { type: [Boolean, Function] }, baz: { type: [Boolean, Function] },
qux: {} qux: {}
}, { ...defaults })`.trim() }, { ...defaults })`.trim(),
) )
}) })
@ -520,7 +520,7 @@ const props = defineProps({ foo: String })
foo: { type: Function, required: false } foo: { type: Function, required: false }
}, { }, {
['fo' + 'o']() { return 'foo' } ['fo' + 'o']() { return 'foo' }
})`.trim() })`.trim(),
) )
}) })
@ -533,8 +533,8 @@ const props = defineProps({ foo: String })
foo: Foo foo: Foo
}>() }>()
</script>`, </script>`,
{ hoistStatic: true } { hoistStatic: true },
).content ).content,
).toMatch(`foo: { type: Number`) ).toMatch(`foo: { type: Number`)
expect( expect(
@ -545,8 +545,8 @@ const props = defineProps({ foo: String })
foo: Foo foo: Foo
}>() }>()
</script>`, </script>`,
{ hoistStatic: true } { hoistStatic: true },
).content ).content,
).toMatch(`foo: { type: String`) ).toMatch(`foo: { type: String`)
expect( expect(
@ -557,8 +557,8 @@ const props = defineProps({ foo: String })
foo: Foo foo: Foo
}>() }>()
</script>`, </script>`,
{ hoistStatic: true } { hoistStatic: true },
).content ).content,
).toMatch(`foo: { type: [String, Number]`) ).toMatch(`foo: { type: [String, Number]`)
expect( expect(
@ -569,8 +569,8 @@ const props = defineProps({ foo: String })
foo: Foo foo: Foo
}>() }>()
</script>`, </script>`,
{ hoistStatic: true } { hoistStatic: true },
).content ).content,
).toMatch(`foo: { type: Number`) ).toMatch(`foo: { type: Number`)
}) })
@ -585,7 +585,7 @@ const props = defineProps({ foo: String })
`) `)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
bar: BindingTypes.SETUP_REF, bar: BindingTypes.SETUP_REF,
computed: BindingTypes.SETUP_CONST computed: BindingTypes.SETUP_CONST,
}) })
}) })
@ -596,7 +596,7 @@ const props = defineProps({ foo: String })
const { foo } = defineProps<{ const { foo } = defineProps<{
foo: Foo foo: Foo
}>() }>()
</script>` </script>`,
) )
expect(content).toMatch(`const { foo } = __props`) expect(content).toMatch(`const { foo } = __props`)
assertCode(content) assertCode(content)
@ -649,7 +649,7 @@ const props = defineProps({ foo: String })
assertCode(content) assertCode(content)
expect(content).toMatch(`"spa ce": { type: null, required: true }`) expect(content).toMatch(`"spa ce": { type: null, required: true }`)
expect(content).toMatch( expect(content).toMatch(
`"exclamation!mark": { type: null, required: true }` `"exclamation!mark": { type: null, required: true }`,
) )
expect(content).toMatch(`"double\\"quote": { type: null, required: true }`) expect(content).toMatch(`"double\\"quote": { type: null, required: true }`)
expect(content).toMatch(`"hash#tag": { type: null, required: true }`) expect(content).toMatch(`"hash#tag": { type: null, required: true }`)
@ -670,7 +670,7 @@ const props = defineProps({ foo: String })
expect(content).toMatch(`"question?mark": { type: null, required: true }`) expect(content).toMatch(`"question?mark": { type: null, required: true }`)
expect(content).toMatch(`"at@sign": { type: null, required: true }`) expect(content).toMatch(`"at@sign": { type: null, required: true }`)
expect(content).toMatch( expect(content).toMatch(
`"square[brack]ets": { type: null, required: true }` `"square[brack]ets": { type: null, required: true }`,
) )
expect(content).toMatch(`"back\\\\slash": { type: null, required: true }`) expect(content).toMatch(`"back\\\\slash": { type: null, required: true }`)
expect(content).toMatch(`"ca^ret": { type: null, required: true }`) expect(content).toMatch(`"ca^ret": { type: null, required: true }`)
@ -707,7 +707,7 @@ const props = defineProps({ foo: String })
'curly{bra}ces': BindingTypes.PROPS, 'curly{bra}ces': BindingTypes.PROPS,
'pi|pe': BindingTypes.PROPS, 'pi|pe': BindingTypes.PROPS,
'til~de': BindingTypes.PROPS, 'til~de': BindingTypes.PROPS,
'da-sh': BindingTypes.PROPS 'da-sh': BindingTypes.PROPS,
}) })
}) })
@ -718,7 +718,7 @@ const props = defineProps({ foo: String })
const props = defineProps<{ foo: number}>() const props = defineProps<{ foo: number}>()
</script>`, </script>`,
{ isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) }, { isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) },
{ filename: 'app.ce.vue' } { filename: 'app.ce.vue' },
) )
expect(content).toMatch(`foo: {type: Number}`) expect(content).toMatch(`foo: {type: Number}`)
@ -736,7 +736,7 @@ const props = defineProps({ foo: String })
}); });
</script>`, </script>`,
{ isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) }, { isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) },
{ filename: 'app.ce.vue' } { filename: 'app.ce.vue' },
) )
expect(content).toMatch(`foo: { default: 5.5, type: Number }`) expect(content).toMatch(`foo: { default: 5.5, type: Number }`)
assertCode(content) assertCode(content)

View File

@ -1,13 +1,13 @@
import { BindingTypes } from '@vue/compiler-core' import { BindingTypes } from '@vue/compiler-core'
import { SFCScriptCompileOptions } from '../../src' import type { SFCScriptCompileOptions } from '../../src'
import { compileSFCScript, assertCode } from '../utils' import { assertCode, compileSFCScript } from '../utils'
describe('sfc reactive props destructure', () => { describe('sfc reactive props destructure', () => {
function compile(src: string, options?: Partial<SFCScriptCompileOptions>) { function compile(src: string, options?: Partial<SFCScriptCompileOptions>) {
return compileSFCScript(src, { return compileSFCScript(src, {
inlineTemplate: true, inlineTemplate: true,
propsDestructure: true, propsDestructure: true,
...options ...options,
}) })
} }
@ -24,7 +24,7 @@ describe('sfc reactive props destructure', () => {
expect(content).toMatch(`_toDisplayString(__props.foo)`) expect(content).toMatch(`_toDisplayString(__props.foo)`)
assertCode(content) assertCode(content)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS foo: BindingTypes.PROPS,
}) })
}) })
@ -44,7 +44,7 @@ describe('sfc reactive props destructure', () => {
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS, foo: BindingTypes.PROPS,
bar: BindingTypes.LITERAL_CONST, bar: BindingTypes.LITERAL_CONST,
hello: BindingTypes.LITERAL_CONST hello: BindingTypes.LITERAL_CONST,
}) })
}) })
@ -65,7 +65,7 @@ describe('sfc reactive props destructure', () => {
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS, foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS, bar: BindingTypes.PROPS,
test: BindingTypes.SETUP_CONST test: BindingTypes.SETUP_CONST,
}) })
}) })
@ -115,11 +115,11 @@ describe('sfc reactive props destructure', () => {
`) `)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
__propsAliases: { __propsAliases: {
fooBar: 'foo:bar' fooBar: 'foo:bar',
}, },
foo: BindingTypes.PROPS, foo: BindingTypes.PROPS,
'foo:bar': BindingTypes.PROPS, 'foo:bar': BindingTypes.PROPS,
fooBar: BindingTypes.PROPS_ALIASED fooBar: BindingTypes.PROPS_ALIASED,
}) })
expect(content).toMatch(` expect(content).toMatch(`
@ -159,13 +159,13 @@ describe('sfc reactive props destructure', () => {
`) `)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
__propsAliases: { __propsAliases: {
fooBar: 'foo:bar' fooBar: 'foo:bar',
}, },
foo: BindingTypes.PROPS, foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS, bar: BindingTypes.PROPS,
'foo:bar': BindingTypes.PROPS, 'foo:bar': BindingTypes.PROPS,
fooBar: BindingTypes.PROPS_ALIASED, fooBar: BindingTypes.PROPS_ALIASED,
'onUpdate:modelValue': BindingTypes.PROPS 'onUpdate:modelValue': BindingTypes.PROPS,
}) })
expect(content).toMatch(` expect(content).toMatch(`
props: { props: {
@ -184,7 +184,7 @@ describe('sfc reactive props destructure', () => {
const { foo = 1, bar = {}, func = () => {} } = defineProps<{ foo?: number, bar?: object, baz?: any, boola?: boolean, boolb?: boolean | number, func?: Function }>() const { foo = 1, bar = {}, func = () => {} } = defineProps<{ foo?: number, bar?: object, baz?: any, boola?: boolean, boolb?: boolean | number, func?: Function }>()
</script> </script>
`, `,
{ isProd: true } { isProd: true },
) )
assertCode(content) assertCode(content)
// literals can be used as-is, non-literals are always returned from a // literals can be used as-is, non-literals are always returned from a
@ -220,8 +220,8 @@ describe('sfc reactive props destructure', () => {
foo: BindingTypes.PROPS, foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS_ALIASED, bar: BindingTypes.PROPS_ALIASED,
__propsAliases: { __propsAliases: {
bar: 'foo' bar: 'foo',
} },
}) })
}) })
@ -242,8 +242,8 @@ describe('sfc reactive props destructure', () => {
'foo.bar': BindingTypes.PROPS, 'foo.bar': BindingTypes.PROPS,
fooBar: BindingTypes.PROPS_ALIASED, fooBar: BindingTypes.PROPS_ALIASED,
__propsAliases: { __propsAliases: {
fooBar: 'foo.bar' fooBar: 'foo.bar',
} },
}) })
}) })
@ -254,14 +254,14 @@ describe('sfc reactive props destructure', () => {
</script> </script>
`) `)
expect(content).toMatch( expect(content).toMatch(
`const rest = _createPropsRestProxy(__props, ["foo","bar"])` `const rest = _createPropsRestProxy(__props, ["foo","bar"])`,
) )
assertCode(content) assertCode(content)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS, foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS, bar: BindingTypes.PROPS,
baz: BindingTypes.PROPS, baz: BindingTypes.PROPS,
rest: BindingTypes.SETUP_REACTIVE_CONST rest: BindingTypes.SETUP_REACTIVE_CONST,
}) })
}) })
@ -279,7 +279,7 @@ describe('sfc reactive props destructure', () => {
expect(content).toMatch(`_toDisplayString(__props.foo)`) expect(content).toMatch(`_toDisplayString(__props.foo)`)
assertCode(content) assertCode(content)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS foo: BindingTypes.PROPS,
}) })
}) })
@ -339,22 +339,22 @@ describe('sfc reactive props destructure', () => {
test('should error on deep destructure', () => { test('should error on deep destructure', () => {
expect(() => expect(() =>
compile( compile(
`<script setup>const { foo: [bar] } = defineProps(['foo'])</script>` `<script setup>const { foo: [bar] } = defineProps(['foo'])</script>`,
) ),
).toThrow(`destructure does not support nested patterns`) ).toThrow(`destructure does not support nested patterns`)
expect(() => expect(() =>
compile( compile(
`<script setup>const { foo: { bar } } = defineProps(['foo'])</script>` `<script setup>const { foo: { bar } } = defineProps(['foo'])</script>`,
) ),
).toThrow(`destructure does not support nested patterns`) ).toThrow(`destructure does not support nested patterns`)
}) })
test('should error on computed key', () => { test('should error on computed key', () => {
expect(() => expect(() =>
compile( compile(
`<script setup>const { [foo]: bar } = defineProps(['foo'])</script>` `<script setup>const { [foo]: bar } = defineProps(['foo'])</script>`,
) ),
).toThrow(`destructure cannot use computed key`) ).toThrow(`destructure cannot use computed key`)
}) })
@ -363,8 +363,8 @@ describe('sfc reactive props destructure', () => {
compile( compile(
`<script setup lang="ts"> `<script setup lang="ts">
const { foo } = withDefaults(defineProps<{ foo: string }>(), { foo: 'foo' }) const { foo } = withDefaults(defineProps<{ foo: string }>(), { foo: 'foo' })
</script>` </script>`,
) ),
).toThrow(`withDefaults() is unnecessary when using destructure`) ).toThrow(`withDefaults() is unnecessary when using destructure`)
}) })
@ -376,8 +376,8 @@ describe('sfc reactive props destructure', () => {
const { const {
foo = () => x foo = () => x
} = defineProps(['foo']) } = defineProps(['foo'])
</script>` </script>`,
) ),
).toThrow(`cannot reference locally declared variables`) ).toThrow(`cannot reference locally declared variables`)
}) })
@ -387,8 +387,8 @@ describe('sfc reactive props destructure', () => {
`<script setup> `<script setup>
const { foo } = defineProps(['foo']) const { foo } = defineProps(['foo'])
foo = 'bar' foo = 'bar'
</script>` </script>`,
) ),
).toThrow(`Cannot assign to destructured props`) ).toThrow(`Cannot assign to destructured props`)
expect(() => expect(() =>
@ -396,8 +396,8 @@ describe('sfc reactive props destructure', () => {
`<script setup> `<script setup>
let { foo } = defineProps(['foo']) let { foo } = defineProps(['foo'])
foo = 'bar' foo = 'bar'
</script>` </script>`,
) ),
).toThrow(`Cannot assign to destructured props`) ).toThrow(`Cannot assign to destructured props`)
}) })
@ -408,10 +408,10 @@ describe('sfc reactive props destructure', () => {
import { watch } from 'vue' import { watch } from 'vue'
const { foo } = defineProps(['foo']) const { foo } = defineProps(['foo'])
watch(foo, () => {}) watch(foo, () => {})
</script>` </script>`,
) ),
).toThrow( ).toThrow(
`"foo" is a destructured prop and should not be passed directly to watch().` `"foo" is a destructured prop and should not be passed directly to watch().`,
) )
expect(() => expect(() =>
@ -420,10 +420,10 @@ describe('sfc reactive props destructure', () => {
import { watch as w } from 'vue' import { watch as w } from 'vue'
const { foo } = defineProps(['foo']) const { foo } = defineProps(['foo'])
w(foo, () => {}) w(foo, () => {})
</script>` </script>`,
) ),
).toThrow( ).toThrow(
`"foo" is a destructured prop and should not be passed directly to watch().` `"foo" is a destructured prop and should not be passed directly to watch().`,
) )
expect(() => expect(() =>
@ -432,10 +432,10 @@ describe('sfc reactive props destructure', () => {
import { toRef } from 'vue' import { toRef } from 'vue'
const { foo } = defineProps(['foo']) const { foo } = defineProps(['foo'])
toRef(foo) toRef(foo)
</script>` </script>`,
) ),
).toThrow( ).toThrow(
`"foo" is a destructured prop and should not be passed directly to toRef().` `"foo" is a destructured prop and should not be passed directly to toRef().`,
) )
expect(() => expect(() =>
@ -444,10 +444,10 @@ describe('sfc reactive props destructure', () => {
import { toRef as r } from 'vue' import { toRef as r } from 'vue'
const { foo } = defineProps(['foo']) const { foo } = defineProps(['foo'])
r(foo) r(foo)
</script>` </script>`,
) ),
).toThrow( ).toThrow(
`"foo" is a destructured prop and should not be passed directly to toRef().` `"foo" is a destructured prop and should not be passed directly to toRef().`,
) )
}) })
@ -457,8 +457,8 @@ describe('sfc reactive props destructure', () => {
compile( compile(
`<script setup lang="ts"> `<script setup lang="ts">
const { foo = 'hello' } = defineProps<{ foo?: number }>() const { foo = 'hello' } = defineProps<{ foo?: number }>()
</script>` </script>`,
) ),
).toThrow(`Default value of prop "foo" does not match declared type.`) ).toThrow(`Default value of prop "foo" does not match declared type.`)
}) })
@ -472,8 +472,8 @@ describe('sfc reactive props destructure', () => {
const { error: e, info } = useRequest(); const { error: e, info } = useRequest();
watch(e, () => {}); watch(e, () => {});
watch(info, () => {}); watch(info, () => {});
</script>` </script>`,
) ),
).not.toThrowError() ).not.toThrowError()
}) })
}) })

View File

@ -1,4 +1,4 @@
import { compileSFCScript as compile, assertCode } from '../utils' import { assertCode, compileSFCScript as compile } from '../utils'
describe('defineSlots()', () => { describe('defineSlots()', () => {
test('basic usage', () => { test('basic usage', () => {

View File

@ -1,13 +1,13 @@
import { BindingTypes } from '@vue/compiler-core' import { BindingTypes } from '@vue/compiler-core'
import { SFCScriptCompileOptions } from '../../src' import type { SFCScriptCompileOptions } from '../../src'
import { compileSFCScript, assertCode } from '../utils' import { assertCode, compileSFCScript } from '../utils'
describe('sfc hoist static', () => { describe('sfc hoist static', () => {
function compile(src: string, options?: Partial<SFCScriptCompileOptions>) { function compile(src: string, options?: Partial<SFCScriptCompileOptions>) {
return compileSFCScript(src, { return compileSFCScript(src, {
inlineTemplate: true, inlineTemplate: true,
hoistStatic: true, hoistStatic: true,
...options ...options,
}) })
} }
@ -34,7 +34,7 @@ describe('sfc hoist static', () => {
boolean: BindingTypes.LITERAL_CONST, boolean: BindingTypes.LITERAL_CONST,
nil: BindingTypes.LITERAL_CONST, nil: BindingTypes.LITERAL_CONST,
bigint: BindingTypes.LITERAL_CONST, bigint: BindingTypes.LITERAL_CONST,
template: BindingTypes.LITERAL_CONST template: BindingTypes.LITERAL_CONST,
}) })
assertCode(content) assertCode(content)
}) })
@ -57,7 +57,7 @@ describe('sfc hoist static', () => {
binary: BindingTypes.LITERAL_CONST, binary: BindingTypes.LITERAL_CONST,
conditional: BindingTypes.LITERAL_CONST, conditional: BindingTypes.LITERAL_CONST,
unary: BindingTypes.LITERAL_CONST, unary: BindingTypes.LITERAL_CONST,
sequence: BindingTypes.LITERAL_CONST sequence: BindingTypes.LITERAL_CONST,
}) })
assertCode(content) assertCode(content)
}) })
@ -79,7 +79,7 @@ describe('sfc hoist static', () => {
expect(content.startsWith(hoistCode)).toBe(true) expect(content.startsWith(hoistCode)).toBe(true)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS, foo: BindingTypes.PROPS,
defaultValue: BindingTypes.LITERAL_CONST defaultValue: BindingTypes.LITERAL_CONST,
}) })
assertCode(content) assertCode(content)
}) })
@ -100,7 +100,7 @@ describe('sfc hoist static', () => {
KEY1: BindingTypes.SETUP_LET, KEY1: BindingTypes.SETUP_LET,
KEY2: BindingTypes.SETUP_LET, KEY2: BindingTypes.SETUP_LET,
regex: BindingTypes.SETUP_CONST, regex: BindingTypes.SETUP_CONST,
undef: BindingTypes.SETUP_MAYBE_REF undef: BindingTypes.SETUP_MAYBE_REF,
}) })
expect(content).toMatch(`setup(__props) {\n\n ${code}`) expect(content).toMatch(`setup(__props) {\n\n ${code}`)
assertCode(content) assertCode(content)
@ -131,7 +131,7 @@ describe('sfc hoist static', () => {
KEY4: BindingTypes.SETUP_CONST, KEY4: BindingTypes.SETUP_CONST,
KEY5: BindingTypes.SETUP_CONST, KEY5: BindingTypes.SETUP_CONST,
KEY6: BindingTypes.SETUP_CONST, KEY6: BindingTypes.SETUP_CONST,
i: BindingTypes.SETUP_LET i: BindingTypes.SETUP_LET,
}) })
expect(content).toMatch(`setup(__props) {\n\n ${code}`) expect(content).toMatch(`setup(__props) {\n\n ${code}`)
assertCode(content) assertCode(content)
@ -149,7 +149,7 @@ describe('sfc hoist static', () => {
`) `)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
arr: BindingTypes.SETUP_CONST, arr: BindingTypes.SETUP_CONST,
obj: BindingTypes.SETUP_CONST obj: BindingTypes.SETUP_CONST,
}) })
expect(content).toMatch(`setup(__props) {\n\n ${code}`) expect(content).toMatch(`setup(__props) {\n\n ${code}`)
assertCode(content) assertCode(content)
@ -169,7 +169,7 @@ describe('sfc hoist static', () => {
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
Foo: BindingTypes.SETUP_CONST, Foo: BindingTypes.SETUP_CONST,
fn: BindingTypes.SETUP_CONST, fn: BindingTypes.SETUP_CONST,
fn2: BindingTypes.SETUP_CONST fn2: BindingTypes.SETUP_CONST,
}) })
expect(content).toMatch(`setup(__props) {\n\n ${code}`) expect(content).toMatch(`setup(__props) {\n\n ${code}`)
assertCode(content) assertCode(content)
@ -185,7 +185,7 @@ describe('sfc hoist static', () => {
</script> </script>
`) `)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.SETUP_CONST foo: BindingTypes.SETUP_CONST,
}) })
assertCode(content) assertCode(content)
}) })
@ -197,10 +197,10 @@ describe('sfc hoist static', () => {
const foo = 'bar' const foo = 'bar'
</script> </script>
`, `,
{ hoistStatic: false } { hoistStatic: false },
) )
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: BindingTypes.SETUP_CONST foo: BindingTypes.SETUP_CONST,
}) })
assertCode(content) assertCode(content)
}) })
@ -212,7 +212,7 @@ describe('sfc hoist static', () => {
const foo = 'bar' const foo = 'bar'
</script> </script>
<template>{{ foo }}</template> <template>{{ foo }}</template>
` `,
) )
expect(content).toMatch('_toDisplayString(foo)') expect(content).toMatch('_toDisplayString(foo)')
}) })

View File

@ -1,13 +1,13 @@
import { normalize } from 'node:path' import { normalize } from 'node:path'
import { Identifier } from '@babel/types' import type { Identifier } from '@babel/types'
import { SFCScriptCompileOptions, parse } from '../../src' import { type SFCScriptCompileOptions, parse } from '../../src'
import { ScriptCompileContext } from '../../src/script/context' import { ScriptCompileContext } from '../../src/script/context'
import { import {
inferRuntimeType, inferRuntimeType,
invalidateTypeCache, invalidateTypeCache,
recordImports, recordImports,
registerTS,
resolveTypeElements, resolveTypeElements,
registerTS
} from '../../src/script/resolveType' } from '../../src/script/resolveType'
import ts from 'typescript' import ts from 'typescript'
@ -25,7 +25,7 @@ describe('resolveType', () => {
expect(props).toStrictEqual({ expect(props).toStrictEqual({
foo: ['Number'], foo: ['Number'],
bar: ['Function'], bar: ['Function'],
baz: ['String'] baz: ['String'],
}) })
expect(calls?.length).toBe(2) expect(calls?.length).toBe(2)
}) })
@ -35,9 +35,9 @@ describe('resolveType', () => {
resolve(` resolve(`
type Aliased = { foo: number } type Aliased = { foo: number }
defineProps<Aliased>() defineProps<Aliased>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['Number'] foo: ['Number'],
}) })
}) })
@ -46,9 +46,9 @@ describe('resolveType', () => {
resolve(` resolve(`
export type Aliased = { foo: number } export type Aliased = { foo: number }
defineProps<Aliased>() defineProps<Aliased>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['Number'] foo: ['Number'],
}) })
}) })
@ -57,9 +57,9 @@ describe('resolveType', () => {
resolve(` resolve(`
interface Aliased { foo: number } interface Aliased { foo: number }
defineProps<Aliased>() defineProps<Aliased>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['Number'] foo: ['Number'],
}) })
}) })
@ -68,9 +68,9 @@ describe('resolveType', () => {
resolve(` resolve(`
export interface Aliased { foo: number } export interface Aliased { foo: number }
defineProps<Aliased>() defineProps<Aliased>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['Number'] foo: ['Number'],
}) })
}) })
@ -82,12 +82,12 @@ describe('resolveType', () => {
interface C { c: string } interface C { c: string }
interface Aliased extends B, C { foo: number } interface Aliased extends B, C { foo: number }
defineProps<Aliased>() defineProps<Aliased>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
a: ['Function'], a: ['Function'],
b: ['Boolean'], b: ['Boolean'],
c: ['String'], c: ['String'],
foo: ['Number'] foo: ['Number'],
}) })
}) })
@ -96,9 +96,9 @@ describe('resolveType', () => {
resolve(` resolve(`
class Foo {} class Foo {}
defineProps<{ foo: Foo }>() defineProps<{ foo: Foo }>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['Object'] foo: ['Object'],
}) })
}) })
@ -106,7 +106,7 @@ describe('resolveType', () => {
expect( expect(
resolve(` resolve(`
defineProps<(e: 'foo') => void>() defineProps<(e: 'foo') => void>()
`).calls?.length `).calls?.length,
).toBe(1) ).toBe(1)
}) })
@ -115,7 +115,7 @@ describe('resolveType', () => {
resolve(` resolve(`
type Fn = (e: 'foo') => void type Fn = (e: 'foo') => void
defineProps<Fn>() defineProps<Fn>()
`).calls?.length `).calls?.length,
).toBe(1) ).toBe(1)
}) })
@ -126,13 +126,13 @@ describe('resolveType', () => {
type Bar = { bar: string } type Bar = { bar: string }
type Baz = { bar: string | boolean } type Baz = { bar: string | boolean }
defineProps<{ self: any } & Foo & Bar & Baz>() defineProps<{ self: any } & Foo & Bar & Baz>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
self: ['Unknown'], self: ['Unknown'],
foo: ['Number'], foo: ['Number'],
// both Bar & Baz has 'bar', but Baz['bar] is wider so it should be // both Bar & Baz has 'bar', but Baz['bar] is wider so it should be
// preferred // preferred
bar: ['String', 'Boolean'] bar: ['String', 'Boolean'],
}) })
}) })
@ -156,12 +156,12 @@ describe('resolveType', () => {
} }
defineProps<CommonProps & ConditionalProps>() defineProps<CommonProps & ConditionalProps>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
size: ['String'], size: ['String'],
color: ['String', 'Number'], color: ['String', 'Number'],
appearance: ['String'], appearance: ['String'],
note: ['String'] note: ['String'],
}) })
}) })
@ -173,12 +173,12 @@ describe('resolveType', () => {
defineProps<{ defineProps<{
[\`_\${T}_\${S}_\`]: string [\`_\${T}_\${S}_\`]: string
}>() }>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
_foo_x_: ['String'], _foo_x_: ['String'],
_foo_y_: ['String'], _foo_y_: ['String'],
_bar_x_: ['String'], _bar_x_: ['String'],
_bar_y_: ['String'] _bar_y_: ['String'],
}) })
}) })
@ -195,7 +195,7 @@ describe('resolveType', () => {
} & { } & {
[K in \`x\${T}\`]: string [K in \`x\${T}\`]: string
}>() }>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['String', 'Number'], foo: ['String', 'Number'],
bar: ['String', 'Number'], bar: ['String', 'Number'],
@ -204,7 +204,7 @@ describe('resolveType', () => {
FOO: ['String'], FOO: ['String'],
xfoo: ['String'], xfoo: ['String'],
xbar: ['String'], xbar: ['String'],
optional: ['Boolean'] optional: ['Boolean'],
}) })
}) })
@ -213,14 +213,14 @@ describe('resolveType', () => {
resolve(` resolve(`
type T = { foo: number, bar: string } type T = { foo: number, bar: string }
defineProps<Partial<T>>() defineProps<Partial<T>>()
`).raw.props `).raw.props,
).toMatchObject({ ).toMatchObject({
foo: { foo: {
optional: true optional: true,
}, },
bar: { bar: {
optional: true optional: true,
} },
}) })
}) })
@ -229,14 +229,14 @@ describe('resolveType', () => {
resolve(` resolve(`
type T = { foo?: number, bar?: string } type T = { foo?: number, bar?: string }
defineProps<Required<T>>() defineProps<Required<T>>()
`).raw.props `).raw.props,
).toMatchObject({ ).toMatchObject({
foo: { foo: {
optional: false optional: false,
}, },
bar: { bar: {
optional: false optional: false,
} },
}) })
}) })
@ -246,10 +246,10 @@ describe('resolveType', () => {
type T = { foo: number, bar: string, baz: boolean } type T = { foo: number, bar: string, baz: boolean }
type K = 'foo' | 'bar' type K = 'foo' | 'bar'
defineProps<Pick<T, K>>() defineProps<Pick<T, K>>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['Number'], foo: ['Number'],
bar: ['String'] bar: ['String'],
}) })
}) })
@ -259,9 +259,9 @@ describe('resolveType', () => {
type T = { foo: number, bar: string, baz: boolean } type T = { foo: number, bar: string, baz: boolean }
type K = 'foo' | 'bar' type K = 'foo' | 'bar'
defineProps<Omit<T, K>>() defineProps<Omit<T, K>>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
baz: ['Boolean'] baz: ['Boolean'],
}) })
}) })
@ -271,9 +271,9 @@ describe('resolveType', () => {
type T = { bar: number } type T = { bar: number }
type S = { nested: { foo: T['bar'] }} type S = { nested: { foo: T['bar'] }}
defineProps<S['nested']>() defineProps<S['nested']>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['Number'] foo: ['Number'],
}) })
}) })
@ -284,10 +284,10 @@ describe('resolveType', () => {
type T = { foo: string, bar: number } type T = { foo: string, bar: number }
type S = { foo: { foo: T[string] }, bar: { bar: string } } type S = { foo: { foo: T[string] }, bar: { bar: string } }
defineProps<S[K]>() defineProps<S[K]>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['String', 'Number'], foo: ['String', 'Number'],
bar: ['String'] bar: ['String'],
}) })
}) })
@ -299,12 +299,12 @@ describe('resolveType', () => {
type T = [1, 'foo'] type T = [1, 'foo']
type TT = [foo: 1, bar: 'foo'] type TT = [foo: 1, bar: 'foo']
defineProps<{ foo: A[number], bar: AA[number], tuple: T[number], namedTuple: TT[number] }>() defineProps<{ foo: A[number], bar: AA[number], tuple: T[number], namedTuple: TT[number] }>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['String', 'Number'], foo: ['String', 'Number'],
bar: ['String'], bar: ['String'],
tuple: ['Number', 'String'], tuple: ['Number', 'String'],
namedTuple: ['Number', 'String'] namedTuple: ['Number', 'String'],
}) })
}) })
@ -321,9 +321,9 @@ describe('resolveType', () => {
} }
} }
defineProps<Foo.Bar.A>() defineProps<Foo.Bar.A>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['Number'] foo: ['Number'],
}) })
}) })
@ -340,10 +340,10 @@ describe('resolveType', () => {
foo: Foo['a'], foo: Foo['a'],
bar: Foo['b'] bar: Foo['b']
}>() }>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['String'], foo: ['String'],
bar: ['Number'] bar: ['Number'],
}) })
}) })
@ -360,10 +360,10 @@ describe('resolveType', () => {
foo: Foo.A, foo: Foo.A,
bar: Foo.B bar: Foo.B
}>() }>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['String'], foo: ['String'],
bar: ['Number'] bar: ['Number'],
}) })
}) })
@ -380,10 +380,10 @@ describe('resolveType', () => {
foo: Foo.A, foo: Foo.A,
bar: Foo['b'] bar: Foo['b']
}>() }>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['String'], foo: ['String'],
bar: ['Number'] bar: ['Number'],
}) })
}) })
@ -399,9 +399,9 @@ describe('resolveType', () => {
defineProps<{ defineProps<{
foo: Foo foo: Foo
}>() }>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['Number', 'String'] foo: ['Number', 'String'],
}) })
}) })
@ -410,9 +410,9 @@ describe('resolveType', () => {
resolve(` resolve(`
declare const a: string declare const a: string
defineProps<{ foo: typeof a }>() defineProps<{ foo: typeof a }>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['String'] foo: ['String'],
}) })
}) })
@ -429,11 +429,11 @@ describe('resolveType', () => {
} }
type Props = ExtractPropTypes<typeof props> type Props = ExtractPropTypes<typeof props>
defineProps<Props>() defineProps<Props>()
` `,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
foo: ['String'], foo: ['String'],
bar: ['Boolean'] bar: ['Boolean'],
}) })
expect(raw.props.bar.optional).toBe(false) expect(raw.props.bar.optional).toBe(false)
}) })
@ -447,11 +447,11 @@ describe('resolveType', () => {
} }
type Props = Partial<import('vue').ExtractPropTypes<ReturnType<typeof props>>> type Props = Partial<import('vue').ExtractPropTypes<ReturnType<typeof props>>>
defineProps<Props>() defineProps<Props>()
` `,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
foo: ['String'], foo: ['String'],
bar: ['Boolean'] bar: ['Boolean'],
}) })
}) })
@ -461,9 +461,9 @@ describe('resolveType', () => {
resolve(` resolve(`
type Props<T> = T type Props<T> = T
defineProps<Props<{ foo: string }>>() defineProps<Props<{ foo: string }>>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['String'] foo: ['String'],
}) })
}) })
@ -474,11 +474,11 @@ describe('resolveType', () => {
type Bar = { bar: number; } type Bar = { bar: number; }
type Props<T,U> = T & U & { baz: boolean } type Props<T,U> = T & U & { baz: boolean }
defineProps<Props<Foo, Bar>>() defineProps<Props<Foo, Bar>>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['String'], foo: ['String'],
bar: ['Number'], bar: ['Number'],
baz: ['Boolean'] baz: ['Boolean'],
}) })
}) })
@ -489,9 +489,9 @@ describe('resolveType', () => {
type Props<T> = Aliased<T> type Props<T> = Aliased<T>
type Foo = { foo: string; } type Foo = { foo: string; }
defineProps<Props<Foo>>() defineProps<Props<Foo>>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['String'] foo: ['String'],
}) })
}) })
@ -500,9 +500,9 @@ describe('resolveType', () => {
resolve(` resolve(`
type Aliased<T> = { foo: T } type Aliased<T> = { foo: T }
defineProps<Aliased<string>>() defineProps<Aliased<string>>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['String'] foo: ['String'],
}) })
}) })
@ -514,25 +514,25 @@ describe('resolveType', () => {
} }
type Foo = string type Foo = string
defineProps<Props<Foo>>() defineProps<Props<Foo>>()
`).props `).props,
).toStrictEqual({ ).toStrictEqual({
foo: ['String'] foo: ['String'],
}) })
}) })
test('generic from external-file', () => { test('generic from external-file', () => {
const files = { const files = {
'/foo.ts': 'export type P<T> = { foo: T }' '/foo.ts': 'export type P<T> = { foo: T }',
} }
const { props } = resolve( const { props } = resolve(
` `
import { P } from './foo' import { P } from './foo'
defineProps<P<string>>() defineProps<P<string>>()
`, `,
files files,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
foo: ['String'] foo: ['String'],
}) })
}) })
}) })
@ -544,7 +544,7 @@ describe('resolveType', () => {
'/bar.d.ts': '/bar.d.ts':
'type X = { bar: string }; export { X as Y };' + 'type X = { bar: string }; export { X as Y };' +
// verify that we can parse syntax that is only valid in d.ts // verify that we can parse syntax that is only valid in d.ts
'export const baz: boolean' 'export const baz: boolean',
} }
const { props, deps } = resolve( const { props, deps } = resolve(
` `
@ -552,11 +552,11 @@ describe('resolveType', () => {
import { Y as PP } from './bar' import { Y as PP } from './bar'
defineProps<P & PP>() defineProps<P & PP>()
`, `,
files files,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
foo: ['Number'], foo: ['Number'],
bar: ['String'] bar: ['String'],
}) })
expect(deps && [...deps]).toStrictEqual(Object.keys(files)) expect(deps && [...deps]).toStrictEqual(Object.keys(files))
}) })
@ -568,7 +568,7 @@ describe('resolveType', () => {
'type X = { bar: string }; export { X as Y };' + 'type X = { bar: string }; export { X as Y };' +
// verify that we can parse syntax that is only valid in d.ts // verify that we can parse syntax that is only valid in d.ts
'export const baz: boolean', 'export const baz: boolean',
'C:\\Test\\FolderB\\buz.ts': 'export type Z = { buz: string }' 'C:\\Test\\FolderB\\buz.ts': 'export type Z = { buz: string }',
} }
const { props, deps } = resolve( const { props, deps } = resolve(
` `
@ -579,32 +579,32 @@ describe('resolveType', () => {
`, `,
files, files,
{}, {},
'C:\\Test\\FolderA\\Test.vue' 'C:\\Test\\FolderA\\Test.vue',
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
foo: ['Number'], foo: ['Number'],
bar: ['String'], bar: ['String'],
buz: ['String'] buz: ['String'],
}) })
expect(deps && [...deps].map(normalize)).toStrictEqual( expect(deps && [...deps].map(normalize)).toStrictEqual(
Object.keys(files).map(normalize) Object.keys(files).map(normalize),
) )
}) })
// #8244 // #8244
test('utility type in external file', () => { test('utility type in external file', () => {
const files = { const files = {
'/foo.ts': 'type A = { n?: number }; export type B = Required<A>' '/foo.ts': 'type A = { n?: number }; export type B = Required<A>',
} }
const { props } = resolve( const { props } = resolve(
` `
import { B } from './foo' import { B } from './foo'
defineProps<B>() defineProps<B>()
`, `,
files files,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
n: ['Number'] n: ['Number'],
}) })
}) })
@ -613,7 +613,7 @@ describe('resolveType', () => {
'/foo.vue': '/foo.vue':
'<script lang="ts">export type P = { foo: number }</script>', '<script lang="ts">export type P = { foo: number }</script>',
'/bar.vue': '/bar.vue':
'<script setup lang="tsx">export type P = { bar: string }</script>' '<script setup lang="tsx">export type P = { bar: string }</script>',
} }
const { props, deps } = resolve( const { props, deps } = resolve(
` `
@ -621,11 +621,11 @@ describe('resolveType', () => {
import { P as PP } from './bar.vue' import { P as PP } from './bar.vue'
defineProps<P & PP>() defineProps<P & PP>()
`, `,
files files,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
foo: ['Number'], foo: ['Number'],
bar: ['String'] bar: ['String'],
}) })
expect(deps && [...deps]).toStrictEqual(Object.keys(files)) expect(deps && [...deps]).toStrictEqual(Object.keys(files))
}) })
@ -635,18 +635,18 @@ describe('resolveType', () => {
'/foo.ts': `import type { P as PP } from './nested/bar.vue' '/foo.ts': `import type { P as PP } from './nested/bar.vue'
export type P = { foo: number } & PP`, export type P = { foo: number } & PP`,
'/nested/bar.vue': '/nested/bar.vue':
'<script setup lang="ts">export type P = { bar: string }</script>' '<script setup lang="ts">export type P = { bar: string }</script>',
} }
const { props, deps } = resolve( const { props, deps } = resolve(
` `
import { P } from './foo' import { P } from './foo'
defineProps<P>() defineProps<P>()
`, `,
files files,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
foo: ['Number'], foo: ['Number'],
bar: ['String'] bar: ['String'],
}) })
expect(deps && [...deps]).toStrictEqual(Object.keys(files)) expect(deps && [...deps]).toStrictEqual(Object.keys(files))
}) })
@ -654,17 +654,17 @@ describe('resolveType', () => {
test('relative (chained, re-export)', () => { test('relative (chained, re-export)', () => {
const files = { const files = {
'/foo.ts': `export { P as PP } from './bar'`, '/foo.ts': `export { P as PP } from './bar'`,
'/bar.ts': 'export type P = { bar: string }' '/bar.ts': 'export type P = { bar: string }',
} }
const { props, deps } = resolve( const { props, deps } = resolve(
` `
import { PP as P } from './foo' import { PP as P } from './foo'
defineProps<P>() defineProps<P>()
`, `,
files files,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
bar: ['String'] bar: ['String'],
}) })
expect(deps && [...deps]).toStrictEqual(Object.keys(files)) expect(deps && [...deps]).toStrictEqual(Object.keys(files))
}) })
@ -672,17 +672,17 @@ describe('resolveType', () => {
test('relative (chained, export *)', () => { test('relative (chained, export *)', () => {
const files = { const files = {
'/foo.ts': `export * from './bar'`, '/foo.ts': `export * from './bar'`,
'/bar.ts': 'export type P = { bar: string }' '/bar.ts': 'export type P = { bar: string }',
} }
const { props, deps } = resolve( const { props, deps } = resolve(
` `
import { P } from './foo' import { P } from './foo'
defineProps<P>() defineProps<P>()
`, `,
files files,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
bar: ['String'] bar: ['String'],
}) })
expect(deps && [...deps]).toStrictEqual(Object.keys(files)) expect(deps && [...deps]).toStrictEqual(Object.keys(files))
}) })
@ -690,7 +690,7 @@ describe('resolveType', () => {
test('relative (default export)', () => { test('relative (default export)', () => {
const files = { const files = {
'/foo.ts': `export default interface P { foo: string }`, '/foo.ts': `export default interface P { foo: string }`,
'/bar.ts': `type X = { bar: string }; export default X` '/bar.ts': `type X = { bar: string }; export default X`,
} }
const { props, deps } = resolve( const { props, deps } = resolve(
` `
@ -698,11 +698,11 @@ describe('resolveType', () => {
import X from './bar' import X from './bar'
defineProps<P & X>() defineProps<P & X>()
`, `,
files files,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
foo: ['String'], foo: ['String'],
bar: ['String'] bar: ['String'],
}) })
expect(deps && [...deps]).toStrictEqual(Object.keys(files)) expect(deps && [...deps]).toStrictEqual(Object.keys(files))
}) })
@ -711,7 +711,7 @@ describe('resolveType', () => {
const files = { const files = {
'/bar.ts': `export { default } from './foo'`, '/bar.ts': `export { default } from './foo'`,
'/foo.ts': `export default interface P { foo: string }; export interface PP { bar: number }`, '/foo.ts': `export default interface P { foo: string }; export interface PP { bar: number }`,
'/baz.ts': `export { PP as default } from './foo'` '/baz.ts': `export { PP as default } from './foo'`,
} }
const { props, deps } = resolve( const { props, deps } = resolve(
` `
@ -719,11 +719,11 @@ describe('resolveType', () => {
import PP from './baz' import PP from './baz'
defineProps<P & PP>() defineProps<P & PP>()
`, `,
files files,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
foo: ['String'], foo: ['String'],
bar: ['Number'] bar: ['Number'],
}) })
expect(deps && [...deps]).toStrictEqual(Object.keys(files)) expect(deps && [...deps]).toStrictEqual(Object.keys(files))
}) })
@ -732,17 +732,17 @@ describe('resolveType', () => {
const files = { const files = {
'/foo.ts': `export default interface P { foo: string }`, '/foo.ts': `export default interface P { foo: string }`,
'/bar.ts': `export default interface PP { bar: number }`, '/bar.ts': `export default interface PP { bar: number }`,
'/baz.ts': `export { default as X } from './foo'; export { default as XX } from './bar'; ` '/baz.ts': `export { default as X } from './foo'; export { default as XX } from './bar'; `,
} }
const { props, deps } = resolve( const { props, deps } = resolve(
`import { X, XX } from './baz' `import { X, XX } from './baz'
defineProps<X & XX>() defineProps<X & XX>()
`, `,
files files,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
foo: ['String'], foo: ['String'],
bar: ['Number'] bar: ['Number'],
}) })
expect(deps && [...deps]).toStrictEqual(['/baz.ts', '/foo.ts', '/bar.ts']) expect(deps && [...deps]).toStrictEqual(['/baz.ts', '/foo.ts', '/bar.ts'])
}) })
@ -750,17 +750,17 @@ describe('resolveType', () => {
test('relative (dynamic import)', () => { test('relative (dynamic import)', () => {
const files = { const files = {
'/foo.ts': `export type P = { foo: string, bar: import('./bar').N }`, '/foo.ts': `export type P = { foo: string, bar: import('./bar').N }`,
'/bar.ts': 'export type N = number' '/bar.ts': 'export type N = number',
} }
const { props, deps } = resolve( const { props, deps } = resolve(
` `
defineProps<import('./foo').P>() defineProps<import('./foo').P>()
`, `,
files files,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
foo: ['String'], foo: ['String'],
bar: ['Number'] bar: ['Number'],
}) })
expect(deps && [...deps]).toStrictEqual(Object.keys(files)) expect(deps && [...deps]).toStrictEqual(Object.keys(files))
}) })
@ -770,17 +770,17 @@ describe('resolveType', () => {
const files = { const files = {
'/foo.d.ts': '/foo.d.ts':
'import { PP } from "./bar.js"; export type P = { foo: PP }', 'import { PP } from "./bar.js"; export type P = { foo: PP }',
'/bar.d.ts': 'export type PP = "foo" | "bar"' '/bar.d.ts': 'export type PP = "foo" | "bar"',
} }
const { props, deps } = resolve( const { props, deps } = resolve(
` `
import { P } from './foo' import { P } from './foo'
defineProps<P>() defineProps<P>()
`, `,
files files,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
foo: ['String'] foo: ['String'],
}) })
expect(deps && [...deps]).toStrictEqual(Object.keys(files)) expect(deps && [...deps]).toStrictEqual(Object.keys(files))
}) })
@ -788,17 +788,17 @@ describe('resolveType', () => {
test('ts module resolve', () => { test('ts module resolve', () => {
const files = { const files = {
'/node_modules/foo/package.json': JSON.stringify({ '/node_modules/foo/package.json': JSON.stringify({
types: 'index.d.ts' types: 'index.d.ts',
}), }),
'/node_modules/foo/index.d.ts': 'export type P = { foo: number }', '/node_modules/foo/index.d.ts': 'export type P = { foo: number }',
'/tsconfig.json': JSON.stringify({ '/tsconfig.json': JSON.stringify({
compilerOptions: { compilerOptions: {
paths: { paths: {
bar: ['./pp.ts'] bar: ['./pp.ts'],
} },
} },
}), }),
'/pp.ts': 'export type PP = { bar: string }' '/pp.ts': 'export type PP = { bar: string }',
} }
const { props, deps } = resolve( const { props, deps } = resolve(
@ -807,16 +807,16 @@ describe('resolveType', () => {
import { PP } from 'bar' import { PP } from 'bar'
defineProps<P & PP>() defineProps<P & PP>()
`, `,
files files,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
foo: ['Number'], foo: ['Number'],
bar: ['String'] bar: ['String'],
}) })
expect(deps && [...deps]).toStrictEqual([ expect(deps && [...deps]).toStrictEqual([
'/node_modules/foo/index.d.ts', '/node_modules/foo/index.d.ts',
'/pp.ts' '/pp.ts',
]) ])
}) })
@ -825,23 +825,23 @@ describe('resolveType', () => {
'/tsconfig.json': JSON.stringify({ '/tsconfig.json': JSON.stringify({
references: [ references: [
{ {
path: './tsconfig.app.json' path: './tsconfig.app.json',
} },
] ],
}), }),
'/tsconfig.app.json': JSON.stringify({ '/tsconfig.app.json': JSON.stringify({
include: ['**/*.ts', '**/*.vue'], include: ['**/*.ts', '**/*.vue'],
extends: './tsconfig.web.json' extends: './tsconfig.web.json',
}), }),
'/tsconfig.web.json': JSON.stringify({ '/tsconfig.web.json': JSON.stringify({
compilerOptions: { compilerOptions: {
composite: true, composite: true,
paths: { paths: {
bar: ['./user.ts'] bar: ['./user.ts'],
} },
} },
}), }),
'/user.ts': 'export type User = { bar: string }' '/user.ts': 'export type User = { bar: string }',
} }
const { props, deps } = resolve( const { props, deps } = resolve(
@ -849,11 +849,11 @@ describe('resolveType', () => {
import { User } from 'bar' import { User } from 'bar'
defineProps<User>() defineProps<User>()
`, `,
files files,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
bar: ['String'] bar: ['String'],
}) })
expect(deps && [...deps]).toStrictEqual(['/user.ts']) expect(deps && [...deps]).toStrictEqual(['/user.ts'])
}) })
@ -864,12 +864,12 @@ describe('resolveType', () => {
compilerOptions: { compilerOptions: {
include: ['**/*.ts', '**/*.vue'], include: ['**/*.ts', '**/*.vue'],
paths: { paths: {
'@/*': ['./src/*'] '@/*': ['./src/*'],
} },
} },
}), }),
'/src/Foo.vue': '/src/Foo.vue':
'<script lang="ts">export type P = { bar: string }</script>' '<script lang="ts">export type P = { bar: string }</script>',
} }
const { props, deps } = resolve( const { props, deps } = resolve(
@ -877,11 +877,11 @@ describe('resolveType', () => {
import { P } from '@/Foo.vue' import { P } from '@/Foo.vue'
defineProps<P>() defineProps<P>()
`, `,
files files,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
bar: ['String'] bar: ['String'],
}) })
expect(deps && [...deps]).toStrictEqual(['/src/Foo.vue']) expect(deps && [...deps]).toStrictEqual(['/src/Foo.vue'])
}) })
@ -898,16 +898,16 @@ describe('resolveType', () => {
type PP = { bar: string } type PP = { bar: string }
} }
export {} export {}
` `,
} }
const { props, deps } = resolve(`defineProps<App.User & PP>()`, files, { const { props, deps } = resolve(`defineProps<App.User & PP>()`, files, {
globalTypeFiles: Object.keys(files) globalTypeFiles: Object.keys(files),
}) })
expect(props).toStrictEqual({ expect(props).toStrictEqual({
name: ['String'], name: ['String'],
bar: ['String'] bar: ['String'],
}) })
expect(deps && [...deps]).toStrictEqual(Object.keys(files)) expect(deps && [...deps]).toStrictEqual(Object.keys(files))
}) })
@ -927,33 +927,33 @@ describe('resolveType', () => {
id: string id: string
} }
} }
` `,
} }
const { props } = resolve(`defineProps<App.Data.AircraftData>()`, files, { const { props } = resolve(`defineProps<App.Data.AircraftData>()`, files, {
globalTypeFiles: Object.keys(files) globalTypeFiles: Object.keys(files),
}) })
expect(props).toStrictEqual({ expect(props).toStrictEqual({
id: ['String'], id: ['String'],
manufacturer: ['Object'] manufacturer: ['Object'],
}) })
}) })
// #9871 // #9871
test('shared generics with different args', () => { test('shared generics with different args', () => {
const files = { const files = {
'/foo.ts': `export interface Foo<T> { value: T }` '/foo.ts': `export interface Foo<T> { value: T }`,
} }
const { props } = resolve( const { props } = resolve(
`import type { Foo } from './foo' `import type { Foo } from './foo'
defineProps<Foo<string>>()`, defineProps<Foo<string>>()`,
files, files,
undefined, undefined,
`/One.vue` `/One.vue`,
) )
expect(props).toStrictEqual({ expect(props).toStrictEqual({
value: ['String'] value: ['String'],
}) })
const { props: props2 } = resolve( const { props: props2 } = resolve(
`import type { Foo } from './foo' `import type { Foo } from './foo'
@ -961,10 +961,10 @@ describe('resolveType', () => {
files, files,
undefined, undefined,
`/Two.vue`, `/Two.vue`,
false /* do not invalidate cache */ false /* do not invalidate cache */,
) )
expect(props2).toStrictEqual({ expect(props2).toStrictEqual({
value: ['Number'] value: ['Number'],
}) })
}) })
}) })
@ -972,25 +972,25 @@ describe('resolveType', () => {
describe('errors', () => { describe('errors', () => {
test('failed type reference', () => { test('failed type reference', () => {
expect(() => resolve(`defineProps<X>()`)).toThrow( expect(() => resolve(`defineProps<X>()`)).toThrow(
`Unresolvable type reference` `Unresolvable type reference`,
) )
}) })
test('unsupported computed keys', () => { test('unsupported computed keys', () => {
expect(() => resolve(`defineProps<{ [Foo]: string }>()`)).toThrow( expect(() => resolve(`defineProps<{ [Foo]: string }>()`)).toThrow(
`Unsupported computed key in type referenced by a macro` `Unsupported computed key in type referenced by a macro`,
) )
}) })
test('unsupported index type', () => { test('unsupported index type', () => {
expect(() => resolve(`defineProps<X[K]>()`)).toThrow( expect(() => resolve(`defineProps<X[K]>()`)).toThrow(
`Unsupported type when resolving index type` `Unsupported type when resolving index type`,
) )
}) })
test('failed import source resolve', () => { test('failed import source resolve', () => {
expect(() => expect(() =>
resolve(`import { X } from './foo'; defineProps<X>()`) resolve(`import { X } from './foo'; defineProps<X>()`),
).toThrow(`Failed to resolve import source "./foo"`) ).toThrow(`Failed to resolve import source "./foo"`)
}) })
@ -1001,7 +1001,7 @@ describe('resolveType', () => {
resolve(` resolve(`
import type P from 'unknown' import type P from 'unknown'
defineProps<{ foo: P }>() defineProps<{ foo: P }>()
`) `),
).not.toThrow() ).not.toThrow()
}) })
@ -1011,7 +1011,7 @@ describe('resolveType', () => {
import type Base from 'unknown' import type Base from 'unknown'
interface Props extends Base {} interface Props extends Base {}
defineProps<Props>() defineProps<Props>()
`) `),
).toThrow(`@vue-ignore`) ).toThrow(`@vue-ignore`)
}) })
@ -1026,11 +1026,11 @@ describe('resolveType', () => {
foo: string foo: string
} }
defineProps<Props>() defineProps<Props>()
`)) `)),
).not.toThrow(`@vue-ignore`) ).not.toThrow(`@vue-ignore`)
expect(res.props).toStrictEqual({ expect(res.props).toStrictEqual({
foo: ['String'] foo: ['String'],
}) })
}) })
}) })
@ -1041,10 +1041,10 @@ function resolve(
files: Record<string, string> = {}, files: Record<string, string> = {},
options?: Partial<SFCScriptCompileOptions>, options?: Partial<SFCScriptCompileOptions>,
sourceFileName: string = '/Test.vue', sourceFileName: string = '/Test.vue',
invalidateCache = true invalidateCache = true,
) { ) {
const { descriptor } = parse(`<script setup lang="ts">\n${code}\n</script>`, { const { descriptor } = parse(`<script setup lang="ts">\n${code}\n</script>`, {
filename: sourceFileName filename: sourceFileName,
}) })
const ctx = new ScriptCompileContext(descriptor, { const ctx = new ScriptCompileContext(descriptor, {
id: 'test', id: 'test',
@ -1054,9 +1054,9 @@ function resolve(
}, },
readFile(file) { readFile(file) {
return files[file] ?? files[normalize(file)] return files[file] ?? files[normalize(file)]
}
}, },
...options },
...options,
}) })
if (invalidateCache) { if (invalidateCache) {
@ -1088,6 +1088,6 @@ function resolve(
props, props,
calls: raw.calls, calls: raw.calls,
deps: ctx.deps, deps: ctx.deps,
raw raw,
} }
} }

View File

@ -1,20 +1,20 @@
import { import {
type SFCStyleCompileOptions,
compileStyle, compileStyle,
compileStyleAsync, compileStyleAsync,
SFCStyleCompileOptions
} from '../src/compileStyle' } from '../src/compileStyle'
import path from 'path' import path from 'node:path'
export function compileScoped( export function compileScoped(
source: string, source: string,
options?: Partial<SFCStyleCompileOptions> options?: Partial<SFCStyleCompileOptions>,
): string { ): string {
const res = compileStyle({ const res = compileStyle({
source, source,
filename: 'test.css', filename: 'test.css',
id: 'data-v-test', id: 'data-v-test',
scoped: true, scoped: true,
...options ...options,
}) })
if (res.errors.length) { if (res.errors.length) {
res.errors.forEach(err => { res.errors.forEach(err => {
@ -28,34 +28,34 @@ export function compileScoped(
describe('SFC scoped CSS', () => { describe('SFC scoped CSS', () => {
test('simple selectors', () => { test('simple selectors', () => {
expect(compileScoped(`h1 { color: red; }`)).toMatch( expect(compileScoped(`h1 { color: red; }`)).toMatch(
`h1[data-v-test] { color: red;` `h1[data-v-test] { color: red;`,
) )
expect(compileScoped(`.foo { color: red; }`)).toMatch( expect(compileScoped(`.foo { color: red; }`)).toMatch(
`.foo[data-v-test] { color: red;` `.foo[data-v-test] { color: red;`,
) )
}) })
test('descendent selector', () => { test('descendent selector', () => {
expect(compileScoped(`h1 .foo { color: red; }`)).toMatch( expect(compileScoped(`h1 .foo { color: red; }`)).toMatch(
`h1 .foo[data-v-test] { color: red;` `h1 .foo[data-v-test] { color: red;`,
) )
}) })
test('multiple selectors', () => { test('multiple selectors', () => {
expect(compileScoped(`h1 .foo, .bar, .baz { color: red; }`)).toMatch( expect(compileScoped(`h1 .foo, .bar, .baz { color: red; }`)).toMatch(
`h1 .foo[data-v-test], .bar[data-v-test], .baz[data-v-test] { color: red;` `h1 .foo[data-v-test], .bar[data-v-test], .baz[data-v-test] { color: red;`,
) )
}) })
test('pseudo class', () => { test('pseudo class', () => {
expect(compileScoped(`.foo:after { color: red; }`)).toMatch( expect(compileScoped(`.foo:after { color: red; }`)).toMatch(
`.foo[data-v-test]:after { color: red;` `.foo[data-v-test]:after { color: red;`,
) )
}) })
test('pseudo element', () => { test('pseudo element', () => {
expect(compileScoped(`::selection { display: none; }`)).toMatch( expect(compileScoped(`::selection { display: none; }`)).toMatch(
'[data-v-test]::selection {' '[data-v-test]::selection {',
) )
}) })
@ -217,30 +217,30 @@ describe('SFC scoped CSS', () => {
to { opacity: 1; } to { opacity: 1; }
} }
`, `,
{ id: 'data-v-test' } { id: 'data-v-test' },
) )
expect(style).toContain( expect(style).toContain(
`.anim[data-v-test] {\n animation: color-test 5s infinite, other 5s;` `.anim[data-v-test] {\n animation: color-test 5s infinite, other 5s;`,
) )
expect(style).toContain( expect(style).toContain(
`.anim-2[data-v-test] {\n animation-name: color-test` `.anim-2[data-v-test] {\n animation-name: color-test`,
) )
expect(style).toContain( expect(style).toContain(
`.anim-3[data-v-test] {\n animation: 5s color-test infinite, 5s other;` `.anim-3[data-v-test] {\n animation: 5s color-test infinite, 5s other;`,
) )
expect(style).toContain(`@keyframes color-test {`) expect(style).toContain(`@keyframes color-test {`)
expect(style).toContain(`@-webkit-keyframes color-test {`) expect(style).toContain(`@-webkit-keyframes color-test {`)
expect(style).toContain( expect(style).toContain(
`.anim-multiple[data-v-test] {\n animation: color-test 5s infinite,opacity-test 2s;` `.anim-multiple[data-v-test] {\n animation: color-test 5s infinite,opacity-test 2s;`,
) )
expect(style).toContain( expect(style).toContain(
`.anim-multiple-2[data-v-test] {\n animation-name: color-test,opacity-test;` `.anim-multiple-2[data-v-test] {\n animation-name: color-test,opacity-test;`,
) )
expect(style).toContain(`@keyframes opacity-test {\nfrom { opacity: 0;`) expect(style).toContain(`@keyframes opacity-test {\nfrom { opacity: 0;`)
expect(style).toContain( expect(style).toContain(
`@-webkit-keyframes opacity-test {\nfrom { opacity: 0;` `@-webkit-keyframes opacity-test {\nfrom { opacity: 0;`,
) )
}) })
@ -265,7 +265,7 @@ describe('SFC scoped CSS', () => {
}" }"
`) `)
expect( expect(
`::v-deep usage as a combinator has been deprecated.` `::v-deep usage as a combinator has been deprecated.`,
).toHaveBeenWarned() ).toHaveBeenWarned()
}) })
@ -276,7 +276,7 @@ describe('SFC scoped CSS', () => {
}" }"
`) `)
expect( expect(
`the >>> and /deep/ combinators have been deprecated.` `the >>> and /deep/ combinators have been deprecated.`,
).toHaveBeenWarned() ).toHaveBeenWarned()
}) })
@ -287,7 +287,7 @@ describe('SFC scoped CSS', () => {
}" }"
`) `)
expect( expect(
`the >>> and /deep/ combinators have been deprecated.` `the >>> and /deep/ combinators have been deprecated.`,
).toHaveBeenWarned() ).toHaveBeenWarned()
}) })
}) })
@ -299,7 +299,7 @@ describe('SFC CSS modules', () => {
source: `.red { color: red }\n.green { color: green }\n:global(.blue) { color: blue }`, source: `.red { color: red }\n.green { color: green }\n:global(.blue) { color: blue }`,
filename: `test.css`, filename: `test.css`,
id: 'test', id: 'test',
modules: true modules: true,
}) })
expect(result.modules).toBeDefined() expect(result.modules).toBeDefined()
expect(result.modules!.red).toMatch('_red_') expect(result.modules!.red).toMatch('_red_')
@ -316,8 +316,8 @@ describe('SFC CSS modules', () => {
modulesOptions: { modulesOptions: {
scopeBehaviour: 'global', scopeBehaviour: 'global',
generateScopedName: `[name]__[local]__[hash:base64:5]`, generateScopedName: `[name]__[local]__[hash:base64:5]`,
localsConvention: 'camelCaseOnly' localsConvention: 'camelCaseOnly',
} },
}) })
expect(result.modules).toBeDefined() expect(result.modules).toBeDefined()
expect(result.modules!.fooBar).toMatch('__foo-bar__') expect(result.modules!.fooBar).toMatch('__foo-bar__')
@ -333,11 +333,11 @@ describe('SFC style preprocessors', () => {
`, `,
filename: path.resolve(__dirname, './fixture/test.scss'), filename: path.resolve(__dirname, './fixture/test.scss'),
id: '', id: '',
preprocessLang: 'scss' preprocessLang: 'scss',
}) })
expect([...res.dependencies]).toStrictEqual([ expect([...res.dependencies]).toStrictEqual([
path.join(__dirname, './fixture/import.scss') path.join(__dirname, './fixture/import.scss'),
]) ])
}) })
@ -348,7 +348,7 @@ describe('SFC style preprocessors', () => {
@mixin square($size) { @mixin square($size) {
width: $size; width: $size;
height: $size; height: $size;
}` }`,
}, },
source: ` source: `
.square { .square {
@ -357,7 +357,7 @@ describe('SFC style preprocessors', () => {
`, `,
filename: path.resolve(__dirname, './fixture/test.scss'), filename: path.resolve(__dirname, './fixture/test.scss'),
id: '', id: '',
preprocessLang: 'scss' preprocessLang: 'scss',
}) })
expect(res.errors.length).toBe(0) expect(res.errors.length).toBe(0)
@ -380,12 +380,12 @@ describe('SFC style preprocessors', () => {
width: $size; width: $size;
height: $size; height: $size;
}` }`
} },
}, },
source, source,
filename, filename,
id: '', id: '',
preprocessLang: 'scss' preprocessLang: 'scss',
}) })
expect(res.errors.length).toBe(0) expect(res.errors.length).toBe(0)

View File

@ -1,15 +1,15 @@
import { RawSourceMap, SourceMapConsumer } from 'source-map-js' import { type RawSourceMap, SourceMapConsumer } from 'source-map-js'
import { import {
type SFCTemplateCompileOptions,
compileTemplate, compileTemplate,
SFCTemplateCompileOptions
} from '../src/compileTemplate' } from '../src/compileTemplate'
import { parse, SFCTemplateBlock } from '../src/parse' import { type SFCTemplateBlock, parse } from '../src/parse'
import { compileScript } from '../src' import { compileScript } from '../src'
function compile(opts: Omit<SFCTemplateCompileOptions, 'id'>) { function compile(opts: Omit<SFCTemplateCompileOptions, 'id'>) {
return compileTemplate({ return compileTemplate({
...opts, ...opts,
id: '' id: '',
}) })
} }
@ -50,13 +50,13 @@ body
p Cool Pug example! p Cool Pug example!
</template> </template>
`, `,
{ filename: 'example.vue', sourceMap: true } { filename: 'example.vue', sourceMap: true },
).descriptor.template as SFCTemplateBlock ).descriptor.template as SFCTemplateBlock
const result = compile({ const result = compile({
filename: 'example.vue', filename: 'example.vue',
source: template.content, source: template.content,
preprocessLang: template.lang preprocessLang: template.lang,
}) })
expect(result.errors.length).toBe(0) expect(result.errors.length).toBe(0)
@ -74,31 +74,31 @@ test('preprocess pug with indents and blank lines', () => {
p This is the last line. p This is the last line.
</template> </template>
`, `,
{ filename: 'example.vue', sourceMap: true } { filename: 'example.vue', sourceMap: true },
).descriptor.template as SFCTemplateBlock ).descriptor.template as SFCTemplateBlock
const result = compile({ const result = compile({
filename: 'example.vue', filename: 'example.vue',
source: template.content, source: template.content,
preprocessLang: template.lang preprocessLang: template.lang,
}) })
expect(result.errors.length).toBe(0) expect(result.errors.length).toBe(0)
expect(result.source).toBe( expect(result.source).toBe(
'<body><h1>The next line contains four spaces.</h1><div class="container"><p>The next line is empty.</p></div><p>This is the last line.</p></body>' '<body><h1>The next line contains four spaces.</h1><div class="container"><p>The next line is empty.</p></div><p>This is the last line.</p></body>',
) )
}) })
test('warn missing preprocessor', () => { test('warn missing preprocessor', () => {
const template = parse(`<template lang="unknownLang">hi</template>\n`, { const template = parse(`<template lang="unknownLang">hi</template>\n`, {
filename: 'example.vue', filename: 'example.vue',
sourceMap: true sourceMap: true,
}).descriptor.template as SFCTemplateBlock }).descriptor.template as SFCTemplateBlock
const result = compile({ const result = compile({
filename: 'example.vue', filename: 'example.vue',
source: template.content, source: template.content,
preprocessLang: template.lang preprocessLang: template.lang,
}) })
expect(result.errors.length).toBe(1) expect(result.errors.length).toBe(1)
@ -110,8 +110,8 @@ test('transform asset url options', () => {
const { code: code1 } = compile({ const { code: code1 } = compile({
...input, ...input,
transformAssetUrls: { transformAssetUrls: {
tags: { foo: ['bar'] } tags: { foo: ['bar'] },
} },
}) })
expect(code1).toMatch(`import _imports_0 from 'baz'\n`) expect(code1).toMatch(`import _imports_0 from 'baz'\n`)
@ -119,15 +119,15 @@ test('transform asset url options', () => {
const { code: code2 } = compile({ const { code: code2 } = compile({
...input, ...input,
transformAssetUrls: { transformAssetUrls: {
foo: ['bar'] foo: ['bar'],
} },
}) })
expect(code2).toMatch(`import _imports_0 from 'baz'\n`) expect(code2).toMatch(`import _imports_0 from 'baz'\n`)
// false option // false option
const { code: code3 } = compile({ const { code: code3 } = compile({
...input, ...input,
transformAssetUrls: false transformAssetUrls: false,
}) })
expect(code3).not.toMatch(`import _imports_0 from 'baz'\n`) expect(code3).not.toMatch(`import _imports_0 from 'baz'\n`)
}) })
@ -139,12 +139,12 @@ test('source map', () => {
<div><p>{{ foobar }}</p></div> <div><p>{{ foobar }}</p></div>
</template> </template>
`, `,
{ filename: 'example.vue', sourceMap: true } { filename: 'example.vue', sourceMap: true },
).descriptor.template! ).descriptor.template!
const { code, map } = compile({ const { code, map } = compile({
filename: 'example.vue', filename: 'example.vue',
source: template.content source: template.content,
}) })
expect(map!.sources).toEqual([`example.vue`]) expect(map!.sources).toEqual([`example.vue`])
@ -152,7 +152,7 @@ test('source map', () => {
const consumer = new SourceMapConsumer(map as RawSourceMap) const consumer = new SourceMapConsumer(map as RawSourceMap)
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, 'foobar')) consumer.originalPositionFor(getPositionInCode(code, 'foobar')),
).toMatchObject(getPositionInCode(template.content, `foobar`)) ).toMatchObject(getPositionInCode(template.content, `foobar`))
}) })
@ -164,7 +164,7 @@ test('should work w/ AST from descriptor', () => {
` `
const template = parse(source, { const template = parse(source, {
filename: 'example.vue', filename: 'example.vue',
sourceMap: true sourceMap: true,
}).descriptor.template! }).descriptor.template!
expect(template.ast!.source).toBe(source) expect(template.ast!.source).toBe(source)
@ -172,7 +172,7 @@ test('should work w/ AST from descriptor', () => {
const { code, map } = compile({ const { code, map } = compile({
filename: 'example.vue', filename: 'example.vue',
source: template.content, source: template.content,
ast: template.ast ast: template.ast,
}) })
expect(map!.sources).toEqual([`example.vue`]) expect(map!.sources).toEqual([`example.vue`])
@ -182,14 +182,14 @@ test('should work w/ AST from descriptor', () => {
const consumer = new SourceMapConsumer(map as RawSourceMap) const consumer = new SourceMapConsumer(map as RawSourceMap)
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, 'foobar')) consumer.originalPositionFor(getPositionInCode(code, 'foobar')),
).toMatchObject(getPositionInCode(source, `foobar`)) ).toMatchObject(getPositionInCode(source, `foobar`))
expect(code).toBe( expect(code).toBe(
compile({ compile({
filename: 'example.vue', filename: 'example.vue',
source: template.content source: template.content,
}).code }).code,
) )
}) })
@ -201,7 +201,7 @@ test('should work w/ AST from descriptor in SSR mode', () => {
` `
const template = parse(source, { const template = parse(source, {
filename: 'example.vue', filename: 'example.vue',
sourceMap: true sourceMap: true,
}).descriptor.template! }).descriptor.template!
expect(template.ast!.source).toBe(source) expect(template.ast!.source).toBe(source)
@ -210,7 +210,7 @@ test('should work w/ AST from descriptor in SSR mode', () => {
filename: 'example.vue', filename: 'example.vue',
source: '', // make sure it's actually using the AST instead of source source: '', // make sure it's actually using the AST instead of source
ast: template.ast, ast: template.ast,
ssr: true ssr: true,
}) })
expect(map!.sources).toEqual([`example.vue`]) expect(map!.sources).toEqual([`example.vue`])
@ -220,15 +220,15 @@ test('should work w/ AST from descriptor in SSR mode', () => {
const consumer = new SourceMapConsumer(map as RawSourceMap) const consumer = new SourceMapConsumer(map as RawSourceMap)
expect( expect(
consumer.originalPositionFor(getPositionInCode(code, 'foobar')) consumer.originalPositionFor(getPositionInCode(code, 'foobar')),
).toMatchObject(getPositionInCode(source, `foobar`)) ).toMatchObject(getPositionInCode(source, `foobar`))
expect(code).toBe( expect(code).toBe(
compile({ compile({
filename: 'example.vue', filename: 'example.vue',
source: template.content, source: template.content,
ssr: true ssr: true,
}).code }).code,
) )
}) })
@ -240,7 +240,7 @@ test('should not reuse AST if using custom compiler', () => {
` `
const template = parse(source, { const template = parse(source, {
filename: 'example.vue', filename: 'example.vue',
sourceMap: true sourceMap: true,
}).descriptor.template! }).descriptor.template!
const { code } = compile({ const { code } = compile({
@ -249,9 +249,9 @@ test('should not reuse AST if using custom compiler', () => {
ast: template.ast, ast: template.ast,
compiler: { compiler: {
parse: () => null as any, parse: () => null as any,
// @ts-ignore // @ts-expect-error
compile: input => ({ code: input }) compile: input => ({ code: input }),
} },
}) })
// what we really want to assert is that the `input` received by the custom // what we really want to assert is that the `input` received by the custom
@ -267,7 +267,7 @@ test('should force re-parse on already transformed AST', () => {
` `
const template = parse(source, { const template = parse(source, {
filename: 'example.vue', filename: 'example.vue',
sourceMap: true sourceMap: true,
}).descriptor.template! }).descriptor.template!
// force set to empty, if this is reused then it won't generate proper code // force set to empty, if this is reused then it won't generate proper code
@ -277,14 +277,14 @@ test('should force re-parse on already transformed AST', () => {
const { code } = compile({ const { code } = compile({
filename: 'example.vue', filename: 'example.vue',
source: '', source: '',
ast: template.ast ast: template.ast,
}) })
expect(code).toBe( expect(code).toBe(
compile({ compile({
filename: 'example.vue', filename: 'example.vue',
source: template.content source: template.content,
}).code }).code,
) )
}) })
@ -296,7 +296,7 @@ test('should force re-parse with correct compiler in SSR mode', () => {
` `
const template = parse(source, { const template = parse(source, {
filename: 'example.vue', filename: 'example.vue',
sourceMap: true sourceMap: true,
}).descriptor.template! }).descriptor.template!
// force set to empty, if this is reused then it won't generate proper code // force set to empty, if this is reused then it won't generate proper code
@ -307,15 +307,15 @@ test('should force re-parse with correct compiler in SSR mode', () => {
filename: 'example.vue', filename: 'example.vue',
source: '', source: '',
ast: template.ast, ast: template.ast,
ssr: true ssr: true,
}) })
expect(code).toBe( expect(code).toBe(
compile({ compile({
filename: 'example.vue', filename: 'example.vue',
source: template.content, source: template.content,
ssr: true ssr: true,
}).code }).code,
) )
}) })
@ -323,7 +323,7 @@ test('template errors', () => {
const result = compile({ const result = compile({
filename: 'example.vue', filename: 'example.vue',
source: `<div source: `<div
:bar="a[" v-model="baz"/>` :bar="a[" v-model="baz"/>`,
}) })
expect(result.errors).toMatchSnapshot() expect(result.errors).toMatchSnapshot()
}) })
@ -335,20 +335,20 @@ test('preprocessor errors', () => {
div(class='class) div(class='class)
</template> </template>
`, `,
{ filename: 'example.vue', sourceMap: true } { filename: 'example.vue', sourceMap: true },
).descriptor.template as SFCTemplateBlock ).descriptor.template as SFCTemplateBlock
const result = compile({ const result = compile({
filename: 'example.vue', filename: 'example.vue',
source: template.content, source: template.content,
preprocessLang: template.lang preprocessLang: template.lang,
}) })
expect(result.errors.length).toBe(1) expect(result.errors.length).toBe(1)
const message = result.errors[0].toString() const message = result.errors[0].toString()
expect(message).toMatch(`Error: example.vue:3:1`) expect(message).toMatch(`Error: example.vue:3:1`)
expect(message).toMatch( expect(message).toMatch(
`The end of the string reached with no closing bracket ) found.` `The end of the string reached with no closing bracket ) found.`,
) )
}) })
@ -362,7 +362,7 @@ test('should generate the correct imports expression', () => {
<img src="./bar.svg"/> <img src="./bar.svg"/>
</Comp> </Comp>
`, `,
ssr: true ssr: true,
}) })
expect(code).toMatch(`_ssrRenderAttr(\"src\", _imports_1)`) expect(code).toMatch(`_ssrRenderAttr(\"src\", _imports_1)`)
expect(code).toMatch(`_createVNode(\"img\", { src: _imports_1 })`) expect(code).toMatch(`_createVNode(\"img\", { src: _imports_1 })`)
@ -384,7 +384,7 @@ test('should not hoist srcset URLs in SSR mode', () => {
</picture> </picture>
</router-link> </router-link>
`, `,
ssr: true ssr: true,
}) })
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -422,7 +422,7 @@ test('prefixing edge case for reused AST', () => {
id: 'xxx', id: 'xxx',
filename: 'test.vue', filename: 'test.vue',
ast: descriptor.template!.ast, ast: descriptor.template!.ast,
source: descriptor.template!.content source: descriptor.template!.content,
}) })
expect(code).not.toMatch(`_ctx.t`) expect(code).not.toMatch(`_ctx.t`)
}) })
@ -436,7 +436,7 @@ interface Pos {
function getPositionInCode( function getPositionInCode(
code: string, code: string,
token: string, token: string,
expectName: string | boolean = false expectName: string | boolean = false,
): Pos { ): Pos {
const generatedOffset = code.indexOf(token) const generatedOffset = code.indexOf(token)
let line = 1 let line = 1
@ -452,7 +452,7 @@ function getPositionInCode(
column: column:
lastNewLinePos === -1 lastNewLinePos === -1
? generatedOffset ? generatedOffset
: generatedOffset - lastNewLinePos - 1 : generatedOffset - lastNewLinePos - 1,
} }
if (expectName) { if (expectName) {
res.name = typeof expectName === 'string' ? expectName : token res.name = typeof expectName === 'string' ? expectName : token

View File

@ -1,5 +1,5 @@
import { compileStyle, parse } from '../src' import { compileStyle, parse } from '../src'
import { mockId, compileSFCScript, assertCode } from './utils' import { assertCode, compileSFCScript, mockId } from './utils'
describe('CSS vars injection', () => { describe('CSS vars injection', () => {
test('generating correct code for nested paths', () => { test('generating correct code for nested paths', () => {
@ -8,7 +8,7 @@ describe('CSS vars injection', () => {
`<style>div{ `<style>div{
color: v-bind(color); color: v-bind(color);
font-size: v-bind('font.size'); font-size: v-bind('font.size');
}</style>` }</style>`,
) )
expect(content).toMatch(`_useCssVars(_ctx => ({ expect(content).toMatch(`_useCssVars(_ctx => ({
"${mockId}-color": (_ctx.color), "${mockId}-color": (_ctx.color),
@ -32,7 +32,7 @@ describe('CSS vars injection', () => {
div { div {
font-size: v-bind(size); font-size: v-bind(size);
} }
</style>` </style>`,
) )
expect(content).toMatch(`_useCssVars(_ctx => ({ expect(content).toMatch(`_useCssVars(_ctx => ({
"${mockId}-size": (_ctx.size) "${mockId}-size": (_ctx.size)
@ -57,7 +57,7 @@ describe('CSS vars injection', () => {
font-size: v-bind(size); font-size: v-bind(size);
border: v-bind(foo); border: v-bind(foo);
} }
</style>` </style>`,
) )
// should handle: // should handle:
// 1. local const bindings // 1. local const bindings
@ -69,7 +69,7 @@ describe('CSS vars injection', () => {
"${mockId}-foo": (__props.foo) "${mockId}-foo": (__props.foo)
})`) })`)
expect(content).toMatch( expect(content).toMatch(
`import { useCssVars as _useCssVars, unref as _unref } from 'vue'` `import { useCssVars as _useCssVars, unref as _unref } from 'vue'`,
) )
assertCode(content) assertCode(content)
}) })
@ -85,7 +85,7 @@ describe('CSS vars injection', () => {
font-family: v-bind(); font-family: v-bind();
}`, }`,
filename: 'test.css', filename: 'test.css',
id: 'data-v-test' id: 'data-v-test',
}) })
expect(code).toMatchInlineSnapshot(` expect(code).toMatchInlineSnapshot(`
".foo { ".foo {
@ -106,7 +106,7 @@ describe('CSS vars injection', () => {
color: v-bind(color); color: v-bind(color);
font-size: v-bind('font.size'); font-size: v-bind('font.size');
}</style>`, }</style>`,
{ isProd: true } { isProd: true },
) )
expect(content).toMatch(`_useCssVars(_ctx => ({ expect(content).toMatch(`_useCssVars(_ctx => ({
"4003f1a6": (_ctx.color), "4003f1a6": (_ctx.color),
@ -120,7 +120,7 @@ describe('CSS vars injection', () => {
}`, }`,
filename: 'test.css', filename: 'test.css',
id: mockId, id: mockId,
isProd: true isProd: true,
}) })
expect(code).toMatchInlineSnapshot(` expect(code).toMatchInlineSnapshot(`
".foo { ".foo {
@ -135,8 +135,8 @@ describe('CSS vars injection', () => {
assertCode( assertCode(
compileSFCScript( compileSFCScript(
`<script>const a = 1</script>\n` + `<script>const a = 1</script>\n` +
`<style>div{ color: v-bind(color); }</style>` `<style>div{ color: v-bind(color); }</style>`,
).content ).content,
) )
}) })
@ -144,8 +144,8 @@ describe('CSS vars injection', () => {
assertCode( assertCode(
compileSFCScript( compileSFCScript(
`<script>export default { setup() {} }</script>\n` + `<script>export default { setup() {} }</script>\n` +
`<style>div{ color: v-bind(color); }</style>` `<style>div{ color: v-bind(color); }</style>`,
).content ).content,
) )
}) })
@ -155,8 +155,8 @@ describe('CSS vars injection', () => {
`<script> `<script>
// export default {} // export default {}
export default {} export default {}
</script>\n` + `<style>div{ color: v-bind(color); }</style>` </script>\n` + `<style>div{ color: v-bind(color); }</style>`,
).content ).content,
) )
}) })
@ -164,8 +164,8 @@ describe('CSS vars injection', () => {
assertCode( assertCode(
compileSFCScript( compileSFCScript(
`<script setup>const color = 'red'</script>\n` + `<script setup>const color = 'red'</script>\n` +
`<style>div{ color: v-bind(color); }</style>` `<style>div{ color: v-bind(color); }</style>`,
).content ).content,
) )
}) })
@ -178,7 +178,7 @@ describe('CSS vars injection', () => {
div{ /* color: v-bind(color); */ width:20; } div{ /* color: v-bind(color); */ width:20; }
div{ width: v-bind(width); } div{ width: v-bind(width); }
/* comment */ /* comment */
</style>` </style>`,
) )
expect(content).not.toMatch(`"${mockId}-color": (color)`) expect(content).not.toMatch(`"${mockId}-color": (color)`)
@ -198,7 +198,7 @@ describe('CSS vars injection', () => {
p { p {
color: v-bind(color); color: v-bind(color);
} }
</style>` </style>`,
) )
// color should only be injected once, even if it is twice in style // color should only be injected once, even if it is twice in style
expect(content).toMatch(`_useCssVars(_ctx => ({ expect(content).toMatch(`_useCssVars(_ctx => ({
@ -229,7 +229,7 @@ describe('CSS vars injection', () => {
p { p {
color: v-bind(((a + b)) / (2 * a)); color: v-bind(((a + b)) / (2 * a));
} }
</style>` </style>`,
) )
expect(content).toMatch(`_useCssVars(_ctx => ({ expect(content).toMatch(`_useCssVars(_ctx => ({
"${mockId}-foo": (_unref(foo)), "${mockId}-foo": (_unref(foo)),
@ -243,7 +243,7 @@ describe('CSS vars injection', () => {
// #6022 // #6022
test('should be able to parse incomplete expressions', () => { test('should be able to parse incomplete expressions', () => {
const { const {
descriptor: { cssVars } descriptor: { cssVars },
} = parse( } = parse(
`<script setup>let xxx = 1</script> `<script setup>let xxx = 1</script>
<style scoped> <style scoped>
@ -251,7 +251,7 @@ describe('CSS vars injection', () => {
font-weight: v-bind("count.toString("); font-weight: v-bind("count.toString(");
font-weight: v-bind(xxx); font-weight: v-bind(xxx);
} }
</style>` </style>`,
) )
expect(cssVars).toMatchObject([`count.toString(`, `xxx`]) expect(cssVars).toMatchObject([`count.toString(`, `xxx`])
}) })
@ -266,10 +266,10 @@ describe('CSS vars injection', () => {
label { label {
background: v-bind(background); background: v-bind(background);
} }
</style>` </style>`,
) )
expect(content).toMatch( expect(content).toMatch(
`export default {\n setup(__props, { expose: __expose }) {\n __expose();\n\n_useCssVars(_ctx => ({\n "xxxxxxxx-background": (_unref(background))\n}))` `export default {\n setup(__props, { expose: __expose }) {\n __expose();\n\n_useCssVars(_ctx => ({\n "xxxxxxxx-background": (_unref(background))\n}))`,
) )
}) })
@ -287,9 +287,9 @@ describe('CSS vars injection', () => {
{ {
inlineTemplate: true, inlineTemplate: true,
templateOptions: { templateOptions: {
ssr: true ssr: true,
} },
} },
) )
expect(content).not.toMatch(`_useCssVars`) expect(content).not.toMatch(`_useCssVars`)
}) })
@ -308,9 +308,9 @@ describe('CSS vars injection', () => {
{ {
inlineTemplate: false, inlineTemplate: false,
templateOptions: { templateOptions: {
ssr: true ssr: true,
} },
} },
) )
expect(content).not.toMatch(`_useCssVars`) expect(content).not.toMatch(`_useCssVars`)
}) })
@ -333,9 +333,9 @@ describe('CSS vars injection', () => {
</style>`, </style>`,
{ {
templateOptions: { templateOptions: {
ssr: true ssr: true,
} },
} },
) )
expect(content).not.toMatch(`_useCssVars`) expect(content).not.toMatch(`_useCssVars`)
}) })

View File

@ -37,7 +37,7 @@ font-weight: bold;
} }
</style>` </style>`
const { const {
descriptor: { styles } descriptor: { styles },
} = parse(src) } = parse(src)
expect(styles[0].map).not.toBeUndefined() expect(styles[0].map).not.toBeUndefined()
@ -69,7 +69,7 @@ font-weight: bold;
// Padding determines how many blank lines will there be before the style block // Padding determines how many blank lines will there be before the style block
const padding = Math.round(Math.random() * 10) const padding = Math.round(Math.random() * 10)
const script = parse( const script = parse(
`${'\n'.repeat(padding)}<script>\nconsole.log(1)\n }\n</script>\n` `${'\n'.repeat(padding)}<script>\nconsole.log(1)\n }\n</script>\n`,
).descriptor.script ).descriptor.script
expect(script!.map).not.toBeUndefined() expect(script!.map).not.toBeUndefined()
@ -88,7 +88,7 @@ font-weight: bold;
h1 foo h1 foo
div bar div bar
span baz span baz
</template>\n` </template>\n`,
).descriptor.template! ).descriptor.template!
expect(template.map).not.toBeUndefined() expect(template.map).not.toBeUndefined()
@ -103,7 +103,7 @@ font-weight: bold;
test('custom block', () => { test('custom block', () => {
const padding = Math.round(Math.random() * 10) const padding = Math.round(Math.random() * 10)
const custom = parse( const custom = parse(
`${'\n'.repeat(padding)}<i18n>\n{\n "greeting": "hello"\n}\n</i18n>\n` `${'\n'.repeat(padding)}<i18n>\n{\n "greeting": "hello"\n}\n</i18n>\n`,
).descriptor.customBlocks[0] ).descriptor.customBlocks[0]
expect(custom!.map).not.toBeUndefined() expect(custom!.map).not.toBeUndefined()
@ -138,42 +138,42 @@ h1 { color: red }
const padTrue = parse(content.trim(), { pad: true }).descriptor const padTrue = parse(content.trim(), { pad: true }).descriptor
expect(padTrue.script!.content).toBe( expect(padTrue.script!.content).toBe(
Array(3 + 1).join('//\n') + '\nexport default {}\n' Array(3 + 1).join('//\n') + '\nexport default {}\n',
) )
expect(padTrue.styles[0].content).toBe( expect(padTrue.styles[0].content).toBe(
Array(6 + 1).join('\n') + '\nh1 { color: red }\n' Array(6 + 1).join('\n') + '\nh1 { color: red }\n',
) )
expect(padTrue.customBlocks[0].content).toBe( expect(padTrue.customBlocks[0].content).toBe(
Array(9 + 1).join('\n') + '\n{ "greeting": "hello" }\n' Array(9 + 1).join('\n') + '\n{ "greeting": "hello" }\n',
) )
const padLine = parse(content.trim(), { pad: 'line' }).descriptor const padLine = parse(content.trim(), { pad: 'line' }).descriptor
expect(padLine.script!.content).toBe( expect(padLine.script!.content).toBe(
Array(3 + 1).join('//\n') + '\nexport default {}\n' Array(3 + 1).join('//\n') + '\nexport default {}\n',
) )
expect(padLine.styles[0].content).toBe( expect(padLine.styles[0].content).toBe(
Array(6 + 1).join('\n') + '\nh1 { color: red }\n' Array(6 + 1).join('\n') + '\nh1 { color: red }\n',
) )
expect(padLine.customBlocks[0].content).toBe( expect(padLine.customBlocks[0].content).toBe(
Array(9 + 1).join('\n') + '\n{ "greeting": "hello" }\n' Array(9 + 1).join('\n') + '\n{ "greeting": "hello" }\n',
) )
const padSpace = parse(content.trim(), { pad: 'space' }).descriptor const padSpace = parse(content.trim(), { pad: 'space' }).descriptor
expect(padSpace.script!.content).toBe( expect(padSpace.script!.content).toBe(
`<template>\n<div></div>\n</template>\n<script>`.replace(/./g, ' ') + `<template>\n<div></div>\n</template>\n<script>`.replace(/./g, ' ') +
'\nexport default {}\n' '\nexport default {}\n',
) )
expect(padSpace.styles[0].content).toBe( expect(padSpace.styles[0].content).toBe(
`<template>\n<div></div>\n</template>\n<script>\nexport default {}\n</script>\n<style>`.replace( `<template>\n<div></div>\n</template>\n<script>\nexport default {}\n</script>\n<style>`.replace(
/./g, /./g,
' ' ' ',
) + '\nh1 { color: red }\n' ) + '\nh1 { color: red }\n',
) )
expect(padSpace.customBlocks[0].content).toBe( expect(padSpace.customBlocks[0].content).toBe(
`<template>\n<div></div>\n</template>\n<script>\nexport default {}\n</script>\n<style>\nh1 { color: red }\n</style>\n<i18n>`.replace( `<template>\n<div></div>\n</template>\n<script>\nexport default {}\n</script>\n<style>\nh1 { color: red }\n</style>\n<i18n>`.replace(
/./g, /./g,
' ' ' ',
) + '\n{ "greeting": "hello" }\n' ) + '\n{ "greeting": "hello" }\n',
) )
}) })
@ -187,8 +187,8 @@ h1 { color: red }
end: { end: {
line: 3, line: 3,
column: 1, column: 1,
offset: 10 + content.length offset: 10 + content.length,
} },
}) })
}) })
@ -198,7 +198,7 @@ h1 { color: red }
expect(descriptor.template!.content).toBeFalsy() expect(descriptor.template!.content).toBeFalsy()
expect(descriptor.template!.loc).toMatchObject({ expect(descriptor.template!.loc).toMatchObject({
start: { line: 1, column: 12, offset: 11 }, start: { line: 1, column: 12, offset: 11 },
end: { line: 1, column: 12, offset: 11 } end: { line: 1, column: 12, offset: 11 },
}) })
}) })
@ -208,7 +208,7 @@ h1 { color: red }
expect(descriptor.template!.content).toBeFalsy() expect(descriptor.template!.content).toBeFalsy()
expect(descriptor.template!.loc).toMatchObject({ expect(descriptor.template!.loc).toMatchObject({
start: { line: 1, column: 11, offset: 10 }, start: { line: 1, column: 11, offset: 10 },
end: { line: 1, column: 11, offset: 10 } end: { line: 1, column: 11, offset: 10 },
}) })
}) })
@ -219,7 +219,7 @@ h1 { color: red }
expect(parse(`<style> \n\t </style>`).descriptor.styles.length).toBe(0) expect(parse(`<style> \n\t </style>`).descriptor.styles.length).toBe(0)
expect(parse(`<custom/>`).descriptor.customBlocks.length).toBe(0) expect(parse(`<custom/>`).descriptor.customBlocks.length).toBe(0)
expect( expect(
parse(`<custom> \n\t </custom>`).descriptor.customBlocks.length parse(`<custom> \n\t </custom>`).descriptor.customBlocks.length,
).toBe(0) ).toBe(0)
}) })
@ -239,19 +239,19 @@ h1 { color: red }
const { descriptor } = parse( const { descriptor } = parse(
`<script></script>\n<script setup>\n</script>`, `<script></script>\n<script setup>\n</script>`,
{ {
ignoreEmpty: false ignoreEmpty: false,
} },
) )
expect(descriptor.script).toBeTruthy() expect(descriptor.script).toBeTruthy()
expect(descriptor.script!.loc).toMatchObject({ expect(descriptor.script!.loc).toMatchObject({
start: { line: 1, column: 9, offset: 8 }, start: { line: 1, column: 9, offset: 8 },
end: { line: 1, column: 9, offset: 8 } end: { line: 1, column: 9, offset: 8 },
}) })
expect(descriptor.scriptSetup).toBeTruthy() expect(descriptor.scriptSetup).toBeTruthy()
expect(descriptor.scriptSetup!.loc).toMatchObject({ expect(descriptor.scriptSetup!.loc).toMatchObject({
start: { line: 2, column: 15, offset: 32 }, start: { line: 2, column: 15, offset: 32 },
end: { line: 3, column: 1, offset: 33 } end: { line: 3, column: 1, offset: 33 },
}) })
}) })
@ -267,7 +267,7 @@ h1 { color: red }
test('treat empty lang attribute as the html', () => { test('treat empty lang attribute as the html', () => {
const content = `<div><template v-if="ok">ok</template></div>` const content = `<div><template v-if="ok">ok</template></div>`
const { descriptor, errors } = parse( const { descriptor, errors } = parse(
`<template lang="">${content}</template>` `<template lang="">${content}</template>`,
) )
expect(descriptor.template!.content).toBe(content) expect(descriptor.template!.content).toBe(content)
expect(errors.length).toBe(0) expect(errors.length).toBe(0)
@ -277,7 +277,7 @@ h1 { color: red }
test('template with preprocessor lang should be treated as plain text', () => { test('template with preprocessor lang should be treated as plain text', () => {
const content = `p(v-if="1 < 2") test <div/>` const content = `p(v-if="1 < 2") test <div/>`
const { descriptor, errors } = parse( const { descriptor, errors } = parse(
`<template lang="pug">` + content + `</template>` `<template lang="pug">` + content + `</template>`,
) )
expect(errors.length).toBe(0) expect(errors.length).toBe(0)
expect(descriptor.template!.content).toBe(content) expect(descriptor.template!.content).toBe(content)
@ -301,17 +301,17 @@ h1 { color: red }
expect(parse(`<template>hi</template>`).descriptor.slotted).toBe(false) expect(parse(`<template>hi</template>`).descriptor.slotted).toBe(false)
expect( expect(
parse(`<template>hi</template><style>h1{color:red;}</style>`).descriptor parse(`<template>hi</template><style>h1{color:red;}</style>`).descriptor
.slotted .slotted,
).toBe(false) ).toBe(false)
expect( expect(
parse( parse(
`<template>hi</template><style scoped>:slotted(h1){color:red;}</style>` `<template>hi</template><style scoped>:slotted(h1){color:red;}</style>`,
).descriptor.slotted ).descriptor.slotted,
).toBe(true) ).toBe(true)
expect( expect(
parse( parse(
`<template>hi</template><style scoped>::v-slotted(h1){color:red;}</style>` `<template>hi</template><style scoped>::v-slotted(h1){color:red;}</style>`,
).descriptor.slotted ).descriptor.slotted,
).toBe(true) ).toBe(true)
}) })
@ -332,8 +332,8 @@ h1 { color: red }
options.onError!(new Error('foo') as any) options.onError!(new Error('foo') as any)
return createRoot([]) return createRoot([])
}, },
compile: baseCompile compile: baseCompile,
} },
}) })
expect(errors.length).toBe(2) expect(errors.length).toBe(2)
// error thrown by the custom parse // error thrown by the custom parse
@ -344,7 +344,7 @@ h1 { color: red }
test('treat custom blocks as raw text', () => { test('treat custom blocks as raw text', () => {
const { errors, descriptor } = parse( const { errors, descriptor } = parse(
`<template><input></template><foo> <-& </foo>` `<template><input></template><foo> <-& </foo>`,
) )
expect(errors.length).toBe(0) expect(errors.length).toBe(0)
expect(descriptor.customBlocks[0].content).toBe(` <-& `) expect(descriptor.customBlocks[0].content).toBe(` <-& `)
@ -358,7 +358,7 @@ h1 { color: red }
test('should only allow single template element', () => { test('should only allow single template element', () => {
assertWarning( assertWarning(
parse(`<template><div/></template><template><div/></template>`).errors, parse(`<template><div/></template><template><div/></template>`).errors,
`Single file component can contain only one <template> element` `Single file component can contain only one <template> element`,
) )
}) })
@ -366,24 +366,24 @@ h1 { color: red }
assertWarning( assertWarning(
parse(`<script>console.log(1)</script><script>console.log(1)</script>`) parse(`<script>console.log(1)</script><script>console.log(1)</script>`)
.errors, .errors,
`Single file component can contain only one <script> element` `Single file component can contain only one <script> element`,
) )
}) })
test('should only allow single script setup element', () => { test('should only allow single script setup element', () => {
assertWarning( assertWarning(
parse( parse(
`<script setup>console.log(1)</script><script setup>console.log(1)</script>` `<script setup>console.log(1)</script><script setup>console.log(1)</script>`,
).errors, ).errors,
`Single file component can contain only one <script setup> element` `Single file component can contain only one <script setup> element`,
) )
}) })
test('should not warn script & script setup', () => { test('should not warn script & script setup', () => {
expect( expect(
parse( parse(
`<script setup>console.log(1)</script><script>console.log(1)</script>` `<script setup>console.log(1)</script><script>console.log(1)</script>`,
).errors.length ).errors.length,
).toBe(0) ).toBe(0)
}) })
@ -391,7 +391,7 @@ h1 { color: red }
test('should throw error if no <template> or <script> is present', () => { test('should throw error if no <template> or <script> is present', () => {
assertWarning( assertWarning(
parse(`import { ref } from 'vue'`).errors, parse(`import { ref } from 'vue'`).errors,
`At least one <template> or <script> is required in a single file component` `At least one <template> or <script> is required in a single file component`,
) )
}) })
}) })

View File

@ -11,7 +11,7 @@ describe('compiler sfc: rewriteDefault', () => {
test('rewrite export default', () => { test('rewrite export default', () => {
expect( expect(
rewriteDefault(`export default {}`, 'script') rewriteDefault(`export default {}`, 'script'),
).toMatchInlineSnapshot(`"const script = {}"`) ).toMatchInlineSnapshot(`"const script = {}"`)
}) })
@ -27,8 +27,8 @@ describe('compiler sfc: rewriteDefault', () => {
expect( expect(
rewriteDefault( rewriteDefault(
`const a = 1 \n export { a as b, a as default, a as c}`, `const a = 1 \n export { a as b, a as default, a as c}`,
'script' 'script',
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const a = 1 "const a = 1
export { a as b, a as c} export { a as b, a as c}
@ -38,8 +38,8 @@ describe('compiler sfc: rewriteDefault', () => {
expect( expect(
rewriteDefault( rewriteDefault(
`const a = 1 \n export { a as b, a as default , a as c}`, `const a = 1 \n export { a as b, a as default , a as c}`,
'script' 'script',
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const a = 1 "const a = 1
export { a as b, a as c} export { a as b, a as c}
@ -49,8 +49,8 @@ describe('compiler sfc: rewriteDefault', () => {
expect( expect(
rewriteDefault( rewriteDefault(
`const a = 1 \n export { a as b } \n export { a as default, a as c }`, `const a = 1 \n export { a as b } \n export { a as default, a as c }`,
'script' 'script',
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const a = 1 "const a = 1
export { a as b } export { a as b }
@ -69,7 +69,7 @@ describe('compiler sfc: rewriteDefault', () => {
test('export named default multiline', () => { test('export named default multiline', () => {
expect( expect(
rewriteDefault(`let App = {}\n export {\nApp as default\n}`, '_sfc_main') rewriteDefault(`let App = {}\n export {\nApp as default\n}`, '_sfc_main'),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"let App = {} "let App = {}
export { export {
@ -84,8 +84,8 @@ describe('compiler sfc: rewriteDefault', () => {
rewriteDefault( rewriteDefault(
`const a = 1 \n export {\n a as b,\n a as default,\n a as c}\n` + `const a = 1 \n export {\n a as b,\n a as default,\n a as c}\n` +
`// export { myFunction as default }`, `// export { myFunction as default }`,
'script' 'script',
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const a = 1 "const a = 1
export { export {
@ -100,8 +100,8 @@ describe('compiler sfc: rewriteDefault', () => {
rewriteDefault( rewriteDefault(
`const a = 1 \n export {\n a as b,\n a as default ,\n a as c}\n` + `const a = 1 \n export {\n a as b,\n a as default ,\n a as c}\n` +
`// export { myFunction as default }`, `// export { myFunction as default }`,
'script' 'script',
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const a = 1 "const a = 1
export { export {
@ -115,7 +115,7 @@ describe('compiler sfc: rewriteDefault', () => {
test(`export { default } from '...'`, async () => { test(`export { default } from '...'`, async () => {
expect( expect(
rewriteDefault(`export { default, foo } from './index.js'`, 'script') rewriteDefault(`export { default, foo } from './index.js'`, 'script'),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"import { default as __VUE_DEFAULT__ } from './index.js' "import { default as __VUE_DEFAULT__ } from './index.js'
export { foo } from './index.js' export { foo } from './index.js'
@ -123,7 +123,7 @@ describe('compiler sfc: rewriteDefault', () => {
`) `)
expect( expect(
rewriteDefault(`export { default , foo } from './index.js'`, 'script') rewriteDefault(`export { default , foo } from './index.js'`, 'script'),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"import { default as __VUE_DEFAULT__ } from './index.js' "import { default as __VUE_DEFAULT__ } from './index.js'
export { foo } from './index.js' export { foo } from './index.js'
@ -131,7 +131,7 @@ describe('compiler sfc: rewriteDefault', () => {
`) `)
expect( expect(
rewriteDefault(`export { foo, default } from './index.js'`, 'script') rewriteDefault(`export { foo, default } from './index.js'`, 'script'),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"import { default as __VUE_DEFAULT__ } from './index.js' "import { default as __VUE_DEFAULT__ } from './index.js'
export { foo, } from './index.js' export { foo, } from './index.js'
@ -141,8 +141,8 @@ describe('compiler sfc: rewriteDefault', () => {
expect( expect(
rewriteDefault( rewriteDefault(
`export { foo as default, bar } from './index.js'`, `export { foo as default, bar } from './index.js'`,
'script' 'script',
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"import { foo as __VUE_DEFAULT__ } from './index.js' "import { foo as __VUE_DEFAULT__ } from './index.js'
export { bar } from './index.js' export { bar } from './index.js'
@ -152,8 +152,8 @@ describe('compiler sfc: rewriteDefault', () => {
expect( expect(
rewriteDefault( rewriteDefault(
`export { foo as default , bar } from './index.js'`, `export { foo as default , bar } from './index.js'`,
'script' 'script',
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"import { foo as __VUE_DEFAULT__ } from './index.js' "import { foo as __VUE_DEFAULT__ } from './index.js'
export { bar } from './index.js' export { bar } from './index.js'
@ -163,8 +163,8 @@ describe('compiler sfc: rewriteDefault', () => {
expect( expect(
rewriteDefault( rewriteDefault(
`export { bar, foo as default } from './index.js'`, `export { bar, foo as default } from './index.js'`,
'script' 'script',
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"import { foo as __VUE_DEFAULT__ } from './index.js' "import { foo as __VUE_DEFAULT__ } from './index.js'
export { bar, } from './index.js' export { bar, } from './index.js'
@ -174,8 +174,8 @@ describe('compiler sfc: rewriteDefault', () => {
expect( expect(
rewriteDefault( rewriteDefault(
`export { foo as default } from './index.js' \n const foo = 1`, `export { foo as default } from './index.js' \n const foo = 1`,
'script' 'script',
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"import { foo as __VUE_DEFAULT__ } from './index.js' "import { foo as __VUE_DEFAULT__ } from './index.js'
export { } from './index.js' export { } from './index.js'
@ -186,8 +186,8 @@ describe('compiler sfc: rewriteDefault', () => {
expect( expect(
rewriteDefault( rewriteDefault(
`const a = 1 \nexport { a as default } from 'xxx'`, `const a = 1 \nexport { a as default } from 'xxx'`,
'script' 'script',
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"import { a as __VUE_DEFAULT__ } from 'xxx' "import { a as __VUE_DEFAULT__ } from 'xxx'
const a = 1 const a = 1
@ -206,7 +206,10 @@ describe('compiler sfc: rewriteDefault', () => {
test('export default class w/ comments', async () => { test('export default class w/ comments', async () => {
expect( expect(
rewriteDefault(`// export default\nexport default class Foo {}`, 'script') rewriteDefault(
`// export default\nexport default class Foo {}`,
'script',
),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"// export default "// export default
class Foo {} class Foo {}
@ -218,8 +221,8 @@ describe('compiler sfc: rewriteDefault', () => {
expect( expect(
rewriteDefault( rewriteDefault(
`export default {}\n` + `// export default class Foo {}`, `export default {}\n` + `// export default class Foo {}`,
'script' 'script',
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const script = {} "const script = {}
// export default class Foo {}" // export default class Foo {}"
@ -230,8 +233,8 @@ describe('compiler sfc: rewriteDefault', () => {
expect( expect(
rewriteDefault( rewriteDefault(
`/*\nexport default class Foo {}*/\n` + `export default class Bar {}`, `/*\nexport default class Foo {}*/\n` + `export default class Bar {}`,
'script' 'script',
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"/* "/*
export default class Foo {}*/ export default class Foo {}*/
@ -243,8 +246,8 @@ describe('compiler sfc: rewriteDefault', () => {
test('@Component\nexport default class', async () => { test('@Component\nexport default class', async () => {
expect( expect(
rewriteDefault(`@Component\nexport default class Foo {}`, 'script', [ rewriteDefault(`@Component\nexport default class Foo {}`, 'script', [
'decorators-legacy' 'decorators-legacy',
]) ]),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"@Component class Foo {} "@Component class Foo {}
const script = Foo" const script = Foo"
@ -256,8 +259,8 @@ describe('compiler sfc: rewriteDefault', () => {
rewriteDefault( rewriteDefault(
`// export default\n@Component\nexport default class Foo {}`, `// export default\n@Component\nexport default class Foo {}`,
'script', 'script',
['decorators-legacy'] ['decorators-legacy'],
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"// export default "// export default
@Component class Foo {} @Component class Foo {}
@ -269,8 +272,8 @@ describe('compiler sfc: rewriteDefault', () => {
expect( expect(
rewriteDefault( rewriteDefault(
`export default {}\n` + `// @Component\n// export default class Foo {}`, `export default {}\n` + `// @Component\n// export default class Foo {}`,
'script' 'script',
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const script = {} "const script = {}
// @Component // @Component
@ -283,8 +286,8 @@ describe('compiler sfc: rewriteDefault', () => {
rewriteDefault( rewriteDefault(
`/*\n@Component\nexport default class Foo {}*/\n` + `/*\n@Component\nexport default class Foo {}*/\n` +
`export default class Bar {}`, `export default class Bar {}`,
'script' 'script',
) ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"/* "/*
@Component @Component

View File

@ -1,14 +1,14 @@
import { import {
generate, type TransformOptions,
baseParse, baseParse,
generate,
transform, transform,
TransformOptions
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { import {
transformAssetUrl, type AssetURLOptions,
createAssetUrlTransformWithOptions, createAssetUrlTransformWithOptions,
AssetURLOptions, normalizeOptions,
normalizeOptions transformAssetUrl,
} from '../src/template/transformAssetUrl' } from '../src/template/transformAssetUrl'
import { transformElement } from '../../compiler-core/src/transforms/transformElement' import { transformElement } from '../../compiler-core/src/transforms/transformElement'
import { transformBind } from '../../compiler-core/src/transforms/vBind' import { transformBind } from '../../compiler-core/src/transforms/vBind'
@ -17,7 +17,7 @@ import { stringifyStatic } from '../../compiler-dom/src/transforms/stringifyStat
function compileWithAssetUrls( function compileWithAssetUrls(
template: string, template: string,
options?: AssetURLOptions, options?: AssetURLOptions,
transformOptions?: TransformOptions transformOptions?: TransformOptions,
) { ) {
const ast = baseParse(template) const ast = baseParse(template)
const t = options const t = options
@ -26,9 +26,9 @@ function compileWithAssetUrls(
transform(ast, { transform(ast, {
nodeTransforms: [t, transformElement], nodeTransforms: [t, transformElement],
directiveTransforms: { directiveTransforms: {
bind: transformBind bind: transformBind,
}, },
...transformOptions ...transformOptions,
}) })
return generate(ast, { mode: 'module' }) return generate(ast, { mode: 'module' })
} }
@ -57,8 +57,8 @@ describe('compiler sfc: transform asset url', () => {
'<use href="~@svg/file.svg#fragment"></use>', '<use href="~@svg/file.svg#fragment"></use>',
{}, {},
{ {
hoistStatic: true hoistStatic: true,
} },
) )
expect(result.code).toMatchSnapshot() expect(result.code).toMatchSnapshot()
}) })
@ -79,8 +79,8 @@ describe('compiler sfc: transform asset url', () => {
`<img src="~bar.png"></img>` + // -> still converts to import `<img src="~bar.png"></img>` + // -> still converts to import
`<img src="@theme/bar.png"></img>`, // -> still converts to import `<img src="@theme/bar.png"></img>`, // -> still converts to import
{ {
base: '/foo' base: '/foo',
} },
) )
expect(code).toMatch(`import _imports_0 from 'bar.png'`) expect(code).toMatch(`import _imports_0 from 'bar.png'`)
expect(code).toMatch(`import _imports_1 from '@theme/bar.png'`) expect(code).toMatch(`import _imports_1 from '@theme/bar.png'`)
@ -94,8 +94,8 @@ describe('compiler sfc: transform asset url', () => {
`<img src="https://foo.bar/baz.png"/>` + `<img src="https://foo.bar/baz.png"/>` +
`<img src="//foo.bar/baz.png"/>`, `<img src="//foo.bar/baz.png"/>`,
{ {
includeAbsolute: true includeAbsolute: true,
} },
) )
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
@ -108,7 +108,7 @@ describe('compiler sfc: transform asset url', () => {
<circle id="myCircle" cx="0" cy="0" r="5" /> <circle id="myCircle" cx="0" cy="0" r="5" />
</defs> </defs>
<use x="5" y="5" xlink:href="#myCircle" /> <use x="5" y="5" xlink:href="#myCircle" />
</svg>` </svg>`,
) )
// should not remove it // should not remove it
expect(code).toMatch(`"xlink:href": "#myCircle"`) expect(code).toMatch(`"xlink:href": "#myCircle"`)
@ -116,7 +116,7 @@ describe('compiler sfc: transform asset url', () => {
test('should allow for full base URLs, with paths', () => { test('should allow for full base URLs, with paths', () => {
const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, { const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, {
base: 'http://localhost:3000/src/' base: 'http://localhost:3000/src/',
}) })
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -124,7 +124,7 @@ describe('compiler sfc: transform asset url', () => {
test('should allow for full base URLs, without paths', () => { test('should allow for full base URLs, without paths', () => {
const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, { const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, {
base: 'http://localhost:3000' base: 'http://localhost:3000',
}) })
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -132,7 +132,7 @@ describe('compiler sfc: transform asset url', () => {
test('should allow for full base URLs, without port', () => { test('should allow for full base URLs, without port', () => {
const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, { const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, {
base: 'http://localhost' base: 'http://localhost',
}) })
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -140,7 +140,7 @@ describe('compiler sfc: transform asset url', () => {
test('should allow for full base URLs, without protocol', () => { test('should allow for full base URLs, without protocol', () => {
const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, { const { code } = compileWithAssetUrls(`<img src="./logo.png" />`, {
base: '//localhost' base: '//localhost',
}) })
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -156,12 +156,12 @@ describe('compiler sfc: transform asset url', () => {
`<img src="./bar.png"/>` + `<img src="./bar.png"/>` +
`</div>`, `</div>`,
{ {
includeAbsolute: true includeAbsolute: true,
}, },
{ {
hoistStatic: true, hoistStatic: true,
transformHoist: stringifyStatic transformHoist: stringifyStatic,
} },
) )
expect(code).toMatch(`_createStaticVNode`) expect(code).toMatch(`_createStaticVNode`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -171,12 +171,12 @@ describe('compiler sfc: transform asset url', () => {
const { code } = compileWithAssetUrls( const { code } = compileWithAssetUrls(
`<div><img src="/foo bar.png"/></div>`, `<div><img src="/foo bar.png"/></div>`,
{ {
includeAbsolute: true includeAbsolute: true,
}, },
{ {
hoistStatic: true, hoistStatic: true,
transformHoist: stringifyStatic transformHoist: stringifyStatic,
} },
) )
expect(code).toMatch(`_createElementVNode`) expect(code).toMatch(`_createElementVNode`)
expect(code).toContain(`import _imports_0 from '/foo bar.png'`) expect(code).toContain(`import _imports_0 from '/foo bar.png'`)
@ -186,12 +186,12 @@ describe('compiler sfc: transform asset url', () => {
const { code } = compileWithAssetUrls( const { code } = compileWithAssetUrls(
`<div><img src="./foo bar.png"/></div>`, `<div><img src="./foo bar.png"/></div>`,
{ {
includeAbsolute: true includeAbsolute: true,
}, },
{ {
hoistStatic: true, hoistStatic: true,
transformHoist: stringifyStatic transformHoist: stringifyStatic,
} },
) )
expect(code).toMatch(`_createElementVNode`) expect(code).toMatch(`_createElementVNode`)
expect(code).toContain(`import _imports_0 from './foo bar.png'`) expect(code).toContain(`import _imports_0 from './foo bar.png'`)

View File

@ -1,25 +1,25 @@
import { import {
generate, type TransformOptions,
baseParse, baseParse,
generate,
transform, transform,
TransformOptions
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { import {
createSrcsetTransformWithOptions,
transformSrcset, transformSrcset,
createSrcsetTransformWithOptions
} from '../src/template/transformSrcset' } from '../src/template/transformSrcset'
import { transformElement } from '../../compiler-core/src/transforms/transformElement' import { transformElement } from '../../compiler-core/src/transforms/transformElement'
import { transformBind } from '../../compiler-core/src/transforms/vBind' import { transformBind } from '../../compiler-core/src/transforms/vBind'
import { import {
AssetURLOptions, type AssetURLOptions,
normalizeOptions normalizeOptions,
} from '../src/template/transformAssetUrl' } from '../src/template/transformAssetUrl'
import { stringifyStatic } from '../../compiler-dom/src/transforms/stringifyStatic' import { stringifyStatic } from '../../compiler-dom/src/transforms/stringifyStatic'
function compileWithSrcset( function compileWithSrcset(
template: string, template: string,
options?: AssetURLOptions, options?: AssetURLOptions,
transformOptions?: TransformOptions transformOptions?: TransformOptions,
) { ) {
const ast = baseParse(template) const ast = baseParse(template)
const srcsetTransform = options const srcsetTransform = options
@ -29,9 +29,9 @@ function compileWithSrcset(
hoistStatic: true, hoistStatic: true,
nodeTransforms: [srcsetTransform, transformElement], nodeTransforms: [srcsetTransform, transformElement],
directiveTransforms: { directiveTransforms: {
bind: transformBind bind: transformBind,
}, },
...transformOptions ...transformOptions,
}) })
return generate(ast, { mode: 'module' }) return generate(ast, { mode: 'module' })
} }
@ -59,16 +59,16 @@ describe('compiler sfc: transform srcset', () => {
test('transform srcset w/ base', () => { test('transform srcset w/ base', () => {
expect( expect(
compileWithSrcset(src, { compileWithSrcset(src, {
base: '/foo' base: '/foo',
}).code }).code,
).toMatchSnapshot() ).toMatchSnapshot()
}) })
test('transform srcset w/ includeAbsolute: true', () => { test('transform srcset w/ includeAbsolute: true', () => {
expect( expect(
compileWithSrcset(src, { compileWithSrcset(src, {
includeAbsolute: true includeAbsolute: true,
}).code }).code,
).toMatchSnapshot() ).toMatchSnapshot()
}) })
@ -76,12 +76,12 @@ describe('compiler sfc: transform srcset', () => {
const code = compileWithSrcset( const code = compileWithSrcset(
`<div>${src}</div>`, `<div>${src}</div>`,
{ {
includeAbsolute: true includeAbsolute: true,
}, },
{ {
hoistStatic: true, hoistStatic: true,
transformHoist: stringifyStatic transformHoist: stringifyStatic,
} },
).code ).code
expect(code).toMatch(`_createStaticVNode`) expect(code).toMatch(`_createStaticVNode`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -94,7 +94,7 @@ describe('compiler sfc: transform srcset', () => {
<img srcset="@/logo.png 1x, ./logo.png 2x"/> <img srcset="@/logo.png 1x, ./logo.png 2x"/>
`, `,
{ base: '/foo/' }, { base: '/foo/' },
{ hoistStatic: true } { hoistStatic: true },
).code ).code
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })

View File

@ -1,7 +1,7 @@
import { import {
isRelativeUrl, isDataUrl,
isExternalUrl, isExternalUrl,
isDataUrl isRelativeUrl,
} from '../src/template/templateUtils' } from '../src/template/templateUtils'
describe('compiler sfc:templateUtils isRelativeUrl', () => { describe('compiler sfc:templateUtils isRelativeUrl', () => {

View File

@ -1,8 +1,8 @@
import { import {
parse, type SFCParseOptions,
SFCScriptCompileOptions, type SFCScriptCompileOptions,
compileScript, compileScript,
SFCParseOptions parse,
} from '../src' } from '../src'
import { parse as babelParse } from '@babel/parser' import { parse as babelParse } from '@babel/parser'
@ -11,7 +11,7 @@ export const mockId = 'xxxxxxxx'
export function compileSFCScript( export function compileSFCScript(
src: string, src: string,
options?: Partial<SFCScriptCompileOptions>, options?: Partial<SFCScriptCompileOptions>,
parseOptions?: SFCParseOptions parseOptions?: SFCParseOptions,
) { ) {
const { descriptor, errors } = parse(src, parseOptions) const { descriptor, errors } = parse(src, parseOptions)
if (errors.length) { if (errors.length) {
@ -19,7 +19,7 @@ export function compileSFCScript(
} }
return compileScript(descriptor, { return compileScript(descriptor, {
...options, ...options,
id: mockId id: mockId,
}) })
} }
@ -30,8 +30,8 @@ export function assertCode(code: string) {
sourceType: 'module', sourceType: 'module',
plugins: [ plugins: [
'typescript', 'typescript',
['importAttributes', { deprecatedAssertSyntax: true }] ['importAttributes', { deprecatedAssertSyntax: true }],
] ],
}) })
} catch (e: any) { } catch (e: any) {
console.log(code) console.log(code)

View File

@ -1,7 +1,7 @@
import { LRUCache } from 'lru-cache' import { LRUCache } from 'lru-cache'
export function createCache<T extends {}>( export function createCache<T extends {}>(
max = 500 max = 500,
): Map<string, T> | LRUCache<string, T> { ): Map<string, T> | LRUCache<string, T> {
if (__GLOBAL__ || __ESM_BROWSER__) { if (__GLOBAL__ || __ESM_BROWSER__) {
return new Map<string, T>() return new Map<string, T>()

Some files were not shown because too many files have changed in this diff Show More