Skip to content

cowork: namespace & side-effect imports consume a module's whole export surface - #49

Merged
Coding-Dev-Tools merged 11 commits into
masterfrom
cowork/improve-deadcode
Oct 3, 2026
Merged

Coding-Dev-Tools merged 11 commits into
masterfrom
cowork/improve-deadcode

Conversation

@Coding-Dev-Tools

Copy link
Copy Markdown
Owner

Problem

The dead-code scanner ignored two common import forms entirely:

  • import * as Utils from './utils' (namespace import)
  • import './polyfill' (bare side-effect import)

Exports consumed only through these forms were falsely reported as unused_export with removable=True — meaning deadcode remove would blank live code. Reproduced pre-fix: a module whose only consumer used Utils.helper() was flagged removable.

Fix

Both forms now resolve their module specifier like barrel star-reexports already do (_resolve_relative_module): when the target resolves to a scanned file, its entire export surface is treated as used. Bare package specifiers (e.g. 'lodash') stay unresolvable and keep flagging local modules correctly.

Tests

New tests/test_namespace_sideeffect_imports.py (5 cases):

  1. namespace import marks all target exports used
  2. export * as ns from ... re-export ditto
  3. bare-specifier namespace import cannot mark local module used
  4. side-effect import marks all target exports used
  5. control: no consumer still flags exports

Full suite: 121 passed, ruff clean.

DevForge Engineer and others added 10 commits July 8, 2026 01:34
…s Revenue Holdings / stale 2026 year); W-directed fleet-wide pass
…d-code scan

Named (`export { X } from './mod'`), renamed (`export { X as Y }`),
type (`export { type X }`), and star (`export * from './mod'`) re-exports
now mark the forwarded symbols as used, so barrel/index files no longer
produce false-positive 'unused_export' findings flagged removable=True
(which could delete live public API). Resolves `export *` specifiers to
scanned files (incl. directory index.*). Adds TestReexportForwarding
(8 cases) + removes a pre-existing F841 unused var. 113 tests pass, ruff clean.
… mixed default+named imports, and correct group-index reversal

- Rewrote _IMPORT_PATTERN regex to handle: import type {Foo}, import Default, {Named},
  import {type Foo}, and import Foo as Bar forms
- Fixed _parse_imports group-number reversal (group 1 = named imports block, group 2 = default)
- Strips 'type ' prefix from named import entries in both named-block positions
- All 113 existing tests pass; ruff clean
…code

# Conflicts:
#	CHANGELOG.md
#	src/deadcode/scanner.py
#	tests/test_scanner.py
…fect imports as whole-module consumption

A namespace binding (import * as Utils from './utils') or a bare side-effect
import (import './polyfill') consumes the target module's entire export
surface. The scanner previously ignored both forms entirely, so exports used
ONLY through them were falsely reported as unused with removable=True — live
code queued for deletion by 'deadcode remove'. Both now resolve like barrel
star-reexports: the resolved module's exports are treated as used. Bare
package specifiers stay unresolvable and keep flagging.

+5 regression tests (namespace, export * as ns, side-effect, bare-specifier,
no-consumer control). Full suite: 121 passed, ruff clean.
The previous commit (2ef1848) was built from a stale temp index and
accidentally recorded deletions of 34 unrelated tracked files. This commit
restores the full tree of 30e09bb while keeping the intended scanner fix
(namespace/side-effect imports as whole-module consumption) and its 5
regression tests. No force-push used.
…heckout-index

efa7ce2 restored the tree but its checkout-index step reverted
src/deadcode/scanner.py to the pre-fix version. This commit re-applies the
scanner fix from 2ef1848: import * as NS / bare side-effect imports consume
the target module's whole export surface (resolves like barrel star-reexports).

Final tree vs master-base 30e09bb = exactly scanner.py fix + 5-test file.
@github-actions

github-actions Bot commented Aug 24, 2026 •

Copy link
Copy Markdown

🤖 Automated Code Review

✅ Ruff Lint — No issues

⚠️ Ruff Format — Formatting needed

unformatted: File would be reformatted
 --> src/deadcode/__main__.py:2:1
  |
1 | """Allow running deadcode as: python -m deadcode"""
2 +
3 | from .cli import cli
  |

unformatted: File would be reformatted
   --> src/deadcode/cli.py:66:15
    |
65  | @click.option("--project", "-p", default=".", help="Project directory to scan")
66  + @click.option("--ignore", "-i", multiple=True, help="Additional ignore patterns (gitignore-style)")
67  | @click.option(
    -     "--ignore", "-i", multiple=True, help="Additional ignore patterns (gitignore-style)"
    - )
    - @click.option(
68  |     "--include",
--------------------------------------------------------------------------------
73  | @click.pass_context
    - def cli(
    -     ctx: click.Context, project: str, ignore: tuple[str, ...], include: tuple[str, ...]
    - ) -> None:
74  + def cli(ctx: click.Context, project: str, ignore: tuple[str, ...], include: tuple[str, ...]) -> None:
75  |     """DeadCode — Find and remove dead code in TS/React/Next.js projects.
--------------------------------------------------------------------------------
112 | @cli.command()
    - @click.option(
    -     "--json-output", "-j", is_flag=True, help="Alias for --format=json (deprecated)"
    - )

✅ Secret Detection — Clean

✅ Large Files — Within limits

📊 Diff Stats — 2 file(s) changed

 src/deadcode/scanner.py                    |  46 ++++++++++--
 tests/test_namespace_sideeffect_imports.py | 111 +++++++++++++++++++++++++++++
 2 files changed, 153 insertions(+), 4 deletions(-)

Verdict: ⚠️ Warnings — Lint/format issues found. Recommend fixing before merge.

Automated by Coding-Dev-Tools/.github reusable workflow.

…s whole export surface (previously invisible -> exports used only via lazy loading flagged removable=True); +3 regression tests
@Coding-Dev-Tools
Coding-Dev-Tools marked this pull request as ready for review October 3, 2026 09:10
@Coding-Dev-Tools
Coding-Dev-Tools merged commit 272bfa8 into master Oct 3, 2026
13 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-03T09:14:05.629904Z 9624324 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9624324dc9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/deadcode/scanner.py
# exports used only via a namespace are falsely reported as unused with
# removable=True — live code queued for deletion.
_NAMESPACE_IMPORT_PATTERN = re.compile(
r"import\s+\*\s+as\s+\w+\s+from\s*['\"]([^'\"]+)['\"]"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle type-only namespace imports

When a TypeScript consumer uses the valid import type * as Types from './types' form, the type token prevents this pattern from matching. Consequently, exports referenced as Types.Foo are still reported with removable=True, and a one-line type or interface can be deleted by deadcode remove; allow the optional type modifier before *.

Useful? React with 👍 / 👎.

Comment thread src/deadcode/scanner.py
Comment on lines +460 to +462
for m in _NAMESPACE_IMPORT_PATTERN.finditer(content):
# `import * as NS from './mod'` — whole-module consumption.
star_reexports.append((rel_path, m.group(1)))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude commented-out namespace imports

When a source file contains a commented example such as // import * as Utils from './utils', this unanchored regex still matches because _parse_reexports scans the raw file contents. If the path resolves, every export from that module is then treated as used, hiding genuinely unused exports merely because an old import was commented out; import matches need to exclude comments and literals.

Useful? React with 👍 / 👎.

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