prettier 0.16.0 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -42,7 +42,7 @@ module.exports = {
42
42
 
43
43
  // It's possible in a when to just have empty void statements, in which case
44
44
  // we would skip adding the body.
45
- if (!stmts.parts.every(part => !part)) {
45
+ if (!stmts.parts.every((part) => !part)) {
46
46
  parts.push(indent(concat([hardline, stmts])));
47
47
  }
48
48
 
@@ -1,11 +1,27 @@
1
1
  const { align, concat, group, ifBreak, join, line } = require("../prettier");
2
2
  const { docLength, makeArgs, makeCall } = require("../utils");
3
3
 
4
- const hasDef = node =>
4
+ const hasDef = (node) =>
5
5
  node.body[1].type === "args_add_block" &&
6
6
  node.body[1].body[0].type === "args" &&
7
7
  node.body[1].body[0].body[0] &&
8
- node.body[1].body[0].body[0].type === "def";
8
+ ["def", "defs"].includes(node.body[1].body[0].body[0].type);
9
+
10
+ // Very special handling case for rspec matchers. In general with rspec matchers
11
+ // you expect to see something like:
12
+ //
13
+ // expect(foo).to receive(:bar).with(
14
+ // 'one',
15
+ // 'two',
16
+ // 'three',
17
+ // 'four',
18
+ // 'five'
19
+ // )
20
+ //
21
+ // In this case the arguments are aligned to the left side as opposed to being
22
+ // aligned with the `receive` call.
23
+ const skipArgsAlign = (path) =>
24
+ ["to", "not_to"].includes(path.getValue().body[2].body);
9
25
 
10
26
  module.exports = {
11
27
  command: (path, opts, print) => {
@@ -53,10 +69,9 @@ module.exports = {
53
69
  }
54
70
 
55
71
  const joinedArgs = join(concat([",", line]), args);
56
- const breakArgs =
57
- path.getValue().body[2].body === "to"
58
- ? joinedArgs
59
- : align(docLength(concat(parts)), joinedArgs);
72
+ const breakArgs = skipArgsAlign(path)
73
+ ? joinedArgs
74
+ : align(docLength(concat(parts)), joinedArgs);
60
75
 
61
76
  const commandDoc = group(
62
77
  ifBreak(concat(parts.concat(breakArgs)), concat(parts.concat(joinedArgs)))
@@ -23,7 +23,7 @@ const printWithAddition = (keyword, path, print, { breaking = false } = {}) =>
23
23
  // For the unary `not` operator, we need to explicitly add parentheses to it in
24
24
  // order for it to be valid from within a ternary. Otherwise if the clause of
25
25
  // the ternary isn't a unary `not`, we can just pass it along.
26
- const printTernaryClause = clause => {
26
+ const printTernaryClause = (clause) => {
27
27
  if (clause.type === "concat") {
28
28
  const [part] = clause.parts;
29
29
 
@@ -75,23 +75,21 @@ const printTernary = (path, _opts, print) => {
75
75
  );
76
76
  };
77
77
 
78
- const makeSingleBlockForm = (keyword, path, print) =>
79
- concat([
78
+ // Prints an `if_mod` or `unless_mod` node. Because it was previously in the
79
+ // modifier form, we're guaranteed to not have an additional node, so we can
80
+ // just work with the predicate and the body.
81
+ const printSingle = (keyword) => (path, { inlineConditionals }, print) => {
82
+ const multiline = concat([
80
83
  `${keyword} `,
81
84
  align(keyword.length + 1, path.call(print, "body", 0)),
82
85
  indent(concat([softline, path.call(print, "body", 1)])),
83
86
  concat([softline, "end"])
84
87
  ]);
85
88
 
86
- // Prints an `if_mod` or `unless_mod` node. Because it was previously in the
87
- // modifier form, we're guaranteed to not have an additional node, so we can
88
- // just work with the predicate and the body.
89
- const printSingle = keyword => (path, { inlineConditionals }, print) => {
90
- const multiline = makeSingleBlockForm(keyword, path, print);
91
-
92
89
  const [_predicate, stmts] = path.getValue().body;
93
90
  const hasComments =
94
- stmts.type === "stmts" && stmts.body.some(stmt => stmt.type === "@comment");
91
+ stmts.type === "stmts" &&
92
+ stmts.body.some((stmt) => stmt.type === "@comment");
95
93
 
96
94
  if (!inlineConditionals || hasComments) {
97
95
  return multiline;
@@ -155,7 +153,7 @@ const noTernary = [
155
153
  // Certain expressions cannot be reduced to a ternary without adding parens
156
154
  // around them. In this case we say they cannot be ternaried and default instead
157
155
  // to breaking them into multiple lines.
158
- const canTernaryStmts = stmts => {
156
+ const canTernaryStmts = (stmts) => {
159
157
  if (stmts.body.length !== 1) {
160
158
  return false;
161
159
  }
@@ -178,10 +176,11 @@ const canTernaryStmts = stmts => {
178
176
  // is of the "else" type. Both the body of the main node and the body of the
179
177
  // additional node must have only one statement, and that statement list must
180
178
  // pass the `canTernaryStmts` check.
181
- const canTernary = path => {
182
- const [_pred, stmts, addition] = path.getValue().body;
179
+ const canTernary = (path) => {
180
+ const [predicate, stmts, addition] = path.getValue().body;
183
181
 
184
182
  return (
183
+ !["assign", "opassign"].includes(predicate.type) &&
185
184
  addition &&
186
185
  addition.type === "else" &&
187
186
  [stmts, addition.body[0]].every(canTernaryStmts)
@@ -189,7 +188,7 @@ const canTernary = path => {
189
188
  };
190
189
 
191
190
  // A normalized print function for both `if` and `unless` nodes.
192
- const printConditional = keyword => (path, { inlineConditionals }, print) => {
191
+ const printConditional = (keyword) => (path, { inlineConditionals }, print) => {
193
192
  if (canTernary(path)) {
194
193
  let ternaryParts = [path.call(print, "body", 0), " ? "].concat(
195
194
  printTernaryClauses(
@@ -230,7 +229,12 @@ const printConditional = keyword => (path, { inlineConditionals }, print) => {
230
229
  // know for sure that it doesn't impact the body of the conditional, so we
231
230
  // have to default to the block form.
232
231
  if (containsAssignment(predicate)) {
233
- return makeSingleBlockForm(keyword, path, print);
232
+ return concat([
233
+ `${keyword} `,
234
+ align(keyword.length + 1, path.call(print, "body", 0)),
235
+ indent(concat([hardline, path.call(print, "body", 1)])),
236
+ concat([hardline, "end"])
237
+ ]);
234
238
  }
235
239
 
236
240
  return printSingle(keyword)(path, { inlineConditionals }, print);
@@ -12,7 +12,7 @@ const { prefix, skipAssignIndent } = require("../utils");
12
12
  const nodeDive = (node, steps) => {
13
13
  let current = node;
14
14
 
15
- steps.forEach(step => {
15
+ steps.forEach((step) => {
16
16
  current = current[step];
17
17
  });
18
18
 
@@ -29,7 +29,7 @@ const nodeDive = (node, steps) => {
29
29
  //
30
30
  // This function represents that check, as it determines if it can convert the
31
31
  // symbol node into a hash label.
32
- const isValidHashLabel = symbolLiteral => {
32
+ const isValidHashLabel = (symbolLiteral) => {
33
33
  const label = symbolLiteral.body[0].body[0].body;
34
34
  return label.match(/^[_A-Za-z]/) && !label.endsWith("=");
35
35
  };
@@ -1,6 +1,6 @@
1
1
  const { concat, group, indent, line } = require("../prettier");
2
2
 
3
- const printHook = name => (path, opts, print) =>
3
+ const printHook = (name) => (path, opts, print) =>
4
4
  group(
5
5
  concat([
6
6
  `${name} {`,
@@ -11,7 +11,7 @@ module.exports = {
11
11
  // If the number is a base 10 number, is sufficiently large, and is not
12
12
  // already formatted with underscores, then add them in in between the
13
13
  // numbers every three characters starting from the right.
14
- if (!body.startsWith("0") && body.length >= 4 && !body.includes("_")) {
14
+ if (!body.startsWith("0") && body.length >= 5 && !body.includes("_")) {
15
15
  return ` ${body}`
16
16
  .slice((body.length + 2) % 3)
17
17
  .match(/.{3}/g)
@@ -21,7 +21,7 @@ module.exports = {
21
21
  paramsConcat = path.call(print, "body", 0, "body", 0);
22
22
  }
23
23
 
24
- const noParams = params.body.every(type => !type);
24
+ const noParams = params.body.every((type) => !type);
25
25
  const inlineLambda = concat([
26
26
  "->",
27
27
  noParams ? "" : concat(["(", paramsConcat, ")"]),
@@ -10,6 +10,22 @@ const {
10
10
  const { containsAssignment } = require("../utils");
11
11
 
12
12
  const printLoop = (keyword, modifier) => (path, { inlineLoops }, print) => {
13
+ const [_predicate, statements] = path.getValue().body;
14
+
15
+ // If the only statement inside this while loop is a void statement, then we
16
+ // can shorten to just displaying the predicate and then a semicolon.
17
+ if (statements.body.length === 1 && statements.body[0].type === "void_stmt") {
18
+ return group(
19
+ concat([
20
+ keyword,
21
+ " ",
22
+ path.call(print, "body", 0),
23
+ ifBreak(softline, "; "),
24
+ "end"
25
+ ])
26
+ );
27
+ }
28
+
13
29
  let inlineParts = [
14
30
  path.call(print, "body", 1),
15
31
  ` ${keyword} `,
@@ -9,7 +9,7 @@ const {
9
9
  } = require("../prettier");
10
10
  const { first, literal } = require("../utils");
11
11
 
12
- const printMethod = offset => (path, opts, print) => {
12
+ const printMethod = (offset) => (path, opts, print) => {
13
13
  const [_name, params, body] = path.getValue().body.slice(offset);
14
14
  const declaration = ["def "];
15
15
 
@@ -21,7 +21,7 @@ const printMethod = offset => (path, opts, print) => {
21
21
 
22
22
  // In case there are no parens but there are arguments
23
23
  const parens =
24
- params.type === "params" && params.body.some(paramType => paramType);
24
+ params.type === "params" && params.body.some((paramType) => paramType);
25
25
 
26
26
  declaration.push(
27
27
  path.call(print, "body", offset),
@@ -7,10 +7,18 @@ module.exports = {
7
7
 
8
8
  return group(
9
9
  concat([
10
- concat([path.call(print, "body", 0), useNoSpace ? "" : " "]),
11
- operator,
10
+ group(path.call(print, "body", 0)),
12
11
  indent(
13
- concat([useNoSpace ? softline : line, path.call(print, "body", 2)])
12
+ concat([
13
+ useNoSpace ? "" : " ",
14
+ group(
15
+ concat([
16
+ operator,
17
+ useNoSpace ? softline : line,
18
+ path.call(print, "body", 2)
19
+ ])
20
+ )
21
+ ])
14
22
  )
15
23
  ])
16
24
  );
@@ -1,6 +1,7 @@
1
1
  const { concat, group, join, line } = require("../prettier");
2
+ const { literal } = require("../utils");
2
3
 
3
- const printGenericRestParam = symbol => (path, opts, print) =>
4
+ const printGenericRestParam = (symbol) => (path, opts, print) =>
4
5
  path.getValue().body[0]
5
6
  ? concat([symbol, path.call(print, "body", 0)])
6
7
  : symbol;
@@ -53,7 +54,7 @@ const printParams = (path, opts, print) => {
53
54
  }
54
55
 
55
56
  if (kwargRest) {
56
- parts.push(path.call(print, "body", 5));
57
+ parts.push(kwargRest === "nil" ? "**nil" : path.call(print, "body", 5));
57
58
  }
58
59
 
59
60
  if (block) {
@@ -79,6 +80,7 @@ const paramError = () => {
79
80
  };
80
81
 
81
82
  module.exports = {
83
+ args_forward: literal("..."),
82
84
  kwrest_param: printGenericRestParam("**"),
83
85
  rest_param: printGenericRestParam("*"),
84
86
  params: printParams,
@@ -7,7 +7,7 @@ module.exports = {
7
7
  const [contents, ending] = path.map(print, "body");
8
8
 
9
9
  const useBraces = contents.some(
10
- content => typeof content === "string" && content.includes("/")
10
+ (content) => typeof content === "string" && content.includes("/")
11
11
  );
12
12
  const parts = [useBraces ? "%r{" : "/"]
13
13
  .concat(contents)
@@ -53,7 +53,11 @@ module.exports = {
53
53
  parts.push(" StandardError");
54
54
  }
55
55
 
56
- parts.push(indent(concat([hardline, path.call(print, "body", 2)])));
56
+ const rescueBody = path.call(print, "body", 2);
57
+
58
+ if (rescueBody.parts.length > 0) {
59
+ parts.push(indent(concat([hardline, rescueBody])));
60
+ }
57
61
 
58
62
  // This is the next clause on the `begin` statement, either another
59
63
  // `rescue`, and `ensure`, or an `else` clause.
@@ -9,6 +9,16 @@ const {
9
9
  } = require("../prettier");
10
10
  const { literal } = require("../utils");
11
11
 
12
+ // You can't skip the parentheses if you have the `and` or `or` operator,
13
+ // because they have low enough operator precedence that you need to explicitly
14
+ // keep them in there.
15
+ const canSkipParens = (args) => {
16
+ const statement = args.body[0].body[0].body[0];
17
+ return (
18
+ statement.type !== "binary" || !["and", "or"].includes(statement.body[1])
19
+ );
20
+ };
21
+
12
22
  const printReturn = (path, opts, print) => {
13
23
  let args = path.getValue().body[0].body[0];
14
24
  let steps = ["body", 0, "body", 0];
@@ -20,17 +30,19 @@ const printReturn = (path, opts, print) => {
20
30
  // If the body of the return contains parens, then just skip directly to the
21
31
  // content of the parens so that we can skip printing parens if we don't
22
32
  // want them.
23
- if (args.body[0] && args.body[0].type === "paren") {
33
+ if (args.body[0] && args.body[0].type === "paren" && canSkipParens(args)) {
24
34
  args = args.body[0].body[0];
25
35
  steps = steps.concat("body", 0, "body", 0);
26
36
  }
27
37
 
28
- // If we're returning an array literal that isn't a special array, then we
29
- // want to grab the arguments so that we can print them out as if they were
30
- // normal return arguments.
38
+ // If we're returning an array literal that isn't a special array, single
39
+ // element array, or an empty array, then we want to grab the arguments so
40
+ // that we can print them out as if they were normal return arguments.
31
41
  if (
32
42
  args.body[0] &&
33
43
  args.body[0].type === "array" &&
44
+ args.body[0].body[0] &&
45
+ args.body[0].body[0].body.length > 1 &&
34
46
  ["args", "args_add_star"].includes(args.body[0].body[0].type)
35
47
  ) {
36
48
  steps = steps.concat("body", 0, "body", 0);
@@ -7,39 +7,31 @@ const {
7
7
  softline,
8
8
  join
9
9
  } = require("../prettier");
10
+
10
11
  const { concatBody, empty, makeList, prefix, surround } = require("../utils");
11
- const escapePattern = require("../escapePattern");
12
12
 
13
13
  // If there is some part of this string that matches an escape sequence or that
14
14
  // contains the interpolation pattern ("#{"), then we are locked into whichever
15
15
  // quote the user chose. (If they chose single quotes, then double quoting
16
16
  // would activate the escape sequence, and if they chose double quotes, then
17
17
  // single quotes would deactivate it.)
18
- const isQuoteLocked = string =>
18
+ const isQuoteLocked = (string) =>
19
19
  string.body.some(
20
- part =>
20
+ (part) =>
21
21
  part.type === "@tstring_content" &&
22
- (escapePattern.test(part.body) || part.body.includes("#{"))
22
+ (part.body.includes("#{") || part.body.includes("\\"))
23
23
  );
24
24
 
25
25
  // A string is considered to be able to use single quotes if it contains only
26
26
  // plain string content and that content does not contain a single quote.
27
- const isSingleQuotable = string =>
27
+ const isSingleQuotable = (string) =>
28
28
  string.body.every(
29
- part => part.type === "@tstring_content" && !part.body.includes("'")
29
+ (part) => part.type === "@tstring_content" && !part.body.includes("'")
30
30
  );
31
31
 
32
- const getStringQuote = (string, preferSingleQuotes) => {
33
- if (isQuoteLocked(string)) {
34
- return string.quote;
35
- }
36
-
37
- return preferSingleQuotes && isSingleQuotable(string) ? "'" : '"';
38
- };
39
-
40
32
  const quotePattern = new RegExp("\\\\([\\s\\S])|(['\"])", "g");
41
33
 
42
- const makeString = (content, enclosingQuote, originalQuote) => {
34
+ const normalizeQuotes = (content, enclosingQuote, originalQuote) => {
43
35
  const replaceOther = ["'", '"'].includes(originalQuote);
44
36
  const otherQuote = enclosingQuote === '"' ? "'" : '"';
45
37
 
@@ -62,51 +54,24 @@ const makeString = (content, enclosingQuote, originalQuote) => {
62
54
  });
63
55
  };
64
56
 
65
- // The `parts` argument that comes into this function is an array of either
66
- // printed embedded expressions or plain strings (that themselves can contain
67
- // newlines). What we want to return is an array where each element represents a
68
- // line in the original text. So we end up tracking every line as we go and
69
- // pushing onto a `currentLine` variable, then when we hit a new line we push
70
- // the current line onto the `lines` variable until we've used up every part.
71
- const makeHeredocLines = parts => {
72
- let lines = [];
73
- let currentLine = [];
74
-
75
- parts.forEach(part => {
76
- if (part.type === "group" || !part.includes("\n")) {
77
- // In this case we've either hit an embedded expression or the piece of
78
- // the current line that we're looking at is in the middle of two of them,
79
- // so we just push onto the current line and continue on.
80
- currentLine.push(part);
81
- return;
82
- }
83
-
84
- let splits = part.split("\n");
85
- if (splits[splits.length - 1] === "") {
86
- // If a line ends with a newline, then we end up with an empty string at
87
- // the end of the splits, we just pop it off here since we'll handle the
88
- // newlines later.
89
- splits = splits.slice(0, -1);
90
- }
91
-
92
- if (currentLine.length > 0) {
93
- lines.push(concat(currentLine.concat(splits[0])));
94
- currentLine = [];
95
- } else {
96
- lines.push(splits[0]);
97
- }
57
+ const quotePairs = {
58
+ "(": ")",
59
+ "[": "]",
60
+ "{": "}",
61
+ "<": ">"
62
+ };
98
63
 
99
- if (splits.length > 1) {
100
- lines = lines.concat(splits.slice(1, -1));
101
- currentLine.push(splits[splits.length - 1]);
102
- }
103
- });
64
+ const getClosingQuote = (quote) => {
65
+ if (!quote.startsWith("%")) {
66
+ return quote;
67
+ }
104
68
 
105
- if (currentLine.length > 0) {
106
- lines = lines.concat(currentLine);
69
+ const boundary = /%q?(.)/.exec(quote)[1];
70
+ if (boundary in quotePairs) {
71
+ return quotePairs[boundary];
107
72
  }
108
73
 
109
- return lines;
74
+ return boundary;
110
75
  };
111
76
 
112
77
  module.exports = {
@@ -121,21 +86,24 @@ module.exports = {
121
86
  return body.length === 2 ? concat([quote, body.slice(1), quote]) : body;
122
87
  },
123
88
  dyna_symbol: (path, opts, print) => {
124
- const { quote } = path.getValue().body[0];
89
+ const { quote } = path.getValue();
125
90
 
126
91
  return concat([":", quote, concat(path.call(print, "body", 0)), quote]);
127
92
  },
128
93
  heredoc: (path, opts, print) => {
129
- const { beging, ending } = path.getValue();
130
- const lines = makeHeredocLines(path.map(print, "body"));
131
-
132
- return concat([
133
- beging,
134
- literalline,
135
- join(literalline, lines),
136
- literalline,
137
- ending
138
- ]);
94
+ const { beging, body, ending } = path.getValue();
95
+
96
+ const parts = body.map((part, index) => {
97
+ if (part.type !== "@tstring_content") {
98
+ // In this case, the part of the string is an embedded expression
99
+ return path.call(print, "body", index);
100
+ }
101
+
102
+ // In this case, the part of the string is just regular string content
103
+ return join(literalline, part.body.split("\n"));
104
+ });
105
+
106
+ return concat([beging, literalline, concat(parts), ending]);
139
107
  },
140
108
  string: makeList,
141
109
  string_concat: (path, opts, print) =>
@@ -162,7 +130,8 @@ module.exports = {
162
130
  );
163
131
  },
164
132
  string_literal: (path, { preferSingleQuotes }, print) => {
165
- const string = path.getValue().body[0];
133
+ const stringLiteral = path.getValue();
134
+ const string = stringLiteral.body[0];
166
135
 
167
136
  // If this string is actually a heredoc, bail out and return to the print
168
137
  // function for heredocs
@@ -176,20 +145,28 @@ module.exports = {
176
145
  return preferSingleQuotes ? "''" : '""';
177
146
  }
178
147
 
179
- const quote = getStringQuote(string, preferSingleQuotes);
180
- const parts = [];
148
+ // Determine the quote that should enclose the new string
149
+ let quote;
150
+ if (isQuoteLocked(string)) {
151
+ ({ quote } = stringLiteral);
152
+ } else {
153
+ quote = preferSingleQuotes && isSingleQuotable(string) ? "'" : '"';
154
+ }
181
155
 
182
- string.body.forEach((part, index) => {
183
- if (part.type === "@tstring_content") {
184
- // In this case, the part of the string is just regular string content
185
- parts.push(makeString(part.body, quote, string.quote));
186
- } else {
156
+ const parts = string.body.map((part, index) => {
157
+ if (part.type !== "@tstring_content") {
187
158
  // In this case, the part of the string is an embedded expression
188
- parts.push(path.call(print, "body", 0, "body", index));
159
+ return path.call(print, "body", 0, "body", index);
189
160
  }
161
+
162
+ // In this case, the part of the string is just regular string content
163
+ return join(
164
+ literalline,
165
+ normalizeQuotes(part.body, quote, stringLiteral.quote).split("\n")
166
+ );
190
167
  });
191
168
 
192
- return concat([quote].concat(parts).concat([quote]));
169
+ return concat([quote].concat(parts).concat(getClosingQuote(quote)));
193
170
  },
194
171
  symbol: prefix(":"),
195
172
  symbol_literal: concatBody,