Repository: prettier/prettier (JavaScript)

Issue:
Less: Spaces incorrectly added in property/value and property/variable accessors
<!--

BEFORE SUBMITTING AN ISSUE:

1.  Search for your issue on GitHub: https://github.com/prettier/prettier/issues
    A large number of opened issues are duplicates of existing issues.
    If someone has already opened an issue for what you are experiencing,
    you do not need to open a new issue — please add a 👍 reaction to the
    existing issue instead.

2.  We get a lot of requests for adding options, but Prettier is
    built on the principle of being opinionated about code formatting.
    This means we have a very high bar for adding new options.
    Find out more: https://prettier.io/docs/en/option-philosophy.html

Tip! Don't write this stuff manually.

1. Go to https://prettier.io/playground
2. Paste your code and set options
3. Press the "Report issue" button in the lower right

-->

**Prettier 2.0.5**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEATAlgNwATADpTbYAOAhqhlAOZLYB0pmcATqVXABQCMAbMQB4AabAFYADAICUAbQACzOAGcArgBsYAXQDcBAL4ECdAEYR+uAkUiqIzWrMhQAZuipyrNxRunFm6ALakzACe2noggiAQxDDo0IrIoIHMEADuAAqBCPEopKoppEHxEUasYADWcDAAymRg6NTIMMzKcBEAFjB+qgDqbejwirVwVVn9WP1ByOCKRSD1iiwwaaxUAciOuQsRAFaK-ABCpRXVpH5wADL1cOubrSC7-FX1VKpwAIrKEPA3qlsgZMwFswpq8ZuF-r5YN10KgYG1kAAOMQRHwQBbdVjEKY+JQsJjggCOn3gyyi2RApEUAFooHA4Kh6eCFET0Aplmw1kgNr87gs-OhGs1ec9Xh8vtcubcIjBSEZobD4UgAEzS1joVTPADCED8nJAShE4OUCwAKrLsty-pgWgBJKAM2BVMC+aIAQXtVRgQVePwWul0QA)

```sh
--parser less
```

**Input:**

```less
div {
  padding: .average(16px, 50px)[@result];
}

.box {
  color: @config[@colors][primary];
}
```

**Output:**

```less
div {
  padding: .average(16px, 50px) [ @result];
}

.box {
  color: @config[ @colors][primary];
}
```

**Expected behavior:**
Spaces should **not** be added before the square brackets (`[`) or the at symbols (`@`).


Discussion:
@charsboo It is official Less syntax? Can't find it in docs
@evilebottnawi The code I posted are from examples in the Less documentation:

- [Property/value accessors](http://lesscss.org/features/#mixins-feature-mixins-as-functions-feature) (under "Using Mixins as Functions")
- [Property/variable accessors](http://lesscss.org/features/#detached-rulesets-feature-property-variable-accessors) (under "Detached Rulesets")
Is this issue still being worked on? We're testing to try adopt Prettier for our LESS stylesheets but this happens to be a major dealbreaker for us because we use the new syntax intensively. :(
@zvuc PR welcome
+1




Patch:
diff --git a/changelog_unreleased/less/17983.md b/changelog_unreleased/less/17983.md
new file mode 100644
index 000000000000..1f739ea3b838
--- /dev/null
+++ b/changelog_unreleased/less/17983.md
@@ -0,0 +1,31 @@
+#### Keep property/variable accessors tight (#17983 by @kovsu)
+
+<!-- prettier-ignore -->
+```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 b54c97c7d1c6..063497b816cd 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 37f11225553c..973380c8639e 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" &&


Test patch:
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 000000000000..930c1b57560a
--- /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 000000000000..82c70eb1bedf
--- /dev/null
+++ b/tests/format/less/lookup/format.test.js
@@ -0,0 +1,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 000000000000..67a0735b8dce
--- /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 000000000000..1f79d05a503e
--- /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 000000000000..50335df5b430
--- /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   ];
+}