Skip to content

Commit

Permalink
Fix avoid-late if initialized (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
maxxlab authored Nov 17, 2023
1 parent 556d739 commit 6aae62c
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 7 deletions.
36 changes: 34 additions & 2 deletions lib/lints/avoid_late_keyword/avoid_late_keyword_rule.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:solid_lints/lints/avoid_late_keyword/models/avoid_late_keyword_parameters.dart';
import 'package:solid_lints/models/rule_config.dart';
import 'package:solid_lints/models/solid_lint_rule.dart';
import 'package:solid_lints/utils/types_utils.dart';

/// A `late` keyword rule which forbids using it to avoid runtime exceptions.
class AvoidLateKeywordRule extends SolidLintRule {
class AvoidLateKeywordRule extends SolidLintRule<AvoidLateKeywordParameters> {
/// The [LintCode] of this lint rule that represents
/// the error whether we use `late` keyword.
static const lintName = 'avoid_late_keyword';
Expand All @@ -17,6 +20,7 @@ class AvoidLateKeywordRule extends SolidLintRule {
final rule = RuleConfig(
configs: configs,
name: lintName,
paramsParser: AvoidLateKeywordParameters.fromJson,
problemMessage: (_) => 'Avoid using the "late" keyword. '
'It may result in runtime exceptions.',
);
Expand All @@ -31,9 +35,37 @@ class AvoidLateKeywordRule extends SolidLintRule {
CustomLintContext context,
) {
context.registry.addVariableDeclaration((node) {
if (node.declaredElement?.isLate ?? false) {
if (_shouldLint(node)) {
reporter.reportErrorForNode(code, node);
}
});
}

bool _shouldLint(VariableDeclaration node) {
final isLateDeclaration = node.declaredElement?.isLate ?? false;
if (!isLateDeclaration) return false;

final hasIgnoredType = _hasIgnoredType(node);
if (hasIgnoredType) return false;

final allowInitialized = config.parameters.allowInitialized;
if (!allowInitialized) return true;

final hasInitializer = node.initializer != null;
return !hasInitializer;
}

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

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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/// A data model class that represents the "avoid late keyword" input
/// parameters.
class AvoidLateKeywordParameters {
/// Allow immediately initialised late variables.
///
/// ```dart
/// late var ok = 0; // ok when allowInitialized == true
/// late var notOk; // initialized elsewhere, not allowed
/// ```
final bool allowInitialized;

/// Types that would be ignored by avoid-late rule
final Iterable<String> ignoredTypes;

/// Constructor for [AvoidLateKeywordParameters] model
const AvoidLateKeywordParameters({
this.allowInitialized = false,
this.ignoredTypes = const [],
});

/// Method for creating from json data
factory AvoidLateKeywordParameters.fromJson(Map<String, Object?> json) =>
AvoidLateKeywordParameters(
allowInitialized: json['allow_initialized'] as bool? ?? false,
ignoredTypes:
List<String>.from(json['ignored_types'] as Iterable? ?? []),
);
}
8 changes: 8 additions & 0 deletions lib/utils/types_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,18 @@
// SOFTWARE.
// ignore_for_file: public_member_api_docs

import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:collection/collection.dart';

extension Subtypes on DartType {
Iterable<DartType> get supertypes {
final element = this.element;
return element is InterfaceElement ? element.allSupertypes : [];
}
}

bool hasWidgetType(DartType type) =>
(isWidgetOrSubclass(type) ||
_isIterable(type) ||
Expand Down
6 changes: 5 additions & 1 deletion lint_test/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ custom_lint:
- function_lines_of_code:
max_lines: 50
- avoid_non_null_assertion
- avoid_late_keyword
- avoid_late_keyword:
allow_initialized: true
ignored_types:
- ColorTween
- AnimationController
- avoid_global_state
- avoid_returning_widgets
- avoid_unnecessary_setstate
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
analyzer:
plugins:
- ../custom_lint

custom_lint:
rules:
- avoid_late_keyword:
allow_initialized: false
ignored_types:
- Animation
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// ignore_for_file: prefer_const_declarations, unused_local_variable, prefer_match_file_name
// ignore_for_file: avoid_global_state

abstract class Animation {}

class AnimationController implements Animation {}

class SubAnimationController extends AnimationController {}

class ColorTween {}

/// Check "late" keyword fail
///
/// `avoid_late_keyword`
/// allow_initialized option disabled
class AvoidLateKeyword {
late final Animation animation1;

late final animation2 = AnimationController();

late final animation3 = SubAnimationController();

/// expect_lint: avoid_late_keyword
late final ColorTween colorTween1;

/// expect_lint: avoid_late_keyword
late final colorTween2 = ColorTween();

/// expect_lint: avoid_late_keyword
late final colorTween3 = colorTween2;

late final AnimationController controller1;

/// expect_lint: avoid_late_keyword
late final field1 = 'string';

/// expect_lint: avoid_late_keyword
late final String field2;

/// expect_lint: avoid_late_keyword
late final String field3 = 'string';

/// expect_lint: avoid_late_keyword
late final field4;

void test() {
late final Animation animation1;

late final animation2 = AnimationController();

late final animation3 = SubAnimationController();

/// expect_lint: avoid_late_keyword
late final ColorTween colorTween1;

/// expect_lint: avoid_late_keyword
late final colorTween2 = ColorTween();

/// expect_lint: avoid_late_keyword
late final colorTween3 = colorTween2;

late final AnimationController controller1;

/// expect_lint: avoid_late_keyword
late final local1 = 'string';

/// expect_lint: avoid_late_keyword
late final String local2;

/// expect_lint: avoid_late_keyword
late final String local4 = 'string';

/// expect_lint: avoid_late_keyword
late final local3;
}
}
11 changes: 11 additions & 0 deletions lint_test/avoid_late_keyword_allow_initialized_test/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: avoid_late_keyword_allow_initialized_test
publish_to: none

environment:
sdk: '>=3.0.0 <4.0.0'

dependencies:

dev_dependencies:
solid_lints:
path: ../../
51 changes: 47 additions & 4 deletions lint_test/avoid_late_keyword_test.dart
Original file line number Diff line number Diff line change
@@ -1,21 +1,64 @@
// ignore_for_file: prefer_const_declarations, unused_local_variable, prefer_match_file_name
// ignore_for_file: avoid_global_state

class ColorTween {}

class AnimationController {}

class SubAnimationController extends AnimationController {}

class NotAllowed {}

/// Check "late" keyword fail
///
/// `avoid_late_keyword`
/// allow_initialized option enabled
class AvoidLateKeyword {
/// expect_lint: avoid_late_keyword
late final ColorTween colorTween;

late final AnimationController controller1;

late final SubAnimationController controller2;

late final controller3 = AnimationController();

late final controller4 = SubAnimationController();

late final field1 = 'string';

/// expect_lint: avoid_late_keyword
late String field2;
late final String field2;

/// expect_lint: avoid_late_keyword
late final field3;

/// expect_lint: avoid_late_keyword
late final NotAllowed na1;

late final na2 = NotAllowed();

void test() {
late final ColorTween colorTween;

late final AnimationController controller1;

late final SubAnimationController controller2;

late final controller3 = AnimationController();

late final controller4 = SubAnimationController();

late final local1 = 'string';

/// expect_lint: avoid_late_keyword
late final field3 = 'string';
late final String local2;

/// expect_lint: avoid_late_keyword
late String field4;
late final local3;

/// expect_lint: avoid_late_keyword
late final NotAllowed na1;

late final na2 = NotAllowed();
}
}

0 comments on commit 6aae62c

Please sign in to comment.