prettier 0.20.0 → 1.0.0.pre.rc2

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +118 -4
  3. data/CONTRIBUTING.md +8 -6
  4. data/README.md +67 -60
  5. data/node_modules/prettier/bin-prettier.js +12317 -50112
  6. data/node_modules/prettier/index.js +33352 -27419
  7. data/node_modules/prettier/third-party.js +5678 -7676
  8. data/package.json +4 -4
  9. data/src/embed.js +27 -8
  10. data/src/nodes.js +6 -2
  11. data/src/nodes/alias.js +65 -24
  12. data/src/nodes/aref.js +55 -0
  13. data/src/nodes/args.js +55 -47
  14. data/src/nodes/arrays.js +150 -137
  15. data/src/nodes/assign.js +32 -32
  16. data/src/nodes/blocks.js +8 -3
  17. data/src/nodes/calls.js +129 -70
  18. data/src/nodes/case.js +11 -7
  19. data/src/nodes/class.js +74 -0
  20. data/src/nodes/commands.js +36 -31
  21. data/src/nodes/conditionals.js +48 -46
  22. data/src/nodes/constants.js +39 -21
  23. data/src/nodes/flow.js +45 -17
  24. data/src/nodes/hashes.js +126 -112
  25. data/src/nodes/heredocs.js +34 -0
  26. data/src/nodes/hooks.js +36 -7
  27. data/src/nodes/ints.js +27 -20
  28. data/src/nodes/lambdas.js +69 -52
  29. data/src/nodes/loops.js +19 -29
  30. data/src/nodes/massign.js +87 -65
  31. data/src/nodes/methods.js +48 -73
  32. data/src/nodes/operators.js +70 -39
  33. data/src/nodes/params.js +26 -16
  34. data/src/nodes/patterns.js +108 -33
  35. data/src/nodes/regexp.js +38 -14
  36. data/src/nodes/rescue.js +72 -59
  37. data/src/nodes/statements.js +86 -44
  38. data/src/nodes/strings.js +94 -90
  39. data/src/nodes/super.js +35 -0
  40. data/src/nodes/undef.js +42 -0
  41. data/src/parser.js +71 -0
  42. data/src/parser.rb +2554 -0
  43. data/src/printer.js +90 -0
  44. data/src/ruby.js +20 -61
  45. data/src/toProc.js +4 -4
  46. data/src/utils.js +24 -88
  47. data/src/utils/inlineEnsureParens.js +42 -0
  48. data/src/utils/isEmptyStmts.js +7 -0
  49. data/src/utils/literalLineNoBreak.js +7 -0
  50. metadata +15 -20
  51. data/src/haml.js +0 -21
  52. data/src/haml/embed.js +0 -58
  53. data/src/haml/nodes/comment.js +0 -27
  54. data/src/haml/nodes/doctype.js +0 -32
  55. data/src/haml/nodes/filter.js +0 -16
  56. data/src/haml/nodes/hamlComment.js +0 -21
  57. data/src/haml/nodes/script.js +0 -29
  58. data/src/haml/nodes/silentScript.js +0 -59
  59. data/src/haml/nodes/tag.js +0 -157
  60. data/src/haml/parse.js +0 -18
  61. data/src/haml/parse.rb +0 -64
  62. data/src/haml/print.js +0 -38
  63. data/src/nodes/scopes.js +0 -61
  64. data/src/parse.js +0 -37
  65. data/src/print.js +0 -23
  66. data/src/ripper.rb +0 -811
@@ -8,7 +8,9 @@ const {
8
8
  indent,
9
9
  softline
10
10
  } = require("../prettier");
11
- const { containsAssignment } = require("../utils");
11
+
12
+ const { containsAssignment, isEmptyStmts } = require("../utils");
13
+ const inlineEnsureParens = require("../utils/inlineEnsureParens");
12
14
 
13
15
  const printWithAddition = (keyword, path, print, { breaking = false } = {}) =>
14
16
  concat([
@@ -78,52 +80,50 @@ const printTernary = (path, _opts, print) => {
78
80
  // Prints an `if_mod` or `unless_mod` node. Because it was previously in the
79
81
  // modifier form, we're guaranteed to not have an additional node, so we can
80
82
  // just work with the predicate and the body.
81
- const printSingle = (keyword) => (path, { inlineConditionals }, print) => {
82
- const multiline = concat([
83
- `${keyword} `,
84
- align(keyword.length + 1, path.call(print, "body", 0)),
85
- indent(concat([softline, path.call(print, "body", 1)])),
86
- concat([softline, "end"])
87
- ]);
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);
88
88
 
89
- const [_predicate, stmts] = path.getValue().body;
90
- const hasComments =
91
- stmts.type === "stmts" &&
92
- 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
+ ];
93
96
 
94
- if (!inlineConditionals || hasComments) {
95
- return multiline;
96
- }
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
+ }
97
103
 
98
- let inlineParts = [
99
- path.call(print, "body", 1),
100
- ` ${keyword} `,
101
- path.call(print, "body", 0)
102
- ];
104
+ const inline = concat(
105
+ inlineEnsureParens(path, [
106
+ path.call(print, "body", 1),
107
+ ` ${keyword} `,
108
+ path.call(print, "body", 0)
109
+ ])
110
+ );
103
111
 
104
- // If the return value of this conditional expression is being assigned to
105
- // anything besides a local variable then we can't inline the entire
106
- // expression without wrapping it in parentheses. This is because the
107
- // following expressions have different semantic meaning:
108
- //
109
- // hash[:key] = :value if false
110
- // hash[:key] = if false then :value end
111
- //
112
- // The first one will not result in an empty hash, whereas the second one
113
- // will result in `{ key: nil }`. In this case what we need to do for the
114
- // first expression to align is wrap it in parens, as in:
115
- //
116
- // hash[:key] = (:value if false)
117
- if (["assign", "massign"].includes(path.getParentNode().type)) {
118
- inlineParts = ["("].concat(inlineParts).concat(")");
119
- }
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
+ }
120
121
 
121
- const inline = concat(inlineParts);
122
- return group(ifBreak(multiline, inline));
123
- };
122
+ return group(ifBreak(concat(multilineParts), inline));
123
+ };
124
+ }
124
125
 
125
126
  const noTernary = [
126
- "@comment",
127
127
  "alias",
128
128
  "assign",
129
129
  "break",
@@ -180,7 +180,9 @@ const canTernary = (path) => {
180
180
  const [predicate, stmts, addition] = path.getValue().body;
181
181
 
182
182
  return (
183
- !["assign", "opassign"].includes(predicate.type) &&
183
+ !["assign", "opassign", "command_call", "command"].includes(
184
+ predicate.type
185
+ ) &&
184
186
  addition &&
185
187
  addition.type === "else" &&
186
188
  [stmts, addition.body[0]].every(canTernaryStmts)
@@ -188,7 +190,7 @@ const canTernary = (path) => {
188
190
  };
189
191
 
190
192
  // A normalized print function for both `if` and `unless` nodes.
191
- const printConditional = (keyword) => (path, { inlineConditionals }, print) => {
193
+ const printConditional = (keyword) => (path, { rubyModifier }, print) => {
192
194
  if (canTernary(path)) {
193
195
  let ternaryParts = [path.call(print, "body", 0), " ? "].concat(
194
196
  printTernaryClauses(
@@ -217,7 +219,7 @@ const printConditional = (keyword) => (path, { inlineConditionals }, print) => {
217
219
 
218
220
  // If the body of the conditional is empty, then we explicitly have to use the
219
221
  // block form.
220
- if (statements.type === "stmts" && statements.body[0].type === "void_stmt") {
222
+ if (isEmptyStmts(statements) && !statements.body[0].comments) {
221
223
  return concat([
222
224
  `${keyword} `,
223
225
  align(keyword.length + 1, path.call(print, "body", 0)),
@@ -237,7 +239,7 @@ const printConditional = (keyword) => (path, { inlineConditionals }, print) => {
237
239
  ]);
238
240
  }
239
241
 
240
- return printSingle(keyword)(path, { inlineConditionals }, print);
242
+ return printSingle(keyword)(path, { rubyModifier }, print);
241
243
  };
242
244
 
243
245
  module.exports = {
@@ -272,7 +274,7 @@ module.exports = {
272
274
  },
273
275
  if: printConditional("if"),
274
276
  ifop: printTernary,
275
- if_mod: printSingle("if"),
277
+ if_mod: printSingle("if", true),
276
278
  unless: printConditional("unless"),
277
- unless_mod: printSingle("unless")
279
+ unless_mod: printSingle("unless", true)
278
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,6 +1,41 @@
1
1
  const { concat, join } = require("../prettier");
2
2
  const { literal } = require("../utils");
3
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
+ };
13
+
14
+ const unskippableParens = [
15
+ "if_mod",
16
+ "rescue_mod",
17
+ "unless_mod",
18
+ "until_mod",
19
+ "while_mod"
20
+ ];
21
+
22
+ const maybeHandleParens = (path, print, keyword, steps) => {
23
+ const node = nodeDive(path.getValue(), steps);
24
+ if (node.type !== "paren") {
25
+ return null;
26
+ }
27
+
28
+ const stmts = node.body[0].body;
29
+ if (stmts.length === 1 && !unskippableParens.includes(stmts[0].type)) {
30
+ return concat([
31
+ `${keyword} `,
32
+ path.call.apply(path, [print].concat(steps).concat("body", 0))
33
+ ]);
34
+ }
35
+
36
+ return concat([keyword, path.call.apply(path, [print].concat(steps))]);
37
+ };
38
+
4
39
  module.exports = {
5
40
  break: (path, opts, print) => {
6
41
  const content = path.getValue().body[0];
@@ -9,14 +44,11 @@ module.exports = {
9
44
  return "break";
10
45
  }
11
46
 
12
- if (content.body[0].body[0].type === "paren") {
13
- return concat([
14
- "break ",
15
- path.call(print, "body", 0, "body", 0, "body", 0, "body", 0)
16
- ]);
17
- }
18
-
19
- return concat(["break ", join(", ", path.call(print, "body", 0))]);
47
+ const steps = ["body", 0, "body", 0, "body", 0];
48
+ return (
49
+ maybeHandleParens(path, print, "break", steps) ||
50
+ concat(["break ", join(", ", path.call(print, "body", 0))])
51
+ );
20
52
  },
21
53
  next: (path, opts, print) => {
22
54
  const args = path.getValue().body[0].body[0];
@@ -25,15 +57,11 @@ module.exports = {
25
57
  return "next";
26
58
  }
27
59
 
28
- if (args.body[0].type === "paren") {
29
- // Ignoring the parens node and just going straight to the content
30
- return concat([
31
- "next ",
32
- path.call(print, "body", 0, "body", 0, "body", 0, "body", 0)
33
- ]);
34
- }
35
-
36
- return concat(["next ", join(", ", path.call(print, "body", 0))]);
60
+ const steps = ["body", 0, "body", 0, "body", 0];
61
+ return (
62
+ maybeHandleParens(path, print, "next", steps) ||
63
+ concat(["next ", join(", ", path.call(print, "body", 0))])
64
+ );
37
65
  },
38
66
  yield: (path, opts, print) => {
39
67
  if (path.getValue().body[0].type === "paren") {
@@ -1,23 +1,14 @@
1
1
  const {
2
2
  concat,
3
3
  group,
4
+ hardline,
4
5
  ifBreak,
5
6
  indent,
6
7
  join,
7
- line,
8
- literalline
8
+ line
9
9
  } = require("../prettier");
10
- const { prefix, skipAssignIndent } = require("../utils");
11
10
 
12
- const nodeDive = (node, steps) => {
13
- let current = node;
14
-
15
- steps.forEach((step) => {
16
- current = current[step];
17
- });
18
-
19
- return current;
20
- };
11
+ const { getTrailingComma, prefix, skipAssignIndent } = require("../utils");
21
12
 
22
13
  // When attempting to convert a hash rocket into a hash label, you need to take
23
14
  // care because only certain patterns are allowed. Ruby source says that they
@@ -29,119 +20,142 @@ const nodeDive = (node, steps) => {
29
20
  //
30
21
  // This function represents that check, as it determines if it can convert the
31
22
  // symbol node into a hash label.
32
- const isValidHashLabel = (symbolLiteral) => {
33
- const label = symbolLiteral.body[0].body[0].body;
23
+ function isValidHashLabel(symbolLiteral) {
24
+ const label = symbolLiteral.body[0].body;
34
25
  return label.match(/^[_A-Za-z]/) && !label.endsWith("=");
35
- };
26
+ }
36
27
 
37
- const makeLabel = (path, { preferHashLabels }, print, steps) => {
38
- const labelNode = nodeDive(path.getValue(), steps);
39
- const labelDoc = path.call.apply(path, [print].concat(steps));
28
+ function canUseHashLabels(contentsNode) {
29
+ return contentsNode.body.every((assocNode) => {
30
+ if (assocNode.type === "assoc_splat") {
31
+ return true;
32
+ }
40
33
 
41
- switch (labelNode.type) {
42
- case "@label":
43
- if (preferHashLabels) {
44
- return labelDoc;
45
- }
46
- return `:${labelDoc.slice(0, labelDoc.length - 1)} =>`;
47
- case "symbol_literal": {
48
- if (preferHashLabels && isValidHashLabel(labelNode)) {
49
- const symbolSteps = steps.concat("body", 0, "body", 0);
50
-
51
- return concat([
52
- path.call.apply(path, [print].concat(symbolSteps)),
53
- ":"
54
- ]);
55
- }
56
- return concat([labelDoc, " =>"]);
34
+ switch (assocNode.body[0].type) {
35
+ case "@label":
36
+ return true;
37
+ case "symbol_literal":
38
+ return isValidHashLabel(assocNode.body[0]);
39
+ case "dyna_symbol":
40
+ return true;
41
+ default:
42
+ return false;
57
43
  }
44
+ });
45
+ }
46
+
47
+ function printHashKeyLabel(path, print) {
48
+ const node = path.getValue();
49
+
50
+ switch (node.type) {
51
+ case "@label":
52
+ return print(path);
53
+ case "symbol_literal":
54
+ return concat([path.call(print, "body", 0), ":"]);
58
55
  case "dyna_symbol":
59
- if (preferHashLabels) {
60
- return concat(labelDoc.parts.slice(1).concat(":"));
61
- }
62
- return concat([labelDoc, " =>"]);
63
- default:
64
- return concat([labelDoc, " =>"]);
56
+ return concat(print(path).parts.slice(1).concat(":"));
65
57
  }
66
- };
58
+ }
67
59
 
68
- module.exports = {
69
- assoc_new: (path, opts, print) => {
70
- const valueDoc = path.call(print, "body", 1);
71
- const parts = [makeLabel(path, opts, print, ["body", 0])];
72
-
73
- if (skipAssignIndent(path.getValue().body[1])) {
74
- parts.push(" ", valueDoc);
75
- } else {
76
- parts.push(indent(concat([line, valueDoc])));
77
- }
60
+ function printHashKeyRocket(path, print) {
61
+ const node = path.getValue();
62
+ const doc = print(path);
78
63
 
79
- return group(concat(parts));
80
- },
81
- assoc_splat: prefix("**"),
82
- assoclist_from_args: (path, opts, print) => {
83
- const { addTrailingCommas } = opts;
84
-
85
- const assocNodes = path.getValue().body[0];
86
- const assocDocs = [];
87
-
88
- assocNodes.forEach((assocNode, index) => {
89
- const isInner = index !== assocNodes.length - 1;
90
- const valueNode = assocNode.body[1];
91
-
92
- const isStraightHeredoc = valueNode && valueNode.type === "heredoc";
93
- const isSquigglyHeredoc =
94
- valueNode &&
95
- valueNode.type === "string_literal" &&
96
- valueNode.body[0].type === "heredoc";
97
-
98
- if (isStraightHeredoc || isSquigglyHeredoc) {
99
- const heredocSteps = isStraightHeredoc
100
- ? ["body", 1]
101
- : ["body", 1, "body", 0];
102
- const { beging, ending } = nodeDive(assocNode, heredocSteps);
103
-
104
- assocDocs.push(
105
- makeLabel(path, opts, print, ["body", 0, index, "body", 0]),
106
- " ",
107
- beging,
108
- isInner || addTrailingCommas ? "," : "",
109
- literalline,
110
- concat(
111
- path.map.apply(
112
- path,
113
- [print, "body", 0, index].concat(heredocSteps).concat("body")
114
- )
115
- ),
116
- ending,
117
- isInner ? line : ""
118
- );
119
- } else {
120
- assocDocs.push(path.call(print, "body", 0, index));
121
-
122
- if (isInner) {
123
- assocDocs.push(concat([",", line]));
124
- } else if (addTrailingCommas) {
125
- assocDocs.push(ifBreak(",", ""));
126
- }
127
- }
128
- });
129
-
130
- return group(concat(assocDocs));
131
- },
132
- bare_assoc_hash: (path, opts, print) =>
133
- group(join(concat([",", line]), path.map(print, "body", 0))),
134
- hash: (path, opts, print) => {
135
- if (path.getValue().body[0] === null) {
136
- return "{}";
137
- }
64
+ if (node.type === "@label") {
65
+ return `:${doc.slice(0, doc.length - 1)} =>`;
66
+ }
67
+
68
+ return concat([doc, " =>"]);
69
+ }
70
+
71
+ function printAssocNew(path, opts, print) {
72
+ const { keyPrinter } = path.getParentNode();
73
+
74
+ const parts = [path.call((keyPath) => keyPrinter(keyPath, print), "body", 0)];
75
+ const valueDoc = path.call(print, "body", 1);
76
+
77
+ if (skipAssignIndent(path.getValue().body[1])) {
78
+ parts.push(" ", valueDoc);
79
+ } else {
80
+ parts.push(indent(concat([line, valueDoc])));
81
+ }
82
+
83
+ return group(concat(parts));
84
+ }
85
+
86
+ function printHashContents(path, opts, print) {
87
+ const node = path.getValue();
138
88
 
89
+ // First determine which key printer we're going to use, so that the child
90
+ // nodes can reference it when they go to get printed.
91
+ node.keyPrinter =
92
+ opts.rubyHashLabel && canUseHashLabels(path.getValue())
93
+ ? printHashKeyLabel
94
+ : printHashKeyRocket;
95
+
96
+ const contents = join(concat([",", line]), path.map(print, "body"));
97
+
98
+ // If we're inside a hash literal, then we want to add the braces at this
99
+ // level so that the grouping is correct. Otherwise you could end up with
100
+ // opening and closing braces being split up, but the contents not being split
101
+ // correctly.
102
+ if (path.getParentNode().type === "hash") {
139
103
  return group(
140
104
  concat([
141
105
  "{",
142
- indent(concat([line, concat(path.map(print, "body"))])),
143
- concat([line, "}"])
106
+ indent(
107
+ concat([
108
+ line,
109
+ contents,
110
+ getTrailingComma(opts) ? ifBreak(",", "") : ""
111
+ ])
112
+ ),
113
+ line,
114
+ "}"
144
115
  ])
145
116
  );
146
117
  }
118
+
119
+ // Otherwise, we're inside a bare_assoc_hash, so we don't want to print
120
+ // braces at all.
121
+ return group(contents);
122
+ }
123
+
124
+ function printEmptyHashWithComments(path, opts) {
125
+ const hashNode = path.getValue();
126
+
127
+ const printComment = (commentPath, index) => {
128
+ hashNode.comments[index].printed = true;
129
+ return opts.printer.printComment(commentPath);
130
+ };
131
+
132
+ return concat([
133
+ "{",
134
+ indent(
135
+ concat([hardline, join(hardline, path.map(printComment, "comments"))])
136
+ ),
137
+ line,
138
+ "}"
139
+ ]);
140
+ }
141
+
142
+ function printHash(path, opts, print) {
143
+ const hashNode = path.getValue();
144
+
145
+ // Hashes normally have a single assoclist_from_args child node. If it's
146
+ // missing, then it means we're dealing with an empty hash, so we can just
147
+ // exit here and print.
148
+ if (hashNode.body[0] === null) {
149
+ return hashNode.comments ? printEmptyHashWithComments(path, opts) : "{}";
150
+ }
151
+
152
+ return path.call(print, "body", 0);
153
+ }
154
+
155
+ module.exports = {
156
+ assoc_new: printAssocNew,
157
+ assoc_splat: prefix("**"),
158
+ assoclist_from_args: printHashContents,
159
+ bare_assoc_hash: printHashContents,
160
+ hash: printHash
147
161
  };