Skip to content

default export unused-flagged when consumer is a bare () => import('./X') (no .then(m => m.default)) #253

@OmerGronich

Description

@OmerGronich

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:

{
  "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:

  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

default

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions