prettier 0.15.0 → 0.15.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.
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