prettier 0.20.0 → 1.0.0.pre.rc2

Sign up to get free protection for your applications and to get access to all the features.
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
  };