chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
package languages
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/zzet/gortex/internal/graph"
|
||||
)
|
||||
|
||||
// extractFor runs the extractor matching the fixture file's suffix.
|
||||
func extractFor(t *testing.T, path, src string) *parserExtractResult {
|
||||
t.Helper()
|
||||
var (
|
||||
nodes []*graph.Node
|
||||
edges []*graph.Edge
|
||||
)
|
||||
if strings.HasSuffix(path, ".ts") || strings.HasSuffix(path, ".tsx") {
|
||||
res, err := NewTypeScriptExtractor().Extract(path, []byte(src))
|
||||
if err != nil {
|
||||
t.Fatalf("ts extract %s: %v", path, err)
|
||||
}
|
||||
nodes, edges = res.Nodes, res.Edges
|
||||
} else {
|
||||
res, err := NewJavaScriptExtractor().Extract(path, []byte(src))
|
||||
if err != nil {
|
||||
t.Fatalf("js extract %s: %v", path, err)
|
||||
}
|
||||
nodes, edges = res.Nodes, res.Edges
|
||||
}
|
||||
return &parserExtractResult{nodes: nodes, edges: edges}
|
||||
}
|
||||
|
||||
type parserExtractResult struct {
|
||||
nodes []*graph.Node
|
||||
edges []*graph.Edge
|
||||
}
|
||||
|
||||
// funcNode returns the function/method node with the given Name, or nil.
|
||||
func (r *parserExtractResult) funcNode(name string) *graph.Node {
|
||||
for _, n := range r.nodes {
|
||||
if (n.Kind == graph.KindFunction || n.Kind == graph.KindMethod) && n.Name == name {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// callTargetsFrom returns the To-ends of every EdgeCalls leaving from.
|
||||
func (r *parserExtractResult) callTargetsFrom(from string) []string {
|
||||
var out []string
|
||||
for _, e := range r.edges {
|
||||
if e.Kind == graph.EdgeCalls && e.From == from {
|
||||
out = append(out, e.To)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func sliceHas(ss []string, want string) bool {
|
||||
for _, s := range ss {
|
||||
if s == want {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TestObjectLiteralMethodShorthand_Extraction pins the fix for object-
|
||||
// literal method shorthand — `export const api = { process() {...} }`.
|
||||
// tree-sitter parses `process()` as a method_definition whose container
|
||||
// is an `object`, not a class; the old extractor's class-only walk
|
||||
// dropped it, so the shorthand method had no graph node at all and a
|
||||
// call `api.process()` could not resolve to it (false negative) — or
|
||||
// worse, fell through to an unrelated free `process` (false positive).
|
||||
//
|
||||
// Both the TS and JS extractors are exercised: the pattern is valid in
|
||||
// both languages and both used to drop the node.
|
||||
func TestObjectLiteralMethodShorthand_Extraction(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
path string
|
||||
src string
|
||||
}{
|
||||
{
|
||||
name: "typescript shorthand method",
|
||||
path: "svc/api.ts",
|
||||
src: `function doStuff(): number { return 1; }
|
||||
function freeProcess(): number { return 2; }
|
||||
|
||||
export const api = {
|
||||
process(): number {
|
||||
return doStuff();
|
||||
},
|
||||
};
|
||||
|
||||
function caller(): void {
|
||||
api.process();
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "javascript shorthand method",
|
||||
path: "svc/api.js",
|
||||
src: `function doStuff() { return 1; }
|
||||
function freeProcess() { return 2; }
|
||||
|
||||
export const api = {
|
||||
process() {
|
||||
return doStuff();
|
||||
},
|
||||
};
|
||||
|
||||
function caller() {
|
||||
api.process();
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "typescript arrow-field member",
|
||||
path: "svc/arrowapi.ts",
|
||||
src: `function doStuff(): number { return 1; }
|
||||
|
||||
export const api = {
|
||||
process: (): number => {
|
||||
return doStuff();
|
||||
},
|
||||
};
|
||||
|
||||
function caller(): void {
|
||||
api.process();
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "javascript arrow-field member",
|
||||
path: "svc/arrowapi.js",
|
||||
src: `function doStuff() { return 1; }
|
||||
|
||||
export const api = {
|
||||
process: () => {
|
||||
return doStuff();
|
||||
},
|
||||
};
|
||||
|
||||
function caller() {
|
||||
api.process();
|
||||
}
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
res := extractFor(t, tc.path, tc.src)
|
||||
|
||||
// True positive (false-negative fix): the object-literal
|
||||
// member must exist as a function node named `api.process`.
|
||||
member := res.funcNode("api.process")
|
||||
if member == nil {
|
||||
t.Fatalf("object-literal member node %q was not emitted", "api.process")
|
||||
}
|
||||
|
||||
// The call inside caller() must bind directly to the member
|
||||
// node — already resolved at extraction, never an
|
||||
// `unresolved::` placeholder.
|
||||
callerID := tc.path + "::caller"
|
||||
targets := res.callTargetsFrom(callerID)
|
||||
if !sliceHas(targets, member.ID) {
|
||||
t.Errorf("api.process() did not bind to the member node %q; call targets = %v", member.ID, targets)
|
||||
}
|
||||
|
||||
// False-positive guard: the call must NOT resolve by name to
|
||||
// the unrelated free `process` placeholder, and the member
|
||||
// node's name must be owner-qualified (not a bare `process`
|
||||
// that would collide with a free function of that name).
|
||||
if sliceHas(targets, "unresolved::*.process") {
|
||||
t.Errorf("api.process() left a bare member placeholder — would mis-resolve to a free `process`")
|
||||
}
|
||||
if res.funcNode("process") != nil {
|
||||
t.Errorf("object-literal member must be owner-qualified, not a bare `process` node")
|
||||
}
|
||||
|
||||
// Calls inside the member body must be attributed to the
|
||||
// member node, not silently dropped for want of an enclosing
|
||||
// function.
|
||||
memberCalls := res.callTargetsFrom(member.ID)
|
||||
if !sliceHas(memberCalls, "unresolved::doStuff") {
|
||||
t.Errorf("call to doStuff() inside the member body was dropped; member calls = %v", memberCalls)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestObjectLiteralMethod_InlineObjectNotQualified guards the negative
|
||||
// case: a method shorthand inside an inline / anonymous object (no
|
||||
// owning `const` binding) still gets a node, but no owner-qualified
|
||||
// member registration — there is no owner name a call could use.
|
||||
func TestObjectLiteralMethod_InlineObjectNotQualified(t *testing.T) {
|
||||
src := `function use(o: { run(): number }): number { return o.run(); }
|
||||
|
||||
use({
|
||||
run(): number {
|
||||
return 1;
|
||||
},
|
||||
});
|
||||
`
|
||||
res := extractFor(t, "svc/inline.ts", src)
|
||||
// The inline shorthand still produces a function node (named by the
|
||||
// bare member, since there is no owner) so its body calls attribute.
|
||||
if res.funcNode("run") == nil {
|
||||
t.Errorf("inline object-literal method should still get a function node")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user