Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.19.2] - 2026-04-24

### Fixed
- Bare dynamic `import("pkg")` calls (e.g. `() => import("pkg")` passed to a loader, or `const mod = await import("pkg")` used opaquely without property access) are now recorded as side-effect imports, so taint on the target package propagates to the importing file. Previously only the three pattern forms (var+property-access, destructure, `.then` callback) produced an `Import` record, leaving bare calls invisible to taint propagation.

## [0.19.1] - 2026-04-23

### Fixed
Expand Down Expand Up @@ -267,6 +272,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Multi-stage Docker build
- Automated vendor upgrade workflow

[0.19.2]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.19.1...v0.19.2
[0.19.1]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.19.0...v0.19.1
[0.19.0]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.18.1...v0.19.0
[0.18.1]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.18.0...v0.18.1
[0.18.0]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.17.1...v0.18.0
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.19.1
0.19.2
31 changes: 31 additions & 0 deletions internal/tsparse/tsparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,13 +387,21 @@ func extractDynamicImports(sf *ast.SourceFile, analysis *FileAnalysis) {
// Phase 1: collect dynamic imports assigned to variables or destructured
// varName → specifier (for pattern 1)
varImports := make(map[string]string)
// Every dynamic import() specifier seen, regardless of surrounding context.
// Used to emit side-effect imports for calls no other pattern captures
// (e.g. `() => import("pkg")` passed to a loader).
allSpecifiers := make(map[string]bool)

var walkPhase1 func(n *ast.Node)
walkPhase1 = func(n *ast.Node) {
if n == nil {
return
}

if spec := extractDynamicImportSpecifier(n); spec != "" {
allSpecifiers[spec] = true
}

if n.Kind == ast.KindVariableDeclaration {
vd := n.AsVariableDeclaration()
name := vd.Name()
Expand Down Expand Up @@ -438,6 +446,7 @@ func extractDynamicImports(sf *ast.SourceFile, analysis *FileAnalysis) {
}

if len(varImports) == 0 {
emitSideEffectDynamicImports(analysis, allSpecifiers)
return
}

Expand Down Expand Up @@ -488,6 +497,28 @@ func extractDynamicImports(sf *ast.SourceFile, analysis *FileAnalysis) {
Source: specifier,
})
}

emitSideEffectDynamicImports(analysis, allSpecifiers)
}

// emitSideEffectDynamicImports adds a side-effect Import entry (empty Names) for
// every dynamic import specifier that none of the pattern-based phases captured.
// Covers bare calls like `() => import("pkg")` and `const mod = await import("pkg")`
// where `mod` is used opaquely (no property access). Downstream taint treats these
// as full-taint imports, matching how static `import "pkg"` is handled.
func emitSideEffectDynamicImports(analysis *FileAnalysis, allSpecifiers map[string]bool) {
if len(allSpecifiers) == 0 {
return
}
covered := make(map[string]bool)
for _, imp := range analysis.Imports {
covered[imp.Source] = true
}
for spec := range allSpecifiers {
if !covered[spec] {
analysis.Imports = append(analysis.Imports, Import{Source: spec})
}
}
}

// extractDynamicImportSpecifier checks if an expression is (or contains)
Expand Down
Loading