prettier 1.2.3 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +349 -358
  3. data/README.md +21 -93
  4. data/node_modules/prettier/index.js +54 -54
  5. data/package.json +1 -2
  6. data/rubocop.yml +26 -0
  7. data/src/haml/embed.js +87 -0
  8. data/src/haml/nodes/comment.js +27 -0
  9. data/src/haml/nodes/doctype.js +34 -0
  10. data/src/haml/nodes/filter.js +16 -0
  11. data/src/haml/nodes/hamlComment.js +21 -0
  12. data/src/haml/nodes/plain.js +6 -0
  13. data/src/haml/nodes/root.js +8 -0
  14. data/src/haml/nodes/script.js +33 -0
  15. data/src/haml/nodes/silentScript.js +59 -0
  16. data/src/haml/nodes/tag.js +193 -0
  17. data/src/haml/parser.js +22 -0
  18. data/src/haml/parser.rb +138 -0
  19. data/src/haml/printer.js +28 -0
  20. data/src/parser/getLang.js +32 -0
  21. data/src/parser/getNetcat.js +50 -0
  22. data/src/parser/netcat.js +15 -0
  23. data/src/parser/parseSync.js +33 -0
  24. data/src/parser/requestParse.js +74 -0
  25. data/src/parser/server.rb +61 -0
  26. data/src/plugin.js +26 -4
  27. data/src/prettier.js +1 -0
  28. data/src/rbs/parser.js +39 -0
  29. data/src/rbs/parser.rb +94 -0
  30. data/src/rbs/printer.js +605 -0
  31. data/src/ruby/embed.js +58 -8
  32. data/src/ruby/nodes/args.js +20 -6
  33. data/src/ruby/nodes/blocks.js +64 -59
  34. data/src/ruby/nodes/calls.js +12 -43
  35. data/src/ruby/nodes/class.js +17 -27
  36. data/src/ruby/nodes/commands.js +7 -2
  37. data/src/ruby/nodes/conditionals.js +1 -1
  38. data/src/ruby/nodes/hashes.js +28 -14
  39. data/src/ruby/nodes/hooks.js +9 -19
  40. data/src/ruby/nodes/loops.js +4 -10
  41. data/src/ruby/nodes/methods.js +8 -17
  42. data/src/ruby/nodes/params.js +22 -14
  43. data/src/ruby/nodes/patterns.js +9 -5
  44. data/src/ruby/nodes/rescue.js +32 -25
  45. data/src/ruby/nodes/return.js +0 -4
  46. data/src/ruby/nodes/statements.js +11 -13
  47. data/src/ruby/nodes/strings.js +27 -35
  48. data/src/ruby/parser.js +2 -49
  49. data/src/ruby/parser.rb +256 -232
  50. data/src/ruby/printer.js +0 -2
  51. data/src/ruby/toProc.js +4 -8
  52. data/src/utils.js +1 -0
  53. data/src/utils/isEmptyBodyStmt.js +7 -0
  54. data/src/utils/isEmptyStmts.js +9 -5
  55. data/src/utils/makeCall.js +3 -0
  56. data/src/utils/noIndent.js +1 -0
  57. data/src/utils/printEmptyCollection.js +9 -2
  58. metadata +26 -2
@@ -20,8 +20,12 @@ const parsers = {
20
20
  scss: "scss"
21
21
  };
22
22
 
23
- const replaceNewlines = (doc) =>
24
- mapDoc(doc, (currentDoc) =>
23
+ // This function is in here because it handles embedded parser values. I don't
24
+ // have a test that exercises it because I'm not sure for which parser it is
25
+ // necessary, but since it's in prettier core I'm keeping it here.
26
+ /* istanbul ignore next */
27
+ function replaceNewlines(doc) {
28
+ return mapDoc(doc, (currentDoc) =>
25
29
  typeof currentDoc === "string" && currentDoc.includes("\n")
26
30
  ? concat(
27
31
  currentDoc
@@ -30,8 +34,44 @@ const replaceNewlines = (doc) =>
30
34
  )
31
35
  : currentDoc
32
36
  );
37
+ }
33
38
 
34
- const embed = (path, print, textToDoc, _opts) => {
39
+ // Returns a number that represents the minimum amount of leading whitespace
40
+ // that is present on every line in the given string. So for example if you have
41
+ // the following heredoc:
42
+ //
43
+ // <<~HERE
44
+ // my
45
+ // content
46
+ // here
47
+ // HERE
48
+ //
49
+ // then the return value of this function would be 2. If you indented every line
50
+ // of the inner content 2 more spaces then this function would return 4.
51
+ function getCommonLeadingWhitespace(content) {
52
+ const pattern = /^\s+/;
53
+
54
+ return content
55
+ .split("\n")
56
+ .slice(0, -1)
57
+ .reduce((minimum, line) => {
58
+ const matched = pattern.exec(line);
59
+ const length = matched ? matched[0].length : 0;
60
+
61
+ return minimum === null ? length : Math.min(minimum, length);
62
+ }, null);
63
+ }
64
+
65
+ // Returns a new string with the common whitespace stripped out. Effectively it
66
+ // emulates what a squiggly heredoc does in Ruby.
67
+ function stripCommonLeadingWhitespace(content) {
68
+ const lines = content.split("\n");
69
+ const minimum = getCommonLeadingWhitespace(content);
70
+
71
+ return lines.map((line) => line.slice(minimum)).join("\n");
72
+ }
73
+
74
+ function embed(path, print, textToDoc, _opts) {
35
75
  const node = path.getValue();
36
76
 
37
77
  // Currently we only support embedded formatting on heredoc nodes
@@ -41,6 +81,8 @@ const embed = (path, print, textToDoc, _opts) => {
41
81
 
42
82
  // First, ensure that we don't have any interpolation
43
83
  const { beging, body, ending } = node;
84
+ const isSquiggly = beging.body[2] === "~";
85
+
44
86
  if (body.some((part) => part.type !== "@tstring_content")) {
45
87
  return null;
46
88
  }
@@ -52,9 +94,17 @@ const embed = (path, print, textToDoc, _opts) => {
52
94
  return null;
53
95
  }
54
96
 
55
- // Get the content as if it were a source string, and then pass that content
56
- // into the embedded parser. Get back the doc node.
57
- const content = body.map((part) => part.body).join("");
97
+ // Get the content as if it were a source string.
98
+ let content = body.map((part) => part.body).join("");
99
+
100
+ // If we're using a squiggly heredoc, then we're going to manually strip off
101
+ // the leading whitespace of each line up to the minimum leading whitespace so
102
+ // that the embedded parser can handle that for us.
103
+ if (isSquiggly) {
104
+ content = stripCommonLeadingWhitespace(content);
105
+ }
106
+
107
+ // Pass that content into the embedded parser. Get back the doc node.
58
108
  const formatted = concat([
59
109
  literalLineNoBreak,
60
110
  replaceNewlines(stripTrailingHardline(textToDoc(content, { parser })))
@@ -62,7 +112,7 @@ const embed = (path, print, textToDoc, _opts) => {
62
112
 
63
113
  // If we're using a squiggly heredoc, then we can properly handle indentation
64
114
  // ourselves.
65
- if (beging.body[2] === "~") {
115
+ if (isSquiggly) {
66
116
  return concat([
67
117
  path.call(print, "beging"),
68
118
  lineSuffix(
@@ -85,6 +135,6 @@ const embed = (path, print, textToDoc, _opts) => {
85
135
  lineSuffix(group(concat([formatted, literalLineNoBreak, ending.trim()])))
86
136
  ])
87
137
  );
88
- };
138
+ }
89
139
 
90
140
  module.exports = embed;
@@ -8,9 +8,26 @@ const {
8
8
  softline
9
9
  } = require("../../prettier");
10
10
  const { getTrailingComma } = require("../../utils");
11
-
12
11
  const toProc = require("../toProc");
13
12
 
13
+ const noTrailingComma = ["command", "command_call"];
14
+
15
+ function getArgParenTrailingComma(node) {
16
+ // If we have a block, then we don't want to add a trailing comma.
17
+ if (node.type === "args_add_block" && node.body[1]) {
18
+ return "";
19
+ }
20
+
21
+ // If we only have one argument and that first argument necessitates that we
22
+ // skip putting a comma (because it would interfere with parsing the argument)
23
+ // then we don't want to add a trailing comma.
24
+ if (node.body.length === 1 && noTrailingComma.includes(node.body[0].type)) {
25
+ return "";
26
+ }
27
+
28
+ return ifBreak(",", "");
29
+ }
30
+
14
31
  function printArgParen(path, opts, print) {
15
32
  const argsNode = path.getValue().body[0];
16
33
 
@@ -32,9 +49,6 @@ function printArgParen(path, opts, print) {
32
49
  );
33
50
  }
34
51
 
35
- const args = path.call(print, "body", 0);
36
- const hasBlock = argsNode.type === "args_add_block" && argsNode.body[1];
37
-
38
52
  // Now here we return a doc that represents the whole grouped expression,
39
53
  // including the surrouding parentheses.
40
54
  return group(
@@ -43,8 +57,8 @@ function printArgParen(path, opts, print) {
43
57
  indent(
44
58
  concat([
45
59
  softline,
46
- join(concat([",", line]), args),
47
- getTrailingComma(opts) && !hasBlock ? ifBreak(",", "") : ""
60
+ join(concat([",", line]), path.call(print, "body", 0)),
61
+ getTrailingComma(opts) && getArgParenTrailingComma(argsNode)
48
62
  ])
49
63
  ),
50
64
  softline,
@@ -10,76 +10,81 @@ const {
10
10
  } = require("../../prettier");
11
11
  const { hasAncestor } = require("../../utils");
12
12
 
13
- const printBlock = (braces) => (path, opts, print) => {
14
- const [variables, statements] = path.getValue().body;
15
- const stmts =
16
- statements.type === "stmts" ? statements.body : statements.body[0].body;
13
+ function printBlockVar(path, opts, print) {
14
+ const parts = ["|", removeLines(path.call(print, "body", 0))];
17
15
 
18
- let doBlockBody = "";
19
- if (
20
- stmts.length !== 1 ||
21
- stmts[0].type !== "void_stmt" ||
22
- stmts[0].comments
23
- ) {
24
- doBlockBody = indent(concat([softline, path.call(print, "body", 1)]));
16
+ // The second part of this node is a list of optional block-local variables
17
+ if (path.getValue().body[1]) {
18
+ parts.push("; ", join(", ", path.map(print, "body", 1)));
25
19
  }
26
20
 
27
- // If this block is nested underneath a command or command_call node, then we
28
- // can't use `do...end` because that will get associated with the parent node
29
- // as opposed to the current node (because of the difference in operator
30
- // precedence). Instead, we still use a multi-line format but switch to using
31
- // braces instead.
32
- const useBraces = braces && hasAncestor(path, ["command", "command_call"]);
21
+ parts.push("| ");
22
+ return concat(parts);
23
+ }
33
24
 
34
- const doBlock = concat([
35
- useBraces ? " {" : " do",
36
- variables ? concat([" ", path.call(print, "body", 0)]) : "",
37
- doBlockBody,
38
- concat([softline, useBraces ? "}" : "end"])
39
- ]);
25
+ function printBlock(braces) {
26
+ return function printBlockWithBraces(path, opts, print) {
27
+ const [variables, statements] = path.getValue().body;
28
+ const stmts =
29
+ statements.type === "stmts" ? statements.body : statements.body[0].body;
40
30
 
41
- // We can hit this next pattern if within the block the only statement is a
42
- // comment.
43
- if (
44
- stmts.length === 1 &&
45
- stmts[0].type === "void_stmt" &&
46
- stmts[0].comments
47
- ) {
48
- return concat([breakParent, doBlock]);
49
- }
31
+ let doBlockBody = "";
32
+ if (
33
+ stmts.length !== 1 ||
34
+ stmts[0].type !== "void_stmt" ||
35
+ stmts[0].comments
36
+ ) {
37
+ doBlockBody = indent(concat([softline, path.call(print, "body", 1)]));
38
+ }
50
39
 
51
- // If the parent node is a command node, then there are no parentheses around
52
- // the arguments to that command, so we need to break the block
53
- if (["command", "command_call"].includes(path.getParentNode().body[0].type)) {
54
- return concat([breakParent, doBlock]);
55
- }
40
+ // If this block is nested underneath a command or command_call node, then
41
+ // we can't use `do...end` because that will get associated with the parent
42
+ // node as opposed to the current node (because of the difference in
43
+ // operator precedence). Instead, we still use a multi-line format but
44
+ // switch to using braces instead.
45
+ const useBraces = braces && hasAncestor(path, ["command", "command_call"]);
56
46
 
57
- const hasBody = stmts.some(({ type }) => type !== "void_stmt");
58
- const braceBlock = concat([
59
- " {",
60
- hasBody || variables ? " " : "",
61
- variables ? path.call(print, "body", 0) : "",
62
- path.call(print, "body", 1),
63
- hasBody ? " " : "",
64
- "}"
65
- ]);
47
+ const doBlock = concat([
48
+ useBraces ? " {" : " do",
49
+ variables ? concat([" ", path.call(print, "body", 0)]) : "",
50
+ doBlockBody,
51
+ concat([softline, useBraces ? "}" : "end"])
52
+ ]);
66
53
 
67
- return group(ifBreak(doBlock, braceBlock));
68
- };
54
+ // We can hit this next pattern if within the block the only statement is a
55
+ // comment.
56
+ if (
57
+ stmts.length === 1 &&
58
+ stmts[0].type === "void_stmt" &&
59
+ stmts[0].comments
60
+ ) {
61
+ return concat([breakParent, doBlock]);
62
+ }
69
63
 
70
- module.exports = {
71
- block_var: (path, opts, print) => {
72
- const parts = ["|", removeLines(path.call(print, "body", 0))];
64
+ const blockReceiver = path.getParentNode().body[0];
73
65
 
74
- // The second part of this node is a list of optional block-local variables
75
- if (path.getValue().body[1]) {
76
- parts.push("; ", join(", ", path.map(print, "body", 1)));
66
+ // If the parent node is a command node, then there are no parentheses
67
+ // around the arguments to that command, so we need to break the block
68
+ if (["command", "command_call"].includes(blockReceiver.type)) {
69
+ return concat([breakParent, doBlock]);
77
70
  }
78
71
 
79
- parts.push("| ");
80
- return concat(parts);
81
- },
72
+ const hasBody = stmts.some(({ type }) => type !== "void_stmt");
73
+ const braceBlock = concat([
74
+ " {",
75
+ hasBody || variables ? " " : "",
76
+ variables ? path.call(print, "body", 0) : "",
77
+ path.call(print, "body", 1),
78
+ hasBody ? " " : "",
79
+ "}"
80
+ ]);
81
+
82
+ return group(ifBreak(doBlock, braceBlock));
83
+ };
84
+ }
85
+
86
+ module.exports = {
87
+ block_var: printBlockVar,
82
88
  brace_block: printBlock(true),
83
- do_block: printBlock(false),
84
- excessed_comma: () => ""
89
+ do_block: printBlock(false)
85
90
  };
@@ -24,6 +24,12 @@ function printCall(path, opts, print) {
24
24
  // call syntax so if `call` is implicit, we don't print it out.
25
25
  const messageDoc = messageNode === "call" ? "" : path.call(print, "body", 2);
26
26
 
27
+ // For certain left sides of the call nodes, we want to attach directly to
28
+ // the } or end.
29
+ if (noIndent.includes(receiverNode.type)) {
30
+ return concat([receiverDoc, operatorDoc, messageDoc]);
31
+ }
32
+
27
33
  // The right side of the call node, as in everything including the operator
28
34
  // and beyond.
29
35
  const rightSideDoc = concat([
@@ -37,7 +43,7 @@ function printCall(path, opts, print) {
37
43
 
38
44
  // If our parent node is a chained node then we're not going to group the
39
45
  // right side of the expression, as we want to have a nice multi-line layout.
40
- if (chained.includes(parentNode.type)) {
46
+ if (chained.includes(parentNode.type) && !node.comments) {
41
47
  parentNode.chain = (node.chain || 0) + 1;
42
48
  parentNode.callChain = (node.callChain || 0) + 1;
43
49
  parentNode.breakDoc = (node.breakDoc || [receiverDoc]).concat(rightSideDoc);
@@ -47,18 +53,10 @@ function printCall(path, opts, print) {
47
53
  // If we're at the top of a chain, then we're going to print out a nice
48
54
  // multi-line layout if this doesn't break into multiple lines.
49
55
  if (!chained.includes(parentNode.type) && (node.chain || 0) >= 3) {
50
- let breakDoc = concat(node.breakDoc.concat(rightSideDoc));
51
- if (!noIndent.includes(node.firstReceiverType)) {
52
- breakDoc = indent(breakDoc);
53
- }
54
-
55
- return ifBreak(group(breakDoc), concat([receiverDoc, group(rightSideDoc)]));
56
- }
57
-
58
- // For certain left sides of the call nodes, we want to attach directly to
59
- // the } or end.
60
- if (noIndent.includes(receiverNode.type)) {
61
- return concat([receiverDoc, operatorDoc, messageDoc]);
56
+ return ifBreak(
57
+ group(indent(concat(node.breakDoc.concat(rightSideDoc)))),
58
+ concat([receiverDoc, group(rightSideDoc)])
59
+ );
62
60
  }
63
61
 
64
62
  return group(concat([receiverDoc, group(indent(rightSideDoc))]));
@@ -105,7 +103,7 @@ function printMethodAddArg(path, opts, print) {
105
103
 
106
104
  // If our parent node is a chained node then we're not going to group the
107
105
  // right side of the expression, as we want to have a nice multi-line layout.
108
- if (chained.includes(parentNode.type)) {
106
+ if (chained.includes(parentNode.type) && !node.comments) {
109
107
  parentNode.chain = (node.chain || 0) + 1;
110
108
  parentNode.breakDoc = (node.breakDoc || [methodDoc]).concat(argsDoc);
111
109
  parentNode.firstReceiverType = node.firstReceiverType;
@@ -138,41 +136,12 @@ function printMethodAddArg(path, opts, print) {
138
136
  return concat([methodDoc, argsDoc]);
139
137
  }
140
138
 
141
- // Sorbet type annotations look like the following:
142
- //
143
- // {method_add_block
144
- // [{method_add_arg
145
- // [{fcall
146
- // [{@ident "sig"}]},
147
- // {args []}]},
148
- // {brace_block [nil, {stmts}]}}]}
149
- //
150
- function isSorbetTypeAnnotation(node) {
151
- const [callNode, blockNode] = node.body;
152
-
153
- return (
154
- callNode.type === "method_add_arg" &&
155
- callNode.body[0].type === "fcall" &&
156
- callNode.body[0].body[0].body === "sig" &&
157
- callNode.body[1].type === "args" &&
158
- callNode.body[1].body.length === 0 &&
159
- blockNode
160
- );
161
- }
162
-
163
139
  function printMethodAddBlock(path, opts, print) {
164
140
  const node = path.getValue();
165
141
 
166
142
  const [callNode, blockNode] = node.body;
167
143
  const [callDoc, blockDoc] = path.map(print, "body");
168
144
 
169
- // Very special handling here for sorbet type annotations. They look like Ruby
170
- // code, but they're not actually Ruby code, so we're not going to mess with
171
- // them at all.
172
- if (isSorbetTypeAnnotation(node)) {
173
- return opts.originalText.slice(opts.locStart(node), opts.locEnd(node));
174
- }
175
-
176
145
  // Don't bother trying to do any kind of fancy toProc transform if the option
177
146
  // is disabled.
178
147
  if (opts.rubyToProc) {
@@ -1,34 +1,22 @@
1
- const {
2
- concat,
3
- group,
4
- hardline,
5
- ifBreak,
6
- indent,
7
- line
8
- } = require("../../prettier");
1
+ const { concat, group, hardline, indent } = require("../../prettier");
2
+ const { isEmptyBodyStmt } = require("../../utils");
9
3
 
10
4
  function printClass(path, opts, print) {
11
5
  const [_constant, superclass, bodystmt] = path.getValue().body;
12
- const stmts = bodystmt.body[0];
13
6
 
14
7
  const parts = ["class ", path.call(print, "body", 0)];
15
8
  if (superclass) {
16
9
  parts.push(" < ", path.call(print, "body", 1));
17
10
  }
18
11
 
19
- // If the body is empty and does not contain any comments, we can just
20
- // replace the body with a semi-colon.
21
- if (
22
- stmts.body.length === 1 &&
23
- stmts.body[0].type === "void_stmt" &&
24
- !stmts.body[0].comments
25
- ) {
26
- return group(concat([concat(parts), ifBreak(line, "; "), "end"]));
12
+ const declaration = group(concat(parts));
13
+ if (isEmptyBodyStmt(bodystmt)) {
14
+ return group(concat([declaration, hardline, "end"]));
27
15
  }
28
16
 
29
17
  return group(
30
18
  concat([
31
- concat(parts),
19
+ declaration,
32
20
  indent(concat([hardline, path.call(print, "body", 2)])),
33
21
  concat([hardline, "end"])
34
22
  ])
@@ -36,16 +24,11 @@ function printClass(path, opts, print) {
36
24
  }
37
25
 
38
26
  function printModule(path, opts, print) {
27
+ const node = path.getValue();
39
28
  const declaration = group(concat(["module ", path.call(print, "body", 0)]));
40
29
 
41
- // If the body is empty, we can replace with a ;
42
- const stmts = path.getValue().body[1].body[0];
43
- if (
44
- stmts.body.length === 1 &&
45
- stmts.body[0].type === "void_stmt" &&
46
- !stmts.body[0].comments
47
- ) {
48
- return group(concat([declaration, ifBreak(line, "; "), "end"]));
30
+ if (isEmptyBodyStmt(node.body[1])) {
31
+ return group(concat([declaration, hardline, "end"]));
49
32
  }
50
33
 
51
34
  return group(
@@ -58,9 +41,16 @@ function printModule(path, opts, print) {
58
41
  }
59
42
 
60
43
  function printSClass(path, opts, print) {
44
+ const bodystmt = path.getValue().body[1];
45
+ const declaration = concat(["class << ", path.call(print, "body", 0)]);
46
+
47
+ if (isEmptyBodyStmt(bodystmt)) {
48
+ return group(concat([declaration, hardline, "end"]));
49
+ }
50
+
61
51
  return group(
62
52
  concat([
63
- concat(["class << ", path.call(print, "body", 0)]),
53
+ declaration,
64
54
  indent(concat([hardline, path.call(print, "body", 1)])),
65
55
  concat([hardline, "end"])
66
56
  ])