mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-14 23:22:54 +00:00
fix(dart): anchor @name so a constructor initializer stops minting a … (#3224)
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
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>
This commit is contained in:
parent
af2d9aec15
commit
68c42009df
2 changed files with 121 additions and 5 deletions
|
|
@ -2243,18 +2243,26 @@ export const DART_QUERIES = `
|
|||
(identifier) @name . (formal_parameter_list))) @definition.constructor
|
||||
|
||||
; ── Field declarations (String name = '', Address address = Address()) ──────
|
||||
; @name is ANCHORED to the first named child. An initialized_identifier whose
|
||||
; value is a constructor call parses the callee as a SECOND (identifier) sibling
|
||||
; of the name — \`final TextEditingController _t = TextEditingController();\` is
|
||||
; initialized_identifier[identifier "_t", identifier "TextEditingController",
|
||||
; selector]. Unanchored, \`(identifier) @name\` matched both and minted a phantom
|
||||
; Property named after the TYPE alongside the real field. The same shape applies
|
||||
; to static_final_declaration (class static and top-level final/const), so every
|
||||
; rule below anchors. languages/dart/query.ts already anchors its mirror rules.
|
||||
(declaration
|
||||
(type_identifier)
|
||||
(initialized_identifier_list
|
||||
(initialized_identifier
|
||||
(identifier) @name))) @definition.property
|
||||
. (identifier) @name))) @definition.property
|
||||
|
||||
; ── Nullable field declarations (String? name) ──────────────────────────────
|
||||
(declaration
|
||||
(nullable_type)
|
||||
(initialized_identifier_list
|
||||
(initialized_identifier
|
||||
(identifier) @name))) @definition.property
|
||||
. (identifier) @name))) @definition.property
|
||||
|
||||
; ── static const / static final / const class fields ────────────────────────
|
||||
; A "static const a = 1;" / "static final String b = ..., c = ...;" field parses
|
||||
|
|
@ -2267,7 +2275,7 @@ export const DART_QUERIES = `
|
|||
(declaration
|
||||
(static_final_declaration_list
|
||||
(static_final_declaration
|
||||
(identifier) @name))) @definition.property
|
||||
. (identifier) @name))) @definition.property
|
||||
|
||||
; ── Getters ──────────────────────────────────────────────────────────────────
|
||||
(method_signature
|
||||
|
|
@ -2290,7 +2298,7 @@ export const DART_QUERIES = `
|
|||
(program
|
||||
(initialized_identifier_list
|
||||
(initialized_identifier
|
||||
(identifier) @name)) @definition.variable)
|
||||
. (identifier) @name)) @definition.variable)
|
||||
; Closure bindings: \`var f = (x) => x;\` binds a CALLABLE, so it emits Function
|
||||
; rather than Variable, matching TS/JS. Overlap with the pattern above is
|
||||
; collapsed by the parse-worker dedup (#2687). Since #2693 this node is also
|
||||
|
|
@ -2336,7 +2344,7 @@ export const DART_QUERIES = `
|
|||
(program
|
||||
(static_final_declaration_list
|
||||
(static_final_declaration
|
||||
(identifier) @name)) @definition.variable)
|
||||
. (identifier) @name)) @definition.variable)
|
||||
|
||||
; ── Imports ──────────────────────────────────────────────────────────────────
|
||||
(import_or_export
|
||||
|
|
|
|||
108
gitnexus/test/unit/dart-field-initializer-phantom.test.ts
Normal file
108
gitnexus/test/unit/dart-field-initializer-phantom.test.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
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']));
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue