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
{{ copyTypes[ @@ -8051,7 +8062,9 @@ can be found in the LICENSE file at http://angular.io/license

top @@ -8072,7 +8085,9 @@ can be found in the LICENSE file at http://angular.io/license [hidden]="isUnchanged")

{{ title @@ -8119,7 +8134,9 @@ can be found in the LICENSE file at http://angular.io/license (keyup)=" (0) " - class="context" + class=" + context + " > Type something: @@ -8132,7 +8149,9 @@ can be found in the LICENSE file at http://angular.io/license
top @@ -8154,7 +8173,9 @@ can be found in the LICENSE file at http://angular.io/license )

top @@ -8428,7 +8463,9 @@ can be found in the LICENSE file at http://angular.io/license /> top @@ -8505,7 +8542,9 @@ can be found in the LICENSE file at http://angular.io/license top @@ -8667,7 +8706,9 @@ can be found in the LICENSE file at http://angular.io/license

top @@ -8761,7 +8802,9 @@ can be found in the LICENSE file at http://angular.io/license
top @@ -8777,7 +8820,11 @@ can be found in the LICENSE file at http://angular.io/license
Bad curly @@ -8786,7 +8833,11 @@ can be found in the LICENSE file at http://angular.io/license
top @@ -8890,7 +8945,9 @@ can be found in the LICENSE file at http://angular.io/license top @@ -8964,7 +9021,9 @@ can be found in the LICENSE file at http://angular.io/license
Click me @@ -9022,7 +9083,9 @@ can be found in the LICENSE file at http://angular.io/license
top @@ -9079,7 +9142,9 @@ can be found in the LICENSE file at http://angular.io/license
top @@ -9151,7 +9216,9 @@ bindon-ngModel (ngModelChange)="setUppercaseName($event)" top @@ -9275,7 +9342,11 @@ bindon-ngModel
Bad curly @@ -9293,7 +9364,9 @@ bindon-ngModel
top @@ -9426,7 +9499,9 @@ bindon-ngModel
top @@ -9554,7 +9629,9 @@ bindon-ngModel
top @@ -9569,7 +9646,9 @@ bindon-ngModel
top @@ -9618,7 +9701,9 @@ bindon-ngModel separator

top @@ -9709,7 +9798,9 @@ bindon-ngModel trackBy

top @@ -9986,7 +10089,9 @@ bindon-ngModel
top @@ -10054,7 +10159,9 @@ bindon-ngModel > top @@ -10107,7 +10214,9 @@ bindon-ngModel }} top @@ -10185,7 +10294,9 @@ bindon-ngModel
top @@ -10282,7 +10393,9 @@ See console log:
top @@ -10319,7 +10432,9 @@ See console log:
top @@ -10371,7 +10486,9 @@ See console log:
top @@ -10433,7 +10550,9 @@ See console log:

top @@ -10456,13 +10575,17 @@ can be found in the LICENSE file at http://angular.io/license #heroForm="ngForm" >
+ +
+
+
+
+ +=====================================output===================================== +
+ Foo +
+ +
+
+
+
+
+ +================================================================================ +`; + +exports[`class-bem2.html format 1`] = ` +====================================options===================================== +parsers: ["html"] +printWidth: 80 + | printWidth +=====================================input====================================== +
+
+

+ ... +

+
+
+ +=====================================output===================================== +
+
+

+ ... +

+
+
+ +================================================================================ +`; + +exports[`class-leading-dashes.html format 1`] = ` +====================================options===================================== +parsers: ["html"] +printWidth: 80 + | printWidth +=====================================input====================================== + + + + +=====================================output===================================== + + + + +================================================================================ +`; + +exports[`class-many-short-names.html format 1`] = ` +====================================options===================================== +parsers: ["html"] +printWidth: 80 + | printWidth +=====================================input====================================== + + +=====================================output===================================== + + +================================================================================ +`; + exports[`class-names.html format 1`] = ` ====================================options===================================== parsers: ["html"] @@ -296,11 +469,41 @@ width="205px" alt="Yahoo"/>
=====================================output===================================== - + @@ -313,10 +516,34 @@ width="205px" alt="Yahoo"/>

================================================================================ `; +exports[`class-print-width-edge.html format 1`] = ` +====================================options===================================== +parsers: ["html"] +printWidth: 80 + | printWidth +=====================================input====================================== + + +=====================================output===================================== + + +================================================================================ +`; + exports[`dobule-quotes.html format 1`] = ` ====================================options===================================== parsers: ["html"] diff --git a/tests/html/attributes/class-bem1.html b/tests/html/attributes/class-bem1.html new file mode 100644 index 000000000..66eba1c9b --- /dev/null +++ b/tests/html/attributes/class-bem1.html @@ -0,0 +1,10 @@ +
Foo
+ +
+
+
+
diff --git a/tests/html/attributes/class-bem2.html b/tests/html/attributes/class-bem2.html new file mode 100644 index 000000000..b1fa92d9d --- /dev/null +++ b/tests/html/attributes/class-bem2.html @@ -0,0 +1,21 @@ +
+
+

+ ... +

+
+
diff --git a/tests/html/attributes/class-leading-dashes.html b/tests/html/attributes/class-leading-dashes.html new file mode 100644 index 000000000..3520dc338 --- /dev/null +++ b/tests/html/attributes/class-leading-dashes.html @@ -0,0 +1,3 @@ + + + diff --git a/tests/html/attributes/class-many-short-names.html b/tests/html/attributes/class-many-short-names.html new file mode 100644 index 000000000..ac51d5aa1 --- /dev/null +++ b/tests/html/attributes/class-many-short-names.html @@ -0,0 +1,4 @@ + diff --git a/tests/html/attributes/class-print-width-edge.html b/tests/html/attributes/class-print-width-edge.html new file mode 100644 index 000000000..ecd71dd96 --- /dev/null +++ b/tests/html/attributes/class-print-width-edge.html @@ -0,0 +1,2 @@ + diff --git a/tests/html/tags/__snapshots__/jsfmt.spec.js.snap b/tests/html/tags/__snapshots__/jsfmt.spec.js.snap index 5e7a41abb..77ee9accb 100644 --- a/tests/html/tags/__snapshots__/jsfmt.spec.js.snap +++ b/tests/html/tags/__snapshots__/jsfmt.spec.js.snap @@ -3316,14 +3316,18 @@ printWidth: 1
    123
  • First
  • 456
  • Second
  • @@ -3465,13 +3469,25 @@ printWidth: 1