mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-29 06:46:49 +08:00
`extension_type_declaration` appears in none of the three places Dart's other type-like declarations do. `extension_declaration` — the older `extension` — is in all three, and the two names are near neighbours. That is why #1780's `isInsideClassLikeNode()` gate dropped these members and no others: the gate asks whether a class-like node is on the stack, and an `extension type` never put one there, while `extension`, `mixin` and `class` all did. The gate surfaced the omission rather than causing it. Before it, the members were still reached — but as top-level `function:km` rather than `method:MetersT::km`, indexed and attributed to nothing. The kernel omits the same node type, so it walks the members through its own fallback and mints them as top-level functions with no `MetersT` to belong to. `kernel-dart-parity.test.ts` fails four cases on `main` because of it; fixing only the wasm side would leave those red. Listing it alongside the others is the whole fix — `extraClassNodeTypes` and `dartEnclosingTypeName` on the wasm side, the two `matches!` arms on the kernel's. Both paths now give: class:MetersT · method:MetersT::km · method:MetersT::report with `report`'s span running to its closing brace rather than stopping at the signature line, and an ordinary class untouched.
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* A Dart 3 `extension type` is a type, and its members are its methods (#1784).
|
||||
*
|
||||
* `extension_type_declaration` was listed in neither extraction path's class
|
||||
* types. `extension_declaration` — the older `extension` — was, and the two
|
||||
* names are near neighbours, so the omission reads as an oversight.
|
||||
*
|
||||
* That is why #1780's `isInsideClassLikeNode()` gate dropped these members and
|
||||
* no others: the gate asks whether a class-like node is on the stack, and an
|
||||
* `extension type` never put one there. Before that gate the members were still
|
||||
* reached, but as top-level `function:km` rather than `method:MetersT::km` —
|
||||
* indexed, and attributed to nothing.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import { extractFromSource } from '../src/extraction';
|
||||
import { initGrammars, loadAllGrammars } from '../src/extraction/grammars';
|
||||
|
||||
beforeAll(async () => {
|
||||
await initGrammars();
|
||||
await loadAllGrammars();
|
||||
});
|
||||
|
||||
const SRC = [
|
||||
'extension type MetersT(double value) {',
|
||||
' double get km => value / 1000;',
|
||||
' void report() {',
|
||||
' print(km);',
|
||||
' }',
|
||||
'}',
|
||||
'',
|
||||
'class Widget {',
|
||||
' double get half => 1.0;',
|
||||
'}',
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
describe('Dart extension type (#1784)', () => {
|
||||
it('is a type of its own, and its members are its methods', () => {
|
||||
const result = extractFromSource('probe.dart', SRC);
|
||||
expect(result.nodes.find((n) => n.name === 'MetersT')?.kind).toBe('class');
|
||||
|
||||
const km = result.nodes.find((n) => n.name === 'km');
|
||||
expect(km, 'the getter must have a node').toBeDefined();
|
||||
expect(km?.kind).toBe('method');
|
||||
expect(km?.qualifiedName).toBe('MetersT::km');
|
||||
});
|
||||
|
||||
it('does not cut the span of the member that follows a getter', () => {
|
||||
const result = extractFromSource('probe.dart', SRC);
|
||||
const report = result.nodes.find((n) => n.name === 'report');
|
||||
// The body runs to its closing brace, not to the signature line.
|
||||
expect(report?.endLine).toBeGreaterThan(report!.startLine);
|
||||
});
|
||||
|
||||
it('leaves an ordinary class as it was', () => {
|
||||
const result = extractFromSource('probe.dart', SRC);
|
||||
expect(result.nodes.find((n) => n.name === 'Widget')?.kind).toBe('class');
|
||||
expect(result.nodes.find((n) => n.name === 'half')?.kind).toBe('method');
|
||||
});
|
||||
});
|
||||
@@ -398,7 +398,7 @@ impl<'t> Walker<'t> {
|
||||
while let Some(parent) = p {
|
||||
if matches!(
|
||||
parent.kind(),
|
||||
"class_definition" | "mixin_declaration" | "extension_declaration" | "enum_declaration"
|
||||
"class_definition" | "mixin_declaration" | "extension_declaration" | "extension_type_declaration" | "enum_declaration"
|
||||
) {
|
||||
return parent.child_by_field_name("name").map(|n| self.text(n));
|
||||
}
|
||||
@@ -630,7 +630,11 @@ impl<'t> Walker<'t> {
|
||||
self.extract_function(node);
|
||||
return;
|
||||
}
|
||||
"class_definition" | "mixin_declaration" | "extension_declaration" => {
|
||||
// `extension_type_declaration` is Dart 3.3's extension type. It is a
|
||||
// different node from `extension_declaration` above, which is the
|
||||
// older `extension` — the names are near neighbours and only one of
|
||||
// them was listed.
|
||||
"class_definition" | "mixin_declaration" | "extension_declaration" | "extension_type_declaration" => {
|
||||
self.extract_class(node);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -34,13 +34,14 @@ function dartConstructorSignature(node: SyntaxNode): SyntaxNode | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** The name of the class/mixin/extension/enum lexically enclosing `node`. */
|
||||
/** The name of the class/mixin/extension/extension type/enum lexically enclosing `node`. */
|
||||
function dartEnclosingTypeName(node: SyntaxNode): string | undefined {
|
||||
let p = node.parent;
|
||||
while (p) {
|
||||
if (
|
||||
p.type === 'class_definition' || p.type === 'mixin_declaration' ||
|
||||
p.type === 'extension_declaration' || p.type === 'enum_declaration'
|
||||
p.type === 'extension_declaration' || p.type === 'extension_type_declaration' ||
|
||||
p.type === 'enum_declaration'
|
||||
) {
|
||||
return p.childForFieldName('name')?.text;
|
||||
}
|
||||
@@ -132,7 +133,11 @@ export const dartExtractor: LanguageExtractor = {
|
||||
importTypes: ['import_or_export'],
|
||||
callTypes: [], // Dart calls use identifier+selector, handled via extractBareCall
|
||||
variableTypes: [],
|
||||
extraClassNodeTypes: ['mixin_declaration', 'extension_declaration'],
|
||||
// `extension_type_declaration` is Dart 3's extension type. It sits beside the
|
||||
// older `extension_declaration` — near-neighbour names — and its members live
|
||||
// in an ordinary `class_body`, so it belongs on this list for the same reason
|
||||
// the other two do.
|
||||
extraClassNodeTypes: ['mixin_declaration', 'extension_declaration', 'extension_type_declaration'],
|
||||
// A Dart `static_final_declaration` is exactly a top-level or class-`static`
|
||||
// `const`/`final` — the shared-constant idiom — so extract it as `constant`
|
||||
// for value-reference edges. Instance fields, `var`, and typed declarations
|
||||
|
||||
Reference in New Issue
Block a user