mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
Some checks are pending
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (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 / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Skill copy sync / shipped skills drift guard (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
* fix(dart): anchor @name so a constructor initializer stops minting a phantom
A Dart declaration whose value is a constructor call parses the callee as a
SECOND (identifier) sibling of the declared name:
final TextEditingController _title = TextEditingController();
-> initialized_identifier[ identifier "_title",
identifier "TextEditingController", selector ]
The five graph-node rules that capture fields and top-level variables matched
`(identifier) @name` without the first-child anchor, so @name bound to both
siblings and the query minted a phantom Property/Variable named after the TYPE
alongside the real declaration. On dart-flutter-conduit that produced a
`Property TextEditingController` next to the genuine `_title` / `_body` in
editor_screen.dart and login_screen.dart.
static_final_declaration has the same shape, so class statics and top-level
final/const were affected too, as were top-level `var`/`final` variables. All
five rules now anchor @name with `.`, matching the mirror rules in
languages/dart/query.ts which already anchored.
Verified against the vendored grammar: the phantoms disappear and every real
declaration is still captured (_title, _body, nullable field, static final,
uninitialized field, top-level final, top-level var). End to end on
dart-flutter-conduit: Property nodes 108 -> 106, type-shaped names 2 -> 0,
real fields unchanged.
The new test loads the grammar via createParserForLanguage rather than
loadLanguage: loadLanguage resolves to void, so the surrounding
`if (!(await loadDartOrSkip())) return;` idiom is always falsy and skips the
body. Confirmed as a negative control -- reverting only the query change makes
the new test fail on the exact phantom.
* chore(autofix): apply prettier + eslint fixes via /autofix command
* Address PR review feedback (#3224)
Correct the RHS_ONLY_TYPES comments so they state the capture invariant
instead of claiming those names appear only as constructor callees.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Address PR review feedback (#3224)
Build the Dart query with Parser.Query and parser.getLanguage() so the
test no longer casts the tree (or Query/captures) through any.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
108 lines
4.5 KiB
TypeScript
108 lines
4.5 KiB
TypeScript
import { describe, it, expect, beforeAll } from 'vitest';
|
|
import Parser from 'tree-sitter';
|
|
import { SupportedLanguages } from '../../src/config/supported-languages.js';
|
|
import { createParserForLanguage } from '../../src/core/tree-sitter/parser-loader.js';
|
|
import { DART_QUERIES } from '../../src/core/ingestion/tree-sitter-queries.js';
|
|
|
|
/**
|
|
* A Dart field whose value is a constructor call parses the callee as a SECOND
|
|
* (identifier) sibling of the field name:
|
|
*
|
|
* final TextEditingController _title = TextEditingController();
|
|
* → initialized_identifier[ identifier "_title",
|
|
* identifier "TextEditingController", selector ]
|
|
*
|
|
* Unanchored, `(identifier) @name` matched both siblings and minted a phantom
|
|
* Property/Variable named after the TYPE next to the real field. The same shape
|
|
* applies to static_final_declaration (class statics and top-level final/const),
|
|
* so all five graph-node rules anchor @name to the first named child.
|
|
*
|
|
* Regression guard: constructor-callee type names must never be captured as
|
|
* declarations, and every real declared name must still be captured.
|
|
*/
|
|
|
|
const CODE = `class S {
|
|
final TextEditingController _title = TextEditingController();
|
|
final TextEditingController _body = TextEditingController();
|
|
Foo? nullableField = Foo();
|
|
static final Bar staticField = Bar();
|
|
final ArticleApi _noInitializer;
|
|
}
|
|
final Baz topLevelFinal = Baz();
|
|
var topLevelVar = Qux();
|
|
`;
|
|
|
|
/** Constructor callee names that must never be captured as declaration names. */
|
|
const RHS_ONLY_TYPES = ['TextEditingController', 'Foo', 'Bar', 'Baz', 'Qux'];
|
|
|
|
/** Every name actually declared in CODE. */
|
|
const DECLARED = [
|
|
'_title',
|
|
'_body',
|
|
'nullableField',
|
|
'staticField',
|
|
'_noInitializer',
|
|
'topLevelFinal',
|
|
'topLevelVar',
|
|
];
|
|
|
|
describe('Dart field/variable declarations with constructor initializers', () => {
|
|
let parser: Parser | null = null;
|
|
let unavailable: string | null = null;
|
|
|
|
beforeAll(async () => {
|
|
// NB: loadLanguage() resolves to void, so the `if (!(await loadDartOrSkip()))`
|
|
// idiom used elsewhere in this suite is always truthy-false and skips the
|
|
// whole test body. createParserForLanguage returns the Parser, so a genuine
|
|
// load failure is distinguishable from a successful load.
|
|
try {
|
|
parser = await createParserForLanguage(SupportedLanguages.Dart);
|
|
} catch (error) {
|
|
unavailable = error instanceof Error ? error.message : String(error);
|
|
}
|
|
});
|
|
|
|
function capturedNames(): { property: string[]; variable: string[] } {
|
|
if (!parser) throw new Error('parser unavailable');
|
|
const tree = parser.parse(CODE);
|
|
const query = new Parser.Query(parser.getLanguage(), DART_QUERIES);
|
|
const property: string[] = [];
|
|
const variable: string[] = [];
|
|
for (const match of query.matches(tree.rootNode)) {
|
|
const name = match.captures.find((c) => c.name === 'name');
|
|
const def = match.captures.find((c) => c.name.startsWith('definition.'));
|
|
if (!name || !def) continue;
|
|
if (def.name === 'definition.property') property.push(name.node.text);
|
|
if (def.name === 'definition.variable') variable.push(name.node.text);
|
|
}
|
|
return { property: [...new Set(property)], variable: [...new Set(variable)] };
|
|
}
|
|
|
|
it('does not mint a phantom named after the initializer type', (ctx) => {
|
|
if (!parser) return ctx.skip(`dart grammar unavailable: ${unavailable}`);
|
|
const { property, variable } = capturedNames();
|
|
const all = [...property, ...variable];
|
|
expect(all.length, 'query produced no captures at all').toBeGreaterThan(0);
|
|
for (const type of RHS_ONLY_TYPES) {
|
|
expect(all, `phantom captured for constructor callee "${type}"`).not.toContain(type);
|
|
}
|
|
});
|
|
|
|
it('still captures every declared field and top-level variable', (ctx) => {
|
|
if (!parser) return ctx.skip(`dart grammar unavailable: ${unavailable}`);
|
|
const { property, variable } = capturedNames();
|
|
const all = [...property, ...variable];
|
|
for (const declared of DECLARED) {
|
|
expect(all, `lost real declaration "${declared}"`).toContain(declared);
|
|
}
|
|
});
|
|
|
|
it('classifies class members as property and top-level names as variable', (ctx) => {
|
|
if (!parser) return ctx.skip(`dart grammar unavailable: ${unavailable}`);
|
|
const { property, variable } = capturedNames();
|
|
expect(property).toEqual(
|
|
expect.arrayContaining(['_title', '_body', 'nullableField', 'staticField', '_noInitializer']),
|
|
);
|
|
expect(variable).toEqual(expect.arrayContaining(['topLevelFinal', 'topLevelVar']));
|
|
});
|
|
});
|