Repository: jlongster/prettier (js)

Issue:
Breaking on printWidth when using class annotations
Giving a code like
```js
class MyContractSelectionWidget extends React.Component<void,  MyContractSelectionWidgetPropsType, void> {
```
The printer puts all the code on a single line( printWidth limit is configured properly) leading to ESLint complaining that the line is too long.

Pull request description:
Add break points for class declaration
```js
class A extends B implements C {
  method() {}
}
```

now outputs

```js
class A
  extends B
  implements C {
  method() {}
}
```

Fixes #147

Patch:
diff --git a/README.md b/README.md
index 422c69695..9d44a2902 100644
--- a/README.md
+++ b/README.md
@@ -207,6 +207,16 @@ features enabled, but you can also use
 All of JSX and Flow syntax is supported. In fact, the test suite in
 `tests` *is* the entire Flow test suite and they all pass.
 
+## Related Projects
+
+- [`prettier-eslint`](https://github.com/kentcdodds/prettier-eslint)
+passes `prettier` output to `eslint --fix`
+- [`prettier-standard-formatter`](https://github.com/dtinth/prettier-standard-formatter)
+passes `prettier` output to `standard --fix`
+- [`prettier-with-tabs`](https://github.com/arijs/prettier-with-tabs)
+allows you to configure prettier to use `tabs`
+
+
 ## Technical Details
 
 This printer is a fork of
diff --git a/src/printer.js b/src/printer.js
index 28990dde8..9681b45aa 100644
--- a/src/printer.js
+++ b/src/printer.js
@@ -51,9 +51,12 @@ function genericPrint(path, options, printPath) {
       // responsible for printing node.decorators.
       !util.getParentExportDeclaration(path)
   ) {
+    const separator = node.decorators.length === 1 &&
+      node.decorators[0].expression.type === "Identifier"
+      ? " " : hardline;
     path.each(
       function(decoratorPath) {
-        parts.push(printPath(decoratorPath), line);
+        parts.push(printPath(decoratorPath), separator);
       },
       "decorators"
     );
@@ -115,6 +118,9 @@ function genericPrintNoParens(path, options, print) {
         path.each(
           function(childPath) {
             parts.push(print(childPath), ";", hardline);
+            if (util.newlineExistsAfter(options.originalText, util.locEnd(childPath.getValue()))) {
+              parts.push(hardline);
+            }
           },
           "directives"
         );
@@ -627,7 +633,7 @@ function genericPrintNoParens(path, options, print) {
                   indent(options.tabWidth, concat([softline, printedValue]))
                 ])
               ),
-              line,
+              softline,
               ifBreak(")")
             ])
           );
@@ -1196,7 +1202,7 @@ function genericPrintNoParens(path, options, print) {
       return concat(parts);
     case "ClassDeclaration":
     case "ClassExpression":
-      return concat(printClass(path, print));
+      return concat(printClass(path, options, print));
     case "TemplateElement":
       return join(literalline, n.value.raw.split("\n"));
     case "TemplateLiteral":
@@ -1285,7 +1291,7 @@ function genericPrintNoParens(path, options, print) {
     case "BooleanLiteralTypeAnnotation":
       return "" + n.value;
     case "DeclareClass":
-      return printFlowDeclaration(path, printClass(path, print));
+      return printFlowDeclaration(path, printClass(path, options, print));
     case "DeclareFunction":
       return printFlowDeclaration(path, [
         "function ",
@@ -1954,7 +1960,7 @@ function printFlowDeclaration(path, parts) {
   return concat(parts);
 }
 
-function printClass(path, print) {
+function printClass(path, options, print) {
   const n = path.getValue();
   const parts = [ "class" ];
 
@@ -1962,18 +1968,24 @@ function printClass(path, print) {
     parts.push(" ", path.call(print, "id"), path.call(print, "typeParameters"));
   }
 
+  const partsGroup = [];
   if (n.superClass) {
-    parts.push(
-      " extends ",
+    partsGroup.push(
+      line,
+      "extends ",
       path.call(print, "superClass"),
       path.call(print, "superTypeParameters")
     );
   } else if (n.extends && n.extends.length > 0) {
-    parts.push(" extends ", join(", ", path.map(print, "extends")));
+    partsGroup.push(line, "extends ", join(", ", path.map(print, "extends")));
   }
 
   if (n["implements"] && n["implements"].length > 0) {
-    parts.push(" implements ", join(", ", path.map(print, "implements")));
+    partsGroup.push(line, "implements ", join(", ", path.map(print, "implements")));
+  }
+
+  if (partsGroup.length > 0) {
+    parts.push(group(indent(options.tabWidth, concat(partsGroup))));
   }
 
   parts.push(" ", path.call(print, "body"));


Test patch:
diff --git a/tests/classes/__snapshots__/jsfmt.spec.js.snap b/tests/classes/__snapshots__/jsfmt.spec.js.snap
index b2bd35aad..d5a3c2580 100644
--- a/tests/classes/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/classes/__snapshots__/jsfmt.spec.js.snap
@@ -11,6 +11,19 @@ exports[`test binary.js 1`] = `
 "
 `;
 
+exports[`test break.js 1`] = `
+"class MyContractSelectionWidget extends React.Component<void,  MyContractSelectionWidgetPropsType, void> implements SomethingLarge {
+  method() {}
+}
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+class MyContractSelectionWidget
+  extends React.Component<void, MyContractSelectionWidgetPropsType, void>
+  implements SomethingLarge {
+  method() {}
+}
+"
+`;
+
 exports[`test call.js 1`] = `
 "(class {})(class {});
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/classes/break.js b/tests/classes/break.js
new file mode 100644
index 000000000..5d2c5c507
--- /dev/null
+++ b/tests/classes/break.js
@@ -0,0 +1,3 @@
+class MyContractSelectionWidget extends React.Component<void,  MyContractSelectionWidgetPropsType, void> implements SomethingLarge {
+  method() {}
+}
diff --git a/tests/decorators/__snapshots__/jsfmt.spec.js.snap b/tests/decorators/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000..7f0de6828
--- /dev/null
+++ b/tests/decorators/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,64 @@
+exports[`test mobx.js 1`] = `
+"import {observable} from \"mobx\";
+
+@observer class OrderLine {
+  @observable price:number = 0;
+  @observable amount:number = 1;
+
+  constructor(price) {
+    this.price = price;
+  }
+
+  @computed get total() {
+    return this.price * this.amount;
+  }
+}
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+import { observable } from \"mobx\";
+
+@observer class OrderLine {
+  @observable price: number = 0;
+  @observable amount: number = 1;
+
+  constructor(price) {
+    this.price = price;
+  }
+
+  @computed get total() {
+    return this.price * this.amount;
+  }
+}
+"
+`;
+
+exports[`test multiple.js 1`] = `
+"const dog = {
+  @readonly
+  @nonenumerable
+  @doubledValue
+  legs: 4
+};
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+const dog = {
+  @readonly
+  @nonenumerable
+  @doubledValue
+  legs: 4
+};
+"
+`;
+
+exports[`test redux.js 1`] = `
+"@connect(mapStateToProps, mapDispatchToProps)
+export class MyApp extends React.Component {}
+
+@connect(state => ({ todos: state.todos }))
+export class Home extends React.Component {}
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+@connect(mapStateToProps, mapDispatchToProps)
+export class MyApp extends React.Component {}
+
+@connect(state => ({ todos: state.todos }))
+export class Home extends React.Component {}
+"
+`;
diff --git a/tests/decorators/jsfmt.spec.js b/tests/decorators/jsfmt.spec.js
new file mode 100644
index 000000000..939578260
--- /dev/null
+++ b/tests/decorators/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(__dirname, {parser: 'babylon'});
diff --git a/tests/decorators/mobx.js b/tests/decorators/mobx.js
new file mode 100644
index 000000000..adb461b0e
--- /dev/null
+++ b/tests/decorators/mobx.js
@@ -0,0 +1,14 @@
+import {observable} from "mobx";
+
+@observer class OrderLine {
+  @observable price:number = 0;
+  @observable amount:number = 1;
+
+  constructor(price) {
+    this.price = price;
+  }
+
+  @computed get total() {
+    return this.price * this.amount;
+  }
+}
diff --git a/tests/decorators/multiple.js b/tests/decorators/multiple.js
new file mode 100644
index 000000000..3a41e7b8d
--- /dev/null
+++ b/tests/decorators/multiple.js
@@ -0,0 +1,6 @@
+const dog = {
+  @readonly
+  @nonenumerable
+  @doubledValue
+  legs: 4
+};
diff --git a/tests/decorators/redux.js b/tests/decorators/redux.js
new file mode 100644
index 000000000..481658f81
--- /dev/null
+++ b/tests/decorators/redux.js
@@ -0,0 +1,5 @@
+@connect(mapStateToProps, mapDispatchToProps)
+export class MyApp extends React.Component {}
+
+@connect(state => ({ todos: state.todos }))
+export class Home extends React.Component {}
diff --git a/tests/directives/__snapshots__/jsfmt.spec.js.snap b/tests/directives/__snapshots__/jsfmt.spec.js.snap
index f680d0532..b63c61eb4 100644
--- a/tests/directives/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/directives/__snapshots__/jsfmt.spec.js.snap
@@ -1,3 +1,40 @@
+exports[`test newline.js 1`] = `
+"/* @flow */
+
+\"use strict\";
+
+import a from \"a\";
+
+a();
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+/* @flow */
+
+\"use strict\";
+
+import a from \"a\";
+
+a();
+"
+`;
+
+exports[`test newline.js 2`] = `
+"/* @flow */
+
+\"use strict\";
+
+import a from \"a\";
+
+a();
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+/* @flow */
+\"use strict\";
+
+import a from \"a\";
+
+a();
+"
+`;
+
 exports[`test test.js 1`] = `
 "\"use strict\";
 
@@ -12,3 +49,18 @@ function fn() {
 }
 "
 `;
+
+exports[`test test.js 2`] = `
+"\"use strict\";
+
+function fn() {
+  \"use strict\";
+}
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+\"use strict\";
+
+function fn() {
+  \"use strict\";
+}
+"
+`;
diff --git a/tests/directives/jsfmt.spec.js b/tests/directives/jsfmt.spec.js
index 989047bcc..d5ec770a9 100644
--- a/tests/directives/jsfmt.spec.js
+++ b/tests/directives/jsfmt.spec.js
@@ -1,1 +1,2 @@
 run_spec(__dirname);
+run_spec(__dirname, {parser: 'babylon'});
diff --git a/tests/directives/newline.js b/tests/directives/newline.js
new file mode 100644
index 000000000..8da10321b
--- /dev/null
+++ b/tests/directives/newline.js
@@ -0,0 +1,7 @@
+/* @flow */
+
+"use strict";
+
+import a from "a";
+
+a();
diff --git a/tests/flow/logical/__snapshots__/jsfmt.spec.js.snap b/tests/flow/logical/__snapshots__/jsfmt.spec.js.snap
index f3cab28a7..2ff1eac92 100644
--- a/tests/flow/logical/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/logical/__snapshots__/jsfmt.spec.js.snap
@@ -901,14 +901,14 @@ function logical12b(y: number): number {
  */
 function logical13(x: number): Array<{ x: string }> {
   return [
-    { x: x && \"bar\"  },
-    { x: true && \"bar\"  },
-    { x: true && false  },
-    { x: false && false  },
-    { x: 1 && \"bar\"  },
-    { x: \"foo\" && \"bar\"  },
-    { x: \"foo\" && \"bar\"  },
-    { x: \"foo\" && \"bar\"  }
+    { x: x && \"bar\" },
+    { x: true && \"bar\" },
+    { x: true && false },
+    { x: false && false },
+    { x: 1 && \"bar\" },
+    { x: \"foo\" && \"bar\" },
+    { x: \"foo\" && \"bar\" },
+    { x: \"foo\" && \"bar\" }
   ];
 }
 
@@ -917,14 +917,14 @@ function logical13(x: number): Array<{ x: string }> {
  */
 function logical14(x: number): Array<{ x: string }> {
   return [
-    { x: x || \"bar\"  },
-    { x: false || \"bar\"  },
-    { x: false || true  },
-    { x: true || false  },
-    { x: 0 || \"bar\"  },
-    { x: \"foo\" || \"bar\"  },
-    { x: \"foo\" || \"bar\"  },
-    { x: \"foo\" || \"bar\"  }
+    { x: x || \"bar\" },
+    { x: false || \"bar\" },
+    { x: false || true },
+    { x: true || false },
+    { x: 0 || \"bar\" },
+    { x: \"foo\" || \"bar\" },
+    { x: \"foo\" || \"bar\" },
+    { x: \"foo\" || \"bar\" }
   ];
 }
 
diff --git a/tests/trailing_comma/__snapshots__/jsfmt.spec.js.snap b/tests/trailing_comma/__snapshots__/jsfmt.spec.js.snap
index d6a325b0a..b03422f52 100644
--- a/tests/trailing_comma/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/trailing_comma/__snapshots__/jsfmt.spec.js.snap
@@ -77,6 +77,11 @@ const aLong = {
   },
   dHasALongName: \'a-long-value-too\'
 };
+
+const aLong = {
+  dHasALongName: \'a-long-value-too\',
+  eHasABooleanExpression: a === a,
+};
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 const a = { b: true, c: { c1: \"hello\" }, d: false };
 
@@ -88,6 +93,11 @@ const aLong = {
   },
   dHasALongName: \"a-long-value-too\"
 };
+
+const aLong = {
+  dHasALongName: \"a-long-value-too\",
+  eHasABooleanExpression: a === a
+};
 "
 `;
 
@@ -108,6 +118,11 @@ const aLong = {
   },
   dHasALongName: \'a-long-value-too\'
 };
+
+const aLong = {
+  dHasALongName: \'a-long-value-too\',
+  eHasABooleanExpression: a === a,
+};
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 const a = { b: true, c: { c1: \"hello\" }, d: false };
 
@@ -119,5 +134,10 @@ const aLong = {
   },
   dHasALongName: \"a-long-value-too\",
 };
+
+const aLong = {
+  dHasALongName: \"a-long-value-too\",
+  eHasABooleanExpression: a === a,
+};
 "
 `;
diff --git a/tests/trailing_comma/object.js b/tests/trailing_comma/object.js
index 9bdc3b1f2..10c074ca9 100644
--- a/tests/trailing_comma/object.js
+++ b/tests/trailing_comma/object.js
@@ -14,3 +14,8 @@ const aLong = {
   },
   dHasALongName: 'a-long-value-too'
 };
+
+const aLong = {
+  dHasALongName: 'a-long-value-too',
+  eHasABooleanExpression: a === a,
+};