Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved avoid_late_keyword to support ignoring the subtype of the node type (#157) #158

Merged
merged 7 commits into from
Apr 18, 2024
11 changes: 3 additions & 8 deletions lib/src/lints/avoid_late_keyword/avoid_late_keyword_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,11 @@ class AvoidLateKeywordRule extends SolidLintRule<AvoidLateKeywordParameters> {
}

bool _hasIgnoredType(VariableDeclaration node) {
final ignoredTypes = config.parameters.ignoredTypes.toSet();
if (ignoredTypes.isEmpty) return false;

yurii-prykhodko-solid marked this conversation as resolved.
Show resolved Hide resolved
final variableType = node.declaredElement?.type;
if (variableType == null) return false;

final checkedTypes = [variableType, ...variableType.supertypes]
.map((t) => t.getDisplayString(withNullability: false))
.toSet();

return checkedTypes.intersection(ignoredTypes).isNotEmpty;
return variableType.hasIgnoredType(
ignoredTypes: config.parameters.ignoredTypes.toSet(),
);
}
}
89 changes: 89 additions & 0 deletions lib/src/utils/types_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,95 @@ extension Subtypes on DartType {
final element = this.element;
return element is InterfaceElement ? element.allSupertypes : [];
}

/// Formats the type string based on nullability and presence of generics.
String getTypeString({
required bool withGenerics,
required bool withNullability,
}) {
final displayString = getDisplayString(withNullability: withNullability);

return withGenerics ? displayString : displayString.replaceGenericString();
}

/// Checks if a variable type is among the ignored types.
bool hasIgnoredType({required Set<String> ignoredTypes}) {
if (ignoredTypes.isEmpty) return false;

final checkedTypes = [this, ...supertypes];

ignoredTypes = getIntersectionTypesFor(ignoredTypes: ignoredTypes);
Yarl745 marked this conversation as resolved.
Show resolved Hide resolved
if (ignoredTypes.isEmpty) return false;

final ignoredTypeRegexes =
ignoredTypes.map((t) => t.getGeneralTypeRegex()).toSet();

for (final type in checkedTypes) {
final typeString =
type.getTypeString(withGenerics: true, withNullability: false);

for (final regex in ignoredTypeRegexes) {
if (typeString.contains(regex)) return true;
}
}

return false;
}

/// Returns a set of intersection types between the current types and
/// ignored types.
Set<String> getIntersectionTypesFor({required Set<String> ignoredTypes}) {
final checkedTypes = [this, ...supertypes];

final uniqueIgnoredTypeStrings = ignoredTypes
.map(
(s) => s.replaceGenericString(),
)
.toSet();

final uniqueCheckedTypeStrings = checkedTypes
.map(
(t) => t.getTypeString(withGenerics: false, withNullability: false),
)
.toSet();

final intersectionUniqueTypes =
uniqueCheckedTypeStrings.intersection(uniqueIgnoredTypeStrings);

// Filters and returns the set of ignored types that match
// any intersection types.
return ignoredTypes.where(
(ignoredType) {
return intersectionUniqueTypes.firstWhereOrNull(
(uniqueType) => ignoredType.contains(uniqueType),
) !=
null;
yurii-prykhodko-solid marked this conversation as resolved.
Show resolved Hide resolved
},
).toSet();
}
}

extension TypeString on String {
static const _baseTypeReplacement = '.*';
static final _genericRegex = RegExp('<.*>');
static final _baseTypesRegex = [
RegExp('dynamic'),
RegExp('Object'),
RegExp('Object?'),
];

bool get hasGenericString => contains(_genericRegex);
yurii-prykhodko-solid marked this conversation as resolved.
Show resolved Hide resolved

String replaceGenericString() => replaceFirst(_genericRegex, '');

RegExp getGeneralTypeRegex() {
var out = this;
for (final regex in _baseTypesRegex) {
out = out.replaceAll(regex, _baseTypeReplacement);
}

return RegExp(out);
}
yurii-prykhodko-solid marked this conversation as resolved.
Show resolved Hide resolved
}

bool hasWidgetType(DartType type) =>
Expand Down
2 changes: 2 additions & 0 deletions lint_test/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ custom_lint:
ignored_types:
- ColorTween
- AnimationController
- Subscription<List<Object?>>
- Subscription<Map<dynamic, String>>
yurii-prykhodko-solid marked this conversation as resolved.
Show resolved Hide resolved
- avoid_global_state
- avoid_returning_widgets
- avoid_unnecessary_setstate
Expand Down
16 changes: 16 additions & 0 deletions lint_test/avoid_late_keyword_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class SubAnimationController extends AnimationController {}

yurii-prykhodko-solid marked this conversation as resolved.
Show resolved Hide resolved
class NotAllowed {}

class Subscription<T> {}

/// Check "late" keyword fail
///
/// `avoid_late_keyword`
Expand Down Expand Up @@ -37,6 +39,20 @@ class AvoidLateKeyword {

late final na2 = NotAllowed();

/// expect_lint: avoid_late_keyword
late final Subscription<String> subscription1;

late final Subscription<List<String>> subscription2;

late final Subscription<List<List<int>>> subscription3;

late final Subscription<Map<dynamic, String>> subscription4;
Yarl745 marked this conversation as resolved.
Show resolved Hide resolved

/// expect_lint: avoid_late_keyword
late final Subscription<Map<String, dynamic>> subscription5;

late final Subscription<Map<List<String>, String>> subscription6;

void test() {
late final ColorTween colorTween;

Expand Down