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.
@@ -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);