prettier 1.2.3 → 1.5.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.
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
  ])