What happened?
When a module is loaded only via a bare dynamic-import expression — () => import('./X'), without .then(m => m.default) — fallow correctly marks the file as reachable but reports the file's default export as unused.
This is the conventional consumption shape for Angular Router (loadChildren: () => import('./X')), React (lazy(() => import('./X'))), Vue (defineAsyncComponent), Solid (lazy()), and Remix / Next / Nuxt route modules — the runtime consumes .default internally, the user code never spells it.
The control case (explicit .then(m => m.default)) does not reproduce, so the bug is specific to the implicit-default form.
Reproduction
mkdir -p /tmp/fallow-repro-default-export/src/feature && cd /tmp/fallow-repro-default-export
cat > package.json <<'EOF'
{ "name": "repro-default-export", "version": "0.0.1", "private": true, "main": "src/main.ts" }
EOF
cat > src/main.ts <<'EOF'
const route = {
path: 'feature',
loadChildren: () => import('./feature/feature.routes')
};
console.log(route);
EOF
cat > src/feature/feature.routes.ts <<'EOF'
const featureRoutes = [{ path: '', component: 'PlaceholderComponent' }];
export default featureRoutes;
EOF
cat > .fallowrc.json <<'EOF'
{
"$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/schema.json",
"entry": ["src/main.ts"]
}
EOF
# Empty node_modules silences a stderr warning that would break the jq pipe below.
mkdir -p node_modules
fallow dead-code --format json --quiet | jq '{summary, unused_exports}'
fallow dead-code --trace 'src/feature/feature.routes.ts:default'
Control (should NOT reproduce):
cat > src/main.ts <<'EOF'
const route = {
path: 'feature',
loadChildren: () => import('./feature/feature.routes').then(m => m.default)
};
console.log(route);
EOF
fallow dead-code --format json --quiet | jq '{summary, unused_exports}'
Expected behavior
unused_exports: [] for both the buggy form and the control. A bare () => import('./X') should treat the resolved namespace's default as used the same way an explicit .then(m => m.default) does.
Actual behavior
Buggy form:
Trace:
UNUSED default in src/feature/feature.routes.ts
File: reachable
Reason: No references found — export is unused
Control: { "summary": { "total_issues": 0 }, "unused_exports": [] }.
Fallow version
2.60.0
Operating system
macOS
Configuration
What happened?
When a module is loaded only via a bare dynamic-import expression —
() => import('./X'), without.then(m => m.default)— fallow correctly marks the file as reachable but reports the file'sdefaultexport as unused.This is the conventional consumption shape for Angular Router (
loadChildren: () => import('./X')), React (lazy(() => import('./X'))), Vue (defineAsyncComponent), Solid (lazy()), and Remix / Next / Nuxt route modules — the runtime consumes.defaultinternally, the user code never spells it.The control case (explicit
.then(m => m.default)) does not reproduce, so the bug is specific to the implicit-default form.Reproduction
Control (should NOT reproduce):
Expected behavior
unused_exports: []for both the buggy form and the control. A bare() => import('./X')should treat the resolved namespace'sdefaultas used the same way an explicit.then(m => m.default)does.Actual behavior
Buggy form:
{ "summary": { "total_issues": 1, "unused_exports": 1 /* …all other counters 0… */ }, "unused_exports": [ { "path": "src/feature/feature.routes.ts", "export_name": "default", "line": 2, "actions": [ { "type": "remove-export", "auto_fixable": true, "description": "Remove the unused export from the public API" }, { "type": "suppress-line", "auto_fixable": false, "comment": "// fallow-ignore-next-line unused-export" } ] } ] }Trace:
Control:
{ "summary": { "total_issues": 0 }, "unused_exports": [] }.Fallow version
2.60.0
Operating system
macOS
Configuration
default