prettier 0.21.0 → 1.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +112 -7
- data/CONTRIBUTING.md +4 -4
- data/README.md +18 -14
- data/package.json +9 -6
- data/src/embed.js +27 -8
- data/src/nodes.js +5 -2
- data/src/nodes/alias.js +29 -31
- data/src/nodes/aref.js +26 -26
- data/src/nodes/args.js +55 -47
- data/src/nodes/arrays.js +132 -106
- data/src/nodes/assign.js +32 -32
- data/src/nodes/blocks.js +8 -3
- data/src/nodes/calls.js +163 -60
- data/src/nodes/case.js +11 -7
- data/src/nodes/class.js +74 -0
- data/src/nodes/commands.js +36 -31
- data/src/nodes/conditionals.js +44 -30
- data/src/nodes/constants.js +39 -21
- data/src/nodes/flow.js +11 -1
- data/src/nodes/hashes.js +90 -109
- data/src/nodes/heredocs.js +34 -0
- data/src/nodes/hooks.js +21 -22
- data/src/nodes/ints.js +27 -20
- data/src/nodes/lambdas.js +14 -27
- data/src/nodes/loops.js +10 -5
- data/src/nodes/massign.js +87 -65
- data/src/nodes/methods.js +48 -73
- data/src/nodes/operators.js +70 -39
- data/src/nodes/params.js +26 -16
- data/src/nodes/patterns.js +108 -33
- data/src/nodes/regexp.js +45 -14
- data/src/nodes/rescue.js +72 -59
- data/src/nodes/statements.js +86 -44
- data/src/nodes/strings.js +95 -85
- data/src/nodes/super.js +35 -0
- data/src/nodes/undef.js +42 -0
- data/src/parser.js +86 -0
- data/src/parser.rb +2400 -621
- data/src/printer.js +90 -0
- data/src/ruby.js +19 -41
- data/src/toProc.js +4 -4
- data/src/utils.js +24 -88
- data/src/utils/literalLineNoBreak.js +7 -0
- data/src/utils/printEmptyCollection.js +42 -0
- metadata +12 -49
- data/src/nodes/scopes.js +0 -61
- data/src/parse.js +0 -37
- data/src/print.js +0 -23
data/src/nodes/conditionals.js
CHANGED
@@ -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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
concat([softline, "end"])
|
89
|
-
]);
|
83
|
+
function printSingle(keyword, modifier = false) {
|
84
|
+
return function printSingleWithKeyword(path, { rubyModifier }, 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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
89
|
+
const multilineParts = [
|
90
|
+
`${keyword} `,
|
91
|
+
align(keyword.length + 1, predicateDoc),
|
92
|
+
indent(concat([softline, statementsDoc])),
|
93
|
+
softline,
|
94
|
+
"end"
|
95
|
+
];
|
95
96
|
|
96
|
-
|
97
|
-
|
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 (!rubyModifier || (!modifier && statementsNode.body[0].comments)) {
|
101
|
+
return concat([concat(multilineParts), breakParent]);
|
102
|
+
}
|
99
103
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
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",
|
@@ -176,7 +190,7 @@ const canTernary = (path) => {
|
|
176
190
|
};
|
177
191
|
|
178
192
|
// A normalized print function for both `if` and `unless` nodes.
|
179
|
-
const printConditional = (keyword) => (path, {
|
193
|
+
const printConditional = (keyword) => (path, { rubyModifier }, print) => {
|
180
194
|
if (canTernary(path)) {
|
181
195
|
let ternaryParts = [path.call(print, "body", 0), " ? "].concat(
|
182
196
|
printTernaryClauses(
|
@@ -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
|
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)),
|
@@ -225,7 +239,7 @@ const printConditional = (keyword) => (path, { inlineConditionals }, print) => {
|
|
225
239
|
]);
|
226
240
|
}
|
227
241
|
|
228
|
-
return printSingle(keyword)(path, {
|
242
|
+
return printSingle(keyword)(path, { rubyModifier }, print);
|
229
243
|
};
|
230
244
|
|
231
245
|
module.exports = {
|
@@ -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
|
};
|
data/src/nodes/constants.js
CHANGED
@@ -1,25 +1,43 @@
|
|
1
1
|
const { concat, group, indent, join, softline } = require("../prettier");
|
2
|
-
const {
|
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:
|
6
|
-
const_path_ref:
|
7
|
-
const_ref:
|
8
|
-
defined:
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
};
|
data/src/nodes/flow.js
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
const { concat, join } = require("../prettier");
|
2
|
-
const { literal
|
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",
|
data/src/nodes/hashes.js
CHANGED
@@ -1,14 +1,11 @@
|
|
1
|
-
const {
|
2
|
-
concat,
|
3
|
-
group,
|
4
|
-
ifBreak,
|
5
|
-
indent,
|
6
|
-
join,
|
7
|
-
line,
|
8
|
-
literalline
|
9
|
-
} = require("../prettier");
|
1
|
+
const { concat, group, ifBreak, indent, join, line } = require("../prettier");
|
10
2
|
|
11
|
-
const {
|
3
|
+
const {
|
4
|
+
getTrailingComma,
|
5
|
+
prefix,
|
6
|
+
printEmptyCollection,
|
7
|
+
skipAssignIndent
|
8
|
+
} = require("../utils");
|
12
9
|
|
13
10
|
// When attempting to convert a hash rocket into a hash label, you need to take
|
14
11
|
// care because only certain patterns are allowed. Ruby source says that they
|
@@ -20,128 +17,112 @@ const { nodeDive, prefix, skipAssignIndent } = require("../utils");
|
|
20
17
|
//
|
21
18
|
// This function represents that check, as it determines if it can convert the
|
22
19
|
// symbol node into a hash label.
|
23
|
-
|
24
|
-
const label = symbolLiteral.body[0].body
|
20
|
+
function isValidHashLabel(symbolLiteral) {
|
21
|
+
const label = symbolLiteral.body[0].body;
|
25
22
|
return label.match(/^[_A-Za-z]/) && !label.endsWith("=");
|
26
|
-
}
|
23
|
+
}
|
27
24
|
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
function canUseHashLabels(contentsNode) {
|
26
|
+
return contentsNode.body.every((assocNode) => {
|
27
|
+
if (assocNode.type === "assoc_splat") {
|
28
|
+
return true;
|
29
|
+
}
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
return concat([
|
43
|
-
path.call.apply(path, [print].concat(symbolSteps)),
|
44
|
-
":"
|
45
|
-
]);
|
46
|
-
}
|
47
|
-
return concat([labelDoc, " =>"]);
|
31
|
+
switch (assocNode.body[0].type) {
|
32
|
+
case "@label":
|
33
|
+
return true;
|
34
|
+
case "symbol_literal":
|
35
|
+
return isValidHashLabel(assocNode.body[0]);
|
36
|
+
case "dyna_symbol":
|
37
|
+
return true;
|
38
|
+
default:
|
39
|
+
return false;
|
48
40
|
}
|
41
|
+
});
|
42
|
+
}
|
43
|
+
|
44
|
+
function printHashKeyLabel(path, print) {
|
45
|
+
const node = path.getValue();
|
46
|
+
|
47
|
+
switch (node.type) {
|
48
|
+
case "@label":
|
49
|
+
return print(path);
|
50
|
+
case "symbol_literal":
|
51
|
+
return concat([path.call(print, "body", 0), ":"]);
|
49
52
|
case "dyna_symbol":
|
50
|
-
|
51
|
-
return concat(labelDoc.parts.slice(1).concat(":"));
|
52
|
-
}
|
53
|
-
return concat([labelDoc, " =>"]);
|
54
|
-
default:
|
55
|
-
return concat([labelDoc, " =>"]);
|
53
|
+
return concat(print(path).parts.slice(1).concat(":"));
|
56
54
|
}
|
57
|
-
}
|
55
|
+
}
|
58
56
|
|
59
|
-
function
|
57
|
+
function printHashKeyRocket(path, print) {
|
58
|
+
const node = path.getValue();
|
59
|
+
const doc = print(path);
|
60
|
+
|
61
|
+
if (node.type === "@label") {
|
62
|
+
return `:${doc.slice(0, doc.length - 1)} =>`;
|
63
|
+
}
|
64
|
+
|
65
|
+
return concat([doc, " =>"]);
|
66
|
+
}
|
67
|
+
|
68
|
+
function printAssocNew(path, opts, print) {
|
69
|
+
const { keyPrinter } = path.getParentNode();
|
70
|
+
|
71
|
+
const parts = [path.call((keyPath) => keyPrinter(keyPath, print), "body", 0)];
|
72
|
+
const valueDoc = path.call(print, "body", 1);
|
73
|
+
|
74
|
+
if (skipAssignIndent(path.getValue().body[1])) {
|
75
|
+
parts.push(" ", valueDoc);
|
76
|
+
} else {
|
77
|
+
parts.push(indent(concat([line, valueDoc])));
|
78
|
+
}
|
79
|
+
|
80
|
+
return group(concat(parts));
|
81
|
+
}
|
82
|
+
|
83
|
+
function printHashContents(path, opts, print) {
|
84
|
+
const node = path.getValue();
|
85
|
+
|
86
|
+
// First determine which key printer we're going to use, so that the child
|
87
|
+
// nodes can reference it when they go to get printed.
|
88
|
+
node.keyPrinter =
|
89
|
+
opts.rubyHashLabel && canUseHashLabels(path.getValue())
|
90
|
+
? printHashKeyLabel
|
91
|
+
: printHashKeyRocket;
|
92
|
+
|
93
|
+
return join(concat([",", line]), path.map(print, "body"));
|
94
|
+
}
|
95
|
+
|
96
|
+
function printHash(path, opts, print) {
|
60
97
|
const hashNode = path.getValue();
|
61
98
|
|
62
99
|
// Hashes normally have a single assoclist_from_args child node. If it's
|
63
100
|
// missing, then it means we're dealing with an empty hash, so we can just
|
64
101
|
// exit here and print.
|
65
102
|
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(",", ""));
|
103
|
+
return printEmptyCollection(path, opts, "{", "}");
|
87
104
|
}
|
88
105
|
|
89
106
|
return group(
|
90
107
|
concat([
|
91
108
|
"{",
|
92
|
-
indent(
|
93
|
-
|
109
|
+
indent(
|
110
|
+
concat([
|
111
|
+
line,
|
112
|
+
path.call(print, "body", 0),
|
113
|
+
getTrailingComma(opts) ? ifBreak(",", "") : ""
|
114
|
+
])
|
115
|
+
),
|
116
|
+
line,
|
117
|
+
"}"
|
94
118
|
])
|
95
119
|
);
|
96
120
|
}
|
97
121
|
|
98
122
|
module.exports = {
|
99
|
-
assoc_new:
|
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
|
-
}
|
108
|
-
|
109
|
-
return group(concat(parts));
|
110
|
-
},
|
123
|
+
assoc_new: printAssocNew,
|
111
124
|
assoc_splat: prefix("**"),
|
112
|
-
assoclist_from_args:
|
113
|
-
|
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))),
|
125
|
+
assoclist_from_args: printHashContents,
|
126
|
+
bare_assoc_hash: printHashContents,
|
146
127
|
hash: printHash
|
147
128
|
};
|
@@ -0,0 +1,34 @@
|
|
1
|
+
const { concat, group, lineSuffix, join } = require("../prettier");
|
2
|
+
const { literalLineNoBreak } = require("../utils");
|
3
|
+
|
4
|
+
function printHeredoc(path, opts, print) {
|
5
|
+
const { body, ending } = path.getValue();
|
6
|
+
|
7
|
+
const parts = body.map((part, index) => {
|
8
|
+
if (part.type !== "@tstring_content") {
|
9
|
+
// In this case, the part of the string is an embedded expression
|
10
|
+
return path.call(print, "body", index);
|
11
|
+
}
|
12
|
+
|
13
|
+
// In this case, the part of the string is just regular string content
|
14
|
+
return join(literalLineNoBreak, part.body.split("\n"));
|
15
|
+
});
|
16
|
+
|
17
|
+
// We use a literalline break because matching indentation is required
|
18
|
+
// for the heredoc contents and ending. If the line suffix contains a
|
19
|
+
// break-parent, all ancestral groups are broken, and heredocs automatically
|
20
|
+
// break lines in groups they appear in. We prefer them to appear in-line if
|
21
|
+
// possible, so we use a literalline without the break-parent.
|
22
|
+
return group(
|
23
|
+
concat([
|
24
|
+
path.call(print, "beging"),
|
25
|
+
lineSuffix(
|
26
|
+
group(concat([literalLineNoBreak].concat(parts).concat(ending)))
|
27
|
+
)
|
28
|
+
])
|
29
|
+
);
|
30
|
+
}
|
31
|
+
|
32
|
+
module.exports = {
|
33
|
+
heredoc: printHeredoc
|
34
|
+
};
|