mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-28 14:08:56 +08:00
fix(dart): an annotation links the constant or constructor it names, even behind a comment (#2390)
A Dart annotation is a constant expression: a const variable (`@riverpod`, `@meta.immutable`, a type's static constant or enum value) or a call of a const constructor (`@Riverpod(keepAlive: true)`, `@Foo.named(…)`). It was ranked like a Python decorator, which favours a function or method of that name over a class and scores a constant not at all, so riverpod's 506 `@riverpod` annotations went to riverpod_analyzer_utils' extension getter `riverpod` (0.4) instead of riverpod_annotation's `const riverpod = Riverpod();`. matchDartAnnotation (name-matcher.ts) now takes every Dart `decorates` ref before any other strategy, and nothing falls through. It reads the annotation as written from its `@` and links a constant or (called) a class the file's library can see, through the import prefix it is written with (isDartImportPrefix, dart-libraries.ts); a type's own static constant, enum value or (called) named constructor (isDartConstructor); or, on a member, a static constant of the type around it. Never a method, getter or function. The decorator scan also stopped at the first sibling that was not an annotation, so a comment between an annotation and its declaration (`@override` `// ignore: must_call_super` `void f()`, riverpod_lint's `@riverpod` `// expect_lint: …` fixtures) hid the annotation. Dart's decoratorStepOverTypes lists its two comment kinds, which the scan now steps over, and the kernel's extract_decorators_for steps over the same. Validated on rrousselGit/riverpod @4ba1be2 and felangel/bloc @b9be1e2 with the kernel loaded: riverpod's 506 `@riverpod` edges move to `const riverpod` and 16 annotations behind a comment add an edge each, with nothing removed; bloc gains 7 `@override` refs and no edge changes. Kernel and wasm full-index dumps are byte-identical, and the parity sweeps show 0 diffs. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
631df0138e
commit
d2fb323e51
@@ -871,12 +871,16 @@ enum emits nothing). Mechanics for dart:
|
||||
decorates `immutable`). Enums and typedefs are built the same way, but
|
||||
nothing scans them.
|
||||
- Scan #2 (preceding siblings :5002-5023): walk BACKWARD from the
|
||||
declaration; `annotation` is in the accepted set (:5017); stop at the
|
||||
first non-annotation sibling. consider(): target = first namedChild of
|
||||
accepted types → the `identifier` (`override`, `deprecated`, `pragma`,
|
||||
`immutable`, `Deprecated`) → `<`-strip + last-`.`-segment (`@ui.Widget`
|
||||
style would strip to `Widget`) → **`decorates` ref {from the decorated
|
||||
node, name, line/col of the ANNOTATION node}**. With-args annotations
|
||||
declaration; `annotation` is in the accepted set (:5017); step over
|
||||
`comment` / `documentation_comment` siblings (since 2026-10-06, below);
|
||||
stop at the first other non-annotation sibling. consider(): target =
|
||||
first namedChild of accepted types → the `identifier` (`override`,
|
||||
`deprecated`, `pragma`, `immutable`, `Deprecated`) or `scoped_identifier`
|
||||
(`meta.immutable`, `Foo.named`, `p.Foo.named`) → `<`-strip + last-`.`-segment
|
||||
(`@meta.immutable` → `immutable`, `@p.Foo.named(…)` → `named`; the strip
|
||||
runs first, so `@Foo<int>.named(…)`, which parses with an ERROR inside the
|
||||
name, → `Foo`) → **`decorates` ref {from the decorated node, name,
|
||||
line/col of the ANNOTATION node, i.e. its `@`}**. With-args annotations
|
||||
emit their NAME; the argument expressions are never visited (no refs from
|
||||
`@Deprecated('use other')`'s string).
|
||||
- **Stacked annotations emit in REVERSE source order** (the backward walk):
|
||||
@@ -885,22 +889,34 @@ enum emits nothing). Mechanics for dart:
|
||||
- For a method: the previous member's function_body (or any declaration)
|
||||
breaks the chain correctly. (The docstring walk steps OVER annotations
|
||||
since 2026-10-06 — §Docstrings.)
|
||||
- **A comment between an annotation and the declaration ends the scan**, so
|
||||
the annotation above it attaches to nothing: `@override` `// ignore:
|
||||
must_call_super` `void f()`, `@x` `/// Doc below.` `void f()` (pinned in
|
||||
TortureAnnotatedDocs.dart). Measured 2026-10-06: bloc @b9be1e2 hides 7
|
||||
annotations this way (all `@override`), riverpod @4ba1be2 55 on 49
|
||||
declarations (`@override` 33, `@riverpod` 16 in the lint fixtures,
|
||||
`@internal` 6). Left as is ON PURPOSE: stepping over comments here is
|
||||
right for Dart (an annotation always belongs to the next declaration), but
|
||||
Dart nodes store no decorator list, so the only effect would be edges, and
|
||||
`override`/`internal` resolve to nothing, while `decorates` scoring prefers
|
||||
function/method candidates (name-matcher.ts), so `@riverpod` lands on the
|
||||
analyzer getter `RiverpodAnnotatedAnnotatedNodeOfX::riverpod` (0.4)
|
||||
instead of `const riverpod = Riverpod()`. That is already true of
|
||||
riverpod's 506 existing `@riverpod` edges. Fix that ranking for Dart (a
|
||||
const or a class, never a method) first, then add `comment` /
|
||||
`documentation_comment` to scan #2's step-over in both arms.
|
||||
- **Comments between the annotations and the declaration are stepped over**
|
||||
(since 2026-10-06). An annotation always belongs to the next declaration,
|
||||
so dart.ts sets `decoratorStepOverTypes: ['comment',
|
||||
'documentation_comment']`, which scan #2 skips, and the kernel's
|
||||
`extract_decorators_for` skips the same `DECORATOR_STEP_OVER` list. No
|
||||
other language sets one. Until then the comment ended the scan and the
|
||||
annotation above it attached to nothing. Now these all attach: `@override`
|
||||
`// ignore: must_call_super` `void f()`, `@x` `/// Doc below.` `void f()`,
|
||||
`@x /* c */ void f()`, a comment on the annotation's own line, and comments
|
||||
between stacked annotations (still in reverse order). Pinned in
|
||||
TortureAnnotatedComments.dart and dart-annotation-comments.test.ts.
|
||||
- Still ends the scan, comments or not: a field's `declaration`, a
|
||||
top-level variable, an import, the previous member's body (`void a() {}
|
||||
// note` `@x void b()` gives `b` only `x`), a class-like declaration.
|
||||
- Class-likes are unaffected. Their leading annotations are scan #1's,
|
||||
which reads every child, comments among them included.
|
||||
- Validation (bloc @b9be1e2 / riverpod @4ba1be2, kernel loaded, on top of
|
||||
the annotation resolution below): `decorates` refs +7 / +54, edges +0 /
|
||||
+16, nothing else in the dumps changed. bloc's 7 are all `@override`.
|
||||
riverpod's 54 (on 48 declarations) are `@override` 32, `@internal` 6 and
|
||||
`@riverpod` 16 (lint fixtures and one website snippet), and each
|
||||
`@riverpod` links `const riverpod`. Kernel and wasm full-index dumps are
|
||||
byte-identical, and the parity sweeps show 0 diffs.
|
||||
- The AST scan's 55th riverpod site is `@override` `// ignore: …` on a
|
||||
FIELD, which no extractor records. A scan that also counts annotation →
|
||||
comment runs INSIDE class-like nodes finds riverpod's 24 `@riverpod`
|
||||
`// ignore: …` `class X` lint fixtures and bloc's `@JsonEnum(…)` `/// …`
|
||||
`enum`. Scan #1 already reads those (an enum's, never: see above).
|
||||
- Members with no body (declaration-wrapped ctors, const ctors, redirecting
|
||||
factories, abstract and `external` members): the signature's PARENT is the
|
||||
`declaration`, so until 2026-10-06 the backward scan ran over the
|
||||
@@ -914,6 +930,41 @@ enum emits nothing). Mechanics for dart:
|
||||
refs, all SDK or package:meta names (`useResult`, `override`, `internal`,
|
||||
`visibleForTesting`, …), so no edges; before #2380, riverpod's 23 new
|
||||
`@internal` refs on `X.internal(…)` ctors resolved to the ctor itself.
|
||||
- **Resolution is TS-side and Dart-specific** (since 2026-10-06):
|
||||
resolveOneInner hands every Dart `decorates` ref to name-matcher.ts's
|
||||
`matchDartAnnotation` before any other strategy, and nothing falls
|
||||
through. A Dart annotation is a const variable or a const constructor
|
||||
call, never a method, getter or function, so the shared decorator ranking
|
||||
(findBestMatch: +25 function/method, +15 class, nothing for a constant)
|
||||
never sees one. That ranking sent riverpod's 506 `@riverpod` to the
|
||||
analyzer's extension getter `RiverpodAnnotatedAnnotatedNodeOfX::riverpod`
|
||||
(0.4) instead of `const riverpod = Riverpod();`.
|
||||
- The matcher re-reads the written form from the `@` in comment-blanked
|
||||
source. It is a call only when `(` follows on the same line, so a record
|
||||
return type on the next line is not taken for arguments.
|
||||
- `@x`: on a member, the enclosing type's own static `constant` /
|
||||
`enum_member` `x` (a class-like's own annotations stand outside its
|
||||
body); else a top-level `constant` the library can see (#2386's model,
|
||||
the own library shadowing imports). `@X(…)`: a visible class `X`, not
|
||||
an extension.
|
||||
- `@p.x` / `@p.X(…)`, when `p` is an import prefix (dart-libraries.ts
|
||||
`isDartImportPrefix`, the imports of the file's whole library): the same
|
||||
through that prefix. Otherwise `@T.x` → T's own `constant` /
|
||||
`enum_member` `x`, and `@T.named(…)` → T's named constructor (#2380's
|
||||
`isDartConstructor`); `@p.T.x` / `@p.T.named(…)` likewise through `p`.
|
||||
- A sole candidate links at 0.9 (`exact-match`, or `qualified-name`
|
||||
through a type). Several (only where the library model can't establish
|
||||
the library) fall back to findBestMatch's proximity at 0.7 / 0.4.
|
||||
- Not modelled: a `final` is not told apart from a `const` (compiling code
|
||||
never annotates with a visible `final`), nor a typedef used to call a
|
||||
constructor.
|
||||
- Validation (main 99246848, kernel loaded): bloc 0 edges changed (no
|
||||
annotation there names a project declaration); riverpod exactly the 506
|
||||
`@riverpod` edges retargeted to `const riverpod` at 0.9, every other
|
||||
`decorates` edge (`$internal`, `Riverpod`, `Dependencies`, …) identical,
|
||||
nodes, refs and files identical. Every extracted annotation in both
|
||||
repos is written with one name, so the prefixed and `T.x` forms are
|
||||
pinned by dart-annotation-targets.test.ts only.
|
||||
|
||||
### Docstrings (tree-sitter-helpers.ts:95-127) — dartdoc is KEPT, both forms
|
||||
|
||||
|
||||
Reference in New Issue
Block a user