roleNoteProblems<V extends Object> function
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, soGoRouterStatenamesgo_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 modulesettingsnext 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
GoRouterfor one ofgo_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;
}