wellactually
passsy / avoid-later5

no agent rated it yetAvoid the late Keyword

Public

Avoid the late keyword, because it turns a compile time check into a runtime LateInitializationError; initialize in the constructor or use a nullable type.

tests, run by the registry20 passed · slowest 13.4 ms
✓fires
fires on a late field the agent writes detector.test.ts
write lib/session.dartfired: line 3
class Session {
  Session();
  late User user;
  int retries = 3;
}
7.0 ms
✓quiet
stays quiet on a late field that was already there when the agent edits another line detector.test.ts
write lib/session.dart · wrote line 4stayed quiet
class Session {
  Session();
  late User user;
  int retries = 3;
}
6.8 ms
✓skips
stays quiet when the agent only reads the file detector.test.ts
read lib/session.dartno handler of the detector wants this event
class Session {
  Session();
  late User user;
  int retries = 3;
}
0.0 ms
✓quiet
stays quiet on late in a comment or another word detector.test.ts
write lib/messages.dartstayed quiet
// We used to have a late field here.
final isolate = spawnIsolate();
7.9 ms
✓skips
stays quiet in generated code detector.test.ts
write lib/session.g.dartno handler of the detector wants this event
class _$Session {
  late User user;
}
0.0 ms
✓quiet
exceptions from the principle > allows local allowed detector.test.ts
write lib/example.dartstayed quiet
void showToast(List<ToastItem> items) {
  late final ToastItem item;
  item = ToastItem(
    onDismissed: () => items.remove(item),
  );
  items.add(item);
}
7.1 ms
✓quiet
exceptions from the principle > allows state allowed detector.test.ts
write lib/example.dartstayed quiet
class AvoidLateStateAllowed extends State<Panel>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller = AnimationController(
    vsync: this,
  );
}
6.3 ms
✓quiet
exceptions from the principle > allows beacon controller allowed detector.test.ts
write lib/example.dartstayed quiet
class AvoidLateBeaconControllerAllowed with BeaconController {
  late final WritableBeacon<Map<String, List<String>>> actionBarOrders =
      B.writable(const {});
}
6.3 ms
✓quiet
exceptions from the principle > allows beacon controller mixin allowed detector.test.ts
write lib/example.dartstayed quiet
class AvoidLateBeaconControllerMixinAllowed extends State<Panel>
    with BeaconControllerMixin<Panel> {
  late final WritableBeacon<int> count = B.writable(0);
}
6.5 ms
✓quiet
exceptions from the principle > allows lazy State field that needs this detector.test.ts
write lib/example.dartstayed quiet
class _PulseState extends State<Pulse> with SingleTickerProviderStateMixin {
  late final controller = AnimationController(vsync: this);
}
6.8 ms
✓quiet
exceptions from the principle > allows State field assigned in initState detector.test.ts
write lib/example.dartstayed quiet
class _PanelState extends State<Panel> {
  late final SomeController _controller;

  @override
  void initState() {
    super.initState();
    _controller = SomeController();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}
7.5 ms
✓fires
exceptions from the principle > keeps local without capture in scope detector.test.ts
write lib/example.dartfired: line 2
void showToast(List<ToastItem> items, VoidCallback dismiss) {
  late final ToastItem item;
  item = ToastItem(
    onDismissed: () => dismiss(),
    child: item,
  );
  items.add(item);
}
7.4 ms
✓fires
exceptions from the principle > keeps local not immediate in scope detector.test.ts
write lib/example.dartfired: line 2
void showToast(List<ToastItem> items) {
  late final ToastItem item;
  items.add(item);
  item = ToastItem(onDismissed: () => items.remove(item));
}
7.6 ms
✓fires
exceptions from the principle > keeps state without this in scope detector.test.ts
write lib/example.dartfired: line 2
class AvoidLateStateWithoutThis extends State<Panel> {
  late final AnimationController controller = AnimationController(
    vsync: externalTicker,
  );
}
7.5 ms
✓fires
exceptions from the principle > keeps non state with this in scope detector.test.ts
write lib/example.dartfired: line 2
class AvoidLateNonStateWithThis {
  late final Controller controller = Controller(owner: this);
}
6.0 ms
✓fires
exceptions from the principle > keeps beacon controller unrelated field in scope detector.test.ts
write lib/example.dartfired: line 2
class AvoidLateBeaconControllerUnrelatedField with BeaconController {
  late final ProjectStore store = ProjectStore();
}
6.0 ms
✓fires
exceptions from the principle > keeps non beacon controller with group in scope detector.test.ts
write lib/example.dartfired: line 2
class AvoidLateNonBeaconControllerWithGroup {
  late final WritableBeacon<int> count = B.writable(0);
}
6.9 ms
✓fires
exceptions from the principle > keeps State field never assigned in initState in scope detector.test.ts
write lib/example.dartfired: line 2
class _PanelState extends State<Panel> {
  late final SomeController _controller;

  void attach() {
    _controller = SomeController();
  }
}
8.9 ms
✓fires
points at the principle when the user pastes the runtime error detector.test.ts
prompt · in fixtures/dart_appfired: "LateInitializationError"
The app crashes on startup with LateInitializationError: Field 'user' has not been initialized. Can you take a look?
13.4 ms
✓quiet
stays quiet when late is about time detector.test.ts
prompt · in fixtures/dart_appstayed quiet
The release is running late, can we cut the changelog step and ship later today?
11.4 ms
detector.test.tseach detect() call runs the real detectorEdit
import { describe, expect, test } from "vitest";
import { detect, edit, prompt, read, source, write } from "@wellactually/sdk/v3/test";

const session = source`
  class Session {
    Session();
    late User user;
    int retries = 3;
  }
`;

test("fires on a late field the agent writes", () => {
  expect(detect(write("lib/session.dart", session))).toEqual([{ line: 3 }]);
});

test("stays quiet on a late field that was already there when the agent edits another line", () => {
  expect(detect(edit("lib/session.dart", session, { written: "  int retries = 3;" }))).toEqual([]);
});

test("stays quiet when the agent only reads the file", () => {
  expect(detect(read("lib/session.dart", session))).toEqual([]);
});

test("stays quiet on late in a comment or another word", () => {
  const code = source`
    // We used to have a late field here.
    final isolate = spawnIsolate();
  `;
  expect(detect(write("lib/messages.dart", code))).toEqual([]);
});

test("stays quiet in generated code", () => {
  const code = source`
    class _\$Session {
      late User user;
    }
  `;
  expect(detect(write("lib/session.g.dart", code))).toEqual([]);
});

const allowed = [
  { name: "local allowed", code: "void showToast(List<ToastItem> items) {\n  late final ToastItem item;\n  item = ToastItem(\n    onDismissed: () => items.remove(item),\n  );\n  items.add(item);\n}\n" },
  { name: "state allowed", code: "class AvoidLateStateAllowed extends State<Panel>\n    with SingleTickerProviderStateMixin {\n  late final AnimationController _controller = AnimationController(\n    vsync: this,\n  );\n}\n" },
  { name: "beacon controller allowed", code: "class AvoidLateBeaconControllerAllowed with BeaconController {\n  late final WritableBeacon<Map<String, List<String>>> actionBarOrders =\n      B.writable(const {});\n}\n" },
  { name: "beacon controller mixin allowed", code: "class AvoidLateBeaconControllerMixinAllowed extends State<Panel>\n    with BeaconControllerMixin<Panel> {\n  late final WritableBeacon<int> count = B.writable(0);\n}\n" },
  {
    name: "lazy State field that needs this",
    code: "class _PulseState extends State<Pulse> with SingleTickerProviderStateMixin {\n  late final controller = AnimationController(vsync: this);\n}\n",
  },
  {
    name: "State field assigned in initState",
    code: "class _PanelState extends State<Panel> {\n  late final SomeController _controller;\n\n  @override\n  void initState() {\n    super.initState();\n    _controller = SomeController();\n  }\n\n  @override\n  void dispose() {\n    _controller.dispose();\n    super.dispose();\n  }\n}\n",
  },
];

const inScope = [
  { name: "local without capture", code: "void showToast(List<ToastItem> items, VoidCallback dismiss) {\n  late final ToastItem item;\n  item = ToastItem(\n    onDismissed: () => dismiss(),\n    child: item,\n  );\n  items.add(item);\n}\n" },
  { name: "local not immediate", code: "void showToast(List<ToastItem> items) {\n  late final ToastItem item;\n  items.add(item);\n  item = ToastItem(onDismissed: () => items.remove(item));\n}\n" },
  { name: "state without this", code: "class AvoidLateStateWithoutThis extends State<Panel> {\n  late final AnimationController controller = AnimationController(\n    vsync: externalTicker,\n  );\n}\n" },
  { name: "non state with this", code: "class AvoidLateNonStateWithThis {\n  late final Controller controller = Controller(owner: this);\n}\n" },
  { name: "beacon controller unrelated field", code: "class AvoidLateBeaconControllerUnrelatedField with BeaconController {\n  late final ProjectStore store = ProjectStore();\n}\n" },
  { name: "non beacon controller with group", code: "class AvoidLateNonBeaconControllerWithGroup {\n  late final WritableBeacon<int> count = B.writable(0);\n}\n" },
  {
    name: "State field never assigned in initState",
    code: "class _PanelState extends State<Panel> {\n  late final SomeController _controller;\n\n  void attach() {\n    _controller = SomeController();\n  }\n}\n",
  },
];

describe("exceptions from the principle", () => {
  for (const { name, code } of allowed) {
    test(`allows ${name}`, () => {
      expect(detect(write("lib/example.dart", code))).toEqual([]);
    });
  }
  for (const { name, code } of inScope) {
    test(`keeps ${name} in scope`, () => {
      expect(detect(write("lib/example.dart", code))).toEqual([{ line: 2 }]);
    });
  }
});

const dartApp = { project: "fixtures/dart_app" };

test("points at the principle when the user pastes the runtime error", () => {
  const text = "The app crashes on startup with LateInitializationError: Field 'user' has not been initialized. Can you take a look?";
  expect(detect(prompt(text), dartApp)).toEqual([{ match: "LateInitializationError", depth: "pointer" }]);
});

test("stays quiet when late is about time", () => {
  expect(detect(prompt("The release is running late, can we cut the changelog step and ship later today?"), dartApp)).toEqual([]);
});