Skip to content

chore(deps): update Rust and Elixir dependencies, and fix the bugs it uncovered - #68

Merged
shahryarjb merged 7 commits into
mainfrom
chore/update-dependencies
Aug 3, 2026
Merged

chore(deps): update Rust and Elixir dependencies, and fix the bugs it uncovered#68
shahryarjb merged 7 commits into
mainfrom
chore/update-dependencies

Conversation

@shahryarjb

@shahryarjb shahryarjb commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

This brings every dependency up to date on both sides. On the Rust side that
meant crossing a lot of major versions — swc moved 10+ majors and oxc jumped
0.86 → 0.142 — which needed real API migrations rather than just version
bumps. biome is deliberately left pinned at =0.5.7: the formatter crates we
actually use have no newer release, and the pins on the unused transitive
crates are what hold that graph on one consistent set. I tried bumping the
leaves that do have 0.5.8/0.5.9 and it fails to compile, because biome_rowan
0.5.8 adds a required is_trivia method to the SyntaxKind trait under a
patch version, and biome_js_syntax has no matching release. The pinned Rust
toolchain moves to 1.97.1 since oxc 0.142 requires 1.95 and rustler 0.38
requires 1.91.

Getting the NIF exercised properly from a consumer's point of view turned up
four genuine bugs, all pre-existing and none caused by the bumps — I verified
the panic reproduces on the pre-update commit too. The test suites grew from
26 to 90 (ExUnit) and 24 to 66 (Rust) while chasing them down.

Dependencies

Crate / package From To
swc_common 14.0.3 24.0.0
swc_ecma_parser 24.0.0 43.0.0
swc_ecma_visit / _ast 15.0.0 27.0.0
swc_ecma_codegen 17.0.0 30.0.0
oxc_* (6 crates) 0.86.0 0.142.0
rustler (crate + hex) 0.36.2 0.38.0
serde / serde_json 1.0.219 / 1.0.143 1.0.229 / 1.0.151
rustler_precompiled ~> 0.8 ~> 0.9
ex_doc / git_ops ~> 0.38 / ~> 2.9 ~> 0.40 / ~> 2.10
Rust toolchain 1.89.0 1.97.1

mix hex.outdated --all is now empty. toml is dropped from the lock — it was
only reachable through rustler, which removed it in 0.38.

API migrations required by the bumps

  • ImportDecl.src.value is now a Wtf8Atom with no Display impl.
  • ParserReturn::errors renamed to diagnostics.
  • to_pretty_estree_ts_json(ranges) became
    to_pretty_estree_json(include_ts_fields, ranges) — the TS flag moved out of
    the method name, so the old call maps to (true, true), not (true, false).
    Getting this wrong silently drops range from every node.
  • OxcDiagnostic::labels is no longer an Option; the empty case is mapped
    back to null so the JSON shape is unchanged.

Bugs fixed

  • NIF panics on unparseable JavaScript. Four .expect() calls on parse()
    aborted the whole NIF with :nif_panicked instead of returning an error
    tuple — a panic can't be handled with a normal case. Now they propagate
    errors, including through the VisitMut visitors, which return () and
    needed a parse_error field to carry the failure out.
  • remove_imports/3 silently did nothing for a bare module name.
    remove_imports(code, "phoenix") returned :ok and changed nothing, because
    only import declarations in the argument were matched. It now accepts bare
    specifiers and paths. Multi-line import statements are handled safely so
    their inner lines aren't mistaken for module names.
  • statistics/2 raised MatchError on an unreadable path — the only entry
    point of 15 that crashed instead of returning {:error, _, _}.
  • var_exists? couldn't use :path — its second parameter was named
    type but callers were passing the variable name into it.

Compatibility

remove_imports/3 is the one behaviour change: a bare module name used to be a
silent no-op and now removes the import. That's what the docs always described,
and nobody relies on a call doing nothing, but it warrants a minor version
bump rather than a patch
. Everything else is backwards compatible —
var_exists?/2 behaves exactly as before, and blank or unmatched
remove_imports arguments remain :ok no-ops rather than becoming errors.

Related: ash-project/ash#2825

From old issue: #12

shahryarjb and others added 7 commits August 3, 2026 07:35
Bump swc (common 14->24, parser 24->43, visit/ast 15->27, codegen 17->30),
oxc 0.86->0.142, rustler 0.36.2->0.38.0, serde and serde_json.

API migrations required by these bumps:

- swc: `ImportDecl.src.value` is now a `Wtf8Atom`, which no longer
  implements `Display`; use `to_string_lossy()` instead of `to_string()`.
- oxc: `ParserReturn::errors` was renamed to `diagnostics`.
- oxc: `to_pretty_estree_ts_json(ranges)` was replaced by
  `to_pretty_estree_json(include_ts_fields, ranges)` -- the TS-field flag
  moved out of the method name into the first argument, so the old call
  maps to `(true, true)` rather than `(true, false)`.
- oxc: `OxcDiagnostic::labels` is no longer an `Option`. The empty case is
  mapped back to `None` so label-less diagnostics still serialize as
  `null` instead of `[]`, keeping the `ast_to_estree/2` output unchanged.

biome stays pinned at =0.5.7: the formatter crates this project actually
uses have no release beyond 0.5.7, and the explicit pins on the unused
transitive biome crates exist to hold that graph on one consistent set.

Raise the pinned Rust toolchain to 1.97.1, since oxc 0.142 requires 1.95
and rustler 0.38 requires 1.91. The Elixir `rustler` dep moves to
~> 0.38.0 in the same commit to stay in lockstep with the crate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bring every dependency, direct and transitive, to its latest release and
align the requirements in mix.exs with what actually resolves:

  rustler_precompiled  ~> 0.8  -> ~> 0.9   (resolves 0.9.0)
  ex_doc               ~> 0.38 -> ~> 0.40  (resolves 0.40.3)
  git_ops              ~> 2.9  -> ~> 2.10  (resolves 2.10.0)

Transitive bumps in the lock:

  earmark_parser 1.4.44 -> 1.4.46
  erlex          0.2.8  -> 0.2.9
  makeup         1.2.1  -> 1.2.2
  yaml_elixir    2.11.0 -> 2.12.2

`toml` is dropped from the lock: it was only reachable through rustler,
which removed that dependency in 0.38.0.

`jason ~> 1.4` and `ex_check ~> 0.16` are left alone -- both already
resolve to the newest stable release (1.4.5 and 0.16.0). `mix hex.outdated
--all` now reports nothing outdated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four call sites used `.expect(...)` on `parse()`, so input that is not valid
JavaScript aborted the NIF with `:nif_panicked` rather than returning the
usual `{:error, fn_name, reason}` tuple. A panicking NIF cannot be handled
with a normal `case` and destabilises the calling process.

The two sites inside `ASTVisitImport` are the awkward ones, because
`VisitMut` methods return `()` and cannot propagate a `Result`. They now
record the failure on a `parse_error` field that `is_module_imported_from_ast`,
`insert_import_to_ast` and `remove_import_from_ast` check once the visit
completes. `contains_variable_from_ast` returns its documented `Err(false)`,
and `remove_objects_of_hooks_from_ast` returns an error string.

Reported errors are actionable: passing a bare path such as
`../vendor/topbar` to `remove_imports/3` now explains that a module name or
a full import statement is expected.

Covered by three Rust tests and two ExUnit tests over both unparseable
arguments and unparseable source.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`cargo fmt --check` reported one drifting block in the
`test_extend_hooks_with_const_let_var_declarations` test data. Test code
only, no behaviour change; `cargo fmt --check` is now clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Elixir goes from 26 to 85 tests, Rust from 27 to 59.

New coverage:

- IgniterJs.Helpers had no tests at all. read_and_validate_file/1 now covers
  .js, .ts, a missing file, an unsupported extension, a directory, and a
  directory named like a file; call_nif_fn/4 covers :content, :path and the
  short-circuit on an unreadable path; normalize_output/2 is covered directly.
- Every public parser entry point is asserted to return {:error, fn_atom,
  reason} for a missing file and for an unsupported extension.
- Formatter coverage for both JS and CSS: syntax errors, :path mode,
  idempotence, indent width, comment preservation and empty input.
- ast_to_estree/2: top-level shape, comment kinds and spans, range on every
  node, syntax errors reported in the errors list, UTF-16 offset conversion.
- helpers.rs had no test module. is_duplicate_import/specifier_equals are now
  covered for named, default and namespace specifiers, differing sources,
  mismatched specifier kinds and non-import items.

Two defects the new tests exposed:

- statistics/2 destructured {status, fn_atom, {_, data}}, but a path failure
  carries a plain reason string, so it raised MatchError instead of returning
  an error tuple. It now matches both shapes.
- var_exists?/2 forwarded its second argument to exist_var/3 as the module
  name while naming the parameter `type`, which made :path mode unreachable.
  It takes var_name and type separately now; the previously working
  var_exists?(content, var_name) call is unchanged.

Also replaces assert_eq!(x, false) with assert!(!x) in the two formatter test
modules to silence clippy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`remove_imports/3` parsed its argument as JavaScript and matched only the
import declarations it found, so a bare module name never matched anything.
`remove_imports(code, "module-name")` reported :ok and changed nothing, even
though the documentation described the argument as module names.

The argument is now parsed first. When it contains import declarations only
their sources are used, which keeps multi-line import statements intact; when
it contains none, each non-empty line is taken literally as a module
specifier. That makes both of these work:

    Parser.remove_imports(code, "module-name")
    Parser.remove_imports(code, "../vendor/topbar")

while the existing full-statement spelling is unchanged.

Matching is on the module source rather than the local binding, so removing
"topbar" does not drop `import topbar from "../vendor/topbar"`.

A blank or unmatched argument stays a no-op returning :ok rather than an
error, so no previously working call starts failing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`ast.rs` had a `mod tests` nested directly inside `mod tests`, which trips
`clippy::module_inception` and makes `cargo clippy -- -D warnings` fail. The
inner module is renamed to `index_operations`, after the functions it covers,
and its redundant `#[cfg(test)]` is dropped since the parent already has one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shahryarjb
shahryarjb requested a review from zachdaniel August 3, 2026 08:28
@shahryarjb shahryarjb self-assigned this Aug 3, 2026
@shahryarjb shahryarjb added bug Something isn't working documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file labels Aug 3, 2026
@shahryarjb
shahryarjb merged commit dba86e2 into main Aug 3, 2026
25 checks passed
@shahryarjb
shahryarjb deleted the chore/update-dependencies branch August 3, 2026 12:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants