Skip to content

Conversation

@PiotrRogulski
Copy link
Member

This PR introduces six new lints enforcing our new architecture guidelines.

(Also, as a cleanup, all created single-type checkers are moved to one location to avoid duplication.)

@PiotrRogulski PiotrRogulski requested a review from shilangyu as a code owner May 9, 2025 20:20
@github-actions github-actions bot added the p: leancode_lint Related to the leancode_lint package label May 9, 2025
@PiotrRogulski PiotrRogulski requested a review from mchudy as a code owner August 13, 2025 07:28
});
}

void addBloc(void Function(ClassDeclaration node, _BlocData data) listener) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add some documentation here. AddBloc is, if I am not wrong, limited to one file. So _BlocData data wouldn't be correct fully. So there is an assumption that the whole bloc with events, states, is in one file. It would be worth mentioning this.

data.eventElement,
data.presentationEventElement,
])
if (element != null) ...{element, ...element.subclasses},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (element != null) ...{element, ...element.subclasses},
...{?element, ...?element.subclasses},

We would have to bump min version of SDK to 3.8 :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has already been bumped 😉

import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:leancode_lint/helpers.dart';

class BlocConstConstructors extends DartLintRule {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about adding DartFix for this lint? Lint that will create a const constructor.
It could be problematic if Bloc has variables, but I think we have to only handle case where there are no parameters in Bloc's body. And in this case, we only have to create an empty const constructor


sealed class Test4Event {}

final class Test4EventFoo extends Test4Event {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test case where the state has only one basic class, without any extended classes?
Bloc with only one possible state.


### `bloc_const_constructors`

**DO** define unnamed const constructors for bloc state, event, and presentation event classes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto, it would be nice to highlight that this lint works only if bloc is in the same file as states, events etc


### `bloc_related_classes_equatable`

**DO** mix in `EquatableMixin` in bloc state, event, and presentation event classes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is obvious why it is better to use this mixin, but we could add some info about it here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reasoning from the issue can be mentioned or quoted: felangel/equatable#160

}
}

class AddMixin extends DartFix {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class AddMixin extends DartFix {
class _AddMixin extends DartFix {

Comment on lines +68 to +69
analysisError.offset + analysisError.length,
' with EquatableMixin',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this fix is not accurate, finite.
eg.

class A extends B {} // will add EquatableMixin before extends 

Additionally, it has to be before implements.


```dart
class MyState {
MyState(this.value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
MyState(this.value);
const MyState(this.value);

class AnotherPackageBloc extends Bloc<int, int>
with BlocPresentationMixin<int, int> {
AnotherPackageBloc() : super(0);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a case where we have more than one StateClass eg:

class Correct1Cubit extends Cubit<Correct1State>
    with BlocPresentationMixin<Correct1State, Correct1Event> {
  Correct1Cubit() : super(Correct1State());
}

selaed class Correct1State {}

final class Correct1Initial {}
final class Correct1Loading {}
final class Correct1Loaded {}

Copy link
Contributor

@Artur-Wisniewski Artur-Wisniewski Aug 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally I think:

final class Correct1Initial {}
final class Correct1Loading {}
final class Correct1Loaded {}

These cases should be legal.
We shouldn't be forced to add in child classes a prefix with the state at the end.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lint closely follows our current standards, which specify that the state of <Something>Cubit should be called <Something>State, with subclasses like <Something>State<Variant>

Copy link
Contributor

@Artur-Wisniewski Artur-Wisniewski Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok makes sense that it follows leancode standards but It could be misleading for external people because in bloc documentation examples uses this format:

selaed class ExampleState {}

final class ExampleInitial {}
final class ExampleLoading {}
final class ExampleLoaded {}

Maybe we could add some configuration to allow this?

return null;
}

final stateElement = stateType.element3;
Copy link
Contributor

@Artur-Wisniewski Artur-Wisniewski Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image What happened here. Why `Element2? get element3` 😆

final library1 = element1.library2?.uri;
final library2 = element2.library2?.uri;

return library1 != null && library2 != null && library1 == library2;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return library1 != null && library2 != null && library1 == library2;
return library1 != null && library1 == library2;

Checking if library2 != null is redundant

Comment on lines +43 to +47
bool _isCubitClass(ClassDeclaration node) =>
switch (node.declaredFragment?.element.thisType) {
final type? => TypeCheckers.cubit.isSuperTypeOf(type),
_ => false,
};
Copy link
Contributor

@Artur-Wisniewski Artur-Wisniewski Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be extracted to helpers

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should rename the file to prefer_equatable_mixin.dart

import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:leancode_lint/common_type_checkers.dart';

class UseEquatableMixin extends DartLintRule {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename class to match lint name

Copy link
Contributor

@Artur-Wisniewski Artur-Wisniewski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow great lints! I've added some comments, but I think you've done a decent job here. Please address comments above :)


### `prefer_equatable_mixin`

**DO** mix in `EquatableMixin` instead of extending `Equatable`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So disappointing that a lint is needed for that. Equatable should have been binned ages ago


### `bloc_related_classes_equatable`

**DO** mix in `EquatableMixin` in bloc state, event, and presentation event classes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reasoning from the issue can be mentioned or quoted: felangel/equatable#160

Comment on lines +267 to +269
if (eventElement is! InterfaceElement2?) {
return null;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There doesn't seem to be a dart lint for unrelated type checks when using is, but there is one for pattern matching. As stupid as this looks,

Suggested change
if (eventElement is! InterfaceElement2?) {
return null;
}
if (stateElement case InterfaceElement2()) {
} else {
return null;
}

at least it will report a warning if InterfaceElement2 is an unrelated type of stateElement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

p: leancode_lint Related to the leancode_lint package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants