Skip to content

fix: Side-effect taint stopping at first importer - #91

Merged
martinnaj merged 1 commit into
masterfrom
mnaj-side-effect-stopping-at-first-importer
Aug 10, 2026
Merged

fix: Side-effect taint stopping at first importer#91
martinnaj merged 1 commit into
masterfrom
mnaj-side-effect-stopping-at-first-importer

Conversation

@martinnaj

@martinnaj martinnaj commented Aug 10, 2026

Copy link
Copy Markdown
Member

Risk: low

Summary by CodeRabbit

  • Bug Fixes

    • Improved change detection for import-time side effects.
    • Side-effect changes now propagate consistently through imported and re-exported modules.
    • Preserved side-effect classifications across dependency chains for more accurate analysis.
  • Documentation

    • Added release notes describing the updated side-effect propagation behavior.
  • Chores

    • Updated the release version to 0.25.5.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The analyzer now marks import-time side-effect changes with a dedicated sentinel and propagates that marker transitively through imports and re-exports. The release version changes to 0.25.5, with corresponding changelog updates.

Changes

Side-effect taint propagation

Layer / File(s) Summary
Side-effect taint marker
internal/analyzer/astdiff.go
AST diff results now include * and sideEffectTaint for import-time side-effect changes.
Importer and re-export propagation
internal/analyzer/analyzer.go
AnalyzeLibraryPackage and FindAffectedFiles propagate side-effect taint through importers and barrel re-exports.
Release metadata
VERSION, CHANGELOG.md
The version changes to 0.25.5. The changelog documents the propagation behavior and adds the comparison link.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ASTDiff
  participant AnalyzeLibraryPackage
  participant FindAffectedFiles
  participant Importer
  ASTDiff->>AnalyzeLibraryPackage: Provide "*" and sideEffectTaint
  AnalyzeLibraryPackage->>Importer: Taint all importer symbols
  Importer->>AnalyzeLibraryPackage: Propagate markers through imports and re-exports
  FindAffectedFiles->>Importer: Taint all importer symbols
  Importer->>FindAffectedFiles: Continue BFS propagation
Loading

Suggested reviewers: quanb-duy

Poem

I’m a rabbit in the import tree,
Carrying taint from leaf to lea.
Through barrels wide and modules bright,
Side effects hop in marked-up flight.
Version twenty-five-five lands just right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main fix: preventing side-effect taint propagation from stopping at the first importer.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/analyzer/analyzer.go`:
- Around line 786-801: Restrict import-time side-effect propagation to runtime
import or re-export edges by checking the edge’s non-type-only status before the
currentTainted[sideEffectTaint] handling in internal/analyzer/analyzer.go lines
786-801. Apply the same runtime-edge condition in FindAffectedFiles at
internal/analyzer/analyzer.go lines 1644-1654; type-only re-exports must not
taint barrel symbols or propagate sideEffectTaint.

In `@internal/analyzer/astdiff.go`:
- Around line 243-247: Update the affected-symbol logic around
hasSideEffectStmtChanges and bareImportsChanged so these checks run
independently of len(affected) and symbol-level changes. Whenever either
side-effect condition is true, append both "*" and sideEffectTaint, while
preserving existing handling for ordinary symbol changes.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 292df3ee-cad5-42f2-9c16-39d0601ebe43

📥 Commits

Reviewing files that changed from the base of the PR and between 4442e29 and c79fe4a.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • VERSION
  • internal/analyzer/analyzer.go
  • internal/analyzer/astdiff.go

Comment on lines +786 to +801
// Import-time side-effect transitivity: if the imported module has an
// import-time side effect (sideEffectTaint), importing it re-runs that
// side effect here, so this file becomes side-effectful too — taint all
// its symbols and carry "*" + sideEffectTaint so it keeps flowing to this
// file's own importers/re-exporters (a barrel re-exporting a side-effectful
// module becomes side-effectful itself).
// TODO: make this precise using the "sideEffects" field in each package's
// package.json — a module marked side-effect-free is tree-shaken and not
// re-executed on import, so it should not propagate. Until then we assume
// the worst and propagate through every import/re-export edge. Follow-up.
if currentTainted[sideEffectTaint] {
for _, sym := range importerAnalysis.Symbols {
newlyTainted = append(newlyTainted, sym.Name)
}
newlyTainted = append(newlyTainted, "*", sideEffectTaint)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Do not propagate import-time side effects through type-only re-export edges.

Both graph builders include exp.IsTypeOnly re-exports as import edges. If the source has "*" and sideEffectTaint, a statement such as export type { T } from "./source" reaches these blocks and taints all runtime symbols in the barrel. Type-only re-exports do not load ./source at runtime.

  • internal/analyzer/analyzer.go#L786-L801: apply side-effect propagation only when the edge represents a runtime import or re-export.
  • internal/analyzer/analyzer.go#L1644-L1654: apply the same runtime-edge condition in FindAffectedFiles.
📍 Affects 1 file
  • internal/analyzer/analyzer.go#L786-L801 (this comment)
  • internal/analyzer/analyzer.go#L1644-L1654
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/analyzer/analyzer.go` around lines 786 - 801, Restrict import-time
side-effect propagation to runtime import or re-export edges by checking the
edge’s non-type-only status before the currentTainted[sideEffectTaint] handling
in internal/analyzer/analyzer.go lines 786-801. Apply the same runtime-edge
condition in FindAffectedFiles at internal/analyzer/analyzer.go lines 1644-1654;
type-only re-exports must not taint barrel symbols or propagate sideEffectTaint.

Comment on lines +243 to +247
// Use "*" wildcard to mark all exports as affected, plus the
// sideEffectTaint sentinel so the *import-time* nature propagates
// through import/re-export edges (a barrel importing this becomes
// side-effectful too).
affected = append(affected, "*", sideEffectTaint)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Detect import-time side effects even when symbols also changed.

The enclosing fallback runs only when len(affected) == 0. If one diff changes an exported symbol and a top-level side-effect statement, this block does not run. The result omits sideEffectTaint, so importer and re-export propagation stops for that side-effect change.

Evaluate hasSideEffectStmtChanges and bareImportsChanged independently of symbol-level changes. Append "*" and sideEffectTaint whenever either check is true.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/analyzer/astdiff.go` around lines 243 - 247, Update the
affected-symbol logic around hasSideEffectStmtChanges and bareImportsChanged so
these checks run independently of len(affected) and symbol-level changes.
Whenever either side-effect condition is true, append both "*" and
sideEffectTaint, while preserving existing handling for ordinary symbol changes.

@martinnaj
martinnaj merged commit f4f4c14 into master Aug 10, 2026
7 checks passed
@martinnaj
martinnaj deleted the mnaj-side-effect-stopping-at-first-importer branch August 10, 2026 10:27
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.

2 participants