Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow unquoted slash attributes #29

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 29 additions & 12 deletions packages/compiler/src/ml_parser/lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {ParseError, ParseLocation, ParseSourceFile, ParseSourceSpan} from '../pa

import {NAMED_ENTITIES} from './entities';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation_config';
import {TagContentType, TagDefinition, mergeNsAndName} from './tags';
import {mergeNsAndName, TagContentType, TagDefinition} from './tags';
import {IncompleteTagOpenToken, TagOpenStartToken, Token, TokenType} from './tokens';

export class TokenError extends ParseError {
Expand Down Expand Up @@ -95,7 +95,10 @@ export interface TokenizeOptions {
}

export function tokenize(
source: string, url: string, getTagContentType: (tagName: string, prefix: string, hasParent: boolean, attrs: Array<{prefix: string, name: string, value?: string}>) => TagContentType,
source: string, url: string,
getTagContentType: (
tagName: string, prefix: string, hasParent: boolean,
attrs: Array<{prefix: string, name: string, value?: string}>) => TagContentType,
options: TokenizeOptions = {}): TokenizeResult {
const tokenizer = new _Tokenizer(new ParseSourceFile(source, url), getTagContentType, options);
tokenizer.tokenize();
Expand Down Expand Up @@ -150,11 +153,15 @@ class _Tokenizer {

/**
* @param _file The html source file being tokenized.
* @param _getTagContentType A function that will retrieve a tag content type for a given tag name.
* @param _getTagContentType A function that will retrieve a tag content type for a given tag
* name.
* @param options Configuration of the tokenization.
*/
constructor(
_file: ParseSourceFile, private _getTagContentType: (tagName: string, prefix: string, hasParent: boolean, attrs: Array<{prefix: string, name: string, value?: string}>) => TagContentType,
_file: ParseSourceFile,
private _getTagContentType:
(tagName: string, prefix: string, hasParent: boolean,
attrs: Array<{prefix: string, name: string, value?: string}>) => TagContentType,
options: TokenizeOptions) {
this._tokenizeIcu = options.tokenizeExpansionForms || false;
this._interpolationConfig = options.interpolationConfig || DEFAULT_INTERPOLATION_CONFIG;
Expand Down Expand Up @@ -368,7 +375,8 @@ class _Tokenizer {
private _requireStrCaseInsensitive(chars: string) {
const location = this._cursor.clone();
if (!this._attemptStrCaseInsensitive(chars)) {
throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(location));
throw this._createError(
_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(location));
}
}

Expand Down Expand Up @@ -541,8 +549,8 @@ class _Tokenizer {
prefix = openTagToken.parts[0];
tagName = openTagToken.parts[1];
this._attemptCharCodeUntilFn(isNotWhitespace);
while (this._cursor.peek() !== chars.$SLASH && this._cursor.peek() !== chars.$GT &&
this._cursor.peek() !== chars.$LT && this._cursor.peek() !== chars.$EOF) {
while (this._cursor.peek() !== chars.$GT && this._cursor.peek() !== chars.$LT &&
this._cursor.peek() !== chars.$EOF) {
const [prefix, name] = this._consumeAttributeName();
this._attemptCharCodeUntilFn(isNotWhitespace);
if (this._attemptCharCode(chars.$EQ)) {
Expand Down Expand Up @@ -572,11 +580,13 @@ class _Tokenizer {
throw e;
}

if (this._canSelfClose && this.tokens[this.tokens.length - 1].type === TokenType.TAG_OPEN_END_VOID) {
if (this._canSelfClose &&
this.tokens[this.tokens.length - 1].type === TokenType.TAG_OPEN_END_VOID) {
return;
}

const contentTokenType = this._getTagContentType(tagName, prefix, this._fullNameStack.length > 0, attrs);
const contentTokenType =
this._getTagContentType(tagName, prefix, this._fullNameStack.length > 0, attrs);
this._handleFullNameStackForTagOpen(prefix, tagName);

if (contentTokenType === TagContentType.RAW_TEXT) {
Expand Down Expand Up @@ -632,7 +642,7 @@ class _Tokenizer {
endPredicate);
this._consumeQuote(quoteChar);
} else {
const endPredicate = () => isNameEnd(this._cursor.peek());
const endPredicate = () => isUnquotedEnd(this._cursor.peek());
value = this._consumeWithInterpolation(
TokenType.ATTR_VALUE_TEXT, TokenType.ATTR_VALUE_INTERPOLATION, endPredicate,
endPredicate);
Expand Down Expand Up @@ -920,14 +930,16 @@ class _Tokenizer {

private _handleFullNameStackForTagOpen(prefix: string, tagName: string) {
const fullName = mergeNsAndName(prefix, tagName);
if (this._fullNameStack.length === 0 || this._fullNameStack[this._fullNameStack.length - 1] === fullName) {
if (this._fullNameStack.length === 0 ||
this._fullNameStack[this._fullNameStack.length - 1] === fullName) {
this._fullNameStack.push(fullName);
}
}

private _handleFullNameStackForTagClose(prefix: string, tagName: string) {
const fullName = mergeNsAndName(prefix, tagName);
if (this._fullNameStack.length !== 0 && this._fullNameStack[this._fullNameStack.length - 1] === fullName) {
if (this._fullNameStack.length !== 0 &&
this._fullNameStack[this._fullNameStack.length - 1] === fullName) {
this._fullNameStack.pop();
}
}
Expand All @@ -943,6 +955,11 @@ function isNameEnd(code: number): boolean {
code === chars.$EOF;
}

function isUnquotedEnd(code: number): boolean {
return chars.isWhitespace(code) || code === chars.$GT || code === chars.$LT ||
code === chars.$SQ || code === chars.$DQ || code === chars.$EQ || code === chars.$EOF;
}

function isPrefixEnd(code: number): boolean {
return (code < chars.$a || chars.$z < code) && (code < chars.$A || chars.$Z < code) &&
(code < chars.$0 || code > chars.$9);
Expand Down