@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.
- package/dart_analyzer/README.md +226 -0
- package/dart_analyzer/analysis_options.yaml +66 -0
- package/dart_analyzer/bin/sunlint-dart-macos +0 -0
- package/dart_analyzer/bin/sunlint_dart_analyzer.dart +124 -0
- package/dart_analyzer/lib/analyzer_service.dart +625 -0
- package/dart_analyzer/lib/json_rpc_server.dart +275 -0
- package/dart_analyzer/lib/models/rule.dart +67 -0
- package/dart_analyzer/lib/models/symbol_table.dart +607 -0
- package/dart_analyzer/lib/models/violation.dart +69 -0
- package/dart_analyzer/lib/rules/base_analyzer.dart +52 -0
- package/dart_analyzer/lib/rules/common/C002_no_duplicate_code.dart +344 -0
- package/dart_analyzer/lib/rules/common/C003_no_vague_abbreviations.dart +318 -0
- package/dart_analyzer/lib/rules/common/C006_function_naming.dart +219 -0
- package/dart_analyzer/lib/rules/common/C008_variable_declaration_locality.dart +205 -0
- package/dart_analyzer/lib/rules/common/C010_limit_block_nesting.dart +162 -0
- package/dart_analyzer/lib/rules/common/C012_command_query_separation.dart +214 -0
- package/dart_analyzer/lib/rules/common/C013_no_dead_code.dart +225 -0
- package/dart_analyzer/lib/rules/common/C014_dependency_injection.dart +249 -0
- package/dart_analyzer/lib/rules/common/C017_constructor_logic.dart +158 -0
- package/dart_analyzer/lib/rules/common/C018_no_throw_generic_error.dart +141 -0
- package/dart_analyzer/lib/rules/common/C019_log_level_usage.dart +165 -0
- package/dart_analyzer/lib/rules/common/C020_unused_imports.dart +128 -0
- package/dart_analyzer/lib/rules/common/C021_import_organization.dart +86 -0
- package/dart_analyzer/lib/rules/common/C023_no_duplicate_variable.dart +112 -0
- package/dart_analyzer/lib/rules/common/C024_no_scatter_hardcoded_constants.dart +79 -0
- package/dart_analyzer/lib/rules/common/C029_catch_block_logging.dart +81 -0
- package/dart_analyzer/lib/rules/common/C030_use_custom_error_classes.dart +77 -0
- package/dart_analyzer/lib/rules/common/C031_validation_separation.dart +90 -0
- package/dart_analyzer/lib/rules/common/C033_separate_service_repository.dart +80 -0
- package/dart_analyzer/lib/rules/common/C035_error_logging_context.dart +148 -0
- package/dart_analyzer/lib/rules/common/C040_centralized_validation.dart +84 -0
- package/dart_analyzer/lib/rules/common/C041_no_sensitive_hardcode.dart +103 -0
- package/dart_analyzer/lib/rules/common/C042_boolean_name_prefix.dart +105 -0
- package/dart_analyzer/lib/rules/common/C043_no_console_or_print.dart +101 -0
- package/dart_analyzer/lib/rules/common/C047_no_duplicate_retry_logic.dart +94 -0
- package/dart_analyzer/lib/rules/common/C048_no_bypass_architectural_layers.dart +132 -0
- package/dart_analyzer/lib/rules/common/C052_parsing_or_data_transformation.dart +95 -0
- package/dart_analyzer/lib/rules/common/C060_no_override_superclass.dart +81 -0
- package/dart_analyzer/lib/rules/common/C065_one_behavior_per_test.dart +83 -0
- package/dart_analyzer/lib/rules/common/C067_no_hardcoded_config.dart +89 -0
- package/dart_analyzer/lib/rules/common/C070_no_real_time_tests.dart +99 -0
- package/dart_analyzer/lib/rules/common/C072_single_test_behavior.dart +78 -0
- package/dart_analyzer/lib/rules/common/C073_validate_required_config_on_startup.dart +82 -0
- package/dart_analyzer/lib/rules/common/C075_explicit_return_types.dart +85 -0
- package/dart_analyzer/lib/rules/common/C076_explicit_function_types.dart +104 -0
- package/dart_analyzer/lib/rules/dart/D001_recommended_lint_rules.dart +309 -0
- package/dart_analyzer/lib/rules/dart/D002_dispose_resources.dart +338 -0
- package/dart_analyzer/lib/rules/dart/D003_prefer_widgets_over_methods.dart +273 -0
- package/dart_analyzer/lib/rules/dart/D004_avoid_shrinkwrap_listview.dart +154 -0
- package/dart_analyzer/lib/rules/dart/D005_limit_widget_nesting.dart +265 -0
- package/dart_analyzer/lib/rules/dart/D006_prefer_extracting_large_callbacks.dart +135 -0
- package/dart_analyzer/lib/rules/dart/D007_prefer_init_first_dispose_last.dart +150 -0
- package/dart_analyzer/lib/rules/dart/D008_avoid_long_functions.dart +394 -0
- package/dart_analyzer/lib/rules/dart/D009_limit_function_parameters.dart +179 -0
- package/dart_analyzer/lib/rules/dart/D010_limit_cyclomatic_complexity.dart +257 -0
- package/dart_analyzer/lib/rules/dart/D011_prefer_named_parameters.dart +152 -0
- package/dart_analyzer/lib/rules/dart/D012_prefer_named_boolean_parameters.dart +156 -0
- package/dart_analyzer/lib/rules/dart/D013_single_public_class.dart +246 -0
- package/dart_analyzer/lib/rules/dart/D014_unsafe_collection_access.dart +202 -0
- package/dart_analyzer/lib/rules/dart/D015_copywith_all_parameters.dart +125 -0
- package/dart_analyzer/lib/rules/dart/D016_project_should_have_tests.dart +134 -0
- package/dart_analyzer/lib/rules/dart/D017_pubspec_dependencies_review.dart +187 -0
- package/dart_analyzer/lib/rules/dart/D018_remove_commented_code.dart +196 -0
- package/dart_analyzer/lib/rules/dart/D019_avoid_single_child_multi_child_widget.dart +161 -0
- package/dart_analyzer/lib/rules/dart/D020_limit_if_else_branches.dart +125 -0
- package/dart_analyzer/lib/rules/dart/D021_avoid_negated_boolean_checks.dart +227 -0
- package/dart_analyzer/lib/rules/dart/D022_use_setstate_correctly.dart +269 -0
- package/dart_analyzer/lib/rules/dart/D023_avoid_unnecessary_method_overrides.dart +191 -0
- package/dart_analyzer/lib/rules/dart/D024_avoid_unnecessary_stateful_widget.dart +194 -0
- package/dart_analyzer/lib/rules/dart/D025_avoid_nested_conditional_expressions.dart +90 -0
- package/dart_analyzer/lib/rules/security/S001_backend_auth_communications.dart +155 -0
- package/dart_analyzer/lib/rules/security/S002_os_command_injection.dart +159 -0
- package/dart_analyzer/lib/rules/security/S003_open_redirect_protection.dart +208 -0
- package/dart_analyzer/lib/rules/security/S004_sensitive_data_logging.dart +391 -0
- package/dart_analyzer/lib/rules/security/S005_trusted_service_authorization.dart +182 -0
- package/dart_analyzer/lib/rules/security/S006_no_default_credentials.dart +208 -0
- package/dart_analyzer/lib/rules/security/S007_output_encoding.dart +224 -0
- package/dart_analyzer/lib/rules/security/S008_svg_content_sanitization.dart +211 -0
- package/dart_analyzer/lib/rules/security/S009_no_insecure_encryption.dart +160 -0
- package/dart_analyzer/lib/rules/security/S010_use_csprng.dart +184 -0
- package/dart_analyzer/lib/rules/security/S011_ech_tls_config.dart +175 -0
- package/dart_analyzer/lib/rules/security/S012_hardcoded_secrets.dart +255 -0
- package/dart_analyzer/lib/rules/security/S013_tls_enforcement.dart +148 -0
- package/dart_analyzer/lib/rules/security/S014_tls_version_enforcement.dart +117 -0
- package/dart_analyzer/lib/rules/security/S015_insecure_tls_certificate.dart +315 -0
- package/dart_analyzer/lib/rules/security/S016_no_sensitive_querystring.dart +244 -0
- package/dart_analyzer/lib/rules/security/S017_use_parameterized_queries.dart +191 -0
- package/dart_analyzer/lib/rules/security/S018_no_sensitive_browser_storage.dart +175 -0
- package/dart_analyzer/lib/rules/security/S019_smtp_injection_protection.dart +166 -0
- package/dart_analyzer/lib/rules/security/S020_no_eval_dynamic_code.dart +149 -0
- package/dart_analyzer/lib/rules/security/S021_referrer_policy.dart +146 -0
- package/dart_analyzer/lib/rules/security/S022_escape_output_context.dart +111 -0
- package/dart_analyzer/lib/rules/security/S023_no_json_injection.dart +550 -0
- package/dart_analyzer/lib/rules/security/S024_xpath_xxe_protection.dart +299 -0
- package/dart_analyzer/lib/rules/security/S025_server_side_validation.dart +140 -0
- package/dart_analyzer/lib/rules/security/S026_tls_all_connections.dart +196 -0
- package/dart_analyzer/lib/rules/security/S027_mtls_certificate_validation.dart +195 -0
- package/dart_analyzer/lib/rules/security/S028_file_upload_size_limits.dart +186 -0
- package/dart_analyzer/lib/rules/security/S029_csrf_protection.dart +171 -0
- package/dart_analyzer/lib/rules/security/S030_directory_browsing_protection.dart +144 -0
- package/dart_analyzer/lib/rules/security/S031_secure_session_cookies.dart +118 -0
- package/dart_analyzer/lib/rules/security/S032_httponly_session_cookies.dart +114 -0
- package/dart_analyzer/lib/rules/security/S033_samesite_session_cookies.dart +120 -0
- package/dart_analyzer/lib/rules/security/S034_host_prefix_session_cookies.dart +160 -0
- package/dart_analyzer/lib/rules/security/S035_separate_app_hostnames.dart +117 -0
- package/dart_analyzer/lib/rules/security/S036_lfi_rfi_protection.dart +188 -0
- package/dart_analyzer/lib/rules/security/S037_cache_headers.dart +113 -0
- package/dart_analyzer/lib/rules/security/S038_no_version_headers.dart +114 -0
- package/dart_analyzer/lib/rules/security/S039_tls_certificate_validation.dart +131 -0
- package/dart_analyzer/lib/rules/security/S040_session_fixation_protection.dart +155 -0
- package/dart_analyzer/lib/rules/security/S041_session_token_invalidation.dart +201 -0
- package/dart_analyzer/lib/rules/security/S042_require_re_authentication_for_long_lived.dart +158 -0
- package/dart_analyzer/lib/rules/security/S043_password_changes_invalidate_all_sessions.dart +88 -0
- package/dart_analyzer/lib/rules/security/S044_re_authentication_required.dart +119 -0
- package/dart_analyzer/lib/rules/security/S045_brute_force_protection.dart +253 -0
- package/dart_analyzer/lib/rules/security/S046_jwt_algorithm_allowlist.dart +113 -0
- package/dart_analyzer/lib/rules/security/S047_oauth_pkce_protection.dart +124 -0
- package/dart_analyzer/lib/rules/security/S048_oauth_redirect_uri_validation.dart +134 -0
- package/dart_analyzer/lib/rules/security/S049_short_validity_tokens.dart +145 -0
- package/dart_analyzer/lib/rules/security/S050_reference_tokens_entropy.dart +234 -0
- package/dart_analyzer/lib/rules/security/S051_password_length_policy.dart +171 -0
- package/dart_analyzer/lib/rules/security/S052_weak_otp_entropy.dart +107 -0
- package/dart_analyzer/lib/rules/security/S053_generic_error_messages.dart +159 -0
- package/dart_analyzer/lib/rules/security/S054_no_default_accounts.dart +141 -0
- package/dart_analyzer/lib/rules/security/S055_content_type_validation.dart +324 -0
- package/dart_analyzer/lib/rules/security/S056_log_injection_protection.dart +119 -0
- package/dart_analyzer/lib/rules/security/S057_utc_logging.dart +114 -0
- package/dart_analyzer/lib/rules/security/S058_no_ssrf.dart +175 -0
- package/dart_analyzer/lib/rules/security/S059_disable_debug_mode.dart +172 -0
- package/dart_analyzer/lib/rules/security/S060_password_minimum_length.dart +170 -0
- package/dart_analyzer/lib/symbol_table_extractor.dart +510 -0
- package/dart_analyzer/lib/utils/common_utils.dart +26 -0
- package/dart_analyzer/pubspec.lock +557 -0
- package/dart_analyzer/pubspec.yaml +39 -0
- package/dart_analyzer/test/fixtures/complex_code.dart +95 -0
- package/docs/GENERATED_FILE_HANDLING_SUMMARY.md +2 -2
- 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
|
+
}
|