Repository: prettier/prettier (JavaScript)
Date: 2017-01-25 16:48:37+00:00
Task type: Bug Fix

Issue:
Decorators for MobX put on separate lines
I understand that class decorators are usually put on their own line above the class, however the standard style with MobX annotations within a class is to put them on the same line as what they annotate. [Example](https://mobx.js.org/refguide/observable-decorator.html):

![screen shot 2017-01-19 at 15 25 22](https://cloud.githubusercontent.com/assets/61575/22110313/88ee7406-de5b-11e6-9121-ab2938885b87.png)

Currently prettier splits all the lines that have decorators, making it look very messy.

https://jlongster.github.io/prettier/#%7B%22content%22%3A%22import%20%7Bobservable%7D%20from%20%5C%22mobx%5C%22%3B%5Cn%5Cnclass%20OrderLine%20%7B%5Cn%20%20%20%20%40observable%20price%3Anumber%20%3D%200%3B%5Cn%20%20%20%20%40observable%20amount%3Anumber%20%3D%201%3B%5Cn%5Cn%20%20%20%20constructor(price)%20%7B%5Cn%20%20%20%20%20%20%20%20this.price%20%3D%20price%3B%5Cn%20%20%20%20%7D%5Cn%5Cn%20%20%20%20%40computed%20get%20total()%20%7B%5Cn%20%20%20%20%20%20%20%20return%20this.price%20*%20this.amount%3B%5Cn%20%20%20%20%7D%5Cn%7D%22%2C%22options%22%3A%7B%22printWidth%22%3A80%2C%22tabWidth%22%3A2%2C%22singleQuote%22%3Afalse%2C%22trailingComma%22%3Afalse%2C%22bracketSpacing%22%3Atrue%7D%7D


Issue discussion:
We could maybe do it if there is a only 1 and it's decorating a property. I'm not sure about getters or functions. I thought the standard style was to put them on top.

Solution patch:
diff --git a/src/printer.js b/src/printer.js
index 28990dde8b65..70bcb708f02c 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"
     );


Test patch:
diff --git a/tests/decorators/__snapshots__/jsfmt.spec.js.snap b/tests/decorators/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000000..7f0de6828c6c
--- /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 000000000000..939578260648
--- /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 000000000000..adb461b0ef44
--- /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 000000000000..3a41e7b8dd78
--- /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 000000000000..481658f81500
--- /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 {}