prettier 0.20.0 → 1.0.0.pre.rc2

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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +118 -4
  3. data/CONTRIBUTING.md +8 -6
  4. data/README.md +67 -60
  5. data/node_modules/prettier/bin-prettier.js +12317 -50112
  6. data/node_modules/prettier/index.js +33352 -27419
  7. data/node_modules/prettier/third-party.js +5678 -7676
  8. data/package.json +4 -4
  9. data/src/embed.js +27 -8
  10. data/src/nodes.js +6 -2
  11. data/src/nodes/alias.js +65 -24
  12. data/src/nodes/aref.js +55 -0
  13. data/src/nodes/args.js +55 -47
  14. data/src/nodes/arrays.js +150 -137
  15. data/src/nodes/assign.js +32 -32
  16. data/src/nodes/blocks.js +8 -3
  17. data/src/nodes/calls.js +129 -70
  18. data/src/nodes/case.js +11 -7
  19. data/src/nodes/class.js +74 -0
  20. data/src/nodes/commands.js +36 -31
  21. data/src/nodes/conditionals.js +48 -46
  22. data/src/nodes/constants.js +39 -21
  23. data/src/nodes/flow.js +45 -17
  24. data/src/nodes/hashes.js +126 -112
  25. data/src/nodes/heredocs.js +34 -0
  26. data/src/nodes/hooks.js +36 -7
  27. data/src/nodes/ints.js +27 -20
  28. data/src/nodes/lambdas.js +69 -52
  29. data/src/nodes/loops.js +19 -29
  30. data/src/nodes/massign.js +87 -65
  31. data/src/nodes/methods.js +48 -73
  32. data/src/nodes/operators.js +70 -39
  33. data/src/nodes/params.js +26 -16
  34. data/src/nodes/patterns.js +108 -33
  35. data/src/nodes/regexp.js +38 -14
  36. data/src/nodes/rescue.js +72 -59
  37. data/src/nodes/statements.js +86 -44
  38. data/src/nodes/strings.js +94 -90
  39. data/src/nodes/super.js +35 -0
  40. data/src/nodes/undef.js +42 -0
  41. data/src/parser.js +71 -0
  42. data/src/parser.rb +2554 -0
  43. data/src/printer.js +90 -0
  44. data/src/ruby.js +20 -61
  45. data/src/toProc.js +4 -4
  46. data/src/utils.js +24 -88
  47. data/src/utils/inlineEnsureParens.js +42 -0
  48. data/src/utils/isEmptyStmts.js +7 -0
  49. data/src/utils/literalLineNoBreak.js +7 -0
  50. metadata +15 -20
  51. data/src/haml.js +0 -21
  52. data/src/haml/embed.js +0 -58
  53. data/src/haml/nodes/comment.js +0 -27
  54. data/src/haml/nodes/doctype.js +0 -32
  55. data/src/haml/nodes/filter.js +0 -16
  56. data/src/haml/nodes/hamlComment.js +0 -21
  57. data/src/haml/nodes/script.js +0 -29
  58. data/src/haml/nodes/silentScript.js +0 -59
  59. data/src/haml/nodes/tag.js +0 -157
  60. data/src/haml/parse.js +0 -18
  61. data/src/haml/parse.rb +0 -64
  62. data/src/haml/print.js +0 -38
  63. data/src/nodes/scopes.js +0 -61
  64. data/src/parse.js +0 -37
  65. data/src/print.js +0 -23
  66. data/src/ripper.rb +0 -811
@@ -1,64 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'bundler/setup' if ENV['CI']
4
- require 'haml'
5
-
6
- class Haml::Parser::ParseNode
7
- ESCAPE = /Haml::Helpers.html_escape\(\((.+)\)\)/.freeze
8
-
9
- def as_json
10
- case type
11
- when :comment, :doctype, :plain, :silent_script
12
- to_h.tap do |json|
13
- json.delete(:parent)
14
- json[:children] = children.map(&:as_json)
15
- end
16
- when :filter, :haml_comment
17
- to_h.tap { |json| json.delete(:parent) }
18
- when :root
19
- to_h.tap { |json| json[:children] = children.map(&:as_json) }
20
- when :script
21
- to_h.tap do |json|
22
- json.delete(:parent)
23
- json[:children] = children.map(&:as_json)
24
-
25
- if json[:value][:text].match?(ESCAPE)
26
- json[:value][:text].gsub!(ESCAPE) { $1 }
27
- json[:value].merge!(escape_html: 'escape_html', interpolate: true)
28
- end
29
- end
30
- when :tag
31
- to_h.tap do |json|
32
- json.delete(:parent)
33
-
34
- # For some reason this is actually using a symbol to represent a null
35
- # object ref instead of nil itself, so just replacing it here for
36
- # simplicity in the printer
37
- json[:value][:object_ref] = nil if json[:value][:object_ref] == :nil
38
-
39
- json.merge!(
40
- children: children.map(&:as_json),
41
- value:
42
- value.merge(dynamic_attributes: value[:dynamic_attributes].to_h)
43
- )
44
- end
45
- else
46
- raise ArgumentError, "Unsupported type: #{type}"
47
- end
48
- end
49
- end
50
-
51
- # If this is the main file we're executing, then most likely this is being
52
- # executed from the haml.js spawn. In that case, read the ruby source from
53
- # stdin and report back the AST over stdout.
54
- if $0 == __FILE__
55
- # Don't explicitly require JSON if there is already as JSON loaded, as this
56
- # can lead to all kinds of trouble where one version of it was already
57
- # "activated" by rubygems.
58
- require 'json' unless defined?(JSON)
59
-
60
- parser = Haml::Parser.new({})
61
- template = $stdin.read
62
-
63
- puts JSON.fast_generate(parser.call(template).as_json)
64
- end
@@ -1,38 +0,0 @@
1
- const { concat, hardline, join, markAsRoot } = require("../prettier");
2
-
3
- const comment = require("./nodes/comment");
4
- const doctype = require("./nodes/doctype");
5
- const filter = require("./nodes/filter");
6
- const hamlComment = require("./nodes/hamlComment");
7
- const script = require("./nodes/script");
8
- const silentScript = require("./nodes/silentScript");
9
- const tag = require("./nodes/tag");
10
-
11
- const nodes = {
12
- comment,
13
- doctype,
14
- filter,
15
- haml_comment: hamlComment,
16
- plain: (path, _opts, _print) => {
17
- const { value } = path.getValue();
18
-
19
- return value.text.startsWith("=") ? `\\${value.text}` : value.text;
20
- },
21
- root: (path, opts, print) =>
22
- markAsRoot(concat([join(hardline, path.map(print, "children")), hardline])),
23
- script,
24
- silent_script: silentScript,
25
- tag
26
- };
27
-
28
- const genericPrint = (path, opts, print) => {
29
- const { type } = path.getValue();
30
-
31
- if (!(type in nodes)) {
32
- throw new Error(`Unsupported node encountered: ${type}`);
33
- }
34
-
35
- return nodes[type](path, opts, print);
36
- };
37
-
38
- module.exports = genericPrint;
@@ -1,61 +0,0 @@
1
- const {
2
- concat,
3
- group,
4
- hardline,
5
- ifBreak,
6
- indent,
7
- line
8
- } = require("../prettier");
9
-
10
- module.exports = {
11
- class: (path, opts, print) => {
12
- const [_constant, superclass, statements] = path.getValue().body;
13
-
14
- const parts = ["class ", path.call(print, "body", 0)];
15
- if (superclass) {
16
- parts.push(" < ", path.call(print, "body", 1));
17
- }
18
-
19
- // If the body is empty, we can replace with a ;
20
- const stmts = statements.body[0].body;
21
- if (stmts.length === 1 && stmts[0].type === "void_stmt") {
22
- return group(concat([concat(parts), ifBreak(line, "; "), "end"]));
23
- }
24
-
25
- return group(
26
- concat([
27
- concat(parts),
28
- indent(concat([hardline, path.call(print, "body", 2)])),
29
- concat([hardline, "end"])
30
- ])
31
- );
32
- },
33
- class_name_error: (_path, _opts, _print) => {
34
- throw new Error("class/module name must be CONSTANT");
35
- },
36
- module: (path, opts, print) => {
37
- const declaration = group(concat(["module ", path.call(print, "body", 0)]));
38
-
39
- // If the body is empty, we can replace with a ;
40
- const stmts = path.getValue().body[1].body[0].body;
41
- if (stmts.length === 1 && stmts[0].type === "void_stmt") {
42
- return group(concat([declaration, ifBreak(line, "; "), "end"]));
43
- }
44
-
45
- return group(
46
- concat([
47
- declaration,
48
- indent(concat([hardline, path.call(print, "body", 1)])),
49
- concat([hardline, "end"])
50
- ])
51
- );
52
- },
53
- sclass: (path, opts, print) =>
54
- group(
55
- concat([
56
- concat(["class << ", path.call(print, "body", 0)]),
57
- indent(concat([hardline, path.call(print, "body", 1)])),
58
- concat([hardline, "end"])
59
- ])
60
- )
61
- };
@@ -1,37 +0,0 @@
1
- const { spawnSync } = require("child_process");
2
- const path = require("path");
3
-
4
- // In order to properly parse ruby code, we need to tell the ruby process to
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];
18
-
19
- module.exports = (text, _parsers, _opts) => {
20
- const child = spawnSync(
21
- "ruby",
22
- ["--disable-gems", path.join(__dirname, "./ripper.rb")],
23
- {
24
- env: Object.assign({}, process.env, { LANG }),
25
- input: text,
26
- maxBuffer: 10 * 1024 * 1024 // 10MB
27
- }
28
- );
29
-
30
- const error = child.stderr.toString();
31
- if (error) {
32
- throw new Error(error);
33
- }
34
-
35
- const response = child.stdout.toString();
36
- return JSON.parse(response);
37
- };
@@ -1,23 +0,0 @@
1
- const { printComments } = require("./utils");
2
- const nodes = require("./nodes");
3
-
4
- module.exports = (path, opts, print) => {
5
- const { type, body, comments, start } = path.getValue();
6
-
7
- if (type in nodes) {
8
- const printed = nodes[type](path, opts, print);
9
-
10
- if (comments) {
11
- return printComments(printed, start, comments);
12
- }
13
- return printed;
14
- }
15
-
16
- if (type[0] === "@") {
17
- return body;
18
- }
19
-
20
- throw new Error(
21
- `Unsupported node encountered: ${type}\n${JSON.stringify(body, null, 2)}`
22
- );
23
- };
@@ -1,811 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # We implement our own version checking here instead of using Gem::Version so
4
- # that we can use the --disable-gems flag.
5
- major, minor, * = RUBY_VERSION.split('.').map(&:to_i)
6
-
7
- if (major < 2) || ((major == 2) && (minor < 5))
8
- warn(
9
- "Ruby version #{RUBY_VERSION} not supported. " \
10
- 'Please upgrade to 2.5.0 or above.'
11
- )
12
-
13
- exit 1
14
- end
15
-
16
- require 'json' unless defined?(JSON)
17
- require 'ripper'
18
-
19
- class RipperJS < Ripper
20
- attr_reader :source, :lines, :__end__
21
-
22
- def initialize(source, *args)
23
- super(source, *args)
24
-
25
- @source = source
26
- @lines = source.split("\n")
27
- @__end__ = nil
28
- end
29
-
30
- private
31
-
32
- # Scanner events occur when the lexer hits a new token, like a keyword or an
33
- # end. These nodes always contain just one argument which is a string
34
- # representing the content. For the most part these can just be printed
35
- # directly, which very few exceptions.
36
- SCANNER_EVENTS.each do |event|
37
- define_method(:"on_#{event}") do |body|
38
- { type: :"@#{event}", body: body, start: lineno, end: lineno }
39
- end
40
- end
41
-
42
- # Parser events represent nodes in the ripper abstract syntax tree. The event
43
- # is reported after the children of the node have already been built.
44
- PARSER_EVENTS.each do |event|
45
- define_method(:"on_#{event}") do |*body|
46
- min = body.map { |part| part.is_a?(Hash) ? part[:start] : lineno }.min
47
- { type: event, body: body, start: min || lineno, end: lineno }
48
- end
49
- end
50
-
51
- # Some nodes are lists that come back from the parser. They always start with
52
- # a `*_new` node (or in the case of string, `*_content`) and each additional
53
- # node in the list is a `*_add` node. This module takes those nodes and turns
54
- # them into one node with an array body.
55
- #
56
- # For example, the statement `[a, b, c]` would be parsed as:
57
- #
58
- # [:args_add,
59
- # [:args_add,
60
- # [:args_add,
61
- # [:args_new],
62
- # [:vcall, [:@ident, "a", [1, 1]]]
63
- # ],
64
- # [:vcall, [:@ident, "b", [1, 4]]]
65
- # ],
66
- # [:vcall, [:@ident, "c", [1, 7]]]
67
- # ]
68
- #
69
- # But after this module is applied that is instead parsed as:
70
- #
71
- # [:args,
72
- # [
73
- # [:vcall, [:@ident, "a", [1, 1]]],
74
- # [:vcall, [:@ident, "b", [1, 4]]],
75
- # [:vcall, [:@ident, "c", [1, 7]]]
76
- # ]
77
- # ]
78
- #
79
- # This makes it a lot easier to join things with commas, and ends up resulting
80
- # in a much flatter `prettier` tree once it has been converted. Note that
81
- # because of this module some extra node types are added (the aggregate of
82
- # the previous `*_add` nodes) and some nodes now have arrays in places where
83
- # they previously had single nodes.
84
- prepend(
85
- Module.new do
86
- events = %i[
87
- args
88
- mlhs
89
- mrhs
90
- qsymbols
91
- qwords
92
- regexp
93
- stmts
94
- string
95
- symbols
96
- words
97
- xstring
98
- ]
99
-
100
- private
101
-
102
- events.each do |event|
103
- suffix = event == :string ? 'content' : 'new'
104
-
105
- define_method(:"on_#{event}_#{suffix}") do
106
- { type: event, body: [], start: lineno, end: lineno }
107
- end
108
-
109
- define_method(:"on_#{event}_add") do |parts, part|
110
- parts.tap do |node|
111
- node[:body] << part
112
- node[:end] = lineno
113
- end
114
- end
115
- end
116
- end
117
- )
118
-
119
- # For each node, we need to attach where it came from in order to be able to
120
- # support placing the cursor correctly before and after formatting.
121
- #
122
- # For most nodes, it's enough to look at the child nodes to determine the
123
- # start of the parent node. However, for some nodes it's necessary to keep
124
- # track of the keywords as they come in from the lexer and to modify the start
125
- # node once we have it.
126
- prepend(
127
- Module.new do
128
- def initialize(source, *args)
129
- super(source, *args)
130
-
131
- @scanner_events = []
132
- @line_counts = [0]
133
-
134
- source.lines.each { |line| line_counts << line_counts.last + line.size }
135
- end
136
-
137
- def self.prepended(base)
138
- base.attr_reader :scanner_events, :line_counts
139
- end
140
-
141
- private
142
-
143
- def char_pos
144
- line_counts[lineno - 1] + column
145
- end
146
-
147
- def char_start_for(body)
148
- children = body.length == 1 && body[0].is_a?(Array) ? body[0] : body
149
- char_starts =
150
- children.map { |part| part[:char_start] if part.is_a?(Hash) }.compact
151
-
152
- char_starts.min || char_pos
153
- end
154
-
155
- def find_scanner_event(type, body = :any)
156
- index =
157
- scanner_events.rindex do |scanner_event|
158
- scanner_event[:type] == type &&
159
- (body == :any || (scanner_event[:body] == body))
160
- end
161
-
162
- scanner_events.delete_at(index)
163
- end
164
-
165
- events = {
166
- BEGIN: [:@kw, 'BEGIN'],
167
- END: [:@kw, 'END'],
168
- alias: [:@kw, 'alias'],
169
- assoc_splat: [:@op, '**'],
170
- arg_paren: :@lparen,
171
- args_add_star: [:@op, '*'],
172
- args_forward: [:@op, '...'],
173
- begin: [:@kw, 'begin'],
174
- blockarg: [:@op, '&'],
175
- brace_block: :@lbrace,
176
- break: [:@kw, 'break'],
177
- case: [:@kw, 'case'],
178
- class: [:@kw, 'class'],
179
- def: [:@kw, 'def'],
180
- defined: [:@kw, 'defined?'],
181
- defs: [:@kw, 'def'],
182
- do_block: [:@kw, 'do'],
183
- else: [:@kw, 'else'],
184
- elsif: [:@kw, 'elsif'],
185
- ensure: [:@kw, 'ensure'],
186
- excessed_comma: :@comma,
187
- for: [:@kw, 'for'],
188
- hash: :@lbrace,
189
- if: [:@kw, 'if'],
190
- in: [:@kw, 'in'],
191
- kwrest_param: [:@op, '**'],
192
- lambda: :@tlambda,
193
- mlhs_paren: :@lparen,
194
- mrhs_add_star: [:@op, '*'],
195
- module: [:@kw, 'module'],
196
- next: [:@kw, 'next'],
197
- paren: :@lparen,
198
- qsymbols_new: :@qsymbols_beg,
199
- qwords_new: :@qwords_beg,
200
- redo: [:@kw, 'redo'],
201
- regexp_literal: :@regexp_beg,
202
- rescue: [:@kw, 'rescue'],
203
- rest_param: [:@op, '*'],
204
- retry: [:@kw, 'retry'],
205
- return0: [:@kw, 'return'],
206
- return: [:@kw, 'return'],
207
- sclass: [:@kw, 'class'],
208
- string_dvar: :@embvar,
209
- string_embexpr: :@embexpr_beg,
210
- super: [:@kw, 'super'],
211
- symbols_new: :@symbols_beg,
212
- top_const_field: [:@op, '::'],
213
- top_const_ref: [:@op, '::'],
214
- undef: [:@kw, 'undef'],
215
- unless: [:@kw, 'unless'],
216
- until: [:@kw, 'until'],
217
- var_alias: [:@kw, 'alias'],
218
- when: [:@kw, 'when'],
219
- while: [:@kw, 'while'],
220
- words_new: :@words_beg,
221
- xstring_literal: :@backtick,
222
- yield0: [:@kw, 'yield'],
223
- yield: [:@kw, 'yield'],
224
- zsuper: [:@kw, 'super']
225
- }
226
-
227
- events.each do |event, (type, scanned)|
228
- define_method(:"on_#{event}") do |*body|
229
- node = find_scanner_event(type, scanned || :any)
230
-
231
- super(*body).merge!(
232
- start: node[:start],
233
- char_start: node[:char_start],
234
- char_end: char_pos
235
- )
236
- end
237
- end
238
-
239
- # Array nodes can contain a myriad of subnodes because of the special
240
- # array literal syntax like %w and %i. As a result, we may be looking for
241
- # an left bracket, or we may be just looking at the children.
242
- def on_array(*body)
243
- if body[0] && %i[args args_add_star].include?(body[0][:type])
244
- node = find_scanner_event(:@lbracket)
245
-
246
- super(*body).merge!(
247
- start: node[:start],
248
- char_start: node[:char_start],
249
- char_end: char_pos
250
- )
251
- else
252
- super(*body).merge!(
253
- char_start: char_start_for(body), char_end: char_pos
254
- )
255
- end
256
- end
257
-
258
- # Array pattern nodes contain an odd mix of potential child nodes based on
259
- # which kind of pattern is being used.
260
- def on_aryptn(*body)
261
- char_start, char_end = char_pos, char_pos
262
-
263
- body.flatten(1).each do |part|
264
- next unless part
265
-
266
- char_start = [char_start, part[:char_start]].min
267
- char_end = [char_end, part[:char_end]].max
268
- end
269
-
270
- super(*body).merge!(char_start: char_start, char_end: char_end)
271
- end
272
-
273
- # Params have a somewhat interesting structure in that they are an array
274
- # of arrays where the position in the top-level array indicates the type
275
- # of param and the subarray is the list of parameters of that type. We
276
- # therefore have to flatten them down to get to the location.
277
- def on_params(*body)
278
- super(*body).merge!(
279
- char_start: char_start_for(body.flatten(1)), char_end: char_pos
280
- )
281
- end
282
-
283
- # String literals and either contain string parts or a heredoc. If it
284
- # contains a heredoc we can just go directly to the child nodes, otherwise
285
- # we need to look for a `tstring_beg`.
286
- def on_string_literal(*body)
287
- if body[0][:type] == :heredoc
288
- super(*body).merge!(
289
- char_start: char_start_for(body), char_end: char_pos
290
- )
291
- else
292
- node = find_scanner_event(:@tstring_beg)
293
-
294
- super(*body).merge!(
295
- start: node[:start],
296
- char_start: node[:char_start],
297
- char_end: char_pos,
298
- quote: node[:body]
299
- )
300
- end
301
- end
302
-
303
- # Technically, the `not` operator is a unary operator but is reported as
304
- # a keyword and not an operator. Because of the inconsistency, we have to
305
- # manually look for the correct scanner event here.
306
- def on_unary(*body)
307
- node =
308
- if body[0] == :not
309
- find_scanner_event(:@kw, 'not')
310
- else
311
- find_scanner_event(:@op)
312
- end
313
-
314
- super(*body).merge!(
315
- start: node[:start], char_start: node[:char_start], char_end: char_pos
316
- )
317
- end
318
-
319
- # Symbols don't necessarily have to have a @symbeg event fired before they
320
- # start. For example, you can have symbol literals inside an `alias` node
321
- # if you're just using bare words, as in: `alias foo bar`. So this is a
322
- # special case in which if there is a `:@symbeg` event we can hook on to
323
- # then we use it, otherwise we just look at the beginning of the first
324
- # child node.
325
- %i[dyna_symbol symbol_literal].each do |event|
326
- define_method(:"on_#{event}") do |*body|
327
- options =
328
- if scanner_events.any? { |sevent| sevent[:type] == :@symbeg }
329
- symbeg = find_scanner_event(:@symbeg)
330
-
331
- {
332
- char_start: symbeg[:char_start],
333
- char_end: char_pos,
334
- quote: symbeg[:body][1]
335
- }
336
- elsif scanner_events.any? { |sevent| sevent[:type] == :@label_end }
337
- label_end = find_scanner_event(:@label_end)
338
-
339
- {
340
- char_start: char_start_for(body),
341
- char_end: char_pos,
342
- quote: label_end[:body][0]
343
- }
344
- else
345
- { char_start: char_start_for(body), char_end: char_pos }
346
- end
347
-
348
- super(*body).merge!(options)
349
- end
350
- end
351
-
352
- def on_program(*body)
353
- super(*body).merge!(start: 1, char_start: 0, char_end: char_pos)
354
- end
355
-
356
- defined =
357
- private_instance_methods(false).grep(/\Aon_/) { $'.to_sym } +
358
- %i[embdoc embdoc_beg embdoc_end heredoc_beg heredoc_end]
359
-
360
- (SCANNER_EVENTS - defined).each do |event|
361
- define_method(:"on_#{event}") do |body|
362
- super(body).tap do |node|
363
- char_end = char_pos + (body ? body.size : 0)
364
- node.merge!(char_start: char_pos, char_end: char_end)
365
-
366
- scanner_events << node
367
- end
368
- end
369
- end
370
-
371
- (PARSER_EVENTS - defined).each do |event|
372
- define_method(:"on_#{event}") do |*body|
373
- super(*body).merge!(
374
- char_start: char_start_for(body), char_end: char_pos
375
- )
376
- end
377
- end
378
- end
379
- )
380
-
381
- # This layer keeps track of inline comments as they come in. Ripper itself
382
- # doesn't attach comments to the AST, so we need to do it manually. In this
383
- # case, inline comments are defined as any comments wherein the lexer state is
384
- # not equal to EXPR_BEG (tracked in the BlockComments layer).
385
- prepend(
386
- Module.new do
387
- # Certain events needs to steal the comments from their children in order
388
- # for them to display properly.
389
- events = {
390
- aref: [:body, 1],
391
- args_add_block: [:body, 0],
392
- break: [:body, 0],
393
- call: [:body, 0],
394
- command: [:body, 1],
395
- command_call: [:body, 3],
396
- regexp_literal: [:body, 0],
397
- string_literal: [:body, 0],
398
- symbol_literal: [:body, 0]
399
- }
400
-
401
- def initialize(*args)
402
- super(*args)
403
- @inline_comments = []
404
- @last_sexp = nil
405
- end
406
-
407
- def self.prepended(base)
408
- base.attr_reader :inline_comments, :last_sexp
409
- end
410
-
411
- private
412
-
413
- events.each do |event, path|
414
- define_method(:"on_#{event}") do |*body|
415
- @last_sexp =
416
- super(*body).tap do |sexp|
417
- comments = (sexp.dig(*path) || {}).delete(:comments)
418
- sexp.merge!(comments: comments) if comments
419
- end
420
- end
421
- end
422
-
423
- SPECIAL_LITERALS = %i[qsymbols qwords symbols words].freeze
424
-
425
- # Special array literals are handled in different ways and so their
426
- # comments need to be passed up to their parent array node.
427
- def on_array(*body)
428
- @last_sexp =
429
- super(*body).tap do |sexp|
430
- next unless SPECIAL_LITERALS.include?(body.dig(0, :type))
431
-
432
- comments = sexp.dig(:body, 0).delete(:comments)
433
- sexp.merge!(comments: comments) if comments
434
- end
435
- end
436
-
437
- # Handling this specially because we want to pull the comments out of both
438
- # child nodes.
439
- def on_assoc_new(*body)
440
- @last_sexp =
441
- super(*body).tap do |sexp|
442
- comments =
443
- (sexp.dig(:body, 0).delete(:comments) || []) +
444
- (sexp.dig(:body, 1).delete(:comments) || [])
445
-
446
- sexp.merge!(comments: comments) if comments.any?
447
- end
448
- end
449
-
450
- # Most scanner events don't stand on their own as s-expressions, but the
451
- # CHAR scanner event is effectively just a string, so we need to track it
452
- # as a s-expression.
453
- def on_CHAR(body)
454
- @last_sexp = super(body)
455
- end
456
-
457
- # We need to know exactly where the comment is, switching off the current
458
- # lexer state. In Ruby 2.7.0-dev, that's defined as:
459
- #
460
- # enum lex_state_bits {
461
- # EXPR_BEG_bit, /* ignore newline, +/- is a sign. */
462
- # EXPR_END_bit, /* newline significant, +/- is an operator. */
463
- # EXPR_ENDARG_bit, /* ditto, and unbound braces. */
464
- # EXPR_ENDFN_bit, /* ditto, and unbound braces. */
465
- # EXPR_ARG_bit, /* newline significant, +/- is an operator. */
466
- # EXPR_CMDARG_bit, /* newline significant, +/- is an operator. */
467
- # EXPR_MID_bit, /* newline significant, +/- is an operator. */
468
- # EXPR_FNAME_bit, /* ignore newline, no reserved words. */
469
- # EXPR_DOT_bit, /* right after `.' or `::', no reserved words. */
470
- # EXPR_CLASS_bit, /* immediate after `class', no here document. */
471
- # EXPR_LABEL_bit, /* flag bit, label is allowed. */
472
- # EXPR_LABELED_bit, /* flag bit, just after a label. */
473
- # EXPR_FITEM_bit, /* symbol literal as FNAME. */
474
- # EXPR_MAX_STATE
475
- # };
476
- def on_comment(body)
477
- sexp = { type: :@comment, body: body.chomp, start: lineno, end: lineno }
478
-
479
- case RipperJS.lex_state_name(state).gsub('EXPR_', '')
480
- when 'END', 'ARG|LABELED', 'ENDFN'
481
- last_sexp.merge!(comments: [sexp])
482
- when 'CMDARG', 'END|ENDARG', 'ENDARG', 'ARG', 'FNAME|FITEM', 'CLASS',
483
- 'END|LABEL'
484
- inline_comments << sexp
485
- when 'BEG|LABEL', 'MID'
486
- inline_comments << sexp.merge!(break: true)
487
- when 'DOT'
488
- last_sexp.merge!(comments: [sexp.merge!(break: true)])
489
- end
490
-
491
- sexp
492
- end
493
-
494
- defined = private_instance_methods(false).grep(/\Aon_/) { $'.to_sym }
495
-
496
- (PARSER_EVENTS - defined).each do |event|
497
- define_method(:"on_#{event}") do |*body|
498
- super(*body).tap do |sexp|
499
- @last_sexp = sexp
500
- next if inline_comments.empty?
501
-
502
- sexp[:comments] = inline_comments.reverse
503
- @inline_comments = []
504
- end
505
- end
506
- end
507
- end
508
- )
509
-
510
- # Nodes that are always on their own line occur when the lexer is in the
511
- # EXPR_BEG state. Those comments are tracked within the @block_comments
512
- # instance variable. Then for each node that could contain them, we attach
513
- # them after the node has been built.
514
- prepend(
515
- Module.new do
516
- events = {
517
- begin: [0, :body, 0],
518
- bodystmt: [0],
519
- class: [2, :body, 0],
520
- def: [2, :body, 0],
521
- defs: [4, :body, 0],
522
- else: [0],
523
- elsif: [1],
524
- ensure: [0],
525
- if: [1],
526
- program: [0],
527
- rescue: [2],
528
- sclass: [1, :body, 0],
529
- unless: [1],
530
- until: [1],
531
- when: [1],
532
- while: [1]
533
- }
534
-
535
- def initialize(*args)
536
- super(*args)
537
- @block_comments = []
538
- @current_embdoc = nil
539
- end
540
-
541
- def self.prepended(base)
542
- base.attr_reader :block_comments, :current_embdoc
543
- end
544
-
545
- private
546
-
547
- def attach_comments(sexp, stmts)
548
- range = sexp[:start]..sexp[:end]
549
- comments =
550
- block_comments.group_by { |comment| range.include?(comment[:start]) }
551
-
552
- if comments[true]
553
- stmts[:body] =
554
- (stmts[:body] + comments[true]).sort_by { |node| node[:start] }
555
-
556
- @block_comments = comments.fetch(false) { [] }
557
- end
558
- end
559
-
560
- events.each do |event, path|
561
- define_method(:"on_#{event}") do |*body|
562
- super(*body).tap { |sexp| attach_comments(sexp, body.dig(*path)) }
563
- end
564
- end
565
-
566
- def on_comment(body)
567
- super(body).tap do |sexp|
568
- lex_state = RipperJS.lex_state_name(state).gsub('EXPR_', '')
569
- block_comments << sexp if lex_state == 'BEG'
570
- end
571
- end
572
-
573
- def on_embdoc_beg(comment)
574
- @current_embdoc = {
575
- type: :embdoc, body: comment, start: lineno, end: lineno
576
- }
577
- end
578
-
579
- def on_embdoc(comment)
580
- @current_embdoc[:body] << comment
581
- end
582
-
583
- def on_embdoc_end(comment)
584
- @current_embdoc[:body] << comment.chomp
585
- @block_comments << @current_embdoc
586
- @current_embdoc = nil
587
- end
588
-
589
- def on_method_add_block(*body)
590
- super(*body).tap do |sexp|
591
- stmts = body[1][:body][1]
592
- stmts = stmts[:type] == :stmts ? stmts : body[1][:body][1][:body][0]
593
-
594
- attach_comments(sexp, stmts)
595
- end
596
- end
597
- end
598
- )
599
-
600
- # Tracking heredocs in somewhat interesting. Straight-line heredocs are
601
- # reported as strings, whereas squiggly-line heredocs are reported as
602
- # heredocs. We track the start and matching end of the heredoc as "beging" and
603
- # "ending" respectively.
604
- prepend(
605
- Module.new do
606
- def initialize(*args)
607
- super(*args)
608
- @heredoc_stack = []
609
- end
610
-
611
- def self.prepended(base)
612
- base.attr_reader :heredoc_stack
613
- end
614
-
615
- private
616
-
617
- def on_embexpr_beg(body)
618
- super(body).tap { |sexp| heredoc_stack << sexp }
619
- end
620
-
621
- def on_embexpr_end(body)
622
- super(body).tap { heredoc_stack.pop }
623
- end
624
-
625
- def on_heredoc_beg(beging)
626
- heredoc = { type: :heredoc, beging: beging, start: lineno, end: lineno }
627
- heredoc_stack << heredoc
628
- end
629
-
630
- def on_heredoc_end(ending)
631
- heredoc_stack[-1].merge!(ending: ending.chomp, end: lineno)
632
- end
633
-
634
- def on_heredoc_dedent(string, _width)
635
- heredoc = heredoc_stack.pop
636
- string.merge!(heredoc.slice(:type, :beging, :ending, :start, :end))
637
- end
638
-
639
- def on_string_literal(string)
640
- heredoc = heredoc_stack[-1]
641
-
642
- if heredoc && string[:type] != :heredoc && heredoc[:type] == :heredoc
643
- heredoc_stack.pop
644
- string.merge!(heredoc.slice(:type, :beging, :ending, :start, :end))
645
- else
646
- super
647
- end
648
- end
649
- end
650
- )
651
-
652
- # This module contains miscellaneous fixes required to get the right
653
- # structure.
654
- prepend(
655
- Module.new do
656
- private
657
-
658
- # These are the event types that contain _actual_ string content. If
659
- # there is an encoding magic comment at the top of the file, ripper will
660
- # actually change into that encoding for the storage of the string. This
661
- # will break everything, so we need to force the encoding back into UTF-8
662
- # so that the JSON library won't break.
663
- %w[comment ident tstring_content].each do |event|
664
- define_method(:"on_#{event}") do |body|
665
- super(body.force_encoding('UTF-8'))
666
- end
667
- end
668
-
669
- # Handles __END__ syntax, which allows individual scripts to keep content
670
- # after the main ruby code that can be read through DATA. It looks like:
671
- #
672
- # foo.bar
673
- #
674
- # __END__
675
- # some other content that isn't normally read by ripper
676
- def on___end__(body)
677
- @__end__ = super(lines[lineno..-1].join("\n"))
678
- end
679
-
680
- def on_program(*body)
681
- super(*body).tap { |node| node[:body][0][:body] << __end__ if __end__ }
682
- end
683
-
684
- # Normally access controls are reported as vcall nodes. This creates a
685
- # new node type to explicitly track those nodes instead, so that the
686
- # printer can add new lines as necessary.
687
- def on_vcall(ident)
688
- @access_controls ||= %w[private protected public].freeze
689
-
690
- super(ident).tap do |node|
691
- if !@access_controls.include?(ident[:body]) ||
692
- ident[:body] != lines[lineno - 1].strip
693
- next
694
- end
695
-
696
- node.merge!(type: :access_ctrl)
697
- end
698
- end
699
-
700
- # When the only statement inside of a `def` node is a `begin` node, then
701
- # you can safely replace the body of the `def` with the body of the
702
- # `begin`. For example:
703
- #
704
- # def foo
705
- # begin
706
- # try_something
707
- # rescue SomeError => error
708
- # handle_error(error)
709
- # end
710
- # end
711
- #
712
- # can get transformed into:
713
- #
714
- # def foo
715
- # try_something
716
- # rescue SomeError => error
717
- # handle_error(error)
718
- # end
719
- #
720
- # This module handles this by hoisting up the `bodystmt` node from the
721
- # inner `begin` up to the `def`.
722
- def on_def(ident, params, bodystmt)
723
- def_bodystmt = bodystmt
724
- stmts, *other_parts = bodystmt[:body]
725
-
726
- if !other_parts.any? && stmts[:body].length == 1 &&
727
- stmts.dig(:body, 0, :type) == :begin
728
- def_bodystmt = stmts.dig(:body, 0, :body, 0)
729
- end
730
-
731
- super(ident, params, def_bodystmt)
732
- end
733
-
734
- # By default, Ripper parses the expression `lambda { foo }` as a
735
- # `method_add_block` node, so we can't turn it back into `-> { foo }`.
736
- # This module overrides that behavior and reports it back as a `lambda`
737
- # node instead.
738
- def on_method_add_block(invocation, block)
739
- # It's possible to hit a `method_add_block` node without going through
740
- # `method_add_arg` node, ex: `super {}`. In that case we're definitely
741
- # not going to transform into a lambda.
742
- return super if invocation[:type] != :method_add_arg
743
-
744
- fcall, args = invocation[:body]
745
-
746
- # If there are arguments to the `lambda`, that means `lambda` has been
747
- # overridden as a function so we cannot transform it into a `lambda`
748
- # node.
749
- if fcall[:type] != :fcall || args[:type] != :args || args[:body].any?
750
- return super
751
- end
752
-
753
- ident = fcall.dig(:body, 0)
754
- return super if ident[:type] != :@ident || ident[:body] != 'lambda'
755
-
756
- super.tap do |sexp|
757
- params, stmts = block[:body]
758
- params ||= { type: :params, body: [] }
759
-
760
- sexp.merge!(type: :lambda, body: [params, stmts])
761
- end
762
- end
763
-
764
- # We need to track for `mlhs_paren` and `massign` nodes whether or not
765
- # there was an extra comma at the end of the expression. For some reason
766
- # it's not showing up in the AST in an obvious way. In this case we're
767
- # just simplifying everything by adding an additional field to `mlhs`
768
- # nodes called `comma` that indicates whether or not there was an extra.
769
- def on_mlhs_paren(body)
770
- super.tap do |node|
771
- next unless body[:type] == :mlhs
772
-
773
- ending = source.rindex(')', char_pos)
774
- buffer = source[(node[:char_start] + 1)...ending]
775
-
776
- body[:comma] = buffer.strip.end_with?(',')
777
- end
778
- end
779
-
780
- def on_massign(left, right)
781
- super.tap do
782
- next unless left[:type] == :mlhs
783
-
784
- range = left[:char_start]..left[:char_end]
785
- left[:comma] = source[range].strip.end_with?(',')
786
- end
787
- end
788
- end
789
- )
790
- end
791
-
792
- # If this is the main file we're executing, then most likely this is being
793
- # executed from the parse.js spawn. In that case, read the ruby source from
794
- # stdin and report back the AST over stdout.
795
-
796
- if $0 == __FILE__
797
- builder = RipperJS.new($stdin.read)
798
- response = builder.parse
799
-
800
- if !response || builder.error?
801
- warn(
802
- '@prettier/plugin-ruby encountered an error when attempting to parse ' \
803
- 'the ruby source. This usually means there was a syntax error in the ' \
804
- 'file in question. You can verify by running `ruby -i [path/to/file]`.'
805
- )
806
-
807
- exit 1
808
- end
809
-
810
- puts JSON.fast_generate(response)
811
- end