fix: prevent TypeScript import merge corruption (#24) - #113
Open
edouard-andrei wants to merge 2 commits into
Open
fix: prevent TypeScript import merge corruption (#24)#113edouard-andrei wants to merge 2 commits into
edouard-andrei wants to merge 2 commits into
Conversation
Weave's commutative import merge had two failure modes that silently
corrupted TypeScript imports (exit 0, no conflict markers):
1. Same-source single-line edits were duplicated instead of merged.
base `import { a }`, ours `import { a, b }`, theirs `import { a, c }`
produced two separate `import ... from "mod"` lines instead of
`import { a, b, c } from "mod"`.
2. Multi-line import blocks leaked. `extract_non_imports` stripped import
continuation lines with a line heuristic that only matched specifier
lines ending in a comma, so a specifier without a trailing comma (and
its closing `} from "..."`) leaked into the non-import diffy merge.
With several multi-line imports from different sources this produced
orphaned specifier lists with no `import {` opener and spurious
conflicts -- the symptom reported in Ataraxy-Labs#24.
Fixes:
- Merge same-source named specifiers into a single import line,
source- and shape-aware: only when the import prefix/suffix match,
otherwise fall back to the existing line-set insertion.
- Replace the heuristic in `extract_non_imports` with
`parse_import_statements`, so import vs non-import classification is
structural and consistent with how the import block is reconstructed.
Adds regression tests for both modes, including multi-source multi-line
imports without trailing commas.
There was a problem hiding this comment.
inspect review
Triage: 8 entities analyzed | 0 critical, 0 high, 3 medium, 5 low
Verdict: standard_review
Findings (5)
- [low] In
merge_same_source_named_import_line, the function returnsNonewhenfinal_specs.is_empty(), but this can cause silent data loss. If both sides remove all specifiers from an import, the function returnsNoneand the caller will addtheirsas a new line instead of recognizing the import should be removed entirely. This leads to duplicate or incorrect import statements. - [low] In
merge_imports_commutatively, whenmerge_same_source_named_import_linereturnsSome(merged), the code updatesgroups[best_group][pos]and continues without checking if the merged line is actually different from the original. More critically, if the merge succeeds, it skips addingaddto the group, butadd(from theirs) might have additional changes beyond just specifiers that are now lost. - [low] In
named_import_parts, the function checksif open < from && close < fromto validate brace positions, but this logic is incorrect. For a valid named import likeimport { foo } from './bar',closeshould be LESS thanfrom, butopenshould also be less thanfrom. However, the condition allows cases where the closing brace comes before 'from' which is correct, but doesn't validate that open comes before close, potentially matching malformed imports. - [low] In
merge_same_source_named_import_line, the logic for detecting removed specifiers is flawed. It computestheirs_removedas specifiers in base but not in theirs, then filters ours to remove those. However, it doesn't check if ours also independently removed those same specifiers. If both sides remove the same specifier, it's correctly removed. But if ours keeps a specifier that theirs removed, the current code will remove it from the final result, which may not be the intended 3-way merge behavior (should potentially be a conflict). - [low] In
merge_same_source_named_import_line, the function callsparse_single_line_specifierson bothoursandtheirs, but then callsnamed_import_partswhich may fail if the import format doesn't match expectations. Ifnamed_import_partsreturnsNoneafter successfully parsing specifiers, the function returnsNoneand the merge silently fails, potentially causing import duplication.
Reviewed by inspect | Entity-level triage found 0 high-risk changes
Reject reversed brace order before extracting named import specifiers or import line parts, so malformed inputs cannot be treated as a mergeable named import shape. Constraint: PR Ataraxy-Labs#113 inspect review flagged weak brace validation in the same-source named import helper. Rejected: Changing specifier deletion-vs-keep semantics | that is a merge policy decision outside the safe Ataraxy-Labs#24 hardening path. Confidence: high Scope-risk: narrow Directive: Keep named import merging shape-conservative; malformed import syntax should fall back, not merge. Tested: cargo fmt --check; cargo test -p weave-core test_named_import_parsing_rejects_reversed_braces; cargo test -p weave-core; cargo clippy -p weave-core --tests Not-tested: Resolving/commenting on GitHub review thread
There was a problem hiding this comment.
inspect review
Triage: 11 entities analyzed | 0 critical, 0 high, 5 medium, 6 low
Verdict: standard_review
Findings (5)
- [low] In
merge_imports_commutatively, the code attempts to merge same-source imports by callingmerge_same_source_named_import_lineand updatinggroups[best_group][pos], but then continues to the next iteration. However, if the merge fails (returns None), the code falls through to add the import again, potentially creating duplicates. The logic should only skip adding when merge succeeds. - [low] In
named_import_parts, the function checksif open < close && close < fromto validate brace positions, but this logic is incorrect for TypeScript imports. Thefromkeyword comes AFTER the closing brace inimport { foo } from 'bar', soclose < fromis correct. However, the function findsfromusingtrimmed.find(" from ")which returns the position intrimmed, but then compares it withopenandclosewhich are also positions intrimmed. This is correct, but the condition should ensure all three are in the right order: open < close < from. - [low] In
merge_same_source_named_import_line, when constructing the final merged import, the code usesours_suffix.trim_start()in the else branch but doesn't trimours_prefixconsistently. Ifours_prefixhas trailing whitespace orours_suffixhas leading whitespace, the spacing in the output could be incorrect, potentially creating malformed imports likeimport { foo } } from 'bar'(double space before closing brace). - [low] In
parse_single_line_specifiers, the new checkif brace_start < brace_endprevents parsing when braces are reversed, but the function usesrfind('}')to find the closing brace. If there are multiple}characters in the line (e.g., in nested structures or comments),rfindwill find the last one, which may not be the matching closing brace for the first{. This could cause incorrect parsing or false negatives. - [low] In
named_import_parts, the function findscloseusingrfind('}')andfromusingfind(" from "), then checksif open < close && close < from. However, if there are multiple occurrences of}orfromin the string, this logic may incorrectly validate malformed imports. For example,import { foo } from 'bar' } from 'baz'would pass this check even though it's invalid.
Reviewed by inspect | Entity-level triage found 0 high-risk changes
Contributor
Author
Thanks — I checked these and I don't think another code change is warranted here.
The #24 regressions are covered by tests, and the branch is green with |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #24. Weave's commutative import merge had two failure modes that silently corrupted TypeScript imports (
exit 0, no conflict markers):Same-source single-line edits were duplicated instead of merged.
base: import { a } from "mod",ours: import { a, b },theirs: import { a, c }produced two separateimport … from "mod"lines instead ofimport { a, b, c } from "mod".Multi-line import blocks leaked.
extract_non_importsstripped import-continuation lines with a line heuristic that only matched specifier lines ending in a comma. A specifier without a trailing comma (and its closing} from "…") leaked into the non-import 3-way diff. With several multi-line imports from different sources this produced orphaned specifier lists with noimport {opener and spurious conflicts — the symptom @kurtextrem reported.Changes
extract_non_importswithparse_import_statements, so import vs non-import classification is structural and consistent with how the import block is reconstructed.Verification
Both failure modes were reproduced deterministically against
mainand confirmed fixed on this branch:main, clean merge heremain, 0 hereconstbetween imports) is preserved — not dropped, duplicated, or orphanedRegression tests added for both modes.
cargo fmt --check,cargo clippy -p weave-core --tests, andcargo test -p weave-coreall pass.cc @rs545837