oxc 0.0.1 → 0.2.0
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/LICENSE.txt +21 -0
- data/README.md +602 -0
- data/ext/oxc/extconf.rb +123 -0
- data/ext/oxc/include/oxc.h +40 -0
- data/ext/oxc/oxc.c +136 -0
- data/lib/oxc/backend.rb +41 -0
- data/lib/oxc/diagnosed.rb +33 -0
- data/lib/oxc/diagnostic.rb +86 -0
- data/lib/oxc/errors.rb +26 -0
- data/lib/oxc/minifier.rb +31 -0
- data/lib/oxc/minify_result.rb +25 -0
- data/lib/oxc/mutation_visitor.rb +128 -0
- data/lib/oxc/node.rb +186 -0
- data/lib/oxc/options.rb +113 -0
- data/lib/oxc/parse_result.rb +116 -0
- data/lib/oxc/result.rb +51 -0
- data/lib/oxc/transform_result.rb +47 -0
- data/lib/oxc/transformer.rb +31 -0
- data/lib/oxc/version.rb +1 -1
- data/lib/oxc/visitor.rb +25 -0
- data/lib/oxc.rb +49 -0
- data/licenses/README.md +12 -0
- data/licenses/oxc-MIT.txt +22 -0
- data/licenses/oxc-THIRD-PARTY.txt +763 -0
- data/oxc.gemspec +14 -2
- data/rust/Cargo.lock +1436 -0
- data/rust/Cargo.toml +32 -0
- data/rust/build.rs +52 -0
- data/rust/cbindgen.toml +24 -0
- data/rust/rustfmt.toml +3 -0
- data/rust/src/diagnostic.rs +75 -0
- data/rust/src/lib.rs +288 -0
- data/rust/src/module_record.rs +262 -0
- data/rust/src/options.rs +744 -0
- data/rust/src/parse.rs +93 -0
- data/rust/src/result.rs +55 -0
- data/rust/src/source_type.rs +26 -0
- data/rust/src/symbols.rs +101 -0
- data/rust/src/transform.rs +116 -0
- data/sig/oxc/backend.rbs +29 -0
- data/sig/oxc/diagnosed.rbs +23 -0
- data/sig/oxc/diagnostic.rbs +57 -0
- data/sig/oxc/errors.rbs +31 -0
- data/sig/oxc/minifier.rbs +21 -0
- data/sig/oxc/minify_result.rbs +11 -0
- data/sig/oxc/mutation_visitor.rbs +80 -0
- data/sig/oxc/node.rbs +94 -0
- data/sig/oxc/options.rbs +42 -0
- data/sig/oxc/parse_result.rbs +66 -0
- data/sig/oxc/result.rbs +32 -0
- data/sig/oxc/transform_result.rbs +22 -0
- data/sig/oxc/transformer.rbs +21 -0
- data/sig/oxc/types.rbs +96 -0
- data/sig/oxc/version.rbs +5 -0
- data/sig/oxc/visitor.rbs +11 -0
- data/sig/oxc.rbs +13 -2
- metadata +56 -2
data/lib/oxc/node.rb
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Oxc
|
|
4
|
+
class Node
|
|
5
|
+
include Enumerable #[Oxc::Node]
|
|
6
|
+
|
|
7
|
+
ACRONYM = /([A-Z]+)([A-Z][a-z])/ #: Regexp
|
|
8
|
+
BOUNDARY = /([a-z\d])([A-Z])/ #: Regexp
|
|
9
|
+
SPAN = ["type", "start", "end"].freeze #: Array[String]
|
|
10
|
+
|
|
11
|
+
attr_reader :parent #: Oxc::Node?
|
|
12
|
+
|
|
13
|
+
protected
|
|
14
|
+
|
|
15
|
+
attr_reader :attributes #: Hash[String, untyped]
|
|
16
|
+
attr_reader :text #: String?
|
|
17
|
+
|
|
18
|
+
public
|
|
19
|
+
|
|
20
|
+
#: (Hash[String, untyped], ?Oxc::Node?, ?String?) -> void
|
|
21
|
+
def initialize(attributes, parent = nil, text = nil)
|
|
22
|
+
@attributes = attributes
|
|
23
|
+
@parent = parent
|
|
24
|
+
@text = text || parent&.text
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#: () -> String
|
|
28
|
+
def type
|
|
29
|
+
attributes.fetch("type")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#: () -> Integer
|
|
33
|
+
def start
|
|
34
|
+
attributes.fetch("start")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
#: () -> Integer
|
|
38
|
+
def finish
|
|
39
|
+
attributes.fetch("end")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
#: () -> String
|
|
43
|
+
def underscored_type
|
|
44
|
+
@underscored_type ||= type.gsub(ACRONYM, '\1_\2').gsub(BOUNDARY, '\1_\2').downcase
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
#: (String) -> untyped
|
|
48
|
+
def [](key)
|
|
49
|
+
attributes[key]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
#: (?String?) -> String?
|
|
53
|
+
def slice(from = text)
|
|
54
|
+
raise ArgumentError, "this node does not know its source, so #slice needs it" unless from
|
|
55
|
+
|
|
56
|
+
from.byteslice(start, finish - start)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
#: () -> Array[String]
|
|
60
|
+
def keys
|
|
61
|
+
attributes.keys
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
#: () -> Hash[String, untyped]
|
|
65
|
+
def to_h
|
|
66
|
+
attributes
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
#: (?untyped) -> String
|
|
70
|
+
def to_json(state = nil)
|
|
71
|
+
state ? attributes.to_json(state) : attributes.to_json
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
#: (Array[Symbol]?) -> Hash[Symbol, untyped]
|
|
75
|
+
def deconstruct_keys(keys)
|
|
76
|
+
found = {} #: Hash[Symbol, untyped]
|
|
77
|
+
|
|
78
|
+
if keys
|
|
79
|
+
keys.each do |name|
|
|
80
|
+
key = field_for(name.to_s)
|
|
81
|
+
|
|
82
|
+
found[name] = wrap(attributes[key]) if key
|
|
83
|
+
end
|
|
84
|
+
else
|
|
85
|
+
attributes.each_key { |key| found[key.to_sym] = wrap(attributes[key]) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
found
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
#: () -> Array[Oxc::Node]
|
|
92
|
+
def child_nodes
|
|
93
|
+
@child_nodes ||= attributes.each_value
|
|
94
|
+
.flat_map { |value| value.is_a?(Array) ? value : [value] }
|
|
95
|
+
.select { |value| value.is_a?(Hash) && value.key?("type") }
|
|
96
|
+
.map { |value| Node.new(value, self) }
|
|
97
|
+
.freeze
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
#: () { (Oxc::Node) -> void } -> void
|
|
101
|
+
#: () -> Enumerator[Oxc::Node, void]
|
|
102
|
+
def each(&)
|
|
103
|
+
return enum_for(:each) unless block_given?
|
|
104
|
+
|
|
105
|
+
yield self
|
|
106
|
+
|
|
107
|
+
child_nodes.each { |child| child.each(&) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
#: () -> Array[Oxc::Node]
|
|
111
|
+
def ancestors
|
|
112
|
+
parent ? [parent, *parent.ancestors] : []
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
#: (Integer) -> Oxc::Node?
|
|
116
|
+
def at(offset)
|
|
117
|
+
covering = each.select { |node| offset >= node.start && offset < node.finish }
|
|
118
|
+
|
|
119
|
+
covering.min_by { |node| node.finish - node.start }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
#: (String) -> Array[Oxc::Node]
|
|
123
|
+
def every(type)
|
|
124
|
+
each.select { |node| node.type == type }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
#: () -> Hash[String, untyped]
|
|
128
|
+
def fields
|
|
129
|
+
attributes.except(*SPAN)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
#: () -> String
|
|
133
|
+
def inspect
|
|
134
|
+
described = fields.map { |key, value| described_field(key, value) }
|
|
135
|
+
|
|
136
|
+
"#<#{self.class.name} #{type} range=[#{start}, #{finish}]#{" #{described.join(" ")}" unless described.empty?}>"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
#: (Symbol, *untyped) -> untyped
|
|
140
|
+
def method_missing(name, *arguments)
|
|
141
|
+
key = field_for(name.to_s)
|
|
142
|
+
|
|
143
|
+
return super unless key
|
|
144
|
+
|
|
145
|
+
wrap(attributes[key])
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
#: (Symbol, ?bool) -> bool
|
|
149
|
+
def respond_to_missing?(name, include_private = false)
|
|
150
|
+
!field_for(name.to_s).nil? || super
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
#: (String, untyped) -> String
|
|
156
|
+
def described_field(key, value)
|
|
157
|
+
case value
|
|
158
|
+
when Array then "#{key}=#{if value.empty?
|
|
159
|
+
"[]"
|
|
160
|
+
else
|
|
161
|
+
"[... #{value.length} #{value.length == 1 ? "item" : "items"}]"
|
|
162
|
+
end}"
|
|
163
|
+
when Hash then "#{key}=#<#{self.class.name} #{value["type"]}>"
|
|
164
|
+
else "#{key}=#{value.inspect}"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
#: (String) -> String?
|
|
169
|
+
def field_for(name)
|
|
170
|
+
return name if attributes.key?(name)
|
|
171
|
+
|
|
172
|
+
camelized = name.gsub(/_([a-z\d])/) { Regexp.last_match(1).to_s.upcase }
|
|
173
|
+
|
|
174
|
+
camelized if attributes.key?(camelized)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
#: (untyped) -> untyped
|
|
178
|
+
def wrap(value)
|
|
179
|
+
case value
|
|
180
|
+
when Hash then value.key?("type") ? Node.new(value, self, text) : value
|
|
181
|
+
when Array then value.map { |item| wrap(item) }
|
|
182
|
+
else value
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
data/lib/oxc/options.rb
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Oxc
|
|
4
|
+
class Options
|
|
5
|
+
MINIFY = [
|
|
6
|
+
:filename,
|
|
7
|
+
:lang,
|
|
8
|
+
:source_type,
|
|
9
|
+
:compress,
|
|
10
|
+
:mangle,
|
|
11
|
+
:codegen,
|
|
12
|
+
:sourcemap,
|
|
13
|
+
:strict
|
|
14
|
+
].freeze #: Array[Symbol]
|
|
15
|
+
|
|
16
|
+
TRANSFORM = [
|
|
17
|
+
:filename,
|
|
18
|
+
:lang,
|
|
19
|
+
:source_type,
|
|
20
|
+
:cwd,
|
|
21
|
+
:target,
|
|
22
|
+
:jsx,
|
|
23
|
+
:typescript,
|
|
24
|
+
:assumptions,
|
|
25
|
+
:decorator,
|
|
26
|
+
:helpers,
|
|
27
|
+
:define,
|
|
28
|
+
:inject,
|
|
29
|
+
:minify,
|
|
30
|
+
:codegen,
|
|
31
|
+
:sourcemap,
|
|
32
|
+
:strict
|
|
33
|
+
].freeze #: Array[Symbol]
|
|
34
|
+
|
|
35
|
+
PARSE = [
|
|
36
|
+
:filename,
|
|
37
|
+
:lang,
|
|
38
|
+
:source_type,
|
|
39
|
+
:ast_type,
|
|
40
|
+
:ast,
|
|
41
|
+
:ranges,
|
|
42
|
+
:preserve_parens,
|
|
43
|
+
:comments,
|
|
44
|
+
:module_record,
|
|
45
|
+
:symbols,
|
|
46
|
+
:semantic_errors
|
|
47
|
+
].freeze #: Array[Symbol]
|
|
48
|
+
|
|
49
|
+
KNOWN = (MINIFY | TRANSFORM | PARSE).freeze #: Array[Symbol]
|
|
50
|
+
RUBY_ONLY = [:strict].freeze #: Array[Symbol]
|
|
51
|
+
|
|
52
|
+
# TODO: support mangle_props. It needs `lazy-regex` and `rustc-hash` as direct dependencies of the
|
|
53
|
+
# Rust crate, because `oxc_minifier::ManglePropertiesOptions` types `include` and `exclude` as
|
|
54
|
+
# `lazy_regex::Regex` and `reserved` as `FxHashSet<CompactStr>`.
|
|
55
|
+
UNSUPPORTED = [:mangle_props].freeze #: Array[Symbol]
|
|
56
|
+
|
|
57
|
+
attr_reader :to_h #: Hash[Symbol, untyped]
|
|
58
|
+
|
|
59
|
+
#: (Hash[Symbol, untyped], ?Array[Symbol], ?String) -> String
|
|
60
|
+
def self.serialize(options, allowed = KNOWN, subject = "a call")
|
|
61
|
+
new(options, allowed, subject).to_json
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
#: (Hash[Symbol, untyped], ?Array[Symbol], ?String) -> void
|
|
65
|
+
def initialize(options, allowed = KNOWN, subject = "a call")
|
|
66
|
+
given = options.transform_keys(&:to_sym)
|
|
67
|
+
|
|
68
|
+
validate!(given.keys, allowed, subject)
|
|
69
|
+
|
|
70
|
+
@to_h = normalize(given).freeze
|
|
71
|
+
|
|
72
|
+
freeze
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
#: (?JSON::options?) -> String
|
|
76
|
+
def to_json(state = nil)
|
|
77
|
+
state ? JSON.generate(to_h, state) : JSON.generate(to_h)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
#: () -> String
|
|
81
|
+
def inspect
|
|
82
|
+
"#<#{self.class.name} #{to_h.inspect}>"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
#: (Array[Symbol], Array[Symbol], String) -> void
|
|
88
|
+
def validate!(names, allowed, subject)
|
|
89
|
+
unsupported = names & UNSUPPORTED
|
|
90
|
+
|
|
91
|
+
raise OptionError, "#{unsupported.join(", ")} is not supported yet" if unsupported.any?
|
|
92
|
+
|
|
93
|
+
unknown = names - KNOWN
|
|
94
|
+
|
|
95
|
+
raise OptionError, "Unknown option#{"s" if unknown.length > 1}: #{unknown.join(", ")}" if unknown.any?
|
|
96
|
+
|
|
97
|
+
unsupported = names - allowed
|
|
98
|
+
|
|
99
|
+
return if unsupported.empty?
|
|
100
|
+
|
|
101
|
+
raise OptionError, "#{unsupported.join(", ")} #{unsupported.one? ? "is not an option" : "are not options"} for #{subject}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
#: (Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
|
|
105
|
+
def normalize(options)
|
|
106
|
+
normalized = options.dup
|
|
107
|
+
|
|
108
|
+
RUBY_ONLY.each { |name| normalized.delete(name) }
|
|
109
|
+
|
|
110
|
+
normalized.compact
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Oxc
|
|
4
|
+
class ParseResult
|
|
5
|
+
include Diagnosed
|
|
6
|
+
|
|
7
|
+
attr_reader :source #: String?
|
|
8
|
+
attr_reader :program #: Hash[String, untyped]?
|
|
9
|
+
attr_reader :module_record #: Hash[String, untyped]?
|
|
10
|
+
attr_reader :symbols #: Hash[String, untyped]?
|
|
11
|
+
attr_reader :comments #: Array[Oxc::Comment]
|
|
12
|
+
attr_reader :diagnostics #: Array[Oxc::Diagnostic]
|
|
13
|
+
|
|
14
|
+
#: (String, ?String?) -> Oxc::ParseResult
|
|
15
|
+
def self.from_json(payload, source = nil)
|
|
16
|
+
parsed = JSON.parse(payload)
|
|
17
|
+
|
|
18
|
+
new(
|
|
19
|
+
source: source,
|
|
20
|
+
program: parsed["program"],
|
|
21
|
+
module_record: parsed["module_record"],
|
|
22
|
+
symbols: parsed["symbols"],
|
|
23
|
+
comments: parsed.fetch("comments").map { |comment| Comment.from_hash(comment) },
|
|
24
|
+
diagnostics: parsed.fetch("errors").map { |diagnostic| Diagnostic.from_hash(diagnostic) },
|
|
25
|
+
panicked: parsed.fetch("panicked")
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
#: (comments: Array[Oxc::Comment], diagnostics: Array[Oxc::Diagnostic], ?source: String?, ?program: Hash[String, untyped]?, ?module_record: Hash[String, untyped]?, ?symbols: Hash[String, untyped]?, ?panicked: bool) -> void
|
|
30
|
+
def initialize(comments:, diagnostics:, source: nil, program: nil, module_record: nil, symbols: nil, panicked: false)
|
|
31
|
+
@source = source
|
|
32
|
+
@program = program.freeze
|
|
33
|
+
@module_record = module_record.freeze
|
|
34
|
+
@symbols = symbols.freeze
|
|
35
|
+
@comments = comments.freeze
|
|
36
|
+
@diagnostics = diagnostics.freeze
|
|
37
|
+
@panicked = panicked
|
|
38
|
+
|
|
39
|
+
freeze
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
#: () -> Oxc::Node?
|
|
43
|
+
def root
|
|
44
|
+
program ? Node.new(program, nil, source) : nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
#: () -> Oxc::ParseResult
|
|
48
|
+
def validate!
|
|
49
|
+
return self unless errors? || panicked?
|
|
50
|
+
|
|
51
|
+
raise SyntaxError.new(errors.first&.message || "oxc could not read the source", self)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
#: () -> String
|
|
55
|
+
def inspect
|
|
56
|
+
parts = [] #: Array[String]
|
|
57
|
+
parts << "#{root&.type} range=[#{root&.start}, #{root&.finish}]" if program
|
|
58
|
+
parts << "module_record" if module_record
|
|
59
|
+
parts << "symbols=#{symbols.fetch("declared").length}" if symbols
|
|
60
|
+
parts << "comments=#{comments.length}" unless comments.empty?
|
|
61
|
+
parts << "diagnostics=#{diagnostics.length}" unless diagnostics.empty?
|
|
62
|
+
|
|
63
|
+
"#<#{self.class.name}#{" #{parts.join(" ")}" unless parts.empty?}>"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class Comment
|
|
68
|
+
LINE = "Line" #: String
|
|
69
|
+
BLOCK = "Block" #: String
|
|
70
|
+
|
|
71
|
+
attr_reader :type #: String
|
|
72
|
+
attr_reader :value #: String
|
|
73
|
+
attr_reader :start #: Integer
|
|
74
|
+
attr_reader :finish #: Integer
|
|
75
|
+
|
|
76
|
+
#: (Hash[String, untyped]) -> Oxc::Comment
|
|
77
|
+
def self.from_hash(parsed)
|
|
78
|
+
new(
|
|
79
|
+
type: parsed.fetch("type"),
|
|
80
|
+
value: parsed.fetch("value"),
|
|
81
|
+
start: parsed.fetch("start"),
|
|
82
|
+
finish: parsed.fetch("end")
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
#: (type: String, value: String, start: Integer, finish: Integer) -> void
|
|
87
|
+
def initialize(type:, value:, start:, finish:)
|
|
88
|
+
@type = type
|
|
89
|
+
@value = value
|
|
90
|
+
@start = start
|
|
91
|
+
@finish = finish
|
|
92
|
+
|
|
93
|
+
freeze
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
#: () -> bool
|
|
97
|
+
def line?
|
|
98
|
+
type == LINE
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
#: () -> bool
|
|
102
|
+
def block?
|
|
103
|
+
type == BLOCK
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
#: (String) -> String?
|
|
107
|
+
def slice(source)
|
|
108
|
+
source.byteslice(start, finish - start)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
#: () -> String
|
|
112
|
+
def inspect
|
|
113
|
+
"#<#{self.class.name} #{type} #{value.inspect}>"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
data/lib/oxc/result.rb
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Oxc
|
|
4
|
+
class Result
|
|
5
|
+
include Diagnosed
|
|
6
|
+
|
|
7
|
+
attr_reader :code #: String
|
|
8
|
+
attr_reader :map #: String?
|
|
9
|
+
attr_reader :legal_comments #: Array[String]
|
|
10
|
+
attr_reader :diagnostics #: Array[Oxc::Diagnostic]
|
|
11
|
+
|
|
12
|
+
#: (code: String, diagnostics: Array[Oxc::Diagnostic], ?map: String?, ?legal_comments: Array[String], ?panicked: bool) -> void
|
|
13
|
+
def initialize(code:, diagnostics:, map: nil, legal_comments: [], panicked: false)
|
|
14
|
+
@code = code
|
|
15
|
+
@map = map
|
|
16
|
+
@legal_comments = legal_comments.freeze
|
|
17
|
+
@diagnostics = diagnostics.freeze
|
|
18
|
+
@panicked = panicked
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#: (?strict: bool?) -> self
|
|
22
|
+
def validate!(strict: false)
|
|
23
|
+
return self unless errors? || panicked?
|
|
24
|
+
return self unless strict || panicked? || code.empty?
|
|
25
|
+
|
|
26
|
+
raise SyntaxError.new(errors.first&.message || "oxc could not read the source", self)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
#: () -> String
|
|
30
|
+
def to_s
|
|
31
|
+
code
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
#: () -> String
|
|
35
|
+
def inspect
|
|
36
|
+
"#<#{self.class.name} #{parts.join(" ")}>"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
#: () -> Array[String]
|
|
42
|
+
def parts
|
|
43
|
+
parts = ["code=#{code.inspect}"] #: Array[String]
|
|
44
|
+
parts << "map=#{map.length} bytes" if map
|
|
45
|
+
parts << "legal_comments=#{legal_comments.inspect}" unless legal_comments.empty?
|
|
46
|
+
parts << "diagnostics=#{diagnostics.length}" unless diagnostics.empty?
|
|
47
|
+
|
|
48
|
+
parts
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Oxc
|
|
4
|
+
class TransformResult < Result
|
|
5
|
+
attr_reader :declaration #: String?
|
|
6
|
+
attr_reader :declaration_map #: String?
|
|
7
|
+
attr_reader :helpers_used #: Hash[String, String]
|
|
8
|
+
|
|
9
|
+
#: (String) -> Oxc::TransformResult
|
|
10
|
+
def self.from_json(payload)
|
|
11
|
+
parsed = JSON.parse(payload)
|
|
12
|
+
|
|
13
|
+
new(
|
|
14
|
+
code: parsed.fetch("code"),
|
|
15
|
+
map: parsed["map"],
|
|
16
|
+
declaration: parsed["declaration"],
|
|
17
|
+
declaration_map: parsed["declaration_map"],
|
|
18
|
+
legal_comments: parsed.fetch("legal_comments"),
|
|
19
|
+
helpers_used: parsed.fetch("helpers_used"),
|
|
20
|
+
diagnostics: parsed.fetch("errors").map { |diagnostic| Diagnostic.from_hash(diagnostic) },
|
|
21
|
+
panicked: parsed.fetch("panicked")
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#: (code: String, diagnostics: Array[Oxc::Diagnostic], ?map: String?, ?declaration: String?, ?declaration_map: String?, ?legal_comments: Array[String], ?helpers_used: Hash[String, String], ?panicked: bool) -> void
|
|
26
|
+
def initialize(declaration: nil, declaration_map: nil, helpers_used: {}, **)
|
|
27
|
+
super(**)
|
|
28
|
+
|
|
29
|
+
@declaration = declaration
|
|
30
|
+
@declaration_map = declaration_map
|
|
31
|
+
@helpers_used = helpers_used.freeze
|
|
32
|
+
|
|
33
|
+
freeze
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
#: () -> Array[String]
|
|
39
|
+
def parts
|
|
40
|
+
parts = super
|
|
41
|
+
parts << "declaration=#{declaration.length} bytes" if declaration
|
|
42
|
+
parts << "helpers_used=#{helpers_used.length}" unless helpers_used.empty?
|
|
43
|
+
|
|
44
|
+
parts
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Oxc
|
|
4
|
+
class Transformer
|
|
5
|
+
attr_reader :options #: Hash[Symbol, untyped]
|
|
6
|
+
|
|
7
|
+
#: (?filename: String?, ?lang: String?, ?source_type: String?, ?cwd: String?, ?target: targets?, ?jsx: jsx?, ?typescript: typescript?, ?assumptions: assumptions?, ?decorator: decorator?, ?helpers: helpers?, ?define: Hash[String, String]?, ?inject: inject?, ?minify: minify?, ?codegen: codegen?, ?sourcemap: bool, ?strict: bool) -> void
|
|
8
|
+
def initialize(**options)
|
|
9
|
+
@options = options.transform_keys(&:to_sym).freeze
|
|
10
|
+
|
|
11
|
+
freeze
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
#: (String, ?filename: String?, ?lang: String?, ?source_type: String?, ?cwd: String?, ?target: targets?, ?jsx: jsx?, ?typescript: typescript?, ?assumptions: assumptions?, ?decorator: decorator?, ?helpers: helpers?, ?define: Hash[String, String]?, ?inject: inject?, ?minify: minify?, ?codegen: codegen?, ?sourcemap: bool, ?strict: bool) -> Oxc::TransformResult
|
|
15
|
+
def transform(source, **overrides)
|
|
16
|
+
Oxc.transform(source, **options, **overrides)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
alias call transform
|
|
20
|
+
|
|
21
|
+
#: (?filename: String?, ?lang: String?, ?source_type: String?, ?cwd: String?, ?target: targets?, ?jsx: jsx?, ?typescript: typescript?, ?assumptions: assumptions?, ?decorator: decorator?, ?helpers: helpers?, ?define: Hash[String, String]?, ?inject: inject?, ?minify: minify?, ?codegen: codegen?, ?sourcemap: bool, ?strict: bool) -> Oxc::Transformer
|
|
22
|
+
def with(**overrides)
|
|
23
|
+
self.class.new(**options, **overrides)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
#: () -> String
|
|
27
|
+
def inspect
|
|
28
|
+
"#<#{self.class.name} #{options.inspect}>"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/oxc/version.rb
CHANGED
data/lib/oxc/visitor.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Oxc
|
|
4
|
+
class Visitor
|
|
5
|
+
#: (Oxc::Node | Oxc::ParseResult) -> void
|
|
6
|
+
def visit(node)
|
|
7
|
+
node = node.root if node.is_a?(ParseResult)
|
|
8
|
+
|
|
9
|
+
return nil unless node
|
|
10
|
+
|
|
11
|
+
answer = "visit_#{node.underscored_type}"
|
|
12
|
+
|
|
13
|
+
respond_to?(answer) ? public_send(answer, node) : visit_children(node)
|
|
14
|
+
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
#: (Oxc::Node) -> void
|
|
19
|
+
def visit_children(node)
|
|
20
|
+
node.child_nodes.each { |child| visit(child) }
|
|
21
|
+
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/oxc.rb
CHANGED
|
@@ -1,6 +1,55 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
3
5
|
require_relative "oxc/version"
|
|
6
|
+
require_relative "oxc/errors"
|
|
7
|
+
require_relative "oxc/backend"
|
|
8
|
+
|
|
9
|
+
begin
|
|
10
|
+
major, minor, = RUBY_VERSION.split(".")
|
|
11
|
+
require_relative "oxc/#{major}.#{minor}/oxc"
|
|
12
|
+
rescue LoadError
|
|
13
|
+
require_relative "oxc/oxc"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
require_relative "oxc/options"
|
|
17
|
+
require_relative "oxc/diagnostic"
|
|
18
|
+
require_relative "oxc/diagnosed"
|
|
19
|
+
require_relative "oxc/result"
|
|
20
|
+
require_relative "oxc/minify_result"
|
|
21
|
+
require_relative "oxc/transform_result"
|
|
22
|
+
require_relative "oxc/node"
|
|
23
|
+
require_relative "oxc/visitor"
|
|
24
|
+
require_relative "oxc/mutation_visitor"
|
|
25
|
+
require_relative "oxc/parse_result"
|
|
26
|
+
require_relative "oxc/transformer"
|
|
27
|
+
require_relative "oxc/minifier"
|
|
4
28
|
|
|
5
29
|
module Oxc
|
|
30
|
+
#: (String, ?filename: String?, ?lang: String?, ?source_type: String?, ?compress: compress?, ?mangle: mangle?, ?codegen: codegen?, ?sourcemap: bool, ?strict: bool) -> Oxc::MinifyResult
|
|
31
|
+
def self.minify(source, **options)
|
|
32
|
+
serialized = Options.serialize(options, Options::MINIFY, "minify")
|
|
33
|
+
|
|
34
|
+
MinifyResult.from_json(Backend.minify(source.to_s, serialized)).validate!(strict: options[:strict] ? true : false)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
#: (String, ?filename: String?, ?lang: String?, ?source_type: String?, ?cwd: String?, ?target: targets?, ?jsx: jsx?, ?typescript: typescript?, ?assumptions: assumptions?, ?decorator: decorator?, ?helpers: helpers?, ?define: Hash[String, String]?, ?inject: inject?, ?minify: minify?, ?codegen: codegen?, ?sourcemap: bool, ?strict: bool) -> Oxc::TransformResult
|
|
38
|
+
def self.transform(source, **options)
|
|
39
|
+
serialized = Options.serialize(options, Options::TRANSFORM, "transform")
|
|
40
|
+
|
|
41
|
+
TransformResult.from_json(Backend.transform(source.to_s, serialized)).validate!(strict: options[:strict] ? true : false)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
#: (String, ?filename: String?, ?lang: String?, ?source_type: String?, ?ast_type: String?, ?ast: bool, ?ranges: bool, ?preserve_parens: bool, ?comments: bool, ?module_record: bool, ?symbols: bool, ?semantic_errors: bool) -> Oxc::ParseResult
|
|
45
|
+
def self.parse(source, **options)
|
|
46
|
+
serialized = Options.serialize(options, Options::PARSE, "parse")
|
|
47
|
+
|
|
48
|
+
ParseResult.from_json(Backend.parse(source.to_s, serialized), source.to_s)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
#: () -> String
|
|
52
|
+
def self.oxc_version
|
|
53
|
+
Backend.oxc_version
|
|
54
|
+
end
|
|
6
55
|
end
|
data/licenses/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Licenses of what this gem builds against
|
|
2
|
+
|
|
3
|
+
A precompiled gem ships a native extension with [Oxc](https://github.com/oxc-project/oxc) compiled into it, so the terms that cover Oxc cover part of what is being distributed.
|
|
4
|
+
|
|
5
|
+
They are carried here so that whoever received the gem has them in hand.
|
|
6
|
+
|
|
7
|
+
| File | Covers | License |
|
|
8
|
+
|-----------------------|---------------------------------------------------------|------------|
|
|
9
|
+
| `oxc-MIT.txt` | Oxc itself | MIT |
|
|
10
|
+
| `oxc-THIRD-PARTY.txt` | the code Oxc carries from TypeScript and from `miette` | Apache-2.0 |
|
|
11
|
+
|
|
12
|
+
The gem's own Ruby, C, and Rust code is MIT, in `LICENSE.txt` at the root.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present VoidZero Inc. & Contributors
|
|
4
|
+
Copyright (c) 2023 Boshen
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|