diff --git a/packages/compiler/src/ml_parser/ast.ts b/packages/compiler/src/ml_parser/ast.ts index acf9eea8887..8faf142f58c 100644 --- a/packages/compiler/src/ml_parser/ast.ts +++ b/packages/compiler/src/ml_parser/ast.ts @@ -26,7 +26,8 @@ export type Node = | Block | BlockParameter | Component - | Directive; + | Directive + | StartTagComment; export abstract class NodeWithI18n implements BaseNode { constructor( @@ -97,6 +98,17 @@ export class Attribute extends NodeWithI18n { } } +export class StartTagComment implements BaseNode { + constructor( + public value: string, + public type: 'single' | 'multi', + public sourceSpan: ParseSourceSpan, + ) {} + visit(visitor: Visitor, context: any): any { + return visitor.visitAttributeComment ? visitor.visitAttributeComment(this, context) : undefined; + } +} + export class Element extends NodeWithI18n { constructor( public name: string, @@ -109,6 +121,7 @@ export class Element extends NodeWithI18n { public endSourceSpan: ParseSourceSpan | null = null, readonly isVoid: boolean, i18n?: I18nMeta, + public comments: StartTagComment[] = [], ) { super(sourceSpan, i18n); } @@ -159,6 +172,7 @@ export class Component extends NodeWithI18n { readonly startSourceSpan: ParseSourceSpan, public endSourceSpan: ParseSourceSpan | null = null, i18n?: I18nMeta, + public comments: StartTagComment[] = [], ) { super(sourceSpan, i18n); } @@ -223,6 +237,7 @@ export interface Visitor { visitLetDeclaration(decl: LetDeclaration, context: any): any; visitComponent(component: Component, context: any): any; visitDirective(directive: Directive, context: any): any; + visitAttributeComment?(comment: StartTagComment, context: any): any; } export function visitAll(visitor: Visitor, nodes: Node[], context: any = null): any[] { @@ -247,11 +262,13 @@ export class RecursiveVisitor implements Visitor { this.visitChildren(context, (visit) => { visit(ast.attrs); visit(ast.directives); + visit(ast.comments); visit(ast.children); }); } visitAttribute(ast: Attribute, context: any): any {} + visitAttributeComment(ast: StartTagComment, context: any): any {} visitText(ast: Text, context: any): any {} visitComment(ast: Comment, context: any): any {} @@ -277,6 +294,7 @@ export class RecursiveVisitor implements Visitor { visitComponent(component: Component, context: any) { this.visitChildren(context, (visit) => { visit(component.attrs); + visit(component.comments); visit(component.children); }); } diff --git a/packages/compiler/src/ml_parser/html_whitespaces.ts b/packages/compiler/src/ml_parser/html_whitespaces.ts index 752a49f6a58..a04e6344c0a 100644 --- a/packages/compiler/src/ml_parser/html_whitespaces.ts +++ b/packages/compiler/src/ml_parser/html_whitespaces.ts @@ -81,6 +81,7 @@ export class WhitespaceVisitor implements html.Visitor { element.endSourceSpan, element.isVoid, element.i18n, + element.comments, ); this.originalNodeMap?.set(newElement, element); return newElement; @@ -97,6 +98,7 @@ export class WhitespaceVisitor implements html.Visitor { element.endSourceSpan, element.isVoid, element.i18n, + element.comments, ); this.originalNodeMap?.set(newElement, element); return newElement; @@ -229,6 +231,7 @@ export class WhitespaceVisitor implements html.Visitor { node.startSourceSpan, node.endSourceSpan, node.i18n, + node.comments, ); this.originalNodeMap?.set(newElement, node); return newElement; @@ -246,6 +249,7 @@ export class WhitespaceVisitor implements html.Visitor { node.startSourceSpan, node.endSourceSpan, node.i18n, + node.comments, ); this.originalNodeMap?.set(newElement, node); return newElement; diff --git a/packages/compiler/src/ml_parser/lexer.ts b/packages/compiler/src/ml_parser/lexer.ts index e33df40540f..b0011fbb35f 100644 --- a/packages/compiler/src/ml_parser/lexer.ts +++ b/packages/compiler/src/ml_parser/lexer.ts @@ -813,7 +813,7 @@ class _Tokenizer { const content = spanEnd.getChars(contentStart); this._beginToken(TokenType.IN_ELEMENT_COMMENT, start); - this._endToken([content], spanEnd); + this._endToken([content, 'single'], spanEnd); this._attemptCharCodeUntilFn(isNotWhitespace); } @@ -842,7 +842,7 @@ class _Tokenizer { } this._beginToken(TokenType.IN_ELEMENT_COMMENT, start); - this._endToken([content], spanEnd); + this._endToken([content, 'multi'], spanEnd); } private _consumeTagOpen(start: CharacterCursor) { diff --git a/packages/compiler/src/ml_parser/parser.ts b/packages/compiler/src/ml_parser/parser.ts index e1e50065691..7f448f79222 100644 --- a/packages/compiler/src/ml_parser/parser.ts +++ b/packages/compiler/src/ml_parser/parser.ts @@ -393,7 +393,8 @@ class _TreeBuilder { private _consumeElementStartTag(startTagToken: TagOpenStartToken | IncompleteTagOpenToken) { const attrs: html.Attribute[] = []; const directives: html.Directive[] = []; - this._consumeAttributesAndDirectives(attrs, directives); + const comments: html.StartTagComment[] = []; + this._consumeAttributesAndDirectives(attrs, directives, comments); const fullName = this._getElementFullName(startTagToken, this._getClosestElementLikeParent()); const tagDef = this._getTagDefinition(fullName); @@ -438,6 +439,8 @@ class _TreeBuilder { startSpan, undefined, tagDef?.isVoid ?? false, + undefined, + comments, ); const parent = this._getContainer(); const isClosedByChild = @@ -464,7 +467,8 @@ class _TreeBuilder { const componentName = startToken.parts[0]; const attrs: html.Attribute[] = []; const directives: html.Directive[] = []; - this._consumeAttributesAndDirectives(attrs, directives); + const comments: html.StartTagComment[] = []; + this._consumeAttributesAndDirectives(attrs, directives, comments); const closestElement = this._getClosestElementLikeParent(); const tagName = this._getComponentTagName(startToken, closestElement); @@ -494,6 +498,8 @@ class _TreeBuilder { span, startSpan, undefined, + undefined, + comments, ); const parent = this._getContainer(); const isClosedByChild = @@ -515,6 +521,7 @@ class _TreeBuilder { private _consumeAttributesAndDirectives( attributesResult: html.Attribute[], directivesResult: html.Directive[], + commentsResult: html.StartTagComment[], ) { while ( this._peek.type === TokenType.ATTR_NAME || @@ -527,7 +534,13 @@ class _TreeBuilder { attributesResult.push(this._consumeAttr(this._advance())); } else { const commentToken = this._advance(); - this._addToParent(new html.Comment(commentToken.parts[0], commentToken.sourceSpan)); + commentsResult.push( + new html.StartTagComment( + commentToken.parts[0], + commentToken.parts[1], + commentToken.sourceSpan, + ), + ); } } } diff --git a/packages/compiler/src/ml_parser/tokens.ts b/packages/compiler/src/ml_parser/tokens.ts index 61ec36f7968..eef8e8d7aab 100644 --- a/packages/compiler/src/ml_parser/tokens.ts +++ b/packages/compiler/src/ml_parser/tokens.ts @@ -164,7 +164,7 @@ export interface CommentEndToken extends TokenBase { export interface InElementCommentToken extends TokenBase { type: TokenType.IN_ELEMENT_COMMENT; - parts: [content: string]; + parts: [content: string, type: 'single' | 'multi']; } export interface CdataStartToken extends TokenBase { diff --git a/packages/compiler/src/render3/r3_template_transform.ts b/packages/compiler/src/render3/r3_template_transform.ts index dfde0a63ed4..ca05df6c5ee 100644 --- a/packages/compiler/src/render3/r3_template_transform.ts +++ b/packages/compiler/src/render3/r3_template_transform.ts @@ -25,6 +25,7 @@ import {BindingParser} from '../template_parser/binding_parser'; import {PreparsedElementType, preparseElement} from '../template_parser/template_preparser'; import * as t from './r3_ast'; +import {createContentBlock} from './r3_content_blocks'; import { createForLoop, createIfBlock, @@ -33,7 +34,6 @@ import { isConnectedIfLoopBlock, } from './r3_control_flow'; import {createDeferredBlock, isConnectedDeferLoopBlock} from './r3_deferred_blocks'; -import {createContentBlock} from './r3_content_blocks'; import {I18N_ICU_VAR_PREFIX} from './view/i18n/util'; const BIND_NAME_REGEXP = /^(?:(bind-)|(let-)|(ref-|#)|(on-)|(bindon-)|(@))(.*)$/; @@ -264,6 +264,12 @@ class HtmlAstToIvyAst implements html.Visitor { ); } + if (this.options.collectCommentNodes) { + element.comments.forEach((comment) => { + this.commentNodes.push(new t.Comment(comment.value || '', comment.sourceSpan)); + }); + } + if (elementHasInlineTemplate) { // If this node is an inline-template (e.g. has *ngFor) then we need to create a template // node that contains this node. @@ -349,6 +355,13 @@ class HtmlAstToIvyAst implements html.Visitor { return null; } + visitAttributeComment(comment: html.StartTagComment): null { + if (this.options.collectCommentNodes) { + this.commentNodes.push(new t.Comment(comment.value || '', comment.sourceSpan)); + } + return null; + } + visitLetDeclaration(decl: html.LetDeclaration, context: any) { const value = this.bindingParser.parseBinding( decl.value, @@ -432,6 +445,12 @@ class HtmlAstToIvyAst implements html.Visitor { component.i18n, ); + if (this.options.collectCommentNodes) { + component.comments.forEach((comment) => { + this.commentNodes.push(new t.Comment(comment.value || '', comment.sourceSpan)); + }); + } + if (elementHasInlineTemplate) { node = this.wrapInTemplate( node,