From 584eae60d1abe80d15b317ec80b7556712df8e5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Mon, 14 Nov 2022 08:36:03 +0800 Subject: [PATCH 01/47] fix(compiler-sfc): always generate runtime prop type for Function (#7112) fix #7111 --- .../__snapshots__/compileScript.spec.ts.snap | 29 +++++++++++++++++++ .../__tests__/compileScript.spec.ts | 29 +++++++++++++++++++ packages/compiler-sfc/src/compileScript.ts | 9 ++---- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index 80fb1f087cc..b167dea7026 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -1729,6 +1729,35 @@ const props = __props as { +return { props, get defaults() { return defaults } } +} + +})" +`; + +exports[`SFC compile + `, + { isProd: true } + ) + assertCode(content) + expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`) + expect(content).toMatch( + ` + _mergeDefaults({ + foo: { type: Function }, + bar: { type: Boolean }, + baz: { type: [Boolean, Function] }, + qux: null + }, { ...defaults })`.trim() + ) + }) + test('defineEmits w/ type', () => { const { content } = compile(` + `, + { isProd: true } + ) + assertCode(content) + expect(content).toMatch(`const props = __props`) + + // foo has no default value, the Function can be dropped + expect(content).toMatch(`foo: null`) + expect(content).toMatch(`bar: { type: Boolean }`) + expect(content).toMatch( + `baz: { type: [Boolean, Function], default: true }` + ) + expect(content).toMatch(`qux: { default: 'hi' }`) + }) + test('withDefaults (dynamic)', () => { const { content } = compile(` ` + ) + ).toThrow(`Assignment to constant variable.`) + }) }) }) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index bb4e7f42c13..997d7c11b9f 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -41,7 +41,8 @@ import { Program, ObjectMethod, LVal, - Expression + Expression, + VariableDeclaration } from '@babel/types' import { walk } from 'estree-walker' import { RawSourceMap } from 'source-map' @@ -310,6 +311,7 @@ export function compileScript( { local: string // local identifier, may be different default?: Expression + isConst: boolean } > = Object.create(null) @@ -404,7 +406,11 @@ export function compileScript( } } - function processDefineProps(node: Node, declId?: LVal): boolean { + function processDefineProps( + node: Node, + declId?: LVal, + declKind?: VariableDeclaration['kind'] + ): boolean { if (!isCallOf(node, DEFINE_PROPS)) { return false } @@ -442,6 +448,7 @@ export function compileScript( } if (declId) { + const isConst = declKind === 'const' if (enablePropsTransform && declId.type === 'ObjectPattern') { propsDestructureDecl = declId // props destructure - handle compilation sugar @@ -468,12 +475,14 @@ export function compileScript( // store default value propsDestructuredBindings[propKey] = { local: left.name, - default: right + default: right, + isConst } } else if (prop.value.type === 'Identifier') { // simple destructure propsDestructuredBindings[propKey] = { - local: prop.value.name + local: prop.value.name, + isConst } } else { error( @@ -494,11 +503,15 @@ export function compileScript( return true } - function processWithDefaults(node: Node, declId?: LVal): boolean { + function processWithDefaults( + node: Node, + declId?: LVal, + declKind?: VariableDeclaration['kind'] + ): boolean { if (!isCallOf(node, WITH_DEFAULTS)) { return false } - if (processDefineProps(node.arguments[0], declId)) { + if (processDefineProps(node.arguments[0], declId, declKind)) { if (propsRuntimeDecl) { error( `${WITH_DEFAULTS} can only be used with type-based ` + @@ -1197,8 +1210,8 @@ export function compileScript( if (decl.init) { // defineProps / defineEmits const isDefineProps = - processDefineProps(decl.init, decl.id) || - processWithDefaults(decl.init, decl.id) + processDefineProps(decl.init, decl.id, node.kind) || + processWithDefaults(decl.init, decl.id, node.kind) const isDefineEmits = processDefineEmits(decl.init, decl.id) if (isDefineProps || isDefineEmits) { if (left === 1) { diff --git a/packages/reactivity-transform/__tests__/reactivityTransform.spec.ts b/packages/reactivity-transform/__tests__/reactivityTransform.spec.ts index d1e0368f686..5255be6ba8b 100644 --- a/packages/reactivity-transform/__tests__/reactivityTransform.spec.ts +++ b/packages/reactivity-transform/__tests__/reactivityTransform.spec.ts @@ -531,4 +531,34 @@ describe('errors', () => { `does not support rest element` ) }) + + test('assignment to constant variable', () => { + expect(() => + transform(` + const foo = $ref(0) + foo = 1 + `) + ).toThrow(`Assignment to constant variable.`) + + expect(() => + transform(` + const [a, b] = $([1, 2]) + a = 1 + `) + ).toThrow(`Assignment to constant variable.`) + + expect(() => + transform(` + const foo = $ref(0) + foo++ + `) + ).toThrow(`Assignment to constant variable.`) + + expect(() => + transform(` + const foo = $ref(0) + bar = foo + `) + ).not.toThrow() + }) }) diff --git a/packages/reactivity-transform/src/reactivityTransform.ts b/packages/reactivity-transform/src/reactivityTransform.ts index 4f6d47a6d84..f1d5d8916fd 100644 --- a/packages/reactivity-transform/src/reactivityTransform.ts +++ b/packages/reactivity-transform/src/reactivityTransform.ts @@ -37,7 +37,11 @@ export function shouldTransform(src: string): boolean { return transformCheckRE.test(src) } -type Scope = Record +interface Binding { + isConst?: boolean + isProp?: boolean +} +type Scope = Record export interface RefTransformOptions { filename?: string @@ -118,6 +122,7 @@ export function transformAST( { local: string // local identifier, may be different default?: any + isConst?: boolean } > ): { @@ -168,17 +173,20 @@ export function transformAST( let escapeScope: CallExpression | undefined // inside $$() const excludedIds = new WeakSet() const parentStack: Node[] = [] - const propsLocalToPublicMap = Object.create(null) + const propsLocalToPublicMap: Record = Object.create(null) if (knownRefs) { for (const key of knownRefs) { - rootScope[key] = true + rootScope[key] = {} } } if (knownProps) { for (const key in knownProps) { - const { local } = knownProps[key] - rootScope[local] = 'prop' + const { local, isConst } = knownProps[key] + rootScope[local] = { + isProp: true, + isConst: !!isConst + } propsLocalToPublicMap[local] = key } } @@ -218,7 +226,7 @@ export function transformAST( return false } - function error(msg: string, node: Node) { + function error(msg: string, node: Node): never { const e = new Error(msg) ;(e as any).node = node throw e @@ -229,10 +237,10 @@ export function transformAST( return `_${msg}` } - function registerBinding(id: Identifier, isRef = false) { + function registerBinding(id: Identifier, binding?: Binding) { excludedIds.add(id) if (currentScope) { - currentScope[id.name] = isRef + currentScope[id.name] = binding ? binding : false } else { error( 'registerBinding called without active scope, something is wrong.', @@ -241,7 +249,8 @@ export function transformAST( } } - const registerRefBinding = (id: Identifier) => registerBinding(id, true) + const registerRefBinding = (id: Identifier, isConst = false) => + registerBinding(id, { isConst }) let tempVarCount = 0 function genTempVar() { @@ -296,7 +305,12 @@ export function transformAST( isCall && (refCall = isRefCreationCall((decl as any).init.callee.name)) ) { - processRefDeclaration(refCall, decl.id, decl.init as CallExpression) + processRefDeclaration( + refCall, + decl.id, + decl.init as CallExpression, + stmt.kind === 'const' + ) } else { const isProps = isRoot && isCall && (decl as any).init.callee.name === 'defineProps' @@ -316,7 +330,8 @@ export function transformAST( function processRefDeclaration( method: string, id: VariableDeclarator['id'], - call: CallExpression + call: CallExpression, + isConst: boolean ) { excludedIds.add(call.callee as Identifier) if (method === convertSymbol) { @@ -325,16 +340,16 @@ export function transformAST( s.remove(call.callee.start! + offset, call.callee.end! + offset) if (id.type === 'Identifier') { // single variable - registerRefBinding(id) + registerRefBinding(id, isConst) } else if (id.type === 'ObjectPattern') { - processRefObjectPattern(id, call) + processRefObjectPattern(id, call, isConst) } else if (id.type === 'ArrayPattern') { - processRefArrayPattern(id, call) + processRefArrayPattern(id, call, isConst) } } else { // shorthands if (id.type === 'Identifier') { - registerRefBinding(id) + registerRefBinding(id, isConst) // replace call s.overwrite( call.start! + offset, @@ -350,6 +365,7 @@ export function transformAST( function processRefObjectPattern( pattern: ObjectPattern, call: CallExpression, + isConst: boolean, tempVar?: string, path: PathSegment[] = [] ) { @@ -384,21 +400,27 @@ export function transformAST( // { foo: bar } nameId = p.value } else if (p.value.type === 'ObjectPattern') { - processRefObjectPattern(p.value, call, tempVar, [...path, key]) + processRefObjectPattern(p.value, call, isConst, tempVar, [ + ...path, + key + ]) } else if (p.value.type === 'ArrayPattern') { - processRefArrayPattern(p.value, call, tempVar, [...path, key]) + processRefArrayPattern(p.value, call, isConst, tempVar, [ + ...path, + key + ]) } else if (p.value.type === 'AssignmentPattern') { if (p.value.left.type === 'Identifier') { // { foo: bar = 1 } nameId = p.value.left defaultValue = p.value.right } else if (p.value.left.type === 'ObjectPattern') { - processRefObjectPattern(p.value.left, call, tempVar, [ + processRefObjectPattern(p.value.left, call, isConst, tempVar, [ ...path, [key, p.value.right] ]) } else if (p.value.left.type === 'ArrayPattern') { - processRefArrayPattern(p.value.left, call, tempVar, [ + processRefArrayPattern(p.value.left, call, isConst, tempVar, [ ...path, [key, p.value.right] ]) @@ -412,7 +434,7 @@ export function transformAST( error(`reactivity destructure does not support rest elements.`, p) } if (nameId) { - registerRefBinding(nameId) + registerRefBinding(nameId, isConst) // inject toRef() after original replaced pattern const source = pathToString(tempVar, path) const keyStr = isString(key) @@ -437,6 +459,7 @@ export function transformAST( function processRefArrayPattern( pattern: ArrayPattern, call: CallExpression, + isConst: boolean, tempVar?: string, path: PathSegment[] = [] ) { @@ -462,12 +485,12 @@ export function transformAST( // [...a] error(`reactivity destructure does not support rest elements.`, e) } else if (e.type === 'ObjectPattern') { - processRefObjectPattern(e, call, tempVar, [...path, i]) + processRefObjectPattern(e, call, isConst, tempVar, [...path, i]) } else if (e.type === 'ArrayPattern') { - processRefArrayPattern(e, call, tempVar, [...path, i]) + processRefArrayPattern(e, call, isConst, tempVar, [...path, i]) } if (nameId) { - registerRefBinding(nameId) + registerRefBinding(nameId, isConst) // inject toRef() after original replaced pattern const source = pathToString(tempVar, path) const defaultStr = defaultValue ? `, ${snip(defaultValue)}` : `` @@ -520,9 +543,18 @@ export function transformAST( parentStack: Node[] ): boolean { if (hasOwn(scope, id.name)) { - const bindingType = scope[id.name] - if (bindingType) { - const isProp = bindingType === 'prop' + const binding = scope[id.name] + + if (binding) { + if ( + binding.isConst && + ((parent.type === 'AssignmentExpression' && id === parent.left) || + parent.type === 'UpdateExpression') + ) { + error(`Assignment to constant variable.`, id) + } + + const { isProp } = binding if (isStaticProperty(parent) && parent.shorthand) { // let binding used in a property shorthand // skip for destructure patterns @@ -638,18 +670,20 @@ export function transformAST( return this.skip() } - if ( - node.type === 'Identifier' && - // if inside $$(), skip unless this is a destructured prop binding - !(escapeScope && rootScope[node.name] !== 'prop') && - isReferencedIdentifier(node, parent!, parentStack) && - !excludedIds.has(node) - ) { - // walk up the scope chain to check if id should be appended .value - let i = scopeStack.length - while (i--) { - if (rewriteId(scopeStack[i], node, parent!, parentStack)) { - return + if (node.type === 'Identifier') { + const binding = rootScope[node.name] + if ( + // if inside $$(), skip unless this is a destructured prop binding + !(escapeScope && (!binding || !binding.isProp)) && + isReferencedIdentifier(node, parent!, parentStack) && + !excludedIds.has(node) + ) { + // walk up the scope chain to check if id should be appended .value + let i = scopeStack.length + while (i--) { + if (rewriteId(scopeStack[i], node, parent!, parentStack)) { + return + } } } } @@ -729,7 +763,10 @@ export function transformAST( }) return { - rootRefs: Object.keys(rootScope).filter(key => rootScope[key] === true), + rootRefs: Object.keys(rootScope).filter(key => { + const binding = rootScope[key] + return binding && !binding.isProp + }), importedHelpers: [...importedHelpers] } } From efa2ac54d91e40bb2aa8801fecabbd19a4027ea4 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 14 Nov 2022 16:10:05 +0800 Subject: [PATCH 22/47] refactor: use Number constructor when coercing array length --- packages/reactivity/src/effect.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index f1799a62d3a..9ef38c1b065 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -1,5 +1,5 @@ import { TrackOpTypes, TriggerOpTypes } from './operations' -import { extend, isArray, isIntegerKey, isMap, toNumber } from '@vue/shared' +import { extend, isArray, isIntegerKey, isMap } from '@vue/shared' import { EffectScope, recordEffectScope } from './effectScope' import { createDep, @@ -276,7 +276,7 @@ export function trigger( // trigger all effects for target deps = [...depsMap.values()] } else if (key === 'length' && isArray(target)) { - const newLength = toNumber(newValue) + const newLength = Number(newValue) depsMap.forEach((dep, key) => { if (key === 'length' || key >= newLength) { deps.push(dep) From 7d0c63ff4361e59e820441a24bf4fb2a93335a1e Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 14 Nov 2022 16:20:12 +0800 Subject: [PATCH 23/47] fix(custom-elements): use strict number casting close #4946 close #2598 close #2604 This commit also refactors internal usage of previous loose implementation of `toNumber` to the stricter version where applicable. Use of `looseToNumber` is preserved for `v-model.number` modifier to ensure backwards compatibility and consistency with Vue 2 behavior. --- packages/runtime-core/src/compat/instance.ts | 6 +++--- packages/runtime-core/src/componentEmits.ts | 6 +++--- .../runtime-core/src/components/Suspense.ts | 11 ++++++++++- packages/runtime-core/src/index.ts | 2 +- packages/runtime-core/src/warning.ts | 12 ++++++++++++ .../runtime-dom/src/components/Transition.ts | 18 ++---------------- packages/runtime-dom/src/directives/vModel.ts | 11 +++++++---- packages/shared/src/index.ts | 14 +++++++++++++- 8 files changed, 51 insertions(+), 29 deletions(-) diff --git a/packages/runtime-core/src/compat/instance.ts b/packages/runtime-core/src/compat/instance.ts index 01e6618d45b..141f0bf0a0f 100644 --- a/packages/runtime-core/src/compat/instance.ts +++ b/packages/runtime-core/src/compat/instance.ts @@ -2,9 +2,9 @@ import { extend, looseEqual, looseIndexOf, + looseToNumber, NOOP, - toDisplayString, - toNumber + toDisplayString } from '@vue/shared' import { ComponentPublicInstance, @@ -148,7 +148,7 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) { $createElement: () => compatH, _c: () => compatH, _o: () => legacyMarkOnce, - _n: () => toNumber, + _n: () => looseToNumber, _s: () => toDisplayString, _l: () => renderList, _t: i => legacyRenderSlot.bind(null, i), diff --git a/packages/runtime-core/src/componentEmits.ts b/packages/runtime-core/src/componentEmits.ts index 9a9fe050150..7568741e24e 100644 --- a/packages/runtime-core/src/componentEmits.ts +++ b/packages/runtime-core/src/componentEmits.ts @@ -10,8 +10,8 @@ import { isObject, isString, isOn, - toNumber, - UnionToIntersection + UnionToIntersection, + looseToNumber } from '@vue/shared' import { ComponentInternalInstance, @@ -126,7 +126,7 @@ export function emit( args = rawArgs.map(a => (isString(a) ? a.trim() : a)) } if (number) { - args = rawArgs.map(toNumber) + args = rawArgs.map(looseToNumber) } } diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts index baf57088626..6ccca1e26b8 100644 --- a/packages/runtime-core/src/components/Suspense.ts +++ b/packages/runtime-core/src/components/Suspense.ts @@ -22,7 +22,12 @@ import { } from '../renderer' import { queuePostFlushCb } from '../scheduler' import { filterSingleRoot, updateHOCHostEl } from '../componentRenderUtils' -import { pushWarningContext, popWarningContext, warn } from '../warning' +import { + pushWarningContext, + popWarningContext, + warn, + assertNumber +} from '../warning' import { handleError, ErrorCodes } from '../errorHandling' export interface SuspenseProps { @@ -419,6 +424,10 @@ function createSuspenseBoundary( } = rendererInternals const timeout = toNumber(vnode.props && vnode.props.timeout) + if (__DEV__) { + assertNumber(timeout, `Suspense timeout`) + } + const suspense: SuspenseBoundary = { vnode, parent, diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 7f822489bdc..086beaa6a97 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -104,7 +104,7 @@ export { useSSRContext, ssrContextKey } from './helpers/useSsrContext' export { createRenderer, createHydrationRenderer } from './renderer' export { queuePostFlushCb } from './scheduler' -export { warn } from './warning' +export { warn, assertNumber } from './warning' export { handleError, callWithErrorHandling, diff --git a/packages/runtime-core/src/warning.ts b/packages/runtime-core/src/warning.ts index 9b793ab5148..b314985b771 100644 --- a/packages/runtime-core/src/warning.ts +++ b/packages/runtime-core/src/warning.ts @@ -162,3 +162,15 @@ function formatProp(key: string, value: unknown, raw?: boolean): any { return raw ? value : [`${key}=`, value] } } + +/** + * @internal + */ +export function assertNumber(val: unknown, type: string) { + if (!__DEV__) return + if (typeof val !== 'number') { + warn(`${type} is not a valid number - ` + `got ${JSON.stringify(val)}.`) + } else if (isNaN(val)) { + warn(`${type} is NaN - ` + 'the duration expression might be incorrect.') + } +} diff --git a/packages/runtime-dom/src/components/Transition.ts b/packages/runtime-dom/src/components/Transition.ts index 2c483c76e72..205bea9668f 100644 --- a/packages/runtime-dom/src/components/Transition.ts +++ b/packages/runtime-dom/src/components/Transition.ts @@ -2,7 +2,7 @@ import { BaseTransition, BaseTransitionProps, h, - warn, + assertNumber, FunctionalComponent, compatUtils, DeprecationTypes @@ -283,24 +283,10 @@ function normalizeDuration( function NumberOf(val: unknown): number { const res = toNumber(val) - if (__DEV__) validateDuration(res) + if (__DEV__) assertNumber(res, ' explicit duration') return res } -function validateDuration(val: unknown) { - if (typeof val !== 'number') { - warn( - ` explicit duration is not a valid number - ` + - `got ${JSON.stringify(val)}.` - ) - } else if (isNaN(val)) { - warn( - ` explicit duration is NaN - ` + - 'the duration expression might be incorrect.' - ) - } -} - export function addTransitionClass(el: Element, cls: string) { cls.split(/\s+/).forEach(c => c && el.classList.add(c)) ;( diff --git a/packages/runtime-dom/src/directives/vModel.ts b/packages/runtime-dom/src/directives/vModel.ts index 722b4d9b44c..2cf5f4cfc16 100644 --- a/packages/runtime-dom/src/directives/vModel.ts +++ b/packages/runtime-dom/src/directives/vModel.ts @@ -11,7 +11,7 @@ import { looseEqual, looseIndexOf, invokeArrayFns, - toNumber, + looseToNumber, isSet } from '@vue/shared' @@ -54,7 +54,7 @@ export const vModelText: ModelDirective< domValue = domValue.trim() } if (castToNumber) { - domValue = toNumber(domValue) + domValue = looseToNumber(domValue) } el._assign(domValue) }) @@ -88,7 +88,10 @@ export const vModelText: ModelDirective< if (trim && el.value.trim() === value) { return } - if ((number || el.type === 'number') && toNumber(el.value) === value) { + if ( + (number || el.type === 'number') && + looseToNumber(el.value) === value + ) { return } } @@ -182,7 +185,7 @@ export const vModelSelect: ModelDirective = { const selectedVal = Array.prototype.filter .call(el.options, (o: HTMLOptionElement) => o.selected) .map((o: HTMLOptionElement) => - number ? toNumber(getValue(o)) : getValue(o) + number ? looseToNumber(getValue(o)) : getValue(o) ) el._assign( el.multiple diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index ff302503819..5c1629d5bc4 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -153,11 +153,23 @@ export const def = (obj: object, key: string | symbol, value: any) => { }) } -export const toNumber = (val: any): any => { +/** + * "123-foo" will be parsed to 123 + * This is used for the .number modifier in v-model + */ +export const looseToNumber = (val: any): any => { const n = parseFloat(val) return isNaN(n) ? val : n } +/** + * "123-foo" will be returned as-is + */ +export const toNumber = (val: any): any => { + const n = Number(val) + return isNaN(n) ? val : n +} + let _globalThis: any export const getGlobalThis = (): any => { return ( From ce363e55a82a82805b370a28cf97329d9394283f Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 14 Nov 2022 16:57:44 +0800 Subject: [PATCH 24/47] chore: fix assertNumber for undefined value --- packages/runtime-core/src/warning.ts | 4 +++- packages/runtime-dom/src/components/Transition.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/src/warning.ts b/packages/runtime-core/src/warning.ts index b314985b771..8e93d9efe59 100644 --- a/packages/runtime-core/src/warning.ts +++ b/packages/runtime-core/src/warning.ts @@ -168,7 +168,9 @@ function formatProp(key: string, value: unknown, raw?: boolean): any { */ export function assertNumber(val: unknown, type: string) { if (!__DEV__) return - if (typeof val !== 'number') { + if (val === undefined) { + return + } else if (typeof val !== 'number') { warn(`${type} is not a valid number - ` + `got ${JSON.stringify(val)}.`) } else if (isNaN(val)) { warn(`${type} is NaN - ` + 'the duration expression might be incorrect.') diff --git a/packages/runtime-dom/src/components/Transition.ts b/packages/runtime-dom/src/components/Transition.ts index 205bea9668f..a331c53c235 100644 --- a/packages/runtime-dom/src/components/Transition.ts +++ b/packages/runtime-dom/src/components/Transition.ts @@ -283,7 +283,9 @@ function normalizeDuration( function NumberOf(val: unknown): number { const res = toNumber(val) - if (__DEV__) assertNumber(res, ' explicit duration') + if (__DEV__) { + assertNumber(res, ' explicit duration') + } return res } From 588bd44f036b79d7dee5d23661aa7244f70e6beb Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 14 Nov 2022 17:17:35 +0800 Subject: [PATCH 25/47] fix(reactivity): track hasOwnProperty fix #2619 close #2621 --- packages/reactivity/__tests__/effect.spec.ts | 26 ++++++++++++++++++++ packages/reactivity/src/baseHandlers.ts | 16 ++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/__tests__/effect.spec.ts b/packages/reactivity/__tests__/effect.spec.ts index a322c7209f4..ed23b18446a 100644 --- a/packages/reactivity/__tests__/effect.spec.ts +++ b/packages/reactivity/__tests__/effect.spec.ts @@ -964,5 +964,31 @@ describe('reactivity/effect', () => { m.set(key, 2) expect(fnSpy).toHaveBeenCalledTimes(2) }) + + test('should track hasOwnProperty', () => { + const obj: any = reactive({}) + let has = false + const fnSpy = jest.fn() + + effect(() => { + fnSpy() + has = obj.hasOwnProperty('foo') + }) + expect(fnSpy).toHaveBeenCalledTimes(1) + expect(has).toBe(false) + + obj.foo = 1 + expect(fnSpy).toHaveBeenCalledTimes(2) + expect(has).toBe(true) + + delete obj.foo + expect(fnSpy).toHaveBeenCalledTimes(3) + expect(has).toBe(false) + + // should not trigger on unrelated key + obj.bar = 2 + expect(fnSpy).toHaveBeenCalledTimes(3) + expect(has).toBe(false) + }) }) }) diff --git a/packages/reactivity/src/baseHandlers.ts b/packages/reactivity/src/baseHandlers.ts index dfff56f7fc5..46240dba77c 100644 --- a/packages/reactivity/src/baseHandlers.ts +++ b/packages/reactivity/src/baseHandlers.ts @@ -85,6 +85,13 @@ function createArrayInstrumentations() { return instrumentations } +function hasOwnProperty(key: string) { + // @ts-ignore + const obj = toRaw(this) + track(obj, TrackOpTypes.HAS, key) + return obj.hasOwnProperty(key) +} + function createGetter(isReadonly = false, shallow = false) { return function get(target: Target, key: string | symbol, receiver: object) { if (key === ReactiveFlags.IS_REACTIVE) { @@ -110,8 +117,13 @@ function createGetter(isReadonly = false, shallow = false) { const targetIsArray = isArray(target) - if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) { - return Reflect.get(arrayInstrumentations, key, receiver) + if (!isReadonly) { + if (targetIsArray && hasOwn(arrayInstrumentations, key)) { + return Reflect.get(arrayInstrumentations, key, receiver) + } + if (key === 'hasOwnProperty') { + return hasOwnProperty + } } const res = Reflect.get(target, key, receiver) From eb2a83283caa9de0a45881d860a3cbd9d0bdd279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BF=A0=20/=20green?= Date: Mon, 14 Nov 2022 18:33:29 +0900 Subject: [PATCH 26/47] fix(types): allow assigning wider SetupContext type (#2818) fix #2362 --- packages/runtime-core/src/component.ts | 15 +++++++++------ test-dts/component.test-d.ts | 12 +++++++++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 3fdf566bb98..786e3f3a030 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -180,12 +180,15 @@ export const enum LifecycleHooks { SERVER_PREFETCH = 'sp' } -export interface SetupContext { - attrs: Data - slots: Slots - emit: EmitFn - expose: (exposed?: Record) => void -} +// use `E extends any` to force evaluating type to fix #2362 +export type SetupContext = E extends any + ? { + attrs: Data + slots: Slots + emit: EmitFn + expose: (exposed?: Record) => void + } + : never /** * @internal diff --git a/test-dts/component.test-d.ts b/test-dts/component.test-d.ts index 3463995613c..5678c8e1ceb 100644 --- a/test-dts/component.test-d.ts +++ b/test-dts/component.test-d.ts @@ -11,7 +11,9 @@ import { FunctionalComponent, ComponentPublicInstance, toRefs, - IsAny + IsAny, + SetupContext, + expectAssignable } from './index' declare function extractComponentOptions( @@ -476,3 +478,11 @@ describe('class', () => { expectType(props.foo) }) + +describe('SetupContext', () => { + describe('can assign', () => { + const wider: SetupContext<{ a: () => true; b: () => true }> = {} as any + + expectAssignable true }>>(wider) + }) +}) From b55846f05c4a3b163be2ed70ce64014feec29fac Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 14 Nov 2022 17:50:56 +0800 Subject: [PATCH 27/47] fix(shared): toNumber should only coerce strings --- packages/runtime-core/src/components/Suspense.ts | 2 +- packages/shared/src/index.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts index 6ccca1e26b8..2862178758f 100644 --- a/packages/runtime-core/src/components/Suspense.ts +++ b/packages/runtime-core/src/components/Suspense.ts @@ -423,7 +423,7 @@ function createSuspenseBoundary( o: { parentNode, remove } } = rendererInternals - const timeout = toNumber(vnode.props && vnode.props.timeout) + const timeout = vnode.props ? toNumber(vnode.props.timeout) : undefined if (__DEV__) { assertNumber(timeout, `Suspense timeout`) } diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 5c1629d5bc4..e3fcd86627b 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -163,10 +163,11 @@ export const looseToNumber = (val: any): any => { } /** + * Only conerces number-like strings * "123-foo" will be returned as-is */ export const toNumber = (val: any): any => { - const n = Number(val) + const n = isString(val) ? Number(val) : NaN return isNaN(n) ? val : n } From 9d5e30d911b4fa874565a9514a094fcf0a2ff4ca Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 14 Nov 2022 18:21:58 +0800 Subject: [PATCH 28/47] build: bump jest, puppeteer and use official rollup terser plugin --- jest.config.js | 19 +- package.json | 14 +- .../__snapshots__/codegen.spec.ts.snap | 28 +- .../__snapshots__/compile.spec.ts.snap | 44 +- .../__snapshots__/parse.spec.ts.snap | 3916 ++++++++--------- .../__snapshots__/scopeId.spec.ts.snap | 34 +- .../compiler-core/__tests__/codegen.spec.ts | 26 +- .../__snapshots__/hoistStatic.spec.ts.snap | 142 +- .../transformExpressions.spec.ts.snap | 4 +- .../__snapshots__/transformText.spec.ts.snap | 32 +- .../__snapshots__/vFor.spec.ts.snap | 40 +- .../transforms/__snapshots__/vIf.spec.ts.snap | 54 +- .../__snapshots__/vMemo.spec.ts.snap | 42 +- .../__snapshots__/vModel.spec.ts.snap | 52 +- .../__snapshots__/vOnce.spec.ts.snap | 20 +- .../__snapshots__/vSlot.spec.ts.snap | 84 +- .../__snapshots__/index.spec.ts.snap | 12 +- .../__snapshots__/Transition.spec.ts.snap | 16 +- .../stringifyStatic.spec.ts.snap | 24 +- .../__snapshots__/vModel.spec.ts.snap | 84 +- .../__snapshots__/vShow.spec.ts.snap | 2 +- .../__snapshots__/compileScript.spec.ts.snap | 96 +- .../compileScriptPropsTransform.spec.ts.snap | 14 +- .../compileTemplate.spec.ts.snap | 34 +- .../__snapshots__/cssVars.spec.ts.snap | 32 +- .../templateTransformAssetUrl.spec.ts.snap | 68 +- .../templateTransformSrcset.spec.ts.snap | 204 +- .../compiler-sfc/__tests__/cssVars.spec.ts | 2 +- .../__tests__/ssrComponent.spec.ts | 132 +- .../compiler-ssr/__tests__/ssrElement.spec.ts | 84 +- .../__tests__/ssrFallthroughAttrs.spec.ts | 14 +- .../__tests__/ssrInjectCssVars.spec.ts | 22 +- .../compiler-ssr/__tests__/ssrPortal.spec.ts | 6 +- .../compiler-ssr/__tests__/ssrScopeId.spec.ts | 32 +- .../__tests__/ssrSlotOutlet.spec.ts | 42 +- .../__tests__/ssrSuspense.spec.ts | 12 +- .../compiler-ssr/__tests__/ssrText.spec.ts | 8 +- .../__tests__/ssrTransitionGroup.spec.ts | 16 +- .../compiler-ssr/__tests__/ssrVFor.spec.ts | 14 +- .../compiler-ssr/__tests__/ssrVIf.spec.ts | 16 +- .../compiler-ssr/__tests__/ssrVModel.spec.ts | 72 +- .../compiler-ssr/__tests__/ssrVShow.spec.ts | 50 +- .../runtime-core/__tests__/hydration.spec.ts | 8 +- .../__snapshots__/codeframe.spec.ts.snap | 34 +- .../shared/__tests__/normalizeProp.spec.ts | 2 +- .../shared/__tests__/toDisplayString.spec.ts | 54 +- .../__tests__/componentFunctional.spec.ts | 2 +- packages/vue/__tests__/e2eUtils.ts | 4 +- packages/vue/examples/__tests__/svg.spec.ts | 4 +- pnpm-lock.yaml | 1522 ++++--- rollup.config.mjs | 2 +- 51 files changed, 3687 insertions(+), 3604 deletions(-) diff --git a/jest.config.js b/jest.config.js index 248fb6d086c..5a75ee75584 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,6 +2,17 @@ module.exports = { testEnvironment: 'jsdom', preset: 'ts-jest', setupFilesAfterEnv: ['./scripts/setupJestEnv.ts'], + transform: { + '^.+\\.tsx?$': [ + 'ts-jest', + { + tsconfig: { + target: 'esnext', + sourceMap: true + } + } + ] + }, globals: { __DEV__: true, __TEST__: true, @@ -15,13 +26,7 @@ module.exports = { __FEATURE_OPTIONS_API__: true, __FEATURE_SUSPENSE__: true, __FEATURE_PROD_DEVTOOLS__: false, - __COMPAT__: true, - 'ts-jest': { - tsconfig: { - target: 'esnext', - sourceMap: true - } - } + __COMPAT__: true }, coverageDirectory: 'coverage', coverageReporters: ['html', 'lcov', 'text'], diff --git a/package.json b/package.json index 10e96191221..8d2b5e1d471 100644 --- a/package.json +++ b/package.json @@ -60,10 +60,10 @@ "@rollup/plugin-json": "^5.0.1", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.1", + "@rollup/plugin-terser": "^0.1.0", "@types/hash-sum": "^1.0.0", - "@types/jest": "^27.0.1", + "@types/jest": "^29.2.2", "@types/node": "^16.4.7", - "@types/puppeteer": "^5.0.0", "@typescript-eslint/parser": "^5.23.0", "@vue/consolidate": "0.17.3", "@vue/reactivity": "workspace:*", @@ -79,7 +79,8 @@ "eslint-plugin-jest": "26.1.5", "execa": "^4.0.2", "fs-extra": "^9.0.1", - "jest": "^27.1.0", + "jest": "^29.3.1", + "jest-environment-jsdom": "^29.3.1", "lint-staged": "^10.2.10", "lodash": "^4.17.15", "marked": "^4.0.10", @@ -87,19 +88,18 @@ "npm-run-all": "^4.1.5", "prettier": "^2.7.1", "pug": "^3.0.1", - "puppeteer": "^10.4.0", + "puppeteer": "^19.2.2", "rollup": "~3.2.3", "rollup-plugin-node-builtins": "^2.1.2", "rollup-plugin-node-globals": "^1.4.0", "rollup-plugin-polyfill-node": "^0.11.0", - "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.34.1", "semver": "^7.3.2", "serve": "^12.0.0", - "terser": "^5.15.1", "simple-git-hooks": "^2.8.1", + "terser": "^5.15.1", "todomvc-app-css": "^2.3.0", - "ts-jest": "^27.0.5", + "ts-jest": "^29.0.3", "tslib": "^2.4.0", "typescript": "^4.8.0", "vite": "^3.0.0", diff --git a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap index c292b032934..036b7c8f953 100644 --- a/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap @@ -48,12 +48,12 @@ exports[`compiler: codegen Element (callExpression + objectExpression + Template " return function render(_ctx, _cache) { with (_ctx) { - return _createElementVNode(\\"div\\", { - id: \\"foo\\", + return _createElementVNode("div", { + id: "foo", [prop]: bar, [foo + bar]: bar }, [ - _createElementVNode(\\"p\\", { \\"some-key\\": \\"foo\\" }) + _createElementVNode("p", { "some-key": "foo" }) ], 16) } }" @@ -63,12 +63,12 @@ exports[`compiler: codegen assets + temps 1`] = ` " return function render(_ctx, _cache) { with (_ctx) { - const _component_Foo = _resolveComponent(\\"Foo\\") - const _component_bar_baz = _resolveComponent(\\"bar-baz\\") - const _component_barbaz = _resolveComponent(\\"barbaz\\") - const _component_Qux = _resolveComponent(\\"Qux\\", true) - const _directive_my_dir_0 = _resolveDirective(\\"my_dir_0\\") - const _directive_my_dir_1 = _resolveDirective(\\"my_dir_1\\") + const _component_Foo = _resolveComponent("Foo") + const _component_bar_baz = _resolveComponent("bar-baz") + const _component_barbaz = _resolveComponent("barbaz") + const _component_Qux = _resolveComponent("Qux", true) + const _directive_my_dir_0 = _resolveDirective("my_dir_0") + const _directive_my_dir_1 = _resolveDirective("my_dir_1") let _temp0, _temp1, _temp2 return null @@ -80,7 +80,7 @@ exports[`compiler: codegen comment 1`] = ` " return function render(_ctx, _cache) { with (_ctx) { - return _createCommentVNode(\\"foo\\") + return _createCommentVNode("foo") } }" `; @@ -135,7 +135,7 @@ return function render(_ctx, _cache) { exports[`compiler: codegen hoists 1`] = ` " const _hoisted_1 = hello -const _hoisted_2 = { id: \\"foo\\" } +const _hoisted_2 = { id: "foo" } return function render(_ctx, _cache) { with (_ctx) { @@ -165,7 +165,7 @@ return function render(_ctx, _cache) { `; exports[`compiler: codegen module mode preamble 1`] = ` -"import { createVNode as _createVNode, resolveDirective as _resolveDirective } from \\"vue\\" +"import { createVNode as _createVNode, resolveDirective as _resolveDirective } from "vue" export function render(_ctx, _cache) { return null @@ -173,7 +173,7 @@ export function render(_ctx, _cache) { `; exports[`compiler: codegen module mode preamble w/ optimizeImports: true 1`] = ` -"import { createVNode, resolveDirective } from \\"vue\\" +"import { createVNode, resolveDirective } from "vue" // Binding optimization for webpack code-split const _createVNode = createVNode, _resolveDirective = resolveDirective @@ -187,7 +187,7 @@ exports[`compiler: codegen static text 1`] = ` " return function render(_ctx, _cache) { with (_ctx) { - return \\"hello\\" + return "hello" } }" `; diff --git a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap index a9189ac72a9..02c995fe97f 100644 --- a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap @@ -7,19 +7,19 @@ return function render(_ctx, _cache) { with (_ctx) { const { toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, renderList: _renderList, createElementVNode: _createElementVNode, normalizeClass: _normalizeClass } = _Vue - return (_openBlock(), _createElementBlock(\\"div\\", { - id: \\"foo\\", + return (_openBlock(), _createElementBlock("div", { + id: "foo", class: _normalizeClass(bar.baz) }, [ - _createTextVNode(_toDisplayString(world.burn()) + \\" \\", 1 /* TEXT */), + _createTextVNode(_toDisplayString(world.burn()) + " ", 1 /* TEXT */), ok - ? (_openBlock(), _createElementBlock(\\"div\\", { key: 0 }, \\"yes\\")) + ? (_openBlock(), _createElementBlock("div", { key: 0 }, "yes")) : (_openBlock(), _createElementBlock(_Fragment, { key: 1 }, [ - _createTextVNode(\\"no\\") + _createTextVNode("no") ], 64 /* STABLE_FRAGMENT */)), (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (value, index) => { - return (_openBlock(), _createElementBlock(\\"div\\", null, [ - _createElementVNode(\\"span\\", null, _toDisplayString(value + index), 1 /* TEXT */) + return (_openBlock(), _createElementBlock("div", null, [ + _createElementVNode("span", null, _toDisplayString(value + index), 1 /* TEXT */) ])) }), 256 /* UNKEYED_FRAGMENT */)) ], 2 /* CLASS */)) @@ -31,19 +31,19 @@ exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1` "const { toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, createTextVNode: _createTextVNode, Fragment: _Fragment, renderList: _renderList, createElementVNode: _createElementVNode, normalizeClass: _normalizeClass } = Vue return function render(_ctx, _cache) { - return (_openBlock(), _createElementBlock(\\"div\\", { - id: \\"foo\\", + return (_openBlock(), _createElementBlock("div", { + id: "foo", class: _normalizeClass(_ctx.bar.baz) }, [ - _createTextVNode(_toDisplayString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */), + _createTextVNode(_toDisplayString(_ctx.world.burn()) + " ", 1 /* TEXT */), (_ctx.ok) - ? (_openBlock(), _createElementBlock(\\"div\\", { key: 0 }, \\"yes\\")) + ? (_openBlock(), _createElementBlock("div", { key: 0 }, "yes")) : (_openBlock(), _createElementBlock(_Fragment, { key: 1 }, [ - _createTextVNode(\\"no\\") + _createTextVNode("no") ], 64 /* STABLE_FRAGMENT */)), (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, (value, index) => { - return (_openBlock(), _createElementBlock(\\"div\\", null, [ - _createElementVNode(\\"span\\", null, _toDisplayString(value + index), 1 /* TEXT */) + return (_openBlock(), _createElementBlock("div", null, [ + _createElementVNode("span", null, _toDisplayString(value + index), 1 /* TEXT */) ])) }), 256 /* UNKEYED_FRAGMENT */)) ], 2 /* CLASS */)) @@ -51,22 +51,22 @@ return function render(_ctx, _cache) { `; exports[`compiler: integration tests module mode 1`] = ` -"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, createTextVNode as _createTextVNode, Fragment as _Fragment, renderList as _renderList, createElementVNode as _createElementVNode, normalizeClass as _normalizeClass } from \\"vue\\" +"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, createTextVNode as _createTextVNode, Fragment as _Fragment, renderList as _renderList, createElementVNode as _createElementVNode, normalizeClass as _normalizeClass } from "vue" export function render(_ctx, _cache) { - return (_openBlock(), _createElementBlock(\\"div\\", { - id: \\"foo\\", + return (_openBlock(), _createElementBlock("div", { + id: "foo", class: _normalizeClass(_ctx.bar.baz) }, [ - _createTextVNode(_toDisplayString(_ctx.world.burn()) + \\" \\", 1 /* TEXT */), + _createTextVNode(_toDisplayString(_ctx.world.burn()) + " ", 1 /* TEXT */), (_ctx.ok) - ? (_openBlock(), _createElementBlock(\\"div\\", { key: 0 }, \\"yes\\")) + ? (_openBlock(), _createElementBlock("div", { key: 0 }, "yes")) : (_openBlock(), _createElementBlock(_Fragment, { key: 1 }, [ - _createTextVNode(\\"no\\") + _createTextVNode("no") ], 64 /* STABLE_FRAGMENT */)), (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, (value, index) => { - return (_openBlock(), _createElementBlock(\\"div\\", null, [ - _createElementVNode(\\"span\\", null, _toDisplayString(value + index), 1 /* TEXT */) + return (_openBlock(), _createElementBlock("div", null, [ + _createElementVNode("span", null, _toDisplayString(value + index), 1 /* TEXT */) ])) }), 256 /* UNKEYED_FRAGMENT */)) ], 2 /* CLASS */)) diff --git a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap index 000cedaf86f..8a12c8c399f 100644 --- a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap @@ -1,21 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`compiler: parse Errors ABRUPT_CLOSING_OF_EMPTY_COMMENT 1`] = ` -Object { +{ "cached": 0, - "children": Array [ - Object { - "children": Array [ - Object { + "children": [ + { + "children": [ + { "content": "", - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 16, "line": 1, "offset": 15, }, "source": "", - "start": Object { + "start": { "column": 11, "line": 1, "offset": 10, @@ -26,40 +26,40 @@ Object { ], "codegenNode": undefined, "isSelfClosing": false, - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 27, "line": 1, "offset": 26, }, "source": "", - "start": Object { + "start": { "column": 1, "line": 1, "offset": 0, }, }, "ns": 0, - "props": Array [], + "props": [], "tag": "template", "tagType": 0, "type": 1, }, ], "codegenNode": undefined, - "components": Array [], - "directives": Array [], + "components": [], + "directives": [], "helpers": Set {}, - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { + "hoists": [], + "imports": [], + "loc": { + "end": { "column": 27, "line": 1, "offset": 26, }, "source": "", - "start": Object { + "start": { "column": 1, "line": 1, "offset": 0, @@ -71,21 +71,21 @@ Object { `; exports[`compiler: parse Errors ABRUPT_CLOSING_OF_EMPTY_COMMENT 1`] = ` -Object { +{ "cached": 0, - "children": Array [ - Object { - "children": Array [ - Object { + "children": [ + { + "children": [ + { "content": "", - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 17, "line": 1, "offset": 16, }, "source": "", - "start": Object { + "start": { "column": 11, "line": 1, "offset": 10, @@ -96,40 +96,40 @@ Object { ], "codegenNode": undefined, "isSelfClosing": false, - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 28, "line": 1, "offset": 27, }, "source": "", - "start": Object { + "start": { "column": 1, "line": 1, "offset": 0, }, }, "ns": 0, - "props": Array [], + "props": [], "tag": "template", "tagType": 0, "type": 1, }, ], "codegenNode": undefined, - "components": Array [], - "directives": Array [], + "components": [], + "directives": [], "helpers": Set {}, - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { + "hoists": [], + "imports": [], + "loc": { + "end": { "column": 28, "line": 1, "offset": 27, }, "source": "", - "start": Object { + "start": { "column": 1, "line": 1, "offset": 0, @@ -141,21 +141,21 @@ Object { `; exports[`compiler: parse Errors ABRUPT_CLOSING_OF_EMPTY_COMMENT 1`] = ` -Object { +{ "cached": 0, - "children": Array [ - Object { - "children": Array [ - Object { + "children": [ + { + "children": [ + { "content": "", - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 18, "line": 1, "offset": 17, }, "source": "", - "start": Object { + "start": { "column": 11, "line": 1, "offset": 10, @@ -166,40 +166,40 @@ Object { ], "codegenNode": undefined, "isSelfClosing": false, - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 29, "line": 1, "offset": 28, }, "source": "", - "start": Object { + "start": { "column": 1, "line": 1, "offset": 0, }, }, "ns": 0, - "props": Array [], + "props": [], "tag": "template", "tagType": 0, "type": 1, }, ], "codegenNode": undefined, - "components": Array [], - "directives": Array [], + "components": [], + "directives": [], "helpers": Set {}, - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { + "hoists": [], + "imports": [], + "loc": { + "end": { "column": 29, "line": 1, "offset": 28, }, "source": "", - "start": Object { + "start": { "column": 1, "line": 1, "offset": 0, @@ -211,21 +211,21 @@ Object { `; exports[`compiler: parse Errors CDATA_IN_HTML_CONTENT 1`] = ` -Object { +{ "cached": 0, - "children": Array [ - Object { - "children": Array [ - Object { + "children": [ + { + "children": [ + { "content": "[CDATA[cdata]]", - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 28, "line": 1, "offset": 27, }, "source": "", - "start": Object { + "start": { "column": 11, "line": 1, "offset": 10, @@ -236,40 +236,40 @@ Object { ], "codegenNode": undefined, "isSelfClosing": false, - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 39, "line": 1, "offset": 38, }, "source": "", - "start": Object { + "start": { "column": 1, "line": 1, "offset": 0, }, }, "ns": 0, - "props": Array [], + "props": [], "tag": "template", "tagType": 0, "type": 1, }, ], "codegenNode": undefined, - "components": Array [], - "directives": Array [], + "components": [], + "directives": [], "helpers": Set {}, - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { + "hoists": [], + "imports": [], + "loc": { + "end": { "column": 39, "line": 1, "offset": 38, }, "source": "", - "start": Object { + "start": { "column": 1, "line": 1, "offset": 0, @@ -281,23 +281,23 @@ Object { `; exports[`compiler: parse Errors CDATA_IN_HTML_CONTENT 1`] = ` -Object { +{ "cached": 0, - "children": Array [ - Object { - "children": Array [ - Object { - "children": Array [ - Object { + "children": [ + { + "children": [ + { + "children": [ + { "content": "cdata", - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 30, "line": 1, "offset": 29, }, "source": "cdata", - "start": Object { + "start": { "column": 25, "line": 1, "offset": 24, @@ -308,21 +308,21 @@ Object { ], "codegenNode": undefined, "isSelfClosing": false, - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 39, "line": 1, "offset": 38, }, "source": "cdata", - "start": Object { + "start": { "column": 11, "line": 1, "offset": 10, }, }, "ns": 1, - "props": Array [], + "props": [], "tag": "svg", "tagType": 0, "type": 1, @@ -330,40 +330,40 @@ Object { ], "codegenNode": undefined, "isSelfClosing": false, - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 50, "line": 1, "offset": 49, }, "source": "", - "start": Object { + "start": { "column": 1, "line": 1, "offset": 0, }, }, "ns": 0, - "props": Array [], + "props": [], "tag": "template", "tagType": 0, "type": 1, }, ], "codegenNode": undefined, - "components": Array [], - "directives": Array [], + "components": [], + "directives": [], "helpers": Set {}, - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { + "hoists": [], + "imports": [], + "loc": { + "end": { "column": 50, "line": 1, "offset": 49, }, "source": "", - "start": Object { + "start": { "column": 1, "line": 1, "offset": 0, @@ -375,39 +375,39 @@ Object { `; exports[`compiler: parse Errors DUPLICATE_ATTRIBUTE 1`] = ` -Object { +{ "cached": 0, - "children": Array [ - Object { - "children": Array [ - Object { - "children": Array [], + "children": [ + { + "children": [ + { + "children": [], "codegenNode": undefined, "isSelfClosing": false, - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 34, "line": 1, "offset": 33, }, - "source": "
", - "start": Object { + "source": "
", + "start": { "column": 11, "line": 1, "offset": 10, }, }, "ns": 0, - "props": Array [ - Object { - "loc": Object { - "end": Object { + "props": [ + { + "loc": { + "end": { "column": 21, "line": 1, "offset": 20, }, - "source": "id=\\"\\"", - "start": Object { + "source": "id=""", + "start": { "column": 16, "line": 1, "offset": 15, @@ -415,16 +415,16 @@ Object { }, "name": "id", "type": 6, - "value": Object { + "value": { "content": "", - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 21, "line": 1, "offset": 20, }, - "source": "\\"\\"", - "start": Object { + "source": """", + "start": { "column": 19, "line": 1, "offset": 18, @@ -433,15 +433,15 @@ Object { "type": 2, }, }, - Object { - "loc": Object { - "end": Object { + { + "loc": { + "end": { "column": 27, "line": 1, "offset": 26, }, - "source": "id=\\"\\"", - "start": Object { + "source": "id=""", + "start": { "column": 22, "line": 1, "offset": 21, @@ -449,16 +449,16 @@ Object { }, "name": "id", "type": 6, - "value": Object { + "value": { "content": "", - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 27, "line": 1, "offset": 26, }, - "source": "\\"\\"", - "start": Object { + "source": """", + "start": { "column": 25, "line": 1, "offset": 24, @@ -475,40 +475,40 @@ Object { ], "codegenNode": undefined, "isSelfClosing": false, - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 45, "line": 1, "offset": 44, }, - "source": "", - "start": Object { + "source": "", + "start": { "column": 1, "line": 1, "offset": 0, }, }, "ns": 0, - "props": Array [], + "props": [], "tag": "template", "tagType": 0, "type": 1, }, ], "codegenNode": undefined, - "components": Array [], - "directives": Array [], + "components": [], + "directives": [], "helpers": Set {}, - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { + "hoists": [], + "imports": [], + "loc": { + "end": { "column": 45, "line": 1, "offset": 44, }, - "source": "", - "start": Object { + "source": "", + "start": { "column": 1, "line": 1, "offset": 0, @@ -520,30 +520,30 @@ Object { `; exports[`compiler: parse Errors END_TAG_WITH_ATTRIBUTES 1`] = ` -Object { +{ "cached": 0, - "children": Array [ - Object { - "children": Array [ - Object { - "children": Array [], + "children": [ + { + "children": [ + { + "children": [], "codegenNode": undefined, "isSelfClosing": false, - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 28, "line": 1, "offset": 27, }, - "source": "
", - "start": Object { + "source": "
", + "start": { "column": 11, "line": 1, "offset": 10, }, }, "ns": 0, - "props": Array [], + "props": [], "tag": "div", "tagType": 0, "type": 1, @@ -551,40 +551,40 @@ Object { ], "codegenNode": undefined, "isSelfClosing": false, - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 39, "line": 1, "offset": 38, }, - "source": "", - "start": Object { + "source": "", + "start": { "column": 1, "line": 1, "offset": 0, }, }, "ns": 0, - "props": Array [], + "props": [], "tag": "template", "tagType": 0, "type": 1, }, ], "codegenNode": undefined, - "components": Array [], - "directives": Array [], + "components": [], + "directives": [], "helpers": Set {}, - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { + "hoists": [], + "imports": [], + "loc": { + "end": { "column": 39, "line": 1, "offset": 38, }, - "source": "", - "start": Object { + "source": "", + "start": { "column": 1, "line": 1, "offset": 0, @@ -596,30 +596,30 @@ Object { `; exports[`compiler: parse Errors END_TAG_WITH_TRAILING_SOLIDUS 1`] = ` -Object { +{ "cached": 0, - "children": Array [ - Object { - "children": Array [ - Object { - "children": Array [], + "children": [ + { + "children": [ + { + "children": [], "codegenNode": undefined, "isSelfClosing": false, - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 23, "line": 1, "offset": 22, }, "source": "
", - "start": Object { + "start": { "column": 11, "line": 1, "offset": 10, }, }, "ns": 0, - "props": Array [], + "props": [], "tag": "div", "tagType": 0, "type": 1, @@ -627,40 +627,40 @@ Object { ], "codegenNode": undefined, "isSelfClosing": false, - "loc": Object { - "end": Object { + "loc": { + "end": { "column": 34, "line": 1, "offset": 33, }, "source": "", - "start": Object { + "start": { "column": 1, "line": 1, "offset": 0, }, }, "ns": 0, - "props": Array [], + "props": [], "tag": "template", "tagType": 0, "type": 1, }, ], "codegenNode": undefined, - "components": Array [], - "directives": Array [], + "components": [], + "directives": [], "helpers": Set {}, - "hoists": Array [], - "imports": Array [], - "loc": Object { - "end": Object { + "hoists": [], + "imports": [], + "loc": { + "end": { "column": 34, "line": 1, "offset": 33, }, "source": "", - "start": Object { + "start": { "column": 1, "line": 1, "offset": 0, @@ -672,21 +672,21 @@ Object { `; exports[`compiler: parse Errors EOF_BEFORE_TAG_NAME