diff --git a/changelog_unreleased/html/7865.md b/changelog_unreleased/html/7865.md new file mode 100644 index 000000000..95a225f04 --- /dev/null +++ b/changelog_unreleased/html/7865.md @@ -0,0 +1,22 @@ +#### Prefix-based multiline formatting for the `class` attribute (#7865 by @thorn0) + +Formatting of HTML class names will now keep classes on one line until the line length limit is reached; at that point, consecutive classes with the same prefix will be grouped together on each line. For layout frameworks such as Bootstrap and Tailwind CSS, which add many classes to an element, this is important for readability and maintainability vs. the previous behavior (keeping all classes on one line) or e.g. putting each class on its own line. + + +```html + +
+ + + +``` diff --git a/src/language-html/syntax-attribute.js b/src/language-html/syntax-attribute.js index bc2802a32..3180ae3b2 100644 --- a/src/language-html/syntax-attribute.js +++ b/src/language-html/syntax-attribute.js @@ -2,7 +2,7 @@ const parseSrcset = require("parse-srcset"); const { - builders: { ifBreak, join, line }, + builders: { group, ifBreak, indent, join, line, softline }, } = require("../document"); function printImgSrcset(value) { @@ -59,8 +59,50 @@ function printImgSrcset(value) { ); } +const prefixDelimiters = ["__", "--", "_", "-"]; + +function getClassPrefix(className) { + const startIndex = className.search(/[^_-]/); + if (startIndex !== -1) { + for (const delimiter of prefixDelimiters) { + const delimiterIndex = className.indexOf(delimiter, startIndex); + if (delimiterIndex !== -1) { + return className.slice(0, delimiterIndex); + } + } + } + return className; +} + function printClassNames(value) { - return value.trim().split(/\s+/).join(" "); + const classNames = value.trim().split(/\s+/); + + // Try keeping consecutive classes with the same prefix on one line. + const groupedByPrefix = []; + let previousPrefix; + for (let i = 0; i < classNames.length; i++) { + const prefix = getClassPrefix(classNames[i]); + if ( + prefix !== previousPrefix && + // "home-link" and "home-link_blue_yes" should be considered same-prefix + prefix !== classNames[i - 1] + ) { + groupedByPrefix.push([]); + } + groupedByPrefix[groupedByPrefix.length - 1].push(classNames[i]); + previousPrefix = prefix; + } + + return [ + indent([ + softline, + join( + line, + groupedByPrefix.map((classNames) => group(join(line, classNames))) + ), + ]), + softline, + ]; } module.exports = { diff --git a/tests/angular/angular/__snapshots__/jsfmt.spec.js.snap b/tests/angular/angular/__snapshots__/jsfmt.spec.js.snap index 2eb34a77d..437a6d4fb 100644 --- a/tests/angular/angular/__snapshots__/jsfmt.spec.js.snap +++ b/tests/angular/angular/__snapshots__/jsfmt.spec.js.snap @@ -110,7 +110,9 @@ printWidth: 1