An independent Gemini 3.8 Flash audit of this repo found real problems. Each was reproduced before being fixed. Confirmed defects: - tool/adjudicate.py crashed with KeyError once R001 started grouping variants under 'variants' instead of 'variant'. Fixing that exposed a second bug of my own: it then divided verified VARIANTS by FINDINGS and reported 216% precision. Counts are now coherent (findings vs variant-level checks) and a clean repo reports 0/0 rather than a fake 100%. - GuideCommand called _readPolicy but never registered --policy, so exited 64 despite the README documenting it. - loading-flag-not-reset treated a constructor-invoked method as inheriting the loading flag without checking flag.defaultsTrue, so a default-FALSE flag was reported as in flight. - Finding.fingerprint embedded the line number for rules whose only identity was , so moving code broke baseline entries. is now a discriminating key; only loading-not-reset, which can emit two findings from one method, carries an explicit catchLine. - suggest grouped unrelated provider-read-in-build findings by file; the group key, label and target list now use valueType/call. Recall defect found while checking the auditor's own claim: UI variant mentions were collected project-wide by unqualified name, so ExportSuccess - which appears in FOUR unions on nuwayze - was cross-credited. Mentions are now scoped per union to files that also name the state type or its owner, which surfaced ArtistStoreManagementState[ExportSuccess] as a real finding. Five negative controls could not fail (an event that is never handled can never be reported as unhandled; a union with no render sites can never be reported as rendered-not-emitted). Added fixture cases that make each one bite: HandledTap (handled AND dispatched), PatternState render sites, GuardedCubit (guarded loading path), QuietCubit._warmUp (constructor-invoked await on a default-false flag). Root cause of a recurring mystery: this repo EXCLUDED test/fixtures/** from analysis, which made any analyzer running in the repo context parse the fixture without resolving its imports and report phantom undefined-class errors. I had dismissed these as stale cache several times; they were my own exclusion. The exclude is gone and the fixture governs itself. A new test asserts the fixture has zero analyzer errors, not merely that it resolves, so IR distortion cannot hide. 84 tests (was 81).
157 lines
3.6 KiB
Dart
157 lines
3.6 KiB
Dart
import 'bloc.dart';
|
|
import 'notifier.dart';
|
|
|
|
/// BAD: `bumpWithoutNotifying` mutates state and never notifies.
|
|
class StaleNotifier extends ChangeNotifier {
|
|
int _count = 0;
|
|
|
|
int get count => _count;
|
|
|
|
void bumpWithoutNotifying() {
|
|
_count = _count + 1;
|
|
}
|
|
|
|
/// GOOD: notifies directly.
|
|
void bump() {
|
|
_count = _count + 1;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// GOOD: mutates, but a notifying method calls it, so the caller announces
|
|
/// the change.
|
|
void setQuiet(int next) {
|
|
_count = next;
|
|
}
|
|
|
|
void setAndAnnounce(int next) {
|
|
setQuiet(next);
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
/// BAD: consumed by a Consumer but has no notifying path at all, so that
|
|
/// Consumer can never rebuild.
|
|
class InertNotifier extends ChangeNotifier {
|
|
int _value = 0;
|
|
|
|
int get value => _value;
|
|
|
|
void setValue(int next) {
|
|
_value = next;
|
|
}
|
|
}
|
|
|
|
/// GOOD: consumed and it notifies.
|
|
class LiveNotifier extends ChangeNotifier {
|
|
int _value = 0;
|
|
|
|
int get value => _value;
|
|
|
|
void setValue(int next) {
|
|
_value = next;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
/// Consumed by the UI, but `unusedFlag` is read nowhere: dead state the user can
|
|
/// never see, while `value` is displayed.
|
|
class PartiallyReadNotifier extends ChangeNotifier {
|
|
int _value = 0;
|
|
bool _unusedFlag = false;
|
|
|
|
int get value => _value;
|
|
|
|
bool get unusedFlag => _unusedFlag;
|
|
|
|
void setValue(int next) {
|
|
_value = next;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
/// A union whose variants are matched ONLY through typed variable patterns
|
|
/// (`case PatternA value:`), which an object-pattern-only matcher misses.
|
|
abstract class PatternState {
|
|
const PatternState();
|
|
}
|
|
|
|
class PatternA extends PatternState {
|
|
const PatternA();
|
|
}
|
|
|
|
class PatternB extends PatternState {
|
|
const PatternB();
|
|
}
|
|
|
|
class PatternEvent {}
|
|
|
|
class PatternBloc extends Bloc<PatternEvent, PatternState> {
|
|
PatternBloc() : super(const PatternA());
|
|
|
|
void go() => emit(const PatternB());
|
|
}
|
|
|
|
/// A loading variant emitted on a path whose catch DOES resolve it. The negative
|
|
/// control for `loading-not-reset`: reporting every loading-emit-then-await would
|
|
/// flag this cubit, and it must stay silent.
|
|
abstract class GuardedState {
|
|
const GuardedState();
|
|
}
|
|
|
|
class GuardedLoading extends GuardedState {
|
|
const GuardedLoading();
|
|
}
|
|
|
|
class GuardedReady extends GuardedState {
|
|
const GuardedReady();
|
|
}
|
|
|
|
class GuardedCubit extends Bloc<PatternEvent, GuardedState> {
|
|
GuardedCubit() : super(const GuardedReady());
|
|
|
|
Future<void> load() async {
|
|
emit(const GuardedLoading());
|
|
try {
|
|
await Future<void>.delayed(Duration.zero);
|
|
emit(const GuardedReady());
|
|
} catch (error) {
|
|
emit(const GuardedReady());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A flag that defaults to FALSE, awaited from the constructor. Nothing is in
|
|
/// flight, so `loading-flag-not-reset` must stay silent - the negative control
|
|
/// for that rule, and the regression guard for the missing `defaultsTrue` check.
|
|
class QuietState {
|
|
const QuietState({this.loading = false, this.saved = false});
|
|
|
|
final bool loading;
|
|
final bool saved;
|
|
|
|
QuietState copyWith({bool? loading, bool? saved}) => QuietState(
|
|
loading: loading ?? this.loading,
|
|
saved: saved ?? this.saved,
|
|
);
|
|
}
|
|
|
|
class QuietCubit extends Bloc<PatternEvent, QuietState> {
|
|
QuietCubit() : super(const QuietState()) {
|
|
_warmUp();
|
|
}
|
|
|
|
Future<void> _warmUp() async {
|
|
await Future<void>.delayed(Duration.zero);
|
|
}
|
|
|
|
/// Sets the flag in flight and resolves it on every path.
|
|
Future<void> save() async {
|
|
emit(state.copyWith(loading: true));
|
|
try {
|
|
await Future<void>.delayed(Duration.zero);
|
|
emit(state.copyWith(loading: false, saved: true));
|
|
} catch (error) {
|
|
emit(state.copyWith(loading: false));
|
|
}
|
|
}
|
|
}
|