@sun-asterisk/sunlint 1.3.43 → 1.3.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/dart_analyzer/README.md +226 -0
  2. package/dart_analyzer/analysis_options.yaml +66 -0
  3. package/dart_analyzer/bin/sunlint-dart-macos +0 -0
  4. package/dart_analyzer/bin/sunlint_dart_analyzer.dart +124 -0
  5. package/dart_analyzer/lib/analyzer_service.dart +625 -0
  6. package/dart_analyzer/lib/json_rpc_server.dart +275 -0
  7. package/dart_analyzer/lib/models/rule.dart +67 -0
  8. package/dart_analyzer/lib/models/symbol_table.dart +607 -0
  9. package/dart_analyzer/lib/models/violation.dart +69 -0
  10. package/dart_analyzer/lib/rules/base_analyzer.dart +52 -0
  11. package/dart_analyzer/lib/rules/common/C002_no_duplicate_code.dart +344 -0
  12. package/dart_analyzer/lib/rules/common/C003_no_vague_abbreviations.dart +318 -0
  13. package/dart_analyzer/lib/rules/common/C006_function_naming.dart +219 -0
  14. package/dart_analyzer/lib/rules/common/C008_variable_declaration_locality.dart +205 -0
  15. package/dart_analyzer/lib/rules/common/C010_limit_block_nesting.dart +162 -0
  16. package/dart_analyzer/lib/rules/common/C012_command_query_separation.dart +214 -0
  17. package/dart_analyzer/lib/rules/common/C013_no_dead_code.dart +225 -0
  18. package/dart_analyzer/lib/rules/common/C014_dependency_injection.dart +249 -0
  19. package/dart_analyzer/lib/rules/common/C017_constructor_logic.dart +158 -0
  20. package/dart_analyzer/lib/rules/common/C018_no_throw_generic_error.dart +141 -0
  21. package/dart_analyzer/lib/rules/common/C019_log_level_usage.dart +165 -0
  22. package/dart_analyzer/lib/rules/common/C020_unused_imports.dart +128 -0
  23. package/dart_analyzer/lib/rules/common/C021_import_organization.dart +86 -0
  24. package/dart_analyzer/lib/rules/common/C023_no_duplicate_variable.dart +112 -0
  25. package/dart_analyzer/lib/rules/common/C024_no_scatter_hardcoded_constants.dart +79 -0
  26. package/dart_analyzer/lib/rules/common/C029_catch_block_logging.dart +81 -0
  27. package/dart_analyzer/lib/rules/common/C030_use_custom_error_classes.dart +77 -0
  28. package/dart_analyzer/lib/rules/common/C031_validation_separation.dart +90 -0
  29. package/dart_analyzer/lib/rules/common/C033_separate_service_repository.dart +80 -0
  30. package/dart_analyzer/lib/rules/common/C035_error_logging_context.dart +148 -0
  31. package/dart_analyzer/lib/rules/common/C040_centralized_validation.dart +84 -0
  32. package/dart_analyzer/lib/rules/common/C041_no_sensitive_hardcode.dart +103 -0
  33. package/dart_analyzer/lib/rules/common/C042_boolean_name_prefix.dart +105 -0
  34. package/dart_analyzer/lib/rules/common/C043_no_console_or_print.dart +101 -0
  35. package/dart_analyzer/lib/rules/common/C047_no_duplicate_retry_logic.dart +94 -0
  36. package/dart_analyzer/lib/rules/common/C048_no_bypass_architectural_layers.dart +132 -0
  37. package/dart_analyzer/lib/rules/common/C052_parsing_or_data_transformation.dart +95 -0
  38. package/dart_analyzer/lib/rules/common/C060_no_override_superclass.dart +81 -0
  39. package/dart_analyzer/lib/rules/common/C065_one_behavior_per_test.dart +83 -0
  40. package/dart_analyzer/lib/rules/common/C067_no_hardcoded_config.dart +89 -0
  41. package/dart_analyzer/lib/rules/common/C070_no_real_time_tests.dart +99 -0
  42. package/dart_analyzer/lib/rules/common/C072_single_test_behavior.dart +78 -0
  43. package/dart_analyzer/lib/rules/common/C073_validate_required_config_on_startup.dart +82 -0
  44. package/dart_analyzer/lib/rules/common/C075_explicit_return_types.dart +85 -0
  45. package/dart_analyzer/lib/rules/common/C076_explicit_function_types.dart +104 -0
  46. package/dart_analyzer/lib/rules/dart/D001_recommended_lint_rules.dart +309 -0
  47. package/dart_analyzer/lib/rules/dart/D002_dispose_resources.dart +338 -0
  48. package/dart_analyzer/lib/rules/dart/D003_prefer_widgets_over_methods.dart +273 -0
  49. package/dart_analyzer/lib/rules/dart/D004_avoid_shrinkwrap_listview.dart +154 -0
  50. package/dart_analyzer/lib/rules/dart/D005_limit_widget_nesting.dart +265 -0
  51. package/dart_analyzer/lib/rules/dart/D006_prefer_extracting_large_callbacks.dart +135 -0
  52. package/dart_analyzer/lib/rules/dart/D007_prefer_init_first_dispose_last.dart +150 -0
  53. package/dart_analyzer/lib/rules/dart/D008_avoid_long_functions.dart +394 -0
  54. package/dart_analyzer/lib/rules/dart/D009_limit_function_parameters.dart +179 -0
  55. package/dart_analyzer/lib/rules/dart/D010_limit_cyclomatic_complexity.dart +257 -0
  56. package/dart_analyzer/lib/rules/dart/D011_prefer_named_parameters.dart +152 -0
  57. package/dart_analyzer/lib/rules/dart/D012_prefer_named_boolean_parameters.dart +156 -0
  58. package/dart_analyzer/lib/rules/dart/D013_single_public_class.dart +246 -0
  59. package/dart_analyzer/lib/rules/dart/D014_unsafe_collection_access.dart +202 -0
  60. package/dart_analyzer/lib/rules/dart/D015_copywith_all_parameters.dart +125 -0
  61. package/dart_analyzer/lib/rules/dart/D016_project_should_have_tests.dart +134 -0
  62. package/dart_analyzer/lib/rules/dart/D017_pubspec_dependencies_review.dart +187 -0
  63. package/dart_analyzer/lib/rules/dart/D018_remove_commented_code.dart +196 -0
  64. package/dart_analyzer/lib/rules/dart/D019_avoid_single_child_multi_child_widget.dart +161 -0
  65. package/dart_analyzer/lib/rules/dart/D020_limit_if_else_branches.dart +125 -0
  66. package/dart_analyzer/lib/rules/dart/D021_avoid_negated_boolean_checks.dart +227 -0
  67. package/dart_analyzer/lib/rules/dart/D022_use_setstate_correctly.dart +269 -0
  68. package/dart_analyzer/lib/rules/dart/D023_avoid_unnecessary_method_overrides.dart +191 -0
  69. package/dart_analyzer/lib/rules/dart/D024_avoid_unnecessary_stateful_widget.dart +194 -0
  70. package/dart_analyzer/lib/rules/dart/D025_avoid_nested_conditional_expressions.dart +90 -0
  71. package/dart_analyzer/lib/rules/security/S001_backend_auth_communications.dart +155 -0
  72. package/dart_analyzer/lib/rules/security/S002_os_command_injection.dart +159 -0
  73. package/dart_analyzer/lib/rules/security/S003_open_redirect_protection.dart +208 -0
  74. package/dart_analyzer/lib/rules/security/S004_sensitive_data_logging.dart +391 -0
  75. package/dart_analyzer/lib/rules/security/S005_trusted_service_authorization.dart +182 -0
  76. package/dart_analyzer/lib/rules/security/S006_no_default_credentials.dart +208 -0
  77. package/dart_analyzer/lib/rules/security/S007_output_encoding.dart +224 -0
  78. package/dart_analyzer/lib/rules/security/S008_svg_content_sanitization.dart +211 -0
  79. package/dart_analyzer/lib/rules/security/S009_no_insecure_encryption.dart +160 -0
  80. package/dart_analyzer/lib/rules/security/S010_use_csprng.dart +184 -0
  81. package/dart_analyzer/lib/rules/security/S011_ech_tls_config.dart +175 -0
  82. package/dart_analyzer/lib/rules/security/S012_hardcoded_secrets.dart +255 -0
  83. package/dart_analyzer/lib/rules/security/S013_tls_enforcement.dart +148 -0
  84. package/dart_analyzer/lib/rules/security/S014_tls_version_enforcement.dart +117 -0
  85. package/dart_analyzer/lib/rules/security/S015_insecure_tls_certificate.dart +315 -0
  86. package/dart_analyzer/lib/rules/security/S016_no_sensitive_querystring.dart +244 -0
  87. package/dart_analyzer/lib/rules/security/S017_use_parameterized_queries.dart +191 -0
  88. package/dart_analyzer/lib/rules/security/S018_no_sensitive_browser_storage.dart +175 -0
  89. package/dart_analyzer/lib/rules/security/S019_smtp_injection_protection.dart +166 -0
  90. package/dart_analyzer/lib/rules/security/S020_no_eval_dynamic_code.dart +149 -0
  91. package/dart_analyzer/lib/rules/security/S021_referrer_policy.dart +146 -0
  92. package/dart_analyzer/lib/rules/security/S022_escape_output_context.dart +111 -0
  93. package/dart_analyzer/lib/rules/security/S023_no_json_injection.dart +550 -0
  94. package/dart_analyzer/lib/rules/security/S024_xpath_xxe_protection.dart +299 -0
  95. package/dart_analyzer/lib/rules/security/S025_server_side_validation.dart +140 -0
  96. package/dart_analyzer/lib/rules/security/S026_tls_all_connections.dart +196 -0
  97. package/dart_analyzer/lib/rules/security/S027_mtls_certificate_validation.dart +195 -0
  98. package/dart_analyzer/lib/rules/security/S028_file_upload_size_limits.dart +186 -0
  99. package/dart_analyzer/lib/rules/security/S029_csrf_protection.dart +171 -0
  100. package/dart_analyzer/lib/rules/security/S030_directory_browsing_protection.dart +144 -0
  101. package/dart_analyzer/lib/rules/security/S031_secure_session_cookies.dart +118 -0
  102. package/dart_analyzer/lib/rules/security/S032_httponly_session_cookies.dart +114 -0
  103. package/dart_analyzer/lib/rules/security/S033_samesite_session_cookies.dart +120 -0
  104. package/dart_analyzer/lib/rules/security/S034_host_prefix_session_cookies.dart +160 -0
  105. package/dart_analyzer/lib/rules/security/S035_separate_app_hostnames.dart +117 -0
  106. package/dart_analyzer/lib/rules/security/S036_lfi_rfi_protection.dart +188 -0
  107. package/dart_analyzer/lib/rules/security/S037_cache_headers.dart +113 -0
  108. package/dart_analyzer/lib/rules/security/S038_no_version_headers.dart +114 -0
  109. package/dart_analyzer/lib/rules/security/S039_tls_certificate_validation.dart +131 -0
  110. package/dart_analyzer/lib/rules/security/S040_session_fixation_protection.dart +155 -0
  111. package/dart_analyzer/lib/rules/security/S041_session_token_invalidation.dart +201 -0
  112. package/dart_analyzer/lib/rules/security/S042_require_re_authentication_for_long_lived.dart +158 -0
  113. package/dart_analyzer/lib/rules/security/S043_password_changes_invalidate_all_sessions.dart +88 -0
  114. package/dart_analyzer/lib/rules/security/S044_re_authentication_required.dart +119 -0
  115. package/dart_analyzer/lib/rules/security/S045_brute_force_protection.dart +253 -0
  116. package/dart_analyzer/lib/rules/security/S046_jwt_algorithm_allowlist.dart +113 -0
  117. package/dart_analyzer/lib/rules/security/S047_oauth_pkce_protection.dart +124 -0
  118. package/dart_analyzer/lib/rules/security/S048_oauth_redirect_uri_validation.dart +134 -0
  119. package/dart_analyzer/lib/rules/security/S049_short_validity_tokens.dart +145 -0
  120. package/dart_analyzer/lib/rules/security/S050_reference_tokens_entropy.dart +234 -0
  121. package/dart_analyzer/lib/rules/security/S051_password_length_policy.dart +171 -0
  122. package/dart_analyzer/lib/rules/security/S052_weak_otp_entropy.dart +107 -0
  123. package/dart_analyzer/lib/rules/security/S053_generic_error_messages.dart +159 -0
  124. package/dart_analyzer/lib/rules/security/S054_no_default_accounts.dart +141 -0
  125. package/dart_analyzer/lib/rules/security/S055_content_type_validation.dart +324 -0
  126. package/dart_analyzer/lib/rules/security/S056_log_injection_protection.dart +119 -0
  127. package/dart_analyzer/lib/rules/security/S057_utc_logging.dart +114 -0
  128. package/dart_analyzer/lib/rules/security/S058_no_ssrf.dart +175 -0
  129. package/dart_analyzer/lib/rules/security/S059_disable_debug_mode.dart +172 -0
  130. package/dart_analyzer/lib/rules/security/S060_password_minimum_length.dart +170 -0
  131. package/dart_analyzer/lib/symbol_table_extractor.dart +510 -0
  132. package/dart_analyzer/lib/utils/common_utils.dart +26 -0
  133. package/dart_analyzer/pubspec.lock +557 -0
  134. package/dart_analyzer/pubspec.yaml +39 -0
  135. package/dart_analyzer/test/fixtures/complex_code.dart +95 -0
  136. package/docs/GENERATED_FILE_HANDLING_SUMMARY.md +2 -2
  137. package/package.json +3 -2
@@ -0,0 +1,257 @@
1
+ import 'package:analyzer/dart/ast/ast.dart';
2
+ import 'package:analyzer/dart/ast/visitor.dart';
3
+ import 'package:analyzer/source/line_info.dart';
4
+
5
+ import '../../models/rule.dart';
6
+ import '../../models/violation.dart';
7
+ import '../base_analyzer.dart';
8
+
9
+ /// D010: Limit Cyclomatic Complexity
10
+ /// Functions should not have high cyclomatic complexity to maintain readability
11
+ class D010LimitCyclomaticComplexityAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'D010';
14
+
15
+ // Default maximum cyclomatic complexity
16
+ static const int _defaultMaxComplexity = 10;
17
+
18
+ @override
19
+ List<Violation> analyze({
20
+ required CompilationUnit unit,
21
+ required String filePath,
22
+ required Rule rule,
23
+ required LineInfo lineInfo,
24
+ }) {
25
+ final violations = <Violation>[];
26
+
27
+ // Get maxComplexity from rule config, default to 10
28
+ final maxComplexity = (rule.config['maxComplexity'] as int?) ?? _defaultMaxComplexity;
29
+
30
+ final visitor = _D010Visitor(
31
+ filePath: filePath,
32
+ lineInfo: lineInfo,
33
+ violations: violations,
34
+ analyzer: this,
35
+ maxComplexity: maxComplexity,
36
+ );
37
+
38
+ unit.accept(visitor);
39
+
40
+ return violations;
41
+ }
42
+ }
43
+
44
+ class _D010Visitor extends RecursiveAstVisitor<void> {
45
+ final String filePath;
46
+ final LineInfo lineInfo;
47
+ final List<Violation> violations;
48
+ final D010LimitCyclomaticComplexityAnalyzer analyzer;
49
+ final int maxComplexity;
50
+
51
+ _D010Visitor({
52
+ required this.filePath,
53
+ required this.lineInfo,
54
+ required this.violations,
55
+ required this.analyzer,
56
+ required this.maxComplexity,
57
+ });
58
+
59
+ @override
60
+ void visitFunctionDeclaration(FunctionDeclaration node) {
61
+ final complexity = _calculateComplexity(node.functionExpression.body);
62
+ if (complexity > maxComplexity) {
63
+ violations.add(analyzer.createViolation(
64
+ filePath: filePath,
65
+ line: analyzer.getLine(lineInfo, node.name.offset),
66
+ column: analyzer.getColumn(lineInfo, node.name.offset),
67
+ message: 'Function "${node.name.lexeme}" has cyclomatic complexity of $complexity, exceeding the limit of $maxComplexity',
68
+ ));
69
+ }
70
+ super.visitFunctionDeclaration(node);
71
+ }
72
+
73
+ @override
74
+ void visitMethodDeclaration(MethodDeclaration node) {
75
+ final complexity = _calculateComplexity(node.body);
76
+ if (complexity > maxComplexity) {
77
+ violations.add(analyzer.createViolation(
78
+ filePath: filePath,
79
+ line: analyzer.getLine(lineInfo, node.name.offset),
80
+ column: analyzer.getColumn(lineInfo, node.name.offset),
81
+ message: 'Method "${node.name.lexeme}" has cyclomatic complexity of $complexity, exceeding the limit of $maxComplexity',
82
+ ));
83
+ }
84
+ super.visitMethodDeclaration(node);
85
+ }
86
+
87
+ @override
88
+ void visitConstructorDeclaration(ConstructorDeclaration node) {
89
+ final complexity = _calculateComplexity(node.body);
90
+ if (complexity > maxComplexity) {
91
+ final name = node.name?.lexeme ?? '(unnamed constructor)';
92
+ violations.add(analyzer.createViolation(
93
+ filePath: filePath,
94
+ line: analyzer.getLine(lineInfo, node.offset),
95
+ column: analyzer.getColumn(lineInfo, node.offset),
96
+ message: 'Constructor "$name" has cyclomatic complexity of $complexity, exceeding the limit of $maxComplexity',
97
+ ));
98
+ }
99
+ super.visitConstructorDeclaration(node);
100
+ }
101
+
102
+ /// Calculate cyclomatic complexity for a function body
103
+ int _calculateComplexity(FunctionBody? body) {
104
+ if (body == null) return 1;
105
+
106
+ final complexityVisitor = _ComplexityVisitor();
107
+ body.accept(complexityVisitor);
108
+
109
+ // Cyclomatic complexity starts at 1 for the function itself
110
+ return 1 + complexityVisitor.complexity;
111
+ }
112
+ }
113
+
114
+ /// Visitor to calculate cyclomatic complexity
115
+ class _ComplexityVisitor extends RecursiveAstVisitor<void> {
116
+ int complexity = 0;
117
+
118
+ @override
119
+ void visitIfStatement(IfStatement node) {
120
+ // if statement adds 1
121
+ complexity++;
122
+ super.visitIfStatement(node);
123
+ }
124
+
125
+ @override
126
+ void visitConditionalExpression(ConditionalExpression node) {
127
+ // ternary operator (? :) adds 1
128
+ complexity++;
129
+ super.visitConditionalExpression(node);
130
+ }
131
+
132
+ @override
133
+ void visitForStatement(ForStatement node) {
134
+ // for loop adds 1
135
+ complexity++;
136
+ super.visitForStatement(node);
137
+ }
138
+
139
+ @override
140
+ void visitForElement(ForElement node) {
141
+ // for in collection adds 1
142
+ complexity++;
143
+ super.visitForElement(node);
144
+ }
145
+
146
+ @override
147
+ void visitWhileStatement(WhileStatement node) {
148
+ // while loop adds 1
149
+ complexity++;
150
+ super.visitWhileStatement(node);
151
+ }
152
+
153
+ @override
154
+ void visitDoStatement(DoStatement node) {
155
+ // do-while loop adds 1
156
+ complexity++;
157
+ super.visitDoStatement(node);
158
+ }
159
+
160
+ @override
161
+ void visitCatchClause(CatchClause node) {
162
+ // catch adds 1
163
+ complexity++;
164
+ super.visitCatchClause(node);
165
+ }
166
+
167
+ @override
168
+ void visitSwitchStatement(SwitchStatement node) {
169
+ // Each case adds 1 (except default or last case)
170
+ final members = node.members;
171
+ if (members.isNotEmpty) {
172
+ // Count all SwitchCase members except the last one
173
+ for (int i = 0; i < members.length - 1; i++) {
174
+ final member = members[i];
175
+ // Only count SwitchCase (not SwitchDefault)
176
+ if (member is SwitchCase || member is SwitchPatternCase) {
177
+ complexity++;
178
+ }
179
+ }
180
+ // Don't count the last case (regardless of whether it's SwitchCase or SwitchDefault)
181
+ }
182
+ super.visitSwitchStatement(node);
183
+ }
184
+
185
+ @override
186
+ void visitSwitchExpression(SwitchExpression node) {
187
+ // Each case in switch expression adds 1 (except last)
188
+ final cases = node.cases;
189
+ if (cases.isNotEmpty) {
190
+ // All cases except the last one
191
+ complexity += cases.length - 1;
192
+ }
193
+ super.visitSwitchExpression(node);
194
+ }
195
+
196
+ @override
197
+ void visitBinaryExpression(BinaryExpression node) {
198
+ // && and || operators add 1
199
+ final operator = node.operator.lexeme;
200
+ if (operator == '&&' || operator == '||') {
201
+ complexity++;
202
+ }
203
+ super.visitBinaryExpression(node);
204
+ }
205
+
206
+ @override
207
+ void visitIfElement(IfElement node) {
208
+ // if in collection literal adds 1
209
+ complexity++;
210
+ super.visitIfElement(node);
211
+ }
212
+
213
+ @override
214
+ void visitPropertyAccess(PropertyAccess node) {
215
+ // Null-aware operator ?. adds 1
216
+ if (node.operator.lexeme == '?.') {
217
+ complexity++;
218
+ }
219
+ super.visitPropertyAccess(node);
220
+ }
221
+
222
+ @override
223
+ void visitMethodInvocation(MethodInvocation node) {
224
+ // Null-aware operator ?. adds 1
225
+ if (node.operator?.lexeme == '?.') {
226
+ complexity++;
227
+ }
228
+ super.visitMethodInvocation(node);
229
+ }
230
+
231
+ @override
232
+ void visitIndexExpression(IndexExpression node) {
233
+ // Null-aware operator ?[] adds 1
234
+ if (node.question != null) {
235
+ complexity++;
236
+ }
237
+ super.visitIndexExpression(node);
238
+ }
239
+
240
+ @override
241
+ void visitSpreadElement(SpreadElement node) {
242
+ // Null-aware spread ...? adds 1
243
+ if (node.spreadOperator.lexeme == '...?') {
244
+ complexity++;
245
+ }
246
+ super.visitSpreadElement(node);
247
+ }
248
+
249
+ @override
250
+ void visitAssignmentExpression(AssignmentExpression node) {
251
+ // ??= operator adds 1
252
+ if (node.operator.lexeme == '??=') {
253
+ complexity++;
254
+ }
255
+ super.visitAssignmentExpression(node);
256
+ }
257
+ }
@@ -0,0 +1,152 @@
1
+ import 'package:analyzer/dart/ast/ast.dart';
2
+ import 'package:analyzer/dart/ast/visitor.dart';
3
+ import 'package:analyzer/source/line_info.dart';
4
+
5
+ import '../../models/rule.dart';
6
+ import '../../models/violation.dart';
7
+ import '../base_analyzer.dart';
8
+
9
+ /// D011: Prefer Named Parameters
10
+ /// Functions with more than 3 parameters and adjacent parameters of the same type should use named parameters
11
+ class D011PreferNamedParametersAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'D011';
14
+
15
+ // Default minimum parameter count to trigger the check
16
+ static const int _defaultMinParameterCount = 3;
17
+
18
+ @override
19
+ List<Violation> analyze({
20
+ required CompilationUnit unit,
21
+ required String filePath,
22
+ required Rule rule,
23
+ required LineInfo lineInfo,
24
+ }) {
25
+ final violations = <Violation>[];
26
+
27
+ // Get minParameterCount from rule config, default to 3
28
+ final minParameterCount = (rule.config['minParameterCount'] as int?) ?? _defaultMinParameterCount;
29
+
30
+ final visitor = _D011Visitor(
31
+ filePath: filePath,
32
+ lineInfo: lineInfo,
33
+ violations: violations,
34
+ analyzer: this,
35
+ minParameterCount: minParameterCount,
36
+ );
37
+
38
+ unit.accept(visitor);
39
+
40
+ return violations;
41
+ }
42
+ }
43
+
44
+ class _D011Visitor extends RecursiveAstVisitor<void> {
45
+ final String filePath;
46
+ final LineInfo lineInfo;
47
+ final List<Violation> violations;
48
+ final D011PreferNamedParametersAnalyzer analyzer;
49
+ final int minParameterCount;
50
+
51
+ _D011Visitor({
52
+ required this.filePath,
53
+ required this.lineInfo,
54
+ required this.violations,
55
+ required this.analyzer,
56
+ required this.minParameterCount,
57
+ });
58
+
59
+ @override
60
+ void visitFunctionDeclaration(FunctionDeclaration node) {
61
+ _checkParameters(
62
+ node.functionExpression.parameters,
63
+ node.name.lexeme,
64
+ node.name.offset,
65
+ );
66
+ super.visitFunctionDeclaration(node);
67
+ }
68
+
69
+ @override
70
+ void visitMethodDeclaration(MethodDeclaration node) {
71
+ _checkParameters(
72
+ node.parameters,
73
+ node.name.lexeme,
74
+ node.name.offset,
75
+ );
76
+ super.visitMethodDeclaration(node);
77
+ }
78
+
79
+ @override
80
+ void visitConstructorDeclaration(ConstructorDeclaration node) {
81
+ final name = node.name?.lexeme ?? '(unnamed constructor)';
82
+ _checkParameters(
83
+ node.parameters,
84
+ name,
85
+ node.offset,
86
+ );
87
+ super.visitConstructorDeclaration(node);
88
+ }
89
+
90
+ void _checkParameters(FormalParameterList? parameters, String name, int offset) {
91
+ if (parameters == null) return;
92
+
93
+ final allParams = parameters.parameters;
94
+ if (allParams.length <= minParameterCount) return;
95
+
96
+ // Check if there are already named parameters
97
+ final hasNamedParams = allParams.any((p) => p.isNamed);
98
+
99
+ // Get only positional parameters for checking
100
+ final positionalParams = allParams.where((p) => !p.isNamed).toList();
101
+
102
+ if (positionalParams.length <= minParameterCount) return;
103
+
104
+ // Check for adjacent parameters with the same type
105
+ bool hasAdjacentSameType = _hasAdjacentSameTypeParameters(positionalParams);
106
+
107
+ if (hasAdjacentSameType) {
108
+ violations.add(analyzer.createViolation(
109
+ filePath: filePath,
110
+ line: analyzer.getLine(lineInfo, offset),
111
+ column: analyzer.getColumn(lineInfo, offset),
112
+ message: hasNamedParams
113
+ ? 'Function "$name" has ${positionalParams.length} positional parameters with adjacent same-type parameters. Consider using named parameters for all non-required parameters.'
114
+ : 'Function "$name" has ${allParams.length} parameters with adjacent same-type parameters. Consider using named parameters.',
115
+ ));
116
+ }
117
+ }
118
+
119
+ bool _hasAdjacentSameTypeParameters(List<FormalParameter> params) {
120
+ if (params.length < 2) return false;
121
+
122
+ for (int i = 0; i < params.length - 1; i++) {
123
+ final currentType = _getParameterType(params[i]);
124
+ final nextType = _getParameterType(params[i + 1]);
125
+
126
+ // If both types are not null and they match, we have adjacent same-type parameters
127
+ if (currentType != null && nextType != null && currentType == nextType) {
128
+ return true;
129
+ }
130
+ }
131
+
132
+ return false;
133
+ }
134
+
135
+ String? _getParameterType(FormalParameter param) {
136
+ // Handle different parameter types
137
+ if (param is SimpleFormalParameter) {
138
+ return param.type?.toSource();
139
+ } else if (param is DefaultFormalParameter) {
140
+ final innerParam = param.parameter;
141
+ if (innerParam is SimpleFormalParameter) {
142
+ return innerParam.type?.toSource();
143
+ } else if (innerParam is FieldFormalParameter) {
144
+ return innerParam.type?.toSource();
145
+ }
146
+ } else if (param is FieldFormalParameter) {
147
+ return param.type?.toSource();
148
+ }
149
+
150
+ return null;
151
+ }
152
+ }
@@ -0,0 +1,156 @@
1
+ import 'package:analyzer/dart/ast/ast.dart';
2
+ import 'package:analyzer/dart/ast/visitor.dart';
3
+ import 'package:analyzer/source/line_info.dart';
4
+
5
+ import '../../models/rule.dart';
6
+ import '../../models/violation.dart';
7
+ import '../base_analyzer.dart';
8
+
9
+ /// D012: Prefer Named Boolean Parameters
10
+ /// Boolean parameters should be named or use separate functions for better readability
11
+ class D012PreferNamedBooleanParametersAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'D012';
14
+
15
+ @override
16
+ List<Violation> analyze({
17
+ required CompilationUnit unit,
18
+ required String filePath,
19
+ required Rule rule,
20
+ required LineInfo lineInfo,
21
+ }) {
22
+ final violations = <Violation>[];
23
+
24
+ final visitor = _D012Visitor(
25
+ filePath: filePath,
26
+ lineInfo: lineInfo,
27
+ violations: violations,
28
+ analyzer: this,
29
+ );
30
+
31
+ unit.accept(visitor);
32
+
33
+ return violations;
34
+ }
35
+ }
36
+
37
+ class _D012Visitor extends RecursiveAstVisitor<void> {
38
+ final String filePath;
39
+ final LineInfo lineInfo;
40
+ final List<Violation> violations;
41
+ final D012PreferNamedBooleanParametersAnalyzer analyzer;
42
+
43
+ _D012Visitor({
44
+ required this.filePath,
45
+ required this.lineInfo,
46
+ required this.violations,
47
+ required this.analyzer,
48
+ });
49
+
50
+ @override
51
+ void visitFunctionDeclaration(FunctionDeclaration node) {
52
+ _checkBooleanParameters(
53
+ node.functionExpression.parameters,
54
+ node.name.lexeme,
55
+ node.name.offset,
56
+ );
57
+ super.visitFunctionDeclaration(node);
58
+ }
59
+
60
+ @override
61
+ void visitMethodDeclaration(MethodDeclaration node) {
62
+ _checkBooleanParameters(
63
+ node.parameters,
64
+ node.name.lexeme,
65
+ node.name.offset,
66
+ );
67
+ super.visitMethodDeclaration(node);
68
+ }
69
+
70
+ @override
71
+ void visitConstructorDeclaration(ConstructorDeclaration node) {
72
+ final name = node.name?.lexeme ?? '(unnamed constructor)';
73
+ _checkBooleanParameters(
74
+ node.parameters,
75
+ name,
76
+ node.offset,
77
+ );
78
+ super.visitConstructorDeclaration(node);
79
+ }
80
+
81
+ void _checkBooleanParameters(FormalParameterList? parameters, String name, int offset) {
82
+ if (parameters == null) return;
83
+
84
+ final allParams = parameters.parameters;
85
+ if (allParams.isEmpty) return;
86
+
87
+ // Get positional boolean parameters
88
+ final positionalBools = <FormalParameter>[];
89
+ final positionalParams = <FormalParameter>[];
90
+
91
+ for (final param in allParams) {
92
+ if (!param.isNamed) {
93
+ positionalParams.add(param);
94
+ if (_isBooleanParameter(param)) {
95
+ positionalBools.add(param);
96
+ }
97
+ }
98
+ }
99
+
100
+ if (positionalBools.isEmpty) return;
101
+
102
+ // Case 1: Single boolean parameter (or only boolean parameters)
103
+ if (positionalParams.length <= 2 && positionalBools.length == 1) {
104
+ violations.add(analyzer.createViolation(
105
+ filePath: filePath,
106
+ line: analyzer.getLine(lineInfo, offset),
107
+ column: analyzer.getColumn(lineInfo, offset),
108
+ message: 'Function "$name" has a boolean parameter. Consider creating separate functions (e.g., ${_generateSeparateFunctionSuggestion(name)}) instead of using a boolean flag.',
109
+ ));
110
+ }
111
+ // Case 2: Multiple parameters with boolean(s)
112
+ else if (positionalBools.isNotEmpty) {
113
+ violations.add(analyzer.createViolation(
114
+ filePath: filePath,
115
+ line: analyzer.getLine(lineInfo, offset),
116
+ column: analyzer.getColumn(lineInfo, offset),
117
+ message: 'Function "$name" has boolean parameter(s). Consider using named parameters for boolean values to improve readability at call sites.',
118
+ ));
119
+ }
120
+ }
121
+
122
+ bool _isBooleanParameter(FormalParameter param) {
123
+ String? typeName;
124
+
125
+ if (param is SimpleFormalParameter) {
126
+ typeName = param.type?.toSource();
127
+ } else if (param is DefaultFormalParameter) {
128
+ final innerParam = param.parameter;
129
+ if (innerParam is SimpleFormalParameter) {
130
+ typeName = innerParam.type?.toSource();
131
+ } else if (innerParam is FieldFormalParameter) {
132
+ typeName = innerParam.type?.toSource();
133
+ }
134
+ } else if (param is FieldFormalParameter) {
135
+ typeName = param.type?.toSource();
136
+ }
137
+
138
+ return typeName == 'bool' || typeName == 'bool?';
139
+ }
140
+
141
+ String _generateSeparateFunctionSuggestion(String functionName) {
142
+ // Generate suggestion for separate function names
143
+ // e.g., "setUser" -> "enableUser/disableUser" or "setUserEnabled/setUserDisabled"
144
+ if (functionName.startsWith('set')) {
145
+ final base = functionName.substring(3);
146
+ return 'enable$base/disable$base';
147
+ } else if (functionName.startsWith('get')) {
148
+ final base = functionName.substring(3);
149
+ return 'get${base}Enabled/get${base}Disabled';
150
+ } else if (functionName.startsWith('is')) {
151
+ return functionName; // is methods are fine
152
+ } else {
153
+ return '${functionName}Enabled/${functionName}Disabled';
154
+ }
155
+ }
156
+ }