diff --git a/changelog_unreleased/less/17983.md b/changelog_unreleased/less/17983.md new file mode 100644 index 000000000..1f739ea3b --- /dev/null +++ b/changelog_unreleased/less/17983.md @@ -0,0 +1,31 @@ +#### Keep property/variable accessors tight (#17983 by @kovsu) + + +```less +// Input +.average(@x, @y) { + @result: ((@x + @y) / 2); +} + +div { + padding: .average(16px, 50px) [ @result ]; +} + +// Prettier stable +.average(@x, @y) { + @result: ((@x + @y) / 2); +} + +div { + padding: .average(16px, 50px) [ @result]; +} + +// Prettier main +.average(@x, @y) { + @result: ((@x + @y) / 2); +} + +div { + padding: .average(16px, 50px)[@result]; +} +``` diff --git a/src/language-css/clean.js b/src/language-css/clean.js index b54c97c7d..063497b81 100644 --- a/src/language-css/clean.js +++ b/src/language-css/clean.js @@ -198,13 +198,15 @@ function clean(original, cloned, parent) { } } - // We parse `@var[ foo ]` and `@var[foo]` differently + // We parse `@var[ foo ]`, `@var[foo]`, and `var [ @foo ]` differently if ( original.type === "value-comma_group" && original.groups.some( (node) => - (node.type === "value-atword" && node.value.endsWith("[")) || - (node.type === "value-word" && node.value.startsWith("]")), + (node.type === "value-atword" && + (node.value.endsWith("[") || node.value.endsWith("]"))) || + (node.type === "value-word" && + (node.value.startsWith("]") || node.value.startsWith("["))), ) ) { return { diff --git a/src/language-css/print/comma-separated-value-group.js b/src/language-css/print/comma-separated-value-group.js index 37f112255..973380c86 100644 --- a/src/language-css/print/comma-separated-value-group.js +++ b/src/language-css/print/comma-separated-value-group.js @@ -191,9 +191,9 @@ function printCommaSeparatedValueGroup(path, options, print) { iNode.type === "value-atword" && (iNode.value === "" || /* - @var[ @notVarNested ][notVar] - ^^^^^ - */ + @var[ @notVarNested ][notVar] + ^^^^^ + */ iNode.value.endsWith("[")) ) { continue; @@ -212,6 +212,38 @@ function printCommaSeparatedValueGroup(path, options, print) { continue; } + // Less property/variable lookup + // https://lesscss.org/features/#detached-rulesets-feature-property-variable-accessors + if (options.parser === "less") { + // `var [@result]` + // ^ + if (iNextNode?.type === "value-word" && iNextNode.value === "[") { + continue; + } + + // `var[ @result]` + // ^ + // `@var [ @@foo ] [ bar ]` + // ^ + if ( + iNode.type === "value-word" && + iNode.value === "[" && + (iNextNode?.type === "value-atword" || iNextNode?.type === "value-word") + ) { + continue; + } + + // `@var [ @@foo ][ bar ]` + // ^^ + if ( + iNode.type === "value-word" && + iNode.value === "][" && + iNextNode?.type === "value-word" + ) { + continue; + } + } + // Ignore escape `\` if ( iNode.type !== "value-string" && diff --git a/tests/format/less/lookup/__snapshots__/format.test.js.snap b/tests/format/less/lookup/__snapshots__/format.test.js.snap new file mode 100644 index 000000000..930c1b575 --- /dev/null +++ b/tests/format/less/lookup/__snapshots__/format.test.js.snap @@ -0,0 +1,129 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`lookup.less format 1`] = ` +====================================options===================================== +parsers: ["less"] +printWidth: 80 + | printWidth +=====================================input====================================== +.average(@x, @y) { + @result: ((@x + @y) / 2); +} + +div { + padding: .average(16px, 50px) [ @result ]; +} + +div { + padding: @foo [ @bar ]; +} + +=====================================output===================================== +.average(@x, @y) { + @result: ((@x + @y) / 2); +} + +div { + padding: .average(16px, 50px)[@result]; +} + +div { + padding: @foo[@bar]; +} + +================================================================================ +`; + +exports[`lookup-1.less format 1`] = ` +====================================options===================================== +parsers: ["less"] +printWidth: 80 + | printWidth +=====================================input====================================== +/* https://lesscss.org/features/#detached-rulesets-feature-property-variable-accessors */ + +@config: { + option1: true; + option2: false; +} + +/* +TODO[prettier]: +The \`@config [ option1]\` part should format as \`@config[option1]\` +**/ +.mixin() when (@config [ option1] = true) { + selected: value; +} + +.box { + .mixin(); +} + +=====================================output===================================== +/* https://lesscss.org/features/#detached-rulesets-feature-property-variable-accessors */ + +@config: { + option1: true; + option2: false; +}; + +/* +TODO[prettier]: +The \`@config [ option1]\` part should format as \`@config[option1]\` +**/ +.mixin() when (@config [ option1] = true) { + selected: value; +} + +.box { + .mixin(); +} + +================================================================================ +`; + +exports[`lookup-2.less format 1`] = ` +====================================options===================================== +parsers: ["less"] +printWidth: 80 + | printWidth +=====================================input====================================== +/* https://lesscss.org/features/#detached-rulesets-feature-property-variable-accessors */ + +@config: { + @dark: { + primary: darkblue; + } + @light: { + primary: lightblue; + } +} + +.box { + @lookup: dark; + color: @config[@@lookup][primary]; + color: @config [ @@lookup ][ primary ]; + color: @config [ @@lookup ] [ primary ]; +} + +=====================================output===================================== +/* https://lesscss.org/features/#detached-rulesets-feature-property-variable-accessors */ + +@config: { + @dark: { + primary: darkblue; + }; + @light: { + primary: lightblue; + }; +}; + +.box { + @lookup: dark; + color: @config[@@lookup][primary]; + color: @config[@@lookup][primary]; + color: @config[@@lookup][primary]; +} + +================================================================================ +`; diff --git a/tests/format/less/lookup/format.test.js b/tests/format/less/lookup/format.test.js new file mode 100644 index 000000000..82c70eb1b --- /dev/null +++ b/tests/format/less/lookup/format.test.js @@ -0,0 +1 @@ +runFormatTest(import.meta, ["less"]); diff --git a/tests/format/less/lookup/lookup-1.less b/tests/format/less/lookup/lookup-1.less new file mode 100644 index 000000000..67a0735b8 --- /dev/null +++ b/tests/format/less/lookup/lookup-1.less @@ -0,0 +1,18 @@ +/* https://lesscss.org/features/#detached-rulesets-feature-property-variable-accessors */ + +@config: { + option1: true; + option2: false; +} + +/* +TODO[prettier]: +The `@config [ option1]` part should format as `@config[option1]` +**/ +.mixin() when (@config [ option1] = true) { + selected: value; +} + +.box { + .mixin(); +} diff --git a/tests/format/less/lookup/lookup-2.less b/tests/format/less/lookup/lookup-2.less new file mode 100644 index 000000000..1f79d05a5 --- /dev/null +++ b/tests/format/less/lookup/lookup-2.less @@ -0,0 +1,17 @@ +/* https://lesscss.org/features/#detached-rulesets-feature-property-variable-accessors */ + +@config: { + @dark: { + primary: darkblue; + } + @light: { + primary: lightblue; + } +} + +.box { + @lookup: dark; + color: @config[@@lookup][primary]; + color: @config [ @@lookup ][ primary ]; + color: @config [ @@lookup ] [ primary ]; +} diff --git a/tests/format/less/lookup/lookup.less b/tests/format/less/lookup/lookup.less new file mode 100644 index 000000000..50335df5b --- /dev/null +++ b/tests/format/less/lookup/lookup.less @@ -0,0 +1,11 @@ +.average(@x, @y) { + @result: ((@x + @y) / 2); +} + +div { + padding: .average(16px, 50px) [ @result ]; +} + +div { + padding: @foo [ @bar ]; +}