prettier 1.0.0.pre.rc1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,6 @@
1
1
  const {
2
2
  concat,
3
3
  group,
4
- hardline,
5
4
  ifBreak,
6
5
  indent,
7
6
  join,
@@ -9,7 +8,7 @@ const {
9
8
  softline
10
9
  } = require("../prettier");
11
10
 
12
- const { getTrailingComma } = require("../utils");
11
+ const { getTrailingComma, printEmptyCollection } = require("../utils");
13
12
 
14
13
  // Checks that every argument within this args node is a string_literal node
15
14
  // that has no spaces or interpolations. This means we're dealing with an array
@@ -18,32 +17,35 @@ const { getTrailingComma } = require("../utils");
18
17
  // ['a', 'b', 'c']
19
18
  //
20
19
  function isStringArray(args) {
21
- return args.body.every((arg) => {
22
- // We want to verify that every node inside of this array is a string
23
- // literal. We also want to make sure none of them have comments attached.
24
- if (arg.type !== "string_literal" || arg.comments) {
25
- return false;
26
- }
27
-
28
- // If the string has multiple parts (meaning plain string content but also
29
- // interpolated content) then we know it's not a simple string.
30
- if (arg.body.length !== 1) {
31
- return false;
32
- }
33
-
34
- const part = arg.body[0];
35
-
36
- // If the only part of this string is not @tstring_content then it's
37
- // interpolated, so again we can return false.
38
- if (part.type !== "@tstring_content") {
39
- return false;
40
- }
41
-
42
- // Finally, verify that the string doesn't contain a space, an escape
43
- // character, or brackets so that we know it can be put into a string
44
- // literal array.
45
- return !/[\s\\[\]]/.test(part.body);
46
- });
20
+ return (
21
+ args.body.length > 1 &&
22
+ args.body.every((arg) => {
23
+ // We want to verify that every node inside of this array is a string
24
+ // literal. We also want to make sure none of them have comments attached.
25
+ if (arg.type !== "string_literal" || arg.comments) {
26
+ return false;
27
+ }
28
+
29
+ // If the string has multiple parts (meaning plain string content but also
30
+ // interpolated content) then we know it's not a simple string.
31
+ if (arg.body.length !== 1) {
32
+ return false;
33
+ }
34
+
35
+ const part = arg.body[0];
36
+
37
+ // If the only part of this string is not @tstring_content then it's
38
+ // interpolated, so again we can return false.
39
+ if (part.type !== "@tstring_content") {
40
+ return false;
41
+ }
42
+
43
+ // Finally, verify that the string doesn't contain a space, an escape
44
+ // character, or brackets so that we know it can be put into a string
45
+ // literal array.
46
+ return !/[\s\\[\]]/.test(part.body);
47
+ })
48
+ );
47
49
  }
48
50
 
49
51
  // Checks that every argument within this args node is a symbol_literal node (as
@@ -53,8 +55,9 @@ function isStringArray(args) {
53
55
  // [:a, :b, :c]
54
56
  //
55
57
  function isSymbolArray(args) {
56
- return args.body.every(
57
- (arg) => arg.type === "symbol_literal" && !arg.comments
58
+ return (
59
+ args.body.length > 1 &&
60
+ args.body.every((arg) => arg.type === "symbol_literal" && !arg.comments)
58
61
  );
59
62
  }
60
63
 
@@ -89,24 +92,6 @@ function printSpecialArray(start) {
89
92
  };
90
93
  }
91
94
 
92
- function printEmptyArrayWithComments(path, opts) {
93
- const arrayNode = path.getValue();
94
-
95
- const printComment = (commentPath, index) => {
96
- arrayNode.comments[index].printed = true;
97
- return opts.printer.printComment(commentPath);
98
- };
99
-
100
- return concat([
101
- "[",
102
- indent(
103
- concat([hardline, join(hardline, path.map(printComment, "comments"))])
104
- ),
105
- line,
106
- "]"
107
- ]);
108
- }
109
-
110
95
  // An array node is any literal array in Ruby. This includes all of the special
111
96
  // array literals as well as regular arrays. If it is a special array literal
112
97
  // then it will have one child that represents the special array, otherwise it
@@ -118,12 +103,12 @@ function printArray(path, opts, print) {
118
103
  // If there is no inner arguments node, then we're dealing with an empty
119
104
  // array, so we can go ahead and return.
120
105
  if (args === null) {
121
- return array.comments ? printEmptyArrayWithComments(path, opts) : "[]";
106
+ return printEmptyCollection(path, opts, "[", "]");
122
107
  }
123
108
 
124
109
  // If we have an array that contains only simple string literals with no
125
110
  // spaces or interpolation, then we're going to print a %w array.
126
- if (isStringArray(args)) {
111
+ if (opts.rubyArrayLiteral && isStringArray(args)) {
127
112
  const printString = (stringPath) => stringPath.call(print, "body", 0);
128
113
  const parts = path.map(printString, "body", 0, "body");
129
114
 
@@ -132,7 +117,7 @@ function printArray(path, opts, print) {
132
117
 
133
118
  // If we have an array that contains only simple symbol literals with no
134
119
  // interpolation, then we're going to print a %i array.
135
- if (isSymbolArray(args)) {
120
+ if (opts.rubyArrayLiteral && isSymbolArray(args)) {
136
121
  const printSymbol = (symbolPath) => symbolPath.call(print, "body", 0);
137
122
  const parts = path.map(printSymbol, "body", 0, "body");
138
123
 
@@ -1,5 +1,5 @@
1
1
  const { concat, group, indent, join, line } = require("../prettier");
2
- const { concatBody, first, skipAssignIndent } = require("../utils");
2
+ const { first, skipAssignIndent } = require("../utils");
3
3
 
4
4
  function printAssign(path, opts, print) {
5
5
  const [_targetNode, valueNode] = path.getValue().body;
@@ -31,9 +31,17 @@ function printOpAssign(path, opts, print) {
31
31
  );
32
32
  }
33
33
 
34
+ function printVarField(path, opts, print) {
35
+ if (path.getValue().body) {
36
+ return path.call(print, "body", 0);
37
+ }
38
+
39
+ return "*";
40
+ }
41
+
34
42
  module.exports = {
35
43
  assign: printAssign,
36
44
  opassign: printOpAssign,
37
- var_field: concatBody,
45
+ var_field: printVarField,
38
46
  var_ref: first
39
47
  };
@@ -6,15 +6,15 @@ const {
6
6
  indent,
7
7
  softline
8
8
  } = require("../prettier");
9
- const { concatBody, first, makeCall, noIndent } = require("../utils");
9
+ const { first, makeCall, noIndent } = require("../utils");
10
10
 
11
11
  const toProc = require("../toProc");
12
12
 
13
- const chained = ["call", "method_add_arg"];
13
+ const chained = ["call", "method_add_arg", "method_add_block"];
14
14
 
15
15
  function printCall(path, opts, print) {
16
- const callNode = path.getValue();
17
- const [receiverNode, _operatorNode, messageNode] = callNode.body;
16
+ const node = path.getValue();
17
+ const [receiverNode, _operatorNode, messageNode] = node.body;
18
18
 
19
19
  const receiverDoc = path.call(print, "body", 0);
20
20
  const operatorDoc = makeCall(path, opts, print);
@@ -24,21 +24,13 @@ function printCall(path, opts, print) {
24
24
  // call syntax so if `call` is implicit, we don't print it out.
25
25
  const messageDoc = messageNode === "call" ? "" : path.call(print, "body", 2);
26
26
 
27
- // For certain left sides of the call nodes, we want to attach directly to
28
- // the } or end.
29
- if (noIndent.includes(receiverNode.type)) {
30
- return concat([receiverDoc, operatorDoc, messageDoc]);
31
- }
32
-
33
27
  // The right side of the call node, as in everything including the operator
34
28
  // and beyond.
35
- const rightSideDoc = indent(
36
- concat([
37
- receiverNode.comments ? hardline : softline,
38
- operatorDoc,
39
- messageDoc
40
- ])
41
- );
29
+ const rightSideDoc = concat([
30
+ receiverNode.comments ? hardline : softline,
31
+ operatorDoc,
32
+ messageDoc
33
+ ]);
42
34
 
43
35
  // Get a reference to the parent node so we can check if we're inside a chain
44
36
  const parentNode = path.getParentNode();
@@ -46,33 +38,56 @@ function printCall(path, opts, print) {
46
38
  // If our parent node is a chained node then we're not going to group the
47
39
  // right side of the expression, as we want to have a nice multi-line layout.
48
40
  if (chained.includes(parentNode.type)) {
49
- parentNode.chain = (callNode.chain || 0) + 1;
50
- parentNode.breakDoc = (callNode.breakDoc || [receiverDoc]).concat(
51
- rightSideDoc
52
- );
41
+ parentNode.chain = (node.chain || 0) + 1;
42
+ parentNode.callChain = (node.callChain || 0) + 1;
43
+ parentNode.breakDoc = (node.breakDoc || [receiverDoc]).concat(rightSideDoc);
53
44
  }
54
45
 
55
46
  // If we're at the top of a chain, then we're going to print out a nice
56
47
  // multi-line layout if this doesn't break into multiple lines.
57
- if (!chained.includes(parentNode.type) && (callNode.chain || 0) >= 3) {
48
+ if (!chained.includes(parentNode.type) && (node.chain || 0) >= 3) {
58
49
  return ifBreak(
59
- group(concat(callNode.breakDoc.concat(rightSideDoc))),
50
+ group(indent(concat(node.breakDoc.concat(rightSideDoc)))),
60
51
  concat([receiverDoc, group(rightSideDoc)])
61
52
  );
62
53
  }
63
54
 
64
- return group(concat([receiverDoc, group(rightSideDoc)]));
55
+ // For certain left sides of the call nodes, we want to attach directly to
56
+ // the } or end.
57
+ if (noIndent.includes(receiverNode.type)) {
58
+ return concat([receiverDoc, operatorDoc, messageDoc]);
59
+ }
60
+
61
+ return group(concat([receiverDoc, group(indent(rightSideDoc))]));
65
62
  }
66
63
 
67
64
  function printMethodAddArg(path, opts, print) {
68
- const methodAddArgNode = path.getValue();
69
- const argNode = methodAddArgNode.body[1];
65
+ const node = path.getValue();
70
66
 
67
+ const [methodNode, argNode] = node.body;
71
68
  const [methodDoc, argsDoc] = path.map(print, "body");
72
69
 
73
70
  // You can end up here if you have a method with a ? ending, presumably
74
- // because the parser knows that it cannot be a local variable.
71
+ // because the parser knows that it cannot be a local variable. You can also
72
+ // end up here if you are explicitly using an empty set of parentheses.
75
73
  if (argsDoc.length === 0) {
74
+ // If you're using an explicit set of parentheses on something that looks
75
+ // like a constant, then we need to match that in order to maintain valid
76
+ // Ruby. For example, you could do something like Foo(), on which we would
77
+ // need to keep the parentheses to make it look like a method call.
78
+ if (methodNode.type === "fcall" && methodNode.body[0].type === "@const") {
79
+ return concat([methodDoc, "()"]);
80
+ }
81
+
82
+ // If you're using an explicit set parentheses with the special call syntax,
83
+ // then we need to explicitly print out an extra set of parentheses. For
84
+ // example, if you call something like Foo.new.() (implicitly calling the
85
+ // #call method on a new instance of the Foo class), then we have to print
86
+ // out those parentheses, otherwise we'll end up with Foo.new.
87
+ if (methodNode.type === "call" && methodNode.body[2] === "call") {
88
+ return concat([methodDoc, "()"]);
89
+ }
90
+
76
91
  return methodDoc;
77
92
  }
78
93
 
@@ -88,20 +103,30 @@ function printMethodAddArg(path, opts, print) {
88
103
  // If our parent node is a chained node then we're not going to group the
89
104
  // right side of the expression, as we want to have a nice multi-line layout.
90
105
  if (chained.includes(parentNode.type)) {
91
- parentNode.chain = (methodAddArgNode.chain || 0) + 1;
92
- parentNode.breakDoc = (methodAddArgNode.breakDoc || [methodDoc]).concat(
93
- argsDoc
94
- );
106
+ parentNode.chain = (node.chain || 0) + 1;
107
+ parentNode.breakDoc = (node.breakDoc || [methodDoc]).concat(argsDoc);
95
108
  }
96
109
 
97
110
  // If we're at the top of a chain, then we're going to print out a nice
98
111
  // multi-line layout if this doesn't break into multiple lines.
99
- if (
100
- !chained.includes(parentNode.type) &&
101
- (methodAddArgNode.chain || 0) >= 3
102
- ) {
112
+ if (!chained.includes(parentNode.type) && (node.chain || 0) >= 3) {
113
+ // This is pretty specialized behavior. Basically if we're at the top of a
114
+ // chain but we've only had method calls without arguments and now we have
115
+ // arguments, then we're effectively trying to call a method with arguments
116
+ // that is nested under a bunch of stuff. So we group together to first part
117
+ // to make it so just the arguments break. This looks like, for example:
118
+ //
119
+ // config.action_dispatch.rescue_responses.merge!(
120
+ // 'ActiveRecord::ConnectionTimeoutError' => :service_unavailable,
121
+ // 'ActiveRecord::QueryCanceled' => :service_unavailable
122
+ // )
123
+ //
124
+ if (node.callChain === node.chain) {
125
+ return concat([group(indent(concat(node.breakDoc))), group(argsDoc)]);
126
+ }
127
+
103
128
  return ifBreak(
104
- group(concat(methodAddArgNode.breakDoc.concat(argsDoc))),
129
+ group(indent(concat(node.breakDoc.concat(argsDoc)))),
105
130
  concat([methodDoc, argsDoc])
106
131
  );
107
132
  }
@@ -109,15 +134,47 @@ function printMethodAddArg(path, opts, print) {
109
134
  return concat([methodDoc, argsDoc]);
110
135
  }
111
136
 
112
- module.exports = {
113
- call: printCall,
114
- fcall: concatBody,
115
- method_add_arg: printMethodAddArg,
116
- method_add_block: (path, opts, print) => {
117
- const [method, block] = path.getValue().body;
118
- const proc = toProc(path, opts, block);
137
+ // Sorbet type annotations look like the following:
138
+ //
139
+ // {method_add_block
140
+ // [{method_add_arg
141
+ // [{fcall
142
+ // [{@ident "sig"}]},
143
+ // {args []}]},
144
+ // {brace_block [nil, {stmts}]}}]}
145
+ //
146
+ function isSorbetTypeAnnotation(node) {
147
+ const [callNode, blockNode] = node.body;
148
+
149
+ return (
150
+ callNode.type === "method_add_arg" &&
151
+ callNode.body[0].type === "fcall" &&
152
+ callNode.body[0].body[0].body === "sig" &&
153
+ callNode.body[1].type === "args" &&
154
+ callNode.body[1].body.length === 0 &&
155
+ blockNode.type === "brace_block"
156
+ );
157
+ }
119
158
 
120
- if (proc && method.type === "call") {
159
+ function printMethodAddBlock(path, opts, print) {
160
+ const node = path.getValue();
161
+
162
+ const [callNode, blockNode] = node.body;
163
+ const [callDoc, blockDoc] = path.map(print, "body");
164
+
165
+ // Very special handling here for sorbet type annotations. They look like Ruby
166
+ // code, but they're not actually Ruby code, so we're not going to mess with
167
+ // them at all.
168
+ if (isSorbetTypeAnnotation(node)) {
169
+ return opts.originalText.slice(opts.locStart(node), opts.locEnd(node));
170
+ }
171
+
172
+ // Don't bother trying to do any kind of fancy toProc transform if the option
173
+ // is disabled.
174
+ if (opts.rubyToProc) {
175
+ const proc = toProc(path, blockNode);
176
+
177
+ if (proc && callNode.type === "call") {
121
178
  return group(
122
179
  concat([
123
180
  path.call(print, "body", 0),
@@ -131,8 +188,34 @@ module.exports = {
131
188
  if (proc) {
132
189
  return path.call(print, "body", 0);
133
190
  }
191
+ }
134
192
 
135
- return concat(path.map(print, "body"));
136
- },
193
+ // Get a reference to the parent node so we can check if we're inside a chain
194
+ const parentNode = path.getParentNode();
195
+
196
+ // If our parent node is a chained node then we're not going to group the
197
+ // right side of the expression, as we want to have a nice multi-line layout.
198
+ if (chained.includes(parentNode.type)) {
199
+ parentNode.chain = (node.chain || 0) + 1;
200
+ parentNode.breakDoc = (node.breakDoc || [callDoc]).concat(blockDoc);
201
+ }
202
+
203
+ // If we're at the top of a chain, then we're going to print out a nice
204
+ // multi-line layout if this doesn't break into multiple lines.
205
+ if (!chained.includes(parentNode.type) && (node.chain || 0) >= 3) {
206
+ return ifBreak(
207
+ group(indent(concat(node.breakDoc.concat(blockDoc)))),
208
+ concat([callDoc, blockDoc])
209
+ );
210
+ }
211
+
212
+ return concat([callDoc, blockDoc]);
213
+ }
214
+
215
+ module.exports = {
216
+ call: printCall,
217
+ fcall: first,
218
+ method_add_arg: printMethodAddArg,
219
+ method_add_block: printMethodAddBlock,
137
220
  vcall: first
138
221
  };
@@ -28,13 +28,17 @@ module.exports = {
28
28
  // The `fill` builder command expects an array of docs alternating with
29
29
  // line breaks. This is so it can loop through and determine where to break.
30
30
  const preds = fill(
31
- path
32
- .call(print, "body", 0)
33
- .reduce(
34
- (accum, pred, index) =>
35
- index === 0 ? [pred] : accum.concat([",", line, pred]),
36
- null
37
- )
31
+ path.call(print, "body", 0).reduce((accum, pred, index) => {
32
+ if (index === 0) {
33
+ return [pred];
34
+ }
35
+
36
+ // Pull off the last element and make it concat with a comma so that
37
+ // we can maintain alternating lines and docs.
38
+ return accum
39
+ .slice(0, -1)
40
+ .concat([concat([accum[accum.length - 1], ","]), line, pred]);
41
+ }, null)
38
42
  );
39
43
 
40
44
  const stmts = path.call(print, "body", 1);
@@ -1,14 +1,11 @@
1
- const {
2
- concat,
3
- group,
4
- hardline,
5
- ifBreak,
6
- indent,
7
- join,
8
- line
9
- } = require("../prettier");
1
+ const { concat, group, ifBreak, indent, join, line } = require("../prettier");
10
2
 
11
- const { getTrailingComma, prefix, skipAssignIndent } = require("../utils");
3
+ const {
4
+ getTrailingComma,
5
+ prefix,
6
+ printEmptyCollection,
7
+ skipAssignIndent
8
+ } = require("../utils");
12
9
 
13
10
  // When attempting to convert a hash rocket into a hash label, you need to take
14
11
  // care because only certain patterns are allowed. Ruby source says that they
@@ -25,35 +22,54 @@ function isValidHashLabel(symbolLiteral) {
25
22
  return label.match(/^[_A-Za-z]/) && !label.endsWith("=");
26
23
  }
27
24
 
28
- function printHashKey(path, { rubyHashLabel }, print) {
29
- const labelNode = path.getValue().body[0];
30
- const labelDoc = path.call(print, "body", 0);
25
+ function canUseHashLabels(contentsNode) {
26
+ return contentsNode.body.every((assocNode) => {
27
+ if (assocNode.type === "assoc_splat") {
28
+ return true;
29
+ }
31
30
 
32
- switch (labelNode.type) {
33
- case "@label":
34
- if (rubyHashLabel) {
35
- return labelDoc;
36
- }
37
- return `:${labelDoc.slice(0, labelDoc.length - 1)} =>`;
38
- case "symbol_literal": {
39
- if (rubyHashLabel && isValidHashLabel(labelNode)) {
40
- return concat([path.call(print, "body", 0, "body", 0), ":"]);
41
- }
42
- return concat([labelDoc, " =>"]);
31
+ switch (assocNode.body[0].type) {
32
+ case "@label":
33
+ return true;
34
+ case "symbol_literal":
35
+ return isValidHashLabel(assocNode.body[0]);
36
+ case "dyna_symbol":
37
+ return true;
38
+ default:
39
+ return false;
43
40
  }
41
+ });
42
+ }
43
+
44
+ function printHashKeyLabel(path, print) {
45
+ const node = path.getValue();
46
+
47
+ switch (node.type) {
48
+ case "@label":
49
+ return print(path);
50
+ case "symbol_literal":
51
+ return concat([path.call(print, "body", 0), ":"]);
44
52
  case "dyna_symbol":
45
- if (rubyHashLabel) {
46
- return concat(labelDoc.parts.slice(1).concat(":"));
47
- }
48
- return concat([labelDoc, " =>"]);
49
- default:
50
- return concat([labelDoc, " =>"]);
53
+ return concat(print(path).parts.slice(1).concat(":"));
54
+ }
55
+ }
56
+
57
+ function printHashKeyRocket(path, print) {
58
+ const node = path.getValue();
59
+ const doc = print(path);
60
+
61
+ if (node.type === "@label") {
62
+ return `:${doc.slice(0, doc.length - 1)} =>`;
51
63
  }
64
+
65
+ return concat([doc, " =>"]);
52
66
  }
53
67
 
54
68
  function printAssocNew(path, opts, print) {
69
+ const { keyPrinter } = path.getParentNode();
70
+
71
+ const parts = [path.call((keyPath) => keyPrinter(keyPath, print), "body", 0)];
55
72
  const valueDoc = path.call(print, "body", 1);
56
- const parts = [printHashKey(path, opts, print)];
57
73
 
58
74
  if (skipAssignIndent(path.getValue().body[1])) {
59
75
  parts.push(" ", valueDoc);
@@ -64,22 +80,17 @@ function printAssocNew(path, opts, print) {
64
80
  return group(concat(parts));
65
81
  }
66
82
 
67
- function printEmptyHashWithComments(path, opts) {
68
- const hashNode = path.getValue();
83
+ function printHashContents(path, opts, print) {
84
+ const node = path.getValue();
85
+
86
+ // First determine which key printer we're going to use, so that the child
87
+ // nodes can reference it when they go to get printed.
88
+ node.keyPrinter =
89
+ opts.rubyHashLabel && canUseHashLabels(path.getValue())
90
+ ? printHashKeyLabel
91
+ : printHashKeyRocket;
69
92
 
70
- const printComment = (commentPath, index) => {
71
- hashNode.comments[index].printed = true;
72
- return opts.printer.printComment(commentPath);
73
- };
74
-
75
- return concat([
76
- "{",
77
- indent(
78
- concat([hardline, join(hardline, path.map(printComment, "comments"))])
79
- ),
80
- line,
81
- "}"
82
- ]);
93
+ return join(concat([",", line]), path.map(print, "body"));
83
94
  }
84
95
 
85
96
  function printHash(path, opts, print) {
@@ -89,7 +100,7 @@ function printHash(path, opts, print) {
89
100
  // missing, then it means we're dealing with an empty hash, so we can just
90
101
  // exit here and print.
91
102
  if (hashNode.body[0] === null) {
92
- return hashNode.comments ? printEmptyHashWithComments(path, opts) : "{}";
103
+ return printEmptyCollection(path, opts, "{", "}");
93
104
  }
94
105
 
95
106
  return group(
@@ -108,10 +119,6 @@ function printHash(path, opts, print) {
108
119
  );
109
120
  }
110
121
 
111
- function printHashContents(path, opts, print) {
112
- return group(join(concat([",", line]), path.map(print, "body")));
113
- }
114
-
115
122
  module.exports = {
116
123
  assoc_new: printAssocNew,
117
124
  assoc_splat: prefix("**"),