@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,219 @@
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
+ /// C006: Function Naming Convention
10
+ /// Ensures function names follow verb-noun pattern
11
+ /// - Functions should start with a verb
12
+ /// - Allows common patterns: get, set, is, has, can, create, etc.
13
+ class C006FunctionNamingAnalyzer extends BaseAnalyzer {
14
+ @override
15
+ String get ruleId => 'C006';
16
+
17
+ /// Accepted verb prefixes for function names
18
+ static final Set<String> acceptedVerbs = {
19
+ // Getters/Queries
20
+ 'get', 'fetch', 'retrieve', 'find', 'search', 'query', 'load', 'read',
21
+ // Setters/Modifiers
22
+ 'set', 'update', 'modify', 'change', 'edit', 'alter', 'transform', 'write',
23
+ // Creation
24
+ 'create', 'build', 'make', 'generate', 'construct', 'produce', 'new',
25
+ // Deletion
26
+ 'delete', 'remove', 'destroy', 'clean', 'clear', 'reset', 'dispose',
27
+ // Validation
28
+ 'validate', 'verify', 'check', 'confirm', 'ensure', 'test', 'compare',
29
+ // Computation
30
+ 'calculate', 'compute', 'parse', 'format', 'convert', 'process',
31
+ // Communication
32
+ 'send', 'receive', 'transmit', 'broadcast', 'emit', 'publish', 'notify',
33
+ // Collections
34
+ 'map', 'filter', 'sort', 'group', 'merge', 'split', 'add', 'append',
35
+ 'insert', 'push', 'pop', 'shift',
36
+ // State checks (Boolean)
37
+ 'is', 'has', 'can', 'should', 'will', 'does', 'contains', 'includes', 'exists',
38
+ // UI actions
39
+ 'show', 'hide', 'display', 'render', 'draw', 'toggle', 'enable', 'disable',
40
+ 'activate', 'deactivate', 'select', 'deselect', 'focus', 'blur',
41
+ // Lifecycle
42
+ 'connect', 'disconnect', 'open', 'close', 'start', 'stop', 'run', 'refresh',
43
+ 'init', 'initialize', 'setup', 'teardown', 'shutdown', 'restart', 'reload',
44
+ 'restore', 'resume', 'pause', 'suspend',
45
+ // Event Handling
46
+ 'on', 'trigger', 'fire', 'dispatch', 'invoke', 'call', 'handle',
47
+ // Monitoring
48
+ 'count', 'measure', 'monitor', 'watch', 'track', 'observe', 'log', 'record',
49
+ // Navigation
50
+ 'navigate', 'redirect', 'route', 'go', 'move', 'scroll',
51
+ // Data Operations
52
+ 'save', 'store', 'persist', 'cache', 'serialize', 'deserialize',
53
+ // Transformation
54
+ 'normalize', 'sanitize', 'encode', 'decode', 'encrypt', 'decrypt',
55
+ 'compress', 'decompress', 'stringify',
56
+ // State Management
57
+ 'apply', 'revert', 'undo', 'redo', 'commit', 'rollback',
58
+ // Async Operations
59
+ 'await', 'defer', 'debounce', 'throttle', 'delay',
60
+ // Error Handling
61
+ 'recover', 'retry',
62
+ // Copying
63
+ 'copy', 'clone', 'duplicate', 'replicate',
64
+ // Comparison
65
+ 'equals', 'match', 'diff',
66
+ // Printing/Output
67
+ 'print', 'debug', 'trace', 'dump',
68
+ // Try operations
69
+ 'try',
70
+ // other common verbs
71
+ 'launch', 'install', 'uninstall', 'upload', 'download', 'backup',
72
+ 'sync', 'synchronize', 'optimize', 'cleanse', 'finalize', 'do',
73
+ 'tracking', 'submit', 'approve', 'reject', 'archive', 'unarchive',
74
+ 'input', 'enter', 'exit', 'wrap', 'unwrap', 'increment', 'decrement',
75
+ 'post', 'patch', 'put', 'drop', 'bind', 'unbind', 'flatten', 'expand',
76
+ 'elevate', 'demote', 'promote', 'subscribe', 'unsubscribe', 'share',
77
+ 'like', 'comment', 'follow', 'unfollow', 'rate', 'review', 'report',
78
+ 'block', 'unblock', 'mute', 'unmute', 'pin', 'unpin', 'highlight',
79
+ 'unhighlight', 'skip', 'mark', 'tag', 'untag', 'flag', 'unflag', 'login',
80
+ 'logout', 'did', 'complete', 'view', 'listen', 'return', 'switch', 'next',
81
+ 'previous', 'recommend', 'suggest', 'explore', 'discover', 'arrange',
82
+ 'link', 'unlink', 'calibrate', 'diagnose', 'repair', 'maintain', 'upgrade',
83
+ 'downgrade', 'authen', 'author', 'play', 'stream', 'cast', 'mirror',
84
+ 'reflect', 'seek', 'fit', 'crop', 'resize', 'rotate', 'enhance', 'adjust',
85
+ 'blend', 'separate', 'isolate', 'synthesize', 'analyze', 'visualize',
86
+ 'simulate', 'model', 'fill', 'stroke', 'outline', 'shade', 'animate',
87
+ 'gen', 'calc', 'click', 'tap', 'swipe', 'pinch', 'zoom', 'drag', 'press',
88
+ };
89
+
90
+ /// Special functions that are allowed without verb prefix
91
+ static final Set<String> specialFunctions = {
92
+ // Dart lifecycle
93
+ 'main', 'build', 'dispose', 'initState', 'didChangeDependencies',
94
+ 'didUpdateWidget', 'deactivate', 'reassemble',
95
+ // Object methods
96
+ 'toString', 'toJson', 'fromJson', 'toMap', 'fromMap',
97
+ 'hashCode', 'compareTo', 'noSuchMethod',
98
+ // Factory constructors often use 'of' or 'from'
99
+ 'of', 'from', 'parse', 'tryParse',
100
+ // Common widget methods
101
+ 'createState',
102
+ // Operators
103
+ 'operator',
104
+ };
105
+
106
+ /// Patterns that indicate the function is a callback/handler
107
+ static final RegExp callbackPattern = RegExp(
108
+ r'^(on[A-Z]|_on[A-Z]|handle[A-Z]|_handle[A-Z])',
109
+ );
110
+
111
+ @override
112
+ List<Violation> analyze({
113
+ required CompilationUnit unit,
114
+ required String filePath,
115
+ required Rule rule,
116
+ required LineInfo lineInfo,
117
+ }) {
118
+ final violations = <Violation>[];
119
+
120
+ final visitor = _FunctionNamingVisitor(
121
+ filePath: filePath,
122
+ lineInfo: lineInfo,
123
+ violations: violations,
124
+ analyzer: this,
125
+ );
126
+
127
+ unit.accept(visitor);
128
+
129
+ return violations;
130
+ }
131
+
132
+ /// Check if a function name starts with an accepted verb
133
+ static bool startsWithVerb(String name) {
134
+ // Remove leading underscore for private functions
135
+ final cleanName = name.replaceAll(RegExp(r'^_+'), '');
136
+ if (cleanName.isEmpty) return true;
137
+
138
+ // Check if it's a special function
139
+ if (specialFunctions.contains(cleanName)) return true;
140
+
141
+ // Check callback pattern
142
+ if (callbackPattern.hasMatch(name)) return true;
143
+
144
+ // Check if starts with any accepted verb
145
+ final lowerName = cleanName.toLowerCase();
146
+ for (final verb in acceptedVerbs) {
147
+ if (lowerName.startsWith(verb)) {
148
+ // Must be followed by uppercase or end of string (verb-noun pattern)
149
+ if (lowerName == verb) return true;
150
+ final afterVerb = cleanName.substring(verb.length);
151
+ if (afterVerb.isNotEmpty && afterVerb[0] == afterVerb[0].toUpperCase()) {
152
+ return true;
153
+ }
154
+ }
155
+ }
156
+
157
+ return false;
158
+ }
159
+ }
160
+
161
+ class _FunctionNamingVisitor extends RecursiveAstVisitor<void> {
162
+ final String filePath;
163
+ final LineInfo lineInfo;
164
+ final List<Violation> violations;
165
+ final C006FunctionNamingAnalyzer analyzer;
166
+
167
+ _FunctionNamingVisitor({
168
+ required this.filePath,
169
+ required this.lineInfo,
170
+ required this.violations,
171
+ required this.analyzer,
172
+ });
173
+
174
+ @override
175
+ void visitFunctionDeclaration(FunctionDeclaration node) {
176
+ _checkFunctionName(node.name.lexeme, node.name.offset, 'Function');
177
+ super.visitFunctionDeclaration(node);
178
+ }
179
+
180
+ @override
181
+ void visitMethodDeclaration(MethodDeclaration node) {
182
+ // Skip operators and property accessors
183
+ if (node.isOperator) return;
184
+ if (node.isGetter || node.isSetter) return;
185
+
186
+ _checkFunctionName(node.name.lexeme, node.name.offset, 'Method');
187
+ super.visitMethodDeclaration(node);
188
+ }
189
+
190
+ void _checkFunctionName(String name, int offset, String type) {
191
+ // Skip private functions starting with underscore followed by lowercase
192
+ // (often internal helpers)
193
+ if (name.startsWith('_') && name.length > 1) {
194
+ final afterUnderscore = name.substring(1);
195
+ if (afterUnderscore.isNotEmpty &&
196
+ afterUnderscore[0] == afterUnderscore[0].toLowerCase()) {
197
+ // Still check if it follows verb pattern
198
+ if (C006FunctionNamingAnalyzer.startsWithVerb(afterUnderscore)) {
199
+ return;
200
+ }
201
+ }
202
+ }
203
+
204
+ if (!C006FunctionNamingAnalyzer.startsWithVerb(name)) {
205
+ violations.add(analyzer.createViolation(
206
+ filePath: filePath,
207
+ line: analyzer.getLine(lineInfo, offset),
208
+ column: analyzer.getColumn(lineInfo, offset),
209
+ message:
210
+ '$type name "$name" should start with a verb (e.g., getSuccessList, createUser...)',
211
+ metadata: {
212
+ 'name': name,
213
+ 'type': type.toLowerCase(),
214
+ 'suggestion': '',
215
+ },
216
+ ));
217
+ }
218
+ }
219
+ }
@@ -0,0 +1,205 @@
1
+ import 'dart:io';
2
+
3
+ import 'package:analyzer/dart/ast/ast.dart';
4
+ import 'package:analyzer/dart/ast/visitor.dart';
5
+ import 'package:analyzer/source/line_info.dart';
6
+ import 'package:sunlint_dart_analyzer/utils/common_utils.dart';
7
+
8
+ import '../../models/rule.dart';
9
+ import '../../models/violation.dart';
10
+ import '../base_analyzer.dart';
11
+
12
+ /// C008: Variable Declaration Locality
13
+ /// Variables should be declared close to where they are first used
14
+ class C008VariableDeclarationLocalityAnalyzer extends BaseAnalyzer {
15
+ @override
16
+ String get ruleId => 'C008';
17
+
18
+ /// Maximum allowed distance between declaration and first usage
19
+ static const int maxLineDistance = 10;
20
+
21
+ @override
22
+ List<Violation> analyze({
23
+ required CompilationUnit unit,
24
+ required String filePath,
25
+ required Rule rule,
26
+ required LineInfo lineInfo,
27
+ }) {
28
+ final violations = <Violation>[];
29
+ final sourceContent = _readSourceContent(filePath);
30
+
31
+ final visitor = _VariableLocalityVisitor(
32
+ filePath: filePath,
33
+ lineInfo: lineInfo,
34
+ violations: violations,
35
+ analyzer: this,
36
+ sourceContent: sourceContent,
37
+ );
38
+
39
+ unit.accept(visitor);
40
+
41
+ return violations;
42
+ }
43
+
44
+ String _readSourceContent(String filePath) {
45
+ try {
46
+ return File(filePath).readAsStringSync();
47
+ } catch (e) {
48
+ return '';
49
+ }
50
+ }
51
+ }
52
+
53
+ class _VariableLocalityVisitor extends RecursiveAstVisitor<void> {
54
+ final String filePath;
55
+ final LineInfo lineInfo;
56
+ final List<Violation> violations;
57
+ final C008VariableDeclarationLocalityAnalyzer analyzer;
58
+ final String sourceContent;
59
+ late final List<String> _sourceLines;
60
+
61
+ _VariableLocalityVisitor({
62
+ required this.filePath,
63
+ required this.lineInfo,
64
+ required this.violations,
65
+ required this.analyzer,
66
+ required this.sourceContent,
67
+ }) {
68
+ _sourceLines = sourceContent.split('\n');
69
+ }
70
+
71
+ @override
72
+ void visitBlockFunctionBody(BlockFunctionBody node) {
73
+ _analyzeBlock(node.block);
74
+ super.visitBlockFunctionBody(node);
75
+ }
76
+
77
+ void _analyzeBlock(Block block) {
78
+ final declarations = _collectVariableDeclarations(block);
79
+ final firstUsages = _findFirstUsages(block, declarations.keys.toSet());
80
+ _reportViolationsForDistantDeclarations(declarations, firstUsages);
81
+ }
82
+
83
+ Map<String, int> _collectVariableDeclarations(Block block) {
84
+ final declarations = <String, int>{};
85
+
86
+ for (final statement in block.statements) {
87
+ if (statement is VariableDeclarationStatement) {
88
+ for (final variable in statement.variables.variables) {
89
+ final name = variable.name.lexeme;
90
+ final line = analyzer.getLine(lineInfo, variable.offset);
91
+ declarations[name] = line;
92
+ }
93
+ }
94
+ }
95
+
96
+ return declarations;
97
+ }
98
+
99
+ Map<String, int> _findFirstUsages(Block block, Set<String> variableNames) {
100
+ final usageFinder = _FirstUsageFinder(
101
+ variableNames: variableNames,
102
+ lineInfo: lineInfo,
103
+ analyzer: analyzer,
104
+ );
105
+ block.accept(usageFinder);
106
+ return usageFinder.firstUsages;
107
+ }
108
+
109
+ void _reportViolationsForDistantDeclarations(
110
+ Map<String, int> declarations,
111
+ Map<String, int> firstUsages,
112
+ ) {
113
+ for (final entry in declarations.entries) {
114
+ final name = entry.key;
115
+ final declLine = entry.value;
116
+ final firstUsageLine = firstUsages[name];
117
+
118
+ if (firstUsageLine == null) continue;
119
+
120
+ final distance = _calculateDistance(declLine, firstUsageLine);
121
+ if (_isDistanceTooFar(distance)) {
122
+ _addViolation(name, declLine, firstUsageLine, distance);
123
+ }
124
+ }
125
+ }
126
+
127
+ int _calculateDistance(int declarationLine, int firstUsageLine) {
128
+ return _countNonBlankNonCommentLines(declarationLine, firstUsageLine);
129
+ }
130
+
131
+ int _countNonBlankNonCommentLines(int startLine, int endLine) {
132
+ int count = 0;
133
+ for (int i = startLine; i < endLine; i++) {
134
+ if (CommonUtils.isCodeLine(i, _sourceLines)) {
135
+ count++;
136
+ }
137
+ }
138
+ return count;
139
+ }
140
+
141
+ bool _isDistanceTooFar(int distance) {
142
+ return distance > C008VariableDeclarationLocalityAnalyzer.maxLineDistance;
143
+ }
144
+
145
+ void _addViolation(
146
+ String variableName,
147
+ int declarationLine,
148
+ int firstUsageLine,
149
+ int distance,
150
+ ) {
151
+ violations.add(analyzer.createViolation(
152
+ filePath: filePath,
153
+ line: declarationLine,
154
+ column: 1,
155
+ message:
156
+ 'Variable "$variableName" is declared $distance lines before first use (max: ${C008VariableDeclarationLocalityAnalyzer.maxLineDistance})',
157
+ metadata: {
158
+ 'variable': variableName,
159
+ 'declarationLine': declarationLine,
160
+ 'firstUsageLine': firstUsageLine,
161
+ 'distance': distance,
162
+ },
163
+ ));
164
+ }
165
+ }
166
+
167
+ class _FirstUsageFinder extends RecursiveAstVisitor<void> {
168
+ final Set<String> variableNames;
169
+ final LineInfo lineInfo;
170
+ final C008VariableDeclarationLocalityAnalyzer analyzer;
171
+ final Map<String, int> firstUsages = {};
172
+ bool _isInsideVariableDeclaration = false;
173
+
174
+ _FirstUsageFinder({
175
+ required this.variableNames,
176
+ required this.lineInfo,
177
+ required this.analyzer,
178
+ });
179
+
180
+ @override
181
+ void visitVariableDeclaration(VariableDeclaration node) {
182
+ _isInsideVariableDeclaration = true;
183
+ super.visitVariableDeclaration(node);
184
+ _isInsideVariableDeclaration = false;
185
+ }
186
+
187
+ @override
188
+ void visitSimpleIdentifier(SimpleIdentifier node) {
189
+ if (_shouldRecordUsage(node)) {
190
+ _recordFirstUsageIfNotExists(node);
191
+ }
192
+ super.visitSimpleIdentifier(node);
193
+ }
194
+
195
+ bool _shouldRecordUsage(SimpleIdentifier node) {
196
+ return !_isInsideVariableDeclaration && variableNames.contains(node.name);
197
+ }
198
+
199
+ void _recordFirstUsageIfNotExists(SimpleIdentifier node) {
200
+ if (firstUsages.containsKey(node.name)) return;
201
+
202
+ final line = analyzer.getLine(lineInfo, node.offset);
203
+ firstUsages[node.name] = line;
204
+ }
205
+ }
@@ -0,0 +1,162 @@
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
+ /// C010: Limit Block Nesting
10
+ /// Limit nested blocks (if/for/while/switch/try) to maximum 3 levels
11
+ class C010LimitBlockNestingAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'C010';
14
+
15
+ /// Maximum allowed nesting depth
16
+ static const int maxNestingDepth = 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
+ final visitor = _NestingVisitor(
28
+ filePath: filePath,
29
+ lineInfo: lineInfo,
30
+ violations: violations,
31
+ analyzer: this,
32
+ );
33
+
34
+ unit.accept(visitor);
35
+
36
+ return violations;
37
+ }
38
+ }
39
+
40
+ class _NestingVisitor extends RecursiveAstVisitor<void> {
41
+ final String filePath;
42
+ final LineInfo lineInfo;
43
+ final List<Violation> violations;
44
+ final C010LimitBlockNestingAnalyzer analyzer;
45
+ int currentDepth = 0;
46
+ bool _hasReportedInCurrentBlockChain = false;
47
+
48
+ _NestingVisitor({
49
+ required this.filePath,
50
+ required this.lineInfo,
51
+ required this.violations,
52
+ required this.analyzer,
53
+ });
54
+
55
+ void _enterBlock(AstNode node) {
56
+ currentDepth++;
57
+ if (_shouldReportViolation()) {
58
+ _reportViolation(node);
59
+ }
60
+ }
61
+
62
+ bool _shouldReportViolation() {
63
+ return currentDepth > C010LimitBlockNestingAnalyzer.maxNestingDepth &&
64
+ !_hasReportedInCurrentBlockChain;
65
+ }
66
+
67
+ void _reportViolation(AstNode node) {
68
+ _hasReportedInCurrentBlockChain = true;
69
+ violations.add(analyzer.createViolation(
70
+ filePath: filePath,
71
+ line: analyzer.getLine(lineInfo, node.offset),
72
+ column: analyzer.getColumn(lineInfo, node.offset),
73
+ message:
74
+ 'Nesting depth $currentDepth exceeds maximum of ${C010LimitBlockNestingAnalyzer.maxNestingDepth}',
75
+ metadata: {
76
+ 'depth': currentDepth,
77
+ 'maxDepth': C010LimitBlockNestingAnalyzer.maxNestingDepth,
78
+ },
79
+ ));
80
+ }
81
+
82
+ void _exitBlock() {
83
+ currentDepth--;
84
+ // Reset flag when we exit back to below max nesting level
85
+ if (currentDepth < C010LimitBlockNestingAnalyzer.maxNestingDepth) {
86
+ _hasReportedInCurrentBlockChain = false;
87
+ }
88
+ }
89
+
90
+ @override
91
+ void visitIfStatement(IfStatement node) {
92
+ _enterBlock(node);
93
+ super.visitIfStatement(node);
94
+ _exitBlock();
95
+ }
96
+
97
+ @override
98
+ void visitForStatement(ForStatement node) {
99
+ _enterBlock(node);
100
+ super.visitForStatement(node);
101
+ _exitBlock();
102
+ }
103
+
104
+ @override
105
+ void visitForElement(ForElement node) {
106
+ _enterBlock(node);
107
+ super.visitForElement(node);
108
+ _exitBlock();
109
+ }
110
+
111
+ @override
112
+ void visitWhileStatement(WhileStatement node) {
113
+ _enterBlock(node);
114
+ super.visitWhileStatement(node);
115
+ _exitBlock();
116
+ }
117
+
118
+ @override
119
+ void visitDoStatement(DoStatement node) {
120
+ _enterBlock(node);
121
+ super.visitDoStatement(node);
122
+ _exitBlock();
123
+ }
124
+
125
+ @override
126
+ void visitSwitchStatement(SwitchStatement node) {
127
+ _enterBlock(node);
128
+ super.visitSwitchStatement(node);
129
+ _exitBlock();
130
+ }
131
+
132
+ @override
133
+ void visitTryStatement(TryStatement node) {
134
+ _enterBlock(node);
135
+ super.visitTryStatement(node);
136
+ _exitBlock();
137
+ }
138
+
139
+
140
+ // Reset depth and flag when entering a new function/method
141
+ @override
142
+ void visitFunctionDeclaration(FunctionDeclaration node) {
143
+ final savedDepth = currentDepth;
144
+ final savedFlag = _hasReportedInCurrentBlockChain;
145
+ currentDepth = 0;
146
+ _hasReportedInCurrentBlockChain = false;
147
+ super.visitFunctionDeclaration(node);
148
+ currentDepth = savedDepth;
149
+ _hasReportedInCurrentBlockChain = savedFlag;
150
+ }
151
+
152
+ @override
153
+ void visitMethodDeclaration(MethodDeclaration node) {
154
+ final savedDepth = currentDepth;
155
+ final savedFlag = _hasReportedInCurrentBlockChain;
156
+ currentDepth = 0;
157
+ _hasReportedInCurrentBlockChain = false;
158
+ super.visitMethodDeclaration(node);
159
+ currentDepth = savedDepth;
160
+ _hasReportedInCurrentBlockChain = savedFlag;
161
+ }
162
+ }