Repository: prettier/prettier (JavaScript)
Date: 2020-05-26 04:35:08+00:00
Task type: Bug Fix

Issue:
Too much indentation in method body selected by range if first line is comment or incompletely selected
**Prettier 1.14.0**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAucAbAhgZ0wAgGIQQ7AA6UOFOA9AFQ04AiEcuMAFgJa4DWcAnjgC26GGDY50OAOYcAbghwBZDlA7DUAaX444AD3hQAJiqk4IAIwBWcMDAD8OGlTKUhIsQApefOPNgBKYhdXSioqHAByFQicQ2ZMKAiYHBUwVABXQzgcAAcAJwgYQr4clgAaHEwiDhgI3Ex0ADNsxog8nHYuMysbGAA6YJCcVvaPVDhkkRh2lQ7OTEDSciGQjkacD07MAG0pvIBdHABCAF4TnG9fBBhdmGn9wLyJ9LzyRvRUTDhBkIBfH9cTxgL3I03S32WFH+UF+IDKIAgORgHGgmGQoHQeQKAHcAAqYhBolDoWQQDiGOEgcx5dBgXgwADKOVpJmQYLg8JUXzyMFxNKkwmQ70+HJAlkwugAQjS6RMGehBHAADIqOBCj5feHMvLc5BU9DmPioaCU-IqGAAdXJ7GQAA4AAxagpfC00nJ6-IsOB5eSUp4AR3SHCefPQAvQ6pF8K+gg4bLy4OjJnGAEV0oU1UhhZqQDADVbDDakAAmeHTdAcVAmADCEEEgpQUGgavh6S+ABUDUTs6KaVApHAGXmecgAIz20fwvsDgCiRmQAGZ7QAWX6-IA)
```sh
--parser babylon
--range-end 304
--range-start 101
```

Can reproduce with just range 100 - 101 (the newline character after the brace on line 4), so that seems to be the culprit.

If I replace line 4 with an expression (e.g. let x) then the method is formatted correctly more often so long as my selection covers the second character of the expression, but you can break it again by moving the start of the selection over the brace on line 3.

**Input:**
```jsx
class Foo {
    /** Does this key match a given MinimalKey extending object? */
    match(keyevent) {
        // 'in' doesn't include prototypes, so it's safe for this object.
        for (let attr in this) {
            if (this[attr] !== keyevent[attr]) return false
        }
        return true
    }
}
```

**Output:**
```jsx
class Foo {
    /** Does this key match a given MinimalKey extending object? */
    match(keyevent) {
                      // 'in' doesn't include prototypes, so it's safe for this object.
                      for (let attr in this) {
                        if (this[attr] !== keyevent[attr]) return false;
                      }
                      return true;
                    }
}
```

**Expected behavior:**

Don't indent so much. Don't be so sensitive to range choice.

Ref: https://github.com/nrwl/precise-commits/issues/15


Issue discussion:
I’m starting to wonder if we should change range formatting to format the whole file, but with two “cursors,” then replace what’s in the range with the text between the cursors.
Sounds sensible to me. In my project we just ended up running prettier on everything so that we could use whole file prettier.
This makes range formatting unusable. Prettier randomly indents half of codebase
btw. to make it worse range formatting is very slow because of API prettier is exposing (you can provide just one range, so if you need to format n ranges you need to call format n times)
@sheerun Kind of related: https://github.com/prettier/prettier/issues/5807#issuecomment-458422589
Here's another even simpler example:

test.js 

```
function test() {
  'foo';
  'bar';
}
```

command:

```
cat test.js | prettier --parser babel --range-start 25 --range-end 25
function test() {
                  ("foo");
                  ("bar");
                }
```
See one more manifestation of this bug in #7082.

Solution patch:
diff --git a/changelog_unreleased/javascript/pr-8410.md b/changelog_unreleased/javascript/pr-8410.md
new file mode 100644
index 000000000000..0a3420bfee55
--- /dev/null
+++ b/changelog_unreleased/javascript/pr-8410.md
@@ -0,0 +1,18 @@
+#### Fix range formatting indentation ([#8410](https://github.com/prettier/prettier/pull/8410) by [@thorn0](https://github.com/thorn0))
+
+```console
+> echo -e "export default class Foo{\n/**/\n}" | prettier --range-start 16 --range-end 31 --parser babel
+```
+
+<!--prettier-ignore-->
+```js
+// Prettier stable
+export default class Foo {
+                 /**/
+               }
+
+// Prettier master
+export default class Foo {
+  /**/
+}
+```
diff --git a/src/main/core.js b/src/main/core.js
index 58ff4fa31a0d..e02d66356302 100644
--- a/src/main/core.js
+++ b/src/main/core.js
@@ -186,7 +186,7 @@ function formatRange(text, opts) {
     rangeStart,
     text.lastIndexOf("\n", rangeStart) + 1
   );
-  const indentString = text.slice(rangeStart2, rangeStart);
+  const indentString = text.slice(rangeStart2, rangeStart).match(/^\s*/)[0];
 
   const alignmentSize = privateUtil.getAlignmentSize(
     indentString,


Test patch:
diff --git a/tests/js/range/__snapshots__/jsfmt.spec.js.snap b/tests/js/range/__snapshots__/jsfmt.spec.js.snap
index 556eac282b19..b0de3e8963ff 100644
--- a/tests/js/range/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/js/range/__snapshots__/jsfmt.spec.js.snap
@@ -88,6 +88,156 @@ function ugly ( {a=1,     b     =   2     }      ) {
 ================================================================================
 `;
 
+exports[`issue-3789-1.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "flow", "typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+export class F {
+  reformatThis() {
+    return 1;
+  }
+
+  dontTouchThis(){
+    return 2    ;
+  }
+}
+
+=====================================output=====================================
+export class F {
+  reformatThis() {
+    return 1;
+  }
+
+  dontTouchThis() {
+    return 2;
+  }
+}
+
+================================================================================
+`;
+
+exports[`issue-3789-2.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "flow", "typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+export class F {
+  reformatThis() {
+    return 1;
+  }
+
+  dontTouchThis(){
+    return 2    ;
+  }
+}
+
+=====================================output=====================================
+export class F {
+  reformatThis() {
+    return 1;
+  }
+
+  dontTouchThis() {
+    return 2;
+  }
+}
+
+================================================================================
+`;
+
+exports[`issue-4206-1.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "flow", "typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+export default class Foo{
+/**/
+}
+
+=====================================output=====================================
+export default class Foo {
+  /**/
+}
+
+================================================================================
+`;
+
+exports[`issue-4206-2.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "flow", "typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+export default function Foo(){
+/**/
+}
+
+=====================================output=====================================
+export default function Foo() {
+  /**/
+}
+
+================================================================================
+`;
+
+exports[`issue-4206-3.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "flow", "typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+/* */ function Foo(){
+/**/
+}
+
+=====================================output=====================================
+/* */ function Foo() {
+  /**/
+}
+
+================================================================================
+`;
+
+exports[`issue-4206-4.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "flow", "typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+/* */ class Foo{
+/**/
+}
+
+=====================================output=====================================
+/* */ class Foo {
+  /**/
+}
+
+================================================================================
+`;
+
+exports[`issue-7082.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "flow", "typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+export const Button = styled.button\`
+color: blue;
+\`;
+
+=====================================output=====================================
+export const Button = styled.button\`
+  color: blue;
+\`;
+
+================================================================================
+`;
+
 exports[`large-dict.js format 1`] = `
 ====================================options=====================================
 parsers: ["babel", "flow", "typescript"]
@@ -396,8 +546,8 @@ catch (err) {}
 =====================================output=====================================
 try {
 1;if (condition) {
-    body;
-  }
+  body;
+}
 }
 catch (err) {}
 
diff --git a/tests/js/range/issue-3789-1.js b/tests/js/range/issue-3789-1.js
new file mode 100644
index 000000000000..2faa8e24ab80
--- /dev/null
+++ b/tests/js/range/issue-3789-1.js
@@ -0,0 +1,9 @@
+export class F {
+<<<PRETTIER_RANGE_START>>>  reformatThis<<<PRETTIER_RANGE_END>>>() {
+    return 1;
+  }
+
+  dontTouchThis(){
+    return 2    ;
+  }
+}
diff --git a/tests/js/range/issue-3789-2.js b/tests/js/range/issue-3789-2.js
new file mode 100644
index 000000000000..b149351d4f66
--- /dev/null
+++ b/tests/js/range/issue-3789-2.js
@@ -0,0 +1,9 @@
+export class F {
+<<<PRETTIER_RANGE_START>>> <<<PRETTIER_RANGE_END>>> reformatThis() {
+    return 1;
+  }
+
+  dontTouchThis(){
+    return 2    ;
+  }
+}
diff --git a/tests/js/range/issue-4206-1.js b/tests/js/range/issue-4206-1.js
new file mode 100644
index 000000000000..6bb8321509f3
--- /dev/null
+++ b/tests/js/range/issue-4206-1.js
@@ -0,0 +1,3 @@
+export default c<<<PRETTIER_RANGE_START>>>lass Foo{
+/**/<<<PRETTIER_RANGE_END>>>
+}
diff --git a/tests/js/range/issue-4206-2.js b/tests/js/range/issue-4206-2.js
new file mode 100644
index 000000000000..903f8ced22a1
--- /dev/null
+++ b/tests/js/range/issue-4206-2.js
@@ -0,0 +1,3 @@
+export default f<<<PRETTIER_RANGE_START>>>unction Foo(){<<<PRETTIER_RANGE_END>>>
+/**/
+}
diff --git a/tests/js/range/issue-4206-3.js b/tests/js/range/issue-4206-3.js
new file mode 100644
index 000000000000..a00548931f08
--- /dev/null
+++ b/tests/js/range/issue-4206-3.js
@@ -0,0 +1,3 @@
+/* */ function F<<<PRETTIER_RANGE_START>>>oo(){
+/**/
+}<<<PRETTIER_RANGE_END>>>
diff --git a/tests/js/range/issue-4206-4.js b/tests/js/range/issue-4206-4.js
new file mode 100644
index 000000000000..6ed9c0c036b9
--- /dev/null
+++ b/tests/js/range/issue-4206-4.js
@@ -0,0 +1,3 @@
+/* */ class Foo{
+<<<PRETTIER_RANGE_START>>>/**/
+}<<<PRETTIER_RANGE_END>>>
diff --git a/tests/js/range/issue-7082.js b/tests/js/range/issue-7082.js
new file mode 100644
index 000000000000..ac13aea669a3
--- /dev/null
+++ b/tests/js/range/issue-7082.js
@@ -0,0 +1,3 @@
+export const Button = styled.<<<PRETTIER_RANGE_START>>>button`
+color<<<PRETTIER_RANGE_END>>>: blue;
+`;
diff --git a/tests/typescript/range/__snapshots__/jsfmt.spec.js.snap b/tests/typescript/range/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000000..e2e097c1cc2a
--- /dev/null
+++ b/tests/typescript/range/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,51 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`issue-4926.ts format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+class Foo {
+    /** Does this key match a given MinimalKey extending object? */
+    match(keyevent) {
+        // 'in' doesn't include prototypes, so it's safe for this object.
+        for (let attr in this) {
+            if (this[attr] !== keyevent[attr]) return false
+        }
+        return true
+    }
+}
+
+=====================================output=====================================
+class Foo {
+    /** Does this key match a given MinimalKey extending object? */
+    match(keyevent) {
+      // 'in' doesn't include prototypes, so it's safe for this object.
+      for (let attr in this) {
+        if (this[attr] !== keyevent[attr]) return false;
+      }
+      return true;
+    }
+}
+
+================================================================================
+`;
+
+exports[`issue-7148.ts format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+export default class Test {
+  private obj = { isTest: true }
+}
+
+=====================================output=====================================
+export default class Test {
+  private obj = { isTest: true };
+}
+
+================================================================================
+`;
diff --git a/tests/typescript/range/issue-4926.ts b/tests/typescript/range/issue-4926.ts
new file mode 100644
index 000000000000..5844c3303ee8
--- /dev/null
+++ b/tests/typescript/range/issue-4926.ts
@@ -0,0 +1,10 @@
+class Foo {
+    /** Does this key match a given MinimalKey extending object? */
+    match(keyevent) {
+<<<PRETTIER_RANGE_START>>>        // 'in' doesn't include prototypes, so it's safe for this object.
+        for (let attr in this) {
+            if (this[attr] !== keyevent[attr]) return false
+        }
+        return true
+    }<<<PRETTIER_RANGE_END>>>
+}
diff --git a/tests/typescript/range/issue-7148.ts b/tests/typescript/range/issue-7148.ts
new file mode 100644
index 000000000000..f0ec2da05dc2
--- /dev/null
+++ b/tests/typescript/range/issue-7148.ts
@@ -0,0 +1,3 @@
+export default class Test {
+ <<<PRETTIER_RANGE_START>>> private obj = { isTest: true <<<PRETTIER_RANGE_END>>>}
+}
diff --git a/tests/typescript/range/jsfmt.spec.js b/tests/typescript/range/jsfmt.spec.js
new file mode 100644
index 000000000000..2ea3bb6eb2e4
--- /dev/null
+++ b/tests/typescript/range/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(__dirname, ["typescript"]);