Understanding the "exclude-public-api" Flag
Several DCM commands (such as check-unused-code
, check-unused-files
and check-parameters
) have a --exclude-public-api
flag to exclude a package's public API from analysis.
This guide will help you understand how this flag works and when it might be useful.
What Is Considered Public API
Let's start with understanding what is considered a package's public API.
At a minimum, a Dart package is a directory containing a pubspec.yaml
file, and usually has several other subfolders such as lib
(containing the source code), test
(containing test files), and others.
By convention (which is also enforced and used by various tools) any file placed under lib/src
is considered an implementation file and should only be used by other implementation files in the same package (you can still import such files in other packages, but you'll get no autocompletion suggestions and the analyzer will show a warning for importing src
files).
And any code under lib
(plus any code re-exported from lib/src
) but outside lib/src
is considered public.
my_package/
├── lib/
│ ├── src/
│ │ └── internal_code.dart
│ └── public_code.dart
└── pubspec.yaml
Default Behavior of Commands
What we found is that most Flutter developers don't follow this convention and put all their code under lib
(which is understandable as they build apps and don't publish/share their packages).
That's why, by default, DCM commands analyze all provided code and do not exclude any public code (even if changing it may introduce breaking changes).
For example,
dcm check-unused-code lib
outputs
lib/src/internal_code.dart:
⚠ unused class SomeClass
at lib/src/internal_code.dart:1:1
lib/public_code.dart:
⚠ unused class AnotherClass
at lib/public_code.dart:1:1
❌ total unused code (top-level declarations and class members) - 2
If you are still using DCM 1.22.1 or lower, the default behavior is different.
DCM commands work as if the --exclude-public-api
flag was passed, and to disable it you need to pass the --monorepo
flag (which was removed in DCM 1.23.0).
The Role of the "exclude-public-api" Flag
But what if you have publishable packages or packages where you rely on the lib/src
convention and want to avoid unexpected breaking changes in public APIs? This is where the --exclude-public-api
flag comes in handy!
When you use this flag, DCM commands start excluding public API code and, to reduce potential breaking changes even more, reuse the results of check-exports-completeness
to exclude any code imported by the publicly available code, since changing that code would also be a breaking change.
dcm check-unused-code lib --exclude-public-api
outputs
lib/src/internal_code.dart:
⚠ unused class SomeClass
at lib/src/internal_code.dart:1:1
❌ total unused code (top-level declarations and class members) - 1
Alternative Approach: Configuring "analysis_options.yaml"
But what if you have a monorepo with both your apps and publishable packages (or packages that rely on the lib/src
convention) and want to run DCM commands from the monorepo root?
my_monorepo/
├── publishable/
│ ├── lib/
│ │ ├── src/
│ │ │ └── internal_file.dart
│ │ └── public_file.dart
└── private/
├── lib/
│ └── public_file.dart
Passing this flag (or not passing it) will always give incorrect output for some of your packages.
This is where you can use the exclude-public-api
configuration option.
Adding exclude-public-api: true
to analysis_options.yaml
of publishable packages will enable this mode only for those packages, and calling dcm check-unused-code .
without the flag will give correct results.
For example,
dcm check-unused-code . # called from the monorepo root
with
dart_code_metrics:
exclude-public-api: true
outputs
publishable/lib/src/internal_file.dart: # <-- only internal code from "publishable"
⚠ unused class SomeClass
at publishable/lib/src/internal_file.dart:1:1
private/lib/public_file.dart: # <-- public code from "private"
⚠ unused class Another
at private/lib/public_file.dart:1:1
❌ total unused code (top-level declarations and class members) - 2