prettier 0.22.0 → 1.1.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.
@@ -28,7 +28,9 @@ function printMethod(offset) {
28
28
 
29
29
  // If the body is empty, we can replace with a ;
30
30
  const stmts = body.body[0].body;
31
+
31
32
  if (
33
+ !body.body.slice(1).some((node) => node) &&
32
34
  stmts.length === 1 &&
33
35
  stmts[0].type === "void_stmt" &&
34
36
  !stmts[0].comments
@@ -1,23 +1,34 @@
1
1
  const { concat, group, indent, line, softline } = require("../prettier");
2
+ const { noIndent } = require("../utils");
2
3
 
3
4
  function printBinary(path, opts, print) {
4
- const operator = path.getValue().body[1];
5
- const useNoSpace = operator === "**";
5
+ const [_leftNode, operator, rightNode] = path.getValue().body;
6
+ const space = operator === "**" ? "" : " ";
7
+
8
+ if (noIndent.includes(rightNode.type)) {
9
+ return group(
10
+ concat([
11
+ group(path.call(print, "body", 0)),
12
+ space,
13
+ operator,
14
+ space,
15
+ group(path.call(print, "body", 2))
16
+ ])
17
+ );
18
+ }
6
19
 
7
20
  return group(
8
21
  concat([
9
22
  group(path.call(print, "body", 0)),
10
- indent(
11
- concat([
12
- useNoSpace ? "" : " ",
13
- group(
14
- concat([
15
- operator,
16
- useNoSpace ? softline : line,
17
- path.call(print, "body", 2)
18
- ])
19
- )
20
- ])
23
+ space,
24
+ group(
25
+ indent(
26
+ concat([
27
+ operator,
28
+ space === "" ? softline : line,
29
+ path.call(print, "body", 2)
30
+ ])
31
+ )
21
32
  )
22
33
  ])
23
34
  );
@@ -1,4 +1,4 @@
1
- const { concat, group, join, line } = require("../prettier");
1
+ const { concat, group, join, indent, line, softline } = require("../prettier");
2
2
  const { literal } = require("../utils");
3
3
 
4
4
  function printRestParam(symbol) {
@@ -64,6 +64,8 @@ function printParams(path, opts, print) {
64
64
  parts.push(path.call(print, "body", 6));
65
65
  }
66
66
 
67
+ const contents = [join(concat([",", line]), parts)];
68
+
67
69
  // You can put an extra comma at the end of block args between pipes to
68
70
  // change what it does. Below is the difference:
69
71
  //
@@ -73,9 +75,19 @@ function printParams(path, opts, print) {
73
75
  // In ruby 2.5, the excessed comma is indicated by having a 0 in the rest
74
76
  // param position. In ruby 2.6+ it's indicated by having an "excessed_comma"
75
77
  // node in the rest position. Seems odd, but it's true.
76
- const comma = rest === 0 || (rest && rest.type === "excessed_comma");
78
+ if (rest === 0 || (rest && rest.type === "excessed_comma")) {
79
+ contents.push(",");
80
+ }
81
+
82
+ // If the parent node is a paren then we skipped printing the parentheses so
83
+ // that we could handle them here and get nicer formatting.
84
+ if (["lambda", "paren"].includes(path.getParentNode().type)) {
85
+ return group(
86
+ concat(["(", indent(concat([softline].concat(contents))), softline, ")"])
87
+ );
88
+ }
77
89
 
78
- return group(concat([join(concat([",", line]), parts), comma ? "," : ""]));
90
+ return group(concat(contents));
79
91
  }
80
92
 
81
93
  module.exports = {
@@ -1,4 +1,27 @@
1
1
  const { concat } = require("../prettier");
2
+ const { hasAncestor } = require("../utils");
3
+
4
+ function hasContent(node, pattern) {
5
+ return node.body.some(
6
+ (child) => child.type === "@tstring_content" && pattern.test(child.body)
7
+ );
8
+ }
9
+
10
+ // If the first part of this regex is plain string content, we have a space
11
+ // or an =, and we're contained within a command or command_call node, then we
12
+ // want to use braces because otherwise we could end up with an ambiguous
13
+ // operator, e.g. foo / bar/ or foo /=bar/
14
+ function forwardSlashIsAmbiguous(path) {
15
+ const node = path.getValue();
16
+ const firstChildNode = node.body[0];
17
+
18
+ return (
19
+ firstChildNode &&
20
+ firstChildNode.type === "@tstring_content" &&
21
+ [" ", "="].includes(firstChildNode.body[0]) &&
22
+ hasAncestor(path, ["command", "command_call"])
23
+ );
24
+ }
2
25
 
3
26
  // This function is responsible for printing out regexp_literal nodes. They can
4
27
  // either use the special %r literal syntax or they can use forward slashes. At
@@ -8,18 +31,24 @@ const { concat } = require("../prettier");
8
31
  // We favor the use of forward slashes unless the regex contains a forward slash
9
32
  // itself. In that case we switch over to using %r with braces.
10
33
  function printRegexpLiteral(path, opts, print) {
11
- const { ending } = path.getValue();
12
- const contents = path.map(print, "body");
34
+ const node = path.getValue();
35
+ const docs = path.map(print, "body");
13
36
 
14
- const useBraces = contents.some(
15
- (content) => typeof content === "string" && content.includes("/")
16
- );
37
+ // We should use braces if using a forward slash would be ambiguous in the
38
+ // current context or if there's a forward slash in the content of the regexp.
39
+ const useBraces = forwardSlashIsAmbiguous(path) || hasContent(node, /\//);
17
40
 
18
- const parts = [useBraces ? "%r{" : "/"]
19
- .concat(contents)
20
- .concat([useBraces ? "}" : "/", ending.slice(1)]);
41
+ // If we should be using braces but we have braces in the body of the regexp,
42
+ // then we're just going to resort to using whatever the original content was.
43
+ if (useBraces && hasContent(node, /[{}]/)) {
44
+ return concat([node.beging].concat(docs).concat(node.ending));
45
+ }
21
46
 
22
- return concat(parts);
47
+ return concat(
48
+ [useBraces ? "%r{" : "/"]
49
+ .concat(docs)
50
+ .concat(useBraces ? "}" : "/", node.ending.slice(1))
51
+ );
23
52
  }
24
53
 
25
54
  module.exports = {
@@ -20,8 +20,8 @@ function printBegin(path, opts, print) {
20
20
 
21
21
  function printEnsure(path, opts, print) {
22
22
  return concat([
23
- "ensure",
24
- indent(concat([hardline, concat(path.map(print, "body"))]))
23
+ path.call(print, "body", 0),
24
+ indent(concat([hardline, path.call(print, "body", 1)]))
25
25
  ]);
26
26
  }
27
27
 
@@ -12,58 +12,78 @@ const {
12
12
  trim
13
13
  } = require("../prettier");
14
14
 
15
- module.exports = {
16
- "@__end__": (path, _opts, _print) => {
17
- const { body } = path.getValue();
18
- return concat([trim, "__END__", literalline, body]);
19
- },
20
- bodystmt: (path, opts, print) => {
21
- const [_statements, rescue, elseClause, ensure] = path.getValue().body;
22
- const parts = [path.call(print, "body", 0)];
15
+ function printBodyStmt(path, opts, print) {
16
+ const [stmts, rescue, elseClause, ensure] = path.getValue().body;
17
+ const parts = [];
18
+
19
+ if (
20
+ stmts.body.length > 1 ||
21
+ stmts.body[0].type != "void_stmt" ||
22
+ stmts.body[0].comments
23
+ ) {
24
+ parts.push(path.call(print, "body", 0));
25
+ }
23
26
 
24
- if (rescue) {
25
- parts.push(dedent(concat([hardline, path.call(print, "body", 1)])));
26
- }
27
+ if (rescue) {
28
+ parts.push(dedent(concat([hardline, path.call(print, "body", 1)])));
29
+ }
27
30
 
28
- if (elseClause) {
29
- // Before Ruby 2.6, this piece of bodystmt was an explicit "else" node
30
- const stmts =
31
- elseClause.type === "else"
32
- ? path.call(print, "body", 2, "body", 0)
33
- : path.call(print, "body", 2);
31
+ if (elseClause) {
32
+ // Before Ruby 2.6, this piece of bodystmt was an explicit "else" node
33
+ const stmts =
34
+ elseClause.type === "else"
35
+ ? path.call(print, "body", 2, "body", 0)
36
+ : path.call(print, "body", 2);
34
37
 
35
- parts.push(concat([dedent(concat([hardline, "else"])), hardline, stmts]));
36
- }
38
+ parts.push(concat([dedent(concat([hardline, "else"])), hardline, stmts]));
39
+ }
37
40
 
38
- if (ensure) {
39
- parts.push(dedent(concat([hardline, path.call(print, "body", 3)])));
40
- }
41
+ if (ensure) {
42
+ parts.push(dedent(concat([hardline, path.call(print, "body", 3)])));
43
+ }
41
44
 
42
- return group(concat(parts));
43
- },
44
- paren: (path, opts, print) => {
45
- if (!path.getValue().body[0]) {
46
- return "()";
47
- }
45
+ return group(concat(parts));
46
+ }
48
47
 
49
- let content = path.call(print, "body", 0);
48
+ const argNodeTypes = ["args", "args_add_star", "args_add_block"];
50
49
 
51
- if (
52
- ["args", "args_add_star", "args_add_block"].includes(
53
- path.getValue().body[0].type
54
- )
55
- ) {
56
- content = join(concat([",", line]), content);
57
- }
50
+ function printParen(path, opts, print) {
51
+ const contentNode = path.getValue().body[0];
52
+
53
+ if (!contentNode) {
54
+ return "()";
55
+ }
56
+
57
+ let contentDoc = path.call(print, "body", 0);
58
+
59
+ // If the content is params, we're going to let it handle its own parentheses
60
+ // so that it breaks nicely.
61
+ if (contentNode.type === "params") {
62
+ return contentDoc;
63
+ }
58
64
 
59
- return group(
60
- concat([
61
- "(",
62
- indent(concat([softline, content])),
63
- concat([softline, ")"])
64
- ])
65
- );
65
+ // If we have an arg type node as the contents, then it's going to return an
66
+ // array, so we need to explicitly join that content here.
67
+ if (argNodeTypes.includes(contentNode.type)) {
68
+ contentDoc = join(concat([",", line]), contentDoc);
69
+ }
70
+
71
+ return group(
72
+ concat([
73
+ "(",
74
+ indent(concat([softline, contentDoc])),
75
+ concat([softline, ")"])
76
+ ])
77
+ );
78
+ }
79
+
80
+ module.exports = {
81
+ "@__end__": (path, _opts, _print) => {
82
+ const { body } = path.getValue();
83
+ return concat([trim, "__END__", literalline, body]);
66
84
  },
85
+ bodystmt: printBodyStmt,
86
+ paren: printParen,
67
87
  program: (path, opts, print) =>
68
88
  concat([join(hardline, path.map(print, "body")), hardline]),
69
89
  stmts: (path, opts, print) => {
@@ -78,8 +98,8 @@ module.exports = {
78
98
  stmts[0].comments
79
99
  ) {
80
100
  const comments = path.map(
81
- (commentPath, index) => {
82
- stmts[0].comments[index].printed = true;
101
+ (commentPath) => {
102
+ commentPath.getValue().printed = true;
83
103
  return opts.printer.printComment(commentPath);
84
104
  },
85
105
  "body",
@@ -32,7 +32,7 @@ function isSingleQuotable(node) {
32
32
  const quotePattern = new RegExp("\\\\([\\s\\S])|(['\"])", "g");
33
33
 
34
34
  function normalizeQuotes(content, enclosingQuote, originalQuote) {
35
- const replaceOther = ["'", '"'].includes(originalQuote);
35
+ const replaceOther = originalQuote === '"';
36
36
  const otherQuote = enclosingQuote === '"' ? "'" : '"';
37
37
 
38
38
  // Escape and unescape single and double quotes as needed to be able to
@@ -66,7 +66,7 @@ function getClosingQuote(quote) {
66
66
  return quote;
67
67
  }
68
68
 
69
- const boundary = /%q?(.)/.exec(quote)[1];
69
+ const boundary = /%[Qq]?(.)/.exec(quote)[1];
70
70
  if (boundary in quotePairs) {
71
71
  return quotePairs[boundary];
72
72
  }
@@ -77,14 +77,14 @@ function getClosingQuote(quote) {
77
77
  // Prints a @CHAR node. @CHAR nodes are special character strings that usually
78
78
  // are strings of length 1. If they're any longer than we'll try to apply the
79
79
  // correct quotes.
80
- function printChar(path, { preferSingleQuotes }, _print) {
80
+ function printChar(path, { rubySingleQuote }, _print) {
81
81
  const { body } = path.getValue();
82
82
 
83
83
  if (body.length !== 2) {
84
84
  return body;
85
85
  }
86
86
 
87
- const quote = preferSingleQuotes ? "'" : '"';
87
+ const quote = rubySingleQuote ? "'" : '"';
88
88
  return concat([quote, body.slice(1), quote]);
89
89
  }
90
90
 
@@ -107,13 +107,13 @@ function printStringDVar(path, opts, print) {
107
107
  // wishes of the user with regards to single versus double quotes, but if the
108
108
  // string contains any escape expressions then it will just keep the original
109
109
  // quotes.
110
- function printStringLiteral(path, { preferSingleQuotes }, print) {
110
+ function printStringLiteral(path, { rubySingleQuote }, print) {
111
111
  const node = path.getValue();
112
112
 
113
113
  // If the string is empty, it will not have any parts, so just print out the
114
114
  // quotes corresponding to the config
115
115
  if (node.body.length === 0) {
116
- return preferSingleQuotes ? "''" : '""';
116
+ return rubySingleQuote ? "''" : '""';
117
117
  }
118
118
 
119
119
  // Determine the quote that should enclose the new string
@@ -121,7 +121,7 @@ function printStringLiteral(path, { preferSingleQuotes }, print) {
121
121
  if (isQuoteLocked(node)) {
122
122
  quote = node.quote;
123
123
  } else {
124
- quote = preferSingleQuotes && isSingleQuotable(node) ? "'" : '"';
124
+ quote = rubySingleQuote && isSingleQuotable(node) ? "'" : '"';
125
125
  }
126
126
 
127
127
  const parts = node.body.map((part, index) => {
@@ -167,10 +167,11 @@ module.exports = {
167
167
  string_embexpr: (path, opts, print) => {
168
168
  const parts = path.call(print, "body", 0);
169
169
 
170
- // If the interpolated expression is inside of an xstring literal (a string
171
- // that gets sent to the command line) then we don't want to automatically
172
- // indent, as this can lead to some very odd looking expressions
173
- if (path.getParentNode().type === "xstring_literal") {
170
+ // If the interpolated expression is inside of a heredoc or an xstring
171
+ // literal (a string that gets sent to the command line) then we don't want
172
+ // to automatically indent, as this can lead to some very odd looking
173
+ // expressions
174
+ if (["heredoc", "xstring_literal"].includes(path.getParentNode().type)) {
174
175
  return concat(["#{", parts, "}"]);
175
176
  }
176
177
 
@@ -3,18 +3,33 @@ const path = require("path");
3
3
 
4
4
  // In order to properly parse ruby code, we need to tell the ruby process to
5
5
  // parse using UTF-8. Unfortunately, the way that you accomplish this looks
6
- // differently depending on your platform. This object below represents all of
7
- // the possible values of process.platform per:
8
- // https://nodejs.org/api/process.html#process_process_platform
9
- const LANG = {
10
- aix: "C.UTF-8",
11
- darwin: "en_US.UTF-8",
12
- freebsd: "C.UTF-8",
13
- linux: "C.UTF-8",
14
- openbsd: "C.UTF-8",
15
- sunos: "C.UTF-8",
16
- win32: ".UTF-8"
17
- }[process.platform];
6
+ // differently depending on your platform.
7
+ const LANG = (() => {
8
+ const { env, platform } = process;
9
+ const envValue = env.LC_ALL || env.LC_CTYPE || env.LANG;
10
+
11
+ // If an env var is set for the locale that already includes UTF-8 in the
12
+ // name, then assume we can go with that.
13
+ if (envValue && envValue.includes("UTF-8")) {
14
+ return envValue;
15
+ }
16
+
17
+ // Otherwise, we're going to guess which encoding to use based on the system.
18
+ // This is probably not the best approach in the world, as you could be on
19
+ // linux and not have C.UTF-8, but in that case you're probably passing an env
20
+ // var for it. This object below represents all of the possible values of
21
+ // process.platform per:
22
+ // https://nodejs.org/api/process.html#process_process_platform
23
+ return {
24
+ aix: "C.UTF-8",
25
+ darwin: "en_US.UTF-8",
26
+ freebsd: "C.UTF-8",
27
+ linux: "C.UTF-8",
28
+ openbsd: "C.UTF-8",
29
+ sunos: "C.UTF-8",
30
+ win32: ".UTF-8"
31
+ }[platform];
32
+ })();
18
33
 
19
34
  // This function is responsible for taking an input string of text and returning
20
35
  // to prettier a JavaScript object that is the equivalent AST that represents
@@ -2,7 +2,7 @@
2
2
 
3
3
  # We implement our own version checking here instead of using Gem::Version so
4
4
  # that we can use the --disable-gems flag.
5
- RUBY_MAJOR, RUBY_MINOR, * = RUBY_VERSION.split('.').map(&:to_i)
5
+ RUBY_MAJOR, RUBY_MINOR, RUBY_PATCH, * = RUBY_VERSION.split('.').map(&:to_i)
6
6
 
7
7
  if (RUBY_MAJOR < 2) || ((RUBY_MAJOR == 2) && (RUBY_MINOR < 5))
8
8
  warn(
@@ -115,15 +115,14 @@ class Prettier::Parser < Ripper
115
115
  # This will break everything, so we need to force the encoding back into
116
116
  # UTF-8 so that the JSON library won't break.
117
117
  def on_comment(value)
118
- @comments <<
119
- {
120
- type: :@comment,
121
- value: value[1..-1].chomp.force_encoding('UTF-8'),
122
- start: lineno,
123
- end: lineno,
124
- char_start: char_pos,
125
- char_end: char_pos + value.length - 1
126
- }
118
+ @comments << {
119
+ type: :@comment,
120
+ value: value[1..-1].chomp.force_encoding('UTF-8'),
121
+ start: lineno,
122
+ end: lineno,
123
+ char_start: char_pos,
124
+ char_end: char_pos + value.length - 1
125
+ }
127
126
  end
128
127
 
129
128
  # ignored_nl is a special kind of scanner event that passes nil as the value,
@@ -187,11 +186,14 @@ class Prettier::Parser < Ripper
187
186
  beging = find_scanner_event(:@lbrace)
188
187
  ending = find_scanner_event(:@rbrace)
189
188
 
190
- stmts.bind(beging[:char_end], ending[:char_start])
189
+ stmts.bind(
190
+ find_next_statement_start(beging[:char_end]),
191
+ ending[:char_start]
192
+ )
191
193
 
192
194
  find_scanner_event(:@kw, 'BEGIN').merge!(
193
195
  type: :BEGIN,
194
- body: [stmts],
196
+ body: [beging, stmts],
195
197
  end: ending[:end],
196
198
  char_end: ending[:char_end]
197
199
  )
@@ -211,10 +213,16 @@ class Prettier::Parser < Ripper
211
213
  beging = find_scanner_event(:@lbrace)
212
214
  ending = find_scanner_event(:@rbrace)
213
215
 
214
- stmts.bind(beging[:char_end], ending[:char_start])
216
+ stmts.bind(
217
+ find_next_statement_start(beging[:char_end]),
218
+ ending[:char_start]
219
+ )
215
220
 
216
221
  find_scanner_event(:@kw, 'END').merge!(
217
- type: :END, body: [stmts], end: ending[:end], char_end: ending[:char_end]
222
+ type: :END,
223
+ body: [beging, stmts],
224
+ end: ending[:end],
225
+ char_end: ending[:char_end]
218
226
  )
219
227
  end
220
228
 
@@ -306,7 +314,9 @@ class Prettier::Parser < Ripper
306
314
  arg.merge(type: :args, body: [arg])
307
315
  else
308
316
  args.merge!(
309
- body: args[:body] << arg, end: arg[:end], char_end: arg[:char_end]
317
+ body: args[:body] << arg,
318
+ end: arg[:end],
319
+ char_end: arg[:char_end]
310
320
  )
311
321
  end
312
322
  end
@@ -352,7 +362,12 @@ class Prettier::Parser < Ripper
352
362
  # method inside a set of parentheses.
353
363
  def on_arg_paren(args)
354
364
  beging = find_scanner_event(:@lparen)
355
- ending = find_scanner_event(:@rparen)
365
+ rparen = find_scanner_event(:@rparen)
366
+
367
+ # If the arguments exceed the ending of the parentheses, then we know we
368
+ # have a heredoc in the arguments, and we need to use the bounds of the
369
+ # arguments to determine how large the arg_paren is.
370
+ ending = (args && args[:end] > rparen[:end]) ? args : rparen
356
371
 
357
372
  {
358
373
  type: :arg_paren,
@@ -553,13 +568,15 @@ class Prettier::Parser < Ripper
553
568
 
554
569
  # Here we're going to determine the bounds for the stmts
555
570
  consequent = parts[1..-1].compact.first
556
- self[:body][0].bind(char_start,
557
- consequent ? consequent[:char_start] : char_end)
571
+ self[:body][0].bind(
572
+ char_start,
573
+ consequent ? consequent[:char_start] : char_end
574
+ )
558
575
 
559
576
  # Next we're going to determine the rescue clause if there is one
560
577
  if parts[1]
561
578
  consequent = parts[2..-1].compact.first
562
- self[:body][1].bind(consequent ? consequent[:char_start] : char_end)
579
+ self[:body][1].bind_end(consequent ? consequent[:char_start] : char_end)
563
580
  end
564
581
  end
565
582
  end
@@ -601,7 +618,16 @@ class Prettier::Parser < Ripper
601
618
  # accepts as an argument an args or args_add_block event that contains all
602
619
  # of the arguments being passed to the break.
603
620
  def on_break(args_add_block)
604
- find_scanner_event(:@kw, 'break').merge!(
621
+ beging = find_scanner_event(:@kw, 'break')
622
+
623
+ # You can hit this if you are passing no arguments to break but it has a
624
+ # comment right after it. In that case we can just use the location
625
+ # information straight from the keyword.
626
+ if args_add_block[:type] == :args
627
+ return beging.merge!(type: :break, body: [args_add_block])
628
+ end
629
+
630
+ beging.merge!(
605
631
  type: :break,
606
632
  body: [args_add_block],
607
633
  end: args_add_block[:end],
@@ -621,6 +647,10 @@ class Prettier::Parser < Ripper
621
647
  # foo.(1, 2, 3)
622
648
  #
623
649
  def on_call(receiver, oper, sending)
650
+ # Make sure we take the operator out of the scanner events so that it
651
+ # doesn't get confused for a unary operator later.
652
+ scanner_events.delete(oper)
653
+
624
654
  ending = sending
625
655
 
626
656
  if sending == :call
@@ -653,6 +683,27 @@ class Prettier::Parser < Ripper
653
683
  )
654
684
  end
655
685
 
686
+ # Finds the next position in the source string that begins a statement. This
687
+ # is used to bind statements lists and make sure they don't include a
688
+ # preceding comment. For example, we want the following comment to be attached
689
+ # to the class node and not the statement node:
690
+ #
691
+ # class Foo # :nodoc:
692
+ # ...
693
+ # end
694
+ #
695
+ # By finding the next non-space character, we can make sure that the bounds of
696
+ # the statement list are correct.
697
+ def find_next_statement_start(position)
698
+ remaining = source[position..-1]
699
+
700
+ if remaining.sub(/\A +/, '')[0] == '#'
701
+ return position + remaining.index("\n")
702
+ end
703
+
704
+ position
705
+ end
706
+
656
707
  # class is a parser event that represents defining a class. It accepts as
657
708
  # arguments the name of the class, the optional name of the superclass,
658
709
  # and the bodystmt event that represents the statements evaluated within
@@ -661,7 +712,10 @@ class Prettier::Parser < Ripper
661
712
  beging = find_scanner_event(:@kw, 'class')
662
713
  ending = find_scanner_event(:@kw, 'end')
663
714
 
664
- bodystmt.bind((superclass || const)[:char_end], ending[:char_start])
715
+ bodystmt.bind(
716
+ find_next_statement_start((superclass || const)[:char_end]),
717
+ ending[:char_start]
718
+ )
665
719
 
666
720
  {
667
721
  type: :class,
@@ -761,6 +815,11 @@ class Prettier::Parser < Ripper
761
815
  # └> ident
762
816
  #
763
817
  def on_def(ident, params, bodystmt)
818
+ # Make sure to delete this scanner event in case you're defining something
819
+ # like def class which would lead to this being a kw and causing all kinds
820
+ # of trouble
821
+ scanner_events.delete(ident)
822
+
764
823
  if params[:type] == :params && !params[:body].any?
765
824
  location = ident[:char_end]
766
825
  params.merge!(char_start: location, char_end: location)
@@ -769,7 +828,10 @@ class Prettier::Parser < Ripper
769
828
  beging = find_scanner_event(:@kw, 'def')
770
829
  ending = find_scanner_event(:@kw, 'end')
771
830
 
772
- bodystmt.bind(params[:char_end], ending[:char_start])
831
+ bodystmt.bind(
832
+ find_next_statement_start(params[:char_end]),
833
+ ending[:char_start]
834
+ )
773
835
 
774
836
  {
775
837
  type: :def,
@@ -796,6 +858,11 @@ class Prettier::Parser < Ripper
796
858
  # └> target
797
859
  #
798
860
  def on_defs(target, oper, ident, params, bodystmt)
861
+ # Make sure to delete this scanner event in case you're defining something
862
+ # like def class which would lead to this being a kw and causing all kinds
863
+ # of trouble
864
+ scanner_events.delete(ident)
865
+
799
866
  if params[:type] == :params && !params[:body].any?
800
867
  location = ident[:char_end]
801
868
  params.merge!(char_start: location, char_end: location)
@@ -804,7 +871,10 @@ class Prettier::Parser < Ripper
804
871
  beging = find_scanner_event(:@kw, 'def')
805
872
  ending = find_scanner_event(:@kw, 'end')
806
873
 
807
- bodystmt.bind(params[:char_end], ending[:char_start])
874
+ bodystmt.bind(
875
+ find_next_statement_start(params[:char_end]),
876
+ ending[:char_start]
877
+ )
808
878
 
809
879
  {
810
880
  type: :defs,
@@ -998,7 +1068,10 @@ class Prettier::Parser < Ripper
998
1068
  # event, so here we'll initialize the current embdoc.
999
1069
  def on_embdoc_beg(value)
1000
1070
  @embdoc = {
1001
- type: :@embdoc, value: value, start: lineno, char_start: char_pos
1071
+ type: :@embdoc,
1072
+ value: value,
1073
+ start: lineno,
1074
+ char_start: char_pos
1002
1075
  }
1003
1076
  end
1004
1077
 
@@ -1029,13 +1102,23 @@ class Prettier::Parser < Ripper
1029
1102
  # and its subsequent statements.
1030
1103
  def on_ensure(stmts)
1031
1104
  beging = find_scanner_event(:@kw, 'ensure')
1032
- ending = find_scanner_event(:@kw, 'end')
1033
1105
 
1034
- stmts.bind(beging[:char_end], ending[:char_start])
1106
+ # Specifically not using find_scanner_event here because we don't want to
1107
+ # consume the :@end event, because that would break def..ensure..end chains.
1108
+ index =
1109
+ scanner_events.rindex do |scanner_event|
1110
+ scanner_event[:type] == :@kw && scanner_event[:body] == 'end'
1111
+ end
1112
+
1113
+ ending = scanner_events[index]
1114
+ stmts.bind(
1115
+ find_next_statement_start(beging[:char_end]),
1116
+ ending[:char_start]
1117
+ )
1035
1118
 
1036
1119
  {
1037
1120
  type: :ensure,
1038
- body: [stmts],
1121
+ body: [beging, stmts],
1039
1122
  start: beging[:start],
1040
1123
  char_start: beging[:char_start],
1041
1124
  end: ending[:end],
@@ -1105,7 +1188,8 @@ class Prettier::Parser < Ripper
1105
1188
  # Here we're going to expand out the location information for the assocs
1106
1189
  # node so that it can grab up any remaining comments inside the hash.
1107
1190
  assoclist_from_args.merge!(
1108
- char_start: beging[:char_end], char_end: ending[:char_start]
1191
+ char_start: beging[:char_end],
1192
+ char_end: ending[:char_start]
1109
1193
  )
1110
1194
  end
1111
1195
 
@@ -1126,21 +1210,28 @@ class Prettier::Parser < Ripper
1126
1210
  # prettier parser, we'll later attempt to print it using that parser and
1127
1211
  # printer through our embed function.
1128
1212
  def on_heredoc_beg(beging)
1129
- {
1130
- type: :heredoc,
1131
- beging: beging,
1213
+ location = {
1132
1214
  start: lineno,
1133
1215
  end: lineno,
1134
- char_start: char_pos - beging.length + 1,
1135
- char_end: char_pos
1136
- }.tap { |node| @heredocs << node }
1216
+ char_start: char_pos,
1217
+ char_end: char_pos + beging.length + 1
1218
+ }
1219
+
1220
+ # Here we're going to artificially create an extra node type so that if
1221
+ # there are comments after the declaration of a heredoc, they get printed.
1222
+ location
1223
+ .merge(
1224
+ type: :heredoc,
1225
+ beging: location.merge(type: :@heredoc_beg, body: beging)
1226
+ )
1227
+ .tap { |node| @heredocs << node }
1137
1228
  end
1138
1229
 
1139
1230
  # This is a parser event that occurs when you're using a heredoc with a
1140
1231
  # tilde. These are considered `heredoc_dedent` nodes, whereas the hyphen
1141
1232
  # heredocs show up as string literals.
1142
1233
  def on_heredoc_dedent(string, _width)
1143
- @heredocs[-1].merge!(string.slice(:body))
1234
+ @heredocs[-1].merge!(body: string[:body])
1144
1235
  end
1145
1236
 
1146
1237
  # This is a scanner event that represents the end of the heredoc.
@@ -1306,6 +1397,14 @@ class Prettier::Parser < Ripper
1306
1397
  # arguments and parentheses. It accepts as arguments the method being called
1307
1398
  # and the arg_paren event that contains the arguments to the method.
1308
1399
  def on_method_add_arg(fcall, arg_paren)
1400
+ # You can hit this if you are passing no arguments to a method that ends in
1401
+ # a question mark. Because it knows it has to be a method and not a local
1402
+ # variable. In that case we can just use the location information straight
1403
+ # from the fcall.
1404
+ if arg_paren[:type] == :args
1405
+ return fcall.merge(type: :method_add_arg, body: [fcall, arg_paren])
1406
+ end
1407
+
1309
1408
  {
1310
1409
  type: :method_add_arg,
1311
1410
  body: [fcall, arg_paren],
@@ -1352,7 +1451,9 @@ class Prettier::Parser < Ripper
1352
1451
  part.merge(type: :mlhs, body: [part])
1353
1452
  else
1354
1453
  mlhs.merge!(
1355
- body: mlhs[:body] << part, end: part[:end], char_end: part[:char_end]
1454
+ body: mlhs[:body] << part,
1455
+ end: part[:end],
1456
+ char_end: part[:char_end]
1356
1457
  )
1357
1458
  end
1358
1459
  end
@@ -1418,7 +1519,10 @@ class Prettier::Parser < Ripper
1418
1519
  beging = find_scanner_event(:@kw, 'module')
1419
1520
  ending = find_scanner_event(:@kw, 'end')
1420
1521
 
1421
- bodystmt.bind(const[:char_end], ending[:char_start])
1522
+ bodystmt.bind(
1523
+ find_next_statement_start(const[:char_end]),
1524
+ ending[:char_start]
1525
+ )
1422
1526
 
1423
1527
  {
1424
1528
  type: :module,
@@ -1452,7 +1556,9 @@ class Prettier::Parser < Ripper
1452
1556
  part.merge(type: :mrhs, body: [part])
1453
1557
  else
1454
1558
  mrhs.merge!(
1455
- body: mrhs[:body] << part, end: part[:end], char_end: part[:char_end]
1559
+ body: mrhs[:body] << part,
1560
+ end: part[:end],
1561
+ char_end: part[:char_end]
1456
1562
  )
1457
1563
  end
1458
1564
  end
@@ -1553,7 +1659,10 @@ class Prettier::Parser < Ripper
1553
1659
  # some found at the end of the source string.
1554
1660
  def on_program(stmts)
1555
1661
  range = {
1556
- start: 1, end: lines.length, char_start: 0, char_end: source.length
1662
+ start: 1,
1663
+ end: lines.length,
1664
+ char_start: 0,
1665
+ char_end: source.length
1557
1666
  }
1558
1667
 
1559
1668
  stmts[:body] << @__end__ if @__end__
@@ -1610,7 +1719,8 @@ class Prettier::Parser < Ripper
1610
1719
  # expression literal, like /foo/. It can be followed by any number of
1611
1720
  # regexp_add events, which we'll append onto an array body.
1612
1721
  def on_regexp_new
1613
- find_scanner_event(:@regexp_beg).merge!(type: :regexp, body: [])
1722
+ beging = find_scanner_event(:@regexp_beg)
1723
+ beging.merge!(type: :regexp, body: [], beging: beging[:body])
1614
1724
  end
1615
1725
 
1616
1726
  # regexp_add is a parser event that represents a piece of a regular
@@ -1643,17 +1753,17 @@ class Prettier::Parser < Ripper
1643
1753
  # determine its ending. Therefore it relies on its parent bodystmt node to
1644
1754
  # report its ending to it.
1645
1755
  class Rescue < SimpleDelegator
1646
- def bind(char_end)
1756
+ def bind_end(char_end)
1647
1757
  merge!(char_end: char_end)
1648
1758
 
1649
1759
  stmts = self[:body][2]
1650
1760
  consequent = self[:body][3]
1651
1761
 
1652
1762
  if consequent
1653
- consequent.bind(char_end)
1654
- stmts.bind(stmts[:char_start], consequent[:char_start])
1763
+ consequent.bind_end(char_end)
1764
+ stmts.bind_end(consequent[:char_start])
1655
1765
  else
1656
- stmts.bind(stmts[:char_start], char_end)
1766
+ stmts.bind_end(char_end)
1657
1767
  end
1658
1768
  end
1659
1769
  end
@@ -1663,10 +1773,10 @@ class Prettier::Parser < Ripper
1663
1773
  def on_rescue(exceptions, variable, stmts, consequent)
1664
1774
  beging = find_scanner_event(:@kw, 'rescue')
1665
1775
 
1666
- stmts.bind(
1667
- ((exceptions || [])[-1] || variable || beging)[:char_end],
1668
- char_pos
1669
- )
1776
+ last_exception = exceptions.is_a?(Array) ? exceptions[-1] : exceptions
1777
+ last_node = variable || last_exception || beging
1778
+
1779
+ stmts.bind(find_next_statement_start(last_node[:char_end]), char_pos)
1670
1780
 
1671
1781
  Rescue.new(
1672
1782
  beging.merge!(
@@ -1750,7 +1860,10 @@ class Prettier::Parser < Ripper
1750
1860
  beging = find_scanner_event(:@kw, 'class')
1751
1861
  ending = find_scanner_event(:@kw, 'end')
1752
1862
 
1753
- bodystmt.bind(target[:char_end], ending[:char_start])
1863
+ bodystmt.bind(
1864
+ find_next_statement_start(target[:char_end]),
1865
+ ending[:char_start]
1866
+ )
1754
1867
 
1755
1868
  {
1756
1869
  type: :sclass,
@@ -1778,6 +1891,10 @@ class Prettier::Parser < Ripper
1778
1891
  end
1779
1892
  end
1780
1893
 
1894
+ def bind_end(char_end)
1895
+ merge!(char_end: char_end)
1896
+ end
1897
+
1781
1898
  def <<(statement)
1782
1899
  if self[:body].any?
1783
1900
  merge!(statement.slice(:end, :char_end))
@@ -1851,7 +1968,9 @@ class Prettier::Parser < Ripper
1851
1968
  # piece of the string.
1852
1969
  def on_string_add(string, piece)
1853
1970
  string.merge!(
1854
- body: string[:body] << piece, end: piece[:end], char_end: piece[:char_end]
1971
+ body: string[:body] << piece,
1972
+ end: piece[:end],
1973
+ char_end: piece[:char_end]
1855
1974
  )
1856
1975
  end
1857
1976
 
@@ -1948,7 +2067,7 @@ class Prettier::Parser < Ripper
1948
2067
  # symbol node (for most cases) or an ident node (in the case that we're
1949
2068
  # using bare words, as in an alias node like alias foo bar).
1950
2069
  def on_symbol_literal(contents)
1951
- if contents[:type] == :@ident
2070
+ if scanner_events[-1] == contents
1952
2071
  contents.merge(type: :symbol_literal, body: [contents])
1953
2072
  else
1954
2073
  beging = find_scanner_event(:@symbeg)
@@ -2042,7 +2161,18 @@ class Prettier::Parser < Ripper
2042
2161
  paren: paren
2043
2162
  )
2044
2163
  else
2045
- find_scanner_event(:@op).merge!(
2164
+ # Special case instead of using find_scanner_event here. It turns out that
2165
+ # if you have a range that goes from a negative number to a negative
2166
+ # number then you can end up with a .. or a ... that's higher in the
2167
+ # stack. So we need to explicitly disallow those operators.
2168
+ index =
2169
+ scanner_events.rindex do |scanner_event|
2170
+ scanner_event[:type] == :@op &&
2171
+ !%w[.. ...].include?(scanner_event[:body])
2172
+ end
2173
+
2174
+ beging = scanner_events.delete_at(index)
2175
+ beging.merge!(
2046
2176
  type: :unary,
2047
2177
  oper: oper[0],
2048
2178
  body: [value],
@@ -2297,7 +2427,9 @@ class Prettier::Parser < Ripper
2297
2427
  piece.merge(type: :word, body: [piece])
2298
2428
  else
2299
2429
  word.merge!(
2300
- body: word[:body] << piece, end: piece[:end], char_end: piece[:char_end]
2430
+ body: word[:body] << piece,
2431
+ end: piece[:end],
2432
+ char_end: piece[:char_end]
2301
2433
  )
2302
2434
  end
2303
2435
  end
@@ -2335,6 +2467,8 @@ class Prettier::Parser < Ripper
2335
2467
 
2336
2468
  if heredoc && heredoc[:beging][3] = '`'
2337
2469
  heredoc.merge(type: :xstring, body: [])
2470
+ elsif RUBY_MAJOR <= 2 && RUBY_MINOR <= 5 && RUBY_PATCH < 7
2471
+ { type: :xstring, body: [] }
2338
2472
  else
2339
2473
  find_scanner_event(:@backtick).merge!(type: :xstring, body: [])
2340
2474
  end
@@ -2376,7 +2510,9 @@ class Prettier::Parser < Ripper
2376
2510
  else
2377
2511
  ending = find_scanner_event(:@tstring_end)
2378
2512
  xstring.merge!(
2379
- type: :xstring_literal, end: ending[:end], char_end: ending[:char_end]
2513
+ type: :xstring_literal,
2514
+ end: ending[:end],
2515
+ char_end: ending[:char_end]
2380
2516
  )
2381
2517
  end
2382
2518
  end