You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 4-6.RSVP
Socket
Book a DemoInstallSign in
Socket

angular-html-parser

Package Overview
Dependencies
Maintainers
4
Versions
34
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

angular-html-parser - npm Package Compare versions

Comparing version

to
9.0.1

32

lib/compiler/src/ml_parser/ast.d.ts

@@ -15,3 +15,3 @@ /**

}
export type Node = Attribute | CDATA | Comment | DocType | Element | Text | Block | BlockParameter | LetDeclaration;
export type Node = Attribute | CDATA | Comment | DocType | Element | Text | Block | BlockParameter | LetDeclaration | Component | Directive;
export declare abstract class NodeWithI18n implements BaseNode {

@@ -69,2 +69,3 @@ sourceSpan: ParseSourceSpan;

attrs: Attribute[];
readonly directives: Directive[];
children: Node[];

@@ -74,3 +75,3 @@ startSourceSpan: ParseSourceSpan;

nameSpan: ParseSourceSpan | null;
constructor(name: string, attrs: Attribute[], children: Node[], sourceSpan: ParseSourceSpan, startSourceSpan: ParseSourceSpan, endSourceSpan?: ParseSourceSpan | null, nameSpan?: ParseSourceSpan | null, i18n?: I18nMeta);
constructor(name: string, attrs: Attribute[], directives: Directive[], children: Node[], sourceSpan: ParseSourceSpan, startSourceSpan: ParseSourceSpan, endSourceSpan?: ParseSourceSpan | null, nameSpan?: ParseSourceSpan | null, i18n?: I18nMeta);
visit(visitor: Visitor, context: any): any;

@@ -104,2 +105,25 @@ readonly type = "element";

}
export declare class Component extends NodeWithI18n {
readonly componentName: string;
readonly tagName: string | null;
readonly fullName: string;
attrs: Attribute[];
readonly directives: Directive[];
readonly children: Node[];
readonly startSourceSpan: ParseSourceSpan;
endSourceSpan: ParseSourceSpan | null;
constructor(componentName: string, tagName: string | null, fullName: string, attrs: Attribute[], directives: Directive[], children: Node[], sourceSpan: ParseSourceSpan, startSourceSpan: ParseSourceSpan, endSourceSpan?: ParseSourceSpan | null, i18n?: I18nMeta);
visit(visitor: Visitor, context: any): any;
readonly type = "component";
}
export declare class Directive implements BaseNode {
readonly name: string;
readonly attrs: Attribute[];
readonly sourceSpan: ParseSourceSpan;
readonly startSourceSpan: ParseSourceSpan;
readonly endSourceSpan: ParseSourceSpan | null;
constructor(name: string, attrs: Attribute[], sourceSpan: ParseSourceSpan, startSourceSpan: ParseSourceSpan, endSourceSpan?: ParseSourceSpan | null);
visit(visitor: Visitor, context: any): any;
readonly type = "directive";
}
export declare class BlockParameter implements BaseNode {

@@ -139,2 +163,4 @@ expression: string;

visitLetDeclaration(decl: LetDeclaration, context: any): any;
visitComponent(component: Component, context: any): any;
visitDirective(directive: Directive, context: any): any;
}

@@ -155,4 +181,6 @@ export declare function visitAll(visitor: Visitor, nodes: Node[], context?: any): any[];

visitLetDeclaration(decl: LetDeclaration, context: any): void;
visitComponent(component: Component, context: any): void;
visitDirective(directive: Directive, context: any): void;
private visitChildren;
}
export {};

@@ -8,3 +8,2 @@ /**

*/
// Expansion|ExpansionCase -- not used by angular-html-parser, should be removed on publish
export class NodeWithI18n {

@@ -82,6 +81,7 @@ constructor(sourceSpan, i18n) {

export class Element extends NodeWithI18n {
constructor(name, attrs, children, sourceSpan, startSourceSpan, endSourceSpan = null, nameSpan = null, i18n) {
constructor(name, attrs, directives, children, sourceSpan, startSourceSpan, endSourceSpan = null, nameSpan = null, i18n) {
super(sourceSpan, i18n);
this.name = name;
this.attrs = attrs;
this.directives = directives;
this.children = children;

@@ -132,2 +132,32 @@ this.startSourceSpan = startSourceSpan;

}
export class Component extends NodeWithI18n {
constructor(componentName, tagName, fullName, attrs, directives, children, sourceSpan, startSourceSpan, endSourceSpan = null, i18n) {
super(sourceSpan, i18n);
this.componentName = componentName;
this.tagName = tagName;
this.fullName = fullName;
this.attrs = attrs;
this.directives = directives;
this.children = children;
this.startSourceSpan = startSourceSpan;
this.endSourceSpan = endSourceSpan;
this.type = 'component';
}
visit(visitor, context) {
return visitor.visitComponent(this, context);
}
}
export class Directive {
constructor(name, attrs, sourceSpan, startSourceSpan, endSourceSpan = null) {
this.name = name;
this.attrs = attrs;
this.sourceSpan = sourceSpan;
this.startSourceSpan = startSourceSpan;
this.endSourceSpan = endSourceSpan;
this.type = 'directive';
}
visit(visitor, context) {
return visitor.visitDirective(this, context);
}
}
export class BlockParameter {

@@ -178,2 +208,3 @@ constructor(expression, sourceSpan) {

visit(ast.attrs);
visit(ast.directives);
visit(ast.children);

@@ -201,2 +232,13 @@ });

visitLetDeclaration(decl, context) { }
visitComponent(component, context) {
this.visitChildren(context, (visit) => {
visit(component.attrs);
visit(component.children);
});
}
visitDirective(directive, context) {
this.visitChildren(context, (visit) => {
visit(directive.attrs);
});
}
visitChildren(context, cb) {

@@ -203,0 +245,0 @@ let results = [];

@@ -94,2 +94,4 @@ /**

tokenizeLet?: boolean;
/** Whether the selectorless syntax is enabled. */
selectorlessEnabled?: boolean;
canSelfClose?: boolean;

@@ -96,0 +98,0 @@ allowHtmComponentClosingTags?: boolean;

@@ -65,2 +65,3 @@ /**

this._expansionCaseStack = [];
this._openDirectiveCount = 0;
this._inInterpolation = false;

@@ -90,2 +91,3 @@ this._fullNameStack = [];

this._tokenizeLet = options.tokenizeLet ?? true;
this._selectorlessEnabled = options.selectorlessEnabled ?? false;
try {

@@ -169,3 +171,3 @@ this._cursor.init();

}
this._beginToken(34 /* TokenType.EOF */);
this._beginToken(42 /* TokenType.EOF */);
this._endToken([]);

@@ -519,3 +521,3 @@ }

this._cursor.advance();
const char = NAMED_ENTITIES[name];
const char = NAMED_ENTITIES.hasOwnProperty(name) && NAMED_ENTITIES[name];
if (!char) {

@@ -583,3 +585,3 @@ throw this._createError(_unknownEntityErrorMsg(name), this._cursor.getSpan(start));

}
_consumePrefixAndName() {
_consumePrefixAndName(endPredicate) {
const nameOrPrefixStart = this._cursor.clone();

@@ -599,3 +601,3 @@ let prefix = '';

}
this._requireCharCodeUntilFn(isNameEnd, prefix === '' ? 0 : 1);
this._requireCharCodeUntilFn(endPredicate, prefix === '' ? 0 : 1);
const name = this._cursor.getChars(nameStart);

@@ -607,35 +609,55 @@ return [prefix, name];

let prefix;
let openTagToken;
let closingTagName;
let openToken;
const attrs = [];
try {
if (!chars.isAsciiLetter(this._cursor.peek())) {
throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(start));
if (this._selectorlessEnabled && isSelectorlessNameStart(this._cursor.peek())) {
openToken = this._consumeComponentOpenStart(start);
[closingTagName, prefix, tagName] = openToken.parts;
if (prefix) {
closingTagName += `:${prefix}`;
}
if (tagName) {
closingTagName += `:${tagName}`;
}
this._attemptCharCodeUntilFn(isNotWhitespace);
}
openTagToken = this._consumeTagOpenStart(start);
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) {
const [prefix, name] = this._consumeAttributeName();
else {
if (!chars.isAsciiLetter(this._cursor.peek())) {
throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(start));
}
openToken = this._consumeTagOpenStart(start);
prefix = openToken.parts[0];
tagName = closingTagName = openToken.parts[1];
this._attemptCharCodeUntilFn(isNotWhitespace);
if (this._attemptCharCode(chars.$EQ)) {
this._attemptCharCodeUntilFn(isNotWhitespace);
const value = this._consumeAttributeValue();
attrs.push({ prefix, name, value });
}
while (!isAttributeTerminator(this._cursor.peek())) {
if (this._selectorlessEnabled && this._cursor.peek() === chars.$AT) {
const start = this._cursor.clone();
const nameStart = start.clone();
nameStart.advance();
if (isSelectorlessNameStart(nameStart.peek())) {
this._consumeDirective(start, nameStart);
}
}
else {
attrs.push({ prefix, name });
const attr = this._consumeAttribute();
attrs.push(attr);
}
this._attemptCharCodeUntilFn(isNotWhitespace);
}
this._consumeTagOpenEnd();
if (openToken.type === 34 /* TokenType.COMPONENT_OPEN_START */) {
this._consumeComponentOpenEnd();
}
else {
this._consumeTagOpenEnd();
}
}
catch (e) {
if (e instanceof _ControlFlowError) {
if (openTagToken) {
if (openToken) {
// We errored before we could close the opening tag, so it is incomplete.
openTagToken.type = 4 /* TokenType.INCOMPLETE_TAG_OPEN */;
openToken.type =
openToken.type === 34 /* TokenType.COMPONENT_OPEN_START */
? 38 /* TokenType.INCOMPLETE_COMPONENT_OPEN */
: 4 /* TokenType.INCOMPLETE_TAG_OPEN */;
}

@@ -659,9 +681,9 @@ else {

if (contentTokenType === TagContentType.RAW_TEXT) {
this._consumeRawTextWithTagClose(prefix, tagName, false);
this._consumeRawTextWithTagClose(prefix, openToken, closingTagName, false);
}
else if (contentTokenType === TagContentType.ESCAPABLE_RAW_TEXT) {
this._consumeRawTextWithTagClose(prefix, tagName, true);
this._consumeRawTextWithTagClose(prefix, openToken, closingTagName, true);
}
}
_consumeRawTextWithTagClose(prefix, tagName, consumeEntities) {
_consumeRawTextWithTagClose(prefix, openToken, tagName, consumeEntities) {
this._consumeRawText(consumeEntities, () => {

@@ -673,3 +695,3 @@ if (!this._attemptCharCode(chars.$LT))

this._attemptCharCodeUntilFn(isNotWhitespace);
if (!this._attemptStrCaseInsensitive(prefix ? `${prefix}:${tagName}` : tagName))
if (!this._attemptStrCaseInsensitive(tagName))
return false;

@@ -679,6 +701,8 @@ this._attemptCharCodeUntilFn(isNotWhitespace);

});
this._beginToken(3 /* TokenType.TAG_CLOSE */);
this._beginToken(openToken.type === 34 /* TokenType.COMPONENT_OPEN_START */
? 37 /* TokenType.COMPONENT_CLOSE */
: 3 /* TokenType.TAG_CLOSE */);
this._requireCharCodeUntilFn((code) => code === chars.$GT, 3);
this._cursor.advance(); // Consume the `>`
this._endToken([prefix, tagName]);
this._endToken(openToken.parts);
this._handleFullNameStackForTagClose(prefix, tagName);

@@ -688,5 +712,35 @@ }

this._beginToken(0 /* TokenType.TAG_OPEN_START */, start);
const parts = this._consumePrefixAndName();
const parts = this._consumePrefixAndName(isNameEnd);
return this._endToken(parts);
}
_consumeComponentOpenStart(start) {
this._beginToken(34 /* TokenType.COMPONENT_OPEN_START */, start);
const parts = this._consumeComponentName();
return this._endToken(parts);
}
_consumeComponentName() {
const nameStart = this._cursor.clone();
while (isSelectorlessNameChar(this._cursor.peek())) {
this._cursor.advance();
}
const name = this._cursor.getChars(nameStart);
let prefix = '';
let tagName = '';
if (this._cursor.peek() === chars.$COLON) {
this._cursor.advance();
[prefix, tagName] = this._consumePrefixAndName(isNameEnd);
}
return [name, prefix, tagName];
}
_consumeAttribute() {
const [prefix, name] = this._consumeAttributeName();
let value;
this._attemptCharCodeUntilFn(isNotWhitespace);
if (this._attemptCharCode(chars.$EQ)) {
this._attemptCharCodeUntilFn(isNotWhitespace);
value = this._consumeAttributeValue();
}
this._attemptCharCodeUntilFn(isNotWhitespace);
return { prefix, name, value };
}
_consumeAttributeName() {

@@ -698,3 +752,29 @@ const attrNameStart = this._cursor.peek();

this._beginToken(14 /* TokenType.ATTR_NAME */);
const prefixAndName = this._consumePrefixAndName();
let nameEndPredicate;
if (this._openDirectiveCount > 0) {
// If we're parsing attributes inside of directive syntax, we have to terminate the name
// on the first non-matching closing paren. For example, if we have `@Dir(someAttr)`,
// `@Dir` and `(` will have already been captured as `DIRECTIVE_NAME` and `DIRECTIVE_OPEN`
// respectively, but the `)` will get captured as a part of the name for `someAttr`
// because normally that would be an event binding.
let openParens = 0;
nameEndPredicate = (code) => {
if (this._openDirectiveCount > 0) {
if (code === chars.$LPAREN) {
openParens++;
}
else if (code === chars.$RPAREN) {
if (openParens === 0) {
return true;
}
openParens--;
}
}
return isNameEnd(code);
};
}
else {
nameEndPredicate = isNameEnd;
}
const prefixAndName = this._consumePrefixAndName(nameEndPredicate);
this._endToken(prefixAndName);

@@ -733,3 +813,25 @@ return prefixAndName;

}
_consumeComponentOpenEnd() {
const tokenType = this._attemptCharCode(chars.$SLASH)
? 36 /* TokenType.COMPONENT_OPEN_END_VOID */
: 35 /* TokenType.COMPONENT_OPEN_END */;
this._beginToken(tokenType);
this._requireCharCode(chars.$GT);
this._endToken([]);
}
_consumeTagClose(start) {
if (this._selectorlessEnabled) {
const clone = start.clone();
while (clone.peek() !== chars.$GT && !isSelectorlessNameStart(clone.peek())) {
clone.advance();
}
if (isSelectorlessNameStart(clone.peek())) {
this._beginToken(37 /* TokenType.COMPONENT_CLOSE */, start);
const parts = this._consumeComponentName();
this._attemptCharCodeUntilFn(isNotWhitespace);
this._requireCharCode(chars.$GT);
this._endToken(parts);
return;
}
}
this._beginToken(3 /* TokenType.TAG_CLOSE */, start);

@@ -744,3 +846,3 @@ this._attemptCharCodeUntilFn(isNotWhitespace);

else {
const [prefix, name] = this._consumePrefixAndName();
const [prefix, name] = this._consumePrefixAndName(isNameEnd);
this._attemptCharCodeUntilFn(isNotWhitespace);

@@ -905,2 +1007,46 @@ this._requireCharCode(chars.$GT);

}
_consumeDirective(start, nameStart) {
this._requireCharCode(chars.$AT);
// Skip over the @ since it's not part of the name.
this._cursor.advance();
// Capture the rest of the name.
while (isSelectorlessNameChar(this._cursor.peek())) {
this._cursor.advance();
}
// Capture the opening token.
this._beginToken(39 /* TokenType.DIRECTIVE_NAME */, start);
const name = this._cursor.getChars(nameStart);
this._endToken([name]);
this._attemptCharCodeUntilFn(isNotWhitespace);
// Optionally there might be attributes bound to the specific directive.
// Stop parsing if there's no opening character for them.
if (this._cursor.peek() !== chars.$LPAREN) {
return;
}
this._openDirectiveCount++;
this._beginToken(40 /* TokenType.DIRECTIVE_OPEN */);
this._cursor.advance();
this._endToken([]);
this._attemptCharCodeUntilFn(isNotWhitespace);
// Capture all the attributes until we hit a closing paren.
while (!isAttributeTerminator(this._cursor.peek()) && this._cursor.peek() !== chars.$RPAREN) {
this._consumeAttribute();
}
// Trim any trailing whitespace.
this._attemptCharCodeUntilFn(isNotWhitespace);
this._openDirectiveCount--;
if (this._cursor.peek() !== chars.$RPAREN) {
// Stop parsing, instead of throwing, if we've hit the end of the tag.
// This can be handled better later when turning the tokens into AST.
if (this._cursor.peek() === chars.$GT || this._cursor.peek() === chars.$SLASH) {
return;
}
throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(start));
}
// Capture the closing token.
this._beginToken(41 /* TokenType.DIRECTIVE_CLOSE */);
this._cursor.advance();
this._endToken([]);
this._attemptCharCodeUntilFn(isNotWhitespace);
}
_getProcessedChars(start, end) {

@@ -1044,2 +1190,11 @@ return this._processCarriageReturns(end.getChars(start));

}
function isSelectorlessNameStart(code) {
return code === chars.$_ || (code >= chars.$A && code <= chars.$Z);
}
function isSelectorlessNameChar(code) {
return chars.isAsciiLetter(code) || chars.isDigit(code) || code === chars.$_;
}
function isAttributeTerminator(code) {
return code === chars.$SLASH || code === chars.$GT || code === chars.$LT || code === chars.$EOF;
}
function mergeTextTokens(srcTokens) {

@@ -1046,0 +1201,0 @@ const dstTokens = [];

1491

lib/compiler/src/ml_parser/parser.js

@@ -14,873 +14,694 @@ /**

export class TreeError extends ParseError {
static create(elementName, span, msg) {
return new TreeError(elementName, span, msg);
}
constructor(elementName, span, msg) {
super(span, msg);
this.elementName = elementName;
}
static create(elementName, span, msg) {
return new TreeError(elementName, span, msg);
}
constructor(elementName, span, msg) {
super(span, msg);
this.elementName = elementName;
}
}
export class ParseTreeResult {
constructor(rootNodes, errors) {
this.rootNodes = rootNodes;
this.errors = errors;
}
constructor(rootNodes, errors) {
this.rootNodes = rootNodes;
this.errors = errors;
}
}
export class Parser {
constructor(getTagDefinition) {
this.getTagDefinition = getTagDefinition;
}
parse(
source,
url,
options,
isTagNameCaseSensitive = false,
getTagContentType,
) {
const lowercasify =
(fn) =>
(x, ...args) =>
fn(x.toLowerCase(), ...args);
const getTagDefinition = isTagNameCaseSensitive
? this.getTagDefinition
: lowercasify(this.getTagDefinition);
const getDefaultTagContentType = (tagName) =>
getTagDefinition(tagName).getContentType();
const getTagContentTypeWithProcessedTagName = isTagNameCaseSensitive
? getTagContentType
: lowercasify(getTagContentType);
const _getTagContentType = getTagContentType
? (tagName, prefix, hasParent, attrs) => {
const contentType = getTagContentTypeWithProcessedTagName(
tagName,
prefix,
hasParent,
attrs,
);
return contentType !== undefined
? contentType
: getDefaultTagContentType(tagName);
}
: getDefaultTagContentType;
const tokenizeResult = tokenize(source, url, _getTagContentType, options);
const canSelfClose = (options && options.canSelfClose) || false;
const allowHtmComponentClosingTags =
(options && options.allowHtmComponentClosingTags) || false;
const parser = new _TreeBuilder(
tokenizeResult.tokens,
getTagDefinition,
canSelfClose,
allowHtmComponentClosingTags,
isTagNameCaseSensitive,
);
parser.build();
return new ParseTreeResult(
parser.rootNodes,
tokenizeResult.errors.concat(parser.errors),
);
}
constructor(getTagDefinition) {
this.getTagDefinition = getTagDefinition;
}
parse(source, url, options, isTagNameCaseSensitive = false, getTagContentType) {
const lowercasify = (fn) => (x, ...args) => fn(x.toLowerCase(), ...args);
const getTagDefinition = isTagNameCaseSensitive ? this.getTagDefinition : lowercasify(this.getTagDefinition);
const getDefaultTagContentType = (tagName) => getTagDefinition(tagName).getContentType();
const getTagContentTypeWithProcessedTagName = isTagNameCaseSensitive ? getTagContentType : lowercasify(getTagContentType);
const _getTagContentType = getTagContentType ?
(tagName, prefix, hasParent, attrs) => {
const contentType = getTagContentTypeWithProcessedTagName(tagName, prefix, hasParent, attrs);
return contentType !== undefined ? contentType : getDefaultTagContentType(tagName);
} :
getDefaultTagContentType;
const tokenizeResult = tokenize(source, url, _getTagContentType, options);
const canSelfClose = (options && options.canSelfClose) || false;
const allowHtmComponentClosingTags = (options && options.allowHtmComponentClosingTags) || false;
const parser = new _TreeBuilder(tokenizeResult.tokens, getTagDefinition, canSelfClose, allowHtmComponentClosingTags, isTagNameCaseSensitive);
parser.build();
return new ParseTreeResult(parser.rootNodes, tokenizeResult.errors.concat(parser.errors));
}
}
class _TreeBuilder {
constructor(
tokens,
getTagDefinition,
canSelfClose,
allowHtmComponentClosingTags,
isTagNameCaseSensitive,
) {
this.tokens = tokens;
this.getTagDefinition = getTagDefinition;
this.canSelfClose = canSelfClose;
this.allowHtmComponentClosingTags = allowHtmComponentClosingTags;
this.isTagNameCaseSensitive = isTagNameCaseSensitive;
this._index = -1;
this._containerStack = [];
this.rootNodes = [];
this.errors = [];
this._advance();
}
build() {
while (this._peek.type !== 34 /* TokenType.EOF */) {
if (
this._peek.type === 0 /* TokenType.TAG_OPEN_START */ ||
this._peek.type === 4 /* TokenType.INCOMPLETE_TAG_OPEN */
) {
this._consumeStartTag(this._advance());
} else if (this._peek.type === 3 /* TokenType.TAG_CLOSE */) {
this._closeVoidElement();
this._consumeEndTag(this._advance());
} else if (this._peek.type === 12 /* TokenType.CDATA_START */) {
this._closeVoidElement();
this._consumeCdata(this._advance());
} else if (this._peek.type === 10 /* TokenType.COMMENT_START */) {
this._closeVoidElement();
this._consumeComment(this._advance());
} else if (
this._peek.type === 5 /* TokenType.TEXT */ ||
this._peek.type === 7 /* TokenType.RAW_TEXT */ ||
this._peek.type === 6 /* TokenType.ESCAPABLE_RAW_TEXT */
) {
this._closeVoidElement();
this._consumeText(this._advance());
} else if (this._peek.type === 20 /* TokenType.EXPANSION_FORM_START */) {
this._consumeExpansion(this._advance());
} else if (this._peek.type === 25 /* TokenType.BLOCK_OPEN_START */) {
this._closeVoidElement();
this._consumeBlockOpen(this._advance());
} else if (this._peek.type === 27 /* TokenType.BLOCK_CLOSE */) {
this._closeVoidElement();
this._consumeBlockClose(this._advance());
} else if (this._peek.type === 29 /* TokenType.INCOMPLETE_BLOCK_OPEN */) {
this._closeVoidElement();
this._consumeIncompleteBlock(this._advance());
} else if (this._peek.type === 30 /* TokenType.LET_START */) {
this._closeVoidElement();
this._consumeLet(this._advance());
} else if (this._peek.type === 18 /* TokenType.DOC_TYPE_START */) {
this._consumeDocType(this._advance());
} else if (this._peek.type === 33 /* TokenType.INCOMPLETE_LET */) {
this._closeVoidElement();
this._consumeIncompleteLet(this._advance());
} else {
// Skip all other tokens...
constructor(tokens, tagDefinitionResolver, canSelfClose, allowHtmComponentClosingTags, isTagNameCaseSensitive) {
this.tokens = tokens;
this.tagDefinitionResolver = tagDefinitionResolver;
this.canSelfClose = canSelfClose;
this.allowHtmComponentClosingTags = allowHtmComponentClosingTags;
this.isTagNameCaseSensitive = isTagNameCaseSensitive;
this._index = -1;
this._containerStack = [];
this.rootNodes = [];
this.errors = [];
this._advance();
}
}
for (const leftoverContainer of this._containerStack) {
// Unlike HTML elements, blocks aren't closed implicitly by the end of the file.
if (leftoverContainer instanceof html.Block) {
this.errors.push(
TreeError.create(
leftoverContainer.name,
leftoverContainer.sourceSpan,
`Unclosed block "${leftoverContainer.name}"`,
),
);
}
build() {
while (this._peek.type !== 42 /* TokenType.EOF */) {
if (this._peek.type === 0 /* TokenType.TAG_OPEN_START */ ||
this._peek.type === 4 /* TokenType.INCOMPLETE_TAG_OPEN */) {
this._consumeElementStartTag(this._advance());
}
else if (this._peek.type === 3 /* TokenType.TAG_CLOSE */) {
this._closeVoidElement();
this._consumeElementEndTag(this._advance());
}
else if (this._peek.type === 12 /* TokenType.CDATA_START */) {
this._closeVoidElement();
this._consumeCdata(this._advance());
}
else if (this._peek.type === 10 /* TokenType.COMMENT_START */) {
this._closeVoidElement();
this._consumeComment(this._advance());
}
else if (this._peek.type === 5 /* TokenType.TEXT */ ||
this._peek.type === 7 /* TokenType.RAW_TEXT */ ||
this._peek.type === 6 /* TokenType.ESCAPABLE_RAW_TEXT */) {
this._closeVoidElement();
this._consumeText(this._advance());
}
else if (this._peek.type === 20 /* TokenType.EXPANSION_FORM_START */) {
this._consumeExpansion(this._advance());
}
else if (this._peek.type === 25 /* TokenType.BLOCK_OPEN_START */) {
this._closeVoidElement();
this._consumeBlockOpen(this._advance());
}
else if (this._peek.type === 27 /* TokenType.BLOCK_CLOSE */) {
this._closeVoidElement();
this._consumeBlockClose(this._advance());
}
else if (this._peek.type === 29 /* TokenType.INCOMPLETE_BLOCK_OPEN */) {
this._closeVoidElement();
this._consumeIncompleteBlock(this._advance());
}
else if (this._peek.type === 30 /* TokenType.LET_START */) {
this._closeVoidElement();
this._consumeLet(this._advance());
}
else if (this._peek.type === 18 /* TokenType.DOC_TYPE_START */) {
this._consumeDocType(this._advance());
}
else if (this._peek.type === 33 /* TokenType.INCOMPLETE_LET */) {
this._closeVoidElement();
this._consumeIncompleteLet(this._advance());
}
else if (this._peek.type === 34 /* TokenType.COMPONENT_OPEN_START */ ||
this._peek.type === 38 /* TokenType.INCOMPLETE_COMPONENT_OPEN */) {
this._consumeComponentStartTag(this._advance());
}
else if (this._peek.type === 37 /* TokenType.COMPONENT_CLOSE */) {
this._consumeComponentEndTag(this._advance());
}
else {
// Skip all other tokens...
this._advance();
}
}
for (const leftoverContainer of this._containerStack) {
// Unlike HTML elements, blocks aren't closed implicitly by the end of the file.
if (leftoverContainer instanceof html.Block) {
this.errors.push(TreeError.create(leftoverContainer.name, leftoverContainer.sourceSpan, `Unclosed block "${leftoverContainer.name}"`));
}
}
}
}
_advance() {
const prev = this._peek;
if (this._index < this.tokens.length - 1) {
// Note: there is always an EOF token at the end
this._index++;
_advance() {
const prev = this._peek;
if (this._index < this.tokens.length - 1) {
// Note: there is always an EOF token at the end
this._index++;
}
this._peek = this.tokens[this._index];
return prev;
}
this._peek = this.tokens[this._index];
return prev;
}
_advanceIf(type) {
if (this._peek.type === type) {
return this._advance();
_advanceIf(type) {
if (this._peek.type === type) {
return this._advance();
}
return null;
}
return null;
}
_consumeCdata(startToken) {
const text = this._advance();
const value = this._getText(text);
const endToken = this._advanceIf(13 /* TokenType.CDATA_END */);
this._addToParent(
new html.CDATA(
value,
new ParseSourceSpan(
startToken.sourceSpan.start,
(endToken || text).sourceSpan.end,
),
[text],
),
);
}
_consumeComment(token) {
const text = this._advanceIf(7 /* TokenType.RAW_TEXT */);
const endToken = this._advanceIf(11 /* TokenType.COMMENT_END */);
const value = text != null ? text.parts[0].trim() : null;
const sourceSpan =
endToken == null
? token.sourceSpan
: new ParseSourceSpan(
token.sourceSpan.start,
endToken.sourceSpan.end,
token.sourceSpan.fullStart,
);
this._addToParent(new html.Comment(value, sourceSpan));
}
_consumeDocType(startToken) {
const text = this._advanceIf(7 /* TokenType.RAW_TEXT */);
const endToken = this._advanceIf(19 /* TokenType.DOC_TYPE_END */);
const value = text != null ? text.parts[0].trim() : null;
const sourceSpan = new ParseSourceSpan(
startToken.sourceSpan.start,
(endToken || text || startToken).sourceSpan.end,
);
this._addToParent(new html.DocType(value, sourceSpan));
}
_consumeExpansion(token) {
const switchValue = this._advance();
const type = this._advance();
const cases = [];
// read =
while (this._peek.type === 21 /* TokenType.EXPANSION_CASE_VALUE */) {
const expCase = this._parseExpansionCase();
if (!expCase) return; // error
cases.push(expCase);
_consumeCdata(startToken) {
const text = this._advance();
const value = this._getText(text);
const endToken = this._advanceIf(13 /* TokenType.CDATA_END */);
this._addToParent(new html.CDATA(value, new ParseSourceSpan(startToken.sourceSpan.start, (endToken || text).sourceSpan.end), [text]));
}
// read the final }
if (this._peek.type !== 24 /* TokenType.EXPANSION_FORM_END */) {
this.errors.push(
TreeError.create(
null,
this._peek.sourceSpan,
`Invalid ICU message. Missing '}'.`,
),
);
return;
_consumeComment(token) {
const text = this._advanceIf(7 /* TokenType.RAW_TEXT */);
const endToken = this._advanceIf(11 /* TokenType.COMMENT_END */);
const value = text != null ? text.parts[0].trim() : null;
const sourceSpan = endToken == null
? token.sourceSpan
: new ParseSourceSpan(token.sourceSpan.start, endToken.sourceSpan.end, token.sourceSpan.fullStart);
this._addToParent(new html.Comment(value, sourceSpan));
}
const sourceSpan = new ParseSourceSpan(
token.sourceSpan.start,
this._peek.sourceSpan.end,
token.sourceSpan.fullStart,
);
this._addToParent(
new html.Expansion(
switchValue.parts[0],
type.parts[0],
cases,
sourceSpan,
switchValue.sourceSpan,
),
);
this._advance();
}
_parseExpansionCase() {
const value = this._advance();
// read {
if (this._peek.type !== 22 /* TokenType.EXPANSION_CASE_EXP_START */) {
this.errors.push(
TreeError.create(
null,
this._peek.sourceSpan,
`Invalid ICU message. Missing '{'.`,
),
);
return null;
_consumeDocType(startToken) {
const text = this._advanceIf(7 /* TokenType.RAW_TEXT */);
const endToken = this._advanceIf(19 /* TokenType.DOC_TYPE_END */);
const value = text != null ? text.parts[0].trim() : null;
const sourceSpan = new ParseSourceSpan(startToken.sourceSpan.start, (endToken || text || startToken).sourceSpan.end);
this._addToParent(new html.DocType(value, sourceSpan));
}
// read until }
const start = this._advance();
const exp = this._collectExpansionExpTokens(start);
if (!exp) return null;
const end = this._advance();
exp.push({
type: 34 /* TokenType.EOF */,
parts: [],
sourceSpan: end.sourceSpan,
});
// parse everything in between { and }
const expansionCaseParser = new _TreeBuilder(
exp,
this.getTagDefinition,
this.canSelfClose,
this.allowHtmComponentClosingTags,
this.isTagNameCaseSensitive,
);
expansionCaseParser.build();
if (expansionCaseParser.errors.length > 0) {
this.errors = this.errors.concat(expansionCaseParser.errors);
return null;
_consumeExpansion(token) {
const switchValue = this._advance();
const type = this._advance();
const cases = [];
// read =
while (this._peek.type === 21 /* TokenType.EXPANSION_CASE_VALUE */) {
const expCase = this._parseExpansionCase();
if (!expCase)
return; // error
cases.push(expCase);
}
// read the final }
if (this._peek.type !== 24 /* TokenType.EXPANSION_FORM_END */) {
this.errors.push(TreeError.create(null, this._peek.sourceSpan, `Invalid ICU message. Missing '}'.`));
return;
}
const sourceSpan = new ParseSourceSpan(token.sourceSpan.start, this._peek.sourceSpan.end, token.sourceSpan.fullStart);
this._addToParent(new html.Expansion(switchValue.parts[0], type.parts[0], cases, sourceSpan, switchValue.sourceSpan));
this._advance();
}
const sourceSpan = new ParseSourceSpan(
value.sourceSpan.start,
end.sourceSpan.end,
value.sourceSpan.fullStart,
);
const expSourceSpan = new ParseSourceSpan(
start.sourceSpan.start,
end.sourceSpan.end,
start.sourceSpan.fullStart,
);
return new html.ExpansionCase(
value.parts[0],
expansionCaseParser.rootNodes,
sourceSpan,
value.sourceSpan,
expSourceSpan,
);
}
_collectExpansionExpTokens(start) {
const exp = [];
const expansionFormStack = [22 /* TokenType.EXPANSION_CASE_EXP_START */];
while (true) {
if (
this._peek.type === 20 /* TokenType.EXPANSION_FORM_START */ ||
this._peek.type === 22 /* TokenType.EXPANSION_CASE_EXP_START */
) {
expansionFormStack.push(this._peek.type);
}
if (this._peek.type === 23 /* TokenType.EXPANSION_CASE_EXP_END */) {
if (
lastOnStack(
expansionFormStack,
22 /* TokenType.EXPANSION_CASE_EXP_START */,
)
) {
expansionFormStack.pop();
if (expansionFormStack.length === 0) return exp;
} else {
this.errors.push(
TreeError.create(
null,
start.sourceSpan,
`Invalid ICU message. Missing '}'.`,
),
);
return null;
_parseExpansionCase() {
const value = this._advance();
// read {
if (this._peek.type !== 22 /* TokenType.EXPANSION_CASE_EXP_START */) {
this.errors.push(TreeError.create(null, this._peek.sourceSpan, `Invalid ICU message. Missing '{'.`));
return null;
}
}
if (this._peek.type === 24 /* TokenType.EXPANSION_FORM_END */) {
if (
lastOnStack(
expansionFormStack,
20 /* TokenType.EXPANSION_FORM_START */,
)
) {
expansionFormStack.pop();
} else {
this.errors.push(
TreeError.create(
null,
start.sourceSpan,
`Invalid ICU message. Missing '}'.`,
),
);
return null;
// read until }
const start = this._advance();
const exp = this._collectExpansionExpTokens(start);
if (!exp)
return null;
const end = this._advance();
exp.push({ type: 42 /* TokenType.EOF */, parts: [], sourceSpan: end.sourceSpan });
// parse everything in between { and }
const expansionCaseParser = new _TreeBuilder(exp, this.tagDefinitionResolver, this.canSelfClose, this.allowHtmComponentClosingTags, this.isTagNameCaseSensitive);
expansionCaseParser.build();
if (expansionCaseParser.errors.length > 0) {
this.errors = this.errors.concat(expansionCaseParser.errors);
return null;
}
}
if (this._peek.type === 34 /* TokenType.EOF */) {
this.errors.push(
TreeError.create(
null,
start.sourceSpan,
`Invalid ICU message. Missing '}'.`,
),
);
return null;
}
exp.push(this._advance());
const sourceSpan = new ParseSourceSpan(value.sourceSpan.start, end.sourceSpan.end, value.sourceSpan.fullStart);
const expSourceSpan = new ParseSourceSpan(start.sourceSpan.start, end.sourceSpan.end, start.sourceSpan.fullStart);
return new html.ExpansionCase(value.parts[0], expansionCaseParser.rootNodes, sourceSpan, value.sourceSpan, expSourceSpan);
}
}
_getText(token) {
let text = token.parts[0];
if (text.length > 0 && text[0] == "\n") {
const parent = this._getClosestParentElement();
if (
parent != null &&
parent.children.length == 0 &&
this.getTagDefinition(parent.name).ignoreFirstLf
) {
text = text.substring(1);
}
_collectExpansionExpTokens(start) {
const exp = [];
const expansionFormStack = [22 /* TokenType.EXPANSION_CASE_EXP_START */];
while (true) {
if (this._peek.type === 20 /* TokenType.EXPANSION_FORM_START */ ||
this._peek.type === 22 /* TokenType.EXPANSION_CASE_EXP_START */) {
expansionFormStack.push(this._peek.type);
}
if (this._peek.type === 23 /* TokenType.EXPANSION_CASE_EXP_END */) {
if (lastOnStack(expansionFormStack, 22 /* TokenType.EXPANSION_CASE_EXP_START */)) {
expansionFormStack.pop();
if (expansionFormStack.length === 0)
return exp;
}
else {
this.errors.push(TreeError.create(null, start.sourceSpan, `Invalid ICU message. Missing '}'.`));
return null;
}
}
if (this._peek.type === 24 /* TokenType.EXPANSION_FORM_END */) {
if (lastOnStack(expansionFormStack, 20 /* TokenType.EXPANSION_FORM_START */)) {
expansionFormStack.pop();
}
else {
this.errors.push(TreeError.create(null, start.sourceSpan, `Invalid ICU message. Missing '}'.`));
return null;
}
}
if (this._peek.type === 42 /* TokenType.EOF */) {
this.errors.push(TreeError.create(null, start.sourceSpan, `Invalid ICU message. Missing '}'.`));
return null;
}
exp.push(this._advance());
}
}
return text;
}
_consumeText(token) {
const tokens = [token];
const startSpan = token.sourceSpan;
let text = token.parts[0];
if (text.length > 0 && text[0] === "\n") {
const parent = this._getContainer();
if (
parent != null &&
parent.children.length === 0 &&
this.getTagDefinition(parent.name).ignoreFirstLf
) {
text = text.substring(1);
tokens[0] = {
type: token.type,
sourceSpan: token.sourceSpan,
parts: [text],
};
}
_getText(token) {
let text = token.parts[0];
if (text.length > 0 && text[0] == '\n') {
const parent = this._getClosestElementLikeParent();
if (parent != null && parent.children.length == 0 &&
this._getTagDefinition(parent)?.ignoreFirstLf) {
text = text.substring(1);
}
}
return text;
}
while (
this._peek.type === 8 /* TokenType.INTERPOLATION */ ||
this._peek.type === 5 /* TokenType.TEXT */ ||
this._peek.type === 9 /* TokenType.ENCODED_ENTITY */
) {
token = this._advance();
tokens.push(token);
if (token.type === 8 /* TokenType.INTERPOLATION */) {
// For backward compatibility we decode HTML entities that appear in interpolation
// expressions. This is arguably a bug, but it could be a considerable breaking change to
// fix it. It should be addressed in a larger project to refactor the entire parser/lexer
// chain after View Engine has been removed.
text += token.parts.join("").replace(/&([^;]+);/g, decodeEntity);
} else if (token.type === 9 /* TokenType.ENCODED_ENTITY */) {
text += token.parts[0];
} else {
text += token.parts.join("");
}
_consumeText(token) {
const tokens = [token];
const startSpan = token.sourceSpan;
let text = token.parts[0];
if (text.length > 0 && text[0] === '\n') {
const parent = this._getContainer();
if (parent != null &&
parent.children.length === 0 &&
this._getTagDefinition(parent)?.ignoreFirstLf) {
text = text.substring(1);
tokens[0] = { type: token.type, sourceSpan: token.sourceSpan, parts: [text] };
}
}
while (this._peek.type === 8 /* TokenType.INTERPOLATION */ ||
this._peek.type === 5 /* TokenType.TEXT */ ||
this._peek.type === 9 /* TokenType.ENCODED_ENTITY */) {
token = this._advance();
tokens.push(token);
if (token.type === 8 /* TokenType.INTERPOLATION */) {
// For backward compatibility we decode HTML entities that appear in interpolation
// expressions. This is arguably a bug, but it could be a considerable breaking change to
// fix it. It should be addressed in a larger project to refactor the entire parser/lexer
// chain after View Engine has been removed.
text += token.parts.join('').replace(/&([^;]+);/g, decodeEntity);
}
else if (token.type === 9 /* TokenType.ENCODED_ENTITY */) {
text += token.parts[0];
}
else {
text += token.parts.join('');
}
}
if (text.length > 0) {
const endSpan = token.sourceSpan;
this._addToParent(new html.Text(text, new ParseSourceSpan(startSpan.start, endSpan.end, startSpan.fullStart, startSpan.details), tokens));
}
}
if (text.length > 0) {
const endSpan = token.sourceSpan;
this._addToParent(
new html.Text(
text,
new ParseSourceSpan(
startSpan.start,
endSpan.end,
startSpan.fullStart,
startSpan.details,
),
tokens,
),
);
_closeVoidElement() {
const el = this._getContainer();
if (el !== null && this._getTagDefinition(el)?.isVoid) {
this._containerStack.pop();
}
}
}
_closeVoidElement() {
const el = this._getContainer();
if (el instanceof html.Element && this.getTagDefinition(el.name).isVoid) {
this._containerStack.pop();
_consumeElementStartTag(startTagToken) {
const attrs = [];
const directives = [];
this._consumeAttributesAndDirectives(attrs, directives);
const fullName = this._getElementFullName(startTagToken, this._getClosestElementLikeParent());
let selfClosing = false;
// Note: There could have been a tokenizer error
// so that we don't get a token for the end tag...
if (this._peek.type === 2 /* TokenType.TAG_OPEN_END_VOID */) {
this._advance();
selfClosing = true;
const tagDef = this._getTagDefinition(fullName);
if (!(this.canSelfClose || tagDef?.canSelfClose || getNsPrefix(fullName) !== null || tagDef?.isVoid)) {
this.errors.push(TreeError.create(fullName, startTagToken.sourceSpan, `Only void, custom and foreign elements can be self closed "${startTagToken.parts[1]}"`));
}
}
else if (this._peek.type === 1 /* TokenType.TAG_OPEN_END */) {
this._advance();
selfClosing = false;
}
const end = this._peek.sourceSpan.fullStart;
const span = new ParseSourceSpan(startTagToken.sourceSpan.start, end, startTagToken.sourceSpan.fullStart);
// Create a separate `startSpan` because `span` will be modified when there is an `end` span.
const startSpan = new ParseSourceSpan(startTagToken.sourceSpan.start, end, startTagToken.sourceSpan.fullStart);
const nameSpan = new ParseSourceSpan(startTagToken.sourceSpan.start.moveBy(1), startTagToken.sourceSpan.end);
const el = new html.Element(fullName, attrs, directives, [], span, startSpan, undefined, nameSpan);
const parent = this._getContainer();
const isClosedByChild = parent !== null && !!this._getTagDefinition(parent)?.isClosedByChild(el.name);
this._pushContainer(el, isClosedByChild);
if (selfClosing) {
// Elements that are self-closed have their `endSourceSpan` set to the full span, as the
// element start tag also represents the end tag.
this._popContainer(fullName, html.Element, span);
}
else if (startTagToken.type === 4 /* TokenType.INCOMPLETE_TAG_OPEN */) {
// We already know the opening tag is not complete, so it is unlikely it has a corresponding
// close tag. Let's optimistically parse it as a full element and emit an error.
this._popContainer(fullName, html.Element, null);
this.errors.push(TreeError.create(fullName, span, `Opening tag "${fullName}" not terminated.`));
}
}
}
_consumeStartTag(startTagToken) {
const [prefix, name] = startTagToken.parts;
const attrs = [];
while (this._peek.type === 14 /* TokenType.ATTR_NAME */) {
attrs.push(this._consumeAttr(this._advance()));
_consumeComponentStartTag(startToken) {
const componentName = startToken.parts[0];
const attrs = [];
const directives = [];
this._consumeAttributesAndDirectives(attrs, directives);
const closestElement = this._getClosestElementLikeParent();
const tagName = this._getComponentTagName(startToken, closestElement);
const fullName = this._getComponentFullName(startToken, closestElement);
const selfClosing = this._peek.type === 36 /* TokenType.COMPONENT_OPEN_END_VOID */;
this._advance();
const end = this._peek.sourceSpan.fullStart;
const span = new ParseSourceSpan(startToken.sourceSpan.start, end, startToken.sourceSpan.fullStart);
const startSpan = new ParseSourceSpan(startToken.sourceSpan.start, end, startToken.sourceSpan.fullStart);
const node = new html.Component(componentName, tagName, fullName, attrs, directives, [], span, startSpan, undefined);
const parent = this._getContainer();
const isClosedByChild = parent !== null &&
node.tagName !== null &&
!!this._getTagDefinition(parent)?.isClosedByChild(node.tagName);
this._pushContainer(node, isClosedByChild);
if (selfClosing) {
this._popContainer(fullName, html.Component, span);
}
else if (startToken.type === 38 /* TokenType.INCOMPLETE_COMPONENT_OPEN */) {
this._popContainer(fullName, html.Component, null);
this.errors.push(TreeError.create(fullName, span, `Opening tag "${fullName}" not terminated.`));
}
}
const fullName = this._getElementFullName(
prefix,
name,
this._getClosestParentElement(),
);
console.log({ prefix, name, fullName });
let selfClosing = false;
// Note: There could have been a tokenizer error
// so that we don't get a token for the end tag...
if (this._peek.type === 2 /* TokenType.TAG_OPEN_END_VOID */) {
this._advance();
selfClosing = true;
const tagDef = this.getTagDefinition(fullName);
if (
!(
this.canSelfClose ||
tagDef.canSelfClose ||
getNsPrefix(fullName) !== null ||
tagDef.isVoid
)
) {
this.errors.push(
TreeError.create(
fullName,
startTagToken.sourceSpan,
`Only void, custom and foreign elements can be self closed "${startTagToken.parts[1]}"`,
),
);
}
} else if (this._peek.type === 1 /* TokenType.TAG_OPEN_END */) {
this._advance();
selfClosing = false;
_consumeAttributesAndDirectives(attributesResult, directivesResult) {
while (this._peek.type === 14 /* TokenType.ATTR_NAME */ ||
this._peek.type === 39 /* TokenType.DIRECTIVE_NAME */) {
if (this._peek.type === 39 /* TokenType.DIRECTIVE_NAME */) {
directivesResult.push(this._consumeDirective(this._peek));
}
else {
attributesResult.push(this._consumeAttr(this._advance()));
}
}
}
const end = this._peek.sourceSpan.fullStart;
const span = new ParseSourceSpan(
startTagToken.sourceSpan.start,
end,
startTagToken.sourceSpan.fullStart,
);
// Create a separate `startSpan` because `span` will be modified when there is an `end` span.
const startSpan = new ParseSourceSpan(
startTagToken.sourceSpan.start,
end,
startTagToken.sourceSpan.fullStart,
);
const nameSpan = new ParseSourceSpan(
startTagToken.sourceSpan.start.moveBy(1),
startTagToken.sourceSpan.end,
);
const el = new html.Element(
fullName,
attrs,
[],
span,
startSpan,
undefined,
nameSpan,
);
const parentEl = this._getContainer();
this._pushContainer(
el,
parentEl instanceof html.Element &&
this.getTagDefinition(parentEl.name).isClosedByChild(el.name),
);
if (selfClosing) {
// Elements that are self-closed have their `endSourceSpan` set to the full span, as the
// element start tag also represents the end tag.
this._popContainer(fullName, html.Element, span);
} else if (startTagToken.type === 4 /* TokenType.INCOMPLETE_TAG_OPEN */) {
// We already know the opening tag is not complete, so it is unlikely it has a corresponding
// close tag. Let's optimistically parse it as a full element and emit an error.
this._popContainer(fullName, html.Element, null);
this.errors.push(
TreeError.create(
fullName,
span,
`Opening tag "${fullName}" not terminated.`,
),
);
_consumeComponentEndTag(endToken) {
const fullName = this._getComponentFullName(endToken, this._getClosestElementLikeParent());
if (!this._popContainer(fullName, html.Component, endToken.sourceSpan)) {
const container = this._containerStack[this._containerStack.length - 1];
let suffix;
if (container instanceof html.Component && container.componentName === endToken.parts[0]) {
suffix = `, did you mean "${container.fullName}"?`;
}
else {
suffix = '. It may happen when the tag has already been closed by another tag.';
}
const errMsg = `Unexpected closing tag "${fullName}"${suffix}`;
this.errors.push(TreeError.create(fullName, endToken.sourceSpan, errMsg));
}
}
}
_pushContainer(node, isClosedByChild) {
if (isClosedByChild) {
this._containerStack.pop();
_getTagDefinition(nodeOrName) {
if (typeof nodeOrName === 'string') {
return this.tagDefinitionResolver(nodeOrName);
}
else if (nodeOrName instanceof html.Element) {
return this.tagDefinitionResolver(nodeOrName.name);
}
else if (nodeOrName instanceof html.Component && nodeOrName.tagName !== null) {
return this.tagDefinitionResolver(nodeOrName.tagName);
}
else {
return null;
}
}
this._addToParent(node);
this._containerStack.push(node);
}
_consumeEndTag(endTagToken) {
// @ts-expect-error -- in angular-html-parser endTagToken.parts.length can be 0 (HTM component
// end-tags)
const fullName =
this.allowHtmComponentClosingTags && endTagToken.parts.length === 0
? null
: this._getElementFullName(
endTagToken.parts[0],
endTagToken.parts[1],
this._getClosestParentElement(),
);
if (fullName && this.getTagDefinition(fullName).isVoid) {
this.errors.push(
TreeError.create(
fullName,
endTagToken.sourceSpan,
`Void elements do not have end tags "${endTagToken.parts[1]}"`,
),
);
} else if (
!this._popContainer(fullName, html.Element, endTagToken.sourceSpan)
) {
const errMsg = `Unexpected closing tag "${fullName}". It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR/html5/syntax.html#closing-elements-that-have-implied-end-tags`;
this.errors.push(
TreeError.create(fullName, endTagToken.sourceSpan, errMsg),
);
_pushContainer(node, isClosedByChild) {
if (isClosedByChild) {
this._containerStack.pop();
}
this._addToParent(node);
this._containerStack.push(node);
}
}
/**
* Closes the nearest element with the tag name `fullName` in the parse tree.
* `endSourceSpan` is the span of the closing tag, or null if the element does
* not have a closing tag (for example, this happens when an incomplete
* opening tag is recovered).
*/
_popContainer(expectedName, expectedType, endSourceSpan) {
let unexpectedCloseTagDetected = false;
for (
let stackIndex = this._containerStack.length - 1;
stackIndex >= 0;
stackIndex--
) {
const node = this._containerStack[stackIndex];
if (
/* isForeignElement */ getNsPrefix(node.name)
? node.name === expectedName
: (expectedName == null ||
node.name.toLowerCase() === expectedName.toLowerCase()) &&
node instanceof expectedType
) {
// Record the parse span with the element that is being closed. Any elements that are
// removed from the element stack at this point are closed implicitly, so they won't get
// an end source span (as there is no explicit closing element).
node.endSourceSpan = endSourceSpan;
node.sourceSpan.end =
endSourceSpan !== null ? endSourceSpan.end : node.sourceSpan.end;
this._containerStack.splice(
stackIndex,
this._containerStack.length - stackIndex,
);
return !unexpectedCloseTagDetected;
}
// Blocks and most elements are not self closing.
if (
node instanceof html.Block ||
(node instanceof html.Element &&
!this.getTagDefinition(node.name).closedByParent)
) {
// Note that we encountered an unexpected close tag but continue processing the element
// stack so we can assign an `endSourceSpan` if there is a corresponding start tag for this
// end tag in the stack.
unexpectedCloseTagDetected = true;
}
_consumeElementEndTag(endTagToken) {
// @ts-expect-error -- in angular-html-parser endTagToken.parts.length can be 0 (HTM component end-tags)
const fullName = this.allowHtmComponentClosingTags && endTagToken.parts.length === 0 ?
null :
this._getElementFullName(endTagToken, this._getClosestElementLikeParent());
;
if (fullName && this._getTagDefinition(fullName)?.isVoid) {
this.errors.push(TreeError.create(fullName, endTagToken.sourceSpan, `Void elements do not have end tags "${endTagToken.parts[1]}"`));
}
else if (!this._popContainer(fullName, html.Element, endTagToken.sourceSpan)) {
const errMsg = `Unexpected closing tag "${fullName}". It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR/html5/syntax.html#closing-elements-that-have-implied-end-tags`;
this.errors.push(TreeError.create(fullName, endTagToken.sourceSpan, errMsg));
}
}
return false;
}
_consumeAttr(attrName) {
const fullName = mergeNsAndName(attrName.parts[0], attrName.parts[1]);
let attrEnd = attrName.sourceSpan.end;
let startQuoteToken;
// Consume any quote
if (this._peek.type === 15 /* TokenType.ATTR_QUOTE */) {
startQuoteToken = this._advance();
/**
* Closes the nearest element with the tag name `fullName` in the parse tree.
* `endSourceSpan` is the span of the closing tag, or null if the element does
* not have a closing tag (for example, this happens when an incomplete
* opening tag is recovered).
*/
_popContainer(expectedName, expectedType, endSourceSpan) {
let unexpectedCloseTagDetected = false;
for (let stackIndex = this._containerStack.length - 1; stackIndex >= 0; stackIndex--) {
const node = this._containerStack[stackIndex];
const nodeName = node instanceof html.Component ? node.fullName : node.name;
if (
/* isForeignElement */ getNsPrefix(nodeName) ?
nodeName === expectedName :
(nodeName === expectedName || expectedName === null) && node instanceof expectedType) {
// Record the parse span with the element that is being closed. Any elements that are
// removed from the element stack at this point are closed implicitly, so they won't get
// an end source span (as there is no explicit closing element).
node.endSourceSpan = endSourceSpan;
node.sourceSpan.end = endSourceSpan !== null ? endSourceSpan.end : node.sourceSpan.end;
this._containerStack.splice(stackIndex, this._containerStack.length - stackIndex);
return !unexpectedCloseTagDetected;
}
// Blocks and most elements are not self closing.
if (node instanceof html.Block || !this._getTagDefinition(node)?.closedByParent) {
// Note that we encountered an unexpected close tag but continue processing the element
// stack so we can assign an `endSourceSpan` if there is a corresponding start tag for this
// end tag in the stack.
unexpectedCloseTagDetected = true;
}
}
return false;
}
// Consume the attribute value
let value = "";
const valueTokens = [];
let valueStartSpan = undefined;
let valueEnd = undefined;
// NOTE: We need to use a new variable `nextTokenType` here to hide the actual type of
// `_peek.type` from TS. Otherwise TS will narrow the type of `_peek.type` preventing it from
// being able to consider `ATTR_VALUE_INTERPOLATION` as an option. This is because TS is not
// able to see that `_advance()` will actually mutate `_peek`.
const nextTokenType = this._peek.type;
if (nextTokenType === 16 /* TokenType.ATTR_VALUE_TEXT */) {
valueStartSpan = this._peek.sourceSpan;
valueEnd = this._peek.sourceSpan.end;
while (
this._peek.type === 16 /* TokenType.ATTR_VALUE_TEXT */ ||
this._peek.type === 17 /* TokenType.ATTR_VALUE_INTERPOLATION */ ||
this._peek.type === 9 /* TokenType.ENCODED_ENTITY */
) {
const valueToken = this._advance();
valueTokens.push(valueToken);
if (valueToken.type === 17 /* TokenType.ATTR_VALUE_INTERPOLATION */) {
// For backward compatibility we decode HTML entities that appear in interpolation
// expressions. This is arguably a bug, but it could be a considerable breaking change to
// fix it. It should be addressed in a larger project to refactor the entire parser/lexer
// chain after View Engine has been removed.
value += valueToken.parts
.join("")
.replace(/&([^;]+);/g, decodeEntity);
} else if (valueToken.type === 9 /* TokenType.ENCODED_ENTITY */) {
value += valueToken.parts[0];
} else {
value += valueToken.parts.join("");
_consumeAttr(attrName) {
const fullName = mergeNsAndName(attrName.parts[0], attrName.parts[1]);
let attrEnd = attrName.sourceSpan.end;
let startQuoteToken;
// Consume any quote
if (this._peek.type === 15 /* TokenType.ATTR_QUOTE */) {
startQuoteToken = this._advance();
}
valueEnd = attrEnd = valueToken.sourceSpan.end;
}
// Consume the attribute value
let value = '';
const valueTokens = [];
let valueStartSpan = undefined;
let valueEnd = undefined;
// NOTE: We need to use a new variable `nextTokenType` here to hide the actual type of
// `_peek.type` from TS. Otherwise TS will narrow the type of `_peek.type` preventing it from
// being able to consider `ATTR_VALUE_INTERPOLATION` as an option. This is because TS is not
// able to see that `_advance()` will actually mutate `_peek`.
const nextTokenType = this._peek.type;
if (nextTokenType === 16 /* TokenType.ATTR_VALUE_TEXT */) {
valueStartSpan = this._peek.sourceSpan;
valueEnd = this._peek.sourceSpan.end;
while (this._peek.type === 16 /* TokenType.ATTR_VALUE_TEXT */ ||
this._peek.type === 17 /* TokenType.ATTR_VALUE_INTERPOLATION */ ||
this._peek.type === 9 /* TokenType.ENCODED_ENTITY */) {
const valueToken = this._advance();
valueTokens.push(valueToken);
if (valueToken.type === 17 /* TokenType.ATTR_VALUE_INTERPOLATION */) {
// For backward compatibility we decode HTML entities that appear in interpolation
// expressions. This is arguably a bug, but it could be a considerable breaking change to
// fix it. It should be addressed in a larger project to refactor the entire parser/lexer
// chain after View Engine has been removed.
value += valueToken.parts.join('').replace(/&([^;]+);/g, decodeEntity);
}
else if (valueToken.type === 9 /* TokenType.ENCODED_ENTITY */) {
value += valueToken.parts[0];
}
else {
value += valueToken.parts.join('');
}
valueEnd = attrEnd = valueToken.sourceSpan.end;
}
}
// Consume any quote
if (this._peek.type === 15 /* TokenType.ATTR_QUOTE */) {
const quoteToken = this._advance();
valueEnd = attrEnd = quoteToken.sourceSpan.end;
}
const valueSpan = valueStartSpan &&
valueEnd &&
new ParseSourceSpan(startQuoteToken?.sourceSpan.start ?? valueStartSpan.start, valueEnd, startQuoteToken?.sourceSpan.fullStart ?? valueStartSpan.fullStart);
return new html.Attribute(fullName, value, new ParseSourceSpan(attrName.sourceSpan.start, attrEnd, attrName.sourceSpan.fullStart), attrName.sourceSpan, valueSpan, valueTokens.length > 0 ? valueTokens : undefined, undefined);
}
// Consume any quote
if (this._peek.type === 15 /* TokenType.ATTR_QUOTE */) {
const quoteToken = this._advance();
valueEnd = attrEnd = quoteToken.sourceSpan.end;
_consumeDirective(nameToken) {
const attributes = [];
let startSourceSpanEnd = nameToken.sourceSpan.end;
let endSourceSpan = null;
this._advance();
if (this._peek.type === 40 /* TokenType.DIRECTIVE_OPEN */) {
// Capture the opening token in the start span.
startSourceSpanEnd = this._peek.sourceSpan.end;
this._advance();
// Cast here is necessary, because TS doesn't know that `_advance` changed `_peek`.
while (this._peek.type === 14 /* TokenType.ATTR_NAME */) {
attributes.push(this._consumeAttr(this._advance()));
}
if (this._peek.type === 41 /* TokenType.DIRECTIVE_CLOSE */) {
endSourceSpan = this._peek.sourceSpan;
this._advance();
}
else {
this.errors.push(TreeError.create(null, nameToken.sourceSpan, 'Unterminated directive definition'));
}
}
const startSourceSpan = new ParseSourceSpan(nameToken.sourceSpan.start, startSourceSpanEnd, nameToken.sourceSpan.fullStart);
const sourceSpan = new ParseSourceSpan(startSourceSpan.start, endSourceSpan === null ? nameToken.sourceSpan.end : endSourceSpan.end, startSourceSpan.fullStart);
return new html.Directive(nameToken.parts[0], attributes, sourceSpan, startSourceSpan, endSourceSpan);
}
const valueSpan =
valueStartSpan &&
valueEnd &&
new ParseSourceSpan(
startQuoteToken?.sourceSpan.start ?? valueStartSpan.start,
valueEnd,
startQuoteToken?.sourceSpan.fullStart ?? valueStartSpan.fullStart,
);
return new html.Attribute(
fullName,
value,
new ParseSourceSpan(
attrName.sourceSpan.start,
attrEnd,
attrName.sourceSpan.fullStart,
),
attrName.sourceSpan,
valueSpan,
valueTokens.length > 0 ? valueTokens : undefined,
undefined,
);
}
_consumeBlockOpen(token) {
const parameters = [];
while (this._peek.type === 28 /* TokenType.BLOCK_PARAMETER */) {
const paramToken = this._advance();
parameters.push(
new html.BlockParameter(paramToken.parts[0], paramToken.sourceSpan),
);
_consumeBlockOpen(token) {
const parameters = [];
while (this._peek.type === 28 /* TokenType.BLOCK_PARAMETER */) {
const paramToken = this._advance();
parameters.push(new html.BlockParameter(paramToken.parts[0], paramToken.sourceSpan));
}
if (this._peek.type === 26 /* TokenType.BLOCK_OPEN_END */) {
this._advance();
}
const end = this._peek.sourceSpan.fullStart;
const span = new ParseSourceSpan(token.sourceSpan.start, end, token.sourceSpan.fullStart);
// Create a separate `startSpan` because `span` will be modified when there is an `end` span.
const startSpan = new ParseSourceSpan(token.sourceSpan.start, end, token.sourceSpan.fullStart);
const block = new html.Block(token.parts[0], parameters, [], span, token.sourceSpan, startSpan);
this._pushContainer(block, false);
}
if (this._peek.type === 26 /* TokenType.BLOCK_OPEN_END */) {
this._advance();
_consumeBlockClose(token) {
if (!this._popContainer(null, html.Block, token.sourceSpan)) {
this.errors.push(TreeError.create(null, token.sourceSpan, `Unexpected closing block. The block may have been closed earlier. ` +
`If you meant to write the } character, you should use the "&#125;" ` +
`HTML entity instead.`));
}
}
const end = this._peek.sourceSpan.fullStart;
const span = new ParseSourceSpan(
token.sourceSpan.start,
end,
token.sourceSpan.fullStart,
);
// Create a separate `startSpan` because `span` will be modified when there is an `end` span.
const startSpan = new ParseSourceSpan(
token.sourceSpan.start,
end,
token.sourceSpan.fullStart,
);
const block = new html.Block(
token.parts[0],
parameters,
[],
span,
token.sourceSpan,
startSpan,
);
this._pushContainer(block, false);
}
_consumeBlockClose(token) {
if (!this._popContainer(null, html.Block, token.sourceSpan)) {
this.errors.push(
TreeError.create(
null,
token.sourceSpan,
`Unexpected closing block. The block may have been closed earlier. ` +
`If you meant to write the } character, you should use the "&#125;" ` +
`HTML entity instead.`,
),
);
_consumeIncompleteBlock(token) {
const parameters = [];
while (this._peek.type === 28 /* TokenType.BLOCK_PARAMETER */) {
const paramToken = this._advance();
parameters.push(new html.BlockParameter(paramToken.parts[0], paramToken.sourceSpan));
}
const end = this._peek.sourceSpan.fullStart;
const span = new ParseSourceSpan(token.sourceSpan.start, end, token.sourceSpan.fullStart);
// Create a separate `startSpan` because `span` will be modified when there is an `end` span.
const startSpan = new ParseSourceSpan(token.sourceSpan.start, end, token.sourceSpan.fullStart);
const block = new html.Block(token.parts[0], parameters, [], span, token.sourceSpan, startSpan);
this._pushContainer(block, false);
// Incomplete blocks don't have children so we close them immediately and report an error.
this._popContainer(null, html.Block, null);
this.errors.push(TreeError.create(token.parts[0], span, `Incomplete block "${token.parts[0]}". If you meant to write the @ character, ` +
`you should use the "&#64;" HTML entity instead.`));
}
}
_consumeIncompleteBlock(token) {
const parameters = [];
while (this._peek.type === 28 /* TokenType.BLOCK_PARAMETER */) {
const paramToken = this._advance();
parameters.push(
new html.BlockParameter(paramToken.parts[0], paramToken.sourceSpan),
);
_consumeLet(startToken) {
const name = startToken.parts[0];
let valueToken;
let endToken;
if (this._peek.type !== 31 /* TokenType.LET_VALUE */) {
this.errors.push(TreeError.create(startToken.parts[0], startToken.sourceSpan, `Invalid @let declaration "${name}". Declaration must have a value.`));
return;
}
else {
valueToken = this._advance();
}
// Type cast is necessary here since TS narrowed the type of `peek` above.
if (this._peek.type !== 32 /* TokenType.LET_END */) {
this.errors.push(TreeError.create(startToken.parts[0], startToken.sourceSpan, `Unterminated @let declaration "${name}". Declaration must be terminated with a semicolon.`));
return;
}
else {
endToken = this._advance();
}
const end = endToken.sourceSpan.fullStart;
const span = new ParseSourceSpan(startToken.sourceSpan.start, end, startToken.sourceSpan.fullStart);
// The start token usually captures the `@let`. Construct a name span by
// offsetting the start by the length of any text before the name.
const startOffset = startToken.sourceSpan.toString().lastIndexOf(name);
const nameStart = startToken.sourceSpan.start.moveBy(startOffset);
const nameSpan = new ParseSourceSpan(nameStart, startToken.sourceSpan.end);
const node = new html.LetDeclaration(name, valueToken.parts[0], span, nameSpan, valueToken.sourceSpan);
this._addToParent(node);
}
const end = this._peek.sourceSpan.fullStart;
const span = new ParseSourceSpan(
token.sourceSpan.start,
end,
token.sourceSpan.fullStart,
);
// Create a separate `startSpan` because `span` will be modified when there is an `end` span.
const startSpan = new ParseSourceSpan(
token.sourceSpan.start,
end,
token.sourceSpan.fullStart,
);
const block = new html.Block(
token.parts[0],
parameters,
[],
span,
token.sourceSpan,
startSpan,
);
this._pushContainer(block, false);
// Incomplete blocks don't have children so we close them immediately and report an error.
this._popContainer(null, html.Block, null);
this.errors.push(
TreeError.create(
token.parts[0],
span,
`Incomplete block "${token.parts[0]}". If you meant to write the @ character, ` +
`you should use the "&#64;" HTML entity instead.`,
),
);
}
_consumeLet(startToken) {
const name = startToken.parts[0];
let valueToken;
let endToken;
if (this._peek.type !== 31 /* TokenType.LET_VALUE */) {
this.errors.push(
TreeError.create(
startToken.parts[0],
startToken.sourceSpan,
`Invalid @let declaration "${name}". Declaration must have a value.`,
),
);
return;
} else {
valueToken = this._advance();
_consumeIncompleteLet(token) {
// Incomplete `@let` declaration may end up with an empty name.
const name = token.parts[0] ?? '';
const nameString = name ? ` "${name}"` : '';
// If there's at least a name, we can salvage an AST node that can be used for completions.
if (name.length > 0) {
const startOffset = token.sourceSpan.toString().lastIndexOf(name);
const nameStart = token.sourceSpan.start.moveBy(startOffset);
const nameSpan = new ParseSourceSpan(nameStart, token.sourceSpan.end);
const valueSpan = new ParseSourceSpan(token.sourceSpan.start, token.sourceSpan.start.moveBy(0));
const node = new html.LetDeclaration(name, '', token.sourceSpan, nameSpan, valueSpan);
this._addToParent(node);
}
this.errors.push(TreeError.create(token.parts[0], token.sourceSpan, `Incomplete @let declaration${nameString}. ` +
`@let declarations must be written as \`@let <name> = <value>;\``));
}
// Type cast is necessary here since TS narrowed the type of `peek` above.
if (this._peek.type !== 32 /* TokenType.LET_END */) {
this.errors.push(
TreeError.create(
startToken.parts[0],
startToken.sourceSpan,
`Unterminated @let declaration "${name}". Declaration must be terminated with a semicolon.`,
),
);
return;
} else {
endToken = this._advance();
_getContainer() {
return this._containerStack.length > 0
? this._containerStack[this._containerStack.length - 1]
: null;
}
const end = endToken.sourceSpan.fullStart;
const span = new ParseSourceSpan(
startToken.sourceSpan.start,
end,
startToken.sourceSpan.fullStart,
);
// The start token usually captures the `@let`. Construct a name span by
// offsetting the start by the length of any text before the name.
const startOffset = startToken.sourceSpan.toString().lastIndexOf(name);
const nameStart = startToken.sourceSpan.start.moveBy(startOffset);
const nameSpan = new ParseSourceSpan(nameStart, startToken.sourceSpan.end);
const node = new html.LetDeclaration(
name,
valueToken.parts[0],
span,
nameSpan,
valueToken.sourceSpan,
);
this._addToParent(node);
}
_consumeIncompleteLet(token) {
// Incomplete `@let` declaration may end up with an empty name.
const name = token.parts[0] ?? "";
const nameString = name ? ` "${name}"` : "";
// If there's at least a name, we can salvage an AST node that can be used for completions.
if (name.length > 0) {
const startOffset = token.sourceSpan.toString().lastIndexOf(name);
const nameStart = token.sourceSpan.start.moveBy(startOffset);
const nameSpan = new ParseSourceSpan(nameStart, token.sourceSpan.end);
const valueSpan = new ParseSourceSpan(
token.sourceSpan.start,
token.sourceSpan.start.moveBy(0),
);
const node = new html.LetDeclaration(
name,
"",
token.sourceSpan,
nameSpan,
valueSpan,
);
this._addToParent(node);
_getClosestElementLikeParent() {
for (let i = this._containerStack.length - 1; i > -1; i--) {
const current = this._containerStack[i];
if (current instanceof html.Element || current instanceof html.Component) {
return current;
}
}
return null;
}
this.errors.push(
TreeError.create(
token.parts[0],
token.sourceSpan,
`Incomplete @let declaration${nameString}. ` +
`@let declarations must be written as \`@let <name> = <value>;\``,
),
);
}
_getContainer() {
return this._containerStack.length > 0
? this._containerStack[this._containerStack.length - 1]
: null;
}
_getClosestParentElement() {
for (let i = this._containerStack.length - 1; i > -1; i--) {
if (this._containerStack[i] instanceof html.Element) {
return this._containerStack[i];
}
_addToParent(node) {
const parent = this._getContainer();
if (parent === null) {
this.rootNodes.push(node);
}
else {
parent.children.push(node);
}
}
return null;
}
_addToParent(node) {
const parent = this._getContainer();
if (parent === null) {
this.rootNodes.push(node);
} else {
parent.children.push(node);
_getElementFullName(token, parent) {
const prefix = this._getPrefix(token, parent);
return mergeNsAndName(prefix, token.parts[1]);
}
}
_getElementFullName(prefix, localName, parentElement) {
if (prefix === "") {
prefix = this.getTagDefinition(localName).implicitNamespacePrefix || "";
if (prefix === "" && parentElement != null) {
const parentTagName = splitNsName(parentElement.name)[1];
const parentTagDefinition = this.getTagDefinition(parentTagName);
if (!parentTagDefinition.preventNamespaceInheritance) {
prefix = getNsPrefix(parentElement.name);
_getComponentFullName(token, parent) {
const componentName = token.parts[0];
const tagName = this._getComponentTagName(token, parent);
if (tagName === null) {
return componentName;
}
}
return tagName.startsWith(':') ? componentName + tagName : `${componentName}:${tagName}`;
}
return mergeNsAndName(prefix, localName);
}
_getComponentTagName(token, parent) {
const prefix = this._getPrefix(token, parent);
const tagName = token.parts[2];
if (!prefix && !tagName) {
return null;
}
else if (!prefix && tagName) {
return tagName;
}
else {
// TODO(crisbeto): re-evaluate this fallback. Maybe base it off the class name?
return mergeNsAndName(prefix, tagName || 'ng-component');
}
}
_getPrefix(token, parent) {
let prefix;
let tagName;
if (token.type === 34 /* TokenType.COMPONENT_OPEN_START */ ||
token.type === 38 /* TokenType.INCOMPLETE_COMPONENT_OPEN */ ||
token.type === 37 /* TokenType.COMPONENT_CLOSE */) {
prefix = token.parts[1];
tagName = token.parts[2];
}
else {
prefix = token.parts[0];
tagName = token.parts[1];
}
prefix = prefix || this._getTagDefinition(tagName)?.implicitNamespacePrefix || '';
if (!prefix && parent) {
const parentName = parent instanceof html.Element ? parent.name : parent.tagName;
if (parentName !== null) {
const parentTagName = splitNsName(parentName)[1];
const parentTagDefinition = this._getTagDefinition(parentTagName);
if (parentTagDefinition !== null && !parentTagDefinition.preventNamespaceInheritance) {
prefix = getNsPrefix(parentName);
}
}
}
return prefix;
}
}
function lastOnStack(stack, element) {
return stack.length > 0 && stack[stack.length - 1] === element;
return stack.length > 0 && stack[stack.length - 1] === element;
}

@@ -893,12 +714,12 @@ /**

function decodeEntity(match, entity) {
if (NAMED_ENTITIES[entity] !== undefined) {
return NAMED_ENTITIES[entity] || match;
}
if (/^#x[a-f0-9]+$/i.test(entity)) {
return String.fromCodePoint(parseInt(entity.slice(2), 16));
}
if (/^#\d+$/.test(entity)) {
return String.fromCodePoint(parseInt(entity.slice(1), 10));
}
return match;
if (NAMED_ENTITIES[entity] !== undefined) {
return NAMED_ENTITIES[entity] || match;
}
if (/^#x[a-f0-9]+$/i.test(entity)) {
return String.fromCodePoint(parseInt(entity.slice(2), 16));
}
if (/^#\d+$/.test(entity)) {
return String.fromCodePoint(parseInt(entity.slice(1), 10));
}
return match;
}

@@ -44,5 +44,13 @@ /**

INCOMPLETE_LET = 33,
EOF = 34
COMPONENT_OPEN_START = 34,
COMPONENT_OPEN_END = 35,
COMPONENT_OPEN_END_VOID = 36,
COMPONENT_CLOSE = 37,
INCOMPLETE_COMPONENT_OPEN = 38,
DIRECTIVE_NAME = 39,
DIRECTIVE_OPEN = 40,
DIRECTIVE_CLOSE = 41,
EOF = 42
}
export type Token = TagOpenStartToken | TagOpenEndToken | TagOpenEndVoidToken | TagCloseToken | IncompleteTagOpenToken | TextToken | InterpolationToken | EncodedEntityToken | CommentStartToken | CommentEndToken | CdataStartToken | CdataEndToken | AttributeNameToken | AttributeQuoteToken | AttributeValueTextToken | AttributeValueInterpolationToken | ExpansionFormStartToken | ExpansionCaseValueToken | ExpansionCaseExpressionStartToken | ExpansionCaseExpressionEndToken | ExpansionFormEndToken | EndOfFileToken | BlockParameterToken | BlockOpenStartToken | BlockOpenEndToken | BlockCloseToken | IncompleteBlockOpenToken | LetStartToken | LetValueToken | LetEndToken | IncompleteLetToken | DocTypeStartToken;
export type Token = TagOpenStartToken | TagOpenEndToken | TagOpenEndVoidToken | TagCloseToken | IncompleteTagOpenToken | TextToken | InterpolationToken | EncodedEntityToken | CommentStartToken | CommentEndToken | CdataStartToken | CdataEndToken | AttributeNameToken | AttributeQuoteToken | AttributeValueTextToken | AttributeValueInterpolationToken | DocTypeStartToken | ExpansionFormStartToken | ExpansionCaseValueToken | ExpansionCaseExpressionStartToken | ExpansionCaseExpressionEndToken | ExpansionFormEndToken | EndOfFileToken | BlockParameterToken | BlockOpenStartToken | BlockOpenEndToken | BlockCloseToken | IncompleteBlockOpenToken | LetStartToken | LetValueToken | LetEndToken | IncompleteLetToken | ComponentOpenStartToken | ComponentOpenEndToken | ComponentOpenEndVoidToken | ComponentCloseToken | IncompleteComponentOpenToken | DirectiveNameToken | DirectiveOpenToken | DirectiveCloseToken;
export type InterpolatedTextToken = TextToken | InterpolationToken | EncodedEntityToken;

@@ -187,1 +195,33 @@ export type InterpolatedAttributeToken = AttributeValueTextToken | AttributeValueInterpolationToken | EncodedEntityToken;

}
export interface ComponentOpenStartToken extends TokenBase {
type: TokenType.COMPONENT_OPEN_START;
parts: [componentName: string, prefix: string, tagName: string];
}
export interface ComponentOpenEndToken extends TokenBase {
type: TokenType.COMPONENT_OPEN_END;
parts: [];
}
export interface ComponentOpenEndVoidToken extends TokenBase {
type: TokenType.COMPONENT_OPEN_END_VOID;
parts: [];
}
export interface ComponentCloseToken extends TokenBase {
type: TokenType.COMPONENT_CLOSE;
parts: [componentName: string, prefix: string, tagName: string];
}
export interface IncompleteComponentOpenToken extends TokenBase {
type: TokenType.INCOMPLETE_COMPONENT_OPEN;
parts: [componentName: string, prefix: string, tagName: string];
}
export interface DirectiveNameToken extends TokenBase {
type: TokenType.DIRECTIVE_NAME;
parts: [name: string];
}
export interface DirectiveOpenToken extends TokenBase {
type: TokenType.DIRECTIVE_OPEN;
parts: [];
}
export interface DirectiveCloseToken extends TokenBase {
type: TokenType.DIRECTIVE_CLOSE;
parts: [];
}

@@ -76,3 +76,3 @@ /**

'[HTMLElement]^[Element]|accessKey,autocapitalize,!autofocus,contentEditable,dir,!draggable,enterKeyHint,!hidden,!inert,innerText,inputMode,lang,nonce,*abort,*animationend,*animationiteration,*animationstart,*auxclick,*beforexrselect,*blur,*cancel,*canplay,*canplaythrough,*change,*click,*close,*contextmenu,*copy,*cuechange,*cut,*dblclick,*drag,*dragend,*dragenter,*dragleave,*dragover,*dragstart,*drop,*durationchange,*emptied,*ended,*error,*focus,*formdata,*gotpointercapture,*input,*invalid,*keydown,*keypress,*keyup,*load,*loadeddata,*loadedmetadata,*loadstart,*lostpointercapture,*mousedown,*mouseenter,*mouseleave,*mousemove,*mouseout,*mouseover,*mouseup,*mousewheel,*paste,*pause,*play,*playing,*pointercancel,*pointerdown,*pointerenter,*pointerleave,*pointermove,*pointerout,*pointerover,*pointerrawupdate,*pointerup,*progress,*ratechange,*reset,*resize,*scroll,*securitypolicyviolation,*seeked,*seeking,*select,*selectionchange,*selectstart,*slotchange,*stalled,*submit,*suspend,*timeupdate,*toggle,*transitioncancel,*transitionend,*transitionrun,*transitionstart,*volumechange,*waiting,*webkitanimationend,*webkitanimationiteration,*webkitanimationstart,*webkittransitionend,*wheel,outerText,!spellcheck,%style,#tabIndex,title,!translate,virtualKeyboardPolicy',
'abbr,address,article,aside,b,bdi,bdo,cite,content,code,dd,dfn,dt,em,figcaption,figure,footer,header,hgroup,i,kbd,main,mark,nav,noscript,rb,rp,rt,rtc,ruby,s,samp,section,small,strong,sub,sup,u,var,wbr^[HTMLElement]|accessKey,autocapitalize,!autofocus,contentEditable,dir,!draggable,enterKeyHint,!hidden,innerText,inputMode,lang,nonce,*abort,*animationend,*animationiteration,*animationstart,*auxclick,*beforexrselect,*blur,*cancel,*canplay,*canplaythrough,*change,*click,*close,*contextmenu,*copy,*cuechange,*cut,*dblclick,*drag,*dragend,*dragenter,*dragleave,*dragover,*dragstart,*drop,*durationchange,*emptied,*ended,*error,*focus,*formdata,*gotpointercapture,*input,*invalid,*keydown,*keypress,*keyup,*load,*loadeddata,*loadedmetadata,*loadstart,*lostpointercapture,*mousedown,*mouseenter,*mouseleave,*mousemove,*mouseout,*mouseover,*mouseup,*mousewheel,*paste,*pause,*play,*playing,*pointercancel,*pointerdown,*pointerenter,*pointerleave,*pointermove,*pointerout,*pointerover,*pointerrawupdate,*pointerup,*progress,*ratechange,*reset,*resize,*scroll,*securitypolicyviolation,*seeked,*seeking,*select,*selectionchange,*selectstart,*slotchange,*stalled,*submit,*suspend,*timeupdate,*toggle,*transitioncancel,*transitionend,*transitionrun,*transitionstart,*volumechange,*waiting,*webkitanimationend,*webkitanimationiteration,*webkitanimationstart,*webkittransitionend,*wheel,outerText,!spellcheck,%style,#tabIndex,title,!translate,virtualKeyboardPolicy',
'abbr,address,article,aside,b,bdi,bdo,cite,content,code,dd,dfn,dt,em,figcaption,figure,footer,header,hgroup,i,kbd,main,mark,nav,noscript,rb,rp,rt,rtc,ruby,s,samp,search,section,small,strong,sub,sup,u,var,wbr^[HTMLElement]|accessKey,autocapitalize,!autofocus,contentEditable,dir,!draggable,enterKeyHint,!hidden,innerText,inputMode,lang,nonce,*abort,*animationend,*animationiteration,*animationstart,*auxclick,*beforexrselect,*blur,*cancel,*canplay,*canplaythrough,*change,*click,*close,*contextmenu,*copy,*cuechange,*cut,*dblclick,*drag,*dragend,*dragenter,*dragleave,*dragover,*dragstart,*drop,*durationchange,*emptied,*ended,*error,*focus,*formdata,*gotpointercapture,*input,*invalid,*keydown,*keypress,*keyup,*load,*loadeddata,*loadedmetadata,*loadstart,*lostpointercapture,*mousedown,*mouseenter,*mouseleave,*mousemove,*mouseout,*mouseover,*mouseup,*mousewheel,*paste,*pause,*play,*playing,*pointercancel,*pointerdown,*pointerenter,*pointerleave,*pointermove,*pointerout,*pointerover,*pointerrawupdate,*pointerup,*progress,*ratechange,*reset,*resize,*scroll,*securitypolicyviolation,*seeked,*seeking,*select,*selectionchange,*selectstart,*slotchange,*stalled,*submit,*suspend,*timeupdate,*toggle,*transitioncancel,*transitionend,*transitionrun,*transitionstart,*volumechange,*waiting,*webkitanimationend,*webkitanimationiteration,*webkitanimationstart,*webkittransitionend,*wheel,outerText,!spellcheck,%style,#tabIndex,title,!translate,virtualKeyboardPolicy',
'media^[HTMLElement]|!autoplay,!controls,%controlsList,%crossOrigin,#currentTime,!defaultMuted,#defaultPlaybackRate,!disableRemotePlayback,!loop,!muted,*encrypted,*waitingforkey,#playbackRate,preload,!preservesPitch,src,%srcObject,#volume',

@@ -139,2 +139,3 @@ ':svg:^[HTMLElement]|!autofocus,nonce,*abort,*animationend,*animationiteration,*animationstart,*auxclick,*beforexrselect,*blur,*cancel,*canplay,*canplaythrough,*change,*click,*close,*contextmenu,*copy,*cuechange,*cut,*dblclick,*drag,*dragend,*dragenter,*dragleave,*dragover,*dragstart,*drop,*durationchange,*emptied,*ended,*error,*focus,*formdata,*gotpointercapture,*input,*invalid,*keydown,*keypress,*keyup,*load,*loadeddata,*loadedmetadata,*loadstart,*lostpointercapture,*mousedown,*mouseenter,*mouseleave,*mousemove,*mouseout,*mouseover,*mouseup,*mousewheel,*paste,*pause,*play,*playing,*pointercancel,*pointerdown,*pointerenter,*pointerleave,*pointermove,*pointerout,*pointerover,*pointerrawupdate,*pointerup,*progress,*ratechange,*reset,*resize,*scroll,*securitypolicyviolation,*seeked,*seeking,*select,*selectionchange,*selectstart,*slotchange,*stalled,*submit,*suspend,*timeupdate,*toggle,*transitioncancel,*transitionend,*transitionrun,*transitionstart,*volumechange,*waiting,*webkitanimationend,*webkitanimationiteration,*webkitanimationstart,*webkittransitionend,*wheel,%style,#tabIndex',

'select^[HTMLElement]|autocomplete,!disabled,#length,!multiple,name,!required,#selectedIndex,#size,value',
'selectedcontent^[HTMLElement]|',
'slot^[HTMLElement]|name',

@@ -144,2 +145,3 @@ 'source^[HTMLElement]|#height,media,sizes,src,srcset,type,#width',

'style^[HTMLElement]|!disabled,media,type',
'search^[HTMLELement]|',
'caption^[HTMLElement]|align',

@@ -146,0 +148,0 @@ 'th,td^[HTMLElement]|abbr,align,axis,bgColor,ch,chOff,#colSpan,headers,height,!noWrap,#rowSpan,scope,vAlign,width',

@@ -9,4 +9,4 @@ /**

export declare function dashCaseToCamelCase(input: string): string;
export declare function splitAtColon(input: string, defaultValues: string[]): string[];
export declare function splitAtPeriod(input: string, defaultValues: string[]): string[];
export declare function splitAtColon(input: string, defaultValues: (string | null)[]): (string | null)[];
export declare function splitAtPeriod(input: string, defaultValues: (string | null)[]): (string | null)[];
export declare function noUndefined<T>(val: T | undefined): T;

@@ -13,0 +13,0 @@ export declare function error(msg: string): never;

{
"name": "angular-html-parser",
"version": "9.0.0",
"version": "9.0.1",
"description": "A HTML parser extracted from Angular with some modifications",

@@ -5,0 +5,0 @@ "repository": "https://github.com/prettier/angular-html-parser/blob/HEAD/packages/angular-html-parser",