Dart_apitool updates

Software developmentFlutter
  |  

Dart_apitool got an update!

The recent weeks I was working a bit on dart_apitool.
The major topics were:

Missing export detection

The way Dart allows to define the public API of a package can lead to situations in which the consumer of the API is able to receive an instance of a certain Type but can’t refer to that type directly (e.g. to define a variable or a parameter of that Type).

Let’s imagine a very simple dart package:

- lib
    - some_package.dart
    - src 
        - some_interface.dart
        - some_interface_impl.dart
        - used_type.dart

some_package.dart

export 'src/some_interface.dart'
export 'src/some_interface_impl.dart'

some_interface.dart

#import 'used_type.dart'

abstract class SomeInterface {
    UsedType doSomethingAndReturnInstance();
}

some_interface_impl

#import 'some_interface.dart'
#import 'used_type.dart'

class SomeInterfaceImpl extends SomeInterface {
    @override
    UsedType doSomethingAndReturnInstance() {
        return UsedType();
    }
}

used_type.dart

class UsedType {
    //...
    void doSomething() {
        //...
    }
}

The consumer now can use that package by importing the main dart file, create and use SomeInterfaceImpl, even retrieve an instance of UsedType, interact with it but not name that type anywhere.

#import 'package:some_package/some_package.dart';

void consumer() {
    //works
    final si = SomeInterfaceImpl();
    // calling and getting instance works
    final ut = si.doSomethingAndReturnInstance();
    // interacting with UsedType works
    ut.doSomething();
    // naming the type doesn't work
    final UsedType ut2 = si.doSomethingAndReturnInstance();
}

This is because Dart hides access to UsedType as it is not part of the public API. It is still there and can still be interacted with, though.

This is most probably not what the author of that public API intended.

Dart_apitool did know almost everything that a tool needs to know In order to detect that issue:

  • it knows the public API (dart_apitool would include UsedType implicitly)
  • It knows which entry points each type has (although it propagated the entry points to all implicitly included types, which got changed now)
  • It is able to determine “required” types and therefore can use the same information (type flow direction) to derive potential issue candidates

So I decided to tweak dart_apitool so that it provides all the needed information to detect those cases and extended the extract command to check for that situation and print it to stdout on detection.
Additionally, there is a new --set-exit-on-missing-export argument that will fail the command on missing exports.

Option to be less strict on required interfaces

As you know dart_apitool tries to understand if an interface is passed into the library as this makes it more likely that the interface is implemented on the consumer side.
If so dart_apitool is more strict in terms of additions to the interface as this would break consumer code that implement the interface.

At BMW, we have situations where we have an internal package that has a very small set of consumer code that is all controlled by us. To not require too frequent major version bumps by adding stuff to a public interface dart_apitool now has the option to turn off the required interfaces special treatment.
If you pass --ignore-requiredness to the diff command then every interface will be treated the same meaning adding stuff to an interface won’t result in a breaking change.
We can do that as we control both sides and are able to do the integration (and making sure that the right minor version number is picked).

You should not use this for public packages as you can not control how your package is used and the last thing you want to do is to break the consumers code by a minor update.

Minor improvements

Besides the above, dart_apitool got some minor improvements:

  • @sealed classes (pre Dart 3) are now detected and not treated as required
  • entry point tracking got improved (in the context of detection of missing exports) so that implicit types in the public API don’t derive the entry point from the referring file.
  • Elements marked as @override are now ignored and not part of the extracted public API anymore.

That’s all for now. The next big step will be Dart 3 support