A comprehensive Dart analyzer plugin that provides powerful annotations and static analysis rules for them.
@Throws: Declare the exceptions that a function can throw, enabling better documentation and static analysis of error handling.@IgnoreThrows/@ignoreThrows: Suppresshandle_throwing_invocations(and its test-directory companion) for invocations inside the annotated declaration. See Quick Fixes & Assists below.
handle_throwing_invocations: Ensures that any function that calls a function annotated with@Throwseither catches the declared exceptions or also declares them with@Throws.handle_throwing_invocations_in_tests: The same rule, reported under its own diagnostic code for code undertest/,integration_test/,test_driver/,testing/,tool/, andbenchmark/directories, so it can be toggled independently of the main rule. See Configuration.
When handle_throwing_invocations reports an unhandled invocation, your IDE
(IntelliJ/Android Studio, VS Code — anything speaking to the Dart Analysis
Server) offers these quick fixes:
- Wrap in 'try' with an 'on' clause per declared exception
- Wrap in generic 'try-catch'
- Add missing 'on' clauses to the enclosing 'try' — when the call is already inside a try that doesn't cover the declared types
- Add '@Throws' to the enclosing function — propagate instead of handle;
merges into an existing
@Throwsset - Suppress with '@ignoreThrows' — inserts a bare
@ignoreThrowsannotation on the enclosing function/method/constructor/top-level variable/field declaration (adding thehyper_lintsimport if needed), silencing the diagnostic by suppression instead of handling or propagating it
Annotate a function, method, getter, setter, field, top-level variable, or
constructor to suppress handle_throwing_invocations for invocations inside
it:
@Throws({FormatException})
void parseData(String input) { /* may throw FormatException */ }
@ignoreThrows // bare form: suppresses every declared exception type
void callerThatAcceptsAnyRisk() {
parseData('...'); // not flagged
}
@IgnoreThrows({FormatException}) // typed form: only these types
void callerThatAcceptsFormatExceptionOnly() {
parseData('...'); // not flagged: FormatException is covered
}The typed set only suppresses invocations whose entire declared @Throws
set is covered by it — a call declaring a type outside the set still lints.
ignoreThrows is shorthand for the bare IgnoreThrows() constructor.
- The rule also flags bare (unqualified) getter reads — e.g. a top-level
or local
@Throwsgetter read as plainriskyValue, not justobj.riskyValue— and compound-assignment reads, e.g.riskyValue += 1(a plainriskyValue = 1assignment doesn't read the getter, so it isn't flagged). .ignore()andunawaited(...)(matched by name, so re-exports work too) on a flaggedFuture-returning call are treated as handled, including through a.then()/.whenComplete()/.timeout()chain, e.g.unawaited(risky().then((_) {}));.
Two assists are available on any try statement (no diagnostic needed):
- Add 'on' clause — inserts a template
on Exception catch (e)clause - Narrow 'catch' to declared exception types — when a broad
catchswallows specific@Throwstypes thrown inside the try body, inserts specificonclauses above it
Fixes are async- and scope-aware:
awaitinsertion — when the flagged call returns aFutureand the enclosing function body isasync, the try-catch fixes insertawaitso the handler actually catches. In a sync body, an un-awaited async call throws after the try/catch has already returned, so no wrap fix orAdd missing 'on' clausescan ever silence the diagnostic for it — those fixes aren't offered for un-awaited async calls in sync bodies (await it, or see the SDK'sunawaited_futureslint);Add '@Throws'remains available since it silences by propagation instead of catching.- Declaration splitting — wrapping
final x = risky();whenxis used later splits the declaration out of the try as a nullable variable (int? x;), keeping later code in scope. Later uses may need!at typed use sites; the fix does not rewrite them. - Apply in file — the two wrap fixes (
Wrap in 'try' with 'on' clausesandWrap in generic 'try-catch') offer an "everywhere in file" variant in the IDE. (dart fixon the command line cannot apply plugin fixes yet; see dart-lang/sdk#53402.) - Narrowing is nesting-aware — the narrow-catch assist ignores exception types already handled by nested try statements.
- Type matching uses real subtype checks; only
dart:core'sObject/Exception/Erroract as catch-alls.
After upgrading the plugin, restart the Dart Analysis Server (IntelliJ: Dart Analysis tool window → restart icon) to pick up the fixes.
Requires Dart 3.11+ (Flutter with Dart 3.11+).
Add this package as a dependency:
dependencies:
hyper_lints: ^1.1.0You can configure it in your analysis_options.yaml. Both rules are
opt-in: handle_throwing_invocations and its companion
handle_throwing_invocations_in_tests are each OFF by default and only take
effect once explicitly listed as true in the diagnostics: map below —
listing one does not enable the other.
plugins:
hyper_lints:
version: ^1.1.0
diagnostics:
handle_throwing_invocations: true
# Set to `false` (or omit this line entirely) to silence `test/`,
# `tool/`, `benchmark/`, and `integration_test/` code instead of
# flagging it.
handle_throwing_invocations_in_tests: truehandle_throwing_invocations_in_tests reports the same problem under its
own diagnostic code for code under test/, integration_test/,
test_driver/, testing/, tool/, and benchmark/ — it has its own on/off
switch, so you can enable the rule in your main code while disabling it for
tests (or vice versa):
diagnostics:
handle_throwing_invocations: true
handle_throwing_invocations_in_tests: falseMigrating from an earlier version: if your existing config lists only
handle_throwing_invocations: true, it will keep flagging lib/ code but
will no longer flag test/, tool/, benchmark/, or
integration_test/ code after upgrading, since
handle_throwing_invocations_in_tests is a separate, independently opt-in
rule rather than something the main rule's true also implies. Add
handle_throwing_invocations_in_tests: true to your config to keep flagging
that code too.
@Throws({CustomException})
void riskyFunction() { /* ... */ }
// ✅ Specific exception type
try {
riskyFunction();
} on CustomException catch (e) {
// handle
}
// ✅ General Exception catch
try {
riskyFunction();
} on Exception catch (e) {
// handle
}
// ✅ Catch-all
try {
riskyFunction();
} catch (e) {
// handle
}
// ✅ Rethrowing with @Throws
@Throws({CustomException})
void callerFunction() {
riskyFunction(); // OK because caller also declares @Throws
}
// ❌ Not declaring @Throws in caller
void anotherCallerFunction() {
riskyFunction(); // Warning: callerFunction should declare @Throws
}
// ❌ Wrong exception type caught
try {
riskyFunction();
} on StateError catch (e) {
// This doesn't catch CustomException!
// Warning: Unhandled exception from invocation annotated with @Throws
}
// ❌ Rethrowing catch clause
try {
riskyFunction();
} on CustomException {
rethrow; // The exception still escapes (even after logging first)!
// Warning: Unhandled exception from invocation annotated with @Throws
// Catch it in an outer try, or declare @Throws on the enclosing function.
}@Throws({CustomException})
Future<void> riskyAsyncFunction() async { /* ... */ }
// ✅ Awaited call inside try-catch
try {
await riskyAsyncFunction();
} catch (e) {
// handle
}
// ✅ Using .catchError()
riskyAsyncFunction().catchError((e) {
// handle
});
// ✅ Using .then() with onError
riskyAsyncFunction().then((_) {
// success
}, onError: (e) {
// handle
});
// ✅ Chained .then().catchError()
riskyAsyncFunction()
.then((_) => print('success'))
.catchError((e) => print('error'));
// ❌ Non-awaited call - try-catch won't catch async exceptions!
try {
riskyAsyncFunction(); // Warning: async call not awaited
} catch (e) {
// This won't catch the exception!
}
// ❌ Unhandled async call
riskyAsyncFunction(); // Warning: Unhandled exceptionContributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under the MIT License - see the LICENSE file for details.