Skip to main content

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
info

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

publishable/analysis_options.yaml
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