prettier 0.20.1 → 0.21.0

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.
@@ -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
  };