mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
* fix: JS/TS scope-resolution coverage gaps — F44, F83, F85, F86, F87 (#1929) F44: Add (class) @scope.class for class expressions in TS query. F83: Fix qualified new_expression (new ns.Foo()) to capture @reference.name. F85: Add enum member declaration patterns (bare + valued) as @declaration.property. F86: Unblocked by F44 — class expression methods get correct Class scope. F87: Add 4 missing optional_parameter type annotation patterns (predefined_type, union_type, array_type, readonly_type) matching required_parameter. Grammar verification via node-types.json confirms all node types exist. 9 new tests proving each fix fails on main and passes on the branch. * chore(bench): update TypeScript scope-capture baseline after F44/F85/F87 --------- Co-authored-by: Sparsh <sparshprajapati2002@gmail.com>
This commit is contained in:
parent
bfe8a87831
commit
7691abf76a
4 changed files with 186 additions and 7 deletions
|
|
@ -51,9 +51,10 @@
|
|||
"_rebaselined": "#1956 synth-widening: + java-iface-extends fixture; synthesizeJavaInheritanceReferences now ALSO walks interface_declaration extends_interfaces (interface IA extends IB, IC<T>), matching the #1940 legacy leg. (Earlier U2+review: java-qualified-base fixture covers 2- AND 3-segment qualified bases guarding the legacy end-anchor; synth tail-resolves scoped bases.) Linear (~1.03). (Earliest: java added to bench, exposed+fixed the O(n^2) findNodeAtRange root-walk; 3.09 -> ~0.99.)"
|
||||
},
|
||||
"typescript": {
|
||||
"fingerprint": "7087f62dbab5fff0d8a9c39f7bc305842ee73a7ba20d7b44677f6511c92e5b92",
|
||||
"fingerprint": "3f44a4a6892698df2d145c8ff2812c3b318807648983c88aca28fbd694f172f9",
|
||||
"scaling_budget": 1.5,
|
||||
"_rebaselined": "#1956 tri-review U2: + typescript-qualified-base fixture AND terminalTsTypeNameNode now treats a member_expression tail (property_identifier) as a leaf name, so qualified `extends ns.Base` synthesizes an edge (was dropped). Linear (~1.03)."
|
||||
"_rebaselined": "#1962: F44 (class scope@), F85 (enum member declarations), F87 (optional_parameter type annotations) add new captures — fingerprint drift expected.",
|
||||
"_note": "#1968: F44, F85, F87 — fingerprint drift expected."
|
||||
},
|
||||
"javascript": {
|
||||
"fingerprint": "a8ddfb15620ae55e50651fc21ab14c4a1f874d9b19e208cc6cbf0a8daac8ec5b",
|
||||
|
|
|
|||
|
|
@ -446,7 +446,8 @@ const JAVASCRIPT_SCOPE_QUERY = `
|
|||
constructor: (identifier) @reference.name) @reference.call.constructor
|
||||
|
||||
(new_expression
|
||||
constructor: (member_expression) @reference.call.constructor.qualified) @reference.call.constructor
|
||||
constructor: (member_expression
|
||||
property: (property_identifier) @reference.name) @reference.call.constructor.qualified) @reference.call.constructor
|
||||
|
||||
;; Write access: obj.field = value
|
||||
(assignment_expression
|
||||
|
|
|
|||
|
|
@ -33,9 +33,8 @@
|
|||
* same identifier also binds as a parameter in the constructor scope
|
||||
* via the normal `required_parameter` → `@type-binding.parameter` path.
|
||||
* - **Enum** — dual type+value. Emits `@scope.class` (enum body contains
|
||||
* member declarations) + `@declaration.enum`. Members are captured as
|
||||
* `@declaration.property` via the generic property_identifier pattern
|
||||
* inside enum_body.
|
||||
* member declarations) + `@declaration.enum`. Enum member names
|
||||
* are captured via `enum_assignment` (see below).
|
||||
*
|
||||
* Node types pinned via `scripts/_probe_typescript_grammar.ts`:
|
||||
* internal_module, namespace_export, namespace_import, import_specifier,
|
||||
|
|
@ -89,6 +88,11 @@ const TYPESCRIPT_SCOPE_QUERY = `
|
|||
(abstract_class_declaration) @scope.class
|
||||
(interface_declaration) @scope.class
|
||||
(enum_declaration) @scope.class
|
||||
;; Class expressions: const Foo = class { ... } / const Foo = class Named { ... }.
|
||||
;; tree-sitter-typescript uses (class) (NOT class_expression) -- same node
|
||||
;; name as tree-sitter-javascript. The name field is optional (anonymous
|
||||
;; class expressions omit it); ScopeExtractor tolerates missing names.
|
||||
(class) @scope.class
|
||||
|
||||
(function_declaration) @scope.function
|
||||
(generator_function_declaration) @scope.function
|
||||
|
|
@ -393,6 +397,9 @@ const TYPESCRIPT_SCOPE_QUERY = `
|
|||
${ARRAY_METHOD_NOT_ANY_OF_PREDICATE})
|
||||
|
||||
;; Method definitions — regular + private (#field) methods.
|
||||
;; These match inside both class_declaration and class (class expression)
|
||||
;; bodies. The @scope.class for class expressions (see (class) above)
|
||||
;; ensures methods inside class expressions get the correct Class scope parent.
|
||||
(method_definition
|
||||
name: (property_identifier) @declaration.name) @declaration.method
|
||||
|
||||
|
|
@ -414,6 +421,15 @@ const TYPESCRIPT_SCOPE_QUERY = `
|
|||
(public_field_definition
|
||||
name: (private_property_identifier) @declaration.name) @declaration.property
|
||||
|
||||
;; Enum members: enum Color { Red, Green = 1, Blue }.
|
||||
;; Bare members (no value): Red, Blue — property_identifier inside enum_body.
|
||||
;; Members with value: Green = 1 — enum_assignment with name field.
|
||||
(enum_body
|
||||
(property_identifier) @declaration.name) @declaration.property
|
||||
|
||||
(enum_assignment
|
||||
name: (_) @declaration.name) @declaration.property
|
||||
|
||||
;; Declarations — parameter properties: \`constructor(public name: string)\`.
|
||||
;; The accessibility_modifier presence distinguishes these from regular
|
||||
;; parameters. The identifier is also bound as a parameter in the
|
||||
|
|
@ -535,6 +551,26 @@ const TYPESCRIPT_SCOPE_QUERY = `
|
|||
type: (type_annotation
|
||||
(generic_type) @type-binding.type)) @type-binding.parameter
|
||||
|
||||
(optional_parameter
|
||||
pattern: (identifier) @type-binding.name
|
||||
type: (type_annotation
|
||||
(predefined_type) @type-binding.type)) @type-binding.parameter
|
||||
|
||||
(optional_parameter
|
||||
pattern: (identifier) @type-binding.name
|
||||
type: (type_annotation
|
||||
(union_type) @type-binding.type)) @type-binding.parameter
|
||||
|
||||
(optional_parameter
|
||||
pattern: (identifier) @type-binding.name
|
||||
type: (type_annotation
|
||||
(array_type) @type-binding.type)) @type-binding.parameter
|
||||
|
||||
(optional_parameter
|
||||
pattern: (identifier) @type-binding.name
|
||||
type: (type_annotation
|
||||
(readonly_type) @type-binding.type)) @type-binding.parameter
|
||||
|
||||
;; Type bindings — variable annotations: \`let u: User = ...\` / \`const u: User\`.
|
||||
(variable_declarator
|
||||
name: (identifier) @type-binding.name
|
||||
|
|
@ -934,7 +970,8 @@ const TYPESCRIPT_SCOPE_QUERY = `
|
|||
constructor: (identifier) @reference.name) @reference.call.constructor
|
||||
|
||||
(new_expression
|
||||
constructor: (member_expression) @reference.call.constructor.qualified) @reference.call.constructor
|
||||
constructor: (member_expression
|
||||
property: (property_identifier) @reference.name) @reference.call.constructor.qualified) @reference.call.constructor
|
||||
|
||||
;; References — write access: \`obj.field = value\`.
|
||||
(assignment_expression
|
||||
|
|
|
|||
140
gitnexus/test/integration/resolvers/js-parsing-coverage.test.ts
Normal file
140
gitnexus/test/integration/resolvers/js-parsing-coverage.test.ts
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/**
|
||||
* Regression tests for JS/TS scope-resolution coverage gaps (issue #1929).
|
||||
*
|
||||
* Each fixture FAILS on main and PASSES on the fix branch.
|
||||
*/
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { emitTsScopeCaptures } from '../../../src/core/ingestion/languages/typescript/captures.js';
|
||||
|
||||
function countTags(src: string, predicate: (tags: string[]) => boolean): number {
|
||||
const matches = emitTsScopeCaptures(src, 'test.ts');
|
||||
return matches.filter((m) => predicate(Object.keys(m))).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* F44: Class expression scope.
|
||||
* On main: (class) is NOT matched → zero @scope.class for class expressions.
|
||||
* On fix: (class) @scope.class matches → exactly one Class scope.
|
||||
*/
|
||||
describe('F44 — class expression @scope.class', () => {
|
||||
it('anonymous class expression emits @scope.class', () => {
|
||||
const src = `
|
||||
export const instance = class {
|
||||
greet(): string { return 'hi'; }
|
||||
};
|
||||
`;
|
||||
const count = countTags(src, (t) => t.includes('@scope.class'));
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
it('named class expression emits @scope.class with the Class scope', () => {
|
||||
const src = `
|
||||
const X = class Named {
|
||||
greet(): string { return 'hi'; }
|
||||
};
|
||||
`;
|
||||
const count = countTags(src, (t) => t.includes('@scope.class'));
|
||||
// 1 for class expression
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* F86: Class expression method_definition scope parent (blocked on F44).
|
||||
* On main: class expression has no @scope.class → method falls through to
|
||||
* enclosing scope, losing Class ownership.
|
||||
* On fix: (class) @scope.class (F44) gives the method a proper Class parent.
|
||||
*/
|
||||
describe('F86 — class expression method ownership', () => {
|
||||
it('class expression method has @declaration.method and @declaration.name', () => {
|
||||
const src = `
|
||||
export const instance = class {
|
||||
greet(): string { return 'hi'; }
|
||||
};
|
||||
`;
|
||||
const matches = emitTsScopeCaptures(src, 'test.ts');
|
||||
const methodDecls = matches.filter((m) => Object.keys(m).includes('@declaration.method'));
|
||||
expect(methodDecls.length).toBe(1);
|
||||
expect(methodDecls[0]['@declaration.name']?.text).toBe('greet');
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* F83: Qualified new_expression name capture.
|
||||
* On main: new ns.Foo() has no @reference.name on the property.
|
||||
* On fix: the member_expression's property is captured as @reference.name.
|
||||
*/
|
||||
describe('F83 — qualified new_expression @reference.name', () => {
|
||||
it('new ns.Foo() captures Foo as @reference.name', () => {
|
||||
const src = `
|
||||
namespace ns {
|
||||
export class Foo {}
|
||||
}
|
||||
const x = new ns.Foo();
|
||||
`;
|
||||
const matches = emitTsScopeCaptures(src, 'test.ts');
|
||||
const nameTags = matches
|
||||
.filter((m) => Object.keys(m).includes('@reference.name'))
|
||||
.map((m) => m['@reference.name']?.text);
|
||||
expect(nameTags).toContain('Foo');
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* F85: Enum member @declaration.property.
|
||||
* On main: enum members are NOT captured as @declaration.property.
|
||||
* On fix: enum_assignment.name is captured as @declaration.property.
|
||||
*/
|
||||
describe('F85 — enum member @declaration.property', () => {
|
||||
it('enum member names are captured as @declaration.property', () => {
|
||||
const src = `
|
||||
enum Color {
|
||||
Red,
|
||||
Green = 1,
|
||||
Blue
|
||||
}
|
||||
`;
|
||||
const propCount = countTags(src, (t) => t.includes('@declaration.property'));
|
||||
// Red, Green, Blue → 3 enum members
|
||||
expect(propCount).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* F87: Optional parameter type annotations.
|
||||
* On main: optional_parameter only matches type_identifier and generic_type.
|
||||
* On fix: matches predefined_type, union_type, array_type, readonly_type too.
|
||||
*/
|
||||
describe('F87 — optional_parameter type annotations', () => {
|
||||
it('optional_parameter with predefined_type (string) captures type binding', () => {
|
||||
const src = `
|
||||
function f(x?: string): void {}
|
||||
`;
|
||||
const typeCount = countTags(src, (t) => t.includes('@type-binding.parameter'));
|
||||
expect(typeCount).toBe(1);
|
||||
});
|
||||
|
||||
it('optional_parameter with union_type captures type binding', () => {
|
||||
const src = `
|
||||
function f(x?: string | null): void {}
|
||||
`;
|
||||
const typeCount = countTags(src, (t) => t.includes('@type-binding.parameter'));
|
||||
expect(typeCount).toBe(1);
|
||||
});
|
||||
|
||||
it('optional_parameter with array_type captures type binding', () => {
|
||||
const src = `
|
||||
function f(x?: string[]): void {}
|
||||
`;
|
||||
const typeCount = countTags(src, (t) => t.includes('@type-binding.parameter'));
|
||||
expect(typeCount).toBe(1);
|
||||
});
|
||||
|
||||
it('optional_parameter with readonly_type captures type binding', () => {
|
||||
const src = `
|
||||
function f(x?: readonly string[]): void {}
|
||||
`;
|
||||
const typeCount = countTags(src, (t) => t.includes('@type-binding.parameter'));
|
||||
expect(typeCount).toBe(1);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue