@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,84 @@
|
|
|
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
|
+
/// C040: Centralized Validation
|
|
10
|
+
/// Centralize validation logic
|
|
11
|
+
class C040CentralizedValidationAnalyzer extends BaseAnalyzer {
|
|
12
|
+
@override
|
|
13
|
+
String get ruleId => 'C040';
|
|
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 = _C040Visitor(
|
|
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 _C040Visitor extends RecursiveAstVisitor<void> {
|
|
38
|
+
final String filePath;
|
|
39
|
+
final LineInfo lineInfo;
|
|
40
|
+
final List<Violation> violations;
|
|
41
|
+
final C040CentralizedValidationAnalyzer analyzer;
|
|
42
|
+
|
|
43
|
+
_C040Visitor({
|
|
44
|
+
required this.filePath,
|
|
45
|
+
required this.lineInfo,
|
|
46
|
+
required this.violations,
|
|
47
|
+
required this.analyzer,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
final Map<String, int> _validationPatterns = {};
|
|
51
|
+
|
|
52
|
+
@override
|
|
53
|
+
void visitIfStatement(IfStatement node) {
|
|
54
|
+
final condition = node.expression.toSource();
|
|
55
|
+
|
|
56
|
+
// Check for common validation patterns
|
|
57
|
+
final validationPatterns = [
|
|
58
|
+
RegExp(r'\.isEmpty'),
|
|
59
|
+
RegExp(r'==\s*null'),
|
|
60
|
+
RegExp(r'!=\s*null'),
|
|
61
|
+
RegExp(r'\.length\s*[<>=]'),
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
for (final pattern in validationPatterns) {
|
|
65
|
+
if (pattern.hasMatch(condition)) {
|
|
66
|
+
final key = pattern.pattern;
|
|
67
|
+
_validationPatterns[key] = (_validationPatterns[key] ?? 0) + 1;
|
|
68
|
+
|
|
69
|
+
if (_validationPatterns[key] == 3) {
|
|
70
|
+
violations.add(analyzer.createViolation(
|
|
71
|
+
filePath: filePath,
|
|
72
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
73
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
74
|
+
message: 'Similar validation pattern repeated multiple times - consider centralizing validation logic',
|
|
75
|
+
));
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
super.visitIfStatement(node);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
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
|
+
/// C041: No Sensitive Hardcode
|
|
10
|
+
/// Avoid hardcoding sensitive information
|
|
11
|
+
class C041NoSensitiveHardcodeAnalyzer extends BaseAnalyzer {
|
|
12
|
+
@override
|
|
13
|
+
String get ruleId => 'C041';
|
|
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 = _C041Visitor(
|
|
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 _C041Visitor extends RecursiveAstVisitor<void> {
|
|
38
|
+
final String filePath;
|
|
39
|
+
final LineInfo lineInfo;
|
|
40
|
+
final List<Violation> violations;
|
|
41
|
+
final C041NoSensitiveHardcodeAnalyzer analyzer;
|
|
42
|
+
|
|
43
|
+
_C041Visitor({
|
|
44
|
+
required this.filePath,
|
|
45
|
+
required this.lineInfo,
|
|
46
|
+
required this.violations,
|
|
47
|
+
required this.analyzer,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Sensitive variable names
|
|
51
|
+
static final _sensitiveNamePatterns = [
|
|
52
|
+
RegExp(r'password', caseSensitive: false),
|
|
53
|
+
RegExp(r'api[_-]?key', caseSensitive: false),
|
|
54
|
+
RegExp(r'secret', caseSensitive: false),
|
|
55
|
+
RegExp(r'token', caseSensitive: false),
|
|
56
|
+
RegExp(r'credential', caseSensitive: false),
|
|
57
|
+
RegExp(r'auth[_-]?key', caseSensitive: false),
|
|
58
|
+
RegExp(r'private[_-]?key', caseSensitive: false),
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
// Long strings that look like tokens/keys
|
|
62
|
+
static final _longTokenPattern = RegExp(r'^[A-Za-z0-9_\-]{32,}$');
|
|
63
|
+
|
|
64
|
+
@override
|
|
65
|
+
void visitVariableDeclaration(VariableDeclaration node) {
|
|
66
|
+
final initializer = node.initializer;
|
|
67
|
+
final varName = node.name.lexeme;
|
|
68
|
+
|
|
69
|
+
if (initializer != null) {
|
|
70
|
+
// Check if variable name suggests sensitive data
|
|
71
|
+
for (final pattern in _sensitiveNamePatterns) {
|
|
72
|
+
if (pattern.hasMatch(varName)) {
|
|
73
|
+
// Check if it's a string literal (hardcoded value)
|
|
74
|
+
if (initializer is SimpleStringLiteral) {
|
|
75
|
+
violations.add(analyzer.createViolation(
|
|
76
|
+
filePath: filePath,
|
|
77
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
78
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
79
|
+
message: 'Variable "$varName" appears to contain hardcoded sensitive data - use environment variables or secure storage',
|
|
80
|
+
));
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check for long token-like strings
|
|
87
|
+
if (initializer is SimpleStringLiteral) {
|
|
88
|
+
final value = initializer.value;
|
|
89
|
+
if (_longTokenPattern.hasMatch(value)) {
|
|
90
|
+
violations.add(analyzer.createViolation(
|
|
91
|
+
filePath: filePath,
|
|
92
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
93
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
94
|
+
message: 'Hardcoded string looks like a token or API key - consider using secure storage',
|
|
95
|
+
));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
super.visitVariableDeclaration(node);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
/// C042: Boolean Name Prefix
|
|
10
|
+
/// Boolean variables should use is/has/can/should prefix
|
|
11
|
+
class C042BooleanNamePrefixAnalyzer extends BaseAnalyzer {
|
|
12
|
+
@override
|
|
13
|
+
String get ruleId => 'C042';
|
|
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 = _C042Visitor(
|
|
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 _C042Visitor extends RecursiveAstVisitor<void> {
|
|
38
|
+
final String filePath;
|
|
39
|
+
final LineInfo lineInfo;
|
|
40
|
+
final List<Violation> violations;
|
|
41
|
+
final C042BooleanNamePrefixAnalyzer analyzer;
|
|
42
|
+
|
|
43
|
+
_C042Visitor({
|
|
44
|
+
required this.filePath,
|
|
45
|
+
required this.lineInfo,
|
|
46
|
+
required this.violations,
|
|
47
|
+
required this.analyzer,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
static const _booleanPrefixes = ['is', 'has', 'can', 'should', 'will', 'did', 'does', 'was', 'are'];
|
|
51
|
+
|
|
52
|
+
@override
|
|
53
|
+
void visitVariableDeclarationList(VariableDeclarationList node) {
|
|
54
|
+
// Check if type annotation is 'bool'
|
|
55
|
+
final typeAnnotation = node.type;
|
|
56
|
+
bool isBoolType = false;
|
|
57
|
+
|
|
58
|
+
if (typeAnnotation is NamedType) {
|
|
59
|
+
final typeName = typeAnnotation.name2.lexeme;
|
|
60
|
+
isBoolType = typeName == 'bool';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (isBoolType) {
|
|
64
|
+
for (final variable in node.variables) {
|
|
65
|
+
final name = variable.name.lexeme;
|
|
66
|
+
final lowerName = name.toLowerCase();
|
|
67
|
+
final hasValidPrefix = _booleanPrefixes.any((prefix) => lowerName.startsWith(prefix));
|
|
68
|
+
|
|
69
|
+
if (!hasValidPrefix && !name.startsWith('_')) {
|
|
70
|
+
violations.add(analyzer.createViolation(
|
|
71
|
+
filePath: filePath,
|
|
72
|
+
line: analyzer.getLine(lineInfo, variable.offset),
|
|
73
|
+
column: analyzer.getColumn(lineInfo, variable.offset),
|
|
74
|
+
message: 'Boolean variable "$name" should start with a prefix like is, has, can, should',
|
|
75
|
+
));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
super.visitVariableDeclarationList(node);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@override
|
|
84
|
+
void visitVariableDeclaration(VariableDeclaration node) {
|
|
85
|
+
// Also check if initializer is a boolean literal
|
|
86
|
+
final initializer = node.initializer;
|
|
87
|
+
if (initializer is BooleanLiteral) {
|
|
88
|
+
final name = node.name.lexeme;
|
|
89
|
+
final lowerName = name.toLowerCase();
|
|
90
|
+
final hasValidPrefix = _booleanPrefixes.any((prefix) => lowerName.startsWith(prefix));
|
|
91
|
+
|
|
92
|
+
if (!hasValidPrefix && !name.startsWith('_')) {
|
|
93
|
+
violations.add(analyzer.createViolation(
|
|
94
|
+
filePath: filePath,
|
|
95
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
96
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
97
|
+
message: 'Boolean variable "$name" should start with a prefix like is, has, can, should',
|
|
98
|
+
));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
super.visitVariableDeclaration(node);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
/// C043: No Console Or Print
|
|
10
|
+
/// Avoid using console.log or print statements in production code
|
|
11
|
+
class C043NoConsoleOrPrintAnalyzer extends BaseAnalyzer {
|
|
12
|
+
@override
|
|
13
|
+
String get ruleId => 'C043';
|
|
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 = _C043Visitor(
|
|
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 _C043Visitor extends RecursiveAstVisitor<void> {
|
|
38
|
+
final String filePath;
|
|
39
|
+
final LineInfo lineInfo;
|
|
40
|
+
final List<Violation> violations;
|
|
41
|
+
final C043NoConsoleOrPrintAnalyzer analyzer;
|
|
42
|
+
|
|
43
|
+
_C043Visitor({
|
|
44
|
+
required this.filePath,
|
|
45
|
+
required this.lineInfo,
|
|
46
|
+
required this.violations,
|
|
47
|
+
required this.analyzer,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
bool _isInDebugModeBlock = false;
|
|
51
|
+
|
|
52
|
+
@override
|
|
53
|
+
void visitIfStatement(IfStatement node) {
|
|
54
|
+
// Check if the condition is kDebugMode
|
|
55
|
+
final condition = node.expression;
|
|
56
|
+
final isDebugModeCheck = _isDebugModeCondition(condition);
|
|
57
|
+
|
|
58
|
+
if (isDebugModeCheck) {
|
|
59
|
+
_isInDebugModeBlock = true;
|
|
60
|
+
node.thenStatement.accept(this);
|
|
61
|
+
_isInDebugModeBlock = false;
|
|
62
|
+
|
|
63
|
+
// Visit else part normally if it exists
|
|
64
|
+
node.elseStatement?.accept(this);
|
|
65
|
+
} else {
|
|
66
|
+
super.visitIfStatement(node);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
bool _isDebugModeCondition(Expression condition) {
|
|
71
|
+
// Check for direct kDebugMode identifier
|
|
72
|
+
if (condition is SimpleIdentifier && condition.name == 'kDebugMode') {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check for expressions containing kDebugMode (e.g., kDebugMode && something)
|
|
77
|
+
final conditionString = condition.toString();
|
|
78
|
+
return conditionString.contains('kDebugMode');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@override
|
|
82
|
+
void visitMethodInvocation(MethodInvocation node) {
|
|
83
|
+
final isTest =
|
|
84
|
+
filePath.contains('_test.dart') || filePath.contains('/test/');
|
|
85
|
+
final name = node.methodName.name;
|
|
86
|
+
|
|
87
|
+
if (!isTest &&
|
|
88
|
+
!_isInDebugModeBlock &&
|
|
89
|
+
(name == 'print' || name == 'debugPrint')) {
|
|
90
|
+
violations.add(analyzer.createViolation(
|
|
91
|
+
filePath: filePath,
|
|
92
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
93
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
94
|
+
message:
|
|
95
|
+
'Avoid using $name() in production code - use a proper logger instead or use inside kDebugMode check',
|
|
96
|
+
));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
super.visitMethodInvocation(node);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
/// C047: No Duplicate Retry Logic
|
|
10
|
+
/// Centralize retry logic instead of duplicating it
|
|
11
|
+
class C047NoDuplicateRetryLogicAnalyzer extends BaseAnalyzer {
|
|
12
|
+
@override
|
|
13
|
+
String get ruleId => 'C047';
|
|
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 = _C047Visitor(
|
|
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 _C047Visitor extends RecursiveAstVisitor<void> {
|
|
38
|
+
final String filePath;
|
|
39
|
+
final LineInfo lineInfo;
|
|
40
|
+
final List<Violation> violations;
|
|
41
|
+
final C047NoDuplicateRetryLogicAnalyzer analyzer;
|
|
42
|
+
|
|
43
|
+
_C047Visitor({
|
|
44
|
+
required this.filePath,
|
|
45
|
+
required this.lineInfo,
|
|
46
|
+
required this.violations,
|
|
47
|
+
required this.analyzer,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
int _retryPatternCount = 0;
|
|
51
|
+
|
|
52
|
+
@override
|
|
53
|
+
void visitForStatement(ForStatement node) {
|
|
54
|
+
final source = node.toSource();
|
|
55
|
+
|
|
56
|
+
// Check for retry patterns
|
|
57
|
+
if (source.contains('retry') || source.contains('attempt') ||
|
|
58
|
+
source.contains('maxRetries') || source.contains('maxAttempts')) {
|
|
59
|
+
_retryPatternCount++;
|
|
60
|
+
|
|
61
|
+
if (_retryPatternCount > 1) {
|
|
62
|
+
violations.add(analyzer.createViolation(
|
|
63
|
+
filePath: filePath,
|
|
64
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
65
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
66
|
+
message: 'Multiple retry logic implementations detected - consider centralizing retry logic',
|
|
67
|
+
));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
super.visitForStatement(node);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@override
|
|
75
|
+
void visitWhileStatement(WhileStatement node) {
|
|
76
|
+
final source = node.toSource();
|
|
77
|
+
|
|
78
|
+
if (source.contains('retry') || source.contains('attempt')) {
|
|
79
|
+
_retryPatternCount++;
|
|
80
|
+
|
|
81
|
+
if (_retryPatternCount > 1) {
|
|
82
|
+
violations.add(analyzer.createViolation(
|
|
83
|
+
filePath: filePath,
|
|
84
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
85
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
86
|
+
message: 'Multiple retry logic implementations detected - consider centralizing retry logic',
|
|
87
|
+
));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
super.visitWhileStatement(node);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
/// C048: No Bypass Architectural Layers
|
|
10
|
+
/// Respect architectural layer boundaries
|
|
11
|
+
class C048NoBypassArchitecturalLayersAnalyzer extends BaseAnalyzer {
|
|
12
|
+
@override
|
|
13
|
+
String get ruleId => 'C048';
|
|
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 = _C048Visitor(
|
|
25
|
+
filePath: filePath,
|
|
26
|
+
lineInfo: lineInfo,
|
|
27
|
+
violations: violations,
|
|
28
|
+
analyzer: this,
|
|
29
|
+
unit: unit,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
unit.accept(visitor);
|
|
33
|
+
|
|
34
|
+
return violations;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class _C048Visitor extends RecursiveAstVisitor<void> {
|
|
39
|
+
final String filePath;
|
|
40
|
+
final LineInfo lineInfo;
|
|
41
|
+
final List<Violation> violations;
|
|
42
|
+
final C048NoBypassArchitecturalLayersAnalyzer analyzer;
|
|
43
|
+
final CompilationUnit unit;
|
|
44
|
+
|
|
45
|
+
_C048Visitor({
|
|
46
|
+
required this.filePath,
|
|
47
|
+
required this.lineInfo,
|
|
48
|
+
required this.violations,
|
|
49
|
+
required this.analyzer,
|
|
50
|
+
required this.unit,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/// Detect layer from class names in the file
|
|
54
|
+
String? _detectLayerFromClasses() {
|
|
55
|
+
for (final declaration in unit.declarations) {
|
|
56
|
+
if (declaration is ClassDeclaration) {
|
|
57
|
+
final className = declaration.name.lexeme.toLowerCase();
|
|
58
|
+
|
|
59
|
+
// Presentation layer patterns
|
|
60
|
+
if (className.contains('screen') || className.contains('page') ||
|
|
61
|
+
className.contains('widget') || className.contains('view') ||
|
|
62
|
+
className.contains('controller') || className.contains('presenter')) {
|
|
63
|
+
return 'presentation';
|
|
64
|
+
}
|
|
65
|
+
// Domain layer patterns
|
|
66
|
+
if (className.contains('usecase') || className.contains('interactor')) {
|
|
67
|
+
return 'domain';
|
|
68
|
+
}
|
|
69
|
+
// Data layer patterns
|
|
70
|
+
if (className.endsWith('repository') || className.contains('datasource') ||
|
|
71
|
+
className.endsWith('api') || className.contains('dao')) {
|
|
72
|
+
return 'data';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@override
|
|
80
|
+
void visitImportDirective(ImportDirective node) {
|
|
81
|
+
final uri = node.uri.stringValue ?? '';
|
|
82
|
+
|
|
83
|
+
// Detect layer from file path or class names
|
|
84
|
+
final classLayer = _detectLayerFromClasses();
|
|
85
|
+
final isPresentation = filePath.contains('/presentation/') ||
|
|
86
|
+
filePath.contains('/ui/') ||
|
|
87
|
+
filePath.contains('/view/') ||
|
|
88
|
+
classLayer == 'presentation';
|
|
89
|
+
final isDomain = filePath.contains('/domain/') || classLayer == 'domain';
|
|
90
|
+
|
|
91
|
+
// Import targets data layer
|
|
92
|
+
final importTargetsData = uri.contains('/data/') ||
|
|
93
|
+
uri.contains('/repositories/') ||
|
|
94
|
+
uri.contains('/repository/') ||
|
|
95
|
+
uri.contains('/database/') ||
|
|
96
|
+
uri.contains('/datasources/') ||
|
|
97
|
+
uri.contains('/datasource/') ||
|
|
98
|
+
uri.contains('_repository_impl') ||
|
|
99
|
+
uri.contains('_api.');
|
|
100
|
+
|
|
101
|
+
// Import targets presentation layer
|
|
102
|
+
final importTargetsPresentation = uri.contains('/presentation/') ||
|
|
103
|
+
uri.contains('/ui/') ||
|
|
104
|
+
uri.contains('/view/') ||
|
|
105
|
+
uri.contains('_screen') ||
|
|
106
|
+
uri.contains('_page') ||
|
|
107
|
+
uri.contains('_widget');
|
|
108
|
+
|
|
109
|
+
// Presentation importing data layer
|
|
110
|
+
if (isPresentation && importTargetsData) {
|
|
111
|
+
violations.add(analyzer.createViolation(
|
|
112
|
+
filePath: filePath,
|
|
113
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
114
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
115
|
+
message: 'Presentation layer should not directly import data layer - use domain layer instead',
|
|
116
|
+
));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Domain importing presentation layer
|
|
120
|
+
if (isDomain && importTargetsPresentation) {
|
|
121
|
+
violations.add(analyzer.createViolation(
|
|
122
|
+
filePath: filePath,
|
|
123
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
124
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
125
|
+
message: 'Domain layer should not import presentation layer',
|
|
126
|
+
));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
super.visitImportDirective(node);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
}
|