prettier 0.22.0 → 1.0.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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