Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

180 lines
4.7 KiB
Go

package languages
import (
"testing"
"github.com/zzet/gortex/internal/graph"
)
// Helper: return all EdgeThrows targets in the result, deduped.
func tsThrowsTargets(edges []*graph.Edge) []string {
seen := map[string]bool{}
var out []string
for _, e := range edges {
if e.Kind != graph.EdgeThrows {
continue
}
if seen[e.To] {
continue
}
seen[e.To] = true
out = append(out, e.To)
}
return out
}
func TestTSThrows_NewExpression(t *testing.T) {
src := `function validate(x: number): void {
if (x < 0) {
throw new RangeError("negative");
}
}
`
_, edges := runTSExtract(t, "src/v.ts", src)
targets := tsThrowsTargets(edges)
want := []string{"unresolved::RangeError"}
if len(targets) != 1 || targets[0] != want[0] {
t.Errorf("got %v, want %v", targets, want)
}
// Origin should be ASTInferred (TS doesn't enforce checked exceptions).
for _, e := range edges {
if e.Kind == graph.EdgeThrows && e.Origin != graph.OriginASTInferred {
t.Errorf("EdgeThrows origin = %q, want ast_inferred", e.Origin)
}
}
}
func TestTSThrows_BareIdentifier(t *testing.T) {
src := `function rethrow(err: Error): never {
throw err;
}
`
_, edges := runTSExtract(t, "src/r.ts", src)
targets := tsThrowsTargets(edges)
if len(targets) != 1 || targets[0] != "unresolved::err" {
t.Errorf("got %v, want [unresolved::err]", targets)
}
}
func TestTSThrows_NamespacedConstructor(t *testing.T) {
src := `function load(): void {
throw new errors.NotFoundError("missing");
}
`
_, edges := runTSExtract(t, "src/n.ts", src)
targets := tsThrowsTargets(edges)
if len(targets) != 1 || targets[0] != "unresolved::NotFoundError" {
t.Errorf("got %v, want [unresolved::NotFoundError]", targets)
}
}
func TestTSThrows_MemberExpression(t *testing.T) {
src := `function fail(kind: string): void {
throw errors.kinds.MyError;
}
`
_, edges := runTSExtract(t, "src/m.ts", src)
targets := tsThrowsTargets(edges)
if len(targets) != 1 || targets[0] != "unresolved::MyError" {
t.Errorf("got %v, want [unresolved::MyError]", targets)
}
}
func TestTSThrows_StringLiteralSkipped(t *testing.T) {
src := `function bad(): void {
throw "oops";
throw 42;
}
`
_, edges := runTSExtract(t, "src/s.ts", src)
targets := tsThrowsTargets(edges)
if len(targets) != 0 {
t.Errorf("string/numeric throws should NOT emit edges; got %v", targets)
}
}
func TestTSThrows_DedupesPerFunction(t *testing.T) {
src := `function many(): void {
if (1) throw new MyError("a");
if (2) throw new MyError("b");
if (3) throw new MyError("c");
}
`
_, edges := runTSExtract(t, "src/d.ts", src)
targets := tsThrowsTargets(edges)
if len(targets) != 1 {
t.Errorf("expected one deduped target, got %d (%v)", len(targets), targets)
}
}
func TestTSThrows_MultipleDistinctTypes(t *testing.T) {
src := `function many(): void {
throw new TypeError("a");
throw new RangeError("b");
throw new MyCustomError("c");
}
`
_, edges := runTSExtract(t, "src/multi.ts", src)
targets := tsThrowsTargets(edges)
if len(targets) != 3 {
t.Errorf("expected 3 distinct targets, got %d (%v)", len(targets), targets)
}
}
// Nested function bodies should NOT contribute their throws to the
// outer function — they're emitted against the nested function's own
// ID via its own emitFunction call.
func TestTSThrows_SkipsNestedFunctionScope(t *testing.T) {
src := `function outer(): void {
throw new OuterError("a");
function inner(): void {
throw new InnerError("b");
}
}
`
_, edges := runTSExtract(t, "src/nest.ts", src)
// outer should see only OuterError; inner should see only InnerError.
outerEdges := []string{}
innerEdges := []string{}
for _, e := range edges {
if e.Kind != graph.EdgeThrows {
continue
}
switch e.From {
case "src/nest.ts::outer":
outerEdges = append(outerEdges, e.To)
case "src/nest.ts::inner":
innerEdges = append(innerEdges, e.To)
}
}
if len(outerEdges) != 1 || outerEdges[0] != "unresolved::OuterError" {
t.Errorf("outer throws = %v, want [unresolved::OuterError]", outerEdges)
}
if len(innerEdges) != 1 || innerEdges[0] != "unresolved::InnerError" {
t.Errorf("inner throws = %v, want [unresolved::InnerError]", innerEdges)
}
}
// Arrow-function bodies inside a method don't leak their throws to
// the enclosing method.
func TestTSThrows_SkipsArrowFunctionScope(t *testing.T) {
src := `class S {
handle(): void {
throw new HandleError("a");
const cb = (): void => { throw new CbError("b"); };
cb();
}
}
`
_, edges := runTSExtract(t, "src/cls.ts", src)
for _, e := range edges {
if e.Kind != graph.EdgeThrows {
continue
}
// The method's throws should NOT include CbError.
if e.From == "src/cls.ts::S.handle" && e.To == "unresolved::CbError" {
t.Errorf("method.handle should NOT see arrow's CbError")
}
}
}