roleNoteProblems<V extends Object> function

List<String> roleNoteProblems<V extends Object>(
  1. Iterable<ContractResult> results,
  2. SocketRef<KeyedSocket<V>> socket, {
  3. required String textOf(
    1. V value
    ),
  4. Map<String, String> allowed = const {},
})

What the notes of roles in the keyed socket name of the modules behind the roles, in the apps of results: one line for each.

A note of a role is an entry of socket that the template of a role gives, from its contributions or from its render hook, such as the note of the router role in the guide for coding agents of an app. It tells what holds whichever modules provide the roles, so it names nothing of such a module. textOf gives the Markdown of a value of the socket.

The modules that a note is held against are the providers of every role in an app of results, not only of the role of the note, and the modules that they depend on. Of them, a note names:

  • no id and no package that one of them adds from pub.flutter-io.cn: neither as it is, nor with - for _, nor in camel case, also inside a longer name, so GoRouterState names go_router. This counts in the text and in code. A name whose words are all words of the ids and descriptions of the roles, such as a module settings next to a settings screen role, cannot be told from the role, so it counts only as it is, in code;
  • in code, no name that only their Dart files declare at the top level or as the prefix of an import;
  • in code, no name in upper camel case that no file of an app declares at its top level and that is only in the code of files which all import the same thing of theirs: a package that one of them adds from pub.flutter-io.cn, or a file that one of them imports and no module generates, as a tool writes it later. Such a name is taken for one of that package or file, such as GoRouter for one of go_router. A name that two files have with different packages, or one file without any, is one of Dart, of Flutter or of the app;
  • in inline code, no file or directory that only they generate.

What a role guarantees is free to every note: the symbols of its Role.interface with their named parameters, their getters and the types that they are declared with, the files of those symbols, and each name that the description of one of its rules gives as code, which is a name before a (, or one with an upper-case letter or a _ after its first character. A file or a directory at the root of the app that every app of results has is free too, such as pubspec.yaml or test/, and so are the names of dart:core and dart:async.

Inline code that is the path of a file or a directory of an app is read as that path only. Other code without a space, a quote or a parenthesis and with a / is a path too, and is not read for the names of code. < and > around lower-case words are a placeholder of a pattern, such as <feature>, and are not read.

The check cannot tell everything. It does not read the text for the names of code. It knows the code of the modules only from the files of the apps, so it misses what a package has and no file uses, and a name in lower case that a file only uses, such as a method of a package. It misses a single word of an id of several words, such as the brand of a package. In the other direction, a name of Flutter, or of a library of Dart other than those two, that only files with one package have is taken for a name of that package. allowed is for that case, and for a note that the caller cannot change: it has what a note may name after all, as the note gives it, each with the reason. An entry that excuses nothing gets a line too.

A note is held against the modules of the apps of results only, so a registry with other providers of the roles, each with an id, packages and files of its own, checks more.

test('the notes of the roles name nothing of their providers', () async {
  final results = await ContractHarness(ModuleRegistry(modules)).checkAll();
  expect(
    roleNoteProblems(
      results,
      AppEntryRole.agentSections,
      textOf: (note) => note.text,
      allowed: {'NavigatorObserver': 'A class of Flutter.'},
    ),
    isEmpty,
  );
});

Implementation

List<String> roleNoteProblems<V extends Object>(
  Iterable<ContractResult> results,
  SocketRef<KeyedSocket<V>> socket, {
  required String Function(V value) textOf,
  Map<String, String> allowed = const {},
}) {
  final apps = _Apps();
  // Each note once, with the case of the first app that has it.
  final notes = <(Role, String, String), String>{};
  for (final result in results) {
    final app = result.app;
    if (app == null) continue;
    apps.add(result);
    for (final (origin, heading, value) in app.entriesOf(socket)) {
      if (origin case RoleTemplateOrigin(:final role)) {
        notes.putIfAbsent(
          (role, heading, textOf(value)),
          () => result.contractCase.name,
        );
      }
    }
  }
  final lines = <String>[];
  final excused = <String>{};
  for (final MapEntry(key: (role, heading, text), value: name)
      in notes.entries) {
    for (final MapEntry(key: named, value: what)
        in apps.problemsOf(text).entries) {
      if (allowed.containsKey(named)) {
        excused.add(named);
      } else {
        lines.add(
          'The note of the $role under "$heading" names $what (in the app '
          'of $name).',
        );
      }
    }
  }
  for (final MapEntry(key: named, value: reason) in allowed.entries) {
    if (excused.contains(named)) continue;
    lines.add(
      '`allowed` has `$named` ("$reason"), which no note of a role names in '
      'a way that the check refuses. Remove the entry.',
    );
  }
  return lines;
}