ttsc 0.15.4 → 0.15.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +12 -10
- package/shim/ast/enums_gen.go +171 -0
- package/shim/checker/enums_gen.go +112 -0
- package/shim/checker/shim.go +116 -0
- package/shim/checker/signature_api_completeness.go +58 -0
- package/shim/core/enums_gen.go +15 -0
- package/tools/shim_audit/baseline.json +705 -0
- package/tools/shim_audit/go.mod +10 -0
- package/tools/shim_audit/go.sum +8 -0
- package/tools/shim_audit/main.go +995 -0
- package/tools/shim_audit/main_test.go +181 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"go/token"
|
|
5
|
+
"go/types"
|
|
6
|
+
"reflect"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// TestLinknameRe pins the two //go:linkname target shapes the reachability scan
|
|
11
|
+
// must recognize — a package-level symbol and a (pointer-)receiver method — plus
|
|
12
|
+
// slashed (nested) package suffixes, and rejects non-typescript-go targets.
|
|
13
|
+
func TestLinknameRe(t *testing.T) {
|
|
14
|
+
cases := []struct {
|
|
15
|
+
line string
|
|
16
|
+
wantPkg string
|
|
17
|
+
wantName string
|
|
18
|
+
wantHit bool
|
|
19
|
+
}{
|
|
20
|
+
{"//go:linkname checkerGetMinArgumentCount github.com/microsoft/typescript-go/internal/checker.(*Checker).getMinArgumentCount", "checker", "getMinArgumentCount", true},
|
|
21
|
+
{"//go:linkname x github.com/microsoft/typescript-go/internal/checker.(Checker).foo", "checker", "foo", true},
|
|
22
|
+
{"//go:linkname GetSourceFileOfNode github.com/microsoft/typescript-go/internal/ast.getSourceFileOfNode", "ast", "getSourceFileOfNode", true},
|
|
23
|
+
{"//go:linkname From github.com/microsoft/typescript-go/internal/vfs/cachedvfs.From", "vfs/cachedvfs", "From", true},
|
|
24
|
+
{"//go:linkname FS github.com/microsoft/typescript-go/internal/vfs/osvfs.FS", "vfs/osvfs", "FS", true},
|
|
25
|
+
{"//go:linkname foo runtime.bar", "", "", false}, // not a tsgo internal target
|
|
26
|
+
{"// just a comment", "", "", false},
|
|
27
|
+
}
|
|
28
|
+
for _, c := range cases {
|
|
29
|
+
m := linknameRe.FindStringSubmatch(c.line)
|
|
30
|
+
if !c.wantHit {
|
|
31
|
+
if m != nil {
|
|
32
|
+
t.Errorf("expected no match for %q, got %v", c.line, m)
|
|
33
|
+
}
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
if m == nil {
|
|
37
|
+
t.Errorf("expected match for %q, got none", c.line)
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
if m[1] != c.wantPkg || m[2] != c.wantName {
|
|
41
|
+
t.Errorf("for %q: got pkg=%q name=%q, want pkg=%q name=%q", c.line, m[1], m[2], c.wantPkg, c.wantName)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// TestDedupe locks the determinism guarantee: one entry per kind|pkg|symbol,
|
|
47
|
+
// keeping the lexicographically smallest detail, ordered by pkg, kind, symbol.
|
|
48
|
+
func TestDedupe(t *testing.T) {
|
|
49
|
+
in := []finding{
|
|
50
|
+
{"FUNC", "checker", "B", "detail-z"},
|
|
51
|
+
{"FUNC", "checker", "B", "detail-a"}, // duplicate key, smaller detail wins
|
|
52
|
+
{"FUNC", "ast", "A", "d2"},
|
|
53
|
+
{"ENUM", "ast", "A", "d1"},
|
|
54
|
+
}
|
|
55
|
+
got := dedupe(in)
|
|
56
|
+
want := []finding{
|
|
57
|
+
{"ENUM", "ast", "A", "d1"},
|
|
58
|
+
{"FUNC", "ast", "A", "d2"},
|
|
59
|
+
{"FUNC", "checker", "B", "detail-a"},
|
|
60
|
+
}
|
|
61
|
+
if !reflect.DeepEqual(got, want) {
|
|
62
|
+
t.Errorf("dedupe mismatch:\n got %+v\nwant %+v", got, want)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// TestTierOf pins the kind→tier mapping the gate and report depend on.
|
|
67
|
+
func TestTierOf(t *testing.T) {
|
|
68
|
+
cases := map[string]int{"ENUM": 1, "FUNC": 2, "ESCAPE": 3, "ENUM?": 4, "UNEXPORTED": 4}
|
|
69
|
+
for kind, want := range cases {
|
|
70
|
+
if got := tierOf(kind); got != want {
|
|
71
|
+
t.Errorf("tierOf(%q) = %d, want %d", kind, got, want)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// TestCommonPrefix covers the helper feeding the abbreviation fallback.
|
|
77
|
+
func TestCommonPrefix(t *testing.T) {
|
|
78
|
+
cases := []struct {
|
|
79
|
+
in []string
|
|
80
|
+
want string
|
|
81
|
+
}{
|
|
82
|
+
{[]string{"OEKParentheses", "OEKTypeAssertions", "OEKSatisfies"}, "OEK"},
|
|
83
|
+
{[]string{"TypeFlagsAny", "TypeFlagsObject"}, "TypeFlags"},
|
|
84
|
+
{[]string{"Foo"}, "Foo"},
|
|
85
|
+
{[]string{"abc", "xyz"}, ""},
|
|
86
|
+
{nil, ""},
|
|
87
|
+
}
|
|
88
|
+
for _, c := range cases {
|
|
89
|
+
if got := commonPrefix(c.in); got != c.want {
|
|
90
|
+
t.Errorf("commonPrefix(%v) = %q, want %q", c.in, got, c.want)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// TestAttachUntypedConsts is the crux: it pins both the type-name attribution
|
|
96
|
+
// (longest wins) and the abbreviation fallback that closes the untyped+
|
|
97
|
+
// unprefixed blind spot (OuterExpressionKinds' OEKExcludeJSDocTypeAssertion),
|
|
98
|
+
// plus the negative twins that must NOT over-attach.
|
|
99
|
+
func TestAttachUntypedConsts(t *testing.T) {
|
|
100
|
+
t.Run("abbreviation fallback (OEK blind spot)", func(t *testing.T) {
|
|
101
|
+
enums := []string{"OuterExpressionKinds", "Kind"}
|
|
102
|
+
typed := map[string][]string{
|
|
103
|
+
"OuterExpressionKinds": {"OEKParentheses", "OEKTypeAssertions", "OEKNonNullAssertions", "OEKSatisfies"},
|
|
104
|
+
"Kind": {"KindCallExpression", "KindIdentifier"},
|
|
105
|
+
}
|
|
106
|
+
untyped := []string{"OEKExcludeJSDocTypeAssertion", "KindFoo", "Unrelated"}
|
|
107
|
+
got := attachUntypedConsts(enums, typed, untyped)
|
|
108
|
+
want := map[string][]string{
|
|
109
|
+
"OuterExpressionKinds": {"OEKExcludeJSDocTypeAssertion"},
|
|
110
|
+
"Kind": {"KindFoo"},
|
|
111
|
+
}
|
|
112
|
+
if !reflect.DeepEqual(got, want) {
|
|
113
|
+
t.Errorf("got %v, want %v", got, want)
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
t.Run("longest type-name prefix wins", func(t *testing.T) {
|
|
118
|
+
enums := []string{"Foo", "FooBar"}
|
|
119
|
+
got := attachUntypedConsts(enums, nil, []string{"FooBarBaz", "FooQux"})
|
|
120
|
+
want := map[string][]string{"FooBar": {"FooBarBaz"}, "Foo": {"FooQux"}}
|
|
121
|
+
if !reflect.DeepEqual(got, want) {
|
|
122
|
+
t.Errorf("got %v, want %v", got, want)
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
t.Run("no abbreviation candidate when members are type-name-prefixed", func(t *testing.T) {
|
|
127
|
+
enums := []string{"NodeFlags"}
|
|
128
|
+
typed := map[string][]string{"NodeFlags": {"NodeFlagsNone", "NodeFlagsConst"}}
|
|
129
|
+
// "NodeFlagsExtra" attaches via the type name; "NFXfoo" must NOT attach
|
|
130
|
+
// (no spurious 2/3-char "NF"/"Nod" abbreviation candidate is created).
|
|
131
|
+
got := attachUntypedConsts(enums, typed, []string{"NodeFlagsExtra", "NFXfoo"})
|
|
132
|
+
want := map[string][]string{"NodeFlags": {"NodeFlagsExtra"}}
|
|
133
|
+
if !reflect.DeepEqual(got, want) {
|
|
134
|
+
t.Errorf("got %v, want %v", got, want)
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
t.Run("lowercase boundary is not a member", func(t *testing.T) {
|
|
139
|
+
got := attachUntypedConsts([]string{"Modifier"}, nil, []string{"Modifierabc"})
|
|
140
|
+
if len(got) != 0 {
|
|
141
|
+
t.Errorf("expected no attachment, got %v", got)
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
t.Run("short shared prefix (<3) is not used as an abbreviation", func(t *testing.T) {
|
|
146
|
+
// Tristate members share only "TS" (2 chars); an unrelated "TSConfig" const
|
|
147
|
+
// must not be pulled in by an over-broad abbreviation.
|
|
148
|
+
enums := []string{"Tristate"}
|
|
149
|
+
typed := map[string][]string{"Tristate": {"TSUnknown", "TSFalse", "TSTrue"}}
|
|
150
|
+
got := attachUntypedConsts(enums, typed, []string{"TSConfig"})
|
|
151
|
+
if len(got) != 0 {
|
|
152
|
+
t.Errorf("expected no attachment, got %v", got)
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// TestIsReachable covers the structural recursion plus the decisive case: an
|
|
158
|
+
// internal named type is reachable only when the shim exposes it.
|
|
159
|
+
func TestIsReachable(t *testing.T) {
|
|
160
|
+
fresh := func() map[types.Type]bool { return map[types.Type]bool{} }
|
|
161
|
+
r := reachable{}
|
|
162
|
+
|
|
163
|
+
if !isReachable(types.Typ[types.Int], r, fresh()) {
|
|
164
|
+
t.Error("basic int should be reachable")
|
|
165
|
+
}
|
|
166
|
+
if !isReachable(types.NewPointer(types.NewSlice(types.Typ[types.String])), r, fresh()) {
|
|
167
|
+
t.Error("pointer-to-slice-of-basic should be reachable")
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
pkg := types.NewPackage("github.com/microsoft/typescript-go/internal/checker", "checker")
|
|
171
|
+
obj := types.NewTypeName(token.NoPos, pkg, "Foo", nil)
|
|
172
|
+
named := types.NewNamed(obj, types.NewStruct(nil, nil), nil)
|
|
173
|
+
if isReachable(named, reachable{}, fresh()) {
|
|
174
|
+
t.Error("internal checker.Foo must be UNreachable when the shim does not expose it")
|
|
175
|
+
}
|
|
176
|
+
exposed := reachable{}
|
|
177
|
+
exposed.add("checker", "Foo")
|
|
178
|
+
if !isReachable(named, exposed, fresh()) {
|
|
179
|
+
t.Error("internal checker.Foo must be reachable once the shim exposes it")
|
|
180
|
+
}
|
|
181
|
+
}
|