prettier 0.21.0 → 0.22.0

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