prettier 0.21.0 → 0.22.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.
@@ -0,0 +1,74 @@
1
+ const {
2
+ concat,
3
+ group,
4
+ hardline,
5
+ ifBreak,
6
+ indent,
7
+ line
8
+ } = require("../prettier");
9
+
10
+ function printClass(path, opts, print) {
11
+ const [_constant, superclass, bodystmt] = path.getValue().body;
12
+ const stmts = bodystmt.body[0];
13
+
14
+ const parts = ["class ", path.call(print, "body", 0)];
15
+ if (superclass) {
16
+ parts.push(" < ", path.call(print, "body", 1));
17
+ }
18
+
19
+ // If the body is empty and does not contain any comments, we can just
20
+ // replace the body with a semi-colon.
21
+ if (
22
+ stmts.body.length === 1 &&
23
+ stmts.body[0].type === "void_stmt" &&
24
+ !stmts.body[0].comments
25
+ ) {
26
+ return group(concat([concat(parts), ifBreak(line, "; "), "end"]));
27
+ }
28
+
29
+ return group(
30
+ concat([
31
+ concat(parts),
32
+ indent(concat([hardline, path.call(print, "body", 2)])),
33
+ concat([hardline, "end"])
34
+ ])
35
+ );
36
+ }
37
+
38
+ function printModule(path, opts, print) {
39
+ const declaration = group(concat(["module ", path.call(print, "body", 0)]));
40
+
41
+ // If the body is empty, we can replace with a ;
42
+ const stmts = path.getValue().body[1].body[0];
43
+ if (
44
+ stmts.body.length === 1 &&
45
+ stmts.body[0].type === "void_stmt" &&
46
+ !stmts.body[0].comments
47
+ ) {
48
+ return group(concat([declaration, ifBreak(line, "; "), "end"]));
49
+ }
50
+
51
+ return group(
52
+ concat([
53
+ declaration,
54
+ indent(concat([hardline, path.call(print, "body", 1)])),
55
+ concat([hardline, "end"])
56
+ ])
57
+ );
58
+ }
59
+
60
+ function printSClass(path, opts, print) {
61
+ return group(
62
+ concat([
63
+ concat(["class << ", path.call(print, "body", 0)]),
64
+ indent(concat([hardline, path.call(print, "body", 1)])),
65
+ concat([hardline, "end"])
66
+ ])
67
+ );
68
+ }
69
+
70
+ module.exports = {
71
+ class: printClass,
72
+ module: printModule,
73
+ sclass: printSClass
74
+ };
@@ -1,5 +1,14 @@
1
- const { align, concat, group, ifBreak, join, line } = require("../prettier");
2
- const { docLength, makeArgs, makeCall } = require("../utils");
1
+ const {
2
+ align,
3
+ concat,
4
+ group,
5
+ ifBreak,
6
+ indent,
7
+ join,
8
+ line,
9
+ softline
10
+ } = require("../prettier");
11
+ const { docLength, makeCall } = require("../utils");
3
12
 
4
13
  const hasDef = (node) =>
5
14
  node.body[1].type === "args_add_block" &&
@@ -23,32 +32,39 @@ const hasDef = (node) =>
23
32
  const skipArgsAlign = (path) =>
24
33
  ["to", "not_to"].includes(path.getValue().body[2].body);
25
34
 
35
+ // If there is a ternary argument to a command and it's going to get broken
36
+ // into multiple lines, then we're going to have to use parentheses around the
37
+ // command in order to make sure operator precedence doesn't get messed up.
38
+ const hasTernaryArg = (path) =>
39
+ path.getValue().body[1].body[0].body.some((node) => node.type === "ifop");
40
+
26
41
  module.exports = {
27
42
  command: (path, opts, print) => {
28
43
  const command = path.call(print, "body", 0);
29
- const { args, heredocs } = makeArgs(path, opts, print, 1);
44
+ const joinedArgs = join(concat([",", line]), path.call(print, "body", 1));
30
45
 
31
- if (heredocs.length > 1) {
32
- return concat([command, " ", join(", ", args)].concat(heredocs));
33
- }
46
+ const hasTernary = hasTernaryArg(path);
47
+ let breakArgs;
34
48
 
35
- const joinedArgs = join(concat([",", line]), args);
36
- const breakArgs = hasDef(path.getValue())
37
- ? joinedArgs
38
- : align(command.length + 1, joinedArgs);
49
+ if (hasTernary) {
50
+ breakArgs = indent(concat([softline, joinedArgs]));
51
+ } else if (hasDef(path.getValue())) {
52
+ breakArgs = joinedArgs;
53
+ } else {
54
+ breakArgs = align(command.length + 1, joinedArgs);
55
+ }
39
56
 
40
- const commandDoc = group(
57
+ return group(
41
58
  ifBreak(
42
- concat([command, " ", breakArgs]),
59
+ concat([
60
+ command,
61
+ hasTernary ? "(" : " ",
62
+ breakArgs,
63
+ hasTernary ? concat([softline, ")"]) : ""
64
+ ]),
43
65
  concat([command, " ", joinedArgs])
44
66
  )
45
67
  );
46
-
47
- if (heredocs.length === 1) {
48
- return group(concat([commandDoc].concat(heredocs)));
49
- }
50
-
51
- return commandDoc;
52
68
  },
53
69
  command_call: (path, opts, print) => {
54
70
  const parts = [
@@ -62,25 +78,14 @@ module.exports = {
62
78
  }
63
79
 
64
80
  parts.push(" ");
65
- const { args, heredocs } = makeArgs(path, opts, print, 3);
66
-
67
- if (heredocs.length > 1) {
68
- return concat(parts.concat([join(", ", args)]).concat(heredocs));
69
- }
70
81
 
71
- const joinedArgs = join(concat([",", line]), args);
82
+ const joinedArgs = join(concat([",", line]), path.call(print, "body", 3));
72
83
  const breakArgs = skipArgsAlign(path)
73
84
  ? joinedArgs
74
85
  : align(docLength(concat(parts)), joinedArgs);
75
86
 
76
- const commandDoc = group(
87
+ return group(
77
88
  ifBreak(concat(parts.concat(breakArgs)), concat(parts.concat(joinedArgs)))
78
89
  );
79
-
80
- if (heredocs.length === 1) {
81
- return group(concat([commandDoc].concat(heredocs)));
82
- }
83
-
84
- return commandDoc;
85
90
  }
86
91
  };
@@ -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, { inlineConditionals }, 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 (!inlineConditionals || (!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",
@@ -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)),
@@ -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,13 @@
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
-
11
- const { nodeDive, prefix, skipAssignIndent } = require("../utils");
10
+ const { prefix, skipAssignIndent } = require("../utils");
12
11
 
13
12
  // When attempting to convert a hash rocket into a hash label, you need to take
14
13
  // care because only certain patterns are allowed. Ruby source says that they
@@ -20,14 +19,14 @@ const { nodeDive, prefix, skipAssignIndent } = require("../utils");
20
19
  //
21
20
  // This function represents that check, as it determines if it can convert the
22
21
  // symbol node into a hash label.
23
- const isValidHashLabel = (symbolLiteral) => {
24
- const label = symbolLiteral.body[0].body[0].body;
22
+ function isValidHashLabel(symbolLiteral) {
23
+ const label = symbolLiteral.body[0].body;
25
24
  return label.match(/^[_A-Za-z]/) && !label.endsWith("=");
26
- };
25
+ }
27
26
 
28
- const makeLabel = (path, { preferHashLabels }, print, steps) => {
29
- const labelNode = nodeDive(path.getValue(), steps);
30
- const labelDoc = path.call.apply(path, [print].concat(steps));
27
+ function printHashKey(path, { preferHashLabels }, print) {
28
+ const labelNode = path.getValue().body[0];
29
+ const labelDoc = path.call(print, "body", 0);
31
30
 
32
31
  switch (labelNode.type) {
33
32
  case "@label":
@@ -37,12 +36,7 @@ const makeLabel = (path, { preferHashLabels }, print, steps) => {
37
36
  return `:${labelDoc.slice(0, labelDoc.length - 1)} =>`;
38
37
  case "symbol_literal": {
39
38
  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
- ]);
39
+ return concat([path.call(print, "body", 0, "body", 0), ":"]);
46
40
  }
47
41
  return concat([labelDoc, " =>"]);
48
42
  }
@@ -54,94 +48,73 @@ const makeLabel = (path, { preferHashLabels }, print, steps) => {
54
48
  default:
55
49
  return concat([labelDoc, " =>"]);
56
50
  }
57
- };
51
+ }
52
+
53
+ function printAssocNew(path, opts, print) {
54
+ const valueDoc = path.call(print, "body", 1);
55
+ const parts = [printHashKey(path, opts, print)];
56
+
57
+ if (skipAssignIndent(path.getValue().body[1])) {
58
+ parts.push(" ", valueDoc);
59
+ } else {
60
+ parts.push(indent(concat([line, valueDoc])));
61
+ }
62
+
63
+ return group(concat(parts));
64
+ }
65
+
66
+ function printEmptyHashWithComments(path, opts) {
67
+ const hashNode = path.getValue();
68
+
69
+ const printComment = (commentPath, index) => {
70
+ hashNode.comments[index].printed = true;
71
+ return opts.printer.printComment(commentPath);
72
+ };
73
+
74
+ return concat([
75
+ "{",
76
+ indent(
77
+ concat([hardline, join(hardline, path.map(printComment, "comments"))])
78
+ ),
79
+ line,
80
+ "}"
81
+ ]);
82
+ }
58
83
 
59
- function printHash(path, { addTrailingCommas }, print) {
84
+ function printHash(path, opts, print) {
60
85
  const hashNode = path.getValue();
61
86
 
62
87
  // Hashes normally have a single assoclist_from_args child node. If it's
63
88
  // missing, then it means we're dealing with an empty hash, so we can just
64
89
  // exit here and print.
65
90
  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(",", ""));
91
+ return hashNode.comments ? printEmptyHashWithComments(path, opts) : "{}";
87
92
  }
88
93
 
89
94
  return group(
90
95
  concat([
91
96
  "{",
92
- indent(concat([line, concat(assocDocs)])),
93
- concat([line, "}"])
97
+ indent(
98
+ concat([
99
+ line,
100
+ path.call(print, "body", 0),
101
+ opts.addTrailingCommas ? ifBreak(",", "") : ""
102
+ ])
103
+ ),
104
+ line,
105
+ "}"
94
106
  ])
95
107
  );
96
108
  }
97
109
 
98
- 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
- }
110
+ function printHashContents(path, opts, print) {
111
+ return group(join(concat([",", line]), path.map(print, "body")));
112
+ }
108
113
 
109
- return group(concat(parts));
110
- },
114
+ module.exports = {
115
+ assoc_new: printAssocNew,
111
116
  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))),
117
+ assoclist_from_args: printHashContents,
118
+ bare_assoc_hash: printHashContents,
146
119
  hash: printHash
147
120
  };