Skip to content

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;
}

Static methods count exactly as static fields do:

// Don't
class StringUtils {
static String slugify(String input) => input.toLowerCase();
static String truncate(String input, int max) =>
input.length <= max ? input : input.substring(0, max);
}
// Do
abstract 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 field
class 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't
class ApiRoutes {
ApiRoutes._();
static const login = '/auth/login';
static const logout = '/auth/logout';
}
// Do
abstract 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.

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.

To turn it off:

many_lints.yaml
rules:
prefer_abstract_final_static_class: false

To keep the rule on but skip certain paths, use per-rule exclude.