Skip to content

Commit 66601b7

Browse files
samchonclaude
andcommitted
test(paths): pin declaration-order tie-breaking between equal-prefix patterns
Third self-review pass, verified against the pinned typescript-go source: module.MatchPatternOrExact consults the exact-string set before any wildcard, and core.FindBestPatternMatch displaces the best match only on a strictly greater prefix length — so between wildcards with identical literal prefixes the first declared pattern wins. orderPatterns already reproduces this via SliceStable; this locks the observable contract so an unstable sort or a >= comparison cannot silently diverge from the type checker on order-sensitive configs. Refs #310. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AivFfPoUxoU26Dko5Jbm21
1 parent 98d229d commit 66601b7

1 file changed

Lines changed: 41 additions & 0 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package paths_test
2+
3+
import "testing"
4+
5+
// TestRewriterOrderPatternsKeepsDeclarationOrderOnPrefixTies verifies tie-breaking matches tsc's scan.
6+
//
7+
// Locks the SliceStable choice in `paths.go::orderPatterns`. tsc's
8+
// FindBestPatternMatch takes a strictly-greater prefix to displace the
9+
// current best, so between wildcards with equal literal prefixes the first
10+
// declared pattern wins. An unstable sort (or a >= comparison) would resolve
11+
// such specifiers through whichever pattern happened to land first,
12+
// disagreeing with the type checker on order-sensitive configs.
13+
//
14+
// 1. Declare two wildcard patterns with identical literal prefixes, both matching one specifier.
15+
// 2. Resolve it under both declaration orders.
16+
// 3. Assert the first-declared pattern wins each time.
17+
func TestRewriterOrderPatternsKeepsDeclarationOrderOnPrefixTies(t *testing.T) {
18+
root := "/repo"
19+
sources := map[string]string{
20+
root + "/src/tie/x/z.ts": root + "/src/tie/x/z.ts",
21+
root + "/src/tie/all/zx.ts": root + "/src/tie/all/zx.ts",
22+
}
23+
suffixed := pathsPathPattern{pattern: "@a/*x", targets: []string{"src/tie/x/*"}}
24+
open := pathsPathPattern{pattern: "@a/*", targets: []string{"src/tie/all/*"}}
25+
26+
for _, c := range []struct {
27+
name string
28+
patterns []pathsPathPattern
29+
expected string
30+
}{
31+
{"suffixed declared first", []pathsPathPattern{suffixed, open}, root + "/src/tie/x/z.ts"},
32+
{"open declared first", []pathsPathPattern{open, suffixed}, root + "/src/tie/all/zx.ts"},
33+
} {
34+
patterns := append([]pathsPathPattern(nil), c.patterns...)
35+
pathsOrderPatterns(patterns)
36+
rewriter := &pathsRewriter{basePath: root, patterns: patterns, sourceFiles: sources}
37+
if source, ok := pathsResolveSource(rewriter, "@a/zx"); !ok || source != c.expected {
38+
t.Fatalf("%s: tie resolution mismatch: source=%q ok=%v expected=%q", c.name, source, ok, c.expected)
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)