Repository: prettier/prettier (JavaScript)
Date: 2017-02-28 04:39:13+00:00
Task type: Bug Fix

Issue:
Unstable behaviours when using comments around variables
Hi, thank you for the great tool!

I have seen that @vjeux reported some unstable behaviours and thought we shouldn't rely on finding them by coincidence so I have written a tool that runs _prettier_ on a file and then runs it again on the prettified output. If the two passes are not the same it displays a diff.
The tool is here: https://github.com/timjacobi/prettier-unstable-finder

I ran the tool against the entire AngularJS code base and the good news is that it only found one unstable behaviour that hasn't been reported yet. Most other findings were related to comments in combination with the ternary operator as reported in https://github.com/jlongster/prettier/issues/617


Original source:
```js
var fnString =
      // The build and minification steps remove the string "use strict" from the code, but this is done using a regex.
      // This is a workaround for this until we do a better job at only removing the prefix only when we should.
      '"' + this.USE + ' ' + this.STRICT + '";\n' +
      this.filterPrefix() +
      'var fn=' + this.generateFunction('fn', 's,l,a,i') +
      extra +
      this.watchFns() +
      'return fn;';
```

Pass 1:
```js
var fnString = // The build and minification steps remove the string "use strict" from the code, but this is done using a regex.
// This is a workaround for this until we do a better job at only removing the prefix only when we should.
'"' +
  this.USE +
  ' ' +
  this.STRICT +
  '";\n' +
  this.filterPrefix() +
  'var fn=' +
  this.generateFunction('fn', 's,l,a,i') +
  extra +
  this.watchFns() +
  'return fn;';
```

Pass 2:
```js
var fnString = // This is a workaround for this until we do a better job at only removing the prefix only when we should. // The build and minification steps remove the string "use strict" from the code, but this is done using a regex.
'"' +
  this.USE +
  ' ' +
  this.STRICT +
  '";\n' +
  this.filterPrefix() +
  'var fn=' +
  this.generateFunction('fn', 's,l,a,i') +
  extra +
  this.watchFns() +
  'return fn;';
```

Pass 3
```js
var fnString = '"' + // This is a workaround for this until we do a better job at only removing the prefix only when we should. // The build and minification steps remove the string "use strict" from the code, but this is done using a regex.
  this.USE +
  ' ' +
  this.STRICT +
  '";\n' +
  this.filterPrefix() +
  'var fn=' +
  this.generateFunction('fn', 's,l,a,i') +
  extra +
  this.watchFns() +
  'return fn;';
```


Issue discussion:
FYI, you can now do `prettier --debug-check file` in order to do that since 3 days ago :) https://github.com/jlongster/prettier/commit/4df5250f74953d88fd800464aaa1c861364c252e
Grand, thank you :)
This is great news for the angular codebase! Hopefully by the end of next week we'll have most of them fixed :)
Note: this only affects comments that are on the first line, comments in the middle are working fine.

Solution patch:
diff --git a/src/printer.js b/src/printer.js
index 92123de39e57..9da8b83869cd 100644
--- a/src/printer.js
+++ b/src/printer.js
@@ -192,8 +192,9 @@ function genericPrintNoParens(path, options, print) {
           path.call(print, "left"),
           " ",
           n.operator,
-          " ",
-          path.call(print, "right")
+          hasLeadingOwnLineComment(options.originalText, n.right) ?
+            indent(options.tabWidth, concat([hardline, path.call(print, "right")])) :
+            concat([" ", path.call(print, "right")]),
         ])
       );
     case "BinaryExpression":
@@ -204,6 +205,9 @@ function genericPrintNoParens(path, options, print) {
 
       // Avoid indenting sub-expressions in if/etc statements.
       if (
+        hasLeadingOwnLineComment(options.originalText, n) &&
+          (parent.type === "AssignmentExpression" ||
+            parent.type === "VariableDeclarator") ||
         shouldInlineLogicalExpression(n) ||
         (n !== parent.body &&
           (parent.type === "IfStatement" ||
@@ -945,7 +949,13 @@ function genericPrintNoParens(path, options, print) {
       return group(concat(parts));
     case "VariableDeclarator":
       return n.init
-        ? concat([path.call(print, "id"), " = ", path.call(print, "init")])
+        ? concat([
+            path.call(print, "id"),
+            " =",
+            hasLeadingOwnLineComment(options.originalText, n.init) ?
+              indent(options.tabWidth, concat([hardline, path.call(print, "init")])) :
+              concat([" ", path.call(print, "init")]),
+          ])
         : path.call(print, "id");
     case "WithStatement":
       return concat([
@@ -2888,6 +2898,16 @@ function isLastStatement(path) {
   return body && body[body.length - 1] === node;
 }
 
+function hasLeadingOwnLineComment(text, node) {
+  const res = node.comments &&
+    node.comments.some(comment =>
+      comment.leading &&
+      util.hasNewline(text, util.locStart(comment), { backwards: true }) &&
+      util.hasNewline(text, util.locEnd(comment))
+    );
+  return res;
+}
+
 // Hack to differentiate between the following two which have the same ast
 // type T = { method: () => void };
 // type T = { method(): void };


Test patch:
diff --git a/tests/assignment_comments/__snapshots__/jsfmt.spec.js.snap b/tests/assignment_comments/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000000..eda7518c928a
--- /dev/null
+++ b/tests/assignment_comments/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,90 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`assignment_comments.js 1`] = `
+"fnString =
+  // Comment
+  'some' + 'long' + 'string';
+
+var fnString =
+  // Comment
+  'some' + 'long' + 'string';
+
+var fnString =
+  // Comment
+
+  'some' + 'long' + 'string';
+
+var fnString =
+
+  // Comment
+
+  'some' + 'long' + 'string';
+
+var fnString =
+  /* comment */
+  'some' + 'long' + 'string';
+
+var fnString =
+  /**
+   * multi-line
+   */
+  'some' + 'long' + 'string';
+
+var fnString =
+  /* inline */ 'some' + 'long' + 'string' + 'some' + 'long' + 'string' + 'some' + 'long' + 'string' + 'some' + 'long' + 'string';
+
+var fnString = // Comment
+  // Comment
+  'some' + 'long' + 'string';
+
+var fnString = // Comment
+  'some' + 'long' + 'string';
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+fnString =
+  // Comment
+  \\"some\\" + \\"long\\" + \\"string\\";
+
+var fnString =
+  // Comment
+  \\"some\\" + \\"long\\" + \\"string\\";
+
+var fnString =
+  // Comment
+
+  \\"some\\" + \\"long\\" + \\"string\\";
+
+var fnString =
+  // Comment
+
+  \\"some\\" + \\"long\\" + \\"string\\";
+
+var fnString =
+  /* comment */
+  \\"some\\" + \\"long\\" + \\"string\\";
+
+var fnString =
+  /**
+   * multi-line
+   */
+  \\"some\\" + \\"long\\" + \\"string\\";
+
+var fnString = /* inline */ \\"some\\" +
+  \\"long\\" +
+  \\"string\\" +
+  \\"some\\" +
+  \\"long\\" +
+  \\"string\\" +
+  \\"some\\" +
+  \\"long\\" +
+  \\"string\\" +
+  \\"some\\" +
+  \\"long\\" +
+  \\"string\\";
+
+var fnString = // Comment
+  // Comment
+  \\"some\\" + \\"long\\" + \\"string\\";
+
+var fnString = \\"some\\" + \\"long\\" + \\"string\\"; // Comment
+"
+`;
diff --git a/tests/assignment_comments/assignment_comments.js b/tests/assignment_comments/assignment_comments.js
new file mode 100644
index 000000000000..f5194f319afe
--- /dev/null
+++ b/tests/assignment_comments/assignment_comments.js
@@ -0,0 +1,38 @@
+fnString =
+  // Comment
+  'some' + 'long' + 'string';
+
+var fnString =
+  // Comment
+  'some' + 'long' + 'string';
+
+var fnString =
+  // Comment
+
+  'some' + 'long' + 'string';
+
+var fnString =
+
+  // Comment
+
+  'some' + 'long' + 'string';
+
+var fnString =
+  /* comment */
+  'some' + 'long' + 'string';
+
+var fnString =
+  /**
+   * multi-line
+   */
+  'some' + 'long' + 'string';
+
+var fnString =
+  /* inline */ 'some' + 'long' + 'string' + 'some' + 'long' + 'string' + 'some' + 'long' + 'string' + 'some' + 'long' + 'string';
+
+var fnString = // Comment
+  // Comment
+  'some' + 'long' + 'string';
+
+var fnString = // Comment
+  'some' + 'long' + 'string';
diff --git a/tests/assignment_comments/jsfmt.spec.js b/tests/assignment_comments/jsfmt.spec.js
new file mode 100644
index 000000000000..989047bccc52
--- /dev/null
+++ b/tests/assignment_comments/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(__dirname);