Skip to content

Commit 67d85b3

Browse files
Merge remote-tracking branch 'origin/master'
2 parents 5a692d8 + 383581e commit 67d85b3

28 files changed

Lines changed: 694 additions & 299 deletions

CHANGELOG.md

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
## 0.1.5
2+
3+
- Added `avoid_debug_print` rule
4+
- Fixed an issue with no_magic_number lint
5+
- Fixed `avoid_unused_parameters` to report positional parameters from typedef if their name are not underscores.
6+
- Improvement for `avoid_returning_widget` lint:
7+
- ignores methods that override ones that return widget (build() for example)
8+
- no longer allows returning widgets from methods/functions named build
9+
- Fixed unexpected avoid_unnecessary_type_assertions
10+
- Added `excludeNames` param for `function_lines_of_code` lint
11+
- Improved `avoid_unrelated_type_assertions` to support true and false results
12+
113
## 0.1.4
214

315
- Removed deprecated lints:
@@ -81,12 +93,12 @@
8193
- Update `dart_code_metrics` dependency to 5.7.3
8294
- Rename deprecated `member-ordering-extended` to `member-ordering`
8395
- Add rule for widgets methods order configuration:
84-
- initState
85-
- build
86-
- didChangeDependencies
87-
- didUpdateWidget
88-
- deactivate
89-
- dispose
96+
- initState
97+
- build
98+
- didChangeDependencies
99+
- didUpdateWidget
100+
- deactivate
101+
- dispose
90102

91103
## 0.0.15
92104

example/analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ custom_lint:
1919
- avoid_unnecessary_setstate
2020
- double_literal_format
2121
- avoid_unnecessary_type_assertions
22+
- avoid_debug_print
2223
- avoid_using_api:
2324
severity: info
2425
entries:

lib/analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ custom_lint:
5050
- avoid_unnecessary_type_casts
5151
- avoid_unrelated_type_assertions
5252
- avoid_unused_parameters
53+
- avoid_debug_print
5354

5455
- cyclomatic_complexity:
5556
max_complexity: 10

lib/solid_lints.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
library solid_metrics;
22

33
import 'package:custom_lint_builder/custom_lint_builder.dart';
4+
import 'package:solid_lints/src/lints/avoid_debug_print/avoid_debug_print_rule.dart';
45
import 'package:solid_lints/src/lints/avoid_global_state/avoid_global_state_rule.dart';
56
import 'package:solid_lints/src/lints/avoid_late_keyword/avoid_late_keyword_rule.dart';
67
import 'package:solid_lints/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart';
@@ -59,6 +60,7 @@ class _SolidLints extends PluginBase {
5960
PreferLastRule.createRule(configs),
6061
PreferMatchFileNameRule.createRule(configs),
6162
ProperSuperCallsRule.createRule(configs),
63+
AvoidDebugPrint.createRule(configs),
6264
];
6365

6466
// Return only enabled rules
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import 'package:analyzer/dart/ast/ast.dart';
2+
import 'package:analyzer/dart/ast/syntactic_entity.dart';
3+
import 'package:analyzer/error/listener.dart';
4+
import 'package:custom_lint_builder/custom_lint_builder.dart';
5+
import 'package:solid_lints/src/lints/avoid_debug_print/models/avoid_debug_print_func_model.dart';
6+
import 'package:solid_lints/src/models/rule_config.dart';
7+
import 'package:solid_lints/src/models/solid_lint_rule.dart';
8+
9+
/// A `avoid_debug_print` rule which forbids calling or referencing
10+
/// debugPrint function from flutter/foundation.
11+
///
12+
/// ### Example
13+
///
14+
/// #### BAD:
15+
///
16+
/// ```dart
17+
/// debugPrint(''); // LINT
18+
/// var ref = debugPrint; // LINT
19+
/// var ref2;
20+
/// ref2 = debugPrint; // LINT
21+
/// ```
22+
///
23+
/// #### GOOD:
24+
///
25+
/// ```dart
26+
/// log('');
27+
/// ```
28+
class AvoidDebugPrint extends SolidLintRule {
29+
/// The [LintCode] of this lint rule that represents
30+
/// the error when debugPrint is called
31+
static const lintName = 'avoid_debug_print';
32+
33+
AvoidDebugPrint._(super.config);
34+
35+
/// Creates a new instance of [AvoidDebugPrint]
36+
/// based on the lint configuration.
37+
factory AvoidDebugPrint.createRule(CustomLintConfigs configs) {
38+
final rule = RuleConfig(
39+
configs: configs,
40+
name: lintName,
41+
problemMessage: (_) => "Avoid using 'debugPrint'",
42+
);
43+
44+
return AvoidDebugPrint._(rule);
45+
}
46+
47+
@override
48+
void run(
49+
CustomLintResolver resolver,
50+
ErrorReporter reporter,
51+
CustomLintContext context,
52+
) {
53+
context.registry.addFunctionExpressionInvocation(
54+
(node) {
55+
final func = node.function;
56+
if (func is! Identifier) {
57+
return;
58+
}
59+
_checkIdentifier(
60+
identifier: func,
61+
node: node,
62+
reporter: reporter,
63+
);
64+
},
65+
);
66+
67+
// addFunctionReference does not get triggered.
68+
// addVariableDeclaration and addAssignmentExpression
69+
// are used as a workaround for simple cases
70+
71+
context.registry.addVariableDeclaration((node) {
72+
_handleVariableAssignmentDeclaration(
73+
node: node,
74+
reporter: reporter,
75+
);
76+
});
77+
78+
context.registry.addAssignmentExpression((node) {
79+
_handleVariableAssignmentDeclaration(
80+
node: node,
81+
reporter: reporter,
82+
);
83+
});
84+
}
85+
86+
/// Checks whether the function identifier satisfies conditions
87+
void _checkIdentifier({
88+
required Identifier identifier,
89+
required AstNode node,
90+
required ErrorReporter reporter,
91+
}) {
92+
final funcModel = AvoidDebugPrintFuncModel.parseExpression(identifier);
93+
94+
if (funcModel.hasSameName && funcModel.hasTheSameSource) {
95+
reporter.reportErrorForNode(code, node);
96+
}
97+
}
98+
99+
/// Returns null if doesnt have right operand
100+
SyntacticEntity? _getRightOperand(List<SyntacticEntity> entities) {
101+
/// Example var t = 15; 15 is in 3d position
102+
if (entities.length < 3) {
103+
return null;
104+
}
105+
return entities[2];
106+
}
107+
108+
/// Handles variable assignment and declaration
109+
void _handleVariableAssignmentDeclaration({
110+
required AstNode node,
111+
required ErrorReporter reporter,
112+
}) {
113+
final rightOperand = _getRightOperand(node.childEntities.toList());
114+
115+
if (rightOperand == null || rightOperand is! Identifier) {
116+
return;
117+
}
118+
119+
_checkIdentifier(
120+
identifier: rightOperand,
121+
node: node,
122+
reporter: reporter,
123+
);
124+
}
125+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import 'package:analyzer/dart/ast/ast.dart';
2+
3+
/// A class used to parse function expression
4+
class AvoidDebugPrintFuncModel {
5+
/// Function name
6+
final String name;
7+
8+
/// Function's source path
9+
final String sourcePath;
10+
11+
/// A class used to parse function expression
12+
const AvoidDebugPrintFuncModel({
13+
required this.name,
14+
required this.sourcePath,
15+
});
16+
17+
/// A constructor that parses identifier into [name] and [sourcePath]
18+
factory AvoidDebugPrintFuncModel.parseExpression(
19+
Identifier identifier,
20+
) {
21+
switch (identifier) {
22+
case PrefixedIdentifier():
23+
final prefix = identifier.prefix.name;
24+
return AvoidDebugPrintFuncModel(
25+
name: identifier.name.replaceAll('$prefix.', ''),
26+
sourcePath:
27+
identifier.staticElement?.librarySource?.uri.toString() ?? '',
28+
);
29+
case SimpleIdentifier():
30+
return AvoidDebugPrintFuncModel(
31+
name: identifier.name,
32+
sourcePath:
33+
identifier.staticElement?.librarySource?.uri.toString() ?? '',
34+
);
35+
default:
36+
return AvoidDebugPrintFuncModel._empty();
37+
}
38+
}
39+
40+
factory AvoidDebugPrintFuncModel._empty() {
41+
return const AvoidDebugPrintFuncModel(
42+
name: '',
43+
sourcePath: '',
44+
);
45+
}
46+
47+
static const String _printPath = 'package:flutter/src/foundation/print.dart';
48+
49+
static const String _debugPrint = 'debugPrint';
50+
51+
/// Ehether the function has the same source library as debugPrint func
52+
bool get hasTheSameSource => _printPath == sourcePath;
53+
54+
/// Ehether the function has the same name as debugPrint
55+
bool get hasSameName => _debugPrint == name;
56+
57+
@override
58+
String toString() {
59+
return '$name, $sourcePath';
60+
}
61+
}

lib/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule.dart

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:analyzer/dart/ast/ast.dart';
2+
import 'package:analyzer/dart/element/type.dart';
23
import 'package:analyzer/error/listener.dart';
34
import 'package:custom_lint_builder/custom_lint_builder.dart';
45
import 'package:solid_lints/src/models/rule_config.dart';
@@ -10,6 +11,9 @@ import 'package:solid_lints/src/utils/types_utils.dart';
1011
/// Using functions instead of Widget subclasses for decomposing Widget trees
1112
/// may cause unexpected behavior and performance issues.
1213
///
14+
/// Exceptions:
15+
/// - overriden methods
16+
///
1317
/// More details: https://github.com/flutter/flutter/issues/19269
1418
///
1519
/// ### Example
@@ -20,7 +24,8 @@ import 'package:solid_lints/src/utils/types_utils.dart';
2024
/// Widget avoidReturningWidgets() => const SizedBox(); // LINT
2125
///
2226
/// class MyWidget extends StatelessWidget {
23-
/// Widget _test1() => const SizedBox(); // LINT
27+
/// Widget get box => SizedBox(); // LINT
28+
/// Widget test1() => const SizedBox(); //LINT
2429
/// Widget get _test3 => const SizedBox(); // LINT
2530
/// }
2631
/// ```
@@ -29,7 +34,14 @@ import 'package:solid_lints/src/utils/types_utils.dart';
2934
/// #### GOOD:
3035
///
3136
/// ```dart
32-
/// class MyWidget extends StatelessWidget {
37+
/// class MyWidget extends MyWidget {
38+
///
39+
/// @override
40+
/// Widget test1() => const SizedBox();
41+
///
42+
/// @override
43+
/// Widget get box => ColoredBox(color: Colors.pink);
44+
///
3345
/// @override
3446
/// Widget build(BuildContext context) {
3547
/// return const SizedBox();
@@ -51,7 +63,8 @@ class AvoidReturningWidgetsRule extends SolidLintRule {
5163
name: lintName,
5264
problemMessage: (_) =>
5365
'Returning a widget from a function is considered an anti-pattern. '
54-
'Extract your widget to a separate class.',
66+
'Unless you are overriding an existing method, '
67+
'consider extracting your widget to a separate class.',
5568
);
5669

5770
return AvoidReturningWidgetsRule._(rule);
@@ -64,15 +77,24 @@ class AvoidReturningWidgetsRule extends SolidLintRule {
6477
CustomLintContext context,
6578
) {
6679
context.registry.addDeclaration((node) {
67-
final isWidgetReturned = switch (node) {
80+
// Check if declaration is function or method,
81+
// simultaneously checks if return type is [DartType]
82+
final DartType? returnType = switch (node) {
6883
FunctionDeclaration(returnType: TypeAnnotation(:final type?)) ||
6984
MethodDeclaration(returnType: TypeAnnotation(:final type?)) =>
70-
hasWidgetType(type),
71-
_ => false,
85+
type,
86+
_ => null,
7287
};
7388

74-
// `build` methods return widgets by nature
75-
if (isWidgetReturned && node.declaredElement?.name != "build") {
89+
if (returnType == null) {
90+
return;
91+
}
92+
93+
final isWidgetReturned = hasWidgetType(returnType);
94+
95+
final isOverriden = node.declaredElement?.hasOverride ?? false;
96+
97+
if (isWidgetReturned && !isOverriden) {
7698
reporter.reportErrorForNode(code, node);
7799
}
78100
});

lib/src/lints/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -98,23 +98,12 @@ class AvoidUnnecessaryTypeAssertions extends SolidLintRule {
9898
if (objectType == null || castedType == null) {
9999
return false;
100100
}
101-
102101
final typeCast = TypeCast(
103102
source: objectType,
104103
target: castedType,
105-
isReversed: true,
104+
isReversed: node.notOperator != null,
106105
);
107-
108-
if (node.notOperator != null &&
109-
objectType is! TypeParameterType &&
110-
objectType is! DynamicType &&
111-
!objectType.isDartCoreObject &&
112-
typeCast.isUnnecessaryTypeCheck) {
113-
return true;
114-
} else {
115-
final typeCast = TypeCast(source: objectType, target: castedType);
116-
return typeCast.isUnnecessaryTypeCheck;
117-
}
106+
return typeCast.isUnnecessaryTypeCheck;
118107
}
119108

120109
bool _isUnnecessaryWhereType(MethodInvocation node) {

lib/src/lints/avoid_unrelated_type_assertions/avoid_unrelated_type_assertions_rule.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class AvoidUnrelatedTypeAssertionsRule extends SolidLintRule {
2222
configs: configs,
2323
name: lintName,
2424
problemMessage: (_) =>
25-
'Avoid unrelated "is" assertion. The result is always "false".',
25+
'Avoid unrelated "is" assertion. The result is always "{0}".',
2626
);
2727

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

4141
for (final element in visitor.expressions.entries) {
42-
reporter.reportErrorForNode(code, element.key);
42+
reporter.reportErrorForNode(
43+
code,
44+
element.key,
45+
[element.value.toString()],
46+
);
4347
}
4448
});
4549
}

0 commit comments

Comments
 (0)