prettier 0.21.0 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,40 +1,72 @@
1
1
  const {
2
2
  concat,
3
3
  group,
4
+ hardline,
4
5
  ifBreak,
5
6
  indent,
6
7
  join,
7
8
  line,
8
- literalline,
9
9
  softline
10
10
  } = require("../prettier");
11
11
 
12
- const preserveArraySubstrings = [" ", "\\"];
13
-
14
- const isStringArray = (args) =>
15
- args.body.every(
16
- (arg) =>
17
- arg.type === "string_literal" &&
18
- arg.body[0].body.length === 1 &&
19
- arg.body[0].body[0].type === "@tstring_content" &&
20
- !preserveArraySubstrings.some((str) =>
21
- arg.body[0].body[0].body.includes(str)
22
- )
23
- );
12
+ // Checks that every argument within this args node is a string_literal node
13
+ // that has no spaces or interpolations. This means we're dealing with an array
14
+ // that looks something like:
15
+ //
16
+ // ['a', 'b', 'c']
17
+ //
18
+ function isStringArray(args) {
19
+ return args.body.every((arg) => {
20
+ // We want to verify that every node inside of this array is a string
21
+ // literal. We also want to make sure none of them have comments attached.
22
+ if (arg.type !== "string_literal" || arg.comments) {
23
+ return false;
24
+ }
25
+
26
+ // If the string has multiple parts (meaning plain string content but also
27
+ // interpolated content) then we know it's not a simple string.
28
+ if (arg.body.length !== 1) {
29
+ return false;
30
+ }
24
31
 
25
- const isSymbolArray = (args) =>
26
- args.body.every((arg) => arg.type === "symbol_literal");
32
+ const part = arg.body[0];
27
33
 
28
- const makeArray = (start) => (path, opts, print) =>
29
- [start].concat(path.map(print, "body"));
34
+ // If the only part of this string is not @tstring_content then it's
35
+ // interpolated, so again we can return false.
36
+ if (part.type !== "@tstring_content") {
37
+ return false;
38
+ }
30
39
 
31
- const getSpecialArrayParts = (path, print, args) =>
32
- args.body.map((_arg, index) =>
33
- path.call(print, "body", 0, "body", index, "body", 0, "body", 0)
40
+ // Finally, verify that the string doesn't contain a space or an escape
41
+ // character so that we know it can be put into a string literal array.
42
+ return !part.body.includes(" ") && !part.body.includes("\\");
43
+ });
44
+ }
45
+
46
+ // Checks that every argument within this args node is a symbol_literal node (as
47
+ // opposed to a dyna_symbol) so it has no interpolation. This means we're
48
+ // dealing with an array that looks something like:
49
+ //
50
+ // [:a, :b, :c]
51
+ //
52
+ function isSymbolArray(args) {
53
+ return args.body.every(
54
+ (arg) => arg.type === "symbol_literal" && !arg.comments
34
55
  );
35
-
36
- const printSpecialArray = (parts) =>
37
- group(
56
+ }
57
+
58
+ // Prints out a word that is a part of a special array literal that accepts
59
+ // interpolation. The body is an array of either plain strings or interpolated
60
+ // expressions.
61
+ function printSpecialArrayWord(path, opts, print) {
62
+ return concat(path.map(print, "body"));
63
+ }
64
+
65
+ // Prints out a special array literal. Accepts the parts of the array literal as
66
+ // an argument, where the first element of the parts array is a string that
67
+ // contains the special start.
68
+ function printSpecialArrayParts(parts) {
69
+ return group(
38
70
  concat([
39
71
  parts[0],
40
72
  "[",
@@ -42,93 +74,99 @@ const printSpecialArray = (parts) =>
42
74
  concat([softline, "]"])
43
75
  ])
44
76
  );
45
-
46
- // Extract out the actual elements, taking into account nesting with
47
- // `args_add_star` nodes. The only nodes that get passed into this function are
48
- // `args` or `args_add_star`.
49
- const getElements = (node, elementPath) => {
50
- if (node.type === "args") {
51
- return node.body.map((element, index) => ({
52
- element,
53
- elementPath: elementPath.concat(["body", index])
54
- }));
77
+ }
78
+
79
+ // Generates a print function with an embedded special start character for the
80
+ // specific type of array literal that we're dealing with. The print function
81
+ // returns an array as it expects to eventually be handed off to
82
+ // printSpecialArrayParts.
83
+ function printSpecialArray(start) {
84
+ return function printSpecialArrayWithStart(path, opts, print) {
85
+ return [start].concat(path.map(print, "body"));
86
+ };
87
+ }
88
+
89
+ function printEmptyArrayWithComments(path, opts) {
90
+ const arrayNode = path.getValue();
91
+
92
+ const printComment = (commentPath, index) => {
93
+ arrayNode.comments[index].printed = true;
94
+ return opts.printer.printComment(commentPath);
95
+ };
96
+
97
+ return concat([
98
+ "[",
99
+ indent(
100
+ concat([hardline, join(hardline, path.map(printComment, "comments"))])
101
+ ),
102
+ line,
103
+ "]"
104
+ ]);
105
+ }
106
+
107
+ // An array node is any literal array in Ruby. This includes all of the special
108
+ // array literals as well as regular arrays. If it is a special array literal
109
+ // then it will have one child that represents the special array, otherwise it
110
+ // will have one child that contains all of the elements of the array.
111
+ function printArray(path, opts, print) {
112
+ const array = path.getValue();
113
+ const args = array.body[0];
114
+
115
+ // If there is no inner arguments node, then we're dealing with an empty
116
+ // array, so we can go ahead and return.
117
+ if (args === null) {
118
+ return array.comments ? printEmptyArrayWithComments(path, opts) : "[]";
55
119
  }
56
120
 
57
- return getElements(node.body[0], elementPath.concat(["body", 0])).concat(
58
- node.body.slice(1).map((element, index) => ({
59
- element,
60
- elementPath: elementPath.concat(["body", index + 1])
61
- }))
62
- );
63
- };
121
+ // If we have an array that contains only simple string literals with no
122
+ // spaces or interpolation, then we're going to print a %w array.
123
+ if (isStringArray(args)) {
124
+ const printString = (stringPath) => stringPath.call(print, "body", 0);
125
+ const parts = path.map(printString, "body", 0, "body");
64
126
 
65
- module.exports = {
66
- array: (path, { addTrailingCommas }, print) => {
67
- const args = path.getValue().body[0];
127
+ return printSpecialArrayParts(["%w"].concat(parts));
128
+ }
68
129
 
69
- if (args === null) {
70
- return "[]";
71
- }
130
+ // If we have an array that contains only simple symbol literals with no
131
+ // interpolation, then we're going to print a %i array.
132
+ if (isSymbolArray(args)) {
133
+ const printSymbol = (symbolPath) => symbolPath.call(print, "body", 0);
134
+ const parts = path.map(printSymbol, "body", 0, "body");
72
135
 
73
- if (isStringArray(args)) {
74
- return printSpecialArray(
75
- ["%w"].concat(getSpecialArrayParts(path, print, args))
76
- );
77
- }
136
+ return printSpecialArrayParts(["%i"].concat(parts));
137
+ }
78
138
 
79
- if (isSymbolArray(args)) {
80
- return printSpecialArray(
81
- ["%i"].concat(getSpecialArrayParts(path, print, args))
82
- );
83
- }
139
+ // If we don't have a regular args node at this point then we have a special
140
+ // array literal. In that case we're going to print out the body (which will
141
+ // return to us an array with the first one being the start of the array) and
142
+ // send that over to the printSpecialArrayParts function.
143
+ if (!["args", "args_add_star"].includes(args.type)) {
144
+ return printSpecialArrayParts(path.call(print, "body", 0));
145
+ }
84
146
 
85
- if (!["args", "args_add_star"].includes(args.type)) {
86
- return printSpecialArray(path.call(print, "body", 0));
87
- }
147
+ // Here we have a normal array of any type of object with no special literal
148
+ // types or anything.
149
+ return group(
150
+ concat([
151
+ "[",
152
+ indent(
153
+ concat([
154
+ softline,
155
+ join(concat([",", line]), path.call(print, "body", 0)),
156
+ opts.addTrailingCommas ? ifBreak(",", "") : ""
157
+ ])
158
+ ),
159
+ softline,
160
+ "]"
161
+ ])
162
+ );
163
+ }
88
164
 
89
- const normalDocs = [];
90
-
91
- const elementDocs = path.call(print, "body", 0);
92
- const elements = getElements(path.getValue().body[0], ["body", 0]);
93
-
94
- // We need to manually loop through the elements in the array in order to
95
- // take care of heredocs printing (their commas go after the opening, as
96
- // opposed to at the end).
97
- elements.forEach(({ element, elementPath }, index) => {
98
- const isInner = index !== elements.length - 1;
99
-
100
- if (element.type === "heredoc") {
101
- normalDocs.push(
102
- element.beging,
103
- isInner || addTrailingCommas ? "," : "",
104
- literalline,
105
- concat(
106
- path.map.apply(path, [print].concat(elementPath).concat("body"))
107
- ),
108
- element.ending,
109
- isInner ? line : ""
110
- );
111
- } else {
112
- normalDocs.push(elementDocs[index]);
113
-
114
- if (isInner) {
115
- normalDocs.push(concat([",", line]));
116
- } else if (addTrailingCommas) {
117
- normalDocs.push(ifBreak(",", ""));
118
- }
119
- }
120
- });
121
-
122
- return group(
123
- concat([
124
- "[",
125
- indent(concat([softline].concat(normalDocs))),
126
- concat([softline, "]"])
127
- ])
128
- );
129
- },
130
- qsymbols: makeArray("%i"),
131
- qwords: makeArray("%w"),
132
- symbols: makeArray("%I"),
133
- words: makeArray("%W")
165
+ module.exports = {
166
+ array: printArray,
167
+ qsymbols: printSpecialArray("%i"),
168
+ qwords: printSpecialArray("%w"),
169
+ symbols: printSpecialArray("%I"),
170
+ word: printSpecialArrayWord,
171
+ words: printSpecialArray("%W")
134
172
  };
@@ -1,39 +1,39 @@
1
1
  const { concat, group, indent, join, line } = require("../prettier");
2
2
  const { concatBody, first, skipAssignIndent } = require("../utils");
3
3
 
4
- module.exports = {
5
- assign: (path, opts, print) => {
6
- const [printedTarget, printedValue] = path.map(print, "body");
7
- let adjustedValue = printedValue;
4
+ function printAssign(path, opts, print) {
5
+ const [_targetNode, valueNode] = path.getValue().body;
6
+ const [targetDoc, valueDoc] = path.map(print, "body");
7
+
8
+ let rightSideDoc = valueDoc;
9
+
10
+ // If the right side of this assignment is a multiple assignment, then we need
11
+ // to join it together with commas.
12
+ if (["mrhs_add_star", "mrhs_new_from_args"].includes(valueNode.type)) {
13
+ rightSideDoc = group(join(concat([",", line]), valueDoc));
14
+ }
8
15
 
9
- if (
10
- ["mrhs_add_star", "mrhs_new_from_args"].includes(
11
- path.getValue().body[1].type
12
- )
13
- ) {
14
- adjustedValue = group(join(concat([",", line]), printedValue));
15
- }
16
+ if (skipAssignIndent(valueNode)) {
17
+ return group(concat([targetDoc, " = ", rightSideDoc]));
18
+ }
16
19
 
17
- if (skipAssignIndent(path.getValue().body[1])) {
18
- return group(concat([printedTarget, " = ", adjustedValue]));
19
- }
20
+ return group(concat([targetDoc, " =", indent(concat([line, rightSideDoc]))]));
21
+ }
20
22
 
21
- return group(
22
- concat([printedTarget, " =", indent(concat([line, adjustedValue]))])
23
- );
24
- },
25
- assign_error: (_path, _opts, _print) => {
26
- throw new Error("Can't set variable");
27
- },
28
- opassign: (path, opts, print) =>
29
- group(
30
- concat([
31
- path.call(print, "body", 0),
32
- " ",
33
- path.call(print, "body", 1),
34
- indent(concat([line, path.call(print, "body", 2)]))
35
- ])
36
- ),
23
+ function printOpAssign(path, opts, print) {
24
+ return group(
25
+ concat([
26
+ path.call(print, "body", 0),
27
+ " ",
28
+ path.call(print, "body", 1),
29
+ indent(concat([line, path.call(print, "body", 2)]))
30
+ ])
31
+ );
32
+ }
33
+
34
+ module.exports = {
35
+ assign: printAssign,
36
+ opassign: printOpAssign,
37
37
  var_field: concatBody,
38
38
  var_ref: first
39
39
  };
@@ -16,7 +16,11 @@ const printBlock = (braces) => (path, opts, print) => {
16
16
  statements.type === "stmts" ? statements.body : statements.body[0].body;
17
17
 
18
18
  let doBlockBody = "";
19
- if (stmts.length !== 1 || stmts[0].type !== "void_stmt") {
19
+ if (
20
+ stmts.length !== 1 ||
21
+ stmts[0].type !== "void_stmt" ||
22
+ stmts[0].comments
23
+ ) {
20
24
  doBlockBody = indent(concat([softline, path.call(print, "body", 1)]));
21
25
  }
22
26
 
@@ -37,8 +41,9 @@ const printBlock = (braces) => (path, opts, print) => {
37
41
  // We can hit this next pattern if within the block the only statement is a
38
42
  // comment.
39
43
  if (
40
- stmts.length > 1 &&
41
- stmts.filter((stmt) => stmt.type !== "@comment").length === 1
44
+ stmts.length === 1 &&
45
+ stmts[0].type === "void_stmt" &&
46
+ stmts[0].comments
42
47
  ) {
43
48
  return concat([breakParent, doBlock]);
44
49
  }
@@ -1,64 +1,119 @@
1
- const { concat, group, indent, literalline, softline } = require("../prettier");
2
- const toProc = require("../toProc");
1
+ const {
2
+ concat,
3
+ group,
4
+ hardline,
5
+ ifBreak,
6
+ indent,
7
+ softline
8
+ } = require("../prettier");
3
9
  const { concatBody, first, makeCall } = require("../utils");
4
10
 
11
+ const toProc = require("../toProc");
12
+
13
+ const chained = ["call", "method_add_arg"];
5
14
  const noIndent = ["array", "hash", "if", "method_add_block", "xstring_literal"];
6
15
 
7
- module.exports = {
8
- call: (path, opts, print) => {
9
- const [receiverNode, _operatorNode, messageNode] = path.getValue().body;
10
-
11
- const printedReceiver = path.call(print, "body", 0);
12
- const printedOperator = makeCall(path, opts, print);
13
-
14
- // You can call lambdas with a special syntax that looks like func.(*args).
15
- // In this case, "call" is returned for the 3rd child node.
16
- const printedMessage =
17
- messageNode === "call" ? messageNode : path.call(print, "body", 2);
18
-
19
- // If we have a heredoc as a receiver, then we need to move the operator and
20
- // the message up to start of the heredoc declaration, as in:
21
- //
22
- // <<~TEXT.strip
23
- // content
24
- // TEXT
25
- if (receiverNode.type === "heredoc") {
26
- return concat([
27
- receiverNode.beging,
28
- printedOperator,
29
- printedMessage,
30
- literalline,
31
- concat(path.map(print, "body", 0, "body")),
32
- receiverNode.ending
33
- ]);
34
- }
16
+ function printCall(path, opts, print) {
17
+ const callNode = path.getValue();
18
+ const [receiverNode, _operatorNode, messageNode] = callNode.body;
35
19
 
36
- // For certain left sides of the call nodes, we want to attach directly to
37
- // the } or end.
38
- if (noIndent.includes(receiverNode.type)) {
39
- return concat([printedReceiver, printedOperator, printedMessage]);
40
- }
20
+ const receiverDoc = path.call(print, "body", 0);
21
+ const operatorDoc = makeCall(path, opts, print);
22
+
23
+ // You can call lambdas with a special syntax that looks like func.(*args).
24
+ // In this case, "call" is returned for the 3rd child node.
25
+ const messageDoc =
26
+ messageNode === "call" ? messageNode : path.call(print, "body", 2);
27
+
28
+ // For certain left sides of the call nodes, we want to attach directly to
29
+ // the } or end.
30
+ if (noIndent.includes(receiverNode.type)) {
31
+ return concat([receiverDoc, operatorDoc, messageDoc]);
32
+ }
41
33
 
42
- return group(
43
- concat([
44
- printedReceiver,
45
- group(indent(concat([softline, printedOperator, printedMessage])))
46
- ])
34
+ // The right side of the call node, as in everything including the operator
35
+ // and beyond.
36
+ const rightSideDoc = indent(
37
+ concat([
38
+ receiverNode.comments ? hardline : softline,
39
+ operatorDoc,
40
+ messageDoc
41
+ ])
42
+ );
43
+
44
+ // Get a reference to the parent node so we can check if we're inside a chain
45
+ const parentNode = path.getParentNode();
46
+
47
+ // If our parent node is a chained node then we're not going to group the
48
+ // right side of the expression, as we want to have a nice multi-line layout.
49
+ if (chained.includes(parentNode.type)) {
50
+ parentNode.chain = (callNode.chain || 0) + 1;
51
+ parentNode.breakDoc = (callNode.breakDoc || [receiverDoc]).concat(
52
+ rightSideDoc
47
53
  );
48
- },
49
- fcall: concatBody,
50
- method_add_arg: (path, opts, print) => {
51
- const [method, args] = path.map(print, "body");
52
- const argNode = path.getValue().body[1];
53
-
54
- // This case will ONLY be hit if we can successfully turn the block into a
55
- // to_proc call. In that case, we just explicitly add the parens around it.
56
- if (argNode.type === "args" && args.length > 0) {
57
- return concat([method, "("].concat(args).concat(")"));
58
- }
54
+ }
59
55
 
60
- return concat([method, args]);
61
- },
56
+ // If we're at the top of a chain, then we're going to print out a nice
57
+ // multi-line layout if this doesn't break into multiple lines.
58
+ if (!chained.includes(parentNode.type) && (callNode.chain || 0) >= 3) {
59
+ return ifBreak(
60
+ group(concat(callNode.breakDoc.concat(rightSideDoc))),
61
+ concat([receiverDoc, group(rightSideDoc)])
62
+ );
63
+ }
64
+
65
+ return group(concat([receiverDoc, group(rightSideDoc)]));
66
+ }
67
+
68
+ function printMethodAddArg(path, opts, print) {
69
+ const methodAddArgNode = path.getValue();
70
+ const argNode = methodAddArgNode.body[1];
71
+
72
+ const [methodDoc, argsDoc] = path.map(print, "body");
73
+
74
+ // You can end up here if you have a method with a ? ending, presumably
75
+ // because the parser knows that it cannot be a local variable.
76
+ if (argsDoc.length === 0) {
77
+ return methodDoc;
78
+ }
79
+
80
+ // This case will ONLY be hit if we can successfully turn the block into a
81
+ // to_proc call. In that case, we just explicitly add the parens around it.
82
+ if (argNode.type === "args" && argsDoc.length > 0) {
83
+ return concat([methodDoc, "("].concat(argsDoc).concat(")"));
84
+ }
85
+
86
+ // Get a reference to the parent node so we can check if we're inside a chain
87
+ const parentNode = path.getParentNode();
88
+
89
+ // If our parent node is a chained node then we're not going to group the
90
+ // right side of the expression, as we want to have a nice multi-line layout.
91
+ if (chained.includes(parentNode.type)) {
92
+ parentNode.chain = (methodAddArgNode.chain || 0) + 1;
93
+ parentNode.breakDoc = (methodAddArgNode.breakDoc || [methodDoc]).concat(
94
+ argsDoc
95
+ );
96
+ }
97
+
98
+ // If we're at the top of a chain, then we're going to print out a nice
99
+ // multi-line layout if this doesn't break into multiple lines.
100
+ if (
101
+ !chained.includes(parentNode.type) &&
102
+ (methodAddArgNode.chain || 0) >= 3
103
+ ) {
104
+ return ifBreak(
105
+ group(concat(methodAddArgNode.breakDoc.concat(argsDoc))),
106
+ concat([methodDoc, argsDoc])
107
+ );
108
+ }
109
+
110
+ return concat([methodDoc, argsDoc]);
111
+ }
112
+
113
+ module.exports = {
114
+ call: printCall,
115
+ fcall: concatBody,
116
+ method_add_arg: printMethodAddArg,
62
117
  method_add_block: (path, opts, print) => {
63
118
  const [method, block] = path.getValue().body;
64
119
  const proc = toProc(path, opts, block);