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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +112 -7
  3. data/CONTRIBUTING.md +4 -4
  4. data/README.md +18 -14
  5. data/package.json +9 -6
  6. data/src/embed.js +27 -8
  7. data/src/nodes.js +5 -2
  8. data/src/nodes/alias.js +29 -31
  9. data/src/nodes/aref.js +26 -26
  10. data/src/nodes/args.js +55 -47
  11. data/src/nodes/arrays.js +132 -106
  12. data/src/nodes/assign.js +32 -32
  13. data/src/nodes/blocks.js +8 -3
  14. data/src/nodes/calls.js +163 -60
  15. data/src/nodes/case.js +11 -7
  16. data/src/nodes/class.js +74 -0
  17. data/src/nodes/commands.js +36 -31
  18. data/src/nodes/conditionals.js +44 -30
  19. data/src/nodes/constants.js +39 -21
  20. data/src/nodes/flow.js +11 -1
  21. data/src/nodes/hashes.js +90 -109
  22. data/src/nodes/heredocs.js +34 -0
  23. data/src/nodes/hooks.js +21 -22
  24. data/src/nodes/ints.js +27 -20
  25. data/src/nodes/lambdas.js +14 -27
  26. data/src/nodes/loops.js +10 -5
  27. data/src/nodes/massign.js +87 -65
  28. data/src/nodes/methods.js +48 -73
  29. data/src/nodes/operators.js +70 -39
  30. data/src/nodes/params.js +26 -16
  31. data/src/nodes/patterns.js +108 -33
  32. data/src/nodes/regexp.js +45 -14
  33. data/src/nodes/rescue.js +72 -59
  34. data/src/nodes/statements.js +86 -44
  35. data/src/nodes/strings.js +95 -85
  36. data/src/nodes/super.js +35 -0
  37. data/src/nodes/undef.js +42 -0
  38. data/src/parser.js +86 -0
  39. data/src/parser.rb +2400 -621
  40. data/src/printer.js +90 -0
  41. data/src/ruby.js +19 -41
  42. data/src/toProc.js +4 -4
  43. data/src/utils.js +24 -88
  44. data/src/utils/literalLineNoBreak.js +7 -0
  45. data/src/utils/printEmptyCollection.js +42 -0
  46. metadata +12 -49
  47. data/src/nodes/scopes.js +0 -61
  48. data/src/parse.js +0 -37
  49. data/src/print.js +0 -23
@@ -1,4 +1,5 @@
1
1
  const {
2
+ breakParent,
2
3
  concat,
3
4
  dedent,
4
5
  group,
@@ -11,63 +12,104 @@ const {
11
12
  trim
12
13
  } = require("../prettier");
13
14
 
14
- module.exports = {
15
- "@__end__": (path, _opts, _print) => {
16
- const { body } = path.getValue();
17
- return concat([trim, "__END__", literalline, body]);
18
- },
19
- bodystmt: (path, opts, print) => {
20
- const [_statements, rescue, elseClause, ensure] = path.getValue().body;
21
- const parts = [path.call(print, "body", 0)];
15
+ function printBodyStmt(path, opts, print) {
16
+ const [stmts, rescue, elseClause, ensure] = path.getValue().body;
17
+ const parts = [];
22
18
 
23
- if (rescue) {
24
- parts.push(dedent(concat([hardline, path.call(print, "body", 1)])));
25
- }
19
+ if (
20
+ stmts.body.length > 1 ||
21
+ stmts.body[0].type != "void_stmt" ||
22
+ stmts.body[0].comments
23
+ ) {
24
+ parts.push(path.call(print, "body", 0));
25
+ }
26
26
 
27
- if (elseClause) {
28
- // Before Ruby 2.6, this piece of bodystmt was an explicit "else" node
29
- const stmts =
30
- elseClause.type === "else"
31
- ? path.call(print, "body", 2, "body", 0)
32
- : path.call(print, "body", 2);
27
+ if (rescue) {
28
+ parts.push(dedent(concat([hardline, path.call(print, "body", 1)])));
29
+ }
33
30
 
34
- parts.push(concat([dedent(concat([hardline, "else"])), hardline, stmts]));
35
- }
31
+ if (elseClause) {
32
+ // Before Ruby 2.6, this piece of bodystmt was an explicit "else" node
33
+ const stmts =
34
+ elseClause.type === "else"
35
+ ? path.call(print, "body", 2, "body", 0)
36
+ : path.call(print, "body", 2);
36
37
 
37
- if (ensure) {
38
- parts.push(dedent(concat([hardline, path.call(print, "body", 3)])));
39
- }
38
+ parts.push(concat([dedent(concat([hardline, "else"])), hardline, stmts]));
39
+ }
40
40
 
41
- return group(concat(parts));
42
- },
43
- embdoc: (path, _opts, _print) => concat([trim, path.getValue().body]),
44
- paren: (path, opts, print) => {
45
- if (!path.getValue().body[0]) {
46
- return "()";
47
- }
41
+ if (ensure) {
42
+ parts.push(dedent(concat([hardline, path.call(print, "body", 3)])));
43
+ }
48
44
 
49
- let content = path.call(print, "body", 0);
45
+ return group(concat(parts));
46
+ }
50
47
 
51
- if (
52
- ["args", "args_add_star", "args_add_block"].includes(
53
- path.getValue().body[0].type
54
- )
55
- ) {
56
- content = join(concat([",", line]), content);
57
- }
48
+ const argNodeTypes = ["args", "args_add_star", "args_add_block"];
49
+
50
+ function printParen(path, opts, print) {
51
+ const contentNode = path.getValue().body[0];
58
52
 
59
- return group(
60
- concat([
61
- "(",
62
- indent(concat([softline, content])),
63
- concat([softline, ")"])
64
- ])
65
- );
53
+ if (!contentNode) {
54
+ return "()";
55
+ }
56
+
57
+ let contentDoc = path.call(print, "body", 0);
58
+
59
+ // If the content is params, we're going to let it handle its own parentheses
60
+ // so that it breaks nicely.
61
+ if (contentNode.type === "params") {
62
+ return contentDoc;
63
+ }
64
+
65
+ // If we have an arg type node as the contents, then it's going to return an
66
+ // array, so we need to explicitly join that content here.
67
+ if (argNodeTypes.includes(contentNode.type)) {
68
+ contentDoc = join(concat([",", line]), contentDoc);
69
+ }
70
+
71
+ return group(
72
+ concat([
73
+ "(",
74
+ indent(concat([softline, contentDoc])),
75
+ concat([softline, ")"])
76
+ ])
77
+ );
78
+ }
79
+
80
+ module.exports = {
81
+ "@__end__": (path, _opts, _print) => {
82
+ const { body } = path.getValue();
83
+ return concat([trim, "__END__", literalline, body]);
66
84
  },
85
+ bodystmt: printBodyStmt,
86
+ paren: printParen,
67
87
  program: (path, opts, print) =>
68
88
  concat([join(hardline, path.map(print, "body")), hardline]),
69
89
  stmts: (path, opts, print) => {
70
90
  const stmts = path.getValue().body;
91
+
92
+ // This is a special case where we have only comments inside a statement
93
+ // list. In this case we want to avoid doing any kind of line number
94
+ // tracking and just print out the comments.
95
+ if (
96
+ stmts.length === 1 &&
97
+ stmts[0].type === "void_stmt" &&
98
+ stmts[0].comments
99
+ ) {
100
+ const comments = path.map(
101
+ (commentPath) => {
102
+ commentPath.getValue().printed = true;
103
+ return opts.printer.printComment(commentPath);
104
+ },
105
+ "body",
106
+ 0,
107
+ "comments"
108
+ );
109
+
110
+ return concat([breakParent, join(hardline, comments)]);
111
+ }
112
+
71
113
  const parts = [];
72
114
  let lineNo = null;
73
115
 
@@ -8,31 +8,31 @@ const {
8
8
  join
9
9
  } = require("../prettier");
10
10
 
11
- const { concatBody, empty, makeList, prefix, surround } = require("../utils");
12
-
13
11
  // If there is some part of this string that matches an escape sequence or that
14
12
  // contains the interpolation pattern ("#{"), then we are locked into whichever
15
13
  // quote the user chose. (If they chose single quotes, then double quoting
16
14
  // would activate the escape sequence, and if they chose double quotes, then
17
15
  // single quotes would deactivate it.)
18
- const isQuoteLocked = (string) =>
19
- string.body.some(
16
+ function isQuoteLocked(node) {
17
+ return node.body.some(
20
18
  (part) =>
21
19
  part.type === "@tstring_content" &&
22
20
  (part.body.includes("#{") || part.body.includes("\\"))
23
21
  );
22
+ }
24
23
 
25
24
  // A string is considered to be able to use single quotes if it contains only
26
25
  // plain string content and that content does not contain a single quote.
27
- const isSingleQuotable = (string) =>
28
- string.body.every(
26
+ function isSingleQuotable(node) {
27
+ return node.body.every(
29
28
  (part) => part.type === "@tstring_content" && !part.body.includes("'")
30
29
  );
30
+ }
31
31
 
32
32
  const quotePattern = new RegExp("\\\\([\\s\\S])|(['\"])", "g");
33
33
 
34
- const normalizeQuotes = (content, enclosingQuote, originalQuote) => {
35
- const replaceOther = ["'", '"'].includes(originalQuote);
34
+ function normalizeQuotes(content, enclosingQuote, originalQuote) {
35
+ const replaceOther = originalQuote === '"';
36
36
  const otherQuote = enclosingQuote === '"' ? "'" : '"';
37
37
 
38
38
  // Escape and unescape single and double quotes as needed to be able to
@@ -52,7 +52,7 @@ const normalizeQuotes = (content, enclosingQuote, originalQuote) => {
52
52
 
53
53
  return `\\${escaped}`;
54
54
  });
55
- };
55
+ }
56
56
 
57
57
  const quotePairs = {
58
58
  "(": ")",
@@ -61,51 +61,100 @@ const quotePairs = {
61
61
  "<": ">"
62
62
  };
63
63
 
64
- const getClosingQuote = (quote) => {
64
+ function getClosingQuote(quote) {
65
65
  if (!quote.startsWith("%")) {
66
66
  return quote;
67
67
  }
68
68
 
69
- const boundary = /%q?(.)/.exec(quote)[1];
69
+ const boundary = /%[Qq]?(.)/.exec(quote)[1];
70
70
  if (boundary in quotePairs) {
71
71
  return quotePairs[boundary];
72
72
  }
73
73
 
74
74
  return boundary;
75
- };
75
+ }
76
76
 
77
- module.exports = {
78
- "@CHAR": (path, { preferSingleQuotes }, _print) => {
79
- const { body } = path.getValue();
77
+ // Prints a @CHAR node. @CHAR nodes are special character strings that usually
78
+ // are strings of length 1. If they're any longer than we'll try to apply the
79
+ // correct quotes.
80
+ function printChar(path, { rubySingleQuote }, _print) {
81
+ const { body } = path.getValue();
80
82
 
81
- if (body.length !== 2) {
82
- return body;
83
+ if (body.length !== 2) {
84
+ return body;
85
+ }
86
+
87
+ const quote = rubySingleQuote ? "'" : '"';
88
+ return concat([quote, body.slice(1), quote]);
89
+ }
90
+
91
+ // Prints a dynamic symbol. Assumes there's a quote property attached to the
92
+ // node that will tell us which quote to use when printing. We're just going to
93
+ // use whatever quote was provided.
94
+ function printDynaSymbol(path, opts, print) {
95
+ const { quote } = path.getValue();
96
+
97
+ return concat([":", quote].concat(path.map(print, "body")).concat(quote));
98
+ }
99
+
100
+ // Prints out an interpolated variable in the string by converting it into an
101
+ // embedded expression.
102
+ function printStringDVar(path, opts, print) {
103
+ return concat(["#{", path.call(print, "body", 0), "}"]);
104
+ }
105
+
106
+ // Prints out a literal string. This function does its best to respect the
107
+ // wishes of the user with regards to single versus double quotes, but if the
108
+ // string contains any escape expressions then it will just keep the original
109
+ // quotes.
110
+ function printStringLiteral(path, { rubySingleQuote }, print) {
111
+ const node = path.getValue();
112
+
113
+ // If the string is empty, it will not have any parts, so just print out the
114
+ // quotes corresponding to the config
115
+ if (node.body.length === 0) {
116
+ return rubySingleQuote ? "''" : '""';
117
+ }
118
+
119
+ // Determine the quote that should enclose the new string
120
+ let quote;
121
+ if (isQuoteLocked(node)) {
122
+ quote = node.quote;
123
+ } else {
124
+ quote = rubySingleQuote && isSingleQuotable(node) ? "'" : '"';
125
+ }
126
+
127
+ const parts = node.body.map((part, index) => {
128
+ if (part.type !== "@tstring_content") {
129
+ // In this case, the part of the string is an embedded expression
130
+ return path.call(print, "body", index);
83
131
  }
84
132
 
85
- const quote = preferSingleQuotes ? "'" : '"';
86
- return body.length === 2 ? concat([quote, body.slice(1), quote]) : body;
87
- },
88
- dyna_symbol: (path, opts, print) => {
89
- const { quote } = path.getValue();
133
+ // In this case, the part of the string is just regular string content
134
+ return join(
135
+ literalline,
136
+ normalizeQuotes(part.body, quote, node.quote).split("\n")
137
+ );
138
+ });
90
139
 
91
- return concat([":", quote, concat(path.call(print, "body", 0)), quote]);
92
- },
93
- heredoc: (path, opts, print) => {
94
- const { beging, body, ending } = path.getValue();
140
+ return concat([quote].concat(parts).concat(getClosingQuote(quote)));
141
+ }
95
142
 
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
- }
143
+ // Prints out a symbol literal. Its child will always be the ident that
144
+ // represents the string content of the symbol.
145
+ function printSymbolLiteral(path, opts, print) {
146
+ return concat([":", path.call(print, "body", 0)]);
147
+ }
101
148
 
102
- // In this case, the part of the string is just regular string content
103
- return join(literalline, part.body.split("\n"));
104
- });
149
+ // Prints out an xstring literal. Its child is an array of string parts,
150
+ // including plain string content and interpolated content.
151
+ function printXStringLiteral(path, opts, print) {
152
+ return concat(["`"].concat(path.map(print, "body")).concat("`"));
153
+ }
105
154
 
106
- return concat([beging, literalline, concat(parts), ending]);
107
- },
108
- string: makeList,
155
+ module.exports = {
156
+ "@CHAR": printChar,
157
+ dyna_symbol: printDynaSymbol,
109
158
  string_concat: (path, opts, print) =>
110
159
  group(
111
160
  concat([
@@ -114,14 +163,15 @@ module.exports = {
114
163
  indent(concat([hardline, path.call(print, "body", 1)]))
115
164
  ])
116
165
  ),
117
- string_dvar: surround("#{", "}"),
166
+ string_dvar: printStringDVar,
118
167
  string_embexpr: (path, opts, print) => {
119
168
  const parts = path.call(print, "body", 0);
120
169
 
121
- // If the interpolated expression is inside of an xstring literal (a string
122
- // that gets sent to the command line) then we don't want to automatically
123
- // indent, as this can lead to some very odd looking expressions
124
- if (path.getParentNode().type === "xstring") {
170
+ // If the interpolated expression is inside of a heredoc or an xstring
171
+ // literal (a string that gets sent to the command line) then we don't want
172
+ // to automatically indent, as this can lead to some very odd looking
173
+ // expressions
174
+ if (["heredoc", "xstring_literal"].includes(path.getParentNode().type)) {
125
175
  return concat(["#{", parts, "}"]);
126
176
  }
127
177
 
@@ -129,47 +179,7 @@ module.exports = {
129
179
  concat(["#{", indent(concat([softline, parts])), concat([softline, "}"])])
130
180
  );
131
181
  },
132
- string_literal: (path, { preferSingleQuotes }, print) => {
133
- const stringLiteral = path.getValue();
134
- const string = stringLiteral.body[0];
135
-
136
- // If the string is empty, it will not have any parts, so just print out the
137
- // quotes corresponding to the config
138
- if (string.body.length === 0) {
139
- return preferSingleQuotes ? "''" : '""';
140
- }
141
-
142
- // Determine the quote that should enclose the new string
143
- let quote;
144
- if (isQuoteLocked(string)) {
145
- ({ quote } = stringLiteral);
146
- } else {
147
- quote = preferSingleQuotes && isSingleQuotable(string) ? "'" : '"';
148
- }
149
-
150
- const parts = string.body.map((part, index) => {
151
- if (part.type !== "@tstring_content") {
152
- // In this case, the part of the string is an embedded expression
153
- return path.call(print, "body", 0, "body", index);
154
- }
155
-
156
- // In this case, the part of the string is just regular string content
157
- return join(
158
- literalline,
159
- normalizeQuotes(part.body, quote, stringLiteral.quote).split("\n")
160
- );
161
- });
162
-
163
- return concat([quote].concat(parts).concat(getClosingQuote(quote)));
164
- },
165
- symbol: prefix(":"),
166
- symbol_literal: concatBody,
167
- word_add: concatBody,
168
- word_new: empty,
169
- xstring: makeList,
170
- xstring_literal: (path, opts, print) => {
171
- const parts = path.call(print, "body", 0);
172
-
173
- return concat(["`"].concat(parts).concat("`"));
174
- }
182
+ string_literal: printStringLiteral,
183
+ symbol_literal: printSymbolLiteral,
184
+ xstring_literal: printXStringLiteral
175
185
  };
@@ -0,0 +1,35 @@
1
+ const { align, concat, group, join, line } = require("../prettier");
2
+ const { literal } = require("../utils");
3
+
4
+ function printSuper(path, opts, print) {
5
+ const args = path.getValue().body[0];
6
+
7
+ if (args.type === "arg_paren") {
8
+ // In case there are explicitly no arguments but they are using parens,
9
+ // we assume they are attempting to override the initializer and pass no
10
+ // arguments up.
11
+ if (args.body[0] === null) {
12
+ return "super()";
13
+ }
14
+
15
+ return concat(["super", path.call(print, "body", 0)]);
16
+ }
17
+
18
+ const keyword = "super ";
19
+ const argsDocs = path.call(print, "body", 0);
20
+
21
+ return group(
22
+ concat([
23
+ keyword,
24
+ align(keyword.length, group(join(concat([",", line]), argsDocs)))
25
+ ])
26
+ );
27
+ }
28
+
29
+ // Version of super without any parens or args.
30
+ const printZSuper = literal("super");
31
+
32
+ module.exports = {
33
+ super: printSuper,
34
+ zsuper: printZSuper
35
+ };
@@ -0,0 +1,42 @@
1
+ const {
2
+ addTrailingComment,
3
+ align,
4
+ concat,
5
+ group,
6
+ join,
7
+ line
8
+ } = require("../prettier");
9
+
10
+ function printUndefSymbol(path, opts, print) {
11
+ const node = path.getValue();
12
+
13
+ // Since we're going to descend into the symbol literal to grab out the ident
14
+ // node, then we need to make sure we copy over any comments as well,
15
+ // otherwise we could accidentally skip printing them.
16
+ if (node.comments) {
17
+ node.comments.forEach((comment) => {
18
+ addTrailingComment(node.body[0], comment);
19
+ });
20
+ }
21
+
22
+ return path.call(print, "body", 0);
23
+ }
24
+
25
+ function printUndef(path, opts, print) {
26
+ const keyword = "undef ";
27
+ const argNodes = path.map(
28
+ (symbolPath) => printUndefSymbol(symbolPath, opts, print),
29
+ "body"
30
+ );
31
+
32
+ return group(
33
+ concat([
34
+ keyword,
35
+ align(keyword.length, join(concat([",", line]), argNodes))
36
+ ])
37
+ );
38
+ }
39
+
40
+ module.exports = {
41
+ undef: printUndef
42
+ };