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
@@ -9,7 +9,7 @@ const {
9
9
  softline
10
10
  } = require("../prettier");
11
11
 
12
- const { containsAssignment } = require("../utils");
12
+ const { containsAssignment, isEmptyStmts } = require("../utils");
13
13
  const inlineEnsureParens = require("../utils/inlineEnsureParens");
14
14
 
15
15
  const printWithAddition = (keyword, path, print, { breaking = false } = {}) =>
@@ -80,36 +80,50 @@ const printTernary = (path, _opts, print) => {
80
80
  // Prints an `if_mod` or `unless_mod` node. Because it was previously in the
81
81
  // modifier form, we're guaranteed to not have an additional node, so we can
82
82
  // just work with the predicate and the body.
83
- const printSingle = (keyword) => (path, { inlineConditionals }, print) => {
84
- const multiline = concat([
85
- `${keyword} `,
86
- align(keyword.length + 1, path.call(print, "body", 0)),
87
- indent(concat([softline, path.call(print, "body", 1)])),
88
- concat([softline, "end"])
89
- ]);
83
+ function printSingle(keyword, modifier = false) {
84
+ return function printSingleWithKeyword(path, { rubyModifier }, print) {
85
+ const [_predicateNode, statementsNode] = path.getValue().body;
86
+ const predicateDoc = path.call(print, "body", 0);
87
+ const statementsDoc = path.call(print, "body", 1);
90
88
 
91
- const [_predicate, stmts] = path.getValue().body;
92
- const hasComments =
93
- stmts.type === "stmts" &&
94
- stmts.body.some((stmt) => stmt.type === "@comment");
89
+ const multilineParts = [
90
+ `${keyword} `,
91
+ align(keyword.length + 1, predicateDoc),
92
+ indent(concat([softline, statementsDoc])),
93
+ softline,
94
+ "end"
95
+ ];
95
96
 
96
- if (!inlineConditionals || hasComments) {
97
- return multiline;
98
- }
97
+ // If we do not allow modifier form conditionals or there are comments
98
+ // inside of the body of the conditional, then we must print in the
99
+ // multiline form.
100
+ if (!rubyModifier || (!modifier && statementsNode.body[0].comments)) {
101
+ return concat([concat(multilineParts), breakParent]);
102
+ }
99
103
 
100
- const inline = concat(
101
- inlineEnsureParens(path, [
102
- path.call(print, "body", 1),
103
- ` ${keyword} `,
104
- path.call(print, "body", 0)
105
- ])
106
- );
104
+ const inline = concat(
105
+ inlineEnsureParens(path, [
106
+ path.call(print, "body", 1),
107
+ ` ${keyword} `,
108
+ path.call(print, "body", 0)
109
+ ])
110
+ );
107
111
 
108
- return group(ifBreak(multiline, inline));
109
- };
112
+ // An expression with a conditional modifier (expression if true), the
113
+ // conditional body is parsed before the predicate expression, meaning that
114
+ // if the parser encountered a variable declaration, it would initialize
115
+ // that variable first before evaluating the predicate expression. That
116
+ // parse order means the difference between a NameError or not. #591
117
+ // https://docs.ruby-lang.org/en/2.0.0/syntax/control_expressions_rdoc.html#label-Modifier+if+and+unless
118
+ if (modifier && containsAssignment(statementsNode)) {
119
+ return inline;
120
+ }
121
+
122
+ return group(ifBreak(concat(multilineParts), inline));
123
+ };
124
+ }
110
125
 
111
126
  const noTernary = [
112
- "@comment",
113
127
  "alias",
114
128
  "assign",
115
129
  "break",
@@ -176,7 +190,7 @@ const canTernary = (path) => {
176
190
  };
177
191
 
178
192
  // A normalized print function for both `if` and `unless` nodes.
179
- const printConditional = (keyword) => (path, { inlineConditionals }, print) => {
193
+ const printConditional = (keyword) => (path, { rubyModifier }, print) => {
180
194
  if (canTernary(path)) {
181
195
  let ternaryParts = [path.call(print, "body", 0), " ? "].concat(
182
196
  printTernaryClauses(
@@ -205,7 +219,7 @@ const printConditional = (keyword) => (path, { inlineConditionals }, print) => {
205
219
 
206
220
  // If the body of the conditional is empty, then we explicitly have to use the
207
221
  // block form.
208
- if (statements.type === "stmts" && statements.body[0].type === "void_stmt") {
222
+ if (isEmptyStmts(statements) && !statements.body[0].comments) {
209
223
  return concat([
210
224
  `${keyword} `,
211
225
  align(keyword.length + 1, path.call(print, "body", 0)),
@@ -225,7 +239,7 @@ const printConditional = (keyword) => (path, { inlineConditionals }, print) => {
225
239
  ]);
226
240
  }
227
241
 
228
- return printSingle(keyword)(path, { inlineConditionals }, print);
242
+ return printSingle(keyword)(path, { rubyModifier }, print);
229
243
  };
230
244
 
231
245
  module.exports = {
@@ -260,7 +274,7 @@ module.exports = {
260
274
  },
261
275
  if: printConditional("if"),
262
276
  ifop: printTernary,
263
- if_mod: printSingle("if"),
277
+ if_mod: printSingle("if", true),
264
278
  unless: printConditional("unless"),
265
- unless_mod: printSingle("unless")
279
+ unless_mod: printSingle("unless", true)
266
280
  };
@@ -1,25 +1,43 @@
1
1
  const { concat, group, indent, join, softline } = require("../prettier");
2
- const { first, makeCall, prefix } = require("../utils");
2
+ const { makeCall } = require("../utils");
3
+
4
+ function printConstPath(path, opts, print) {
5
+ return join("::", path.map(print, "body"));
6
+ }
7
+
8
+ function printConstRef(path, opts, print) {
9
+ return path.call(print, "body", 0);
10
+ }
11
+
12
+ function printDefined(path, opts, print) {
13
+ return group(
14
+ concat([
15
+ "defined?(",
16
+ indent(concat([softline, path.call(print, "body", 0)])),
17
+ concat([softline, ")"])
18
+ ])
19
+ );
20
+ }
21
+
22
+ function printField(path, opts, print) {
23
+ return group(
24
+ concat([
25
+ path.call(print, "body", 0),
26
+ concat([makeCall(path, opts, print), path.call(print, "body", 2)])
27
+ ])
28
+ );
29
+ }
30
+
31
+ function printTopConst(path, opts, print) {
32
+ return concat(["::", path.call(print, "body", 0)]);
33
+ }
3
34
 
4
35
  module.exports = {
5
- const_path_field: (path, opts, print) => join("::", path.map(print, "body")),
6
- const_path_ref: (path, opts, print) => join("::", path.map(print, "body")),
7
- const_ref: first,
8
- defined: (path, opts, print) =>
9
- group(
10
- concat([
11
- "defined?(",
12
- indent(concat([softline, path.call(print, "body", 0)])),
13
- concat([softline, ")"])
14
- ])
15
- ),
16
- field: (path, opts, print) =>
17
- group(
18
- concat([
19
- path.call(print, "body", 0),
20
- concat([makeCall(path, opts, print), path.call(print, "body", 2)])
21
- ])
22
- ),
23
- top_const_field: prefix("::"),
24
- top_const_ref: prefix("::")
36
+ const_path_field: printConstPath,
37
+ const_path_ref: printConstPath,
38
+ const_ref: printConstRef,
39
+ defined: printDefined,
40
+ field: printField,
41
+ top_const_field: printTopConst,
42
+ top_const_ref: printTopConst
25
43
  };
@@ -1,5 +1,15 @@
1
1
  const { concat, join } = require("../prettier");
2
- const { literal, nodeDive } = require("../utils");
2
+ const { literal } = require("../utils");
3
+
4
+ const nodeDive = (node, steps) => {
5
+ let current = node;
6
+
7
+ steps.forEach((step) => {
8
+ current = current[step];
9
+ });
10
+
11
+ return current;
12
+ };
3
13
 
4
14
  const unskippableParens = [
5
15
  "if_mod",
@@ -1,14 +1,11 @@
1
- const {
2
- concat,
3
- group,
4
- ifBreak,
5
- indent,
6
- join,
7
- line,
8
- literalline
9
- } = require("../prettier");
1
+ const { concat, group, ifBreak, indent, join, line } = require("../prettier");
10
2
 
11
- const { nodeDive, 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
@@ -20,128 +17,112 @@ const { nodeDive, prefix, skipAssignIndent } = require("../utils");
20
17
  //
21
18
  // This function represents that check, as it determines if it can convert the
22
19
  // symbol node into a hash label.
23
- const isValidHashLabel = (symbolLiteral) => {
24
- const label = symbolLiteral.body[0].body[0].body;
20
+ function isValidHashLabel(symbolLiteral) {
21
+ const label = symbolLiteral.body[0].body;
25
22
  return label.match(/^[_A-Za-z]/) && !label.endsWith("=");
26
- };
23
+ }
27
24
 
28
- const makeLabel = (path, { preferHashLabels }, print, steps) => {
29
- const labelNode = nodeDive(path.getValue(), steps);
30
- const labelDoc = path.call.apply(path, [print].concat(steps));
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 (preferHashLabels) {
35
- return labelDoc;
36
- }
37
- return `:${labelDoc.slice(0, labelDoc.length - 1)} =>`;
38
- case "symbol_literal": {
39
- if (preferHashLabels && isValidHashLabel(labelNode)) {
40
- const symbolSteps = steps.concat("body", 0, "body", 0);
41
-
42
- return concat([
43
- path.call.apply(path, [print].concat(symbolSteps)),
44
- ":"
45
- ]);
46
- }
47
- 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;
48
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), ":"]);
49
52
  case "dyna_symbol":
50
- if (preferHashLabels) {
51
- return concat(labelDoc.parts.slice(1).concat(":"));
52
- }
53
- return concat([labelDoc, " =>"]);
54
- default:
55
- return concat([labelDoc, " =>"]);
53
+ return concat(print(path).parts.slice(1).concat(":"));
56
54
  }
57
- };
55
+ }
58
56
 
59
- function printHash(path, { addTrailingCommas }, print) {
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)} =>`;
63
+ }
64
+
65
+ return concat([doc, " =>"]);
66
+ }
67
+
68
+ function printAssocNew(path, opts, print) {
69
+ const { keyPrinter } = path.getParentNode();
70
+
71
+ const parts = [path.call((keyPath) => keyPrinter(keyPath, print), "body", 0)];
72
+ const valueDoc = path.call(print, "body", 1);
73
+
74
+ if (skipAssignIndent(path.getValue().body[1])) {
75
+ parts.push(" ", valueDoc);
76
+ } else {
77
+ parts.push(indent(concat([line, valueDoc])));
78
+ }
79
+
80
+ return group(concat(parts));
81
+ }
82
+
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;
92
+
93
+ return join(concat([",", line]), path.map(print, "body"));
94
+ }
95
+
96
+ function printHash(path, opts, print) {
60
97
  const hashNode = path.getValue();
61
98
 
62
99
  // Hashes normally have a single assoclist_from_args child node. If it's
63
100
  // missing, then it means we're dealing with an empty hash, so we can just
64
101
  // exit here and print.
65
102
  if (hashNode.body[0] === null) {
66
- return "{}";
67
- }
68
-
69
- // Here we get a reference to the printed assoclist_from_args child node,
70
- // which handles printing all of the key-value pairs of the hash. We're
71
- // wrapping it in an array in case we need to append a trailing comma.
72
- const assocDocs = [path.call(print, "body", 0)];
73
-
74
- // Here we get a reference to the last key-value pair's value node, in order
75
- // to check if we're dealing with a heredoc. If we are, then the trailing
76
- // comma printing is handled from within the assoclist_from_args node
77
- // printing, because the trailing comma has to go after the heredoc
78
- // declaration.
79
- const assocNodes = hashNode.body[0].body[0];
80
- const lastAssocValueNode = assocNodes[assocNodes.length - 1].body[1];
81
-
82
- // If we're adding a trailing comma and the last key-value pair's value node
83
- // is not a heredoc node, then we can safely append the extra comma if the
84
- // hash ends up getting printed on multiple lines.
85
- if (addTrailingCommas && lastAssocValueNode.type !== "heredoc") {
86
- assocDocs.push(ifBreak(",", ""));
103
+ return printEmptyCollection(path, opts, "{", "}");
87
104
  }
88
105
 
89
106
  return group(
90
107
  concat([
91
108
  "{",
92
- indent(concat([line, concat(assocDocs)])),
93
- concat([line, "}"])
109
+ indent(
110
+ concat([
111
+ line,
112
+ path.call(print, "body", 0),
113
+ getTrailingComma(opts) ? ifBreak(",", "") : ""
114
+ ])
115
+ ),
116
+ line,
117
+ "}"
94
118
  ])
95
119
  );
96
120
  }
97
121
 
98
122
  module.exports = {
99
- assoc_new: (path, opts, print) => {
100
- const valueDoc = path.call(print, "body", 1);
101
- const parts = [makeLabel(path, opts, print, ["body", 0])];
102
-
103
- if (skipAssignIndent(path.getValue().body[1])) {
104
- parts.push(" ", valueDoc);
105
- } else {
106
- parts.push(indent(concat([line, valueDoc])));
107
- }
108
-
109
- return group(concat(parts));
110
- },
123
+ assoc_new: printAssocNew,
111
124
  assoc_splat: prefix("**"),
112
- assoclist_from_args: (path, opts, print) => {
113
- const { addTrailingCommas } = opts;
114
-
115
- const assocNodes = path.getValue().body[0];
116
- const assocDocs = [];
117
-
118
- assocNodes.forEach((assocNode, index) => {
119
- const isInner = index !== assocNodes.length - 1;
120
- const valueNode = assocNode.body[1];
121
-
122
- if (valueNode && valueNode.type === "heredoc") {
123
- assocDocs.push(
124
- makeLabel(path, opts, print, ["body", 0, index, "body", 0]),
125
- " ",
126
- valueNode.beging,
127
- isInner || addTrailingCommas ? "," : "",
128
- literalline,
129
- concat(path.map(print, "body", 0, index, "body", 1, "body")),
130
- valueNode.ending,
131
- isInner ? line : ""
132
- );
133
- } else {
134
- assocDocs.push(path.call(print, "body", 0, index));
135
-
136
- if (isInner) {
137
- assocDocs.push(concat([",", line]));
138
- }
139
- }
140
- });
141
-
142
- return group(concat(assocDocs));
143
- },
144
- bare_assoc_hash: (path, opts, print) =>
145
- group(join(concat([",", line]), path.map(print, "body", 0))),
125
+ assoclist_from_args: printHashContents,
126
+ bare_assoc_hash: printHashContents,
146
127
  hash: printHash
147
128
  };
@@ -0,0 +1,34 @@
1
+ const { concat, group, lineSuffix, join } = require("../prettier");
2
+ const { literalLineNoBreak } = require("../utils");
3
+
4
+ function printHeredoc(path, opts, print) {
5
+ const { body, ending } = path.getValue();
6
+
7
+ const parts = body.map((part, index) => {
8
+ if (part.type !== "@tstring_content") {
9
+ // In this case, the part of the string is an embedded expression
10
+ return path.call(print, "body", index);
11
+ }
12
+
13
+ // In this case, the part of the string is just regular string content
14
+ return join(literalLineNoBreak, part.body.split("\n"));
15
+ });
16
+
17
+ // We use a literalline break because matching indentation is required
18
+ // for the heredoc contents and ending. If the line suffix contains a
19
+ // break-parent, all ancestral groups are broken, and heredocs automatically
20
+ // break lines in groups they appear in. We prefer them to appear in-line if
21
+ // possible, so we use a literalline without the break-parent.
22
+ return group(
23
+ concat([
24
+ path.call(print, "beging"),
25
+ lineSuffix(
26
+ group(concat([literalLineNoBreak].concat(parts).concat(ending)))
27
+ )
28
+ ])
29
+ );
30
+ }
31
+
32
+ module.exports = {
33
+ heredoc: printHeredoc
34
+ };