Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
illia-romanenko committed Mar 26, 2024
2 parents 5a692d8 + 383581e commit 67d85b3
Show file tree
Hide file tree
Showing 28 changed files with 694 additions and 299 deletions.
24 changes: 18 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## 0.1.5

- Added `avoid_debug_print` rule
- Fixed an issue with no_magic_number lint
- Fixed `avoid_unused_parameters` to report positional parameters from typedef if their name are not underscores.
- Improvement for `avoid_returning_widget` lint:
- ignores methods that override ones that return widget (build() for example)
- no longer allows returning widgets from methods/functions named build
- Fixed unexpected avoid_unnecessary_type_assertions
- Added `excludeNames` param for `function_lines_of_code` lint
- Improved `avoid_unrelated_type_assertions` to support true and false results

## 0.1.4

- Removed deprecated lints:
Expand Down Expand Up @@ -81,12 +93,12 @@
- Update `dart_code_metrics` dependency to 5.7.3
- Rename deprecated `member-ordering-extended` to `member-ordering`
- Add rule for widgets methods order configuration:
- initState
- build
- didChangeDependencies
- didUpdateWidget
- deactivate
- dispose
- initState
- build
- didChangeDependencies
- didUpdateWidget
- deactivate
- dispose

## 0.0.15

Expand Down
1 change: 1 addition & 0 deletions example/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ custom_lint:
- avoid_unnecessary_setstate
- double_literal_format
- avoid_unnecessary_type_assertions
- avoid_debug_print
- avoid_using_api:
severity: info
entries:
Expand Down
1 change: 1 addition & 0 deletions lib/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ custom_lint:
- avoid_unnecessary_type_casts
- avoid_unrelated_type_assertions
- avoid_unused_parameters
- avoid_debug_print

- 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
@@ -1,6 +1,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_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 @@ -59,6 +60,7 @@ class _SolidLints extends PluginBase {
PreferLastRule.createRule(configs),
PreferMatchFileNameRule.createRule(configs),
ProperSuperCallsRule.createRule(configs),
AvoidDebugPrint.createRule(configs),
];

// Return only enabled rules
Expand Down
125 changes: 125 additions & 0 deletions lib/src/lints/avoid_debug_print/avoid_debug_print_rule.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:solid_lints/src/lints/avoid_debug_print/models/avoid_debug_print_func_model.dart';
import 'package:solid_lints/src/models/rule_config.dart';
import 'package:solid_lints/src/models/solid_lint_rule.dart';

/// A `avoid_debug_print` rule which forbids calling or referencing
/// debugPrint function from flutter/foundation.
///
/// ### Example
///
/// #### BAD:
///
/// ```dart
/// debugPrint(''); // LINT
/// var ref = debugPrint; // LINT
/// var ref2;
/// ref2 = debugPrint; // LINT
/// ```
///
/// #### GOOD:
///
/// ```dart
/// log('');
/// ```
class AvoidDebugPrint extends SolidLintRule {
/// The [LintCode] of this lint rule that represents
/// the error when debugPrint is called
static const lintName = 'avoid_debug_print';

AvoidDebugPrint._(super.config);

/// Creates a new instance of [AvoidDebugPrint]
/// based on the lint configuration.
factory AvoidDebugPrint.createRule(CustomLintConfigs configs) {
final rule = RuleConfig(
configs: configs,
name: lintName,
problemMessage: (_) => "Avoid using 'debugPrint'",
);

return AvoidDebugPrint._(rule);
}

@override
void run(
CustomLintResolver resolver,
ErrorReporter reporter,
CustomLintContext context,
) {
context.registry.addFunctionExpressionInvocation(
(node) {
final func = node.function;
if (func is! Identifier) {
return;
}
_checkIdentifier(
identifier: func,
node: node,
reporter: reporter,
);
},
);

// addFunctionReference does not get triggered.
// addVariableDeclaration and addAssignmentExpression
// are used as a workaround for simple cases

context.registry.addVariableDeclaration((node) {
_handleVariableAssignmentDeclaration(
node: node,
reporter: reporter,
);
});

context.registry.addAssignmentExpression((node) {
_handleVariableAssignmentDeclaration(
node: node,
reporter: reporter,
);
});
}

/// Checks whether the function identifier satisfies conditions
void _checkIdentifier({
required Identifier identifier,
required AstNode node,
required ErrorReporter reporter,
}) {
final funcModel = AvoidDebugPrintFuncModel.parseExpression(identifier);

if (funcModel.hasSameName && funcModel.hasTheSameSource) {
reporter.reportErrorForNode(code, node);
}
}

/// Returns null if doesnt have right operand
SyntacticEntity? _getRightOperand(List<SyntacticEntity> entities) {
/// Example var t = 15; 15 is in 3d position
if (entities.length < 3) {
return null;
}
return entities[2];
}

/// Handles variable assignment and declaration
void _handleVariableAssignmentDeclaration({
required AstNode node,
required ErrorReporter reporter,
}) {
final rightOperand = _getRightOperand(node.childEntities.toList());

if (rightOperand == null || rightOperand is! Identifier) {
return;
}

_checkIdentifier(
identifier: rightOperand,
node: node,
reporter: reporter,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import 'package:analyzer/dart/ast/ast.dart';

/// A class used to parse function expression
class AvoidDebugPrintFuncModel {
/// Function name
final String name;

/// Function's source path
final String sourcePath;

/// A class used to parse function expression
const AvoidDebugPrintFuncModel({
required this.name,
required this.sourcePath,
});

/// A constructor that parses identifier into [name] and [sourcePath]
factory AvoidDebugPrintFuncModel.parseExpression(
Identifier identifier,
) {
switch (identifier) {
case PrefixedIdentifier():
final prefix = identifier.prefix.name;
return AvoidDebugPrintFuncModel(
name: identifier.name.replaceAll('$prefix.', ''),
sourcePath:
identifier.staticElement?.librarySource?.uri.toString() ?? '',
);
case SimpleIdentifier():
return AvoidDebugPrintFuncModel(
name: identifier.name,
sourcePath:
identifier.staticElement?.librarySource?.uri.toString() ?? '',
);
default:
return AvoidDebugPrintFuncModel._empty();
}
}

factory AvoidDebugPrintFuncModel._empty() {
return const AvoidDebugPrintFuncModel(
name: '',
sourcePath: '',
);
}

static const String _printPath = 'package:flutter/src/foundation/print.dart';

static const String _debugPrint = 'debugPrint';

/// Ehether the function has the same source library as debugPrint func
bool get hasTheSameSource => _printPath == sourcePath;

/// Ehether the function has the same name as debugPrint
bool get hasSameName => _debugPrint == name;

@override
String toString() {
return '$name, $sourcePath';
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:solid_lints/src/models/rule_config.dart';
Expand All @@ -10,6 +11,9 @@ import 'package:solid_lints/src/utils/types_utils.dart';
/// Using functions instead of Widget subclasses for decomposing Widget trees
/// may cause unexpected behavior and performance issues.
///
/// Exceptions:
/// - overriden methods
///
/// More details: https://github.com/flutter/flutter/issues/19269
///
/// ### Example
Expand All @@ -20,7 +24,8 @@ import 'package:solid_lints/src/utils/types_utils.dart';
/// Widget avoidReturningWidgets() => const SizedBox(); // LINT
///
/// class MyWidget extends StatelessWidget {
/// Widget _test1() => const SizedBox(); // LINT
/// Widget get box => SizedBox(); // LINT
/// Widget test1() => const SizedBox(); //LINT
/// Widget get _test3 => const SizedBox(); // LINT
/// }
/// ```
Expand All @@ -29,7 +34,14 @@ import 'package:solid_lints/src/utils/types_utils.dart';
/// #### GOOD:
///
/// ```dart
/// class MyWidget extends StatelessWidget {
/// class MyWidget extends MyWidget {
///
/// @override
/// Widget test1() => const SizedBox();
///
/// @override
/// Widget get box => ColoredBox(color: Colors.pink);
///
/// @override
/// Widget build(BuildContext context) {
/// return const SizedBox();
Expand All @@ -51,7 +63,8 @@ class AvoidReturningWidgetsRule extends SolidLintRule {
name: lintName,
problemMessage: (_) =>
'Returning a widget from a function is considered an anti-pattern. '
'Extract your widget to a separate class.',
'Unless you are overriding an existing method, '
'consider extracting your widget to a separate class.',
);

return AvoidReturningWidgetsRule._(rule);
Expand All @@ -64,15 +77,24 @@ class AvoidReturningWidgetsRule extends SolidLintRule {
CustomLintContext context,
) {
context.registry.addDeclaration((node) {
final isWidgetReturned = switch (node) {
// Check if declaration is function or method,
// simultaneously checks if return type is [DartType]
final DartType? returnType = switch (node) {
FunctionDeclaration(returnType: TypeAnnotation(:final type?)) ||
MethodDeclaration(returnType: TypeAnnotation(:final type?)) =>
hasWidgetType(type),
_ => false,
type,
_ => null,
};

// `build` methods return widgets by nature
if (isWidgetReturned && node.declaredElement?.name != "build") {
if (returnType == null) {
return;
}

final isWidgetReturned = hasWidgetType(returnType);

final isOverriden = node.declaredElement?.hasOverride ?? false;

if (isWidgetReturned && !isOverriden) {
reporter.reportErrorForNode(code, node);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,23 +98,12 @@ class AvoidUnnecessaryTypeAssertions extends SolidLintRule {
if (objectType == null || castedType == null) {
return false;
}

final typeCast = TypeCast(
source: objectType,
target: castedType,
isReversed: true,
isReversed: node.notOperator != null,
);

if (node.notOperator != null &&
objectType is! TypeParameterType &&
objectType is! DynamicType &&
!objectType.isDartCoreObject &&
typeCast.isUnnecessaryTypeCheck) {
return true;
} else {
final typeCast = TypeCast(source: objectType, target: castedType);
return typeCast.isUnnecessaryTypeCheck;
}
return typeCast.isUnnecessaryTypeCheck;
}

bool _isUnnecessaryWhereType(MethodInvocation node) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class AvoidUnrelatedTypeAssertionsRule extends SolidLintRule {
configs: configs,
name: lintName,
problemMessage: (_) =>
'Avoid unrelated "is" assertion. The result is always "false".',
'Avoid unrelated "is" assertion. The result is always "{0}".',
);

return AvoidUnrelatedTypeAssertionsRule._(rule);
Expand All @@ -39,7 +39,11 @@ class AvoidUnrelatedTypeAssertionsRule extends SolidLintRule {
visitor.visitIsExpression(node);

for (final element in visitor.expressions.entries) {
reporter.reportErrorForNode(code, element.key);
reporter.reportErrorForNode(
code,
element.key,
[element.value.toString()],
);
}
});
}
Expand Down
Loading

0 comments on commit 67d85b3

Please sign in to comment.