feat(compiler): basic transform implementation
This commit is contained in:
parent
a5c1b3283d
commit
bbb57c26a2
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -8,20 +8,23 @@ export const enum Namespaces {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const enum NodeTypes {
|
export const enum NodeTypes {
|
||||||
|
ROOT,
|
||||||
|
ELEMENT,
|
||||||
TEXT,
|
TEXT,
|
||||||
COMMENT,
|
COMMENT,
|
||||||
ELEMENT,
|
|
||||||
ATTRIBUTE,
|
|
||||||
EXPRESSION,
|
EXPRESSION,
|
||||||
|
ATTRIBUTE,
|
||||||
DIRECTIVE,
|
DIRECTIVE,
|
||||||
ROOT
|
IF,
|
||||||
|
IF_BRANCH,
|
||||||
|
FOR
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum ElementTypes {
|
export const enum ElementTypes {
|
||||||
ELEMENT,
|
ELEMENT,
|
||||||
COMPONENT,
|
COMPONENT,
|
||||||
SLOT, // slot
|
SLOT,
|
||||||
TEMPLATE // template, component
|
TEMPLATE
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Node {
|
export interface Node {
|
||||||
@ -29,9 +32,18 @@ export interface Node {
|
|||||||
loc: SourceLocation
|
loc: SourceLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ParentNode = RootNode | ElementNode | IfBranchNode | ForNode
|
||||||
|
export type ChildNode =
|
||||||
|
| ElementNode
|
||||||
|
| ExpressionNode
|
||||||
|
| TextNode
|
||||||
|
| CommentNode
|
||||||
|
| IfNode
|
||||||
|
| ForNode
|
||||||
|
|
||||||
export interface RootNode extends Node {
|
export interface RootNode extends Node {
|
||||||
type: NodeTypes.ROOT
|
type: NodeTypes.ROOT
|
||||||
children: Array<ElementNode | ExpressionNode | TextNode | CommentNode>
|
children: ChildNode[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ElementNode extends Node {
|
export interface ElementNode extends Node {
|
||||||
@ -40,8 +52,9 @@ export interface ElementNode extends Node {
|
|||||||
tag: string
|
tag: string
|
||||||
tagType: ElementTypes
|
tagType: ElementTypes
|
||||||
isSelfClosing: boolean
|
isSelfClosing: boolean
|
||||||
props: Array<AttributeNode | DirectiveNode>
|
attrs: AttributeNode[]
|
||||||
children: Array<ElementNode | ExpressionNode | TextNode | CommentNode>
|
directives: DirectiveNode[]
|
||||||
|
children: ChildNode[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TextNode extends Node {
|
export interface TextNode extends Node {
|
||||||
@ -75,8 +88,28 @@ export interface ExpressionNode extends Node {
|
|||||||
isStatic: boolean
|
isStatic: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IfNode extends Node {
|
||||||
|
type: NodeTypes.IF
|
||||||
|
branches: IfBranchNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IfBranchNode extends Node {
|
||||||
|
type: NodeTypes.IF_BRANCH
|
||||||
|
condition: ExpressionNode | undefined // else
|
||||||
|
children: ChildNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ForNode extends Node {
|
||||||
|
type: NodeTypes.FOR
|
||||||
|
source: ExpressionNode
|
||||||
|
valueAlias: ExpressionNode
|
||||||
|
keyAlias: ExpressionNode
|
||||||
|
objectIndexAlias: ExpressionNode
|
||||||
|
children: ChildNode[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface Position {
|
export interface Position {
|
||||||
offset: number // from start of file
|
offset: number // from start of file (in SFCs)
|
||||||
line: number
|
line: number
|
||||||
column: number
|
column: number
|
||||||
}
|
}
|
||||||
|
@ -1 +1,56 @@
|
|||||||
// TODO
|
import { createDirectiveTransform } from '../transform'
|
||||||
|
import {
|
||||||
|
NodeTypes,
|
||||||
|
ElementTypes,
|
||||||
|
ElementNode,
|
||||||
|
DirectiveNode,
|
||||||
|
IfBranchNode
|
||||||
|
} from '../ast'
|
||||||
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
|
|
||||||
|
export const transformIf = createDirectiveTransform(
|
||||||
|
/^(if|else|else-if)$/,
|
||||||
|
(node, dir, context) => {
|
||||||
|
if (dir.name === 'if') {
|
||||||
|
context.replaceNode({
|
||||||
|
type: NodeTypes.IF,
|
||||||
|
loc: node.loc,
|
||||||
|
branches: [createIfBranch(node, dir)]
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// locate the adjacent v-if
|
||||||
|
const siblings = context.parent.children
|
||||||
|
let i = context.childIndex
|
||||||
|
while (i--) {
|
||||||
|
const sibling = siblings[i]
|
||||||
|
if (sibling.type === NodeTypes.COMMENT) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (sibling.type === NodeTypes.IF) {
|
||||||
|
// move the node to the if node's branches
|
||||||
|
context.removeNode()
|
||||||
|
sibling.branches.push(createIfBranch(node, dir))
|
||||||
|
} else {
|
||||||
|
context.onError(
|
||||||
|
createCompilerError(
|
||||||
|
dir.name === 'else'
|
||||||
|
? ErrorCodes.X_ELSE_NO_ADJACENT_IF
|
||||||
|
: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF,
|
||||||
|
node.loc.start
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
|
||||||
|
return {
|
||||||
|
type: NodeTypes.IF_BRANCH,
|
||||||
|
loc: node.loc,
|
||||||
|
condition: dir.name === 'else' ? undefined : dir.exp,
|
||||||
|
children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
export const enum ParserErrorTypes {
|
|
||||||
ABRUPT_CLOSING_OF_EMPTY_COMMENT,
|
|
||||||
ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE,
|
|
||||||
CDATA_IN_HTML_CONTENT,
|
|
||||||
CHARACTER_REFERENCE_OUTSIDE_UNICODE_RANGE,
|
|
||||||
CONTROL_CHARACTER_REFERENCE,
|
|
||||||
DUPLICATE_ATTRIBUTE,
|
|
||||||
END_TAG_WITH_ATTRIBUTES,
|
|
||||||
END_TAG_WITH_TRAILING_SOLIDUS,
|
|
||||||
EOF_BEFORE_TAG_NAME,
|
|
||||||
EOF_IN_CDATA,
|
|
||||||
EOF_IN_COMMENT,
|
|
||||||
EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT,
|
|
||||||
EOF_IN_TAG,
|
|
||||||
INCORRECTLY_CLOSED_COMMENT,
|
|
||||||
INCORRECTLY_OPENED_COMMENT,
|
|
||||||
INVALID_FIRST_CHARACTER_OF_TAG_NAME,
|
|
||||||
MISSING_ATTRIBUTE_VALUE,
|
|
||||||
MISSING_END_TAG_NAME,
|
|
||||||
MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE,
|
|
||||||
MISSING_WHITESPACE_BETWEEN_ATTRIBUTES,
|
|
||||||
NESTED_COMMENT,
|
|
||||||
NONCHARACTER_CHARACTER_REFERENCE,
|
|
||||||
NULL_CHARACTER_REFERENCE,
|
|
||||||
SURROGATE_CHARACTER_REFERENCE,
|
|
||||||
UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
|
|
||||||
UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
|
|
||||||
UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME,
|
|
||||||
UNEXPECTED_NULL_CHARACTER,
|
|
||||||
UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
|
|
||||||
UNEXPECTED_SOLIDUS_IN_TAG,
|
|
||||||
UNKNOWN_NAMED_CHARACTER_REFERENCE,
|
|
||||||
X_INVALID_END_TAG,
|
|
||||||
X_MISSING_END_TAG,
|
|
||||||
X_MISSING_INTERPOLATION_END,
|
|
||||||
X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END
|
|
||||||
}
|
|
||||||
|
|
||||||
export const errorMessages: { [code: number]: string } = {
|
|
||||||
[ParserErrorTypes.ABRUPT_CLOSING_OF_EMPTY_COMMENT]: 'Illegal comment.',
|
|
||||||
[ParserErrorTypes.ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE]:
|
|
||||||
'Illegal numeric character reference: invalid character.',
|
|
||||||
[ParserErrorTypes.CDATA_IN_HTML_CONTENT]:
|
|
||||||
'CDATA section is allowed only in XML context.',
|
|
||||||
[ParserErrorTypes.CHARACTER_REFERENCE_OUTSIDE_UNICODE_RANGE]:
|
|
||||||
'Illegal numeric character reference: too big.',
|
|
||||||
[ParserErrorTypes.CONTROL_CHARACTER_REFERENCE]:
|
|
||||||
'Illegal numeric character reference: control character.',
|
|
||||||
[ParserErrorTypes.DUPLICATE_ATTRIBUTE]: 'Duplicate attribute.',
|
|
||||||
[ParserErrorTypes.END_TAG_WITH_ATTRIBUTES]: 'End tag cannot have attributes.',
|
|
||||||
[ParserErrorTypes.END_TAG_WITH_TRAILING_SOLIDUS]: "Illegal '/' in tags.",
|
|
||||||
[ParserErrorTypes.EOF_BEFORE_TAG_NAME]: 'Unexpected EOF in tag.',
|
|
||||||
[ParserErrorTypes.EOF_IN_CDATA]: 'Unexpected EOF in CDATA section.',
|
|
||||||
[ParserErrorTypes.EOF_IN_COMMENT]: 'Unexpected EOF in comment.',
|
|
||||||
[ParserErrorTypes.EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT]:
|
|
||||||
'Unexpected EOF in script.',
|
|
||||||
[ParserErrorTypes.EOF_IN_TAG]: 'Unexpected EOF in tag.',
|
|
||||||
[ParserErrorTypes.INCORRECTLY_CLOSED_COMMENT]: 'Incorrectly closed comment.',
|
|
||||||
[ParserErrorTypes.INCORRECTLY_OPENED_COMMENT]: 'Incorrectly opened comment.',
|
|
||||||
[ParserErrorTypes.INVALID_FIRST_CHARACTER_OF_TAG_NAME]:
|
|
||||||
"Illegal tag name. Use '<' to print '<'.",
|
|
||||||
[ParserErrorTypes.MISSING_ATTRIBUTE_VALUE]: 'Attribute value was expected.',
|
|
||||||
[ParserErrorTypes.MISSING_END_TAG_NAME]: 'End tag name was expected.',
|
|
||||||
[ParserErrorTypes.MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE]:
|
|
||||||
'Semicolon was expected.',
|
|
||||||
[ParserErrorTypes.MISSING_WHITESPACE_BETWEEN_ATTRIBUTES]:
|
|
||||||
'Whitespace was expected.',
|
|
||||||
[ParserErrorTypes.NESTED_COMMENT]: "Unexpected '<!--' in comment.",
|
|
||||||
[ParserErrorTypes.NONCHARACTER_CHARACTER_REFERENCE]:
|
|
||||||
'Illegal numeric character reference: non character.',
|
|
||||||
[ParserErrorTypes.NULL_CHARACTER_REFERENCE]:
|
|
||||||
'Illegal numeric character reference: null character.',
|
|
||||||
[ParserErrorTypes.SURROGATE_CHARACTER_REFERENCE]:
|
|
||||||
'Illegal numeric character reference: non-pair surrogate.',
|
|
||||||
[ParserErrorTypes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME]:
|
|
||||||
'Attribute name cannot contain U+0022 ("), U+0027 (\'), and U+003C (<).',
|
|
||||||
[ParserErrorTypes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE]:
|
|
||||||
'Unquoted attribute value cannot contain U+0022 ("), U+0027 (\'), U+003C (<), U+003D (=), and U+0060 (`).',
|
|
||||||
[ParserErrorTypes.UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME]:
|
|
||||||
"Attribute name cannot start with '='.",
|
|
||||||
[ParserErrorTypes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME]:
|
|
||||||
"'<?' is allowed only in XML context.",
|
|
||||||
[ParserErrorTypes.UNEXPECTED_SOLIDUS_IN_TAG]: "Illegal '/' in tags.",
|
|
||||||
[ParserErrorTypes.UNKNOWN_NAMED_CHARACTER_REFERENCE]: 'Unknown entity name.',
|
|
||||||
[ParserErrorTypes.X_INVALID_END_TAG]: 'Invalid end tag.',
|
|
||||||
[ParserErrorTypes.X_MISSING_END_TAG]: 'End tag was not found.',
|
|
||||||
[ParserErrorTypes.X_MISSING_INTERPOLATION_END]:
|
|
||||||
'Interpolation end sign was not found.'
|
|
||||||
}
|
|
120
packages/compiler-core/src/errors.ts
Normal file
120
packages/compiler-core/src/errors.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { Position } from './ast'
|
||||||
|
|
||||||
|
export interface CompilerError extends SyntaxError {
|
||||||
|
code: ErrorCodes
|
||||||
|
loc: Position
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCompilerError(
|
||||||
|
code: ErrorCodes,
|
||||||
|
loc: Position
|
||||||
|
): CompilerError {
|
||||||
|
const error = new SyntaxError(
|
||||||
|
`${__DEV__ || !__BROWSER__ ? errorMessages[code] : code} (${loc.line}:${
|
||||||
|
loc.column
|
||||||
|
})`
|
||||||
|
) as CompilerError
|
||||||
|
error.code = code
|
||||||
|
error.loc = loc
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum ErrorCodes {
|
||||||
|
// parse errors
|
||||||
|
ABRUPT_CLOSING_OF_EMPTY_COMMENT,
|
||||||
|
ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE,
|
||||||
|
CDATA_IN_HTML_CONTENT,
|
||||||
|
CHARACTER_REFERENCE_OUTSIDE_UNICODE_RANGE,
|
||||||
|
CONTROL_CHARACTER_REFERENCE,
|
||||||
|
DUPLICATE_ATTRIBUTE,
|
||||||
|
END_TAG_WITH_ATTRIBUTES,
|
||||||
|
END_TAG_WITH_TRAILING_SOLIDUS,
|
||||||
|
EOF_BEFORE_TAG_NAME,
|
||||||
|
EOF_IN_CDATA,
|
||||||
|
EOF_IN_COMMENT,
|
||||||
|
EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT,
|
||||||
|
EOF_IN_TAG,
|
||||||
|
INCORRECTLY_CLOSED_COMMENT,
|
||||||
|
INCORRECTLY_OPENED_COMMENT,
|
||||||
|
INVALID_FIRST_CHARACTER_OF_TAG_NAME,
|
||||||
|
MISSING_ATTRIBUTE_VALUE,
|
||||||
|
MISSING_END_TAG_NAME,
|
||||||
|
MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE,
|
||||||
|
MISSING_WHITESPACE_BETWEEN_ATTRIBUTES,
|
||||||
|
NESTED_COMMENT,
|
||||||
|
NONCHARACTER_CHARACTER_REFERENCE,
|
||||||
|
NULL_CHARACTER_REFERENCE,
|
||||||
|
SURROGATE_CHARACTER_REFERENCE,
|
||||||
|
UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
|
||||||
|
UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
|
||||||
|
UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME,
|
||||||
|
UNEXPECTED_NULL_CHARACTER,
|
||||||
|
UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
|
||||||
|
UNEXPECTED_SOLIDUS_IN_TAG,
|
||||||
|
UNKNOWN_NAMED_CHARACTER_REFERENCE,
|
||||||
|
X_INVALID_END_TAG,
|
||||||
|
X_MISSING_END_TAG,
|
||||||
|
X_MISSING_INTERPOLATION_END,
|
||||||
|
X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END,
|
||||||
|
|
||||||
|
// transform errors
|
||||||
|
X_ELSE_IF_NO_ADJACENT_IF,
|
||||||
|
X_ELSE_NO_ADJACENT_IF
|
||||||
|
}
|
||||||
|
|
||||||
|
export const errorMessages: { [code: number]: string } = {
|
||||||
|
// parse errors
|
||||||
|
[ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT]: 'Illegal comment.',
|
||||||
|
[ErrorCodes.ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE]:
|
||||||
|
'Illegal numeric character reference: invalid character.',
|
||||||
|
[ErrorCodes.CDATA_IN_HTML_CONTENT]:
|
||||||
|
'CDATA section is allowed only in XML context.',
|
||||||
|
[ErrorCodes.CHARACTER_REFERENCE_OUTSIDE_UNICODE_RANGE]:
|
||||||
|
'Illegal numeric character reference: too big.',
|
||||||
|
[ErrorCodes.CONTROL_CHARACTER_REFERENCE]:
|
||||||
|
'Illegal numeric character reference: control character.',
|
||||||
|
[ErrorCodes.DUPLICATE_ATTRIBUTE]: 'Duplicate attribute.',
|
||||||
|
[ErrorCodes.END_TAG_WITH_ATTRIBUTES]: 'End tag cannot have attributes.',
|
||||||
|
[ErrorCodes.END_TAG_WITH_TRAILING_SOLIDUS]: "Illegal '/' in tags.",
|
||||||
|
[ErrorCodes.EOF_BEFORE_TAG_NAME]: 'Unexpected EOF in tag.',
|
||||||
|
[ErrorCodes.EOF_IN_CDATA]: 'Unexpected EOF in CDATA section.',
|
||||||
|
[ErrorCodes.EOF_IN_COMMENT]: 'Unexpected EOF in comment.',
|
||||||
|
[ErrorCodes.EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT]:
|
||||||
|
'Unexpected EOF in script.',
|
||||||
|
[ErrorCodes.EOF_IN_TAG]: 'Unexpected EOF in tag.',
|
||||||
|
[ErrorCodes.INCORRECTLY_CLOSED_COMMENT]: 'Incorrectly closed comment.',
|
||||||
|
[ErrorCodes.INCORRECTLY_OPENED_COMMENT]: 'Incorrectly opened comment.',
|
||||||
|
[ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME]:
|
||||||
|
"Illegal tag name. Use '<' to print '<'.",
|
||||||
|
[ErrorCodes.MISSING_ATTRIBUTE_VALUE]: 'Attribute value was expected.',
|
||||||
|
[ErrorCodes.MISSING_END_TAG_NAME]: 'End tag name was expected.',
|
||||||
|
[ErrorCodes.MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE]:
|
||||||
|
'Semicolon was expected.',
|
||||||
|
[ErrorCodes.MISSING_WHITESPACE_BETWEEN_ATTRIBUTES]:
|
||||||
|
'Whitespace was expected.',
|
||||||
|
[ErrorCodes.NESTED_COMMENT]: "Unexpected '<!--' in comment.",
|
||||||
|
[ErrorCodes.NONCHARACTER_CHARACTER_REFERENCE]:
|
||||||
|
'Illegal numeric character reference: non character.',
|
||||||
|
[ErrorCodes.NULL_CHARACTER_REFERENCE]:
|
||||||
|
'Illegal numeric character reference: null character.',
|
||||||
|
[ErrorCodes.SURROGATE_CHARACTER_REFERENCE]:
|
||||||
|
'Illegal numeric character reference: non-pair surrogate.',
|
||||||
|
[ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME]:
|
||||||
|
'Attribute name cannot contain U+0022 ("), U+0027 (\'), and U+003C (<).',
|
||||||
|
[ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE]:
|
||||||
|
'Unquoted attribute value cannot contain U+0022 ("), U+0027 (\'), U+003C (<), U+003D (=), and U+0060 (`).',
|
||||||
|
[ErrorCodes.UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME]:
|
||||||
|
"Attribute name cannot start with '='.",
|
||||||
|
[ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME]:
|
||||||
|
"'<?' is allowed only in XML context.",
|
||||||
|
[ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG]: "Illegal '/' in tags.",
|
||||||
|
[ErrorCodes.UNKNOWN_NAMED_CHARACTER_REFERENCE]: 'Unknown entity name.',
|
||||||
|
[ErrorCodes.X_INVALID_END_TAG]: 'Invalid end tag.',
|
||||||
|
[ErrorCodes.X_MISSING_END_TAG]: 'End tag was not found.',
|
||||||
|
[ErrorCodes.X_MISSING_INTERPOLATION_END]:
|
||||||
|
'Interpolation end sign was not found.',
|
||||||
|
|
||||||
|
// transform errors
|
||||||
|
[ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF]: `v-else-if has no adjacent v-if`,
|
||||||
|
[ErrorCodes.X_ELSE_NO_ADJACENT_IF]: `v-else has no adjacent v-if`
|
||||||
|
}
|
@ -1,3 +1,6 @@
|
|||||||
export { parse, ParserOptions, TextModes } from './parser'
|
export { parse, ParserOptions, TextModes } from './parse'
|
||||||
export { ParserErrorTypes } from './errorTypes'
|
export { transform, Transform, TransformContext } from './transform'
|
||||||
|
export { ErrorCodes } from './errors'
|
||||||
export * from './ast'
|
export * from './ast'
|
||||||
|
|
||||||
|
export { transformIf } from './directives/vIf'
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ParserErrorTypes, errorMessages } from './errorTypes'
|
import { ErrorCodes, CompilerError, createCompilerError } from './errors'
|
||||||
import {
|
import {
|
||||||
Namespace,
|
Namespace,
|
||||||
Namespaces,
|
Namespaces,
|
||||||
@ -12,7 +12,8 @@ import {
|
|||||||
Position,
|
Position,
|
||||||
RootNode,
|
RootNode,
|
||||||
SourceLocation,
|
SourceLocation,
|
||||||
TextNode
|
TextNode,
|
||||||
|
ChildNode
|
||||||
} from './ast'
|
} from './ast'
|
||||||
|
|
||||||
export interface ParserOptions {
|
export interface ParserOptions {
|
||||||
@ -26,7 +27,7 @@ export interface ParserOptions {
|
|||||||
// The full set is https://html.spec.whatwg.org/multipage/named-characters.html#named-character-references
|
// The full set is https://html.spec.whatwg.org/multipage/named-characters.html#named-character-references
|
||||||
namedCharacterReferences?: { [name: string]: string | undefined }
|
namedCharacterReferences?: { [name: string]: string | undefined }
|
||||||
|
|
||||||
onError?: (type: ParserErrorTypes, loc: Position) => void
|
onError?: (error: CompilerError) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultParserOptions: Required<ParserOptions> = {
|
export const defaultParserOptions: Required<ParserOptions> = {
|
||||||
@ -42,14 +43,7 @@ export const defaultParserOptions: Required<ParserOptions> = {
|
|||||||
'apos;': "'",
|
'apos;': "'",
|
||||||
'quot;': '"'
|
'quot;': '"'
|
||||||
},
|
},
|
||||||
onError(code: ParserErrorTypes, loc: Position): void {
|
onError(error: CompilerError): void {
|
||||||
const error: any = new SyntaxError(
|
|
||||||
`${__DEV__ || !__BROWSER__ ? errorMessages[code] : code} (${loc.line}:${
|
|
||||||
loc.column
|
|
||||||
})`
|
|
||||||
)
|
|
||||||
error.code = code
|
|
||||||
error.loc = loc
|
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,10 +100,10 @@ function parseChildren(
|
|||||||
context: ParserContext,
|
context: ParserContext,
|
||||||
mode: TextModes,
|
mode: TextModes,
|
||||||
ancestors: ElementNode[]
|
ancestors: ElementNode[]
|
||||||
): RootNode['children'] {
|
): ChildNode[] {
|
||||||
const parent = last(ancestors)
|
const parent = last(ancestors)
|
||||||
const ns = parent ? parent.ns : Namespaces.HTML
|
const ns = parent ? parent.ns : Namespaces.HTML
|
||||||
const nodes: RootNode['children'] = []
|
const nodes: ChildNode[] = []
|
||||||
|
|
||||||
while (!isEnd(context, mode, ancestors)) {
|
while (!isEnd(context, mode, ancestors)) {
|
||||||
__DEV__ && assert(context.source.length > 0)
|
__DEV__ && assert(context.source.length > 0)
|
||||||
@ -122,7 +116,7 @@ function parseChildren(
|
|||||||
} else if (mode === TextModes.DATA && s[0] === '<') {
|
} else if (mode === TextModes.DATA && s[0] === '<') {
|
||||||
// https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
|
// https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
|
||||||
if (s.length === 1) {
|
if (s.length === 1) {
|
||||||
emitError(context, ParserErrorTypes.EOF_BEFORE_TAG_NAME, 1)
|
emitError(context, ErrorCodes.EOF_BEFORE_TAG_NAME, 1)
|
||||||
} else if (s[1] === '!') {
|
} else if (s[1] === '!') {
|
||||||
// https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state
|
// https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state
|
||||||
if (startsWith(s, '<!--')) {
|
if (startsWith(s, '<!--')) {
|
||||||
@ -134,31 +128,27 @@ function parseChildren(
|
|||||||
if (ns !== Namespaces.HTML) {
|
if (ns !== Namespaces.HTML) {
|
||||||
node = parseCDATA(context, ancestors)
|
node = parseCDATA(context, ancestors)
|
||||||
} else {
|
} else {
|
||||||
emitError(context, ParserErrorTypes.CDATA_IN_HTML_CONTENT)
|
emitError(context, ErrorCodes.CDATA_IN_HTML_CONTENT)
|
||||||
node = parseBogusComment(context)
|
node = parseBogusComment(context)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
emitError(context, ParserErrorTypes.INCORRECTLY_OPENED_COMMENT)
|
emitError(context, ErrorCodes.INCORRECTLY_OPENED_COMMENT)
|
||||||
node = parseBogusComment(context)
|
node = parseBogusComment(context)
|
||||||
}
|
}
|
||||||
} else if (s[1] === '/') {
|
} else if (s[1] === '/') {
|
||||||
// https://html.spec.whatwg.org/multipage/parsing.html#end-tag-open-state
|
// https://html.spec.whatwg.org/multipage/parsing.html#end-tag-open-state
|
||||||
if (s.length === 2) {
|
if (s.length === 2) {
|
||||||
emitError(context, ParserErrorTypes.EOF_BEFORE_TAG_NAME, 2)
|
emitError(context, ErrorCodes.EOF_BEFORE_TAG_NAME, 2)
|
||||||
} else if (s[2] === '>') {
|
} else if (s[2] === '>') {
|
||||||
emitError(context, ParserErrorTypes.MISSING_END_TAG_NAME, 2)
|
emitError(context, ErrorCodes.MISSING_END_TAG_NAME, 2)
|
||||||
advanceBy(context, 3)
|
advanceBy(context, 3)
|
||||||
continue
|
continue
|
||||||
} else if (/[a-z]/i.test(s[2])) {
|
} else if (/[a-z]/i.test(s[2])) {
|
||||||
emitError(context, ParserErrorTypes.X_INVALID_END_TAG)
|
emitError(context, ErrorCodes.X_INVALID_END_TAG)
|
||||||
parseTag(context, TagType.End, parent)
|
parseTag(context, TagType.End, parent)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
emitError(
|
emitError(context, ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME, 2)
|
||||||
context,
|
|
||||||
ParserErrorTypes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
|
|
||||||
2
|
|
||||||
)
|
|
||||||
node = parseBogusComment(context)
|
node = parseBogusComment(context)
|
||||||
}
|
}
|
||||||
} else if (/[a-z]/i.test(s[1])) {
|
} else if (/[a-z]/i.test(s[1])) {
|
||||||
@ -166,16 +156,12 @@ function parseChildren(
|
|||||||
} else if (s[1] === '?') {
|
} else if (s[1] === '?') {
|
||||||
emitError(
|
emitError(
|
||||||
context,
|
context,
|
||||||
ParserErrorTypes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
|
ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
|
||||||
1
|
1
|
||||||
)
|
)
|
||||||
node = parseBogusComment(context)
|
node = parseBogusComment(context)
|
||||||
} else {
|
} else {
|
||||||
emitError(
|
emitError(context, ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME, 1)
|
||||||
context,
|
|
||||||
ParserErrorTypes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
|
|
||||||
1
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!node) {
|
if (!node) {
|
||||||
@ -183,7 +169,9 @@ function parseChildren(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(node)) {
|
if (Array.isArray(node)) {
|
||||||
node.forEach(pushNode.bind(null, context, nodes))
|
for (let i = 0; i < node.length; i++) {
|
||||||
|
pushNode(context, nodes, node[i])
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
pushNode(context, nodes, node)
|
pushNode(context, nodes, node)
|
||||||
}
|
}
|
||||||
@ -194,8 +182,8 @@ function parseChildren(
|
|||||||
|
|
||||||
function pushNode(
|
function pushNode(
|
||||||
context: ParserContext,
|
context: ParserContext,
|
||||||
nodes: RootNode['children'],
|
nodes: ChildNode[],
|
||||||
node: RootNode['children'][0]
|
node: ChildNode
|
||||||
): void {
|
): void {
|
||||||
if (context.ignoreSpaces && node.type === NodeTypes.TEXT && node.isEmpty) {
|
if (context.ignoreSpaces && node.type === NodeTypes.TEXT && node.isEmpty) {
|
||||||
return
|
return
|
||||||
@ -222,7 +210,7 @@ function pushNode(
|
|||||||
function parseCDATA(
|
function parseCDATA(
|
||||||
context: ParserContext,
|
context: ParserContext,
|
||||||
ancestors: ElementNode[]
|
ancestors: ElementNode[]
|
||||||
): RootNode['children'] {
|
): ChildNode[] {
|
||||||
__DEV__ &&
|
__DEV__ &&
|
||||||
assert(last(ancestors) == null || last(ancestors)!.ns !== Namespaces.HTML)
|
assert(last(ancestors) == null || last(ancestors)!.ns !== Namespaces.HTML)
|
||||||
__DEV__ && assert(startsWith(context.source, '<![CDATA['))
|
__DEV__ && assert(startsWith(context.source, '<![CDATA['))
|
||||||
@ -230,7 +218,7 @@ function parseCDATA(
|
|||||||
advanceBy(context, 9)
|
advanceBy(context, 9)
|
||||||
const nodes = parseChildren(context, TextModes.CDATA, ancestors)
|
const nodes = parseChildren(context, TextModes.CDATA, ancestors)
|
||||||
if (context.source.length === 0) {
|
if (context.source.length === 0) {
|
||||||
emitError(context, ParserErrorTypes.EOF_IN_CDATA)
|
emitError(context, ErrorCodes.EOF_IN_CDATA)
|
||||||
} else {
|
} else {
|
||||||
__DEV__ && assert(startsWith(context.source, ']]>'))
|
__DEV__ && assert(startsWith(context.source, ']]>'))
|
||||||
advanceBy(context, 3)
|
advanceBy(context, 3)
|
||||||
@ -250,13 +238,13 @@ function parseComment(context: ParserContext): CommentNode {
|
|||||||
if (!match) {
|
if (!match) {
|
||||||
content = context.source.slice(4)
|
content = context.source.slice(4)
|
||||||
advanceBy(context, context.source.length)
|
advanceBy(context, context.source.length)
|
||||||
emitError(context, ParserErrorTypes.EOF_IN_COMMENT)
|
emitError(context, ErrorCodes.EOF_IN_COMMENT)
|
||||||
} else {
|
} else {
|
||||||
if (match.index <= 3) {
|
if (match.index <= 3) {
|
||||||
emitError(context, ParserErrorTypes.ABRUPT_CLOSING_OF_EMPTY_COMMENT)
|
emitError(context, ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT)
|
||||||
}
|
}
|
||||||
if (match[1]) {
|
if (match[1]) {
|
||||||
emitError(context, ParserErrorTypes.INCORRECTLY_CLOSED_COMMENT)
|
emitError(context, ErrorCodes.INCORRECTLY_CLOSED_COMMENT)
|
||||||
}
|
}
|
||||||
content = context.source.slice(4, match.index)
|
content = context.source.slice(4, match.index)
|
||||||
|
|
||||||
@ -267,7 +255,7 @@ function parseComment(context: ParserContext): CommentNode {
|
|||||||
while ((nestedIndex = s.indexOf('<!--', prevIndex)) !== -1) {
|
while ((nestedIndex = s.indexOf('<!--', prevIndex)) !== -1) {
|
||||||
advanceBy(context, nestedIndex - prevIndex + 1)
|
advanceBy(context, nestedIndex - prevIndex + 1)
|
||||||
if (nestedIndex + 4 < s.length) {
|
if (nestedIndex + 4 < s.length) {
|
||||||
emitError(context, ParserErrorTypes.NESTED_COMMENT)
|
emitError(context, ErrorCodes.NESTED_COMMENT)
|
||||||
}
|
}
|
||||||
prevIndex = nestedIndex + 1
|
prevIndex = nestedIndex + 1
|
||||||
}
|
}
|
||||||
@ -333,14 +321,11 @@ function parseElement(
|
|||||||
if (startsWithEndTagOpen(context.source, element.tag)) {
|
if (startsWithEndTagOpen(context.source, element.tag)) {
|
||||||
parseTag(context, TagType.End, parent)
|
parseTag(context, TagType.End, parent)
|
||||||
} else {
|
} else {
|
||||||
emitError(context, ParserErrorTypes.X_MISSING_END_TAG)
|
emitError(context, ErrorCodes.X_MISSING_END_TAG)
|
||||||
if (context.source.length === 0 && element.tag.toLowerCase() === 'script') {
|
if (context.source.length === 0 && element.tag.toLowerCase() === 'script') {
|
||||||
const first = children[0]
|
const first = children[0]
|
||||||
if (first && startsWith(first.loc.source, '<!--')) {
|
if (first && startsWith(first.loc.source, '<!--')) {
|
||||||
emitError(
|
emitError(context, ErrorCodes.EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT)
|
||||||
context,
|
|
||||||
ParserErrorTypes.EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -372,7 +357,8 @@ function parseTag(
|
|||||||
const start = getCursor(context)
|
const start = getCursor(context)
|
||||||
const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!
|
const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!
|
||||||
const tag = match[1]
|
const tag = match[1]
|
||||||
const props = []
|
const attrs = []
|
||||||
|
const directives = []
|
||||||
const ns = context.getNamespace(tag, parent)
|
const ns = context.getNamespace(tag, parent)
|
||||||
|
|
||||||
advanceBy(context, match[0].length)
|
advanceBy(context, match[0].length)
|
||||||
@ -386,22 +372,26 @@ function parseTag(
|
|||||||
!startsWith(context.source, '/>')
|
!startsWith(context.source, '/>')
|
||||||
) {
|
) {
|
||||||
if (startsWith(context.source, '/')) {
|
if (startsWith(context.source, '/')) {
|
||||||
emitError(context, ParserErrorTypes.UNEXPECTED_SOLIDUS_IN_TAG)
|
emitError(context, ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG)
|
||||||
advanceBy(context, 1)
|
advanceBy(context, 1)
|
||||||
advanceSpaces(context)
|
advanceSpaces(context)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (type === TagType.End) {
|
if (type === TagType.End) {
|
||||||
emitError(context, ParserErrorTypes.END_TAG_WITH_ATTRIBUTES)
|
emitError(context, ErrorCodes.END_TAG_WITH_ATTRIBUTES)
|
||||||
}
|
}
|
||||||
|
|
||||||
const attr = parseAttribute(context, attributeNames)
|
const attr = parseAttribute(context, attributeNames)
|
||||||
if (type === TagType.Start) {
|
if (type === TagType.Start) {
|
||||||
props.push(attr)
|
if (attr.type === NodeTypes.DIRECTIVE) {
|
||||||
|
directives.push(attr)
|
||||||
|
} else {
|
||||||
|
attrs.push(attr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/^[^\t\r\n\f />]/.test(context.source)) {
|
if (/^[^\t\r\n\f />]/.test(context.source)) {
|
||||||
emitError(context, ParserErrorTypes.MISSING_WHITESPACE_BETWEEN_ATTRIBUTES)
|
emitError(context, ErrorCodes.MISSING_WHITESPACE_BETWEEN_ATTRIBUTES)
|
||||||
}
|
}
|
||||||
advanceSpaces(context)
|
advanceSpaces(context)
|
||||||
}
|
}
|
||||||
@ -409,11 +399,11 @@ function parseTag(
|
|||||||
// Tag close.
|
// Tag close.
|
||||||
let isSelfClosing = false
|
let isSelfClosing = false
|
||||||
if (context.source.length === 0) {
|
if (context.source.length === 0) {
|
||||||
emitError(context, ParserErrorTypes.EOF_IN_TAG)
|
emitError(context, ErrorCodes.EOF_IN_TAG)
|
||||||
} else {
|
} else {
|
||||||
isSelfClosing = startsWith(context.source, '/>')
|
isSelfClosing = startsWith(context.source, '/>')
|
||||||
if (type === TagType.End && isSelfClosing) {
|
if (type === TagType.End && isSelfClosing) {
|
||||||
emitError(context, ParserErrorTypes.END_TAG_WITH_TRAILING_SOLIDUS)
|
emitError(context, ErrorCodes.END_TAG_WITH_TRAILING_SOLIDUS)
|
||||||
}
|
}
|
||||||
advanceBy(context, isSelfClosing ? 2 : 1)
|
advanceBy(context, isSelfClosing ? 2 : 1)
|
||||||
}
|
}
|
||||||
@ -429,7 +419,8 @@ function parseTag(
|
|||||||
ns,
|
ns,
|
||||||
tag,
|
tag,
|
||||||
tagType,
|
tagType,
|
||||||
props,
|
attrs,
|
||||||
|
directives,
|
||||||
isSelfClosing,
|
isSelfClosing,
|
||||||
children: [],
|
children: [],
|
||||||
loc: getSelection(context, start)
|
loc: getSelection(context, start)
|
||||||
@ -448,15 +439,12 @@ function parseAttribute(
|
|||||||
const name = match[0]
|
const name = match[0]
|
||||||
|
|
||||||
if (nameSet.has(name)) {
|
if (nameSet.has(name)) {
|
||||||
emitError(context, ParserErrorTypes.DUPLICATE_ATTRIBUTE)
|
emitError(context, ErrorCodes.DUPLICATE_ATTRIBUTE)
|
||||||
}
|
}
|
||||||
nameSet.add(name)
|
nameSet.add(name)
|
||||||
|
|
||||||
if (name[0] === '=') {
|
if (name[0] === '=') {
|
||||||
emitError(
|
emitError(context, ErrorCodes.UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME)
|
||||||
context,
|
|
||||||
ParserErrorTypes.UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const pattern = /["'<]/g
|
const pattern = /["'<]/g
|
||||||
@ -464,7 +452,7 @@ function parseAttribute(
|
|||||||
while ((m = pattern.exec(name)) !== null) {
|
while ((m = pattern.exec(name)) !== null) {
|
||||||
emitError(
|
emitError(
|
||||||
context,
|
context,
|
||||||
ParserErrorTypes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
|
ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
|
||||||
m.index
|
m.index
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -480,7 +468,7 @@ function parseAttribute(
|
|||||||
advanceSpaces(context)
|
advanceSpaces(context)
|
||||||
value = parseAttributeValue(context)
|
value = parseAttributeValue(context)
|
||||||
if (!value) {
|
if (!value) {
|
||||||
emitError(context, ParserErrorTypes.MISSING_ATTRIBUTE_VALUE)
|
emitError(context, ErrorCodes.MISSING_ATTRIBUTE_VALUE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const loc = getSelection(context, start)
|
const loc = getSelection(context, start)
|
||||||
@ -508,7 +496,7 @@ function parseAttribute(
|
|||||||
if (!content.endsWith(']')) {
|
if (!content.endsWith(']')) {
|
||||||
emitError(
|
emitError(
|
||||||
context,
|
context,
|
||||||
ParserErrorTypes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END
|
ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -590,7 +578,7 @@ function parseAttributeValue(
|
|||||||
while ((m = unexpectedChars.exec(match[0])) !== null) {
|
while ((m = unexpectedChars.exec(match[0])) !== null) {
|
||||||
emitError(
|
emitError(
|
||||||
context,
|
context,
|
||||||
ParserErrorTypes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
|
ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
|
||||||
m.index
|
m.index
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -609,7 +597,7 @@ function parseInterpolation(
|
|||||||
|
|
||||||
const closeIndex = context.source.indexOf(close, open.length)
|
const closeIndex = context.source.indexOf(close, open.length)
|
||||||
if (closeIndex === -1) {
|
if (closeIndex === -1) {
|
||||||
emitError(context, ParserErrorTypes.X_MISSING_INTERPOLATION_END)
|
emitError(context, ErrorCodes.X_MISSING_INTERPOLATION_END)
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -712,12 +700,12 @@ function parseTextData(
|
|||||||
if (!semi) {
|
if (!semi) {
|
||||||
emitError(
|
emitError(
|
||||||
context,
|
context,
|
||||||
ParserErrorTypes.MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE
|
ErrorCodes.MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
emitError(context, ParserErrorTypes.UNKNOWN_NAMED_CHARACTER_REFERENCE)
|
emitError(context, ErrorCodes.UNKNOWN_NAMED_CHARACTER_REFERENCE)
|
||||||
text += '&'
|
text += '&'
|
||||||
text += name
|
text += name
|
||||||
advanceBy(context, 1 + name.length)
|
advanceBy(context, 1 + name.length)
|
||||||
@ -735,33 +723,33 @@ function parseTextData(
|
|||||||
text += head[0]
|
text += head[0]
|
||||||
emitError(
|
emitError(
|
||||||
context,
|
context,
|
||||||
ParserErrorTypes.ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE
|
ErrorCodes.ABSENCE_OF_DIGITS_IN_NUMERIC_CHARACTER_REFERENCE
|
||||||
)
|
)
|
||||||
advanceBy(context, head[0].length)
|
advanceBy(context, head[0].length)
|
||||||
} else {
|
} else {
|
||||||
// https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state
|
// https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state
|
||||||
let cp = Number.parseInt(body[1], hex ? 16 : 10)
|
let cp = Number.parseInt(body[1], hex ? 16 : 10)
|
||||||
if (cp === 0) {
|
if (cp === 0) {
|
||||||
emitError(context, ParserErrorTypes.NULL_CHARACTER_REFERENCE)
|
emitError(context, ErrorCodes.NULL_CHARACTER_REFERENCE)
|
||||||
cp = 0xfffd
|
cp = 0xfffd
|
||||||
} else if (cp > 0x10ffff) {
|
} else if (cp > 0x10ffff) {
|
||||||
emitError(
|
emitError(
|
||||||
context,
|
context,
|
||||||
ParserErrorTypes.CHARACTER_REFERENCE_OUTSIDE_UNICODE_RANGE
|
ErrorCodes.CHARACTER_REFERENCE_OUTSIDE_UNICODE_RANGE
|
||||||
)
|
)
|
||||||
cp = 0xfffd
|
cp = 0xfffd
|
||||||
} else if (cp >= 0xd800 && cp <= 0xdfff) {
|
} else if (cp >= 0xd800 && cp <= 0xdfff) {
|
||||||
emitError(context, ParserErrorTypes.SURROGATE_CHARACTER_REFERENCE)
|
emitError(context, ErrorCodes.SURROGATE_CHARACTER_REFERENCE)
|
||||||
cp = 0xfffd
|
cp = 0xfffd
|
||||||
} else if ((cp >= 0xfdd0 && cp <= 0xfdef) || (cp & 0xfffe) === 0xfffe) {
|
} else if ((cp >= 0xfdd0 && cp <= 0xfdef) || (cp & 0xfffe) === 0xfffe) {
|
||||||
emitError(context, ParserErrorTypes.NONCHARACTER_CHARACTER_REFERENCE)
|
emitError(context, ErrorCodes.NONCHARACTER_CHARACTER_REFERENCE)
|
||||||
} else if (
|
} else if (
|
||||||
(cp >= 0x01 && cp <= 0x08) ||
|
(cp >= 0x01 && cp <= 0x08) ||
|
||||||
cp === 0x0b ||
|
cp === 0x0b ||
|
||||||
(cp >= 0x0d && cp <= 0x1f) ||
|
(cp >= 0x0d && cp <= 0x1f) ||
|
||||||
(cp >= 0x7f && cp <= 0x9f)
|
(cp >= 0x7f && cp <= 0x9f)
|
||||||
) {
|
) {
|
||||||
emitError(context, ParserErrorTypes.CONTROL_CHARACTER_REFERENCE)
|
emitError(context, ErrorCodes.CONTROL_CHARACTER_REFERENCE)
|
||||||
cp = CCR_REPLACEMENTS[cp] || cp
|
cp = CCR_REPLACEMENTS[cp] || cp
|
||||||
}
|
}
|
||||||
text += String.fromCodePoint(cp)
|
text += String.fromCodePoint(cp)
|
||||||
@ -769,7 +757,7 @@ function parseTextData(
|
|||||||
if (!body![0].endsWith(';')) {
|
if (!body![0].endsWith(';')) {
|
||||||
emitError(
|
emitError(
|
||||||
context,
|
context,
|
||||||
ParserErrorTypes.MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE
|
ErrorCodes.MISSING_SEMICOLON_AFTER_CHARACTER_REFERENCE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -854,7 +842,7 @@ function getNewPosition(
|
|||||||
|
|
||||||
function emitError(
|
function emitError(
|
||||||
context: ParserContext,
|
context: ParserContext,
|
||||||
type: ParserErrorTypes,
|
code: ErrorCodes,
|
||||||
offset?: number
|
offset?: number
|
||||||
): void {
|
): void {
|
||||||
const loc = getCursor(context)
|
const loc = getCursor(context)
|
||||||
@ -862,7 +850,7 @@ function emitError(
|
|||||||
loc.offset += offset
|
loc.offset += offset
|
||||||
loc.column += offset
|
loc.column += offset
|
||||||
}
|
}
|
||||||
context.onError(type, loc)
|
context.onError(createCompilerError(code, loc))
|
||||||
}
|
}
|
||||||
|
|
||||||
function isEnd(
|
function isEnd(
|
@ -1 +1,143 @@
|
|||||||
// TODO
|
import {
|
||||||
|
RootNode,
|
||||||
|
NodeTypes,
|
||||||
|
ParentNode,
|
||||||
|
ChildNode,
|
||||||
|
ElementNode,
|
||||||
|
DirectiveNode
|
||||||
|
} from './ast'
|
||||||
|
import { isString } from '@vue/shared'
|
||||||
|
import { CompilerError } from './errors'
|
||||||
|
|
||||||
|
export type Transform = (node: ChildNode, context: TransformContext) => void
|
||||||
|
|
||||||
|
export type DirectiveTransform = (
|
||||||
|
node: ElementNode,
|
||||||
|
dir: DirectiveNode,
|
||||||
|
context: TransformContext
|
||||||
|
) => false | void
|
||||||
|
|
||||||
|
export interface TransformOptions {
|
||||||
|
transforms: Transform[]
|
||||||
|
onError?: (error: CompilerError) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransformContext extends Required<TransformOptions> {
|
||||||
|
parent: ParentNode
|
||||||
|
ancestors: ParentNode[]
|
||||||
|
childIndex: number
|
||||||
|
replaceNode(node: ChildNode): void
|
||||||
|
removeNode(): void
|
||||||
|
nodeRemoved: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transform(root: RootNode, options: TransformOptions) {
|
||||||
|
const context = createTransformContext(root, options)
|
||||||
|
traverseChildren(root, context, context.ancestors)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTransformContext(
|
||||||
|
root: RootNode,
|
||||||
|
options: TransformOptions
|
||||||
|
): TransformContext {
|
||||||
|
const context: TransformContext = {
|
||||||
|
onError(error: CompilerError) {
|
||||||
|
throw error
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
parent: root,
|
||||||
|
ancestors: [root],
|
||||||
|
childIndex: 0,
|
||||||
|
replaceNode(node) {
|
||||||
|
context.parent.children[context.childIndex] = node
|
||||||
|
},
|
||||||
|
removeNode() {
|
||||||
|
context.parent.children.splice(context.childIndex, 1)
|
||||||
|
context.nodeRemoved = true
|
||||||
|
},
|
||||||
|
nodeRemoved: false
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
function traverseChildren(
|
||||||
|
parent: ParentNode,
|
||||||
|
context: TransformContext,
|
||||||
|
ancestors: ParentNode[]
|
||||||
|
) {
|
||||||
|
ancestors = ancestors.concat(parent)
|
||||||
|
for (let i = 0; i < parent.children.length; i++) {
|
||||||
|
context.parent = parent
|
||||||
|
context.ancestors = ancestors
|
||||||
|
context.childIndex = i
|
||||||
|
traverseNode(parent.children[i], context, ancestors)
|
||||||
|
if (context.nodeRemoved) {
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function traverseNode(
|
||||||
|
node: ChildNode,
|
||||||
|
context: TransformContext,
|
||||||
|
ancestors: ParentNode[]
|
||||||
|
) {
|
||||||
|
// apply transform plugins
|
||||||
|
const transforms = context.transforms
|
||||||
|
for (let i = 0; i < transforms.length; i++) {
|
||||||
|
const transform = transforms[i]
|
||||||
|
context.nodeRemoved = false
|
||||||
|
transform(node, context)
|
||||||
|
if (context.nodeRemoved) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
// node may have been replaced
|
||||||
|
node = context.parent.children[context.childIndex]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// further traverse downwards
|
||||||
|
switch (node.type) {
|
||||||
|
case NodeTypes.IF:
|
||||||
|
for (let i = 0; i < node.branches.length; i++) {
|
||||||
|
traverseChildren(node.branches[i], context, ancestors)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case NodeTypes.FOR:
|
||||||
|
case NodeTypes.ELEMENT:
|
||||||
|
traverseChildren(node, context, ancestors)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const identity = <T>(_: T): T => _
|
||||||
|
|
||||||
|
export function createDirectiveTransform(
|
||||||
|
name: string | RegExp,
|
||||||
|
fn: DirectiveTransform
|
||||||
|
): Transform {
|
||||||
|
const matches = isString(name)
|
||||||
|
? (n: string) => n === name
|
||||||
|
: (n: string) => name.test(n)
|
||||||
|
|
||||||
|
return (node, context) => {
|
||||||
|
if (node.type === NodeTypes.ELEMENT) {
|
||||||
|
const dirs = node.directives
|
||||||
|
let didRemove = false
|
||||||
|
for (let i = 0; i < dirs.length; i++) {
|
||||||
|
if (matches(dirs[i].name)) {
|
||||||
|
const res = fn(node, dirs[i], context)
|
||||||
|
// Directives are removed after transformation by default. A transform
|
||||||
|
// returning false means the directive should not be removed.
|
||||||
|
if (res !== false) {
|
||||||
|
;(dirs as any)[i] = undefined
|
||||||
|
didRemove = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (didRemove) {
|
||||||
|
node.directives = dirs.filter(identity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,7 +3,7 @@ import {
|
|||||||
NodeTypes,
|
NodeTypes,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
TextNode,
|
TextNode,
|
||||||
ParserErrorTypes,
|
ErrorCodes,
|
||||||
ExpressionNode,
|
ExpressionNode,
|
||||||
ElementTypes
|
ElementTypes
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
@ -134,7 +134,8 @@ describe('DOM parser', () => {
|
|||||||
ns: DOMNamespaces.HTML,
|
ns: DOMNamespaces.HTML,
|
||||||
tag: 'img',
|
tag: 'img',
|
||||||
tagType: ElementTypes.ELEMENT,
|
tagType: ElementTypes.ELEMENT,
|
||||||
props: [],
|
attrs: [],
|
||||||
|
directives: [],
|
||||||
isSelfClosing: false,
|
isSelfClosing: false,
|
||||||
children: [],
|
children: [],
|
||||||
loc: {
|
loc: {
|
||||||
@ -150,9 +151,9 @@ describe('DOM parser', () => {
|
|||||||
'<textarea>hello</textarea</textarea0></texTArea a="<>">',
|
'<textarea>hello</textarea</textarea0></texTArea a="<>">',
|
||||||
{
|
{
|
||||||
...parserOptions,
|
...parserOptions,
|
||||||
onError: type => {
|
onError: err => {
|
||||||
if (type !== ParserErrorTypes.END_TAG_WITH_ATTRIBUTES) {
|
if (err.code !== ErrorCodes.END_TAG_WITH_ATTRIBUTES) {
|
||||||
throw new Error(String(type))
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
NodeTypes,
|
|
||||||
TextModes,
|
TextModes,
|
||||||
ParserOptions,
|
ParserOptions,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
@ -23,9 +22,8 @@ export const parserOptionsMinimal: ParserOptions = {
|
|||||||
return DOMNamespaces.SVG
|
return DOMNamespaces.SVG
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
parent.props.some(
|
parent.attrs.some(
|
||||||
a =>
|
a =>
|
||||||
a.type === NodeTypes.ATTRIBUTE &&
|
|
||||||
a.name === 'encoding' &&
|
a.name === 'encoding' &&
|
||||||
a.value != null &&
|
a.value != null &&
|
||||||
(a.value.content === 'text/html' ||
|
(a.value.content === 'text/html' ||
|
||||||
|
Loading…
Reference in New Issue
Block a user