import { SourceLocation } from '../ast'
import { CompilerError } from '../errors'
-// @ts-expect-error TODO
-import { ParserContext } from '../parse'
+import { MergedParserOptions } from '../parser'
import { TransformContext } from '../transform'
export type CompilerCompatConfig = Partial<
export const enum CompilerDeprecationTypes {
COMPILER_IS_ON_ELEMENT = 'COMPILER_IS_ON_ELEMENT',
COMPILER_V_BIND_SYNC = 'COMPILER_V_BIND_SYNC',
- COMPILER_V_BIND_PROP = 'COMPILER_V_BIND_PROP',
COMPILER_V_BIND_OBJECT_ORDER = 'COMPILER_V_BIND_OBJECT_ORDER',
COMPILER_V_ON_NATIVE = 'COMPILER_V_ON_NATIVE',
COMPILER_V_IF_V_FOR_PRECEDENCE = 'COMPILER_V_IF_V_FOR_PRECEDENCE',
link: `https://v3-migration.vuejs.org/breaking-changes/v-model.html`
},
- [CompilerDeprecationTypes.COMPILER_V_BIND_PROP]: {
- message:
- `.prop modifier for v-bind has been removed and no longer necessary. ` +
- `Vue 3 will automatically set a binding as DOM property when appropriate.`
- },
-
[CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER]: {
message:
`v-bind="obj" usage is now order sensitive and behaves like JavaScript ` +
function getCompatValue(
key: CompilerDeprecationTypes | 'MODE',
- context: ParserContext | TransformContext
+ { compatConfig }: MergedParserOptions | TransformContext
) {
- const config = (context as ParserContext).options
- ? (context as ParserContext).options.compatConfig
- : (context as TransformContext).compatConfig
- const value = config && config[key]
+ const value = compatConfig && compatConfig[key]
if (key === 'MODE') {
return value || 3 // compiler defaults to v3 behavior
} else {
export function isCompatEnabled(
key: CompilerDeprecationTypes,
- context: ParserContext | TransformContext
+ context: MergedParserOptions | TransformContext
) {
const mode = getCompatValue('MODE', context)
const value = getCompatValue(key, context)
export function checkCompatEnabled(
key: CompilerDeprecationTypes,
- context: ParserContext | TransformContext,
+ context: MergedParserOptions | TransformContext,
loc: SourceLocation | null,
...args: any[]
): boolean {
export function warnDeprecation(
key: CompilerDeprecationTypes,
- context: ParserContext | TransformContext,
+ context: MergedParserOptions | TransformContext,
loc: SourceLocation | null,
...args: any[]
) {
isWhitespace,
toCharCodes
} from './Tokenizer'
-import { CompilerCompatOptions } from '../compat/compatConfig'
+import {
+ CompilerCompatOptions,
+ CompilerDeprecationTypes,
+ checkCompatEnabled,
+ isCompatEnabled,
+ warnDeprecation
+} from '../compat/compatConfig'
import { NO, extend } from '@vue/shared'
import {
ErrorCodes,
defaultOnError,
defaultOnWarn
} from '../errors'
-import { forAliasRE, isCoreComponent } from '../utils'
+import { forAliasRE, isCoreComponent, isStaticArgOf } from '../utils'
import { decodeHTML } from 'entities/lib/decode.js'
type OptionalOptions =
| 'isBuiltInComponent'
| keyof CompilerCompatOptions
-type MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &
+export type MergedParserOptions = Omit<
+ Required<ParserOptions>,
+ OptionalOptions
+> &
Pick<ParserOptions, OptionalOptions>
export const defaultParserOptions: MergedParserOptions = {
// parser state
let currentInput = ''
-let currentElement: ElementNode | null = null
+let currentOpenTag: ElementNode | null = null
let currentProp: AttributeNode | DirectiveNode | null = null
let currentAttrValue = ''
let currentAttrStartIndex = -1
const startIndex = tokenizer.inSFCRoot
? end + fastForward(end, CharCodes.Gt) + 1
: start - 1
- currentElement = {
+ currentOpenTag = {
type: NodeTypes.ELEMENT,
tag: name,
ns: currentOptions.getNamespace(name, stack[0], currentOptions.ns),
},
onselfclosingtag(end) {
- const name = currentElement!.tag
+ const name = currentOpenTag!.tag
endOpenTag(end)
if (stack[0]?.tag === name) {
onCloseTag(stack.shift()!, end)
}
if (name === 'pre') {
inVPre = true
- currentVPreBoundary = currentElement
+ currentVPreBoundary = currentOpenTag
// convert dirs before this one to attributes
- const props = currentElement!.props
+ const props = currentOpenTag!.props
for (let i = 0; i < props.length; i++) {
if (props[i].type === NodeTypes.DIRECTIVE) {
props[i] = dirToAttr(props[i] as DirectiveNode)
}
// check duplicate attrs
if (
- currentElement!.props.some(
+ currentOpenTag!.props.some(
p => (p.type === NodeTypes.DIRECTIVE ? p.rawName : p.name) === name
)
) {
},
onattribend(quote, end) {
- if (currentElement && currentProp) {
+ if (currentOpenTag && currentProp) {
+ // finalize end pos
+ currentProp.loc.end = tokenizer.getPos(end)
+
if (quote !== QuoteType.NoValue) {
if (__BROWSER__ && currentAttrValue.includes('&')) {
currentAttrValue = currentOptions.decodeEntities!(
true
)
}
+
if (currentProp.type === NodeTypes.ATTRIBUTE) {
// assign value
}
if (
tokenizer.inSFCRoot &&
- currentElement.tag === 'template' &&
+ currentOpenTag.tag === 'template' &&
currentProp.name === 'lang' &&
currentAttrValue &&
currentAttrValue !== 'html'
if (currentProp.name === 'for') {
currentProp.forParseResult = parseForExpression(currentProp.exp)
}
+ // 2.x compat v-bind:foo.sync -> v-model:foo
+ let syncIndex = -1
+ if (
+ __COMPAT__ &&
+ currentProp.name === 'bind' &&
+ (syncIndex = currentProp.modifiers.indexOf('sync')) > -1 &&
+ checkCompatEnabled(
+ CompilerDeprecationTypes.COMPILER_V_BIND_SYNC,
+ currentOptions,
+ currentProp.loc,
+ currentProp.rawName
+ )
+ ) {
+ currentProp.name = 'model'
+ currentProp.modifiers.splice(syncIndex, 1)
+ }
}
}
- currentProp.loc.end = tokenizer.getPos(end)
if (
currentProp.type !== NodeTypes.DIRECTIVE ||
currentProp.name !== 'pre'
) {
- currentElement.props.push(currentProp)
+ currentOpenTag.props.push(currentProp)
}
}
currentAttrValue = ''
}
function endOpenTag(end: number) {
- addNode(currentElement!)
- const { tag, ns } = currentElement!
+ addNode(currentOpenTag!)
+ const { tag, ns } = currentOpenTag!
if (ns === Namespaces.HTML && currentOptions.isPreTag(tag)) {
inPre++
}
if (currentOptions.isVoidTag(tag)) {
- onCloseTag(currentElement!, end)
+ onCloseTag(currentOpenTag!, end)
} else {
- stack.unshift(currentElement!)
+ stack.unshift(currentOpenTag!)
if (ns === Namespaces.SVG || ns === Namespaces.MATH_ML) {
tokenizer.inXML = true
}
}
- currentElement = null
+ currentOpenTag = null
}
function onText(content: string, start: number, end: number) {
) {
tokenizer.inXML = false
}
+
+ // 2.x compat / deprecation checks
+ if (__COMPAT__) {
+ const props = el.props
+ if (
+ __DEV__ &&
+ isCompatEnabled(
+ CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE,
+ currentOptions
+ )
+ ) {
+ let hasIf = false
+ let hasFor = false
+ for (let i = 0; i < props.length; i++) {
+ const p = props[i]
+ if (p.type === NodeTypes.DIRECTIVE) {
+ if (p.name === 'if') {
+ hasIf = true
+ } else if (p.name === 'for') {
+ hasFor = true
+ }
+ }
+ if (hasIf && hasFor) {
+ warnDeprecation(
+ CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE,
+ currentOptions,
+ el.loc
+ )
+ break
+ }
+ }
+ }
+
+ if (
+ isCompatEnabled(
+ CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
+ currentOptions
+ ) &&
+ el.tag === 'template' &&
+ !isFragmentTemplate(el)
+ ) {
+ __DEV__ &&
+ warnDeprecation(
+ CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
+ currentOptions,
+ el.loc
+ )
+ // unwrap
+ const parent = stack[0] || currentRoot
+ const index = parent.children.indexOf(el)
+ parent.children.splice(index, 1, ...el.children)
+ }
+
+ const inlineTemplateProp = props.find(
+ p => p.type === NodeTypes.ATTRIBUTE && p.name === 'inline-template'
+ ) as AttributeNode
+ if (
+ inlineTemplateProp &&
+ checkCompatEnabled(
+ CompilerDeprecationTypes.COMPILER_INLINE_TEMPLATE,
+ currentOptions,
+ inlineTemplateProp.loc
+ ) &&
+ el.children.length
+ ) {
+ inlineTemplateProp.value = {
+ type: NodeTypes.TEXT,
+ content: getSlice(
+ el.children[0].loc.start.offset,
+ el.children[el.children.length - 1].loc.end.offset
+ ),
+ loc: inlineTemplateProp.loc
+ }
+ }
+ }
}
function fastForward(start: number, c: number) {
if (p.name === 'is' && p.value) {
if (p.value.content.startsWith('vue:')) {
return true
+ } else if (
+ __COMPAT__ &&
+ checkCompatEnabled(
+ CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
+ currentOptions,
+ p.loc
+ )
+ ) {
+ return true
}
- // TODO else if (
- // __COMPAT__ &&
- // checkCompatEnabled(
- // CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
- // context,
- // p.loc
- // )
- // ) {
- // return true
- // }
}
+ } else if (
+ __COMPAT__ &&
+ // :is on plain element - only treat as component in compat mode
+ p.name === 'bind' &&
+ isStaticArgOf(p.arg, 'is') &&
+ checkCompatEnabled(
+ CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
+ currentOptions,
+ p.loc
+ )
+ ) {
+ return true
}
- // TODO else if (
- // __COMPAT__ &&
- // // :is on plain element - only treat as component in compat mode
- // p.name === 'bind' &&
- // isStaticArgOf(p.arg, 'is') &&
- // checkCompatEnabled(
- // CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
- // context,
- // p.loc
- // )
- // ) {
- // return true
- // }
}
return false
}
function reset() {
tokenizer.reset()
- currentElement = null
+ currentOpenTag = null
currentProp = null
currentAttrValue = ''
currentAttrStartIndex = -1
export function baseParse(input: string, options?: ParserOptions): RootNode {
reset()
currentInput = input
- currentOptions = extend({}, defaultParserOptions, options)
+ currentOptions = extend({}, defaultParserOptions)
+
+ if (options) {
+ let key: keyof ParserOptions
+ for (key in options) {
+ if (options[key] != null) {
+ // @ts-ignore
+ currentOptions[key] = options[key]
+ }
+ }
+ }
if (__DEV__) {
if (!__BROWSER__ && currentOptions.decodeEntities) {