diff --git a/CHANGELOG.md b/CHANGELOG.md index 72a730c..50279f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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.24.10] - 2026-07-30 + +### Fixed +- Symbol-level AST diff no longer marks an unchanged declaration as changed when a *neighboring* declaration is edited or removed. Each symbol's body text was extracted starting at `stmt.Pos()`, which in the TS AST includes the symbol's **leading trivia** (the preceding comments and blank lines). Deleting a sibling — or otherwise changing what comes before a symbol — re-attaches the intervening comment (e.g. a `// SECTION` header) to the next symbol, so its extracted body differed between the old and new versions and it was reported as a runtime change. That false "change" then spread through the intra-file reference graph and out via the library's exports, over-tainting downstream consumers. Symbol start lines are now taken from the first real token (`scanner.SkipTrivia`), excluding leading comments/blank lines from the compared body. Example: removing three unused `createSelector` exports from `bootstrap_selector.ts` previously tainted nine untouched sibling selectors (and everything reachable from them); it now taints none. + ## [0.24.9] - 2026-07-30 ### Changed @@ -367,6 +372,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Multi-stage Docker build - Automated vendor upgrade workflow +[0.24.10]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.24.9...v0.24.10 [0.24.9]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.24.8...v0.24.9 [0.24.8]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.24.7...v0.24.8 [0.24.7]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.24.6...v0.24.7 diff --git a/VERSION b/VERSION index fe1c569..fd4d2a3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.24.9 \ No newline at end of file +0.24.10 \ No newline at end of file diff --git a/internal/tsparse/tsparse.go b/internal/tsparse/tsparse.go index 0a286e3..4e5740e 100644 --- a/internal/tsparse/tsparse.go +++ b/internal/tsparse/tsparse.go @@ -8,6 +8,7 @@ import ( "goodchanges/tsgo-vendor/pkg/ast" "goodchanges/tsgo-vendor/pkg/core" "goodchanges/tsgo-vendor/pkg/parser" + "goodchanges/tsgo-vendor/pkg/scanner" ) type Import struct { @@ -266,9 +267,20 @@ func extractExports(stmt *ast.Node, analysis *FileAnalysis) { } } +// stmtStartLine returns the 1-based line of a statement's first real token, +// skipping leading trivia (comments and blank lines). Using stmt.Pos() directly +// would put StartLine at the start of the leading comment/blank-line block, so +// that block rides along in the symbol's extracted body text. Deleting or editing +// a *preceding* sibling then re-attaches its trailing comment to this symbol and +// spuriously marks this (unchanged) symbol as changed in the AST diff. +func stmtStartLine(stmt *ast.Node, text string, lineMap []core.TextPos) int { + return posToLine(scanner.SkipTrivia(text, stmt.Pos()), lineMap) +} + func extractDeclarations(stmt *ast.Node, lineMap []core.TextPos, analysis *FileAnalysis) { isExported := ast.HasSyntacticModifier(stmt, ast.ModifierFlagsExport) isDefault := ast.HasSyntacticModifier(stmt, ast.ModifierFlagsDefault) + text := analysis.SourceFile.Text() switch stmt.Kind { case ast.KindFunctionDeclaration: @@ -284,7 +296,7 @@ func extractDeclarations(stmt *ast.Node, lineMap []core.TextPos, analysis *FileA analysis.Symbols = append(analysis.Symbols, SymbolDecl{ Name: name, Kind: "function", - StartLine: posToLine(stmt.Pos(), lineMap), + StartLine: stmtStartLine(stmt, text, lineMap), EndLine: posToLine(stmt.End(), lineMap), IsExported: isExported, ExportName: exportName, @@ -303,7 +315,7 @@ func extractDeclarations(stmt *ast.Node, lineMap []core.TextPos, analysis *FileA analysis.Symbols = append(analysis.Symbols, SymbolDecl{ Name: name, Kind: "class", - StartLine: posToLine(stmt.Pos(), lineMap), + StartLine: stmtStartLine(stmt, text, lineMap), EndLine: posToLine(stmt.End(), lineMap), IsExported: isExported, ExportName: exportName, @@ -315,7 +327,7 @@ func extractDeclarations(stmt *ast.Node, lineMap []core.TextPos, analysis *FileA analysis.Symbols = append(analysis.Symbols, SymbolDecl{ Name: name, Kind: "interface", - StartLine: posToLine(stmt.Pos(), lineMap), + StartLine: stmtStartLine(stmt, text, lineMap), EndLine: posToLine(stmt.End(), lineMap), IsExported: isExported, ExportName: name, @@ -328,7 +340,7 @@ func extractDeclarations(stmt *ast.Node, lineMap []core.TextPos, analysis *FileA analysis.Symbols = append(analysis.Symbols, SymbolDecl{ Name: name, Kind: "type", - StartLine: posToLine(stmt.Pos(), lineMap), + StartLine: stmtStartLine(stmt, text, lineMap), EndLine: posToLine(stmt.End(), lineMap), IsExported: isExported, ExportName: name, @@ -341,7 +353,7 @@ func extractDeclarations(stmt *ast.Node, lineMap []core.TextPos, analysis *FileA analysis.Symbols = append(analysis.Symbols, SymbolDecl{ Name: name, Kind: "enum", - StartLine: posToLine(stmt.Pos(), lineMap), + StartLine: stmtStartLine(stmt, text, lineMap), EndLine: posToLine(stmt.End(), lineMap), IsExported: isExported, ExportName: name, @@ -358,7 +370,7 @@ func extractDeclarations(stmt *ast.Node, lineMap []core.TextPos, analysis *FileA analysis.Symbols = append(analysis.Symbols, SymbolDecl{ Name: name, Kind: "variable", - StartLine: posToLine(stmt.Pos(), lineMap), + StartLine: stmtStartLine(stmt, text, lineMap), EndLine: posToLine(stmt.End(), lineMap), IsExported: isExported, ExportName: name,