Skip to content

avoid_banned_imports

v1.0.0WarningConfigurableArchitecture

Flags imports of libraries you ban, optionally only inside the directories you name. This is architecture enforcement: it keeps a Clean Architecture domain layer free of Flutter, stops dart:io reaching code that also targets the web, and confines a legacy package to the module still using it.

Layering rules are the easiest architectural decision to make and the hardest to keep. A domain layer stays platform-independent only as long as nobody types import 'package:flutter/material.dart' — and code review catches that in the pull request it appears in, not in the fifty that follow.

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

analysis_options.yaml
many_lints:
rules:
avoid_banned_imports:
banned:
- deny: ['package:flutter/material.dart']
in: ['lib/domain/**']
message: 'The domain layer must not depend on Flutter.'

Write the URI exactly as it appears in the import line, not as a file path. in: takes globs relative to the package root; omit it to ban everywhere.

- deny: ['package:flutter/material.dart', 'package:flutter/widgets.dart']
in: ['lib/domain/**']
message: 'The domain layer must not depend on Flutter.'
// Don't — in lib/domain/cart.dart
import 'package:flutter/material.dart'; // LINT
Color badgeColor(int items) => items > 0 ? Colors.red : Colors.grey;
// Do — in lib/domain/cart.dart: return a value the UI layer maps to a Color
enum CartBadge { empty, filled }
CartBadge badgeFor(int items) =>
items > 0 ? CartBadge.filled : CartBadge.empty;

The same import inside lib/ui/** is untouched — only the directories named in in: are checked.

Keep dart:io out of code that also targets the web

Section titled “Keep dart:io out of code that also targets the web”
- deny: ['dart:io']
in: ['lib/shared/**']
message: 'Shared code also runs on the web; use an injected abstraction.'
// Don't — in lib/shared/config_loader.dart
import 'dart:io'; // LINT
Future<String> readConfig(File file) => file.readAsString();
// Do — in lib/shared/config_loader.dart
abstract class ConfigSource {
Future<String> read();
}
class ConfigLoader {
const ConfigLoader(this._source);
final ConfigSource _source;
Future<String> load() => _source.read();
}

deny_pattern is anchored to the whole URI, so package:legacy_.* matches every library in every package whose name starts with legacy_:

- deny_pattern: ['package:legacy_.*']
message: 'Legacy packages are being removed; see ADR-014.'
// Don't
import 'package:legacy_http/legacy_http.dart'; // LINT
// Do
import 'package:http/http.dart';

Ban the deep paths and leave the barrel, so consumers depend on the public API rather than internals:

- deny_pattern: ['package:design_system/src/.*']
message: 'Import package:design_system/design_system.dart instead.'
// Don't
import 'package:design_system/src/buttons/primary_button.dart'; // LINT
// Do
import 'package:design_system/design_system.dart';

Ban one library everywhere except its owner

Section titled “Ban one library everywhere except its owner”

in: scopes an entry; the rule-wide exclude: skips paths for the whole rule. They compose into “banned across the app, except in the module that owns it”:

analysis_options.yaml
many_lints:
rules:
avoid_banned_imports:
exclude: ['lib/infrastructure/analytics/**']
banned:
- deny: ['package:firebase_analytics/firebase_analytics.dart']
in: ['lib/**']
message: 'Log through AnalyticsService, not the vendor SDK.'
Option Type Default Description
banned list of entries [] The entries to enforce. With none, the rule reports nothing

Per entry:

Key Type Required Description
deny string or list one of deny / deny_pattern Import URIs banned by exact match
deny_pattern string or list one of deny / deny_pattern Regular expressions, anchored to the whole URI
in list of globs no Paths, relative to the package root, where the entry applies. Omit to apply everywhere
message string no A project-specific explanation appended to the diagnostic

The URI is matched as written. deny: ['package:flutter/material.dart'] does not also ban package:flutter/widgets.dart, and a relative import is matched as the relative path you typed. List each URI, or use deny_pattern.

Exact unless you ask otherwise. Banning async does not ban dart:async — deny never matches a substring. deny_pattern opts into matching by shape and anchors to the whole URI, so dart:.* catches every dart: library while legacy matches only a library named exactly legacy.

Transitive imports are invisible. A file importing a permitted library that itself imports a banned one is not reported; the rule reads the import lines in the file it is analysing.

A malformed entry is skipped silently. An entry denying nothing is dropped and an invalid regular expression costs you that pattern only — a plugin cannot report problems against a YAML file, so bad configuration degrades quietly rather than failing analysis.