Angular: Support comments in start tag (#19465)

This commit is contained in:
fisker Cheung authored and GitHub committed 2026-06-26 19:43:04 +08:00
1 parent 9ed48ccb14
commit af720efb42
19 files changed
+315 -47

No files matched your search

+2
View File
@@ -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) =>
+1
View File
@@ -39,6 +39,7 @@ const angularParser = createParser({
name: "angular",
tokenizeAngularBlocks: true,
tokenizeAngularLetDeclaration: true,
allowStartTagComments: true,
});
// Vue.js
+10 -3
View File
@@ -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;
+2
View File
@@ -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) &&
@@ -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 };
+39 -18
View File
@@ -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
? /**
* <br />
@@ -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),
),
]),
];
+5
View File
@@ -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:
+2 -1
View File
@@ -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"],