Skip to content

refactor(semantic): shared helpers, ImportKind, UTF-8 trace, populated exports - #117

Merged
yogthos merged 1 commit into
mainfrom
refactor/semantic-helpers-imports-exports
May 21, 2026
Merged

refactor(semantic): shared helpers, ImportKind, UTF-8 trace, populated exports#117
yogthos merged 1 commit into
mainfrom
refactor/semantic-helpers-imports-exports

Conversation

@yogthos

@yogthos yogthos commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Four cross-cutting cleanups across all 9 semantic adapters. (1) Shared common.rs for node_text / find_node_at_range / signature builders — ~150 lines of duplication gone; ByteRange::from(Node) replaces 7 hand-written constructors. (2) ImportKind enum (Header/Module/Qualified) so cross-language import queries can normalize. (3) UTF-8 fallback now logs at debug (visible under --verbose). (4) exports field populated from is_exported symbols. 3 new regression tests. 812 all-features pass (was 809).

…d exports

Four cross-cutting cleanups identified in the adapter review,
applied across all 9 language adapters (bash + clojure + go + ruby +
rust + java + c + cpp + python + typescript).

## 1. Shared helpers — `semantic/common.rs`

Three helpers previously duplicated 7+ times across adapters now
live in one place:

- `node_text(node, source)` — UTF-8 decode with structured-log
  fallback (see #3).
- `find_node_at_range(root, start, end)` — depth-first range
  match used by every `find_callees_in_range` impl.
- `signature_first_line(node, source)` and
  `signature_up_to_body(node, source)` — the two signature
  builders. Per-adapter rules choose between them based on the
  language's body-field convention.

`ByteRange::from(tree_sitter::Node)` (in `types.rs`) replaces
the 7 hand-written constructors. Each adapter's local `text()` /
`range()` shim is preserved as a one-liner that delegates,
keeping the call-site shape (`self.text(n, s)`) so the bulk of
each adapter reads unchanged.

Net effect: ~150 lines of duplication removed; any future fix to
range arithmetic / signature truncation lands in one place.

## 2. `ImportKind` normalization

`Import` gained a `kind: ImportKind` field with three variants:

- `Header` — C / C++ `#include`
- `Module` — single-token namespaces (Go `"fmt"`, Python
  `os.path`, Ruby `'json'`, Clojure `clojure.string`)
- `Qualified` — fully-qualified names with explicit scoping
  (Java `java.util.List`, Rust `std::sync::Arc`, TypeScript
  module specifiers)

Lets cross-language queries ("what files import this module?")
normalize without re-parsing the source string. Each adapter
sets `kind` on every `Import` it constructs.

## 3. UTF-8 fallback → `tracing::debug!`

`node.utf8_text(s).unwrap_or("")` (the pattern every adapter
used) was silent. The fallback is now logged at `debug` level
via `common::node_text` — visible under `--verbose` (which
enables `dirge=debug`), quiet by default.

Chose `debug` over `warn` deliberately: UTF-8 failures aren't
load-bearing (the resulting empty symbol name is filtered out
downstream) and the log line exists for diagnostic purposes only.
Default-mode users running on a single oddball file shouldn't
see a noisy warning.

## 4. `exports` field populated

The field on `ExtractedFile` was `Vec::new()` in every
adapter; now backfilled from `is_exported=true` symbols at the
end of each `extract()`. TypeScript + Python preserve their
existing explicit-exports list (TS index re-exports are
load-bearing); other adapters fall through to the
is_exported-derived backfill when their per-language exports
vec is empty.

Consumers asking "what does this file export?" can now read
`extracted.exports` directly instead of iterating the symbol
vec.

## Tests

3 new regression tests:
- `byte_range_from_node_uses_1_based_lines` — pins the new
  `From<Node>` impl's line-number contract (the 7 hand-written
  copies all used `row + 1`; the shared converter has to match).
- `imports_are_tagged_module_kind` (Clojure) — verifies the
  kind classification.
- `exports_mirror_is_exported_symbols` (Clojure) — verifies
  the backfill picks up `defn` while filtering `defn-`.

812 all-features / 729 plugin / 603 default pass (was 809 / 729 /
603). All build profiles + `cargo fmt --check` clean.

## Files touched

- `src/semantic/common.rs` (new)
- `src/semantic/types.rs` — `ImportKind` enum, `Import.kind`
  field, `ByteRange::From<Node>` impl, `exports` docstring.
- `src/semantic/mod.rs` — wire `common` module.
- `src/semantic/adapters/{bash,clojure,go,ruby,rust,java,c,cpp,python,typescript}.rs` — refactored to use shared helpers + new
  Import shape + populate exports.
@yogthos
yogthos merged commit 585676f into main May 21, 2026
1 check passed
@yogthos
yogthos deleted the refactor/semantic-helpers-imports-exports branch May 21, 2026 20:07
allen-munsch pushed a commit to allen-munsch/dirge that referenced this pull request Jun 3, 2026
…d exports (dirge-code#117)

Four cross-cutting cleanups identified in the adapter review,
applied across all 9 language adapters (bash + clojure + go + ruby +
rust + java + c + cpp + python + typescript).

## 1. Shared helpers — `semantic/common.rs`

Three helpers previously duplicated 7+ times across adapters now
live in one place:

- `node_text(node, source)` — UTF-8 decode with structured-log
  fallback (see #3).
- `find_node_at_range(root, start, end)` — depth-first range
  match used by every `find_callees_in_range` impl.
- `signature_first_line(node, source)` and
  `signature_up_to_body(node, source)` — the two signature
  builders. Per-adapter rules choose between them based on the
  language's body-field convention.

`ByteRange::from(tree_sitter::Node)` (in `types.rs`) replaces
the 7 hand-written constructors. Each adapter's local `text()` /
`range()` shim is preserved as a one-liner that delegates,
keeping the call-site shape (`self.text(n, s)`) so the bulk of
each adapter reads unchanged.

Net effect: ~150 lines of duplication removed; any future fix to
range arithmetic / signature truncation lands in one place.

## 2. `ImportKind` normalization

`Import` gained a `kind: ImportKind` field with three variants:

- `Header` — C / C++ `#include`
- `Module` — single-token namespaces (Go `"fmt"`, Python
  `os.path`, Ruby `'json'`, Clojure `clojure.string`)
- `Qualified` — fully-qualified names with explicit scoping
  (Java `java.util.List`, Rust `std::sync::Arc`, TypeScript
  module specifiers)

Lets cross-language queries ("what files import this module?")
normalize without re-parsing the source string. Each adapter
sets `kind` on every `Import` it constructs.

## 3. UTF-8 fallback → `tracing::debug!`

`node.utf8_text(s).unwrap_or("")` (the pattern every adapter
used) was silent. The fallback is now logged at `debug` level
via `common::node_text` — visible under `--verbose` (which
enables `dirge=debug`), quiet by default.

Chose `debug` over `warn` deliberately: UTF-8 failures aren't
load-bearing (the resulting empty symbol name is filtered out
downstream) and the log line exists for diagnostic purposes only.
Default-mode users running on a single oddball file shouldn't
see a noisy warning.

## 4. `exports` field populated

The field on `ExtractedFile` was `Vec::new()` in every
adapter; now backfilled from `is_exported=true` symbols at the
end of each `extract()`. TypeScript + Python preserve their
existing explicit-exports list (TS index re-exports are
load-bearing); other adapters fall through to the
is_exported-derived backfill when their per-language exports
vec is empty.

Consumers asking "what does this file export?" can now read
`extracted.exports` directly instead of iterating the symbol
vec.

## Tests

3 new regression tests:
- `byte_range_from_node_uses_1_based_lines` — pins the new
  `From<Node>` impl's line-number contract (the 7 hand-written
  copies all used `row + 1`; the shared converter has to match).
- `imports_are_tagged_module_kind` (Clojure) — verifies the
  kind classification.
- `exports_mirror_is_exported_symbols` (Clojure) — verifies
  the backfill picks up `defn` while filtering `defn-`.

812 all-features / 729 plugin / 603 default pass (was 809 / 729 /
603). All build profiles + `cargo fmt --check` clean.

## Files touched

- `src/semantic/common.rs` (new)
- `src/semantic/types.rs` — `ImportKind` enum, `Import.kind`
  field, `ByteRange::From<Node>` impl, `exports` docstring.
- `src/semantic/mod.rs` — wire `common` module.
- `src/semantic/adapters/{bash,clojure,go,ruby,rust,java,c,cpp,python,typescript}.rs` — refactored to use shared helpers + new
  Import shape + populate exports.

Co-authored-by: Yogthos <yogthos@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant