Skip to content

Commit

Permalink
Merge branch 'master' into use_reverse_if_to_reduce_nesting
Browse files Browse the repository at this point in the history
  • Loading branch information
4akloon committed Apr 19, 2024
2 parents 17ebe00 + 8dc2cd5 commit b09bd81
Show file tree
Hide file tree
Showing 20 changed files with 413 additions and 11 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 0.2.0

- Added `avoid_final_with_getter` rule
- Improve `avoid_late_keyword` - `ignored_types` to support ignoring subtype of the node type (https://github.com/solid-software/solid_lints/issues/157)
- Abstract methods should be omitted by `proper_super_calls` (https://github.com/solid-software/solid_lints/issues/159)


## 0.1.5

- Added `avoid_debug_print` rule
Expand Down
1 change: 1 addition & 0 deletions lib/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ custom_lint:
- avoid_unrelated_type_assertions
- avoid_unused_parameters
- avoid_debug_print
- avoid_final_with_getter

- cyclomatic_complexity:
max_complexity: 10
Expand Down
2 changes: 2 additions & 0 deletions lib/solid_lints.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ library solid_metrics;

import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:solid_lints/src/lints/avoid_debug_print/avoid_debug_print_rule.dart';
import 'package:solid_lints/src/lints/avoid_final_with_getter/avoid_final_with_getter_rule.dart';
import 'package:solid_lints/src/lints/avoid_global_state/avoid_global_state_rule.dart';
import 'package:solid_lints/src/lints/avoid_late_keyword/avoid_late_keyword_rule.dart';
import 'package:solid_lints/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart';
Expand Down Expand Up @@ -63,6 +64,7 @@ class _SolidLints extends PluginBase {
ProperSuperCallsRule.createRule(configs),
AvoidDebugPrint.createRule(configs),
PreferEarlyReturnRule.createRule(configs),
AvoidFinalWithGetterRule.createRule(configs),
];

// Return only enabled rules
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:solid_lints/src/lints/avoid_final_with_getter/visitors/avoid_final_with_getter_visitor.dart';
import 'package:solid_lints/src/models/rule_config.dart';
import 'package:solid_lints/src/models/solid_lint_rule.dart';

/// Avoid using final private fields with getters.
///
/// Final private variables used in a pair with a getter
/// must be changed to a final public type without a getter
/// because it is the same as a public field.
///
/// ### Example
///
/// #### BAD:
///
/// ```dart
/// class MyClass {
/// final int _myField = 0;
///
/// int get myField => _myField;
/// }
/// ```
///
/// #### GOOD:
///
/// ```dart
/// class MyClass {
/// final int myField = 0;
/// }
/// ```
///
class AvoidFinalWithGetterRule extends SolidLintRule {
/// The [LintCode] of this lint rule that represents
/// the error whether we use final private fields with getters.
static const lintName = 'avoid_final_with_getter';

AvoidFinalWithGetterRule._(super.config);

/// Creates a new instance of [AvoidFinalWithGetterRule]
/// based on the lint configuration.
factory AvoidFinalWithGetterRule.createRule(CustomLintConfigs configs) {
final rule = RuleConfig(
configs: configs,
name: lintName,
problemMessage: (_) => 'Avoid final private fields with getters.',
);

return AvoidFinalWithGetterRule._(rule);
}

@override
void run(
CustomLintResolver resolver,
ErrorReporter reporter,
CustomLintContext context,
) {
context.registry.addCompilationUnit((node) {
final visitor = AvoidFinalWithGetterVisitor();
node.accept(visitor);

for (final getter in visitor.getters) {
reporter.reportErrorForNode(code, getter);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:solid_lints/src/lints/avoid_final_with_getter/visitors/getter_variable_visitor.dart';

/// A visitor that checks for final private fields with getters.
/// If a final private field has a getter, it is considered as a public field.
class AvoidFinalWithGetterVisitor extends RecursiveAstVisitor<void> {
final _getters = <MethodDeclaration>[];

/// List of getters
Iterable<MethodDeclaration> get getters => _getters;

@override
void visitMethodDeclaration(MethodDeclaration node) {
if (node
case MethodDeclaration(
isGetter: true,
declaredElement: ExecutableElement(
isAbstract: false,
isPublic: true,
)
)) {
final visitor = GetterVariableVisitor(node);
node.parent?.accept(visitor);

if (visitor.hasVariable) {
_getters.add(node);
}
}
super.visitMethodDeclaration(node);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';

/// A visitor that checks the association of the getter with
/// the final private variable
class GetterVariableVisitor extends RecursiveAstVisitor<void> {
final int? _getterId;
VariableDeclaration? _variable;

/// Creates a new instance of [GetterVariableVisitor]
GetterVariableVisitor(MethodDeclaration getter)
: _getterId = getter.getterReferenceId;

/// Is there a variable associated with the getter
bool get hasVariable => _variable != null;

@override
void visitVariableDeclaration(VariableDeclaration node) {
if (node
case VariableDeclaration(
isFinal: true,
declaredElement: VariableElement(id: final id, isPrivate: true)
) when id == _getterId) {
_variable = node;
}

super.visitVariableDeclaration(node);
}
}

extension on MethodDeclaration {
int? get getterReferenceId => switch (body) {
ExpressionFunctionBody(
expression: SimpleIdentifier(
staticElement: Element(
declaration: PropertyAccessorElement(
variable: PropertyInducingElement(:final id)
)
)
)
) =>
id,
_ => null,
};
}
8 changes: 3 additions & 5 deletions lib/src/lints/avoid_late_keyword/avoid_late_keyword_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,8 @@ class AvoidLateKeywordRule extends SolidLintRule<AvoidLateKeywordParameters> {
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: ignoredTypes,
);
}
}
6 changes: 4 additions & 2 deletions lib/src/lints/proper_super_calls/proper_super_calls_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,11 @@ class ProperSuperCallsRule extends SolidLintRule {
context.registry.addMethodDeclaration(
(node) {
final methodName = node.name.toString();
final body = node.body;

if (methodName == _initState || methodName == _dispose) {
final statements = (node.body as BlockFunctionBody).block.statements;
if (methodName case _initState || _dispose
when body is BlockFunctionBody) {
final statements = body.block.statements;

_checkSuperCalls(
node,
Expand Down
66 changes: 66 additions & 0 deletions lib/src/utils/named_type_utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';

/// Parses the provided type string to extract a [NamedType].
NamedType parseNamedTypeFromString(String typeString) {
try {
final namedTypeFinder = _NamedTypeFinder();

final parseResult = parseString(content: "$typeString _;");
parseResult.unit.visitChildren(namedTypeFinder);

return namedTypeFinder.foundNamedType!;
} catch (_) {
throw Exception("No NamedType could be parsed from the input "
"typeString: '$typeString'. Ensure it's a valid Dart "
"type declaration.");
}
}

class _NamedTypeFinder extends GeneralizingAstVisitor<void> {
NamedType? _foundNamedType;

NamedType? get foundNamedType => _foundNamedType;

@override
void visitNamedType(NamedType namedType) {
_foundNamedType ??= namedType;
}
}

///
extension ChildNamedTypes on NamedType {
/// Retrieves child [NamedType] instances from type arguments.
List<NamedType> get childNamedTypes =>
typeArguments?.arguments.whereType<NamedType>().toList() ?? [];

/// Gets the token name of this type instance.
String get tokenName => name2.toString();

/// Checks if the current token name is 'dynamic'.
bool get isDynamic => tokenName == "dynamic";

/// Checks if the current token name is 'Object'.
bool get isObject => tokenName == "Object";

/// Checks if this node is a subtype of the specified node
/// based on their structures.
bool isSubtypeOf({required NamedType node}) {
if (isDynamic || isObject) return true;

if (tokenName != node.tokenName) return false;

if (childNamedTypes.isEmpty) return true;

if (childNamedTypes.length != node.childNamedTypes.length) return false;

for (int i = 0; i < childNamedTypes.length; i++) {
if (!childNamedTypes[i].isSubtypeOf(node: node.childNamedTypes[i])) {
return false;
}
}

return true;
}
}
49 changes: 49 additions & 0 deletions lib/src/utils/types_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,65 @@
// SOFTWARE.
// ignore_for_file: public_member_api_docs

import 'package:analyzer/dart/ast/ast.dart';
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';
import 'package:solid_lints/src/utils/named_type_utils.dart';

extension Subtypes on DartType {
Iterable<DartType> get supertypes {
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();
}

/// Parses a [NamedType] instance from current type.
NamedType getNamedType() {
final typeString = getTypeString(
withGenerics: true,
withNullability: false,
);

return parseNamedTypeFromString(typeString);
}

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

final checkedTypeNodes = [this, ...supertypes].map(
(type) => type.getNamedType(),
);

final ignoredTypeNodes = ignoredTypes.map(parseNamedTypeFromString);

for (final ignoredTypeNode in ignoredTypeNodes) {
for (final checkedTypeNode in checkedTypeNodes) {
if (ignoredTypeNode.isSubtypeOf(node: checkedTypeNode)) {
return true;
}
}
}

return false;
}
}

extension TypeString on String {
static final _genericRegex = RegExp('<.*>');

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

bool hasWidgetType(DartType type) =>
Expand Down
4 changes: 1 addition & 3 deletions lint_test/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ custom_lint:
- avoid_non_null_assertion
- avoid_late_keyword:
allow_initialized: true
ignored_types:
- ColorTween
- AnimationController
- avoid_global_state
- avoid_returning_widgets
- avoid_unnecessary_setstate
Expand Down Expand Up @@ -60,3 +57,4 @@ custom_lint:
- prefer_last
- prefer_match_file_name
- proper_super_calls
- avoid_final_with_getter
35 changes: 35 additions & 0 deletions lint_test/avoid_final_with_getter_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// ignore_for_file: type_annotate_public_apis, prefer_match_file_name, unused_local_variable

/// Check final private field with getter fail
/// `avoid_final_with_getter`
class Fail {
final int _myField = 0;

// expect_lint: avoid_final_with_getter
int get myField => _myField;
}

class FailOtherName {
final int _myField = 0;

// expect_lint: avoid_final_with_getter
int get myFieldInt => _myField;
}

class FailStatic {
static final int _myField = 0;

// expect_lint: avoid_final_with_getter
static int get myField => _myField;
}

class Skip {
final int _myField = 0;

int get myField => _myField + 1; // it is not a getter for the field
}

class Good {
final int myField = 0;
}
Loading

0 comments on commit b09bd81

Please sign in to comment.