staticElement Is Gone in analyzer 8.x — Ask the Return Type Instead

If you maintain a custom lint rule for Dart and you have been putting off the analyzer 8.x bump, here is the thing that will stop your build, and the shape of the fix.
SimpleIdentifier.staticElement was removed. Not deprecated with a grace period you can ignore for two more releases — removed. Rule code that reaches through an identifier to ask "what class is this, and which library did it come from?" no longer compiles.
This is worth saying plainly: it is not a bug and there is no workaround coming. It is a deliberate removal in the element model. The migration is not "wait for it to be fixed," it is "ask a different question."
What breaks
The pattern that stops compiling looks like this:
final classElement = methodTarget.staticElement;
if (classElement is! ClassElement || classElement.name != 'MediaQuery') return;
The logic is reasonable and it is how a lot of rule code was written: take the identifier on the left of the call, resolve it to an element, check the element is the class you care about. On analyzer: ^8.4.0 it does not build.
What replaces it
Stop asking the identifier what it refers to. Ask the invocation what it produces.
For a MethodInvocation:
final returnElement = invocation.staticType?.element;
final libraryUri = returnElement?.library?.uri.toString();
if (returnElement?.name != 'MediaQueryData' ||
libraryUri == null ||
!libraryUri.startsWith('package:flutter/')) {
return;
}
Read what changed there, because it is not a mechanical rename:
- The subject moved. Before, you interrogated
methodTarget— the receiver. Now you interrogateinvocation.staticType— the result. - The name you check changed with it.
MediaQuerybecameMediaQueryData. That is not a typo, it is the consequence of asking about the return type instead of the receiver. If you port this by search-and-replace and keep the old class name, your rule silently stops matching. It compiles, it runs, it flags nothing. That is a worse outcome than the build break you started with. - The library check is now explicit.
libraryUri.startsWith('package:flutter/')is doing the work that "is this element the realMediaQuery?" used to do implicitly. Without it, anyone'sMediaQueryDatasatisfies your rule.
For an InstanceCreationExpression, the same principle with a different entry point: use element?.library?.uri on the constructor's static element, reached through the element model of the creation expression itself — not through a SimpleIdentifier. The identifier is the thing that no longer answers.
Why this generalizes past one repo
The recipe above lives in one repository's CLAUDE.md, which is exactly the kind of context that makes people assume it is project-specific trivia. It is not.
The removal is a property of the analyzer 8.x API surface. Any custom-lint package that needs to resolve identifier → class → originating library will hit the same wall and the same replacement applies, regardless of which lint framework sits on top. If your rule asks "is this symbol the one from that package," you are in scope.
One boundary I want to be honest about: I do not know that the return-type route is the only replacement. It is the one that is sanctioned and shipped, and it works. Whether the analyzer exposes a more direct accessor for this relationship is something I have not established — so treat the recipe as the known-good path rather than as proof that no better one exists.
The part worth keeping
The migration itself is small. The lesson underneath it is the one I keep relearning:
A removed API is usually a removed question, not a removed method. The temptation is to hunt for whatever replaced staticElement one-for-one. There isn't one, because the framework stopped considering "what element does this identifier refer to" the right thing to ask at that position. Once you accept the question changed, the fix is short. While you are still looking for the drop-in, it feels like an unsolvable regression.
And the practical trap, worth repeating because it is silent: when the subject of the question changes, the answer's name changes with it. MediaQuery → MediaQueryData. A rule that ports the call but keeps the old name compiles clean and matches nothing forever. If you have lint rules with no tests, this migration is the moment to write one — a rule that flags nothing looks exactly like a codebase with no violations.