pkg/linter/doc/use_build_context_synchronously.md
use_build_context_synchronously designThe idea behind the use_build_context_synchronously lint rule requires
careful tracking of a function body's possible control flow. At various points
in the syntax tree, we must be able to answer questions like, "when this
expression is reached, is it possible that we have traversed through an
asynchronous gap?" and "does this mounted check definitely guard this
expression?" The intricacies of such tracking are quite different from the
requirements of most linter rules.
At a high level, the task of the lint rule can be broken down into three steps:
The first step just uses the linter's standard rule-registering mechanism. The latter steps are explained below.
It would be egregious to require a function with a reference to a BuildContext to have zero async gaps (await expressions) above the reference. Developers are allowed a safeguard: a "mounted check" which leads to a "mounted guard."
mounted property of a
BuildContext expression is read. It typically looks like context.mounted.In this step, we have a single expression with a reference to a BuildContext object, and we need to examine nodes which contain an async gap which could be crossed before the access to the reference. This means we are simulating, to a very limited extent, the runtime flow between an async gap and reference.
Note: We use child in the loop, rather than just reference, in order to make use of child's relationship to parent, when computing the "async gap" (see the next section).
Given the way we walk up the syntax tree, the async state between parent and child is the same state as between parent and reference: if there is a possible async gap between parent and child, then there is a possible async gap between parent and reference, and if there is a definite mounted guard between parent and child, then there is a definite mounted guard between parent and reference.
This is the most complex and delicate step. Given two nodes, a parent and child, we must calculate whether there is a possible async gap between the two (with no mounted guard between the async gap and the child), or a definite mounted guard between the two (without a possible async gap between the mounted guard and child), or no interesting async state between the two. This calculation is based on a few simple properties:
null). Otherwise, child follows
the YieldStatement, and any await expressions occurring in the
YieldStatement's expression result in AsyncState.asynchronous.AsyncState.asynchronous.context.mounted guards the
then-statement, but not the else-statement, and no statements that follow the
IfStatement. An IfStatement with a condition like !context.mounted and a
then-statement that definitely exits (e.g. with a return or a throw),
definitely guards the statements that follow it.