Skip to content

use_class_suffix

v1.0.0WarningFixConfigurableClass Naming

Flags a class that derives from a type you name but does not carry the suffix you require. A quick fix appends it.

When a class extends Bloc but is called CounterManager, a reader has to follow the inheritance chain to learn its role. A consistent suffix puts the role in the name, and makes the codebase greppable by layer.

This rule is in no preset and reports nothing until you add entries:. Every example below shows the configuration that produces it.

analysis_options.yaml
many_lints:
rules:
use_class_suffix:
entries:
- type: Bloc
package: bloc
suffix: Bloc

package: is optional. Omit it for a type you declare yourself — a local type has no package: URI to pin against. Add it when the name is ambiguous, so an unrelated Bloc from another dependency does not match.

A type matches whether it is reached by extends, implements, with, or an indirect ancestor. The configured base type is never reported against itself.

With entries: [{type: Bloc, package: bloc, suffix: Bloc}]:

import 'package:bloc/bloc.dart';
sealed class CounterEvent {}
class CounterManager extends Bloc<CounterEvent, int> { // LINT
CounterManager() : super(0);
}
import 'package:bloc/bloc.dart';
sealed class CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);
}

Omit package: — the type is declared in the code being analysed:

- type: Repository
suffix: Repository
abstract class Repository {}
// Don't
class UserData implements Repository {} // LINT
// Do
class UserRepository implements Repository {}

Repository itself is not reported: requiring the base type to be named RepositoryRepository is nonsense, and for a type owned by a dependency you could not act on it anyway.

entries:
- type: Bloc
package: bloc
suffix: Bloc
- type: Cubit
package: bloc
suffix: Cubit
- type: UseCase
suffix: UseCase
abstract class UseCase {}
// Don't
class PlaceOrder implements UseCase {} // LINT
// Do
class PlaceOrderUseCase implements UseCase {}

When a class matches several entries, the first one wins and it is reported once.

ignore_private: true skips classes whose name starts with _. A private class cannot be referenced outside its library, so the naming convention buys less there.

rules:
use_class_suffix:
ignore_private: true # rule-wide default
entries:
- type: Repository
suffix: Repository
- type: UseCase
suffix: UseCase
ignore_private: false # this one checks private classes too
abstract class Repository {}
abstract class UseCase {}
// Not reported — private, and this entry ignores private classes
class _CachedUsers implements Repository {}
// Reported — this entry overrides the rule-wide default
class _PlaceOrder implements UseCase {} // LINT
- type: Repository
suffix: Repository
abstract class Repository {}
abstract class CachingRepository implements Repository {}
// Don't — reaches Repository through CachingRepository
class UserStore extends CachingRepository {} // LINT
// Do
class UserCachingRepository extends CachingRepository {}

The fix appends the required suffix, and repairs a near-miss rather than stacking onto it — CounterBlok becomes CounterBloc, not CounterBlokBloc. A same-named unnamed constructor is renamed along with the class, so the result still compiles.

Option Type Default Description
entries list of maps [] The base types to track. With none, the rule reports nothing
ignore_private bool false Rule-wide default for skipping classes whose name starts with _

Each entry accepts:

Key Type Required Description
type string yes Name of the base type, e.g. Bloc
suffix string yes The suffix subtypes must end with
package string no Package declaring type. Omit to match a type of that name from any library, including your own package
ignore_private bool no Overrides the rule-wide ignore_private for this entry

Classes only. A mixin, enum or extension type is not checked, even when it implements a configured type. For those, reach for match_class_name_pattern.

A malformed entry is silent. An entry missing type or suffix is skipped, because a plugin cannot report problems against a YAML file. If an entry never fires, check both keys are present and non-empty.

Migration from use_bloc_suffix / use_cubit_suffix / use_notifier_suffix

Section titled “Migration from use_bloc_suffix / use_cubit_suffix / use_notifier_suffix”

Those three rules were replaced by this one. To restore their exact behaviour:

analysis_options.yaml
many_lints:
rules:
use_class_suffix:
entries:
- type: Bloc
package: bloc
suffix: Bloc
- type: Cubit
package: bloc
suffix: Cubit
- type: Notifier
package: riverpod
suffix: Notifier

To disable this rule:

many_lints.yaml
rules:
use_class_suffix: false

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