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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +118 -4
- data/CONTRIBUTING.md +8 -6
- data/README.md +67 -60
- data/node_modules/prettier/bin-prettier.js +12317 -50112
- data/node_modules/prettier/index.js +33352 -27419
- data/node_modules/prettier/third-party.js +5678 -7676
- data/package.json +4 -4
- data/src/embed.js +27 -8
- data/src/nodes.js +6 -2
- data/src/nodes/alias.js +65 -24
- data/src/nodes/aref.js +55 -0
- data/src/nodes/args.js +55 -47
- data/src/nodes/arrays.js +150 -137
- data/src/nodes/assign.js +32 -32
- data/src/nodes/blocks.js +8 -3
- data/src/nodes/calls.js +129 -70
- data/src/nodes/case.js +11 -7
- data/src/nodes/class.js +74 -0
- data/src/nodes/commands.js +36 -31
- data/src/nodes/conditionals.js +48 -46
- data/src/nodes/constants.js +39 -21
- data/src/nodes/flow.js +45 -17
- data/src/nodes/hashes.js +126 -112
- data/src/nodes/heredocs.js +34 -0
- data/src/nodes/hooks.js +36 -7
- data/src/nodes/ints.js +27 -20
- data/src/nodes/lambdas.js +69 -52
- data/src/nodes/loops.js +19 -29
- data/src/nodes/massign.js +87 -65
- data/src/nodes/methods.js +48 -73
- data/src/nodes/operators.js +70 -39
- data/src/nodes/params.js +26 -16
- data/src/nodes/patterns.js +108 -33
- data/src/nodes/regexp.js +38 -14
- data/src/nodes/rescue.js +72 -59
- data/src/nodes/statements.js +86 -44
- data/src/nodes/strings.js +94 -90
- data/src/nodes/super.js +35 -0
- data/src/nodes/undef.js +42 -0
- data/src/parser.js +71 -0
- data/src/parser.rb +2554 -0
- data/src/printer.js +90 -0
- data/src/ruby.js +20 -61
- data/src/toProc.js +4 -4
- data/src/utils.js +24 -88
- data/src/utils/inlineEnsureParens.js +42 -0
- data/src/utils/isEmptyStmts.js +7 -0
- data/src/utils/literalLineNoBreak.js +7 -0
- metadata +15 -20
- data/src/haml.js +0 -21
- data/src/haml/embed.js +0 -58
- data/src/haml/nodes/comment.js +0 -27
- data/src/haml/nodes/doctype.js +0 -32
- data/src/haml/nodes/filter.js +0 -16
- data/src/haml/nodes/hamlComment.js +0 -21
- data/src/haml/nodes/script.js +0 -29
- data/src/haml/nodes/silentScript.js +0 -59
- data/src/haml/nodes/tag.js +0 -157
- data/src/haml/parse.js +0 -18
- data/src/haml/parse.rb +0 -64
- data/src/haml/print.js +0 -38
- data/src/nodes/scopes.js +0 -61
- data/src/parse.js +0 -37
- data/src/print.js +0 -23
- data/src/ripper.rb +0 -811
data/src/haml/parse.rb
DELETED
@@ -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
|
data/src/haml/print.js
DELETED
@@ -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;
|
data/src/nodes/scopes.js
DELETED
@@ -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
|
-
};
|
data/src/parse.js
DELETED
@@ -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
|
-
};
|
data/src/print.js
DELETED
@@ -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
|
-
};
|
data/src/ripper.rb
DELETED
@@ -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
|