mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
Merge branch 'main' into fix/1907-impact-file-path-binder
This commit is contained in:
commit
1244f0cb51
2 changed files with 64 additions and 13 deletions
|
|
@ -86,16 +86,33 @@ const NEW_QUALIFIED_CTOR_SPEC: PatternSpec<Record<string, never>> = {
|
|||
// proto loader). Matches either a bare call or an `obj.loadPackageDefinition(...)`
|
||||
// call. Plugin gates the qualified-constructor consumer on this —
|
||||
// structural check avoids materializing `tree.rootNode.text` for every file.
|
||||
const LOAD_PACKAGE_DEFINITION_SPEC: PatternSpec<Record<string, never>> = {
|
||||
meta: {},
|
||||
query: `
|
||||
(call_expression
|
||||
function: [
|
||||
(identifier) @fn (#eq? @fn "loadPackageDefinition")
|
||||
(member_expression property: (property_identifier) @fn (#eq? @fn "loadPackageDefinition"))
|
||||
])
|
||||
`,
|
||||
};
|
||||
//
|
||||
// These are TWO separate specs, NOT one `function: [ (identifier) ... (member_expression) ... ]`
|
||||
// alternation. Under the pinned tree-sitter@0.21.1 binding a top-level alternation
|
||||
// whose branches reuse the same capture name (`@fn`) collapses to one pattern with
|
||||
// a shared predicate bucket; the second branch's `@fn` is left unbound and its
|
||||
// `#eq?` is never enforced, so the member-expression branch would match EVERY
|
||||
// `obj.method(...)` call (e.g. `console.log(...)`) — turning this gate always-on
|
||||
// and emitting spurious qualified-constructor consumers. Two specs compile to two
|
||||
// queries with independent predicate buckets; `runCompiledPatterns` concatenates
|
||||
// their matches, so the `.length > 0` gate still means "either form is present".
|
||||
const LOAD_PACKAGE_DEFINITION_SPECS: PatternSpec<Record<string, never>>[] = [
|
||||
{
|
||||
meta: {},
|
||||
query: `
|
||||
(call_expression
|
||||
function: (identifier) @fn (#eq? @fn "loadPackageDefinition"))
|
||||
`,
|
||||
},
|
||||
{
|
||||
meta: {},
|
||||
query: `
|
||||
(call_expression
|
||||
function: (member_expression
|
||||
property: (property_identifier) @fn (#eq? @fn "loadPackageDefinition")))
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
interface NodeGrpcPatternBundle {
|
||||
grpcMethod: CompiledPatterns<Record<string, never>>;
|
||||
|
|
@ -107,11 +124,14 @@ interface NodeGrpcPatternBundle {
|
|||
}
|
||||
|
||||
function compileBundle(language: unknown, name: string): NodeGrpcPatternBundle {
|
||||
const mk = (spec: PatternSpec<Record<string, never>>, suffix: string) =>
|
||||
const mk = (
|
||||
spec: PatternSpec<Record<string, never>> | PatternSpec<Record<string, never>>[],
|
||||
suffix: string,
|
||||
) =>
|
||||
compilePatterns({
|
||||
name: `${name}-${suffix}`,
|
||||
language,
|
||||
patterns: [spec],
|
||||
patterns: Array.isArray(spec) ? spec : [spec],
|
||||
} satisfies LanguagePatterns<Record<string, never>>);
|
||||
return {
|
||||
grpcMethod: mk(GRPC_METHOD_SPEC, 'grpc-method'),
|
||||
|
|
@ -119,7 +139,7 @@ function compileBundle(language: unknown, name: string): NodeGrpcPatternBundle {
|
|||
getService: mk(GET_SERVICE_SPEC, 'get-service'),
|
||||
newSimpleCtor: mk(NEW_SIMPLE_CTOR_SPEC, 'new-simple-ctor'),
|
||||
newQualifiedCtor: mk(NEW_QUALIFIED_CTOR_SPEC, 'new-qualified-ctor'),
|
||||
loadPackageDefinition: mk(LOAD_PACKAGE_DEFINITION_SPEC, 'load-package-definition'),
|
||||
loadPackageDefinition: mk(LOAD_PACKAGE_DEFINITION_SPECS, 'load-package-definition'),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1135,6 +1135,37 @@ export const authClient = new authProto.auth.v1.AuthService(
|
|||
expect(consumers[0].contractId).toBe('grpc::auth.v1.AuthService/*');
|
||||
});
|
||||
|
||||
it('test_extract_ts_qualified_ctor_without_loadPackageDefinition_is_ignored', async () => {
|
||||
// Regression: an unrelated `obj.method(...)` member call must not trip the
|
||||
// loadPackageDefinition gate. With no loadPackageDefinition call present, a
|
||||
// qualified `new pkg...Service(...)` constructor must NOT become a consumer.
|
||||
// Pre-fix, the gate's shared-capture `function: [...]` alternation matched
|
||||
// every member call, so this spuriously emitted an AuthService consumer.
|
||||
writeFile(
|
||||
'proto/auth.proto',
|
||||
`syntax = "proto3";
|
||||
package auth.v1;
|
||||
service AuthService {
|
||||
rpc Login (LoginRequest) returns (LoginResponse);
|
||||
}`,
|
||||
);
|
||||
writeFile(
|
||||
'src/auth.client.ts',
|
||||
`import * as grpc from '@grpc/grpc-js';
|
||||
|
||||
logger.info('starting up');
|
||||
export const authClient = new authProto.auth.v1.AuthService(
|
||||
'localhost:50051',
|
||||
grpc.credentials.createInsecure(),
|
||||
);`,
|
||||
);
|
||||
|
||||
const contracts = await extractor.extract(null, tmpDir, makeRepo(tmpDir));
|
||||
const consumers = contracts.filter((c) => c.role === 'consumer');
|
||||
|
||||
expect(consumers).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('test_extract_ts_duplicate_consumer_patterns_in_one_file_dedupes_deterministically', async () => {
|
||||
writeFile(
|
||||
'proto/auth.proto',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue