@gjsify/oxfmt-native 0.4.44
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.
- package/README.md +45 -0
- package/lib/esm/_virtual/_rolldown/runtime.js +1 -0
- package/lib/esm/index.js +1 -0
- package/lib/types/index.d.ts +46 -0
- package/meson.build +87 -0
- package/package.json +74 -0
- package/prebuilds/linux-x86_64/GjsifyOxfmt-1.0.gir +65 -0
- package/prebuilds/linux-x86_64/GjsifyOxfmt-1.0.typelib +0 -0
- package/prebuilds/linux-x86_64/libgjsify_oxfmt.so +0 -0
- package/prebuilds/linux-x86_64/libgjsifyoxfmt.so +0 -0
- package/src/rust/Cargo.lock +613 -0
- package/src/rust/Cargo.toml +38 -0
- package/src/rust/src/lib.rs +274 -0
- package/src/vala/gjsify-oxfmt-glue.c +63 -0
- package/src/vala/gjsify-oxfmt-glue.h +55 -0
- package/src/vala/gjsify-oxfmt.h +46 -0
- package/src/vala/oxfmt.vala +79 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
//! gjsify_oxfmt — Rust→C FFI for oxc's formatter (the `oxfmt` app crate).
|
|
2
|
+
//!
|
|
3
|
+
//! Reference: refs/oxc/apps/oxfmt/src/main.rs (oxc-project/oxc, MIT) — the
|
|
4
|
+
//! pure-Rust (non-napi) oxfmt CLI entry point. Reimplemented for GJS as two
|
|
5
|
+
//! FFI surfaces:
|
|
6
|
+
//!
|
|
7
|
+
//! * `gjsify_oxfmt_run(argv, argc) -> exit_code` — the full oxfmt CLI
|
|
8
|
+
//! in-process: bpaf arg parsing, `.oxfmtrc(.json)` + `.editorconfig`
|
|
9
|
+
//! resolution, ignore handling, parallel file walking (rayon),
|
|
10
|
+
//! `--write` / `--check` / `--list-different` modes, stdout/stderr
|
|
11
|
+
//! reporting. The same engine as the npm `oxfmt` launcher for
|
|
12
|
+
//! JS/TS/JSX (+ JSON/TOML), minus the Node-hosted pieces: the
|
|
13
|
+
//! `ExternalFormatter` (CSS/HTML/Vue/Markdown via Prettier-NAPI
|
|
14
|
+
//! callbacks), JS/TS config files (`.oxfmtrc.ts`), `--init`/`--migrate`
|
|
15
|
+
//! and the LSP — those all require a Node host by design (napi feature).
|
|
16
|
+
//!
|
|
17
|
+
//! * `gjsify_oxfmt_format(opts) -> GjsifyResult` — single-shot in-memory
|
|
18
|
+
//! format of one source string via `oxc_formatter::format` with default
|
|
19
|
+
//! (Prettier-compatible) options. No config resolution — callers that
|
|
20
|
+
//! need `.oxfmtrc` semantics use `gjsify_oxfmt_run`.
|
|
21
|
+
//!
|
|
22
|
+
//! The result struct owns its buffers until `gjsify_oxfmt_result_free()` is
|
|
23
|
+
//! called.
|
|
24
|
+
|
|
25
|
+
#![allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
26
|
+
|
|
27
|
+
use std::ffi::{CStr, CString, OsString};
|
|
28
|
+
use std::os::raw::c_char;
|
|
29
|
+
use std::panic::{catch_unwind, AssertUnwindSafe};
|
|
30
|
+
use std::ptr;
|
|
31
|
+
use std::sync::Once;
|
|
32
|
+
|
|
33
|
+
use oxc_allocator::Allocator;
|
|
34
|
+
use oxc_formatter::JsFormatOptions;
|
|
35
|
+
use oxc_span::SourceType;
|
|
36
|
+
use oxfmt::cli::{format_command, init_miette, init_rayon, init_tracing, WalkRunner};
|
|
37
|
+
|
|
38
|
+
/// Input to `gjsify_oxfmt_format`.
|
|
39
|
+
#[repr(C)]
|
|
40
|
+
pub struct GjsifyFormatOpts {
|
|
41
|
+
/// Logical filename — its extension selects JS/TS/JSX. NULL → `input.ts`.
|
|
42
|
+
pub filename: *const c_char,
|
|
43
|
+
pub code: *const u8,
|
|
44
|
+
pub code_len: usize,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Result of a format. On success `error` is NULL and `code` holds the
|
|
48
|
+
/// formatted source; on failure `code` is NULL and `error` is a
|
|
49
|
+
/// NUL-terminated UTF-8 message. All non-NULL pointers were allocated on the
|
|
50
|
+
/// Rust side and MUST be freed via `gjsify_oxfmt_result_free` — never libc free.
|
|
51
|
+
#[repr(C)]
|
|
52
|
+
pub struct GjsifyResult {
|
|
53
|
+
pub code: *mut u8,
|
|
54
|
+
pub code_len: usize,
|
|
55
|
+
pub code_cap: usize,
|
|
56
|
+
pub error: *mut c_char,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
impl GjsifyResult {
|
|
60
|
+
fn ok(s: String) -> Self {
|
|
61
|
+
let mut v = s.into_bytes();
|
|
62
|
+
let code = v.as_mut_ptr();
|
|
63
|
+
let code_len = v.len();
|
|
64
|
+
let code_cap = v.capacity();
|
|
65
|
+
std::mem::forget(v);
|
|
66
|
+
GjsifyResult {
|
|
67
|
+
code,
|
|
68
|
+
code_len,
|
|
69
|
+
code_cap,
|
|
70
|
+
error: ptr::null_mut(),
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fn err(msg: impl Into<String>) -> Self {
|
|
75
|
+
let cstr = CString::new(msg.into()).unwrap_or_else(|_| {
|
|
76
|
+
CString::new("oxfmt: error message contained interior NUL byte").unwrap()
|
|
77
|
+
});
|
|
78
|
+
GjsifyResult {
|
|
79
|
+
code: ptr::null_mut(),
|
|
80
|
+
code_len: 0,
|
|
81
|
+
code_cap: 0,
|
|
82
|
+
error: cstr.into_raw(),
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
fn cstr_to_str<'a>(p: *const c_char) -> Option<&'a str> {
|
|
88
|
+
if p.is_null() {
|
|
89
|
+
None
|
|
90
|
+
} else {
|
|
91
|
+
unsafe { CStr::from_ptr(p) }.to_str().ok()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Core: parse + format `source` (typed from `filename`'s extension, TS by
|
|
96
|
+
/// default) via `oxc_formatter::format` with default options. Pure Rust,
|
|
97
|
+
/// no FFI — unit-tested directly.
|
|
98
|
+
fn format_impl(source: &str, filename: &str) -> Result<String, String> {
|
|
99
|
+
let source_type = SourceType::from_path(filename).unwrap_or_else(|_| SourceType::ts());
|
|
100
|
+
let allocator = Allocator::new();
|
|
101
|
+
let formatted = oxc_formatter::format(
|
|
102
|
+
&allocator,
|
|
103
|
+
source,
|
|
104
|
+
source_type,
|
|
105
|
+
JsFormatOptions::default(),
|
|
106
|
+
None,
|
|
107
|
+
)
|
|
108
|
+
.map_err(|e| format!("oxfmt: parse error in {filename}: {e}"))?;
|
|
109
|
+
formatted
|
|
110
|
+
.print()
|
|
111
|
+
.map(|printed| printed.into_code())
|
|
112
|
+
.map_err(|e| format!("oxfmt: print failed: {e:?}"))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// One-shot format. Caller MUST pass the result to
|
|
116
|
+
/// `gjsify_oxfmt_result_free` exactly once to release buffers.
|
|
117
|
+
#[no_mangle]
|
|
118
|
+
pub extern "C" fn gjsify_oxfmt_format(opts: GjsifyFormatOpts) -> GjsifyResult {
|
|
119
|
+
if opts.code.is_null() {
|
|
120
|
+
return GjsifyResult::err("oxfmt: NULL input code");
|
|
121
|
+
}
|
|
122
|
+
let source = {
|
|
123
|
+
let slice = unsafe { std::slice::from_raw_parts(opts.code, opts.code_len) };
|
|
124
|
+
match std::str::from_utf8(slice) {
|
|
125
|
+
Ok(s) => s,
|
|
126
|
+
Err(_) => return GjsifyResult::err("oxfmt: invalid UTF-8 in input"),
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
let filename = cstr_to_str(opts.filename).unwrap_or("input.ts");
|
|
130
|
+
match format_impl(source, filename) {
|
|
131
|
+
Ok(code) => GjsifyResult::ok(code),
|
|
132
|
+
Err(e) => GjsifyResult::err(e),
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// Free a result returned by `gjsify_oxfmt_format`. Call exactly once.
|
|
137
|
+
#[no_mangle]
|
|
138
|
+
pub extern "C" fn gjsify_oxfmt_result_free(result: GjsifyResult) {
|
|
139
|
+
if !result.code.is_null() {
|
|
140
|
+
unsafe {
|
|
141
|
+
drop(Vec::from_raw_parts(
|
|
142
|
+
result.code,
|
|
143
|
+
result.code_len,
|
|
144
|
+
result.code_cap,
|
|
145
|
+
))
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if !result.error.is_null() {
|
|
149
|
+
unsafe { drop(CString::from_raw(result.error)) };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// Process-global one-time init (tracing, miette hook, rayon pool). Mirrors
|
|
154
|
+
/// main.rs — these all panic or error when initialized twice, and a CLI
|
|
155
|
+
/// process only ever needs them once. The rayon thread count is taken from
|
|
156
|
+
/// the FIRST run's `--threads` option (subsequent in-process runs reuse the
|
|
157
|
+
/// pool — matches the one-run-per-process CLI model).
|
|
158
|
+
static INIT: Once = Once::new();
|
|
159
|
+
|
|
160
|
+
/// Run the oxfmt CLI in-process: `argv`/`argc` are the CLI arguments
|
|
161
|
+
/// (WITHOUT the program name — i.e. `process.argv.slice(2)` shaped, same as
|
|
162
|
+
/// the napi `run_cli`). Prints diagnostics/reports to stdout/stderr exactly
|
|
163
|
+
/// like the `oxfmt` binary. Returns the process exit code
|
|
164
|
+
/// (0 = success, 1 = config error/`--check` mismatch, 2 = no files/failed).
|
|
165
|
+
#[no_mangle]
|
|
166
|
+
pub extern "C" fn gjsify_oxfmt_run(argv: *const *const c_char, argc: usize) -> i32 {
|
|
167
|
+
// Never unwind across the FFI boundary — a panic would abort the host
|
|
168
|
+
// (GJS) process. Surface it as a non-zero exit code instead.
|
|
169
|
+
match catch_unwind(AssertUnwindSafe(|| run_impl(argv, argc))) {
|
|
170
|
+
Ok(code) => code,
|
|
171
|
+
Err(_) => {
|
|
172
|
+
eprintln!("oxfmt: internal panic during run (this is a bug in gjsify_oxfmt)");
|
|
173
|
+
101
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
fn run_impl(argv: *const *const c_char, argc: usize) -> i32 {
|
|
179
|
+
let mut args: Vec<OsString> = Vec::with_capacity(argc);
|
|
180
|
+
if argc > 0 {
|
|
181
|
+
if argv.is_null() {
|
|
182
|
+
eprintln!("oxfmt: NULL argv with non-zero argc");
|
|
183
|
+
return 2;
|
|
184
|
+
}
|
|
185
|
+
for i in 0..argc {
|
|
186
|
+
let p = unsafe { *argv.add(i) };
|
|
187
|
+
match cstr_to_str(p) {
|
|
188
|
+
Some(s) => args.push(OsString::from(s)),
|
|
189
|
+
None => {
|
|
190
|
+
eprintln!("oxfmt: argument {i} is NULL or not valid UTF-8");
|
|
191
|
+
return 2;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Use `run_inner()` to report errors instead of panicking (same as the
|
|
198
|
+
// napi `run_cli`). bpaf returns exit code 0 for --help/--version and
|
|
199
|
+
// non-0 for actual parse errors.
|
|
200
|
+
let command = match format_command().run_inner(&*args) {
|
|
201
|
+
Ok(cmd) => cmd,
|
|
202
|
+
Err(e) => {
|
|
203
|
+
e.print_message(100);
|
|
204
|
+
return i32::from(e.exit_code() != 0);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
INIT.call_once(|| {
|
|
209
|
+
init_tracing();
|
|
210
|
+
init_miette();
|
|
211
|
+
init_rayon(command.runtime_options.threads);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
i32::from(WalkRunner::new(command).run().exit_code())
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#[cfg(test)]
|
|
218
|
+
mod tests {
|
|
219
|
+
use std::ffi::CString;
|
|
220
|
+
use std::os::raw::c_char;
|
|
221
|
+
|
|
222
|
+
use super::format_impl;
|
|
223
|
+
|
|
224
|
+
#[test]
|
|
225
|
+
fn formats_typescript() {
|
|
226
|
+
let out = format_impl("const x:number=1", "f.ts").unwrap();
|
|
227
|
+
// oxc_formatter is Prettier-compatible: collapse whitespace, add the
|
|
228
|
+
// type-annotation spacing + a trailing newline.
|
|
229
|
+
assert!(out.contains("const x"), "expected reflow, got: {out:?}");
|
|
230
|
+
assert!(
|
|
231
|
+
out.ends_with('\n'),
|
|
232
|
+
"expected trailing newline, got: {out:?}"
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
#[test]
|
|
237
|
+
fn formats_tsx() {
|
|
238
|
+
let out = format_impl("const el=<div className=\"x\">hi</div>;", "f.tsx").unwrap();
|
|
239
|
+
assert!(out.contains("<div"), "got: {out:?}");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fn run(args: &[&str]) -> i32 {
|
|
243
|
+
let cstrings: Vec<CString> = args.iter().map(|a| CString::new(*a).unwrap()).collect();
|
|
244
|
+
let ptrs: Vec<*const c_char> = cstrings.iter().map(|c| c.as_ptr()).collect();
|
|
245
|
+
super::gjsify_oxfmt_run(ptrs.as_ptr(), ptrs.len())
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
#[test]
|
|
249
|
+
fn run_rejects_unknown_flags() {
|
|
250
|
+
assert_eq!(run(&["--definitely-not-a-real-flag"]), 1);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#[test]
|
|
254
|
+
fn run_help_exits_zero() {
|
|
255
|
+
assert_eq!(run(&["--help"]), 0);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
#[test]
|
|
259
|
+
fn run_formats_a_file_in_place() {
|
|
260
|
+
let dir =
|
|
261
|
+
std::env::temp_dir().join(format!("gjsify-oxfmt-run-test-{}", std::process::id()));
|
|
262
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
263
|
+
let file = dir.join("f.ts");
|
|
264
|
+
std::fs::write(&file, "const x:number=1").unwrap();
|
|
265
|
+
let code = run(&["--write", file.to_str().unwrap()]);
|
|
266
|
+
let out = std::fs::read_to_string(&file).unwrap();
|
|
267
|
+
std::fs::remove_dir_all(&dir).ok();
|
|
268
|
+
assert_eq!(code, 0, "expected exit 0, got {code}");
|
|
269
|
+
assert!(
|
|
270
|
+
out.contains("const x: number = 1;"),
|
|
271
|
+
"expected formatted output, got: {out:?}"
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* GLib-friendly glue for gjsify_oxfmt. See gjsify-oxfmt-glue.h.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include "gjsify-oxfmt-glue.h"
|
|
6
|
+
|
|
7
|
+
GQuark
|
|
8
|
+
gjsify_oxfmt_error_quark (void)
|
|
9
|
+
{
|
|
10
|
+
return g_quark_from_static_string ("gjsify-oxfmt-error-quark");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
GBytes *
|
|
14
|
+
gjsify_oxfmt_glue_format (const char *filename,
|
|
15
|
+
GBytes *code,
|
|
16
|
+
GError **error)
|
|
17
|
+
{
|
|
18
|
+
if (code == NULL)
|
|
19
|
+
{
|
|
20
|
+
g_set_error_literal (error,
|
|
21
|
+
GJSIFY_OXFMT_ERROR,
|
|
22
|
+
GJSIFY_OXFMT_ERROR_FAILED,
|
|
23
|
+
"oxfmt: NULL input bytes");
|
|
24
|
+
return NULL;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
gsize code_len = 0;
|
|
28
|
+
const guint8 *code_data = g_bytes_get_data (code, &code_len);
|
|
29
|
+
|
|
30
|
+
GjsifyFormatOpts opts;
|
|
31
|
+
opts.filename = filename;
|
|
32
|
+
opts.code = code_data;
|
|
33
|
+
opts.code_len = code_len;
|
|
34
|
+
|
|
35
|
+
GjsifyResult res = gjsify_oxfmt_format (opts);
|
|
36
|
+
|
|
37
|
+
if (res.error != NULL)
|
|
38
|
+
{
|
|
39
|
+
g_set_error_literal (error,
|
|
40
|
+
GJSIFY_OXFMT_ERROR,
|
|
41
|
+
GJSIFY_OXFMT_ERROR_FAILED,
|
|
42
|
+
res.error);
|
|
43
|
+
gjsify_oxfmt_result_free (res);
|
|
44
|
+
return NULL;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Copy the Rust-allocated buffer into GBytes (GLib heap), then free the
|
|
48
|
+
* Rust result — keeps the ownership boundary clean; formatted source is
|
|
49
|
+
* typically small. */
|
|
50
|
+
GBytes *out = g_bytes_new (res.code, res.code_len);
|
|
51
|
+
gjsify_oxfmt_result_free (res);
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
int
|
|
56
|
+
gjsify_oxfmt_glue_run (char **args,
|
|
57
|
+
int n_args)
|
|
58
|
+
{
|
|
59
|
+
if (n_args < 0)
|
|
60
|
+
n_args = 0;
|
|
61
|
+
|
|
62
|
+
return gjsify_oxfmt_run ((const char * const *) args, (size_t) n_args);
|
|
63
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* GLib-friendly wrapper around the Rust format shim.
|
|
3
|
+
*
|
|
4
|
+
* The Rust side returns its own struct with malloc'd buffers. This glue
|
|
5
|
+
* layer copies the formatted code into GBytes (so GLib's refcount controls
|
|
6
|
+
* lifetime — friendly to SpiderMonkey GC) and frees the Rust result
|
|
7
|
+
* immediately. Errors become GError on the GJSIFY_OXFMT quark.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#ifndef GJSIFY_OXFMT_GLUE_H
|
|
11
|
+
#define GJSIFY_OXFMT_GLUE_H
|
|
12
|
+
|
|
13
|
+
#include <glib.h>
|
|
14
|
+
#include <stdbool.h>
|
|
15
|
+
#include "gjsify-oxfmt.h"
|
|
16
|
+
|
|
17
|
+
G_BEGIN_DECLS
|
|
18
|
+
|
|
19
|
+
#define GJSIFY_OXFMT_ERROR (gjsify_oxfmt_error_quark ())
|
|
20
|
+
|
|
21
|
+
typedef enum {
|
|
22
|
+
GJSIFY_OXFMT_ERROR_FAILED = 0,
|
|
23
|
+
} GjsifyOxfmtError;
|
|
24
|
+
|
|
25
|
+
GQuark gjsify_oxfmt_error_quark (void);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* gjsify_oxfmt_glue_format:
|
|
29
|
+
* @filename: logical filename — its extension selects JS/TS/JSX (NULL OK)
|
|
30
|
+
* @code: input source bytes (UTF-8)
|
|
31
|
+
* @error: (out)(optional): GError on failure
|
|
32
|
+
*
|
|
33
|
+
* Returns: (transfer full): formatted source as GBytes, or NULL on error.
|
|
34
|
+
*/
|
|
35
|
+
GBytes *gjsify_oxfmt_glue_format (const char *filename,
|
|
36
|
+
GBytes *code,
|
|
37
|
+
GError **error);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* gjsify_oxfmt_glue_run:
|
|
41
|
+
* @args: (array length=n_args): CLI arguments (without the program name)
|
|
42
|
+
* @n_args: number of arguments
|
|
43
|
+
*
|
|
44
|
+
* Runs the oxfmt CLI in-process (config resolution, file walking,
|
|
45
|
+
* write/check/list-different). Prints to stdout/stderr like the `oxfmt`
|
|
46
|
+
* binary.
|
|
47
|
+
*
|
|
48
|
+
* Returns: the process exit code (0 success, 1 warning, 2 fatal).
|
|
49
|
+
*/
|
|
50
|
+
int gjsify_oxfmt_glue_run (char **args,
|
|
51
|
+
int n_args);
|
|
52
|
+
|
|
53
|
+
G_END_DECLS
|
|
54
|
+
|
|
55
|
+
#endif /* GJSIFY_OXFMT_GLUE_H */
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hand-written C header matching the Rust extern "C" surface of the
|
|
3
|
+
* gjsify_oxfmt cdylib (src/rust/src/lib.rs).
|
|
4
|
+
*
|
|
5
|
+
* Vala includes this via [CCode (cheader_filename = "gjsify-oxfmt.h")] to
|
|
6
|
+
* call into the Rust shim. The surface is tiny (one format call + a free),
|
|
7
|
+
* so a hand-maintained header is simpler than pulling cbindgen into meson.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#ifndef GJSIFY_OXFMT_H
|
|
11
|
+
#define GJSIFY_OXFMT_H
|
|
12
|
+
|
|
13
|
+
#include <glib.h>
|
|
14
|
+
#include <stdbool.h>
|
|
15
|
+
#include <stddef.h>
|
|
16
|
+
#include <stdint.h>
|
|
17
|
+
|
|
18
|
+
G_BEGIN_DECLS
|
|
19
|
+
|
|
20
|
+
typedef struct {
|
|
21
|
+
const char *filename; /* extension selects JS/TS/JSX; NULL → "input.ts" */
|
|
22
|
+
const uint8_t *code;
|
|
23
|
+
size_t code_len;
|
|
24
|
+
} GjsifyFormatOpts;
|
|
25
|
+
|
|
26
|
+
typedef struct {
|
|
27
|
+
uint8_t *code; /* formatted source; NULL on error */
|
|
28
|
+
size_t code_len;
|
|
29
|
+
size_t code_cap;
|
|
30
|
+
char *error; /* NUL-terminated message; NULL on success */
|
|
31
|
+
} GjsifyResult;
|
|
32
|
+
|
|
33
|
+
/* One-shot format. The returned struct owns its buffers — pass it to
|
|
34
|
+
* gjsify_oxfmt_result_free() exactly once when done. */
|
|
35
|
+
GjsifyResult gjsify_oxfmt_format (GjsifyFormatOpts opts);
|
|
36
|
+
|
|
37
|
+
void gjsify_oxfmt_result_free (GjsifyResult result);
|
|
38
|
+
|
|
39
|
+
/* Run the oxfmt CLI in-process. argv = the CLI arguments WITHOUT the
|
|
40
|
+
* program name (process.argv.slice(2) shaped). Prints to stdout/stderr
|
|
41
|
+
* like the oxfmt binary; returns the process exit code. */
|
|
42
|
+
int gjsify_oxfmt_run (const char * const *argv, size_t argc);
|
|
43
|
+
|
|
44
|
+
G_END_DECLS
|
|
45
|
+
|
|
46
|
+
#endif /* GJSIFY_OXFMT_H */
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* GjsifyOxfmt — Vala wrapper around the Rust oxc-formatter cdylib.
|
|
3
|
+
*
|
|
4
|
+
* The real formatting pipeline lives in src/rust/ (compiled by meson via
|
|
5
|
+
* cargo to libgjsify_oxfmt.so) and a tiny C glue file
|
|
6
|
+
* (gjsify-oxfmt-glue.{h,c}) translates the malloc'd Rust struct into
|
|
7
|
+
* GBytes + GError, which GJS handles natively.
|
|
8
|
+
*
|
|
9
|
+
* This Vala layer exposes the glue function as a GObject method that emits
|
|
10
|
+
* to GIR/typelib, so JS can do:
|
|
11
|
+
*
|
|
12
|
+
* import GjsifyOxfmt from "gi://GjsifyOxfmt?version=1.0";
|
|
13
|
+
* const fmt = new GjsifyOxfmt.Formatter();
|
|
14
|
+
* const out = fmt.format("file.ts", input); // GLib.Bytes
|
|
15
|
+
*
|
|
16
|
+
* Pattern: same as @gjsify/lightningcss-native — Rust/C ownership stays in
|
|
17
|
+
* native land via GLib.Bytes; SpiderMonkey only sees refcounted handles.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
namespace GjsifyOxfmt {
|
|
21
|
+
|
|
22
|
+
[CCode (cname = "gjsify_oxfmt_glue_format",
|
|
23
|
+
cheader_filename = "gjsify-oxfmt-glue.h")]
|
|
24
|
+
private extern GLib.Bytes? _glue_format (string? filename,
|
|
25
|
+
GLib.Bytes code) throws GLib.Error;
|
|
26
|
+
|
|
27
|
+
[CCode (cname = "gjsify_oxfmt_glue_run",
|
|
28
|
+
cheader_filename = "gjsify-oxfmt-glue.h")]
|
|
29
|
+
private extern int _glue_run (string[] args);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Formatter — stateless one-shot oxc formatter.
|
|
33
|
+
*
|
|
34
|
+
* Each call parses the source on the Rust side (typed from the
|
|
35
|
+
* filename's extension, TypeScript by default) and prints it back
|
|
36
|
+
* through oxc_formatter (Prettier-compatible). Only the native
|
|
37
|
+
* JS/TS/JSX path is wrapped — embedded CSS/HTML/Markdown formatting
|
|
38
|
+
* (oxfmt's Prettier-NAPI ExternalFormatter) is out of scope.
|
|
39
|
+
*/
|
|
40
|
+
public class Formatter : GLib.Object {
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* format:
|
|
44
|
+
* @filename: logical filename; its extension selects JS/TS/JSX
|
|
45
|
+
* (may be null → treated as TypeScript)
|
|
46
|
+
* @code: input source bytes (UTF-8)
|
|
47
|
+
*
|
|
48
|
+
* Returns: (transfer full): formatted source as GLib.Bytes.
|
|
49
|
+
* Throws GjsifyOxfmtError.FAILED on a fatal parse/print error.
|
|
50
|
+
*/
|
|
51
|
+
public GLib.Bytes format (string? filename,
|
|
52
|
+
GLib.Bytes code) throws GLib.Error {
|
|
53
|
+
var bytes = _glue_format (filename, code);
|
|
54
|
+
if (bytes == null)
|
|
55
|
+
throw new GLib.Error (GLib.Quark.from_string ("gjsify-oxfmt-error-quark"),
|
|
56
|
+
0, "oxfmt: unknown error (NULL result without GError)");
|
|
57
|
+
return bytes;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* run:
|
|
62
|
+
* @args: CLI arguments WITHOUT the program name
|
|
63
|
+
* (`process.argv.slice(2)` shaped) — e.g.
|
|
64
|
+
* `["--check", "--config", "/abs/.oxfmtrc.json", "src"]`
|
|
65
|
+
*
|
|
66
|
+
* Runs the full oxfmt CLI in-process: `.oxfmtrc(.json)` +
|
|
67
|
+
* `.editorconfig` resolution, ignore handling, parallel file
|
|
68
|
+
* walking, `--write` / `--check` / `--list-different` modes.
|
|
69
|
+
* Reports to stdout/stderr exactly like the `oxfmt` binary.
|
|
70
|
+
* Working directory = the current process working directory.
|
|
71
|
+
*
|
|
72
|
+
* Returns: the process exit code (0 = success, 1 = config error or
|
|
73
|
+
* `--check` mismatch, 2 = no files found / format failed).
|
|
74
|
+
*/
|
|
75
|
+
public int run (string[] args) {
|
|
76
|
+
return _glue_run (args);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|