prettier 0.17.0 → 0.19.1

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.
@@ -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,11 @@
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
9
 
10
10
  // Very special handling case for rspec matchers. In general with rspec matchers
11
11
  // you expect to see something like:
@@ -20,7 +20,7 @@ const hasDef = node =>
20
20
  //
21
21
  // In this case the arguments are aligned to the left side as opposed to being
22
22
  // aligned with the `receive` call.
23
- const skipArgsAlign = path =>
23
+ const skipArgsAlign = (path) =>
24
24
  ["to", "not_to"].includes(path.getValue().body[2].body);
25
25
 
26
26
  module.exports = {
@@ -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,7 +30,7 @@ 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
  }
@@ -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,