prettier 0.20.0 → 1.0.0.pre.rc2

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