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/ext/oxc/extconf.rb
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mkmf"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
ext_dir = __dir__
|
|
7
|
+
root_dir = File.expand_path("../..", ext_dir)
|
|
8
|
+
|
|
9
|
+
rust_dir = File.join(root_dir, "rust")
|
|
10
|
+
|
|
11
|
+
unless File.exist?(File.join(rust_dir, "Cargo.toml"))
|
|
12
|
+
abort <<~MESSAGE
|
|
13
|
+
|
|
14
|
+
ERROR: Rust sources not found at #{rust_dir}.
|
|
15
|
+
|
|
16
|
+
MESSAGE
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
unless system("cargo --version > /dev/null 2>&1")
|
|
20
|
+
abort <<~MESSAGE
|
|
21
|
+
|
|
22
|
+
ERROR: Rust toolchain not found.
|
|
23
|
+
|
|
24
|
+
oxc requires the Rust toolchain to compile from source.
|
|
25
|
+
|
|
26
|
+
Install Rust: https://rustup.rs
|
|
27
|
+
|
|
28
|
+
MESSAGE
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
RUST_TARGETS = {
|
|
32
|
+
"aarch64-linux-gnu" => "aarch64-unknown-linux-gnu",
|
|
33
|
+
"aarch64-linux-musl" => "aarch64-unknown-linux-musl",
|
|
34
|
+
"arm-linux-gnu" => "armv7-unknown-linux-gnueabihf",
|
|
35
|
+
"arm-linux-musl" => "armv7-unknown-linux-musleabihf",
|
|
36
|
+
"arm64-darwin" => "aarch64-apple-darwin",
|
|
37
|
+
"x86_64-darwin" => "x86_64-apple-darwin",
|
|
38
|
+
"x86_64-linux-gnu" => "x86_64-unknown-linux-gnu",
|
|
39
|
+
"x86_64-linux-musl" => "x86_64-unknown-linux-musl",
|
|
40
|
+
"x86-linux-gnu" => "i686-unknown-linux-gnu",
|
|
41
|
+
"x86-linux-musl" => "i686-unknown-linux-musl",
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
cross_compiling = ENV.key?("RUBY_CC_VERSION")
|
|
45
|
+
target_platform = ENV.fetch("CARGO_BUILD_TARGET", nil)
|
|
46
|
+
|
|
47
|
+
if cross_compiling && target_platform.nil?
|
|
48
|
+
rcd_platform = ENV.fetch("RCD_PLATFORM", "")
|
|
49
|
+
target_platform = RUST_TARGETS[rcd_platform]
|
|
50
|
+
|
|
51
|
+
if target_platform.nil?
|
|
52
|
+
ruby_platform = RbConfig::CONFIG["arch"]
|
|
53
|
+
target_platform = RUST_TARGETS.values.find { |target| ruby_platform.include?(target.split("-").first) }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
header_path = File.join(ext_dir, "include", "oxc.h")
|
|
58
|
+
|
|
59
|
+
FileUtils.mkdir_p(File.dirname(header_path))
|
|
60
|
+
|
|
61
|
+
target_dir = File.join(rust_dir, "target")
|
|
62
|
+
|
|
63
|
+
if target_platform
|
|
64
|
+
puts "oxc: Cross-compiling Rust for target: #{target_platform}"
|
|
65
|
+
|
|
66
|
+
system("rustup target add #{target_platform}") || warn("oxc: Failed to add Rust target #{target_platform}")
|
|
67
|
+
|
|
68
|
+
cargo_args = "--release --locked --target #{target_platform}"
|
|
69
|
+
lib_dir = File.join(target_dir, target_platform, "release")
|
|
70
|
+
else
|
|
71
|
+
puts "oxc: Compiling Rust library for native platform..."
|
|
72
|
+
|
|
73
|
+
cargo_args = "--release --locked"
|
|
74
|
+
lib_dir = File.join(target_dir, "release")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
unless system("cd #{rust_dir} && cargo build #{cargo_args}")
|
|
78
|
+
abort "ERROR: Failed to compile oxc from Rust source."
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
unless File.exist?(header_path)
|
|
82
|
+
abort "ERROR: cbindgen did not generate #{header_path}. Try `cargo clean` in #{rust_dir} and reinstall."
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
static_lib = File.join(lib_dir, "liboxc_ffi.a")
|
|
86
|
+
|
|
87
|
+
developing = File.exist?(File.join(root_dir, ".git"))
|
|
88
|
+
|
|
89
|
+
if File.exist?(static_lib) && !developing
|
|
90
|
+
vendored = File.join(ext_dir, "liboxc_ffi.a")
|
|
91
|
+
|
|
92
|
+
FileUtils.cp(static_lib, vendored)
|
|
93
|
+
FileUtils.rm_rf(target_dir)
|
|
94
|
+
|
|
95
|
+
puts "oxc: Static library vendored at #{vendored}, Rust build directory removed"
|
|
96
|
+
|
|
97
|
+
$LDFLAGS << " #{vendored}"
|
|
98
|
+
elsif File.exist?(static_lib)
|
|
99
|
+
puts "oxc: Static library found at #{static_lib}"
|
|
100
|
+
|
|
101
|
+
$LDFLAGS << " #{static_lib}"
|
|
102
|
+
else
|
|
103
|
+
host_os = target_platform || RbConfig::CONFIG["host_os"]
|
|
104
|
+
|
|
105
|
+
lib_name = case host_os
|
|
106
|
+
when /darwin/ then "liboxc_ffi.dylib"
|
|
107
|
+
when /mingw|mswin|windows/ then "oxc_ffi.dll"
|
|
108
|
+
else "liboxc_ffi.so"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
lib_path = File.join(lib_dir, lib_name)
|
|
112
|
+
|
|
113
|
+
abort "ERROR: Shared library not found at #{lib_path}" unless File.exist?(lib_path)
|
|
114
|
+
|
|
115
|
+
puts "oxc: Shared library found at #{lib_path} (dynamic)"
|
|
116
|
+
|
|
117
|
+
$LDFLAGS << " -L#{lib_dir} -loxc_ffi"
|
|
118
|
+
$LDFLAGS << " -Wl,-rpath,#{lib_dir}" if RbConfig::CONFIG["host_os"].match?(/darwin|linux/)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
$CFLAGS << " -I#{ext_dir}"
|
|
122
|
+
|
|
123
|
+
create_makefile("oxc/oxc")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/* Generated by cbindgen — do not edit manually */
|
|
2
|
+
|
|
3
|
+
#include <stdbool.h>
|
|
4
|
+
#include <stdint.h>
|
|
5
|
+
#include <stddef.h>
|
|
6
|
+
|
|
7
|
+
#ifndef OXC_H
|
|
8
|
+
#define OXC_H
|
|
9
|
+
|
|
10
|
+
typedef enum OxcErrorCode {
|
|
11
|
+
OXC_ERROR_CODE_NONE = 0,
|
|
12
|
+
OXC_ERROR_CODE_OPTION,
|
|
13
|
+
OXC_ERROR_CODE_ENCODING,
|
|
14
|
+
OXC_ERROR_CODE_TRANSFORM,
|
|
15
|
+
OXC_ERROR_CODE_INTERNAL,
|
|
16
|
+
OXC_ERROR_CODE_PANIC,
|
|
17
|
+
} OxcErrorCode;
|
|
18
|
+
|
|
19
|
+
typedef struct OxcResult {
|
|
20
|
+
char *value;
|
|
21
|
+
uintptr_t value_len;
|
|
22
|
+
char *error;
|
|
23
|
+
enum OxcErrorCode code;
|
|
24
|
+
} OxcResult;
|
|
25
|
+
|
|
26
|
+
struct OxcResult oxc_parse(const char *source, const char *options_json);
|
|
27
|
+
|
|
28
|
+
struct OxcResult oxc_transform(const char *source, const char *options_json);
|
|
29
|
+
|
|
30
|
+
struct OxcResult oxc_minify(const char *source, const char *options_json);
|
|
31
|
+
|
|
32
|
+
char *oxc_version(void);
|
|
33
|
+
|
|
34
|
+
char *oxc_oxc_version(void);
|
|
35
|
+
|
|
36
|
+
void oxc_string_free(char *value);
|
|
37
|
+
|
|
38
|
+
void oxc_result_free(struct OxcResult result);
|
|
39
|
+
|
|
40
|
+
#endif /* OXC_H */
|
data/ext/oxc/oxc.c
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#include <ruby.h>
|
|
2
|
+
#include <ruby/encoding.h>
|
|
3
|
+
#include <ruby/thread.h>
|
|
4
|
+
#include "include/oxc.h"
|
|
5
|
+
|
|
6
|
+
static VALUE rb_mOxc;
|
|
7
|
+
static VALUE rb_mBackend;
|
|
8
|
+
static VALUE rb_eError;
|
|
9
|
+
static VALUE rb_eOptionError;
|
|
10
|
+
static VALUE rb_eOxcEncodingError;
|
|
11
|
+
static VALUE rb_eTransformError;
|
|
12
|
+
static VALUE rb_eInternalError;
|
|
13
|
+
static VALUE rb_ePanicError;
|
|
14
|
+
|
|
15
|
+
typedef struct OxcResult (*oxc_function)(const char *, const char *);
|
|
16
|
+
|
|
17
|
+
struct call_arguments {
|
|
18
|
+
oxc_function function;
|
|
19
|
+
const char *source;
|
|
20
|
+
const char *options;
|
|
21
|
+
struct OxcResult result;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
static VALUE make_utf8_string(const char *cstring) {
|
|
25
|
+
return rb_enc_str_new_cstr(cstring, rb_utf8_encoding());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static VALUE take_utf8_string(char *cstring) {
|
|
29
|
+
if (!cstring) return Qnil;
|
|
30
|
+
|
|
31
|
+
VALUE string = make_utf8_string(cstring);
|
|
32
|
+
oxc_string_free(cstring);
|
|
33
|
+
|
|
34
|
+
return string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static VALUE error_class_for(enum OxcErrorCode code) {
|
|
38
|
+
switch (code) {
|
|
39
|
+
case OXC_ERROR_CODE_OPTION: return rb_eOptionError;
|
|
40
|
+
case OXC_ERROR_CODE_ENCODING: return rb_eOxcEncodingError;
|
|
41
|
+
case OXC_ERROR_CODE_TRANSFORM: return rb_eTransformError;
|
|
42
|
+
case OXC_ERROR_CODE_PANIC: return rb_ePanicError;
|
|
43
|
+
default: return rb_eInternalError;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static VALUE unwrap(struct OxcResult result) {
|
|
48
|
+
if (result.error) {
|
|
49
|
+
VALUE message = make_utf8_string(result.error);
|
|
50
|
+
VALUE error_class = error_class_for(result.code);
|
|
51
|
+
|
|
52
|
+
oxc_result_free(result);
|
|
53
|
+
|
|
54
|
+
rb_raise(error_class, "%s", StringValueCStr(message));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!result.value) {
|
|
58
|
+
oxc_result_free(result);
|
|
59
|
+
|
|
60
|
+
rb_raise(rb_eInternalError, "oxc returned no result");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
VALUE value = rb_enc_str_new(result.value, (long) result.value_len, rb_utf8_encoding());
|
|
64
|
+
|
|
65
|
+
oxc_result_free(result);
|
|
66
|
+
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static void *without_gvl(void *data) {
|
|
71
|
+
struct call_arguments *arguments = (struct call_arguments *) data;
|
|
72
|
+
|
|
73
|
+
arguments->result = arguments->function(arguments->source, arguments->options);
|
|
74
|
+
|
|
75
|
+
return NULL;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static VALUE call(oxc_function function, VALUE source, VALUE options) {
|
|
79
|
+
struct call_arguments arguments;
|
|
80
|
+
|
|
81
|
+
arguments.function = function;
|
|
82
|
+
arguments.source = StringValueCStr(source);
|
|
83
|
+
arguments.options = StringValueCStr(options);
|
|
84
|
+
|
|
85
|
+
rb_thread_call_without_gvl(without_gvl, &arguments, NULL, NULL);
|
|
86
|
+
|
|
87
|
+
return unwrap(arguments.result);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static VALUE rb_minify(VALUE self, VALUE source, VALUE options) {
|
|
91
|
+
(void) self;
|
|
92
|
+
|
|
93
|
+
return call(oxc_minify, source, options);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static VALUE rb_transform(VALUE self, VALUE source, VALUE options) {
|
|
97
|
+
(void) self;
|
|
98
|
+
|
|
99
|
+
return call(oxc_transform, source, options);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static VALUE rb_parse(VALUE self, VALUE source, VALUE options) {
|
|
103
|
+
(void) self;
|
|
104
|
+
|
|
105
|
+
return call(oxc_parse, source, options);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
static VALUE rb_native_version(VALUE self) {
|
|
109
|
+
(void) self;
|
|
110
|
+
|
|
111
|
+
return take_utf8_string(oxc_version());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static VALUE rb_oxc_version(VALUE self) {
|
|
115
|
+
(void) self;
|
|
116
|
+
|
|
117
|
+
return take_utf8_string(oxc_oxc_version());
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
void Init_oxc(void) {
|
|
121
|
+
rb_mOxc = rb_define_module("Oxc");
|
|
122
|
+
rb_mBackend = rb_define_module_under(rb_mOxc, "Backend");
|
|
123
|
+
|
|
124
|
+
rb_eError = rb_define_class_under(rb_mOxc, "Error", rb_eStandardError);
|
|
125
|
+
rb_eOptionError = rb_define_class_under(rb_mOxc, "OptionError", rb_eError);
|
|
126
|
+
rb_eOxcEncodingError = rb_define_class_under(rb_mOxc, "EncodingError", rb_eError);
|
|
127
|
+
rb_eTransformError = rb_define_class_under(rb_mOxc, "TransformError", rb_eError);
|
|
128
|
+
rb_eInternalError = rb_define_class_under(rb_mOxc, "InternalError", rb_eError);
|
|
129
|
+
rb_ePanicError = rb_define_class_under(rb_mOxc, "PanicError", rb_eInternalError);
|
|
130
|
+
|
|
131
|
+
rb_define_singleton_method(rb_mBackend, "minify", rb_minify, 2);
|
|
132
|
+
rb_define_singleton_method(rb_mBackend, "transform", rb_transform, 2);
|
|
133
|
+
rb_define_singleton_method(rb_mBackend, "parse", rb_parse, 2);
|
|
134
|
+
rb_define_singleton_method(rb_mBackend, "version", rb_native_version, 0);
|
|
135
|
+
rb_define_singleton_method(rb_mBackend, "oxc_version", rb_oxc_version, 0);
|
|
136
|
+
}
|
data/lib/oxc/backend.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Oxc
|
|
4
|
+
module Backend
|
|
5
|
+
module Unavailable
|
|
6
|
+
#: (String, String) -> String
|
|
7
|
+
def minify(_source, _options_json)
|
|
8
|
+
unavailable(__method__)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
#: (String, String) -> String
|
|
12
|
+
def transform(_source, _options_json)
|
|
13
|
+
unavailable(__method__)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
#: (String, String) -> String
|
|
17
|
+
def parse(_source, _options_json)
|
|
18
|
+
unavailable(__method__)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#: () -> String
|
|
22
|
+
def version
|
|
23
|
+
unavailable(__method__)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
#: () -> String
|
|
27
|
+
def oxc_version
|
|
28
|
+
unavailable(__method__)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
#: (Symbol?) -> bot
|
|
34
|
+
def unavailable(name)
|
|
35
|
+
raise NotImplementedError, "Oxc::Backend.#{name} is defined by the native extension, which did not load"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
extend Unavailable
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Oxc
|
|
4
|
+
# @rbs module-self _Diagnosed
|
|
5
|
+
module Diagnosed
|
|
6
|
+
# @rbs @panicked: bool
|
|
7
|
+
|
|
8
|
+
#: () -> Array[Oxc::Diagnostic]
|
|
9
|
+
def errors
|
|
10
|
+
diagnostics.select(&:error?)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
#: () -> Array[Oxc::Diagnostic]
|
|
14
|
+
def warnings
|
|
15
|
+
diagnostics.select(&:warning?)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
#: () -> bool
|
|
19
|
+
def errors?
|
|
20
|
+
!errors.empty?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
#: () -> bool
|
|
24
|
+
def warnings?
|
|
25
|
+
!warnings.empty?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
#: () -> bool
|
|
29
|
+
def panicked?
|
|
30
|
+
@panicked
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Oxc
|
|
4
|
+
class Diagnostic
|
|
5
|
+
ERROR = "error" #: String
|
|
6
|
+
WARNING = "warning" #: String
|
|
7
|
+
|
|
8
|
+
attr_reader :severity #: String
|
|
9
|
+
attr_reader :message #: String
|
|
10
|
+
attr_reader :labels #: Array[Oxc::Label]
|
|
11
|
+
attr_reader :help #: String?
|
|
12
|
+
attr_reader :codeframe #: String?
|
|
13
|
+
|
|
14
|
+
#: (Hash[String, untyped]) -> Oxc::Diagnostic
|
|
15
|
+
def self.from_hash(parsed)
|
|
16
|
+
new(
|
|
17
|
+
severity: parsed.fetch("severity"),
|
|
18
|
+
message: parsed.fetch("message"),
|
|
19
|
+
labels: parsed.fetch("labels").map { |label| Label.from_hash(label) },
|
|
20
|
+
help: parsed["help"],
|
|
21
|
+
codeframe: parsed["codeframe"]
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#: (severity: String, message: String, labels: Array[Oxc::Label], ?help: String?, ?codeframe: String?) -> void
|
|
26
|
+
def initialize(severity:, message:, labels:, help: nil, codeframe: nil)
|
|
27
|
+
@severity = severity
|
|
28
|
+
@message = message
|
|
29
|
+
@labels = labels.freeze
|
|
30
|
+
@help = help
|
|
31
|
+
@codeframe = codeframe
|
|
32
|
+
|
|
33
|
+
freeze
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#: () -> bool
|
|
37
|
+
def error?
|
|
38
|
+
severity == ERROR
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
#: () -> bool
|
|
42
|
+
def warning?
|
|
43
|
+
severity == WARNING
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
#: () -> String
|
|
47
|
+
def to_s
|
|
48
|
+
message
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
#: () -> String
|
|
52
|
+
def inspect
|
|
53
|
+
"#<#{self.class.name} #{severity} #{message.inspect}>"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class Label
|
|
58
|
+
attr_reader :message #: String?
|
|
59
|
+
attr_reader :start #: Integer
|
|
60
|
+
attr_reader :finish #: Integer
|
|
61
|
+
|
|
62
|
+
#: (Hash[String, untyped]) -> Oxc::Label
|
|
63
|
+
def self.from_hash(parsed)
|
|
64
|
+
new(message: parsed["message"], start: parsed.fetch("start"), finish: parsed.fetch("end"))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
#: (start: Integer, finish: Integer, ?message: String?) -> void
|
|
68
|
+
def initialize(start:, finish:, message: nil)
|
|
69
|
+
@start = start
|
|
70
|
+
@finish = finish
|
|
71
|
+
@message = message
|
|
72
|
+
|
|
73
|
+
freeze
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
#: (String) -> String?
|
|
77
|
+
def slice(source)
|
|
78
|
+
source.byteslice(start, finish - start)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
#: () -> String
|
|
82
|
+
def inspect
|
|
83
|
+
"#<#{self.class.name} range=[#{start}, #{finish}]#{" #{message.inspect}" if message}>"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
data/lib/oxc/errors.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Oxc
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
class OptionError < Error; end
|
|
6
|
+
class EncodingError < Error; end
|
|
7
|
+
class TransformError < Error; end
|
|
8
|
+
class InternalError < Error; end
|
|
9
|
+
class PanicError < InternalError; end
|
|
10
|
+
|
|
11
|
+
class SyntaxError < Error
|
|
12
|
+
attr_reader :result #: (Oxc::Result | Oxc::ParseResult)?
|
|
13
|
+
|
|
14
|
+
#: (String, ?(Oxc::Result | Oxc::ParseResult)?) -> void
|
|
15
|
+
def initialize(message, result = nil)
|
|
16
|
+
super(message)
|
|
17
|
+
|
|
18
|
+
@result = result
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#: () -> Array[Oxc::Diagnostic]
|
|
22
|
+
def diagnostics
|
|
23
|
+
result ? result.diagnostics : []
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/oxc/minifier.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Oxc
|
|
4
|
+
class Minifier
|
|
5
|
+
attr_reader :options #: Hash[Symbol, untyped]
|
|
6
|
+
|
|
7
|
+
#: (?filename: String?, ?lang: String?, ?source_type: String?, ?compress: compress?, ?mangle: mangle?, ?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?, ?compress: compress?, ?mangle: mangle?, ?codegen: codegen?, ?sourcemap: bool, ?strict: bool) -> Oxc::MinifyResult
|
|
15
|
+
def minify(source, **overrides)
|
|
16
|
+
Oxc.minify(source, **options, **overrides)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
alias call minify
|
|
20
|
+
|
|
21
|
+
#: (?filename: String?, ?lang: String?, ?source_type: String?, ?compress: compress?, ?mangle: mangle?, ?codegen: codegen?, ?sourcemap: bool, ?strict: bool) -> Oxc::Minifier
|
|
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
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Oxc
|
|
4
|
+
class MinifyResult < Result
|
|
5
|
+
#: (String) -> Oxc::MinifyResult
|
|
6
|
+
def self.from_json(payload)
|
|
7
|
+
parsed = JSON.parse(payload)
|
|
8
|
+
|
|
9
|
+
new(
|
|
10
|
+
code: parsed.fetch("code"),
|
|
11
|
+
map: parsed["map"],
|
|
12
|
+
legal_comments: parsed.fetch("legal_comments"),
|
|
13
|
+
diagnostics: parsed.fetch("errors").map { |diagnostic| Diagnostic.from_hash(diagnostic) },
|
|
14
|
+
panicked: parsed.fetch("panicked")
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
#: (code: String, diagnostics: Array[Oxc::Diagnostic], ?map: String?, ?legal_comments: Array[String], ?panicked: bool) -> void
|
|
19
|
+
def initialize(...)
|
|
20
|
+
super
|
|
21
|
+
|
|
22
|
+
freeze
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Oxc
|
|
4
|
+
# A visitor that rewrites the source it walked, by recording what to do to a node and splicing the
|
|
5
|
+
# original text at the end. Everything nothing touched survives byte for byte.
|
|
6
|
+
#
|
|
7
|
+
# class Renamer < Oxc::MutationVisitor
|
|
8
|
+
# def visit_identifier(node)
|
|
9
|
+
# replace(node, "renamed") if node["name"] == "count"
|
|
10
|
+
# end
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# Renamer.new.rewrite("let count = 1") #=> "let renamed = 1"
|
|
14
|
+
#
|
|
15
|
+
class MutationVisitor < Visitor
|
|
16
|
+
class Overlap < StandardError; end
|
|
17
|
+
class Invalid < Error; end
|
|
18
|
+
|
|
19
|
+
Edit = Data.define(
|
|
20
|
+
:start, #: Integer
|
|
21
|
+
:finish, #: Integer
|
|
22
|
+
:text, #: String
|
|
23
|
+
:order #: Integer
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
attr_reader :source #: String
|
|
27
|
+
attr_reader :parsed #: Oxc::ParseResult
|
|
28
|
+
|
|
29
|
+
#: (String, ?verify: bool, **untyped) -> String
|
|
30
|
+
def rewrite(source, verify: true, **options)
|
|
31
|
+
@source = source
|
|
32
|
+
@edits = [] #: Array[Edit]
|
|
33
|
+
@replaced = [] #: Array[[Integer, Integer]]
|
|
34
|
+
@parsed = Oxc.parse(source, **options).validate!
|
|
35
|
+
|
|
36
|
+
visit(parsed)
|
|
37
|
+
|
|
38
|
+
rewritten = apply
|
|
39
|
+
|
|
40
|
+
verify ? verified(rewritten, options) : rewritten
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#: (Oxc::Node, String) -> void
|
|
44
|
+
def replace(node, text)
|
|
45
|
+
@replaced << [node.start, node.finish]
|
|
46
|
+
|
|
47
|
+
edit(node.start, node.finish, text)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
#: (Oxc::Node) -> void
|
|
51
|
+
def remove(node)
|
|
52
|
+
replace(node, "")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
#: (Oxc::Node, String) -> void
|
|
56
|
+
def insert_before(node, text)
|
|
57
|
+
edit(node.start, node.start, text)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
#: (Oxc::Node, String) -> void
|
|
61
|
+
def insert_after(node, text)
|
|
62
|
+
edit(node.finish, node.finish, text)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
#: (Oxc::Node, String, String) -> void
|
|
66
|
+
def wrap(node, before, after)
|
|
67
|
+
insert_before(node, before)
|
|
68
|
+
insert_after(node, after)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
#: (Oxc::Node) -> void
|
|
72
|
+
def visit_children(node)
|
|
73
|
+
return nil if replaced?(node)
|
|
74
|
+
|
|
75
|
+
super
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
#: (String, Hash[Symbol, untyped]) -> String
|
|
81
|
+
def verified(rewritten, options)
|
|
82
|
+
answer = Oxc.parse(rewritten, **options, ast: false)
|
|
83
|
+
|
|
84
|
+
return rewritten unless answer.errors?
|
|
85
|
+
|
|
86
|
+
raise Invalid, "what was rewritten no longer reads as JavaScript: #{answer.errors.first&.message}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
#: (Oxc::Node) -> bool
|
|
90
|
+
def replaced?(node)
|
|
91
|
+
@replaced.any? { |start, finish| node.start >= start && node.finish <= finish }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
#: (Integer, Integer, String) -> void
|
|
95
|
+
def edit(start, finish, text)
|
|
96
|
+
@edits.each do |existing|
|
|
97
|
+
next unless overlaps?(existing, start, finish)
|
|
98
|
+
|
|
99
|
+
raise Overlap, "an edit at #{start}..#{finish} overlaps one at #{existing.start}..#{existing.finish}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
@edits << Edit.new(start: start, finish: finish, text: text, order: @edits.length)
|
|
103
|
+
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
#: (Edit, Integer, Integer) -> bool
|
|
108
|
+
def overlaps?(existing, start, finish)
|
|
109
|
+
return false if existing.start == existing.finish && start == finish
|
|
110
|
+
|
|
111
|
+
start < existing.finish && existing.start < finish
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
#: () -> String
|
|
115
|
+
def apply
|
|
116
|
+
taken = 0
|
|
117
|
+
result = +""
|
|
118
|
+
|
|
119
|
+
@edits.sort_by { |edit| [edit.start, edit.order] }.each do |edit|
|
|
120
|
+
result << source.byteslice(taken, edit.start - taken).to_s << edit.text
|
|
121
|
+
|
|
122
|
+
taken = edit.finish
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
result << source.byteslice(taken..).to_s
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|