prefer_abstract_final_static_class
v0.3.0 Warning Fix Code Organization
Flags a class that holds only static members but is not declared abstract final.
Without those modifiers the class can be instantiated and subclassed. AppColors() compiles and yields a useless empty object; class MyColors extends AppColors compiles and inherits nothing. Marking it abstract final makes both a compile error and states in the declaration that this is a namespace, not a type.
A quick fix adds the modifiers.
This rule is in the opinionated preset, so it is on with preset: opinionated or preset: pedantic.
See also: Dart language - Abstract classes | Dart language - Final classes
class AppSpacing { static const small = 4.0; static const medium = 8.0; static const large = 16.0;}abstract final class AppSpacing { static const small = 4.0; static const medium = 8.0; static const large = 16.0;}Examples
Section titled “Examples”A utility namespace is the same case
Section titled “A utility namespace is the same case”Static methods count exactly as static fields do:
// Don'tclass StringUtils { static String slugify(String input) => input.toLowerCase();
static String truncate(String input, int max) => input.length <= max ? input : input.substring(0, max);}// Doabstract final class StringUtils { static String slugify(String input) => input.toLowerCase();
static String truncate(String input, int max) => input.length <= max ? input : input.substring(0, max);}A single instance member exempts the class
Section titled “A single instance member exempts the class”The rule fires only when every member is static. One instance field or method means the class is a real type and is left alone:
// Accepted — `name` is an instance fieldclass Environment { const Environment(this.name);
final String name;
static const production = Environment('prod'); static const staging = Environment('staging');}The older private-constructor idiom is still reported
Section titled “The older private-constructor idiom is still reported”ClassName._() guards against instantiation, but it does not block subclassing and it costs a line. The rule reports it and the quick fix deletes the constructor while adding the modifiers:
// Don'tclass ApiRoutes { ApiRoutes._();
static const login = '/auth/login'; static const logout = '/auth/logout';}// Doabstract final class ApiRoutes { static const login = '/auth/login'; static const logout = '/auth/logout';}Only a trivial private constructor is treated this way — parameterless, no const, no factory, no initializers, no redirect and an empty body. Anything richer is doing real work, so the class is left alone.
Known limitations
Section titled “Known limitations”Any non-trivial constructor exempts the class. A public one, or a private one taking parameters or running a body, means the class is meant to be constructed, so the rule steps back.
sealed, base, interface and mixin classes are skipped. Each already carries a decision about extension that abstract final would contradict.
Only classes are checked. A mixin, extension or enum holding static members is a different construct and is never reported.
An empty class is never reported — and neither is one holding only a private constructor, since there is no static member to namespace.
Configuration
Section titled “Configuration”To turn it off:
rules: prefer_abstract_final_static_class: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Related rules
Section titled “Related rules”arguments_ordering— Keep named arguments in a configured order.avoid_duplicate_mixins— Flag a mixin applied twice in onewithclause.avoid_generics_shadowing— Avoid generic type parameters that shadow top-level declarations.avoid_unnecessary_constructor— Remove a constructor identical to the default one.