diff --git a/changelog_unreleased/angular/19465.md b/changelog_unreleased/angular/19465.md
new file mode 100644
index 000000000..fd6685017
--- /dev/null
+++ b/changelog_unreleased/angular/19465.md
@@ -0,0 +1,31 @@
+#### Support comments in html element (#19465 by @fisker)
+
+
+```html
+
+
+
+
+
+
+
+
+```
diff --git a/src/language-html/parse/parse-options.js b/src/language-html/parse/parse-options.js
index 9a0a74af7..15ca553b1 100644
--- a/src/language-html/parse/parse-options.js
+++ b/src/language-html/parse/parse-options.js
@@ -51,6 +51,7 @@ function toAngularHtmlParserParseOptions(parseOptions) {
const {
canSelfClose,
allowHtmComponentClosingTags,
+ allowStartTagComments,
isTagNameCaseSensitive,
shouldParseAsRawText,
tokenizeAngularBlocks,
@@ -60,6 +61,7 @@ function toAngularHtmlParserParseOptions(parseOptions) {
return {
canSelfClose,
allowHtmComponentClosingTags,
+ allowStartTagComments,
isTagNameCaseSensitive,
getTagContentType: shouldParseAsRawText
? (...args) =>
diff --git a/src/language-html/parse/parsers.js b/src/language-html/parse/parsers.js
index f1b6293bb..16afabb66 100644
--- a/src/language-html/parse/parsers.js
+++ b/src/language-html/parse/parsers.js
@@ -39,6 +39,7 @@ const angularParser = createParser({
name: "angular",
tokenizeAngularBlocks: true,
tokenizeAngularLetDeclaration: true,
+ allowStartTagComments: true,
});
// Vue.js
diff --git a/src/language-html/parse/postprocess.js b/src/language-html/parse/postprocess.js
index f89e01c46..495e6ff50 100644
--- a/src/language-html/parse/postprocess.js
+++ b/src/language-html/parse/postprocess.js
@@ -36,6 +36,8 @@ class Visitor extends RecursiveVisitor {
}
function postprocess(rawAst, frontMatter, parseOptions, parseSubHtml) {
+ const isAngular = parseOptions.name === "angular";
+
visitAll(new Visitor(), rawAst.children, { parseOptions });
if (frontMatter) {
@@ -53,11 +55,16 @@ function postprocess(rawAst, frontMatter, parseOptions, parseSubHtml) {
if (ieConditionalComment) {
node.parent.replaceChild(node, ieConditionalComment);
}
+ } else if (isAngular && node.kind === "element" && node.comments) {
+ node.startTagComments = node.comments;
+ delete node.comments;
}
- normalizeAngularControlFlowBlock(node);
- normalizeAngularLetDeclaration(node);
- normalizeAngularIcuExpression(node);
+ if (isAngular) {
+ normalizeAngularControlFlowBlock(node);
+ normalizeAngularLetDeclaration(node);
+ normalizeAngularIcuExpression(node);
+ }
});
return ast;
diff --git a/src/language-html/print-preprocess.js b/src/language-html/print-preprocess.js
index 49e7ed99d..487b012b8 100644
--- a/src/language-html/print-preprocess.js
+++ b/src/language-html/print-preprocess.js
@@ -1,5 +1,6 @@
import { ParseSourceSpan } from "angular-html-parser";
import htmlWhitespace from "../utilities/html-whitespace.js";
+import isNonEmptyArray from "../utilities/is-non-empty-array.js";
import {
canHaveInterpolation,
getLeadingAndTrailingHtmlWhitespace,
@@ -141,6 +142,7 @@ function mergeSimpleElementIntoText(ast /* , options */) {
const isSimpleElement = (node) =>
node.kind === "element" &&
node.attrs.length === 0 &&
+ !isNonEmptyArray(node.startTagComments) &&
node.children.length === 1 &&
node.firstChild.kind === "text" &&
!htmlWhitespace.hasWhitespaceCharacter(node.children[0].value) &&
diff --git a/src/language-html/print/start-tag-comment.js b/src/language-html/print/start-tag-comment.js
new file mode 100644
index 000000000..ed43fae86
--- /dev/null
+++ b/src/language-html/print/start-tag-comment.js
@@ -0,0 +1,19 @@
+import { replaceEndOfLine } from "../../document/index.js";
+
+// Based on JS comment print src/language-js/print/comment.js
+
+function printStartTagComment(path) {
+ const {
+ node: { value, type },
+ } = path;
+
+ if (type === "single") {
+ return `//${value.trimEnd()}`;
+ }
+
+ // TODO: support indentable block comment
+
+ return ["/*", replaceEndOfLine(value), "*/"];
+}
+
+export { printStartTagComment };
diff --git a/src/language-html/print/tag.js b/src/language-html/print/tag.js
index 57968903d..26396f317 100644
--- a/src/language-html/print/tag.js
+++ b/src/language-html/print/tag.js
@@ -221,7 +221,9 @@ function needsToBorrowParentOpeningTagEndMarker(node) {
function printAttributes(path, options, print) {
const { node } = path;
- if (!isNonEmptyArray(node.attrs)) {
+ const { attrs: attributes = [], startTagComments = [] } = node;
+
+ if (attributes.length === 0 && startTagComments.length === 0) {
return node.isSelfClosing
? /**
*
@@ -242,34 +244,53 @@ function printAttributes(path, options, print) {
? (attribute) => ignoreAttributeData.includes(attribute.rawName)
: () => false;
- const printedAttributes = path.map(
- ({ node: attribute }) =>
- hasPrettierIgnoreAttribute(attribute)
- ? replaceEndOfLine(
- options.originalText.slice(locStart(attribute), locEnd(attribute)),
- )
- : print(),
- "attrs",
+ const properties = ["attrs", "startTagComments"].filter((property) =>
+ isNonEmptyArray(node[property]),
);
+ const printedAttributes = properties.flatMap((property) =>
+ path.map(
+ ({ node }) => ({
+ loc: locStart(node),
+ printed:
+ node.kind === "attribute" && hasPrettierIgnoreAttribute(node)
+ ? replaceEndOfLine(
+ options.originalText.slice(locStart(node), locEnd(node)),
+ )
+ : print(),
+ }),
+ property,
+ ),
+ );
+
+ if (properties.length > 1) {
+ printedAttributes.sort((a, b) => a.loc - b.loc);
+ }
const forceNotToBreakAttrContent =
node.kind === "element" &&
node.fullName === "script" &&
- node.attrs.length === 1 &&
- node.attrs[0].fullName === "src" &&
- node.children.length === 0;
-
+ attributes.length === 1 &&
+ attributes[0].fullName === "src" &&
+ node.children.length === 0 &&
+ startTagComments.length === 0;
+ const shouldForceBreak = startTagComments.some(
+ (comment) => comment.type === "single",
+ );
const shouldPrintAttributePerLine =
- options.singleAttributePerLine &&
- node.attrs.length > 1 &&
- !isVueSfcBlock(node, options);
+ shouldForceBreak ||
+ (options.singleAttributePerLine &&
+ attributes.length > 1 &&
+ !isVueSfcBlock(node, options));
const attributeLine = shouldPrintAttributePerLine ? hardline : line;
/** @type {Doc[]} */
const parts = [
indent([
- forceNotToBreakAttrContent ? " " : line,
- join(attributeLine, printedAttributes),
+ forceNotToBreakAttrContent ? " " : shouldForceBreak ? hardline : line,
+ join(
+ attributeLine,
+ printedAttributes.map(({ printed }) => printed),
+ ),
]),
];
diff --git a/src/language-html/printer-html.js b/src/language-html/printer-html.js
index d1caf98e1..bd5228d58 100644
--- a/src/language-html/printer-html.js
+++ b/src/language-html/printer-html.js
@@ -28,6 +28,7 @@ import {
} from "./print/angular-icu-expression.js";
import { printChildren } from "./print/children.js";
import { printElement } from "./print/element.js";
+import { printStartTagComment } from "./print/start-tag-comment.js";
import {
printClosingTagEnd,
printClosingTagSuffix,
@@ -150,6 +151,10 @@ function genericPrint(path, options, print) {
quote,
];
}
+
+ case "startTagComment":
+ return printStartTagComment(path);
+
case "frontMatter": // Handled in core
case "cdata": // Transformed into `text`
default:
diff --git a/src/language-html/visitor-keys.evaluate.js b/src/language-html/visitor-keys.evaluate.js
index cf036e223..d5ea4cbf6 100644
--- a/src/language-html/visitor-keys.evaluate.js
+++ b/src/language-html/visitor-keys.evaluate.js
@@ -2,7 +2,7 @@ import { generateReferenceSharedVisitorKeys } from "../utilities/visitor-keys.js
const visitorKeys = generateReferenceSharedVisitorKeys({
root: ["children"],
- element: ["attrs", "children"],
+ element: ["attrs", "startTagComments", "children"],
ieConditionalComment: ["children"],
ieConditionalStartComment: [],
ieConditionalEndComment: [],
@@ -11,6 +11,7 @@ const visitorKeys = generateReferenceSharedVisitorKeys({
docType: [],
comment: [],
attribute: [],
+ startTagComment: [],
cdata: [],
angularControlFlowBlock: ["children", "parameters"],
angularControlFlowBlockParameters: ["children"],
diff --git a/tests/format/angular/start-tag-comments/__snapshots__/format.test.js.snap b/tests/format/angular/start-tag-comments/__snapshots__/format.test.js.snap
new file mode 100644
index 000000000..ca63f8d6a
--- /dev/null
+++ b/tests/format/angular/start-tag-comments/__snapshots__/format.test.js.snap
@@ -0,0 +1,144 @@
+// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
+
+exports[`merge-into-text.html format 1`] = `
+====================================options=====================================
+parsers: ["angular"]
+ printWidth: 80 (default) |
+=====================================input======================================
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit,
+ "seddoeiusmod".
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit,
+ "seddoeiusmod".
+
+
+=====================================output=====================================
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit,
+ "seddoeiusmod".
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, "seddoeiusmod".
+
+
+================================================================================
+`;
+
+exports[`start-tag-comments.html format 1`] = `
+====================================options=====================================
+parsers: ["angular"]
+ printWidth: 80 (default) |
+=====================================input======================================
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+=====================================output=====================================
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+================================================================================
+`;
diff --git a/tests/format/angular/start-tag-comments/format.test.js b/tests/format/angular/start-tag-comments/format.test.js
new file mode 100644
index 000000000..8cbc48b1a
--- /dev/null
+++ b/tests/format/angular/start-tag-comments/format.test.js
@@ -0,0 +1 @@
+runFormatTest(import.meta, ["angular"]);
diff --git a/tests/format/angular/start-tag-comments/merge-into-text.html b/tests/format/angular/start-tag-comments/merge-into-text.html
new file mode 100644
index 000000000..d364a47b8
--- /dev/null
+++ b/tests/format/angular/start-tag-comments/merge-into-text.html
@@ -0,0 +1,11 @@
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit,
+ "seddoeiusmod".
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit,
+ "seddoeiusmod".
+
diff --git a/tests/format/angular/start-tag-comments/start-tag-comments.html b/tests/format/angular/start-tag-comments/start-tag-comments.html
new file mode 100644
index 000000000..047d3e6cc
--- /dev/null
+++ b/tests/format/angular/start-tag-comments/start-tag-comments.html
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/format/html/_errors_/in-element-comments/format.test.js b/tests/format/html/_errors_/in-element-comments/format.test.js
deleted file mode 100644
index 2af3dd93d..000000000
--- a/tests/format/html/_errors_/in-element-comments/format.test.js
+++ /dev/null
@@ -1,8 +0,0 @@
-runFormatTest(import.meta, [
- "html",
- "vue",
- "lwc",
- "mjml",
- // https://github.com/prettier/prettier/issues/18974
- "angular",
-]);
diff --git a/tests/format/html/_errors_/in-element-comments/__snapshots__/format.test.js.snap b/tests/format/html/_errors_/start-tag-comments/__snapshots__/format.test.js.snap
similarity index 80%
rename from tests/format/html/_errors_/in-element-comments/__snapshots__/format.test.js.snap
rename to tests/format/html/_errors_/start-tag-comments/__snapshots__/format.test.js.snap
index d1aeb8d43..293894462 100644
--- a/tests/format/html/_errors_/in-element-comments/__snapshots__/format.test.js.snap
+++ b/tests/format/html/_errors_/start-tag-comments/__snapshots__/format.test.js.snap
@@ -1,13 +1,5 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
-exports[`block-comment.html format[angular] 1`] = `
-"Opening tag "div" not terminated. (1:1)
-> 1 |
- | ^^^^^^
- 2 |
-Cause: Opening tag "div" not terminated."
-`;
-
exports[`block-comment.html format[html] 1`] = `
"Opening tag "div" not terminated. (1:1)
> 1 |
@@ -40,15 +32,6 @@ exports[`block-comment.html format[vue] 1`] = `
Cause: Opening tag "div" not terminated."
`;
-exports[`line-comment.html format[angular] 1`] = `
-"Opening tag "div" not terminated. (1:1)
-> 1 |
- 3 |
-Cause: Opening tag "div" not terminated."
-`;
-
exports[`line-comment.html format[html] 1`] = `
"Opening tag "div" not terminated. (1:1)
> 1 |