Skip to content

Commit dd8167d

Browse files
author
cowork-bot
committed
cowork-bot: re-add namespace/side-effect import consumption lost by checkout-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.
1 parent efa7ce2 commit dd8167d

1 file changed

Lines changed: 28 additions & 4 deletions

File tree

src/deadcode/scanner.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,22 @@ def unreferenced_components(self) -> list[Finding]:
120120
re.DOTALL,
121121
)
122122

123+
# Namespace import: `import * as Utils from './utils'`. A namespace binding
124+
# reaches every export of the module through one object (`Utils.foo`), so
125+
# individual names cannot be attributed to use sites. Like a barrel re-export,
126+
# the whole export surface of the target module counts as consumed; otherwise
127+
# exports used only via a namespace are falsely reported as unused with
128+
# removable=True — live code queued for deletion.
129+
_NAMESPACE_IMPORT_PATTERN = re.compile(
130+
r"import\s+\*\s+as\s+\w+\s+from\s*['\"]([^'\"]+)['\"]"
131+
)
132+
133+
# Bare side-effect import: `import './polyfill';` — executes the module without
134+
# binding any names, consuming its entire export surface.
135+
_SIDE_EFFECT_IMPORT_PATTERN = re.compile(
136+
r"^\s*import\s*['\"]([^'\"]+)['\"]", re.MULTILINE
137+
)
138+
123139
# className="..." or className={...} in JSX
124140
_CLASSNAME_PATTERN = re.compile(
125141
r"class(?:Name)?\s*[=:]\s*['\"]([^'\"]+)['\"]|"
@@ -404,14 +420,16 @@ def _parse_reexports(
404420
imports: dict[str, set[str]],
405421
star_reexports: list[tuple[str, str]],
406422
) -> None:
407-
"""Record re-export forwarding so barrel/index files don't false-positive.
423+
"""Record whole-module consumption so source modules don't false-positive.
408424
409425
``export { A, B as C } from './mod'`` consumes ``A`` and ``B`` from
410426
``./mod``; the consumed (left-hand) names are registered as imports of
411427
this file so the source module's exports are not reported as unused.
412-
``export * from './mod'`` forwards every export of ``./mod``; the
413-
(file, module) pair is recorded so those exports can be treated as used
414-
once ``./mod`` is resolved to a scanned file.
428+
``export * from './mod'``, ``import * as NS from './mod'``, and bare
429+
``import './mod'`` all consume ``./mod``'s *entire* export surface —
430+
individual names cannot be attributed — so each (file, module) pair is
431+
recorded; those exports are treated as used once ``./mod`` resolves to
432+
a scanned file.
415433
"""
416434
for m in _REEXPORT_PATTERN.finditer(content):
417435
named = m.group(1)
@@ -430,6 +448,12 @@ def _parse_reexports(
430448
else:
431449
# `export * from './mod'` — resolved to a file in phase 2.
432450
star_reexports.append((rel_path, module_path))
451+
for m in _NAMESPACE_IMPORT_PATTERN.finditer(content):
452+
# `import * as NS from './mod'` — whole-module consumption.
453+
star_reexports.append((rel_path, m.group(1)))
454+
for m in _SIDE_EFFECT_IMPORT_PATTERN.finditer(content):
455+
# `import './mod'` — side-effect-only consumption.
456+
star_reexports.append((rel_path, m.group(1)))
433457

434458
@staticmethod
435459
def _resolve_relative_module(importer_rel: str, spec: str, file_set: set[str]) -> str | None:

0 commit comments

Comments
 (0)