prettier 0.21.0 → 1.0.1

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +112 -7
  3. data/CONTRIBUTING.md +4 -4
  4. data/README.md +18 -14
  5. data/package.json +9 -6
  6. data/src/embed.js +27 -8
  7. data/src/nodes.js +5 -2
  8. data/src/nodes/alias.js +29 -31
  9. data/src/nodes/aref.js +26 -26
  10. data/src/nodes/args.js +55 -47
  11. data/src/nodes/arrays.js +132 -106
  12. data/src/nodes/assign.js +32 -32
  13. data/src/nodes/blocks.js +8 -3
  14. data/src/nodes/calls.js +163 -60
  15. data/src/nodes/case.js +11 -7
  16. data/src/nodes/class.js +74 -0
  17. data/src/nodes/commands.js +36 -31
  18. data/src/nodes/conditionals.js +44 -30
  19. data/src/nodes/constants.js +39 -21
  20. data/src/nodes/flow.js +11 -1
  21. data/src/nodes/hashes.js +90 -109
  22. data/src/nodes/heredocs.js +34 -0
  23. data/src/nodes/hooks.js +21 -22
  24. data/src/nodes/ints.js +27 -20
  25. data/src/nodes/lambdas.js +14 -27
  26. data/src/nodes/loops.js +10 -5
  27. data/src/nodes/massign.js +87 -65
  28. data/src/nodes/methods.js +48 -73
  29. data/src/nodes/operators.js +70 -39
  30. data/src/nodes/params.js +26 -16
  31. data/src/nodes/patterns.js +108 -33
  32. data/src/nodes/regexp.js +45 -14
  33. data/src/nodes/rescue.js +72 -59
  34. data/src/nodes/statements.js +86 -44
  35. data/src/nodes/strings.js +95 -85
  36. data/src/nodes/super.js +35 -0
  37. data/src/nodes/undef.js +42 -0
  38. data/src/parser.js +86 -0
  39. data/src/parser.rb +2400 -621
  40. data/src/printer.js +90 -0
  41. data/src/ruby.js +19 -41
  42. data/src/toProc.js +4 -4
  43. data/src/utils.js +24 -88
  44. data/src/utils/literalLineNoBreak.js +7 -0
  45. data/src/utils/printEmptyCollection.js +42 -0
  46. metadata +12 -49
  47. data/src/nodes/scopes.js +0 -61
  48. data/src/parse.js +0 -37
  49. data/src/print.js +0 -23
@@ -1,39 +1,39 @@
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
- module.exports = {
5
- assign: (path, opts, print) => {
6
- const [printedTarget, printedValue] = path.map(print, "body");
7
- let adjustedValue = printedValue;
4
+ function printAssign(path, opts, print) {
5
+ const [_targetNode, valueNode] = path.getValue().body;
6
+ const [targetDoc, valueDoc] = path.map(print, "body");
7
+
8
+ let rightSideDoc = valueDoc;
9
+
10
+ // If the right side of this assignment is a multiple assignment, then we need
11
+ // to join it together with commas.
12
+ if (["mrhs_add_star", "mrhs_new_from_args"].includes(valueNode.type)) {
13
+ rightSideDoc = group(join(concat([",", line]), valueDoc));
14
+ }
8
15
 
9
- if (
10
- ["mrhs_add_star", "mrhs_new_from_args"].includes(
11
- path.getValue().body[1].type
12
- )
13
- ) {
14
- adjustedValue = group(join(concat([",", line]), printedValue));
15
- }
16
+ if (skipAssignIndent(valueNode)) {
17
+ return group(concat([targetDoc, " = ", rightSideDoc]));
18
+ }
16
19
 
17
- if (skipAssignIndent(path.getValue().body[1])) {
18
- return group(concat([printedTarget, " = ", adjustedValue]));
19
- }
20
+ return group(concat([targetDoc, " =", indent(concat([line, rightSideDoc]))]));
21
+ }
20
22
 
21
- return group(
22
- concat([printedTarget, " =", indent(concat([line, adjustedValue]))])
23
- );
24
- },
25
- assign_error: (_path, _opts, _print) => {
26
- throw new Error("Can't set variable");
27
- },
28
- opassign: (path, opts, print) =>
29
- group(
30
- concat([
31
- path.call(print, "body", 0),
32
- " ",
33
- path.call(print, "body", 1),
34
- indent(concat([line, path.call(print, "body", 2)]))
35
- ])
36
- ),
37
- var_field: concatBody,
23
+ function printOpAssign(path, opts, print) {
24
+ return group(
25
+ concat([
26
+ path.call(print, "body", 0),
27
+ " ",
28
+ path.call(print, "body", 1),
29
+ indent(concat([line, path.call(print, "body", 2)]))
30
+ ])
31
+ );
32
+ }
33
+
34
+ module.exports = {
35
+ assign: printAssign,
36
+ opassign: printOpAssign,
37
+ var_field: first,
38
38
  var_ref: first
39
39
  };
@@ -16,7 +16,11 @@ const printBlock = (braces) => (path, opts, print) => {
16
16
  statements.type === "stmts" ? statements.body : statements.body[0].body;
17
17
 
18
18
  let doBlockBody = "";
19
- if (stmts.length !== 1 || stmts[0].type !== "void_stmt") {
19
+ if (
20
+ stmts.length !== 1 ||
21
+ stmts[0].type !== "void_stmt" ||
22
+ stmts[0].comments
23
+ ) {
20
24
  doBlockBody = indent(concat([softline, path.call(print, "body", 1)]));
21
25
  }
22
26
 
@@ -37,8 +41,9 @@ const printBlock = (braces) => (path, opts, print) => {
37
41
  // We can hit this next pattern if within the block the only statement is a
38
42
  // comment.
39
43
  if (
40
- stmts.length > 1 &&
41
- stmts.filter((stmt) => stmt.type !== "@comment").length === 1
44
+ stmts.length === 1 &&
45
+ stmts[0].type === "void_stmt" &&
46
+ stmts[0].comments
42
47
  ) {
43
48
  return concat([breakParent, doBlock]);
44
49
  }
@@ -1,69 +1,146 @@
1
- const { concat, group, indent, literalline, softline } = require("../prettier");
1
+ const {
2
+ concat,
3
+ group,
4
+ hardline,
5
+ ifBreak,
6
+ indent,
7
+ softline
8
+ } = require("../prettier");
9
+ const { first, makeCall, noIndent } = require("../utils");
10
+
2
11
  const toProc = require("../toProc");
3
- const { concatBody, first, makeCall } = require("../utils");
4
12
 
5
- const noIndent = ["array", "hash", "if", "method_add_block", "xstring_literal"];
13
+ const chained = ["call", "method_add_arg", "method_add_block"];
6
14
 
7
- module.exports = {
8
- call: (path, opts, print) => {
9
- const [receiverNode, _operatorNode, messageNode] = path.getValue().body;
10
-
11
- const printedReceiver = path.call(print, "body", 0);
12
- const printedOperator = makeCall(path, opts, print);
13
-
14
- // You can call lambdas with a special syntax that looks like func.(*args).
15
- // In this case, "call" is returned for the 3rd child node.
16
- const printedMessage =
17
- messageNode === "call" ? messageNode : path.call(print, "body", 2);
18
-
19
- // If we have a heredoc as a receiver, then we need to move the operator and
20
- // the message up to start of the heredoc declaration, as in:
21
- //
22
- // <<~TEXT.strip
23
- // content
24
- // TEXT
25
- if (receiverNode.type === "heredoc") {
26
- return concat([
27
- receiverNode.beging,
28
- printedOperator,
29
- printedMessage,
30
- literalline,
31
- concat(path.map(print, "body", 0, "body")),
32
- receiverNode.ending
33
- ]);
34
- }
15
+ function printCall(path, opts, print) {
16
+ const node = path.getValue();
17
+ const [receiverNode, _operatorNode, messageNode] = node.body;
35
18
 
36
- // For certain left sides of the call nodes, we want to attach directly to
37
- // the } or end.
38
- if (noIndent.includes(receiverNode.type)) {
39
- return concat([printedReceiver, printedOperator, printedMessage]);
40
- }
19
+ const receiverDoc = path.call(print, "body", 0);
20
+ const operatorDoc = makeCall(path, opts, print);
21
+
22
+ // You can call lambdas with a special syntax that looks like func.(*args).
23
+ // In this case, "call" is returned for the 3rd child node. We don't alter
24
+ // call syntax so if `call` is implicit, we don't print it out.
25
+ const messageDoc = messageNode === "call" ? "" : path.call(print, "body", 2);
26
+
27
+ // The right side of the call node, as in everything including the operator
28
+ // and beyond.
29
+ const rightSideDoc = concat([
30
+ receiverNode.comments ? hardline : softline,
31
+ operatorDoc,
32
+ messageDoc
33
+ ]);
34
+
35
+ // Get a reference to the parent node so we can check if we're inside a chain
36
+ const parentNode = path.getParentNode();
37
+
38
+ // If our parent node is a chained node then we're not going to group the
39
+ // right side of the expression, as we want to have a nice multi-line layout.
40
+ if (chained.includes(parentNode.type)) {
41
+ parentNode.chain = (node.chain || 0) + 1;
42
+ parentNode.breakDoc = (node.breakDoc || [receiverDoc]).concat(rightSideDoc);
43
+ }
41
44
 
42
- return group(
43
- concat([
44
- printedReceiver,
45
- group(indent(concat([softline, printedOperator, printedMessage])))
46
- ])
45
+ // If we're at the top of a chain, then we're going to print out a nice
46
+ // multi-line layout if this doesn't break into multiple lines.
47
+ if (!chained.includes(parentNode.type) && (node.chain || 0) >= 3) {
48
+ return ifBreak(
49
+ group(indent(concat(node.breakDoc.concat(rightSideDoc)))),
50
+ concat([receiverDoc, group(rightSideDoc)])
47
51
  );
48
- },
49
- fcall: concatBody,
50
- method_add_arg: (path, opts, print) => {
51
- const [method, args] = path.map(print, "body");
52
- const argNode = path.getValue().body[1];
53
-
54
- // This case will ONLY be hit if we can successfully turn the block into a
55
- // to_proc call. In that case, we just explicitly add the parens around it.
56
- if (argNode.type === "args" && args.length > 0) {
57
- return concat([method, "("].concat(args).concat(")"));
58
- }
52
+ }
53
+
54
+ // For certain left sides of the call nodes, we want to attach directly to
55
+ // the } or end.
56
+ if (noIndent.includes(receiverNode.type)) {
57
+ return concat([receiverDoc, operatorDoc, messageDoc]);
58
+ }
59
+
60
+ return group(concat([receiverDoc, group(indent(rightSideDoc))]));
61
+ }
62
+
63
+ function printMethodAddArg(path, opts, print) {
64
+ const node = path.getValue();
65
+ const argNode = node.body[1];
66
+
67
+ const [methodDoc, argsDoc] = path.map(print, "body");
68
+
69
+ // You can end up here if you have a method with a ? ending, presumably
70
+ // because the parser knows that it cannot be a local variable.
71
+ if (argsDoc.length === 0) {
72
+ return methodDoc;
73
+ }
74
+
75
+ // This case will ONLY be hit if we can successfully turn the block into a
76
+ // to_proc call. In that case, we just explicitly add the parens around it.
77
+ if (argNode.type === "args" && argsDoc.length > 0) {
78
+ return concat([methodDoc, "("].concat(argsDoc).concat(")"));
79
+ }
80
+
81
+ // Get a reference to the parent node so we can check if we're inside a chain
82
+ const parentNode = path.getParentNode();
59
83
 
60
- return concat([method, args]);
61
- },
62
- method_add_block: (path, opts, print) => {
63
- const [method, block] = path.getValue().body;
64
- const proc = toProc(path, opts, block);
84
+ // If our parent node is a chained node then we're not going to group the
85
+ // right side of the expression, as we want to have a nice multi-line layout.
86
+ if (chained.includes(parentNode.type)) {
87
+ parentNode.chain = (node.chain || 0) + 1;
88
+ parentNode.breakDoc = (node.breakDoc || [methodDoc]).concat(argsDoc);
89
+ }
65
90
 
66
- if (proc && method.type === "call") {
91
+ // If we're at the top of a chain, then we're going to print out a nice
92
+ // multi-line layout if this doesn't break into multiple lines.
93
+ if (!chained.includes(parentNode.type) && (node.chain || 0) >= 3) {
94
+ return ifBreak(
95
+ group(indent(concat(node.breakDoc.concat(argsDoc)))),
96
+ concat([methodDoc, argsDoc])
97
+ );
98
+ }
99
+
100
+ return concat([methodDoc, argsDoc]);
101
+ }
102
+
103
+ // Sorbet type annotations look like the following:
104
+ //
105
+ // {method_add_block
106
+ // [{method_add_arg
107
+ // [{fcall
108
+ // [{@ident "sig"}]},
109
+ // {args []}]},
110
+ // {brace_block [nil, {stmts}]}}]}
111
+ //
112
+ function isSorbetTypeAnnotation(node) {
113
+ const [callNode, blockNode] = node.body;
114
+
115
+ return (
116
+ callNode.type === "method_add_arg" &&
117
+ callNode.body[0].type === "fcall" &&
118
+ callNode.body[0].body[0].body === "sig" &&
119
+ callNode.body[1].type === "args" &&
120
+ callNode.body[1].body.length === 0 &&
121
+ blockNode.type === "brace_block"
122
+ );
123
+ }
124
+
125
+ function printMethodAddBlock(path, opts, print) {
126
+ const node = path.getValue();
127
+
128
+ const [callNode, blockNode] = node.body;
129
+ const [callDoc, blockDoc] = path.map(print, "body");
130
+
131
+ // Very special handling here for sorbet type annotations. They look like Ruby
132
+ // code, but they're not actually Ruby code, so we're not going to mess with
133
+ // them at all.
134
+ if (isSorbetTypeAnnotation(node)) {
135
+ return opts.originalText.slice(opts.locStart(node), opts.locEnd(node));
136
+ }
137
+
138
+ // Don't bother trying to do any kind of fancy toProc transform if the option
139
+ // is disabled.
140
+ if (opts.rubyToProc) {
141
+ const proc = toProc(path, blockNode);
142
+
143
+ if (proc && callNode.type === "call") {
67
144
  return group(
68
145
  concat([
69
146
  path.call(print, "body", 0),
@@ -77,8 +154,34 @@ module.exports = {
77
154
  if (proc) {
78
155
  return path.call(print, "body", 0);
79
156
  }
157
+ }
158
+
159
+ // Get a reference to the parent node so we can check if we're inside a chain
160
+ const parentNode = path.getParentNode();
80
161
 
81
- return concat(path.map(print, "body"));
82
- },
162
+ // If our parent node is a chained node then we're not going to group the
163
+ // right side of the expression, as we want to have a nice multi-line layout.
164
+ if (chained.includes(parentNode.type)) {
165
+ parentNode.chain = (node.chain || 0) + 1;
166
+ parentNode.breakDoc = (node.breakDoc || [callDoc]).concat(blockDoc);
167
+ }
168
+
169
+ // If we're at the top of a chain, then we're going to print out a nice
170
+ // multi-line layout if this doesn't break into multiple lines.
171
+ if (!chained.includes(parentNode.type) && (node.chain || 0) >= 3) {
172
+ return ifBreak(
173
+ group(indent(concat(node.breakDoc.concat(blockDoc)))),
174
+ concat([callDoc, blockDoc])
175
+ );
176
+ }
177
+
178
+ return concat([callDoc, blockDoc]);
179
+ }
180
+
181
+ module.exports = {
182
+ call: printCall,
183
+ fcall: first,
184
+ method_add_arg: printMethodAddArg,
185
+ method_add_block: printMethodAddBlock,
83
186
  vcall: first
84
187
  };
@@ -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);
@@ -0,0 +1,74 @@
1
+ const {
2
+ concat,
3
+ group,
4
+ hardline,
5
+ ifBreak,
6
+ indent,
7
+ line
8
+ } = require("../prettier");
9
+
10
+ function printClass(path, opts, print) {
11
+ const [_constant, superclass, bodystmt] = path.getValue().body;
12
+ const stmts = bodystmt.body[0];
13
+
14
+ const parts = ["class ", path.call(print, "body", 0)];
15
+ if (superclass) {
16
+ parts.push(" < ", path.call(print, "body", 1));
17
+ }
18
+
19
+ // If the body is empty and does not contain any comments, we can just
20
+ // replace the body with a semi-colon.
21
+ if (
22
+ stmts.body.length === 1 &&
23
+ stmts.body[0].type === "void_stmt" &&
24
+ !stmts.body[0].comments
25
+ ) {
26
+ return group(concat([concat(parts), ifBreak(line, "; "), "end"]));
27
+ }
28
+
29
+ return group(
30
+ concat([
31
+ concat(parts),
32
+ indent(concat([hardline, path.call(print, "body", 2)])),
33
+ concat([hardline, "end"])
34
+ ])
35
+ );
36
+ }
37
+
38
+ function printModule(path, opts, print) {
39
+ const declaration = group(concat(["module ", path.call(print, "body", 0)]));
40
+
41
+ // If the body is empty, we can replace with a ;
42
+ const stmts = path.getValue().body[1].body[0];
43
+ if (
44
+ stmts.body.length === 1 &&
45
+ stmts.body[0].type === "void_stmt" &&
46
+ !stmts.body[0].comments
47
+ ) {
48
+ return group(concat([declaration, ifBreak(line, "; "), "end"]));
49
+ }
50
+
51
+ return group(
52
+ concat([
53
+ declaration,
54
+ indent(concat([hardline, path.call(print, "body", 1)])),
55
+ concat([hardline, "end"])
56
+ ])
57
+ );
58
+ }
59
+
60
+ function printSClass(path, opts, print) {
61
+ return group(
62
+ concat([
63
+ concat(["class << ", path.call(print, "body", 0)]),
64
+ indent(concat([hardline, path.call(print, "body", 1)])),
65
+ concat([hardline, "end"])
66
+ ])
67
+ );
68
+ }
69
+
70
+ module.exports = {
71
+ class: printClass,
72
+ module: printModule,
73
+ sclass: printSClass
74
+ };
@@ -1,5 +1,14 @@
1
- const { align, concat, group, ifBreak, join, line } = require("../prettier");
2
- const { docLength, makeArgs, makeCall } = require("../utils");
1
+ const {
2
+ align,
3
+ concat,
4
+ group,
5
+ ifBreak,
6
+ indent,
7
+ join,
8
+ line,
9
+ softline
10
+ } = require("../prettier");
11
+ const { docLength, makeCall } = require("../utils");
3
12
 
4
13
  const hasDef = (node) =>
5
14
  node.body[1].type === "args_add_block" &&
@@ -23,32 +32,39 @@ const hasDef = (node) =>
23
32
  const skipArgsAlign = (path) =>
24
33
  ["to", "not_to"].includes(path.getValue().body[2].body);
25
34
 
35
+ // If there is a ternary argument to a command and it's going to get broken
36
+ // into multiple lines, then we're going to have to use parentheses around the
37
+ // command in order to make sure operator precedence doesn't get messed up.
38
+ const hasTernaryArg = (path) =>
39
+ path.getValue().body[1].body[0].body.some((node) => node.type === "ifop");
40
+
26
41
  module.exports = {
27
42
  command: (path, opts, print) => {
28
43
  const command = path.call(print, "body", 0);
29
- const { args, heredocs } = makeArgs(path, opts, print, 1);
44
+ const joinedArgs = join(concat([",", line]), path.call(print, "body", 1));
30
45
 
31
- if (heredocs.length > 1) {
32
- return concat([command, " ", join(", ", args)].concat(heredocs));
33
- }
46
+ const hasTernary = hasTernaryArg(path);
47
+ let breakArgs;
34
48
 
35
- const joinedArgs = join(concat([",", line]), args);
36
- const breakArgs = hasDef(path.getValue())
37
- ? joinedArgs
38
- : align(command.length + 1, joinedArgs);
49
+ if (hasTernary) {
50
+ breakArgs = indent(concat([softline, joinedArgs]));
51
+ } else if (hasDef(path.getValue())) {
52
+ breakArgs = joinedArgs;
53
+ } else {
54
+ breakArgs = align(command.length + 1, joinedArgs);
55
+ }
39
56
 
40
- const commandDoc = group(
57
+ return group(
41
58
  ifBreak(
42
- concat([command, " ", breakArgs]),
59
+ concat([
60
+ command,
61
+ hasTernary ? "(" : " ",
62
+ breakArgs,
63
+ hasTernary ? concat([softline, ")"]) : ""
64
+ ]),
43
65
  concat([command, " ", joinedArgs])
44
66
  )
45
67
  );
46
-
47
- if (heredocs.length === 1) {
48
- return group(concat([commandDoc].concat(heredocs)));
49
- }
50
-
51
- return commandDoc;
52
68
  },
53
69
  command_call: (path, opts, print) => {
54
70
  const parts = [
@@ -62,25 +78,14 @@ module.exports = {
62
78
  }
63
79
 
64
80
  parts.push(" ");
65
- const { args, heredocs } = makeArgs(path, opts, print, 3);
66
-
67
- if (heredocs.length > 1) {
68
- return concat(parts.concat([join(", ", args)]).concat(heredocs));
69
- }
70
81
 
71
- const joinedArgs = join(concat([",", line]), args);
82
+ const joinedArgs = join(concat([",", line]), path.call(print, "body", 3));
72
83
  const breakArgs = skipArgsAlign(path)
73
84
  ? joinedArgs
74
85
  : align(docLength(concat(parts)), joinedArgs);
75
86
 
76
- const commandDoc = group(
87
+ return group(
77
88
  ifBreak(concat(parts.concat(breakArgs)), concat(parts.concat(joinedArgs)))
78
89
  );
79
-
80
- if (heredocs.length === 1) {
81
- return group(concat([commandDoc].concat(heredocs)));
82
- }
83
-
84
- return commandDoc;
85
90
  }
86
91
  };