prettier 0.20.1 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,20 +4,6 @@ const { concatBody, first, makeCall } = require("../utils");
4
4
 
5
5
  const noIndent = ["array", "hash", "if", "method_add_block", "xstring_literal"];
6
6
 
7
- const getHeredoc = (path, print, node) => {
8
- if (node.type === "heredoc") {
9
- const { beging, ending } = node;
10
- return { beging, ending, content: ["body", 0, "body"] };
11
- }
12
-
13
- if (node.type === "string_literal" && node.body[0].type === "heredoc") {
14
- const { beging, ending } = node.body[0];
15
- return { beging, ending, content: ["body", 0, "body", 0, "body"] };
16
- }
17
-
18
- return null;
19
- };
20
-
21
7
  module.exports = {
22
8
  call: (path, opts, print) => {
23
9
  const [receiverNode, _operatorNode, messageNode] = path.getValue().body;
@@ -36,15 +22,14 @@ module.exports = {
36
22
  // <<~TEXT.strip
37
23
  // content
38
24
  // TEXT
39
- const heredoc = getHeredoc(path, print, receiverNode);
40
- if (heredoc) {
25
+ if (receiverNode.type === "heredoc") {
41
26
  return concat([
42
- heredoc.beging,
27
+ receiverNode.beging,
43
28
  printedOperator,
44
29
  printedMessage,
45
30
  literalline,
46
- concat(path.map.apply(path, [print].concat(heredoc.content))),
47
- heredoc.ending
31
+ concat(path.map(print, "body", 0, "body")),
32
+ receiverNode.ending
48
33
  ]);
49
34
  }
50
35
 
@@ -8,7 +8,9 @@ const {
8
8
  indent,
9
9
  softline
10
10
  } = require("../prettier");
11
+
11
12
  const { containsAssignment } = require("../utils");
13
+ const inlineEnsureParens = require("../utils/inlineEnsureParens");
12
14
 
13
15
  const printWithAddition = (keyword, path, print, { breaking = false } = {}) =>
14
16
  concat([
@@ -95,30 +97,14 @@ const printSingle = (keyword) => (path, { inlineConditionals }, print) => {
95
97
  return multiline;
96
98
  }
97
99
 
98
- let inlineParts = [
99
- path.call(print, "body", 1),
100
- ` ${keyword} `,
101
- path.call(print, "body", 0)
102
- ];
103
-
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
- }
100
+ const inline = concat(
101
+ inlineEnsureParens(path, [
102
+ path.call(print, "body", 1),
103
+ ` ${keyword} `,
104
+ path.call(print, "body", 0)
105
+ ])
106
+ );
120
107
 
121
- const inline = concat(inlineParts);
122
108
  return group(ifBreak(multiline, inline));
123
109
  };
124
110
 
@@ -180,7 +166,9 @@ const canTernary = (path) => {
180
166
  const [predicate, stmts, addition] = path.getValue().body;
181
167
 
182
168
  return (
183
- !["assign", "opassign"].includes(predicate.type) &&
169
+ !["assign", "opassign", "command_call", "command"].includes(
170
+ predicate.type
171
+ ) &&
184
172
  addition &&
185
173
  addition.type === "else" &&
186
174
  [stmts, addition.body[0]].every(canTernaryStmts)
@@ -1,5 +1,30 @@
1
1
  const { concat, join } = require("../prettier");
2
- const { literal } = require("../utils");
2
+ const { literal, nodeDive } = require("../utils");
3
+
4
+ const unskippableParens = [
5
+ "if_mod",
6
+ "rescue_mod",
7
+ "unless_mod",
8
+ "until_mod",
9
+ "while_mod"
10
+ ];
11
+
12
+ const maybeHandleParens = (path, print, keyword, steps) => {
13
+ const node = nodeDive(path.getValue(), steps);
14
+ if (node.type !== "paren") {
15
+ return null;
16
+ }
17
+
18
+ const stmts = node.body[0].body;
19
+ if (stmts.length === 1 && !unskippableParens.includes(stmts[0].type)) {
20
+ return concat([
21
+ `${keyword} `,
22
+ path.call.apply(path, [print].concat(steps).concat("body", 0))
23
+ ]);
24
+ }
25
+
26
+ return concat([keyword, path.call.apply(path, [print].concat(steps))]);
27
+ };
3
28
 
4
29
  module.exports = {
5
30
  break: (path, opts, print) => {
@@ -9,14 +34,11 @@ module.exports = {
9
34
  return "break";
10
35
  }
11
36
 
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))]);
37
+ const steps = ["body", 0, "body", 0, "body", 0];
38
+ return (
39
+ maybeHandleParens(path, print, "break", steps) ||
40
+ concat(["break ", join(", ", path.call(print, "body", 0))])
41
+ );
20
42
  },
21
43
  next: (path, opts, print) => {
22
44
  const args = path.getValue().body[0].body[0];
@@ -25,15 +47,11 @@ module.exports = {
25
47
  return "next";
26
48
  }
27
49
 
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))]);
50
+ const steps = ["body", 0, "body", 0, "body", 0];
51
+ return (
52
+ maybeHandleParens(path, print, "next", steps) ||
53
+ concat(["next ", join(", ", path.call(print, "body", 0))])
54
+ );
37
55
  },
38
56
  yield: (path, opts, print) => {
39
57
  if (path.getValue().body[0].type === "paren") {
@@ -7,17 +7,8 @@ const {
7
7
  line,
8
8
  literalline
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 { nodeDive, 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
@@ -65,6 +56,45 @@ const makeLabel = (path, { preferHashLabels }, print, steps) => {
65
56
  }
66
57
  };
67
58
 
59
+ function printHash(path, { addTrailingCommas }, print) {
60
+ const hashNode = path.getValue();
61
+
62
+ // Hashes normally have a single assoclist_from_args child node. If it's
63
+ // missing, then it means we're dealing with an empty hash, so we can just
64
+ // exit here and print.
65
+ 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(",", ""));
87
+ }
88
+
89
+ return group(
90
+ concat([
91
+ "{",
92
+ indent(concat([line, concat(assocDocs)])),
93
+ concat([line, "}"])
94
+ ])
95
+ );
96
+ }
97
+
68
98
  module.exports = {
69
99
  assoc_new: (path, opts, print) => {
70
100
  const valueDoc = path.call(print, "body", 1);
@@ -89,31 +119,15 @@ module.exports = {
89
119
  const isInner = index !== assocNodes.length - 1;
90
120
  const valueNode = assocNode.body[1];
91
121
 
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
-
122
+ if (valueNode && valueNode.type === "heredoc") {
104
123
  assocDocs.push(
105
124
  makeLabel(path, opts, print, ["body", 0, index, "body", 0]),
106
125
  " ",
107
- beging,
126
+ valueNode.beging,
108
127
  isInner || addTrailingCommas ? "," : "",
109
128
  literalline,
110
- concat(
111
- path.map.apply(
112
- path,
113
- [print, "body", 0, index].concat(heredocSteps).concat("body")
114
- )
115
- ),
116
- ending,
129
+ concat(path.map(print, "body", 0, index, "body", 1, "body")),
130
+ valueNode.ending,
117
131
  isInner ? line : ""
118
132
  );
119
133
  } else {
@@ -121,8 +135,6 @@ module.exports = {
121
135
 
122
136
  if (isInner) {
123
137
  assocDocs.push(concat([",", line]));
124
- } else if (addTrailingCommas) {
125
- assocDocs.push(ifBreak(",", ""));
126
138
  }
127
139
  }
128
140
  });
@@ -131,17 +143,5 @@ module.exports = {
131
143
  },
132
144
  bare_assoc_hash: (path, opts, print) =>
133
145
  group(join(concat([",", line]), path.map(print, "body", 0))),
134
- hash: (path, opts, print) => {
135
- if (path.getValue().body[0] === null) {
136
- return "{}";
137
- }
138
-
139
- return group(
140
- concat([
141
- "{",
142
- indent(concat([line, concat(path.map(print, "body"))])),
143
- concat([line, "}"])
144
- ])
145
- );
146
- }
146
+ hash: printHash
147
147
  };
@@ -1,13 +1,43 @@
1
1
  const { concat, group, indent, line } = require("../prettier");
2
+ const { isEmptyStmts } = require("../utils");
2
3
 
3
- const printHook = (name) => (path, opts, print) =>
4
- group(
5
- concat([
4
+ /* The `BEGIN` and `END` keywords are used to hook into the Ruby process. Any
5
+ * `BEGIN` blocks are executed right when the process starts up, and the `END`
6
+ * blocks are executed right before exiting.
7
+ *
8
+ * BEGIN {
9
+ * # content goes here
10
+ * }
11
+ *
12
+ * END {
13
+ * # content goes here
14
+ * }
15
+ *
16
+ * Interesting side note, you don't use `do...end` blocks with these hooks. Both
17
+ * nodes contain one child which is a `stmts` node.
18
+ */
19
+ function printHook(name) {
20
+ function printHookWithName(path, opts, print) {
21
+ const stmtsNode = path.getValue().body[0];
22
+ const printedStmts = path.call(print, "body", 0);
23
+
24
+ const parts = [
6
25
  `${name} {`,
7
- indent(concat([line, path.call(print, "body", 0)])),
26
+ indent(concat([line, printedStmts])),
8
27
  concat([line, "}"])
9
- ])
10
- );
28
+ ];
29
+
30
+ // If there are no statements but there are comments, then we want to skip
31
+ // printing the newline so that we don't end up with multiple spaces.
32
+ if (isEmptyStmts(stmtsNode) && stmtsNode.comments) {
33
+ parts[1] = indent(printedStmts);
34
+ }
35
+
36
+ return group(concat(parts));
37
+ }
38
+
39
+ return printHookWithName;
40
+ }
11
41
 
12
42
  module.exports = {
13
43
  BEGIN: printHook("BEGIN"),
@@ -4,56 +4,86 @@ const {
4
4
  ifBreak,
5
5
  indent,
6
6
  line,
7
- removeLines,
8
7
  softline
9
8
  } = require("../prettier");
10
- const { hasAncestor } = require("../utils");
9
+ const { hasAncestor, nodeDive } = require("../utils");
11
10
 
12
- module.exports = {
13
- lambda: (path, opts, print) => {
14
- let params = path.getValue().body[0];
15
- let paramsConcat = "";
11
+ // We can have our params coming in as the first child of the main lambda node,
12
+ // or if we have them wrapped in parens then they'll be one level deeper. Even
13
+ // though it's possible to omit the parens if you only have one argument, we're
14
+ // going to keep them in no matter what for consistency.
15
+ const printLambdaParams = (path, print) => {
16
+ const steps = ["body", 0];
17
+ let params = nodeDive(path.getValue(), steps);
16
18
 
17
- if (params.type === "params") {
18
- paramsConcat = path.call(print, "body", 0);
19
- } else {
20
- [params] = params.body;
21
- paramsConcat = path.call(print, "body", 0, "body", 0);
22
- }
19
+ if (params.type !== "params") {
20
+ steps.push("body", 0);
21
+ params = nodeDive(path.getValue(), steps);
22
+ }
23
23
 
24
- const noParams = params.body.every((type) => !type);
25
- const inlineLambda = concat([
26
- "->",
27
- noParams ? "" : concat(["(", paramsConcat, ")"]),
28
- " { ",
29
- path.call(print, "body", 1),
30
- " }"
31
- ]);
24
+ // If we don't have any params at all, then we're just going to bail out and
25
+ // print nothing. This is to avoid printing an empty set of parentheses.
26
+ if (params.body.every((type) => !type)) {
27
+ return "";
28
+ }
32
29
 
33
- if (hasAncestor(path, ["command", "command_call"])) {
34
- return group(
35
- ifBreak(
36
- concat([
37
- "lambda {",
38
- noParams ? "" : concat([" |", removeLines(paramsConcat), "|"]),
39
- indent(concat([line, path.call(print, "body", 1)])),
40
- concat([line, "}"])
41
- ]),
42
- inlineLambda
43
- )
44
- );
45
- }
30
+ return group(
31
+ concat([
32
+ "(",
33
+ indent(concat([softline, path.call.apply(path, [print].concat(steps))])),
34
+ softline,
35
+ ")"
36
+ ])
37
+ );
38
+ };
46
39
 
47
- return group(
48
- ifBreak(
49
- concat([
50
- "lambda do",
51
- noParams ? "" : concat([" |", removeLines(paramsConcat), "|"]),
52
- indent(concat([softline, path.call(print, "body", 1)])),
53
- concat([softline, "end"])
54
- ]),
55
- inlineLambda
56
- )
57
- );
58
- }
40
+ // Lambda nodes represent stabby lambda literals, which can come in a couple of
41
+ // flavors. They can use either braces or do...end for their block, and their
42
+ // arguments can be not present, have no parentheses for a single argument, or
43
+ // have parentheses for multiple arguments. Below are a couple of examples:
44
+ //
45
+ // -> { 1 }
46
+ // -> a { a + 1 }
47
+ // ->(a) { a + 1 }
48
+ // ->(a, b) { a + b }
49
+ // ->(a, b = 1) { a + b }
50
+ //
51
+ // -> do
52
+ // 1
53
+ // end
54
+ //
55
+ // -> a do
56
+ // a + 1
57
+ // end
58
+ //
59
+ // ->(a, b) do
60
+ // a + b
61
+ // end
62
+ //
63
+ // Generally, we're going to favor do...end for the multi-line form and braces
64
+ // for the single-line form. However, if we have an ancestor that is a command
65
+ // or command_call node, then we'll need to use braces either way because of
66
+ // operator precendence.
67
+ const printLambda = (path, opts, print) => {
68
+ const params = printLambdaParams(path, print);
69
+ const inCommand = hasAncestor(path, ["command", "command_call"]);
70
+
71
+ return group(
72
+ ifBreak(
73
+ concat([
74
+ "->",
75
+ params,
76
+ " ",
77
+ inCommand ? "{" : "do",
78
+ indent(concat([line, path.call(print, "body", 1)])),
79
+ line,
80
+ inCommand ? "}" : "end"
81
+ ]),
82
+ concat(["->", params, " { ", path.call(print, "body", 1), " }"])
83
+ )
84
+ );
85
+ };
86
+
87
+ module.exports = {
88
+ lambda: printLambda
59
89
  };