diff --git a/packages/compiler-core/src/parser.ts b/packages/compiler-core/src/parser.ts index 4d1fb84f..d9adf171 100644 --- a/packages/compiler-core/src/parser.ts +++ b/packages/compiler-core/src/parser.ts @@ -77,11 +77,6 @@ function createParserContext( } } -function getCursor(context: ParserContext): Position { - const { column, line, offset } = context - return { column, line, offset } -} - function parseChildren( context: ParserContext, mode: TextModes, @@ -172,134 +167,6 @@ function parseChildren( return nodes } -function getSelection( - context: ParserContext, - start: Position, - end?: Position -): SourceLocation { - end = end || getCursor(context) - return { - start, - end, - source: context.originalSource.slice(start.offset, end.offset) - } -} - -function last(xs: T[]): T | undefined { - return xs[xs.length - 1] -} - -function startsWith(source: string, searchString: string): boolean { - return source.startsWith(searchString) -} - -function advanceBy(context: ParserContext, numberOfCharacters: number): void { - __DEV__ && assert(numberOfCharacters <= context.source.length) - - const { column, source } = context - const str = source.slice(0, numberOfCharacters) - const lines = str.split(/\r?\n/) - - context.source = source.slice(numberOfCharacters) - context.offset += numberOfCharacters - context.line += lines.length - 1 - context.column = - lines.length === 1 - ? column + numberOfCharacters - : Math.max(1, lines.pop()!.length) -} - -function advanceSpaces(context: ParserContext): void { - const match = /^[\t\r\n\f ]+/.exec(context.source) - if (match) { - advanceBy(context, match[0].length) - } -} - -function getNewPosition( - context: ParserContext, - start: Position, - numberOfCharacters: number -): Position { - const { originalSource } = context - const str = originalSource.slice(start.offset, numberOfCharacters) - const lines = str.split(/\r?\n/) - - const newPosition = { - column: start.column, - line: start.line, - offset: start.offset - } - - newPosition.offset += numberOfCharacters - newPosition.line += lines.length - 1 - newPosition.column = - lines.length === 1 - ? start.column + numberOfCharacters - : Math.max(1, lines.pop()!.length) - - return newPosition -} - -function emitError( - context: ParserContext, - type: ParserErrorTypes, - offset?: number -): void { - const loc = getCursor(context) - if (offset) { - loc.offset += offset - loc.column += offset - } - context.onError(type, loc) -} - -function isEnd( - context: ParserContext, - mode: TextModes, - ancestors: ElementNode[] -): boolean { - const s = context.source - - switch (mode) { - case TextModes.DATA: - if (startsWith(s, '= 0; --i) { - if (startsWithEndTagOpen(s, ancestors[i].tag)) { - return true - } - } - } - break - - case TextModes.RCDATA: - case TextModes.RAWTEXT: { - const parent = last(ancestors) - if (parent && startsWithEndTagOpen(s, parent.tag)) { - return true - } - break - } - - case TextModes.CDATA: - if (startsWith(s, ']]>')) { - return true - } - break - } - - return !s -} - -function startsWithEndTagOpen(source: string, tag: string): boolean { - return ( - startsWith(source, ']/.test(source[2 + tag.length] || '>') - ) -} - function pushNode( context: ParserContext, nodes: RootNode['children'], @@ -887,6 +754,145 @@ function parseTextData( return text } +function getCursor(context: ParserContext): Position { + const { column, line, offset } = context + return { column, line, offset } +} + +function getSelection( + context: ParserContext, + start: Position, + end?: Position +): SourceLocation { + end = end || getCursor(context) + return { + start, + end, + source: context.originalSource.slice(start.offset, end.offset) + } +} + +function last(xs: T[]): T | undefined { + return xs[xs.length - 1] +} + +function startsWith(source: string, searchString: string): boolean { + return source.startsWith(searchString) +} + +function advanceBy(context: ParserContext, numberOfCharacters: number): void { + __DEV__ && assert(numberOfCharacters <= context.source.length) + + const { column, source } = context + const str = source.slice(0, numberOfCharacters) + const lines = str.split(/\r?\n/) + + context.source = source.slice(numberOfCharacters) + context.offset += numberOfCharacters + context.line += lines.length - 1 + context.column = + lines.length === 1 + ? column + numberOfCharacters + : Math.max(1, lines.pop()!.length) +} + +function advanceSpaces(context: ParserContext): void { + const match = /^[\t\r\n\f ]+/.exec(context.source) + if (match) { + advanceBy(context, match[0].length) + } +} + +function getNewPosition( + context: ParserContext, + start: Position, + numberOfCharacters: number +): Position { + const { originalSource } = context + const str = originalSource.slice(start.offset, numberOfCharacters) + const lines = str.split(/\r?\n/) + + const newPosition = { + column: start.column, + line: start.line, + offset: start.offset + } + + newPosition.offset += numberOfCharacters + newPosition.line += lines.length - 1 + newPosition.column = + lines.length === 1 + ? start.column + numberOfCharacters + : Math.max(1, lines.pop()!.length) + + return newPosition +} + +function emitError( + context: ParserContext, + type: ParserErrorTypes, + offset?: number +): void { + const loc = getCursor(context) + if (offset) { + loc.offset += offset + loc.column += offset + } + context.onError(type, loc) +} + +function isEnd( + context: ParserContext, + mode: TextModes, + ancestors: ElementNode[] +): boolean { + const s = context.source + + switch (mode) { + case TextModes.DATA: + if (startsWith(s, '= 0; --i) { + if (startsWithEndTagOpen(s, ancestors[i].tag)) { + return true + } + } + } + break + + case TextModes.RCDATA: + case TextModes.RAWTEXT: { + const parent = last(ancestors) + if (parent && startsWithEndTagOpen(s, parent.tag)) { + return true + } + break + } + + case TextModes.CDATA: + if (startsWith(s, ']]>')) { + return true + } + break + } + + return !s +} + +function startsWithEndTagOpen(source: string, tag: string): boolean { + return ( + startsWith(source, ']/.test(source[2 + tag.length] || '>') + ) +} + +function assert(condition: boolean, msg?: string) { + if (!condition) { + throw new Error(msg || `unexpected parser condition`) + } +} + // https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state const CCR_REPLACEMENTS: { [key: number]: number | undefined } = { 0x80: 0x20ac, @@ -917,9 +923,3 @@ const CCR_REPLACEMENTS: { [key: number]: number | undefined } = { 0x9e: 0x017e, 0x9f: 0x0178 } - -function assert(condition: boolean, msg?: string) { - if (!condition) { - throw new Error(msg || `unexpected parser condition`) - } -} diff --git a/packages/compiler-core/src/parserErrorTypes.ts b/packages/compiler-core/src/parserErrorTypes.ts index aad1047d..b449e0e3 100644 --- a/packages/compiler-core/src/parserErrorTypes.ts +++ b/packages/compiler-core/src/parserErrorTypes.ts @@ -35,3 +35,55 @@ export const enum ParserErrorTypes { 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 '