diff --git a/.claude/rules/detection.md b/.claude/rules/detection.md index 272e0eaeb..882fe2d15 100644 --- a/.claude/rules/detection.md +++ b/.claude/rules/detection.md @@ -41,11 +41,11 @@ Non-obvious implementation details for each detection feature. These are NOT dis - **JSDoc `import()` type references**: JSDoc comments matching `import('./path').Member` inside `@param`, `@returns`, `@type`, `@typedef`, `@callback`, or other tag contexts are extracted as type-only imports. Supports single/double quotes, bare specifiers, parent-relative paths, union annotations with multiple `import()` expressions per comment, and nested member access (first segment wins). Side-effect-only `import('./types')` without `.Member` is recorded as `SideEffect` to keep the file reachable. Prevents false `unused-export` reports for types referenced only from JSDoc annotations in untyped JS files. Only `/** */` block comments are scanned (single-star `/* */` is not JSDoc). See issue #105 (till's comment). - **`typeof import('./path').X` in TypeScript type positions**: `TSImportType` AST nodes are extracted as type-only `ImportInfo` entries. A flat qualifier (`typeof import('./x').foo`) credits a `Named("foo")` import; a nested qualifier (`typeof import('./x').A.B`) walks to the leftmost identifier and credits `Named("A")`; no qualifier and the indexed-access form (`typeof import('./x')['default']`) record a `SideEffect` import so the target file stays reachable. Fires inside any type position, including the bodies of `declare global { ... }` and `declare module 'pkg' { ... }` ambient declarations produced by unplugin-auto-import (`auto-imports.d.ts`) and unplugin-vue-components (`components.d.ts`). Pairs with the graph-level promotion of every `.d.ts` / `.d.mts` / `.d.cts` file to an overall reachability root (`ModuleGraph::build_with_reachability_roots` augments `entry_point_ids` with declaration files), so the new edges propagate through BFS even when the d.ts file is not in the user's `entry` config. Side effect: `is_entry_point()` returns `true` for declaration files, which auto-skips them in `unused-exports` / `unused-types` detection (preventing false positives on module-augmentation interfaces TypeScript consumes via global type resolution); pass `--include-entry-exports` to re-enable. Runtime / test reachability is unchanged because declaration files have no runtime side effects. See issues #396 and #397. - **`new URL('./', import.meta.url)` directory specifiers skipped**: the worker / asset detector for `new URL(literal, import.meta.url)` admits relative specifiers (`./`, `../` prefixes) but rejects directory-only forms (`./`, `../`, `./foo/`). Directory URLs do not resolve to a module, so reporting them as `unresolved-imports` was a false positive on the canonical ESM `__dirname` idiom (`fileURLToPath(new URL('./', import.meta.url))`). File-pointing specifiers (`./worker.js`, `./assets/foo.svg`) are unaffected. See issue #399. -- **Vue/Svelte SFC**: handles `>` in quoted attributes like `generic="T extends Foo"`, `` as runtime HTML, not bundled SFC script modules. Fallow now keeps parsing the `src` metadata for span fidelity but no longer emits synthetic imports for Svelte `script src` references, so root-relative browser assets do not become false `unresolved-imports`. Vue external scripts keep their existing graph edges. Thanks [@codingthat](https://github.com/codingthat) for the report. (Closes [#835](https://github.com/fallow-rs/fallow/issues/835).) - **TanStack Router `routeFileIgnorePattern` warnings now call out JavaScript regex compatibility instead of treating every unsupported pattern as a user typo.** TanStack accepts JavaScript regular expressions, while fallow validates route ignore patterns with Rust's regex engine before matching route file segments. Patterns that use JavaScript-only syntax such as lookahead are still ignored during analysis so the run can continue, but the warning now names `routeFileIgnorePattern`, points at the source config file when known, and explains that the syntax is unsupported by fallow's Rust regex engine. (Refs [#513](https://github.com/fallow-rs/fallow/issues/513).) - **Nuxt composables and utils referenced only through script auto-imports are now tracked in the module graph.** Fallow now records unresolved value identifiers in JS/TS and Vue/Svelte script blocks, then resolves Nuxt convention exports from top-level `composables/`, `app/composables/`, `utils/`, and `app/utils/`, plus recursive `shared/utils/` and `shared/types/`, during graph build. This keeps files like `composables/useCounter.ts`, `utils/format-price.ts`, and named exports from `composables/index.ts` reachable when a page calls `useCounter()` or `formatPrice()` without an import. Local declarations, explicit imports, type-only references, and known JS/Web/Vue/Nuxt built-ins do not synthesize edges. With `autoImports: true`, component entry-pattern removal remains guarded by `components:` config, while composable/util pattern removal is separately guarded by `imports:` config. (Closes [#739](https://github.com/fallow-rs/fallow/issues/739).) - **`fallow health` now surfaces CRAP coverage-source consistency in JSON and lets teams tune the secondary CRAP refactor band.** CRAP source precedence is explicit (`template` inheritance first, then Istanbul data including unmatched files, then static estimates), health JSON emits `summary.coverage_source_consistency` and grouped health emits `groups[].coverage_source_consistency` whenever CRAP findings carry source data, and `health.crapRefactorBand` configures the old fixed 5-point window that adds a secondary `refactor-function` action to near-threshold CRAP-only findings. This closes the remaining JSON/config contract work from issue #474 after the human-output clarification below. (Closes [#474](https://github.com/fallow-rs/fallow/issues/474).) diff --git a/crates/core/tests/integration_test/sfc_parsing.rs b/crates/core/tests/integration_test/sfc_parsing.rs index d163b46f7..eec618a6d 100644 --- a/crates/core/tests/integration_test/sfc_parsing.rs +++ b/crates/core/tests/integration_test/sfc_parsing.rs @@ -427,6 +427,24 @@ fn sveltekit_generated_types_not_unresolved() { ); } +#[test] +fn sveltekit_head_script_src_not_unresolved() { + let root = fixture_path("issue-835-svelte-script-src"); + let config = create_config(root); + let results = fallow_core::analyze(&config).expect("analysis should succeed"); + + let unresolved_specs: Vec<&str> = results + .unresolved_imports + .iter() + .map(|u| u.import.specifier.as_str()) + .collect(); + + assert!( + !unresolved_specs.contains(&"/some-lib.min.js"), + "SvelteKit markup script src should not be unresolved: {unresolved_specs:?}" + ); +} + #[test] fn sveltekit_workspace_types_not_unresolved() { let root = fixture_path("workspace-sveltekit"); diff --git a/crates/extract/src/asset_url.rs b/crates/extract/src/asset_url.rs index 79231c59b..632b0a5e5 100644 --- a/crates/extract/src/asset_url.rs +++ b/crates/extract/src/asset_url.rs @@ -2,12 +2,12 @@ //! //! Used by parsers that emit side-effect imports from user-authored asset //! references: Angular `@Component({ templateUrl, styleUrl })`, HTML -//! `
hi
"#, "App.svelte", ); - assert_eq!(info.imports.len(), 1); - assert_eq!(info.imports[0].source, "./store.js"); + assert!( + info.imports.is_empty(), + "Svelte markup script src should not create imports: {:?}", + info.imports + ); +} + +#[test] +fn svelte_script_src_root_relative_creates_no_import() { + let info = parse_sfc( + r#"
hi
"#, + "App.svelte", + ); + assert!( + info.imports.is_empty(), + "Svelte root-relative script src should not create imports: {:?}", + info.imports + ); +} + +#[test] +fn svelte_script_src_relative_creates_no_import() { + let info = parse_sfc( + r#"
hi
"#, + "App.svelte", + ); + assert!( + info.imports.is_empty(), + "Svelte relative script src should not create imports: {:?}", + info.imports + ); +} + +#[test] +fn svelte_script_src_type_module_creates_no_import() { + let info = parse_sfc( + r#"
hi
"#, + "App.svelte", + ); + assert!( + info.imports.is_empty(), + "Svelte type=module script src should not create imports: {:?}", + info.imports + ); +} + +#[test] +fn svelte_head_script_src_creates_no_import() { + let info = parse_sfc( + r#""#, + "App.svelte", + ); + assert!( + info.imports.is_empty(), + "Svelte head script src should not create imports: {:?}", + info.imports + ); +} + +#[test] +fn svelte_script_src_cdn_urls_create_no_import() { + for src in [ + "https://cdn.example.com/lib.js", + "http://cdn.example.com/lib.js", + "//cdn.example.com/lib.js", + ] { + let source = format!(r#"
hi
"#); + let info = parse_sfc(&source, "App.svelte"); + assert!( + info.imports.is_empty(), + "Svelte CDN script src should not create imports for {src}: {:?}", + info.imports + ); + } } #[test] diff --git a/scripts/agent-files.sha256 b/scripts/agent-files.sha256 index 45f984af0..f71a47fb4 100644 --- a/scripts/agent-files.sha256 +++ b/scripts/agent-files.sha256 @@ -12,7 +12,7 @@ b40014209809c5a25ce8d4f8b434a77a1fb6d9deb78b9059badd098d39062c13 .claude/agents 7f64808f1721c30f92123e2d363e9b7b5c782b3155571d23a66a58b2b3a9bf26 .claude/rules/cli-crate.md ba291bbb73bed92aa102c43eb6360f1107e1d86af443d3249b800f4292900c07 .claude/rules/code-quality.md 80d9fea77af6652946d6eefe4f30ed35c1df8dcbabeb9d8f47f4308019caddc6 .claude/rules/core-crate.md -3e0ebd2c95ae273de689770bb06939fa94d291169fbdf836e0425f886662ac90 .claude/rules/detection.md +6f1c7c3b2f7594388d212b946e1e8055f37b1e8aead48c6449577ff5b4d5732d .claude/rules/detection.md 4d34b7f8589ea48e012346d294947867ddaa78fb74263707f6b8951817f357db .claude/rules/extract-crate.md d550b4a609495732c98006423b168437d8b6c435ab7ed360adf15b8a4346fc71 .claude/rules/graph-crate.md 0279743bfb1c788af2f0273ff2dd6bc9c255743813d0213798f7bd4c71002ab9 .claude/rules/lsp-server.md diff --git a/tests/fixtures/issue-835-svelte-script-src/package.json b/tests/fixtures/issue-835-svelte-script-src/package.json new file mode 100644 index 000000000..38fd452b8 --- /dev/null +++ b/tests/fixtures/issue-835-svelte-script-src/package.json @@ -0,0 +1,12 @@ +{ + "name": "issue-835-svelte-script-src", + "private": true, + "dependencies": { + "svelte": "^5.0.0" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^3.0.0", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^4.0.0" + } +} diff --git a/tests/fixtures/issue-835-svelte-script-src/src/routes/+page.svelte b/tests/fixtures/issue-835-svelte-script-src/src/routes/+page.svelte new file mode 100644 index 000000000..7d9732f86 --- /dev/null +++ b/tests/fixtures/issue-835-svelte-script-src/src/routes/+page.svelte @@ -0,0 +1,5 @@ + + + + +

Issue 835