prettier 1.4.0 → 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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prettier/plugin-ruby",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "prettier plugin for the Ruby programming language",
5
5
  "main": "src/plugin.js",
6
6
  "scripts": {
@@ -36,6 +36,7 @@ module.exports = {
36
36
  ".rabl",
37
37
  ".rake",
38
38
  ".rb",
39
+ ".rbi",
39
40
  ".rbuild",
40
41
  ".rbw",
41
42
  ".rbx",
@@ -23,8 +23,14 @@ end
23
23
  class RBS::Location
24
24
  def to_json(*args)
25
25
  {
26
- start: { line: start_line, column: start_column },
27
- end: { line: end_line, column: end_column },
26
+ start: {
27
+ line: start_line,
28
+ column: start_column
29
+ },
30
+ end: {
31
+ line: end_line,
32
+ column: end_column
33
+ },
28
34
  start_pos: start_pos,
29
35
  end_pos: end_pos
30
36
  }.to_json(*args)
@@ -24,8 +24,8 @@ const parsers = {
24
24
  // have a test that exercises it because I'm not sure for which parser it is
25
25
  // necessary, but since it's in prettier core I'm keeping it here.
26
26
  /* istanbul ignore next */
27
- const replaceNewlines = (doc) =>
28
- mapDoc(doc, (currentDoc) =>
27
+ function replaceNewlines(doc) {
28
+ return mapDoc(doc, (currentDoc) =>
29
29
  typeof currentDoc === "string" && currentDoc.includes("\n")
30
30
  ? concat(
31
31
  currentDoc
@@ -34,8 +34,44 @@ const replaceNewlines = (doc) =>
34
34
  )
35
35
  : currentDoc
36
36
  );
37
+ }
37
38
 
38
- 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) {
39
75
  const node = path.getValue();
40
76
 
41
77
  // Currently we only support embedded formatting on heredoc nodes
@@ -45,6 +81,8 @@ const embed = (path, print, textToDoc, _opts) => {
45
81
 
46
82
  // First, ensure that we don't have any interpolation
47
83
  const { beging, body, ending } = node;
84
+ const isSquiggly = beging.body[2] === "~";
85
+
48
86
  if (body.some((part) => part.type !== "@tstring_content")) {
49
87
  return null;
50
88
  }
@@ -56,9 +94,17 @@ const embed = (path, print, textToDoc, _opts) => {
56
94
  return null;
57
95
  }
58
96
 
59
- // Get the content as if it were a source string, and then pass that content
60
- // into the embedded parser. Get back the doc node.
61
- 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.
62
108
  const formatted = concat([
63
109
  literalLineNoBreak,
64
110
  replaceNewlines(stripTrailingHardline(textToDoc(content, { parser })))
@@ -66,7 +112,7 @@ const embed = (path, print, textToDoc, _opts) => {
66
112
 
67
113
  // If we're using a squiggly heredoc, then we can properly handle indentation
68
114
  // ourselves.
69
- if (beging.body[2] === "~") {
115
+ if (isSquiggly) {
70
116
  return concat([
71
117
  path.call(print, "beging"),
72
118
  lineSuffix(
@@ -89,6 +135,6 @@ const embed = (path, print, textToDoc, _opts) => {
89
135
  lineSuffix(group(concat([formatted, literalLineNoBreak, ending.trim()])))
90
136
  ])
91
137
  );
92
- };
138
+ }
93
139
 
94
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,
@@ -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
  ])
@@ -219,7 +219,7 @@ const printConditional = (keyword) => (path, { rubyModifier }, print) => {
219
219
 
220
220
  // If the body of the conditional is empty, then we explicitly have to use the
221
221
  // block form.
222
- if (isEmptyStmts(statements) && !statements.body[0].comments) {
222
+ if (isEmptyStmts(statements)) {
223
223
  return concat([
224
224
  `${keyword} `,
225
225
  align(keyword.length + 1, path.call(print, "body", 0)),
@@ -6,6 +6,7 @@ const {
6
6
  join,
7
7
  line
8
8
  } = require("../../prettier");
9
+
9
10
  const {
10
11
  getTrailingComma,
11
12
  printEmptyCollection,
@@ -89,6 +90,13 @@ function printAssocNew(path, opts, print) {
89
90
  const parts = [path.call((keyPath) => keyPrinter(keyPath, print), "body", 0)];
90
91
  const valueDoc = path.call(print, "body", 1);
91
92
 
93
+ // If we're printing a child hash then we want it to break along with its
94
+ // parent hash, so we don't group the parts.
95
+ if (valueNode.type === "hash") {
96
+ parts.push(" ", valueDoc);
97
+ return concat(parts);
98
+ }
99
+
92
100
  if (!skipAssignIndent(valueNode) || keyNode.comments) {
93
101
  parts.push(indent(concat([line, valueDoc])));
94
102
  } else {
@@ -125,20 +133,26 @@ function printHash(path, opts, print) {
125
133
  return printEmptyCollection(path, opts, "{", "}");
126
134
  }
127
135
 
128
- return group(
129
- concat([
130
- "{",
131
- indent(
132
- concat([
133
- line,
134
- path.call(print, "body", 0),
135
- getTrailingComma(opts) ? ifBreak(",", "") : ""
136
- ])
137
- ),
138
- line,
139
- "}"
140
- ])
141
- );
136
+ let hashDoc = concat([
137
+ "{",
138
+ indent(
139
+ concat([
140
+ line,
141
+ path.call(print, "body", 0),
142
+ getTrailingComma(opts) ? ifBreak(",", "") : ""
143
+ ])
144
+ ),
145
+ line,
146
+ "}"
147
+ ]);
148
+
149
+ // If we're inside another hash, then we don't want to group our contents
150
+ // because we want this hash to break along with its parent hash.
151
+ if (path.getParentNode().type === "assoc_new") {
152
+ return hashDoc;
153
+ }
154
+
155
+ return group(hashDoc);
142
156
  }
143
157
 
144
158
  module.exports = {
@@ -10,7 +10,7 @@ const {
10
10
  softline
11
11
  } = require("../../prettier");
12
12
 
13
- const { containsAssignment } = require("../../utils");
13
+ const { containsAssignment, isEmptyStmts } = require("../../utils");
14
14
  const inlineEnsureParens = require("../../utils/inlineEnsureParens");
15
15
 
16
16
  function printLoop(keyword, modifier) {
@@ -19,17 +19,11 @@ function printLoop(keyword, modifier) {
19
19
 
20
20
  // If the only statement inside this while loop is a void statement, then we
21
21
  // can shorten to just displaying the predicate and then a semicolon.
22
- if (
23
- stmts.body.length === 1 &&
24
- stmts.body[0].type === "void_stmt" &&
25
- !stmts.body[0].comments
26
- ) {
22
+ if (isEmptyStmts(stmts)) {
27
23
  return group(
28
24
  concat([
29
- keyword,
30
- " ",
31
- path.call(print, "body", 0),
32
- ifBreak(softline, "; "),
25
+ group(concat([keyword, " ", path.call(print, "body", 0)])),
26
+ hardline,
33
27
  "end"
34
28
  ])
35
29
  );
@@ -1,8 +1,9 @@
1
1
  const { concat, group, hardline, indent, line } = require("../../prettier");
2
+ const { isEmptyBodyStmt } = require("../../utils");
2
3
 
3
4
  function printMethod(offset) {
4
5
  return function printMethodWithOffset(path, opts, print) {
5
- const [_name, params, body] = path.getValue().body.slice(offset);
6
+ const [_name, params, bodystmt] = path.getValue().body.slice(offset);
6
7
  const declaration = ["def "];
7
8
 
8
9
  // In this case, we're printing a method that's defined as a singleton, so
@@ -24,16 +25,8 @@ function printMethod(offset) {
24
25
  parens ? ")" : ""
25
26
  );
26
27
 
27
- // If the body is empty, we can replace with a ;
28
- const stmts = body.body[0].body;
29
-
30
- if (
31
- !body.body.slice(1).some((node) => node) &&
32
- stmts.length === 1 &&
33
- stmts[0].type === "void_stmt" &&
34
- !stmts[0].comments
35
- ) {
36
- return group(concat(declaration.concat(["; end"])));
28
+ if (isEmptyBodyStmt(bodystmt)) {
29
+ return group(concat(declaration.concat("; end")));
37
30
  }
38
31
 
39
32
  return group(
@@ -12,15 +12,13 @@ const {
12
12
  trim
13
13
  } = require("../../prettier");
14
14
 
15
+ const { isEmptyStmts } = require("../../utils");
16
+
15
17
  function printBodyStmt(path, opts, print) {
16
18
  const [stmts, rescue, elseClause, ensure] = path.getValue().body;
17
19
  const parts = [];
18
20
 
19
- if (
20
- stmts.body.length > 1 ||
21
- stmts.body[0].type != "void_stmt" ||
22
- stmts.body[0].comments
23
- ) {
21
+ if (!isEmptyStmts(stmts)) {
24
22
  parts.push(path.call(print, "body", 0));
25
23
  }
26
24
 
@@ -17,7 +17,8 @@ require 'delegate'
17
17
  require 'json'
18
18
  require 'ripper'
19
19
 
20
- module Prettier; end
20
+ module Prettier
21
+ end
21
22
 
22
23
  class Prettier::Parser < Ripper
23
24
  attr_reader :source, :lines, :scanner_events, :line_counts
@@ -1,6 +1,7 @@
1
1
  module.exports = {
2
2
  containsAssignment: require("./utils/containsAssignment"),
3
3
  getTrailingComma: require("./utils/getTrailingComma"),
4
+ isEmptyBodyStmt: require("./utils/isEmptyBodyStmt"),
4
5
  isEmptyStmts: require("./utils/isEmptyStmts"),
5
6
  hasAncestor: require("./utils/hasAncestor"),
6
7
  literal: require("./utils/literal"),
@@ -0,0 +1,7 @@
1
+ const isEmptyStmts = require("./isEmptyStmts");
2
+
3
+ function isEmptyBodyStmt(node) {
4
+ return isEmptyStmts(node.body[0]) && !node.body.slice(1).some(Boolean);
5
+ }
6
+
7
+ module.exports = isEmptyBodyStmt;
@@ -1,7 +1,11 @@
1
- const isEmptyStmts = (node) =>
2
- node &&
3
- node.type === "stmts" &&
4
- node.body.length === 1 &&
5
- node.body[0].type === "void_stmt";
1
+ function isEmptyStmts(node) {
2
+ return (
3
+ node &&
4
+ node.type === "stmts" &&
5
+ node.body.length === 1 &&
6
+ node.body[0].type === "void_stmt" &&
7
+ !node.body[0].comments
8
+ );
9
+ }
6
10
 
7
11
  module.exports = isEmptyStmts;