prettier 0.22.0 → 1.0.0.pre.rc1

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,5 +1,24 @@
1
1
  const { concat } = require("../prettier");
2
2
 
3
+ function isStringContent(node) {
4
+ return node.type === "@tstring_content";
5
+ }
6
+
7
+ function shouldUseBraces(node) {
8
+ const first = node.body[0];
9
+
10
+ // If the first part of this regex is plain string content and we have a
11
+ // space or an =, then we want to use braces because otherwise we could end up
12
+ // with an ambiguous operator, e.g. foo / bar/ or foo /=bar/
13
+ if (first && isStringContent(first) && [" ", "="].includes(first.body[0])) {
14
+ return true;
15
+ }
16
+
17
+ return node.body.some(
18
+ (child) => isStringContent(child) && child.body.includes("/")
19
+ );
20
+ }
21
+
3
22
  // This function is responsible for printing out regexp_literal nodes. They can
4
23
  // either use the special %r literal syntax or they can use forward slashes. At
5
24
  // the end of either of those they can have modifiers like m or x that have
@@ -8,16 +27,12 @@ const { concat } = require("../prettier");
8
27
  // We favor the use of forward slashes unless the regex contains a forward slash
9
28
  // itself. In that case we switch over to using %r with braces.
10
29
  function printRegexpLiteral(path, opts, print) {
11
- const { ending } = path.getValue();
12
- const contents = path.map(print, "body");
13
-
14
- const useBraces = contents.some(
15
- (content) => typeof content === "string" && content.includes("/")
16
- );
30
+ const node = path.getValue();
31
+ const useBraces = shouldUseBraces(node);
17
32
 
18
33
  const parts = [useBraces ? "%r{" : "/"]
19
- .concat(contents)
20
- .concat([useBraces ? "}" : "/", ending.slice(1)]);
34
+ .concat(path.map(print, "body"))
35
+ .concat([useBraces ? "}" : "/", node.ending.slice(1)]);
21
36
 
22
37
  return concat(parts);
23
38
  }
@@ -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,35 +12,45 @@ 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
+ }
44
+
45
+ return group(concat(parts));
46
+ }
41
47
 
42
- return group(concat(parts));
48
+ module.exports = {
49
+ "@__end__": (path, _opts, _print) => {
50
+ const { body } = path.getValue();
51
+ return concat([trim, "__END__", literalline, body]);
43
52
  },
53
+ bodystmt: printBodyStmt,
44
54
  paren: (path, opts, print) => {
45
55
  if (!path.getValue().body[0]) {
46
56
  return "()";
@@ -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
 
@@ -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
 
@@ -352,7 +360,12 @@ class Prettier::Parser < Ripper
352
360
  # method inside a set of parentheses.
353
361
  def on_arg_paren(args)
354
362
  beging = find_scanner_event(:@lparen)
355
- ending = find_scanner_event(:@rparen)
363
+ rparen = find_scanner_event(:@rparen)
364
+
365
+ # If the arguments exceed the ending of the parentheses, then we know we
366
+ # have a heredoc in the arguments, and we need to use the bounds of the
367
+ # arguments to determine how large the arg_paren is.
368
+ ending = (args && args[:end] > rparen[:end]) ? args : rparen
356
369
 
357
370
  {
358
371
  type: :arg_paren,
@@ -559,7 +572,7 @@ class Prettier::Parser < Ripper
559
572
  # Next we're going to determine the rescue clause if there is one
560
573
  if parts[1]
561
574
  consequent = parts[2..-1].compact.first
562
- self[:body][1].bind(consequent ? consequent[:char_start] : char_end)
575
+ self[:body][1].bind_end(consequent ? consequent[:char_start] : char_end)
563
576
  end
564
577
  end
565
578
  end
@@ -601,7 +614,16 @@ class Prettier::Parser < Ripper
601
614
  # accepts as an argument an args or args_add_block event that contains all
602
615
  # of the arguments being passed to the break.
603
616
  def on_break(args_add_block)
604
- find_scanner_event(:@kw, 'break').merge!(
617
+ beging = find_scanner_event(:@kw, 'break')
618
+
619
+ # You can hit this if you are passing no arguments to break but it has a
620
+ # comment right after it. In that case we can just use the location
621
+ # information straight from the keyword.
622
+ if args_add_block[:type] == :args
623
+ return beging.merge!(type: :break, body: [args_add_block])
624
+ end
625
+
626
+ beging.merge!(
605
627
  type: :break,
606
628
  body: [args_add_block],
607
629
  end: args_add_block[:end],
@@ -621,6 +643,10 @@ class Prettier::Parser < Ripper
621
643
  # foo.(1, 2, 3)
622
644
  #
623
645
  def on_call(receiver, oper, sending)
646
+ # Make sure we take the operator out of the scanner events so that it
647
+ # doesn't get confused for a unary operator later.
648
+ scanner_events.delete(oper)
649
+
624
650
  ending = sending
625
651
 
626
652
  if sending == :call
@@ -653,6 +679,27 @@ class Prettier::Parser < Ripper
653
679
  )
654
680
  end
655
681
 
682
+ # Finds the next position in the source string that begins a statement. This
683
+ # is used to bind statements lists and make sure they don't include a
684
+ # preceding comment. For example, we want the following comment to be attached
685
+ # to the class node and not the statement node:
686
+ #
687
+ # class Foo # :nodoc:
688
+ # ...
689
+ # end
690
+ #
691
+ # By finding the next non-space character, we can make sure that the bounds of
692
+ # the statement list are correct.
693
+ def find_next_statement_start(position)
694
+ remaining = source[position..-1]
695
+
696
+ if remaining.sub(/\A +/, '')[0] == '#'
697
+ return position + remaining.index("\n")
698
+ end
699
+
700
+ position
701
+ end
702
+
656
703
  # class is a parser event that represents defining a class. It accepts as
657
704
  # arguments the name of the class, the optional name of the superclass,
658
705
  # and the bodystmt event that represents the statements evaluated within
@@ -661,7 +708,10 @@ class Prettier::Parser < Ripper
661
708
  beging = find_scanner_event(:@kw, 'class')
662
709
  ending = find_scanner_event(:@kw, 'end')
663
710
 
664
- bodystmt.bind((superclass || const)[:char_end], ending[:char_start])
711
+ bodystmt.bind(
712
+ find_next_statement_start((superclass || const)[:char_end]),
713
+ ending[:char_start]
714
+ )
665
715
 
666
716
  {
667
717
  type: :class,
@@ -761,6 +811,11 @@ class Prettier::Parser < Ripper
761
811
  # └> ident
762
812
  #
763
813
  def on_def(ident, params, bodystmt)
814
+ # Make sure to delete this scanner event in case you're defining something
815
+ # like def class which would lead to this being a kw and causing all kinds
816
+ # of trouble
817
+ scanner_events.delete(ident)
818
+
764
819
  if params[:type] == :params && !params[:body].any?
765
820
  location = ident[:char_end]
766
821
  params.merge!(char_start: location, char_end: location)
@@ -769,7 +824,10 @@ class Prettier::Parser < Ripper
769
824
  beging = find_scanner_event(:@kw, 'def')
770
825
  ending = find_scanner_event(:@kw, 'end')
771
826
 
772
- bodystmt.bind(params[:char_end], ending[:char_start])
827
+ bodystmt.bind(
828
+ find_next_statement_start(params[:char_end]),
829
+ ending[:char_start]
830
+ )
773
831
 
774
832
  {
775
833
  type: :def,
@@ -796,6 +854,11 @@ class Prettier::Parser < Ripper
796
854
  # └> target
797
855
  #
798
856
  def on_defs(target, oper, ident, params, bodystmt)
857
+ # Make sure to delete this scanner event in case you're defining something
858
+ # like def class which would lead to this being a kw and causing all kinds
859
+ # of trouble
860
+ scanner_events.delete(ident)
861
+
799
862
  if params[:type] == :params && !params[:body].any?
800
863
  location = ident[:char_end]
801
864
  params.merge!(char_start: location, char_end: location)
@@ -804,7 +867,10 @@ class Prettier::Parser < Ripper
804
867
  beging = find_scanner_event(:@kw, 'def')
805
868
  ending = find_scanner_event(:@kw, 'end')
806
869
 
807
- bodystmt.bind(params[:char_end], ending[:char_start])
870
+ bodystmt.bind(
871
+ find_next_statement_start(params[:char_end]),
872
+ ending[:char_start]
873
+ )
808
874
 
809
875
  {
810
876
  type: :defs,
@@ -1029,13 +1095,23 @@ class Prettier::Parser < Ripper
1029
1095
  # and its subsequent statements.
1030
1096
  def on_ensure(stmts)
1031
1097
  beging = find_scanner_event(:@kw, 'ensure')
1032
- ending = find_scanner_event(:@kw, 'end')
1033
1098
 
1034
- stmts.bind(beging[:char_end], ending[:char_start])
1099
+ # Specifically not using find_scanner_event here because we don't want to
1100
+ # consume the :@end event, because that would break def..ensure..end chains.
1101
+ index =
1102
+ scanner_events.rindex do |scanner_event|
1103
+ scanner_event[:type] == :@kw && scanner_event[:body] == 'end'
1104
+ end
1105
+
1106
+ ending = scanner_events[index]
1107
+ stmts.bind(
1108
+ find_next_statement_start(beging[:char_end]),
1109
+ ending[:char_start]
1110
+ )
1035
1111
 
1036
1112
  {
1037
1113
  type: :ensure,
1038
- body: [stmts],
1114
+ body: [beging, stmts],
1039
1115
  start: beging[:start],
1040
1116
  char_start: beging[:char_start],
1041
1117
  end: ending[:end],
@@ -1126,21 +1202,25 @@ class Prettier::Parser < Ripper
1126
1202
  # prettier parser, we'll later attempt to print it using that parser and
1127
1203
  # printer through our embed function.
1128
1204
  def on_heredoc_beg(beging)
1129
- {
1130
- type: :heredoc,
1131
- beging: beging,
1205
+ location = {
1132
1206
  start: lineno,
1133
1207
  end: lineno,
1134
- char_start: char_pos - beging.length + 1,
1135
- char_end: char_pos
1136
- }.tap { |node| @heredocs << node }
1208
+ char_start: char_pos,
1209
+ char_end: char_pos + beging.length + 1
1210
+ }
1211
+
1212
+ # Here we're going to artificially create an extra node type so that if
1213
+ # there are comments after the declaration of a heredoc, they get printed.
1214
+ location.merge(
1215
+ type: :heredoc, beging: location.merge(type: :@heredoc_beg, body: beging)
1216
+ ).tap { |node| @heredocs << node }
1137
1217
  end
1138
1218
 
1139
1219
  # This is a parser event that occurs when you're using a heredoc with a
1140
1220
  # tilde. These are considered `heredoc_dedent` nodes, whereas the hyphen
1141
1221
  # heredocs show up as string literals.
1142
1222
  def on_heredoc_dedent(string, _width)
1143
- @heredocs[-1].merge!(string.slice(:body))
1223
+ @heredocs[-1].merge!(body: string[:body])
1144
1224
  end
1145
1225
 
1146
1226
  # This is a scanner event that represents the end of the heredoc.
@@ -1306,6 +1386,14 @@ class Prettier::Parser < Ripper
1306
1386
  # arguments and parentheses. It accepts as arguments the method being called
1307
1387
  # and the arg_paren event that contains the arguments to the method.
1308
1388
  def on_method_add_arg(fcall, arg_paren)
1389
+ # You can hit this if you are passing no arguments to a method that ends in
1390
+ # a question mark. Because it knows it has to be a method and not a local
1391
+ # variable. In that case we can just use the location information straight
1392
+ # from the fcall.
1393
+ if arg_paren[:type] == :args
1394
+ return fcall.merge(type: :method_add_arg, body: [fcall, arg_paren])
1395
+ end
1396
+
1309
1397
  {
1310
1398
  type: :method_add_arg,
1311
1399
  body: [fcall, arg_paren],
@@ -1418,7 +1506,10 @@ class Prettier::Parser < Ripper
1418
1506
  beging = find_scanner_event(:@kw, 'module')
1419
1507
  ending = find_scanner_event(:@kw, 'end')
1420
1508
 
1421
- bodystmt.bind(const[:char_end], ending[:char_start])
1509
+ bodystmt.bind(
1510
+ find_next_statement_start(const[:char_end]),
1511
+ ending[:char_start]
1512
+ )
1422
1513
 
1423
1514
  {
1424
1515
  type: :module,
@@ -1643,17 +1734,17 @@ class Prettier::Parser < Ripper
1643
1734
  # determine its ending. Therefore it relies on its parent bodystmt node to
1644
1735
  # report its ending to it.
1645
1736
  class Rescue < SimpleDelegator
1646
- def bind(char_end)
1737
+ def bind_end(char_end)
1647
1738
  merge!(char_end: char_end)
1648
1739
 
1649
1740
  stmts = self[:body][2]
1650
1741
  consequent = self[:body][3]
1651
1742
 
1652
1743
  if consequent
1653
- consequent.bind(char_end)
1654
- stmts.bind(stmts[:char_start], consequent[:char_start])
1744
+ consequent.bind_end(char_end)
1745
+ stmts.bind_end(consequent[:char_start])
1655
1746
  else
1656
- stmts.bind(stmts[:char_start], char_end)
1747
+ stmts.bind_end(char_end)
1657
1748
  end
1658
1749
  end
1659
1750
  end
@@ -1663,10 +1754,10 @@ class Prettier::Parser < Ripper
1663
1754
  def on_rescue(exceptions, variable, stmts, consequent)
1664
1755
  beging = find_scanner_event(:@kw, 'rescue')
1665
1756
 
1666
- stmts.bind(
1667
- ((exceptions || [])[-1] || variable || beging)[:char_end],
1668
- char_pos
1669
- )
1757
+ last_exception = exceptions.is_a?(Array) ? exceptions[-1] : exceptions
1758
+ last_node = variable || last_exception || beging
1759
+
1760
+ stmts.bind(find_next_statement_start(last_node[:char_end]), char_pos)
1670
1761
 
1671
1762
  Rescue.new(
1672
1763
  beging.merge!(
@@ -1750,7 +1841,10 @@ class Prettier::Parser < Ripper
1750
1841
  beging = find_scanner_event(:@kw, 'class')
1751
1842
  ending = find_scanner_event(:@kw, 'end')
1752
1843
 
1753
- bodystmt.bind(target[:char_end], ending[:char_start])
1844
+ bodystmt.bind(
1845
+ find_next_statement_start(target[:char_end]),
1846
+ ending[:char_start]
1847
+ )
1754
1848
 
1755
1849
  {
1756
1850
  type: :sclass,
@@ -1778,6 +1872,10 @@ class Prettier::Parser < Ripper
1778
1872
  end
1779
1873
  end
1780
1874
 
1875
+ def bind_end(char_end)
1876
+ merge!(char_end: char_end)
1877
+ end
1878
+
1781
1879
  def <<(statement)
1782
1880
  if self[:body].any?
1783
1881
  merge!(statement.slice(:end, :char_end))
@@ -1948,7 +2046,7 @@ class Prettier::Parser < Ripper
1948
2046
  # symbol node (for most cases) or an ident node (in the case that we're
1949
2047
  # using bare words, as in an alias node like alias foo bar).
1950
2048
  def on_symbol_literal(contents)
1951
- if contents[:type] == :@ident
2049
+ if scanner_events[-1] == contents
1952
2050
  contents.merge(type: :symbol_literal, body: [contents])
1953
2051
  else
1954
2052
  beging = find_scanner_event(:@symbeg)
@@ -2042,7 +2140,18 @@ class Prettier::Parser < Ripper
2042
2140
  paren: paren
2043
2141
  )
2044
2142
  else
2045
- find_scanner_event(:@op).merge!(
2143
+ # Special case instead of using find_scanner_event here. It turns out that
2144
+ # if you have a range that goes from a negative number to a negative
2145
+ # number then you can end up with a .. or a ... that's higher in the
2146
+ # stack. So we need to explicitly disallow those operators.
2147
+ index =
2148
+ scanner_events.rindex do |scanner_event|
2149
+ scanner_event[:type] == :@op &&
2150
+ !%w[.. ...].include?(scanner_event[:body])
2151
+ end
2152
+
2153
+ beging = scanner_events.delete_at(index)
2154
+ beging.merge!(
2046
2155
  type: :unary,
2047
2156
  oper: oper[0],
2048
2157
  body: [value],
@@ -2335,6 +2444,8 @@ class Prettier::Parser < Ripper
2335
2444
 
2336
2445
  if heredoc && heredoc[:beging][3] = '`'
2337
2446
  heredoc.merge(type: :xstring, body: [])
2447
+ elsif RUBY_MAJOR <= 2 && RUBY_MINOR <= 5 && RUBY_PATCH < 7
2448
+ { type: :xstring, body: [] }
2338
2449
  else
2339
2450
  find_scanner_event(:@backtick).merge!(type: :xstring, body: [])
2340
2451
  end