Skip to content

Commit 687cb17

Browse files
authored
feat(query): detect import conflicts (#100)
## Summary - add `query import-conflicts` to find modules reached through runtime-static and dynamic paths from the same configured entrypoint - print every relevant importer and specifier plus one shortest static/dynamic proof route - ignore type-only, test-only, cross-entrypoint, and unreachable-loader false positives - support text and JSON output, exit 1 for findings, and document the workflow in README and site docs ## Validation - `bazel test //crates/codescythe:codescythe_test //crates/codescythe_cli:codescythe_cli_test //crates/codescythe_cli:codescythe_cli_e2e_test` - `cargo fmt --all -- --check` - `git diff --check` - docs build attempted; blocked by environment DNS resolving registry.npmjs.org
1 parent 52d879d commit 687cb17

8 files changed

Lines changed: 1040 additions & 22 deletions

File tree

README.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ codescythe query somepath src/main.ts src/features/
143143
codescythe query allpaths src/main.ts src/runtime.ts:initRuntime --json
144144
codescythe query allpaths src/main.ts src/runtime.ts:initRuntime --output mermaid
145145
codescythe query allpaths src/main.ts src/runtime.ts:initRuntime --output svg > graph.svg
146+
codescythe query import-conflicts
146147
```
147148

148149
Selectors can point at files, directories, or exported symbols written as
@@ -154,6 +155,130 @@ by `-C` or `--config`.
154155
many.
155156
- `allpaths` returns the subgraph of every node and edge that lies on a path
156157
from the source selector to the target selector.
158+
- `import-conflicts` lists modules reached through both runtime-static and
159+
dynamic paths from the same configured entrypoint, including every conflicting
160+
importer and specifier plus one shortest proof route. Type-only and configured
161+
test-file imports do not count. It exits with status `1` when conflicts are
162+
found and supports `--json` for CI or bulk cleanup.
163+
164+
### Finding Static/Dynamic Import Conflicts
165+
166+
Use `import-conflicts` when a module intended as a lazy boundary may also be
167+
pulled into the runtime graph eagerly:
168+
169+
```sh
170+
codescythe query import-conflicts -C . --config codescythe.json
171+
```
172+
173+
Each finding names the resolved target, then prints every runtime-static and
174+
dynamic edge that reaches it:
175+
176+
```text
177+
Found 1 module with runtime static/dynamic import conflicts:
178+
179+
src/module.ts
180+
runtime static imports:
181+
src/main.ts -- named import ./module
182+
dynamic imports:
183+
src/main.ts -- dynamic import ./module
184+
shortest conflicting entrypoint route (src/main.ts):
185+
runtime static path:
186+
src/main.ts
187+
-- named import ./module:value -> src/module.ts:value
188+
-- defined in file value -> src/module.ts
189+
dynamic path:
190+
src/main.ts
191+
-- dynamic import ./module -> src/module.ts
192+
```
193+
194+
Runtime-static edges include named imports, side-effect imports, re-exports,
195+
and namespace imports or member access. Dynamic edges come from supported
196+
string-literal `import()` calls. `import type` edges are reported as
197+
`typeImport` by path queries but do not affect bundling, so they are excluded
198+
from conflict findings. Configured test files are also excluded.
199+
200+
A finding is reported only when one configured entrypoint can reach the target
201+
through runtime-static edges and can also reach a dynamic importer of that
202+
target. The printed route is the shortest deterministic proof: one fully static
203+
path from the entrypoint to the target, plus one runtime path ending at the
204+
conflicting dynamic import. This avoids treating imports isolated in separate
205+
entrypoint graphs as conflicts.
206+
207+
The command exits with status `1` when findings exist and `0` when the scan is
208+
clean. Use `--json` for CI or scripted cleanup:
209+
210+
```json
211+
{
212+
"scannedFileCount": 2,
213+
"entrypointCount": 1,
214+
"conflicts": [
215+
{
216+
"target": "src/module.ts",
217+
"runtimeStaticImports": [
218+
{
219+
"importer": "src/main.ts",
220+
"specifier": "./module",
221+
"kind": "namedImport"
222+
}
223+
],
224+
"dynamicImports": [
225+
{
226+
"importer": "src/main.ts",
227+
"specifier": "./module",
228+
"kind": "dynamicImport"
229+
}
230+
],
231+
"entrypointRoute": {
232+
"entrypoint": "src/main.ts",
233+
"runtimeStaticPath": {
234+
"nodes": [
235+
{ "id": "file:src/main.ts", "kind": "file", "path": "src/main.ts" },
236+
{ "id": "export:src/module.ts:value", "kind": "export", "path": "src/module.ts", "symbol": "value" },
237+
{ "id": "file:src/module.ts", "kind": "file", "path": "src/module.ts" }
238+
],
239+
"edges": [
240+
{
241+
"from": "file:src/main.ts",
242+
"to": "export:src/module.ts:value",
243+
"kind": "namedImport",
244+
"importer": "src/main.ts",
245+
"specifier": "./module",
246+
"imported": "value"
247+
},
248+
{
249+
"from": "export:src/module.ts:value",
250+
"to": "file:src/module.ts",
251+
"kind": "exportDefinition",
252+
"imported": "value"
253+
}
254+
]
255+
},
256+
"dynamicPath": {
257+
"nodes": [
258+
{ "id": "file:src/main.ts", "kind": "file", "path": "src/main.ts" },
259+
{ "id": "file:src/module.ts", "kind": "file", "path": "src/module.ts" }
260+
],
261+
"edges": [
262+
{
263+
"from": "file:src/main.ts",
264+
"to": "file:src/module.ts",
265+
"kind": "dynamicImport",
266+
"importer": "src/main.ts",
267+
"specifier": "./module"
268+
}
269+
]
270+
}
271+
}
272+
}
273+
]
274+
}
275+
```
276+
277+
Fix the listed runtime-static edges, rerun the command, and confirm the target
278+
disappears. Entrypoint reachability follows Codescythe's source graph, not
279+
bundler-specific chunk naming, shared-chunk extraction, or preload behavior.
280+
Use a production bundle inspection when you need proof of emitted boundaries
281+
and transferred bytes.
157282

158283
Text output is optimized for terminal inspection, including the resolved
159284
selector kinds, match counts, and a reachability summary that helps explain

crates/codescythe/analyze.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ mod tests;
1414
pub use doctor::doctor_config;
1515
pub use explain::ignored_unresolved_patterns_for_file;
1616
pub use query::{
17+
ImportConflict, ImportConflictEdge, ImportConflictResult, ImportConflictRoute,
1718
QueryDiagnostics, QueryEdge, QueryEdgeKind, QueryGraph, QueryKind, QueryNode, QueryNodeKind,
1819
QueryPath, QueryRequest, QueryResult, QuerySelector, QuerySelectorKind, QueryUnresolvedImport,
19-
query_path, render_query_mermaid, render_query_svg,
20+
query_import_conflicts, query_path, render_query_mermaid, render_query_svg,
2021
};
2122
pub use resolver::{
2223
source_alias_fix_blocking_ignore_warnings_for_config, source_alias_ignore_warnings_for_config,

0 commit comments

Comments
 (0)