diff --git a/packages/compiler/src/ml_parser/lexer.ts b/packages/compiler/src/ml_parser/lexer.ts index 3886bcbabc0a..dec56cbb145b 100644 --- a/packages/compiler/src/ml_parser/lexer.ts +++ b/packages/compiler/src/ml_parser/lexer.ts @@ -805,12 +805,41 @@ class _Tokenizer { return [prefix, name]; } - private _consumeSingleLineComment() { + private _consumeSingleLineComment(start: CharacterCursor) { + const contentStart = this._cursor.clone(); this._attemptCharCodeUntilFn((code) => chars.isNewLine(code) || code === chars.$EOF); + + const originalEnd = this._cursor.clone(); + const content = originalEnd.getChars(contentStart); + + let trimmedStart = 0; + while (trimmedStart < content.length && chars.isWhitespace(content.charCodeAt(trimmedStart))) { + trimmedStart++; + } + + let trimmedEnd = content.length; + while (trimmedEnd > trimmedStart && chars.isWhitespace(content.charCodeAt(trimmedEnd - 1))) { + trimmedEnd--; + } + + let spanStart = contentStart.clone(); + for (let i = 0; i < trimmedStart; i++) { + spanStart.advance(); + } + + let spanEnd = contentStart.clone(); + for (let i = 0; i < trimmedEnd; i++) { + spanEnd.advance(); + } + + this._beginToken(TokenType.IN_ELEMENT_COMMENT, spanStart); + this._endToken([content.trim()], spanEnd); + this._attemptCharCodeUntilFn(isNotWhitespace); } - private _consumeMultiLineComment() { + private _consumeMultiLineComment(start: CharacterCursor) { + const contentStart = this._cursor.clone(); this._attemptCharCodeUntilFn((code) => { if (code === chars.$EOF) { return true; @@ -822,9 +851,35 @@ class _Tokenizer { } return false; }); + + const content = this._cursor.getChars(contentStart); + + let trimmedStart = 0; + while (trimmedStart < content.length && chars.isWhitespace(content.charCodeAt(trimmedStart))) { + trimmedStart++; + } + + let trimmedEnd = content.length; + while (trimmedEnd > trimmedStart && chars.isWhitespace(content.charCodeAt(trimmedEnd - 1))) { + trimmedEnd--; + } + + let spanStart = contentStart.clone(); + for (let i = 0; i < trimmedStart; i++) { + spanStart.advance(); + } + + let spanEnd = contentStart.clone(); + for (let i = 0; i < trimmedEnd; i++) { + spanEnd.advance(); + } + if (this._attemptStr('*/')) { this._attemptCharCodeUntilFn(isNotWhitespace); } + + this._beginToken(TokenType.IN_ELEMENT_COMMENT, spanStart); + this._endToken([content.trim()], spanEnd); } private _consumeTagOpen(start: CharacterCursor) { @@ -864,13 +919,15 @@ class _Tokenizer { } while (true) { + const singleLineCommentStart = this._cursor.clone(); if (this._attemptStr('//')) { - this._consumeSingleLineComment(); + this._consumeSingleLineComment(singleLineCommentStart); continue; } + const multilineCommentStart = this._cursor.clone(); if (this._attemptStr('/*')) { - this._consumeMultiLineComment(); + this._consumeMultiLineComment(multilineCommentStart); continue; } diff --git a/packages/compiler/src/ml_parser/parser.ts b/packages/compiler/src/ml_parser/parser.ts index a0a697191656..e1e50065691a 100644 --- a/packages/compiler/src/ml_parser/parser.ts +++ b/packages/compiler/src/ml_parser/parser.ts @@ -31,6 +31,7 @@ import { IncompleteComponentOpenToken, IncompleteLetToken, IncompleteTagOpenToken, + InElementCommentToken, InterpolatedAttributeToken, InterpolatedTextToken, LetEndToken, @@ -517,12 +518,16 @@ class _TreeBuilder { ) { while ( this._peek.type === TokenType.ATTR_NAME || - this._peek.type === TokenType.DIRECTIVE_NAME + this._peek.type === TokenType.DIRECTIVE_NAME || + this._peek.type === TokenType.IN_ELEMENT_COMMENT ) { if (this._peek.type === TokenType.DIRECTIVE_NAME) { directivesResult.push(this._consumeDirective(this._peek)); - } else { + } else if (this._peek.type === TokenType.ATTR_NAME) { attributesResult.push(this._consumeAttr(this._advance())); + } else { + const commentToken = this._advance(); + this._addToParent(new html.Comment(commentToken.parts[0], commentToken.sourceSpan)); } } } diff --git a/packages/compiler/src/ml_parser/tokens.ts b/packages/compiler/src/ml_parser/tokens.ts index 1cf340c6992d..61ec36f79685 100644 --- a/packages/compiler/src/ml_parser/tokens.ts +++ b/packages/compiler/src/ml_parser/tokens.ts @@ -21,6 +21,7 @@ export const enum TokenType { ENCODED_ENTITY, COMMENT_START, COMMENT_END, + IN_ELEMENT_COMMENT, CDATA_START, CDATA_END, ATTR_NAME, @@ -64,6 +65,7 @@ export type Token = | EncodedEntityToken | CommentStartToken | CommentEndToken + | InElementCommentToken | CdataStartToken | CdataEndToken | AttributeNameToken @@ -160,6 +162,11 @@ export interface CommentEndToken extends TokenBase { parts: []; } +export interface InElementCommentToken extends TokenBase { + type: TokenType.IN_ELEMENT_COMMENT; + parts: [content: string]; +} + export interface CdataStartToken extends TokenBase { type: TokenType.CDATA_START; parts: []; diff --git a/packages/compiler/test/render3/view/parse_template_options_spec.ts b/packages/compiler/test/render3/view/parse_template_options_spec.ts index 5888245e4f19..2d86b1bd71b9 100644 --- a/packages/compiler/test/render3/view/parse_template_options_spec.ts +++ b/packages/compiler/test/render3/view/parse_template_options_spec.ts @@ -23,6 +23,10 @@ describe('collectCommentNodes', () => { Text

+
`; @@ -33,7 +37,7 @@ describe('collectCommentNodes', () => { expect(templateCommentsOptionDisabled.commentNodes).toBeUndefined(); const templateCommentsOptionEnabled = parseTemplate(html, '', {collectCommentNodes: true}); - expect(templateCommentsOptionEnabled.commentNodes!.length).toEqual(2); + expect(templateCommentsOptionEnabled.commentNodes!.length).toEqual(4); expect(templateCommentsOptionEnabled.commentNodes![0]).toBeInstanceOf(Comment); expect(templateCommentsOptionEnabled.commentNodes![0].value).toEqual( 'eslint-disable-next-line', @@ -52,5 +56,25 @@ describe('collectCommentNodes', () => { expect(templateCommentsOptionEnabled.commentNodes![1].sourceSpan.toString()).toEqual( '', ); + + expect(templateCommentsOptionEnabled.commentNodes![2]).toBeInstanceOf(Comment); + expect(templateCommentsOptionEnabled.commentNodes![2].value).toEqual( + 'some comment in the middle of an element', + ); + expect(templateCommentsOptionEnabled.commentNodes![2].sourceSpan).toBeInstanceOf( + ParseSourceSpan, + ); + expect(templateCommentsOptionEnabled.commentNodes![2].sourceSpan.toString()).toEqual( + 'some comment in the middle of an element', + ); + + expect(templateCommentsOptionEnabled.commentNodes![3]).toBeInstanceOf(Comment); + expect(templateCommentsOptionEnabled.commentNodes![3].value).toEqual('some other comment'); + expect(templateCommentsOptionEnabled.commentNodes![3].sourceSpan).toBeInstanceOf( + ParseSourceSpan, + ); + expect(templateCommentsOptionEnabled.commentNodes![3].sourceSpan.toString()).toEqual( + 'some other comment', + ); }); });