prettier 0.15.0 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4601f1c4bcd732027d5e7077aeece6ce80485b33f6d6122149903a3b768bae01
4
- data.tar.gz: 7b8934c54be0b0125b25325d45a82d801314c9a4f6074033f9e719db8e873ffc
3
+ metadata.gz: b3562a5d108cf70183f77a181bc6d2370c28119596741bfedaf0f40b17062098
4
+ data.tar.gz: ecafaf2f71979173250bf03aabe13c7d6fa5d6fe4f7373042405c81bdc6a3b60
5
5
  SHA512:
6
- metadata.gz: 5b9adb4c97a45dd1323cd36af7e8fbd6b542a5bb15f9c5c5824155ee09ea2c6cd2d4b0e27eedcfb4c3d79b1045fa7454cfba3ead575262a1740352a74f47798d
7
- data.tar.gz: 3e6bd216100a983f702ca001c1f2578609361fe11e7e7a3ffa70da6b8a0db988d23afd3d7ba5328dfea8f0b1fe5d124bd9767f498708c931aef27e6f05994c40
6
+ metadata.gz: fc85edcee8ac7892a6d3b0e727abff488d72a5ecfa717383229774f24faae4bf0384f77e1c5c459586b363dc8822968413c1e0aa8da8f998ca46ccf986a3344b
7
+ data.tar.gz: 79f754fd9fa9a46171e9c8a6871d4b487fd4cc19a8cb21527b5a0d322761771d3339e28bdc4128ab044240d5a7c149a9ea6c40d8acb1db036ecdea7163419af0
data/CHANGELOG.md CHANGED
@@ -6,6 +6,72 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.15.1] - 2019-11-05
10
+
11
+ ### Changed
12
+
13
+ - [INTERNAL] Add `bin/lex` for viewing the tokenized result of Ripper on Ruby code (Thanks to @AlanFoster.)
14
+ - When predicates from within an `if`, `unless`, `while`, or `until` loop break the line, they should be aligned together. For example,
15
+
16
+ <!-- prettier-ignore -->
17
+ ```ruby
18
+ if foooooo || barrrrrr
19
+ baz
20
+ end
21
+ ```
22
+
23
+ If the line was set to very short, the binary node should be aligned to 3 spaces from the left of the file (which aligns with the `if`, it would be more for `unless`). So it would look like:
24
+
25
+ <!-- prettier-ignore -->
26
+ ```ruby
27
+ if foooooo ||
28
+ barrrrrr
29
+ baz
30
+ end
31
+ ```
32
+
33
+ (Thanks to @jakeprime for the report.)
34
+
35
+ - Empty `if`, and `unless` conditionals are now handled gracefully:
36
+
37
+ <!-- prettier-ignore -->
38
+ ```ruby
39
+ if foo?
40
+ end
41
+ ```
42
+
43
+ (Thanks to @AlanFoster for the fix, and @jamescostian for the report.)
44
+
45
+ - Hash keys are not converted to keyword syntax if they would make invalid symbols. For example,
46
+
47
+ <!-- prettier-ignore -->
48
+ ```ruby
49
+ { :[] => nil }
50
+ ```
51
+
52
+ cannot be translated into `[]:` as that is an invalid symbol. Instead, it stays with the hash rocket syntax. (Thanks to @mmainz for the report.)
53
+
54
+ - Do not attempt to format the insides of xstring literals (string that get sent to the command line surrounded by backticks or `%x`). (Thanks to @cldevs for the report.)
55
+ - When predicates for `if`, `unless`, `while`, or `until` nodes contain an assignment, we can't know for sure that it doesn't modify the body. In this case we need to always break and form a multi-line block. (Thanks to @cldevs for the report.)
56
+ - When the return value of `if`, `unless`, `while`, or `until` nodes are assigned to anything other than a local variable, we need to wrap them in parentheses if we're changing to the modifier form. This is because the following expressions have different semantic meaning:
57
+
58
+ <!-- prettier-ignore -->
59
+ ```ruby
60
+ hash[:key] = break :value while false
61
+ hash[:key] = while false do break :value end
62
+ ```
63
+
64
+ The first one will not result in an empty hash, whereas the second one will result in `{ key: nil }`. In this case what we need to do for the first expression to align is wrap it in parens, as in:
65
+
66
+ <!-- prettier-ignore -->
67
+ ```ruby
68
+ hash[:key] = (break :value while false)
69
+ ```
70
+
71
+ That will guarantee that the expressions are equivalent. (Thanks to @MarcManiez for the report.)
72
+
73
+ - Fix crashes that were happening with `ignored_nl` nodes. (Thanks to @AlanFoster.)
74
+
9
75
  ## [0.15.0] - 2019-08-06
10
76
 
11
77
  ### Changed
@@ -545,7 +611,8 @@ would previously result in `array[]`, but now prints properly. (Thanks to @xipgr
545
611
 
546
612
  - Initial release 🎉
547
613
 
548
- [unreleased]: https://github.com/prettier/plugin-ruby/compare/v0.15.0...HEAD
614
+ [unreleased]: https://github.com/prettier/plugin-ruby/compare/v0.15.1...HEAD
615
+ [0.15.1]: https://github.com/prettier/plugin-ruby/compare/v0.15.0...v0.15.1
549
616
  [0.15.0]: https://github.com/prettier/plugin-ruby/compare/v0.14.0...v0.15.0
550
617
  [0.14.0]: https://github.com/prettier/plugin-ruby/compare/v0.13.0...v0.14.0
551
618
  [0.13.0]: https://github.com/prettier/plugin-ruby/compare/v0.12.3...v0.13.0
data/CONTRIBUTING.md CHANGED
@@ -118,6 +118,18 @@ yarn run test
118
118
  yarn run test -u
119
119
  ```
120
120
 
121
+ **Viewing the tokenized result of Ripper on code**
122
+
123
+ ```
124
+ bin/lex 'your_ruby_code(1, 2, 3)'
125
+ ```
126
+
127
+ **Viewing the tokenized result of Ripper on a file**
128
+
129
+ ```
130
+ bin/lex file.rb
131
+ ```
132
+
121
133
  **Viewing the output of RipperJS on a file**
122
134
 
123
135
  ```
data/README.md CHANGED
@@ -191,6 +191,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
191
191
  </tr>
192
192
  <tr>
193
193
  <td align="center"><a href="https://github.com/jakeprime"><img src="https://avatars1.githubusercontent.com/u/1019036?v=4" width="100px;" alt="jakeprime"/><br /><sub><b>jakeprime</b></sub></a><br /><a href="https://github.com/prettier/plugin-ruby/issues?q=author%3Ajakeprime" title="Bug reports">🐛</a></td>
194
+ <td align="center"><a href="http://mmainz.github.io"><img src="https://avatars2.githubusercontent.com/u/5417714?v=4" width="100px;" alt="Mario Mainz"/><br /><sub><b>Mario Mainz</b></sub></a><br /><a href="https://github.com/prettier/plugin-ruby/issues?q=author%3Ammainz" title="Bug reports">🐛</a></td>
195
+ <td align="center"><a href="http://www.cldevs.com"><img src="https://avatars3.githubusercontent.com/u/38632061?v=4" width="100px;" alt="CL Web Developers"/><br /><sub><b>CL Web Developers</b></sub></a><br /><a href="https://github.com/prettier/plugin-ruby/issues?q=author%3Acldevs" title="Bug reports">🐛</a></td>
194
196
  </tr>
195
197
  </table>
196
198
 
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prettier/plugin-ruby",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "description": "prettier plugin for the Ruby programming language",
5
5
  "main": "src/ruby.js",
6
6
  "scripts": {
@@ -24,7 +24,7 @@
24
24
  "devDependencies": {
25
25
  "all-contributors-cli": "^6.1.2",
26
26
  "eslint": "^6.0.1",
27
- "eslint-config-airbnb-base": "^13.1.0",
27
+ "eslint-config-airbnb-base": "^14.0.0",
28
28
  "eslint-config-prettier": "^6.0.0",
29
29
  "eslint-plugin-import": "^2.16.0",
30
30
  "jest": "^24.0.0"
@@ -8,6 +8,7 @@ const {
8
8
  indent,
9
9
  softline
10
10
  } = require("../prettier");
11
+ const { containsAssignment } = require("../utils");
11
12
 
12
13
  const noTernary = [
13
14
  "@comment",
@@ -40,7 +41,7 @@ const noTernary = [
40
41
  const printWithAddition = (keyword, path, print, { breaking = false } = {}) =>
41
42
  concat([
42
43
  `${keyword} `,
43
- align(keyword.length - 1, path.call(print, "body", 0)),
44
+ align(keyword.length + 1, path.call(print, "body", 0)),
44
45
  indent(concat([softline, path.call(print, "body", 1)])),
45
46
  concat([softline, path.call(print, "body", 2)]),
46
47
  concat([softline, "end"]),
@@ -91,7 +92,7 @@ const printTernary = (path, _opts, print) => {
91
92
  ifBreak(
92
93
  concat([
93
94
  "if ",
94
- predicate,
95
+ align(3, predicate),
95
96
  indent(concat([softline, truthyClause])),
96
97
  concat([softline, "else"]),
97
98
  indent(concat([softline, falsyClause])),
@@ -102,31 +103,52 @@ const printTernary = (path, _opts, print) => {
102
103
  );
103
104
  };
104
105
 
105
- // Prints an `if_mod` or `unless_mod` node. Because it was previously in the
106
- // modifier form, we're guaranteed to not have an additional node, so we can
107
- // just work with the predicate and the body.
108
- const printSingle = keyword => (path, { inlineConditionals }, print) => {
109
- const multiline = concat([
106
+ const makeSingleBlockForm = (keyword, path, print) =>
107
+ concat([
110
108
  `${keyword} `,
111
- align(keyword.length - 1, path.call(print, "body", 0)),
109
+ align(keyword.length + 1, path.call(print, "body", 0)),
112
110
  indent(concat([softline, path.call(print, "body", 1)])),
113
111
  concat([softline, "end"])
114
112
  ]);
115
113
 
116
- const [predicate, stmts] = path.getValue().body;
114
+ // Prints an `if_mod` or `unless_mod` node. Because it was previously in the
115
+ // modifier form, we're guaranteed to not have an additional node, so we can
116
+ // just work with the predicate and the body.
117
+ const printSingle = keyword => (path, { inlineConditionals }, print) => {
118
+ const multiline = makeSingleBlockForm(keyword, path, print);
119
+
120
+ const [_predicate, stmts] = path.getValue().body;
117
121
  const hasComments =
118
122
  stmts.type === "stmts" && stmts.body.some(stmt => stmt.type === "@comment");
119
123
 
120
- if (!inlineConditionals || hasComments || predicate.type === "assign") {
124
+ if (!inlineConditionals || hasComments) {
121
125
  return multiline;
122
126
  }
123
127
 
124
- const inline = concat([
128
+ let inlineParts = [
125
129
  path.call(print, "body", 1),
126
130
  ` ${keyword} `,
127
131
  path.call(print, "body", 0)
128
- ]);
132
+ ];
133
+
134
+ // If the return value of this conditional expression is being assigned to
135
+ // anything besides a local variable then we can't inline the entire
136
+ // expression without wrapping it in parentheses. This is because the
137
+ // following expressions have different semantic meaning:
138
+ //
139
+ // hash[:key] = :value if false
140
+ // hash[:key] = if false then :value end
141
+ //
142
+ // The first one will not result in an empty hash, whereas the second one
143
+ // will result in `{ key: nil }`. In this case what we need to do for the
144
+ // first expression to align is wrap it in parens, as in:
145
+ //
146
+ // hash[:key] = (:value if false)
147
+ if (["assign", "massign"].includes(path.getParentNode().type)) {
148
+ inlineParts = ["("].concat(inlineParts).concat(")");
149
+ }
129
150
 
151
+ const inline = concat(inlineParts);
130
152
  return group(ifBreak(multiline, inline));
131
153
  };
132
154
 
@@ -171,12 +193,31 @@ const printConditional = keyword => (path, { inlineConditionals }, print) => {
171
193
  );
172
194
  }
173
195
 
196
+ const [predicate, statements, addition] = path.getValue().body;
197
+
174
198
  // If there's an additional clause that wasn't matched earlier, we know we
175
199
  // can't go for the inline option.
176
- if (path.getValue().body[2]) {
200
+ if (addition) {
177
201
  return group(printWithAddition(keyword, path, print, { breaking: true }));
178
202
  }
179
203
 
204
+ // If the body of the conditional is empty, then we explicitly have to use the
205
+ // block form.
206
+ if (statements.type === "stmts" && statements.body[0].type === "void_stmt") {
207
+ return concat([
208
+ `${keyword} `,
209
+ align(keyword.length + 1, path.call(print, "body", 0)),
210
+ concat([hardline, "end"])
211
+ ]);
212
+ }
213
+
214
+ // If the predicate of the conditional contains an assignment, then we can't
215
+ // know for sure that it doesn't impact the body of the conditional, so we
216
+ // have to default to the block form.
217
+ if (containsAssignment(predicate)) {
218
+ return makeSingleBlockForm(keyword, path, print);
219
+ }
220
+
180
221
  return printSingle(keyword)(path, { inlineConditionals }, print);
181
222
  };
182
223
 
data/src/nodes/hashes.js CHANGED
@@ -19,6 +19,21 @@ const nodeDive = (node, steps) => {
19
19
  return current;
20
20
  };
21
21
 
22
+ // When attempting to convert a hash rocket into a hash label, you need to take
23
+ // care because only certain patterns are allowed. Ruby source says that they
24
+ // have to match keyword arguments to methods, but don't specify what that is.
25
+ // After some experimentation, it looks like it's:
26
+ //
27
+ // * Starts with a letter (either case) or an underscore
28
+ // * Does not end in equal
29
+ //
30
+ // This function represents that check, as it determines if it can convert the
31
+ // symbol node into a hash label.
32
+ const isValidHashLabel = symbolLiteral => {
33
+ const label = symbolLiteral.body[0].body[0].body;
34
+ return label.match(/^[_A-Za-z]/) && !label.endsWith("=");
35
+ };
36
+
22
37
  const makeLabel = (path, { preferHashLabels }, print, steps) => {
23
38
  const labelNode = nodeDive(path.getValue(), steps);
24
39
  const labelDoc = path.call.apply(path, [print].concat(steps));
@@ -30,11 +45,7 @@ const makeLabel = (path, { preferHashLabels }, print, steps) => {
30
45
  }
31
46
  return `:${labelDoc.slice(0, labelDoc.length - 1)} =>`;
32
47
  case "symbol_literal": {
33
- // You can have a symbol literal as a key in a hash that ends with an =
34
- // character, which breaks when you use hash labels.
35
- const endsInEquals = labelNode.body[0].body[0].body.endsWith("=");
36
-
37
- if (preferHashLabels && labelNode.body.length === 1 && !endsInEquals) {
48
+ if (preferHashLabels && isValidHashLabel(labelNode)) {
38
49
  const symbolSteps = steps.concat("body", 0, "body", 0);
39
50
 
40
51
  return concat([
data/src/nodes/loops.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const {
2
+ align,
2
3
  concat,
3
4
  group,
4
5
  hardline,
@@ -6,13 +7,33 @@ const {
6
7
  indent,
7
8
  softline
8
9
  } = require("../prettier");
10
+ const { containsAssignment } = require("../utils");
9
11
 
10
12
  const printLoop = (keyword, modifier) => (path, { inlineLoops }, print) => {
11
- const inlineLoop = concat([
13
+ let inlineParts = [
12
14
  path.call(print, "body", 1),
13
15
  ` ${keyword} `,
14
16
  path.call(print, "body", 0)
15
- ]);
17
+ ];
18
+
19
+ // If the return value of this loop expression is being assigned to anything
20
+ // besides a local variable then we can't inline the entire expression
21
+ // without wrapping it in parentheses. This is because the following
22
+ // expressions have different semantic meaning:
23
+ //
24
+ // hash[:key] = break :value while false
25
+ // hash[:key] = while false do break :value end
26
+ //
27
+ // The first one will not result in an empty hash, whereas the second one
28
+ // will result in `{ key: nil }`. In this case what we need to do for the
29
+ // first expression to align is wrap it in parens, as in:
30
+ //
31
+ // hash[:key] = (break :value while false)
32
+ if (["assign", "massign"].includes(path.getParentNode().type)) {
33
+ inlineParts = ["("].concat(inlineParts).concat(")");
34
+ }
35
+
36
+ const inlineLoop = concat(inlineParts);
16
37
 
17
38
  // If we're in the modifier form and we're modifying a `begin`, then this is a
18
39
  // special case where we need to explicitly use the modifier form because
@@ -28,12 +49,19 @@ const printLoop = (keyword, modifier) => (path, { inlineLoops }, print) => {
28
49
  }
29
50
 
30
51
  const blockLoop = concat([
31
- concat([`${keyword} `, path.call(print, "body", 0)]),
52
+ concat([
53
+ `${keyword} `,
54
+ align(keyword.length + 1, path.call(print, "body", 0))
55
+ ]),
32
56
  indent(concat([softline, path.call(print, "body", 1)])),
33
57
  concat([softline, "end"])
34
58
  ]);
35
59
 
36
- if (!inlineLoops) {
60
+ // If we're disallowing inline loops or if the predicate of the loop contains
61
+ // an assignment (in which case we can't know for certain that that
62
+ // assignment doesn't impact the statements inside the loop) then we can't
63
+ // use the modifier form and we must use the block form.
64
+ if (!inlineLoops || containsAssignment(path.getValue().body[0])) {
37
65
  return blockLoop;
38
66
  }
39
67
 
data/src/nodes/strings.js CHANGED
@@ -3,7 +3,6 @@ const {
3
3
  group,
4
4
  hardline,
5
5
  indent,
6
- join,
7
6
  literalline,
8
7
  softline
9
8
  } = require("../prettier");
@@ -96,14 +95,20 @@ module.exports = {
96
95
  ])
97
96
  ),
98
97
  string_dvar: surround("#{", "}"),
99
- string_embexpr: (path, opts, print) =>
100
- group(
101
- concat([
102
- "#{",
103
- indent(concat([softline, path.call(print, "body", 0)])),
104
- concat([softline, "}"])
105
- ])
106
- ),
98
+ string_embexpr: (path, opts, print) => {
99
+ const parts = path.call(print, "body", 0);
100
+
101
+ // If the interpolated expression is inside of an xstring literal (a string
102
+ // that gets sent to the command line) then we don't want to automatically
103
+ // indent, as this can lead to some very odd looking expressions
104
+ if (path.getParentNode().type === "xstring") {
105
+ return concat(["#{", parts, "}"]);
106
+ }
107
+
108
+ return group(
109
+ concat(["#{", indent(concat([softline, parts])), concat([softline, "}"])])
110
+ );
111
+ },
107
112
  string_literal: (path, { preferSingleQuotes }, print) => {
108
113
  const string = path.getValue().body[0];
109
114
 
@@ -142,21 +147,6 @@ module.exports = {
142
147
  xstring_literal: (path, opts, print) => {
143
148
  const parts = path.call(print, "body", 0);
144
149
 
145
- if (typeof parts[0] === "string") {
146
- parts[0] = parts[0].replace(/^\s+/, "");
147
- }
148
-
149
- const lastIndex = parts.length - 1;
150
- if (typeof parts[lastIndex] === "string") {
151
- parts[lastIndex] = parts[lastIndex].replace(/\s+$/, "");
152
- }
153
-
154
- return group(
155
- concat([
156
- "`",
157
- indent(concat([softline, join(softline, parts)])),
158
- concat([softline, "`"])
159
- ])
160
- );
150
+ return concat(["`"].concat(parts).concat("`"));
161
151
  }
162
152
  };
data/src/ripper.rb CHANGED
@@ -340,7 +340,8 @@ class RipperJS < Ripper
340
340
  (SCANNER_EVENTS - defined).each do |event|
341
341
  define_method(:"on_#{event}") do |body|
342
342
  super(body).tap do |node|
343
- node.merge!(char_start: char_pos, char_end: char_pos + body.size)
343
+ char_end = char_pos + (body ? body.size : 0)
344
+ node.merge!(char_start: char_pos, char_end: char_end)
344
345
 
345
346
  scanner_events << node
346
347
  end
@@ -678,7 +679,7 @@ class RipperJS < Ripper
678
679
 
679
680
  super(ident).tap do |node|
680
681
  if !@access_controls.include?(ident[:body]) ||
681
- ident[:body] != lines[lineno - 1].strip
682
+ ident[:body] != lines[lineno - 1].strip
682
683
  next
683
684
  end
684
685
 
@@ -713,7 +714,7 @@ class RipperJS < Ripper
713
714
  stmts, *other_parts = bodystmt[:body]
714
715
 
715
716
  if !other_parts.any? && stmts[:body].length == 1 &&
716
- stmts.dig(:body, 0, :type) == :begin
717
+ stmts.dig(:body, 0, :type) == :begin
717
718
  def_bodystmt = stmts.dig(:body, 0, :body, 0)
718
719
  end
719
720
 
data/src/utils.js CHANGED
@@ -8,6 +8,12 @@ const {
8
8
 
9
9
  const concatBody = (path, opts, print) => concat(path.map(print, "body"));
10
10
 
11
+ // If the node is a type of assignment or if the node is a paren and nested
12
+ // inside that paren is a node that is a type of assignment.
13
+ const containsAssignment = node =>
14
+ ["assign", "massign"].includes(node.type) ||
15
+ (node.type === "paren" && node.body[0].body.some(containsAssignment));
16
+
11
17
  const docLength = doc => {
12
18
  if (doc.length) {
13
19
  return doc.length;
@@ -135,6 +141,7 @@ const surround = (left, right) => (path, opts, print) =>
135
141
 
136
142
  module.exports = {
137
143
  concatBody,
144
+ containsAssignment,
138
145
  docLength,
139
146
  empty,
140
147
  first,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prettier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.15.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Deisz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-08-06 00:00:00.000000000 Z
11
+ date: 2019-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -126,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
126
  - !ruby/object:Gem::Version
127
127
  version: '0'
128
128
  requirements: []
129
- rubygems_version: 3.0.3
129
+ rubygems_version: 3.0.6
130
130
  signing_key:
131
131
  specification_version: 4
132
132
  summary: prettier plugin for the Ruby programming language