mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-11-04 07:27:20 +00:00
Some checks are pending
Gitleaks / gitleaks (push) Waiting to run
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
* fix(js): withdraw receiver drops for object-literal key writes a later pass answers
The JS capture makes the enclosing object literal the receiver of each key
it writes (`return { wickRatio: row.a }`). Case 0 of the receiver-bound pass
treats any receiver text holding `.` or `(` as a compound expression, and a
literal holding `row.a`, `0.5` or `...seed` does, so the fold failed and the
site was recorded as a `receiver-unresolved` drop. The return-shape and
unique-name passes run later and answered 17 of these 19 writes with an
ACCESSES edge, but nothing withdrew the earlier drop.
Both passes now report the sites they emitted an edge for, keyed by file,
position, site kind and member name (position alone is shared by `a.b` and
`a.b.c()`), and run.ts withdraws the matching `receiver-unresolved` drops
once they have run. A site they leave unanswered keeps its drop.
The other two writes, in `SignalService.make()`, had no edge at all: the
own-return-shape tier read the caller's owner qualifier as
`SignalService.make#0`, so it never matched the method's keys
(`SignalService.make.secretFlag`) and three same-named candidates were
refused. The qualifier is now the qualified name after `<Label>:<file>:`
without the `#<arity>` suffix. It also stops cutting a positioned id
(`outer.parse@25:2`) down to its column.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(js): type a static field from its initializer for class-level receivers
`Config.DEFAULT.validate()` with `static DEFAULT = new Config()` produced no
CALLS edge. JS and TS keep a static field's type binding out of the class
scope on purpose: the scope has one binding map for static and instance
members, so the static binding would retype an instance field of the same
name. The class-level receiver route (`declaredTypeOfClassLevelField`) reads
the static Property node instead, but only its annotation-derived
`declaredType`, and an unannotated field has none. The type had nowhere to
live.
The field extractor now records `initializerType` for a field that declares
no type and is initialized by `new X()` / `new ns.X()` (the same two shapes
the scope queries bind), the parse worker stamps it on the Property node, and
the class-level route falls back to it after `declaredType`. The binding still
never enters the shared class-scope map, so the instance-twin hazard does not
return.
This resolves one hop: `Host.q.m()` now reaches `Alien.m`. A longer chain off
the field still goes through the compound fold, which types a field step only
from the class-scope map, so it stays unresolved. The matrix known-gap row
`static-read-without-a-twin-loses-its-type` flips to a positive row for TS,
with a JS twin added.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(js): keep CommonJS module.exports declarations out of receiver drops
`module.exports.arrowRequest = (url) => fetch(url)` declares this module's
`arrowRequest` export; the CJS capture rules already bind it like
`exports.memberRequest = …`. The same line also yields a write reference
whose receiver is `module.exports`, and because that text has a dot the
receiver-bound pass folded it as a compound value, failed, and recorded the
declaration as a `receiver-unresolved` write. The bare `exports.x = …`
spelling never reached that path.
A new ScopeResolver hook, `isModuleExportAssignment`, lets a language say a
write site is a module-export declaration. The receiver-bound pass consults
it only at the drop guard, after every case has failed, so it records no
drop and changes nothing else: the site is not marked handled, no edge is
added, and the existing module-level write edge stays. The JS and TS
resolvers implement it for a receiver of exactly `module.exports` (from the
minted chain, or the text when no chain was minted) when `module` resolves
to nothing in scope. A `module` parameter, as in a UMD wrapper, or a write
into a value hanging off the export object still records its drop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ts): record an owned-`this` chain receiver as owned-but-unbound
`this.route.go()` inside an object-literal method was filed as a generic
`receiver-unresolved` drop (or, depending on site order, as nothing). The
method scope owns `this` and gives it no type, which the pass detects with
`isReceiverOwnedButUnbound`, but that check was handed the whole receiver
text `this.route`. No scope owns a chain, so the call fell through to Case 0,
whose fold cannot type an owned `this` either.
The check now receives the receiver's base (from the minted chain, else the
text before the first `.`/`(`), the same base the `suppressReceiverLookup`
check already used. The call is recorded as `receiver-owned-but-unbound`,
its real reason, and still emits no edge: the dynamic-this negative control
holds. On the fixture corpus this moves only that site; no edge changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(resolution): preserve receiver ownership
Bind a return key's declaration write by exact file and position so
unrelated input accesses keep their ordinary candidate ranking. Stop at
untyped static overrides and resolve constructor initializers as values.
Preserve the combined drop guards after rebasing onto main, cover the
binding regressions, and refresh the receiver baseline after #3654.
Key answered sites by their full range to retain repeated-name outer
reads. Keep owned-but-unbound calls in the persisted completeness signal.
* fix(resolution): address feedback (#3653)
Classify module-export declarations using captured module kind before
suppressing receiver drops. Match same-named field metadata by source
position, including declarations on one line.
Require reference kinds in suppression assertions and guard temporary
fixture cleanup. Cover the reported failures with regression tests.
* perf(parser): index repeated field declarations
Sort private field buckets once and use binary search to avoid
quadratic scans while preserving declaration disambiguation.
* fix(ingestion): preserve capture metadata
Record module kind from the parsed tree on worker and main-thread paths.
Match Ruby field metadata at the accessor call instead of its argument.
* fix(ingestion): harden capture metadata
Keep Ruby reader/writer properties writable. Walk module-kind ASTs iteratively so deep expressions cannot abort extraction.
* test(ingestion): clarify worker metadata scope
* docs(ingestion): state capture metadata effects
---------
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
51 lines
2.5 KiB
TypeScript
51 lines
2.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import Parser from 'tree-sitter';
|
|
import TypeScript from 'tree-sitter-typescript';
|
|
import JavaScript from 'tree-sitter-javascript';
|
|
import {
|
|
javascriptProvider,
|
|
typescriptProvider,
|
|
} from '../../src/core/ingestion/languages/typescript.js';
|
|
import { extractParsedFile } from '../../src/core/ingestion/scope-extractor-bridge.js';
|
|
import { isCommonJsModule } from '../../src/core/ingestion/languages/typescript/cjs-export-assignment.js';
|
|
import type { SyntaxNode } from '../../src/core/ingestion/utils/ast-helpers.js';
|
|
|
|
describe('deep CommonJS classification', () => {
|
|
it.each([false, true])('handles a deep expression with trailing ESM=%s', (esm) => {
|
|
const node = (type: string, namedChildren: SyntaxNode[], text = '') =>
|
|
({ type, namedChildren, text, childForFieldName: () => null }) as unknown as SyntaxNode;
|
|
let expression = node('identifier', [], 'module');
|
|
for (let i = 0; i < 20000; i++) expression = node('binary_expression', [expression]);
|
|
const root = node('program', esm ? [expression, node('export_statement', [])] : [expression]);
|
|
expect(isCommonJsModule(root, 'deep.ts')).toBe(!esm);
|
|
});
|
|
});
|
|
|
|
describe.each([
|
|
['JavaScript', javascriptProvider, JavaScript, 'js', 'cjs', 'mjs'],
|
|
['TypeScript', typescriptProvider, TypeScript.typescript, 'ts', 'cts', 'mts'],
|
|
] as const)(
|
|
'%s module-kind capture',
|
|
(_, provider, grammar, extension, commonJsExtension, esmExtension) => {
|
|
it.each([
|
|
['module.exports.value = 1;', extension, true],
|
|
['export {}; module.exports.value = 1;', extension, false],
|
|
['import "./dep"; module.exports.value = 1;', extension, false],
|
|
['export {}; module.exports.value = 1;', commonJsExtension, true],
|
|
['module.exports.value = 1;', esmExtension, false],
|
|
] as const)('carries %j in .%s as %s through serialization', (source, suffix, expected) => {
|
|
const parser = new Parser();
|
|
parser.setLanguage(grammar);
|
|
const tree = parser.parse(source);
|
|
const filePath = `module.${suffix}`;
|
|
const parsed = extractParsedFile(provider, source, filePath, undefined, tree);
|
|
expect(parsed).toBeDefined();
|
|
const channel = provider.collectCaptureSideChannel?.(filePath);
|
|
expect(channel).toBe(expected);
|
|
expect(
|
|
JSON.parse(JSON.stringify({ ...parsed, captureSideChannel: channel })).captureSideChannel,
|
|
).toBe(expected);
|
|
expect(provider.collectCaptureSideChannel?.(filePath)).toBeUndefined();
|
|
});
|
|
},
|
|
);
|