@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,112 @@
|
|
|
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
|
+
/// C023: No Duplicate Variable
|
|
10
|
+
/// Avoid declaring duplicate variables in the same scope
|
|
11
|
+
class C023NoDuplicateVariableAnalyzer extends BaseAnalyzer {
|
|
12
|
+
@override
|
|
13
|
+
String get ruleId => 'C023';
|
|
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 = _C023Visitor(
|
|
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 _C023Visitor extends RecursiveAstVisitor<void> {
|
|
38
|
+
final String filePath;
|
|
39
|
+
final LineInfo lineInfo;
|
|
40
|
+
final List<Violation> violations;
|
|
41
|
+
final C023NoDuplicateVariableAnalyzer analyzer;
|
|
42
|
+
|
|
43
|
+
_C023Visitor({
|
|
44
|
+
required this.filePath,
|
|
45
|
+
required this.lineInfo,
|
|
46
|
+
required this.violations,
|
|
47
|
+
required this.analyzer,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
final List<Map<String, int>> _scopeStack = [];
|
|
51
|
+
|
|
52
|
+
void _enterScope() {
|
|
53
|
+
final newScope = <String, int>{};
|
|
54
|
+
_scopeStack.add(newScope);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
void _exitScope() {
|
|
58
|
+
if (_scopeStack.isNotEmpty) {
|
|
59
|
+
_scopeStack.removeLast();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
Map<String, int>? get _currentScope =>
|
|
64
|
+
_scopeStack.isEmpty ? null : _scopeStack.last;
|
|
65
|
+
|
|
66
|
+
@override
|
|
67
|
+
void visitBlock(Block node) {
|
|
68
|
+
_enterScope();
|
|
69
|
+
super.visitBlock(node);
|
|
70
|
+
_exitScope();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@override
|
|
74
|
+
void visitSwitchCase(SwitchCase node) {
|
|
75
|
+
_enterScope();
|
|
76
|
+
super.visitSwitchCase(node);
|
|
77
|
+
_exitScope();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@override
|
|
81
|
+
void visitSwitchPatternCase(SwitchPatternCase node) {
|
|
82
|
+
_enterScope();
|
|
83
|
+
super.visitSwitchPatternCase(node);
|
|
84
|
+
_exitScope();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@override
|
|
88
|
+
void visitVariableDeclaration(VariableDeclaration node) {
|
|
89
|
+
final name = node.name.lexeme;
|
|
90
|
+
final scope = _currentScope;
|
|
91
|
+
|
|
92
|
+
// Skip if no active scope (class-level or top-level variable)
|
|
93
|
+
if (scope == null) {
|
|
94
|
+
super.visitVariableDeclaration(node);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (scope.containsKey(name)) {
|
|
99
|
+
violations.add(analyzer.createViolation(
|
|
100
|
+
filePath: filePath,
|
|
101
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
102
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
103
|
+
message: 'Variable "$name" is already declared in this scope',
|
|
104
|
+
));
|
|
105
|
+
} else {
|
|
106
|
+
scope[name] = node.offset;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
super.visitVariableDeclaration(node);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
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
|
+
/// C024: No Scatter Hardcoded Constants
|
|
10
|
+
/// Centralize hardcoded constants
|
|
11
|
+
class C024NoScatterHardcodedConstantsAnalyzer extends BaseAnalyzer {
|
|
12
|
+
@override
|
|
13
|
+
String get ruleId => 'C024';
|
|
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 = _C024Visitor(
|
|
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 _C024Visitor extends RecursiveAstVisitor<void> {
|
|
38
|
+
final String filePath;
|
|
39
|
+
final LineInfo lineInfo;
|
|
40
|
+
final List<Violation> violations;
|
|
41
|
+
final C024NoScatterHardcodedConstantsAnalyzer analyzer;
|
|
42
|
+
|
|
43
|
+
_C024Visitor({
|
|
44
|
+
required this.filePath,
|
|
45
|
+
required this.lineInfo,
|
|
46
|
+
required this.violations,
|
|
47
|
+
required this.analyzer,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
final Map<String, List<int>> _stringLiterals = {};
|
|
51
|
+
|
|
52
|
+
@override
|
|
53
|
+
void visitSimpleStringLiteral(SimpleStringLiteral node) {
|
|
54
|
+
final value = node.value;
|
|
55
|
+
|
|
56
|
+
// Skip short strings (likely not constants)
|
|
57
|
+
if (value.length < 5) return;
|
|
58
|
+
|
|
59
|
+
// Skip format strings and interpolations
|
|
60
|
+
if (value.contains('%') || value.contains('{')) return;
|
|
61
|
+
|
|
62
|
+
// Track repeated literals
|
|
63
|
+
_stringLiterals.putIfAbsent(value, () => []);
|
|
64
|
+
_stringLiterals[value]!.add(node.offset);
|
|
65
|
+
|
|
66
|
+
// Report if same literal appears multiple times
|
|
67
|
+
if (_stringLiterals[value]!.length == 2) {
|
|
68
|
+
violations.add(analyzer.createViolation(
|
|
69
|
+
filePath: filePath,
|
|
70
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
71
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
72
|
+
message: 'Hardcoded string "$value" appears multiple times - consider extracting to a constant',
|
|
73
|
+
));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
super.visitSimpleStringLiteral(node);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
/// C029: Catch Block Logging
|
|
10
|
+
/// Ensure proper logging in catch blocks
|
|
11
|
+
class C029CatchBlockLoggingAnalyzer extends BaseAnalyzer {
|
|
12
|
+
@override
|
|
13
|
+
String get ruleId => 'C029';
|
|
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 = _C029Visitor(
|
|
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 _C029Visitor extends RecursiveAstVisitor<void> {
|
|
38
|
+
final String filePath;
|
|
39
|
+
final LineInfo lineInfo;
|
|
40
|
+
final List<Violation> violations;
|
|
41
|
+
final C029CatchBlockLoggingAnalyzer analyzer;
|
|
42
|
+
|
|
43
|
+
_C029Visitor({
|
|
44
|
+
required this.filePath,
|
|
45
|
+
required this.lineInfo,
|
|
46
|
+
required this.violations,
|
|
47
|
+
required this.analyzer,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
@override
|
|
51
|
+
void visitCatchClause(CatchClause node) {
|
|
52
|
+
final body = node.body;
|
|
53
|
+
final hasLogging = _containsLogging(body);
|
|
54
|
+
final hasRethrow = _containsRethrow(body);
|
|
55
|
+
|
|
56
|
+
if (!hasLogging && !hasRethrow) {
|
|
57
|
+
violations.add(analyzer.createViolation(
|
|
58
|
+
filePath: filePath,
|
|
59
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
60
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
61
|
+
message: 'Catch block should log the exception or rethrow it',
|
|
62
|
+
));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
super.visitCatchClause(node);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
bool _containsLogging(Block body) {
|
|
69
|
+
final source = body.toSource();
|
|
70
|
+
return source.contains('print(') ||
|
|
71
|
+
source.contains('log(') ||
|
|
72
|
+
source.contains('logger.') ||
|
|
73
|
+
source.contains('debugPrint(');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
bool _containsRethrow(Block body) {
|
|
77
|
+
final source = body.toSource();
|
|
78
|
+
return source.contains('rethrow') || source.contains('throw ');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
/// C030: Use Custom Error Classes
|
|
10
|
+
/// Use custom error classes instead of generic errors
|
|
11
|
+
class C030UseCustomErrorClassesAnalyzer extends BaseAnalyzer {
|
|
12
|
+
@override
|
|
13
|
+
String get ruleId => 'C030';
|
|
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 = _C030Visitor(
|
|
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 _C030Visitor extends RecursiveAstVisitor<void> {
|
|
38
|
+
final String filePath;
|
|
39
|
+
final LineInfo lineInfo;
|
|
40
|
+
final List<Violation> violations;
|
|
41
|
+
final C030UseCustomErrorClassesAnalyzer analyzer;
|
|
42
|
+
|
|
43
|
+
_C030Visitor({
|
|
44
|
+
required this.filePath,
|
|
45
|
+
required this.lineInfo,
|
|
46
|
+
required this.violations,
|
|
47
|
+
required this.analyzer,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
static const _genericExceptions = {'Exception', 'Error'};
|
|
51
|
+
|
|
52
|
+
@override
|
|
53
|
+
void visitThrowExpression(ThrowExpression node) {
|
|
54
|
+
final expression = node.expression;
|
|
55
|
+
String? typeName;
|
|
56
|
+
|
|
57
|
+
// Handle both InstanceCreationExpression and MethodInvocation
|
|
58
|
+
if (expression is InstanceCreationExpression) {
|
|
59
|
+
typeName = expression.constructorName.type.name2.lexeme;
|
|
60
|
+
} else if (expression is MethodInvocation) {
|
|
61
|
+
// In Dart, Exception('msg') is parsed as MethodInvocation
|
|
62
|
+
typeName = expression.methodName.name;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (typeName != null && _genericExceptions.contains(typeName)) {
|
|
66
|
+
violations.add(analyzer.createViolation(
|
|
67
|
+
filePath: filePath,
|
|
68
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
69
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
70
|
+
message: 'Use a custom exception class instead of generic "$typeName"',
|
|
71
|
+
));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
super.visitThrowExpression(node);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
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
|
+
/// C031: Validation Separation
|
|
10
|
+
/// Separate validation logic from business logic
|
|
11
|
+
class C031ValidationSeparationAnalyzer extends BaseAnalyzer {
|
|
12
|
+
@override
|
|
13
|
+
String get ruleId => 'C031';
|
|
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 = _C031Visitor(
|
|
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 _C031Visitor extends RecursiveAstVisitor<void> {
|
|
38
|
+
final String filePath;
|
|
39
|
+
final LineInfo lineInfo;
|
|
40
|
+
final List<Violation> violations;
|
|
41
|
+
final C031ValidationSeparationAnalyzer analyzer;
|
|
42
|
+
|
|
43
|
+
_C031Visitor({
|
|
44
|
+
required this.filePath,
|
|
45
|
+
required this.lineInfo,
|
|
46
|
+
required this.violations,
|
|
47
|
+
required this.analyzer,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
@override
|
|
51
|
+
void visitMethodDeclaration(MethodDeclaration node) {
|
|
52
|
+
final name = node.name.lexeme;
|
|
53
|
+
final body = node.body;
|
|
54
|
+
|
|
55
|
+
// Skip validation methods themselves
|
|
56
|
+
if (name.contains('validate') || name.contains('Validate')) {
|
|
57
|
+
super.visitMethodDeclaration(node);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check if method contains inline validation
|
|
62
|
+
if (body != null) {
|
|
63
|
+
final source = body.toSource();
|
|
64
|
+
final validationPatterns = [
|
|
65
|
+
RegExp(r'if\s*\(.*==\s*null'),
|
|
66
|
+
RegExp(r'if\s*\(.*\.isEmpty'),
|
|
67
|
+
RegExp(r'if\s*\(.*\.length\s*[<>=]'),
|
|
68
|
+
RegExp(r'throw.*Invalid'),
|
|
69
|
+
RegExp(r'throw.*Argument'),
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
int validationCount = 0;
|
|
73
|
+
for (final pattern in validationPatterns) {
|
|
74
|
+
if (pattern.hasMatch(source)) validationCount++;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (validationCount >= 3) {
|
|
78
|
+
violations.add(analyzer.createViolation(
|
|
79
|
+
filePath: filePath,
|
|
80
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
81
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
82
|
+
message: 'Method "$name" contains too much inline validation - consider extracting to a validate method',
|
|
83
|
+
));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
super.visitMethodDeclaration(node);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
/// C033: Separate Service Repository
|
|
10
|
+
/// Maintain separation between service and repository layers
|
|
11
|
+
class C033SeparateServiceRepositoryAnalyzer extends BaseAnalyzer {
|
|
12
|
+
@override
|
|
13
|
+
String get ruleId => 'C033';
|
|
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 = _C033Visitor(
|
|
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 _C033Visitor extends RecursiveAstVisitor<void> {
|
|
38
|
+
final String filePath;
|
|
39
|
+
final LineInfo lineInfo;
|
|
40
|
+
final List<Violation> violations;
|
|
41
|
+
final C033SeparateServiceRepositoryAnalyzer analyzer;
|
|
42
|
+
|
|
43
|
+
_C033Visitor({
|
|
44
|
+
required this.filePath,
|
|
45
|
+
required this.lineInfo,
|
|
46
|
+
required this.violations,
|
|
47
|
+
required this.analyzer,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
@override
|
|
51
|
+
void visitClassDeclaration(ClassDeclaration node) {
|
|
52
|
+
final className = node.name.lexeme;
|
|
53
|
+
|
|
54
|
+
// Check if it's a service class
|
|
55
|
+
if (className.endsWith('Service')) {
|
|
56
|
+
// Look for direct database/storage access
|
|
57
|
+
final source = node.toSource();
|
|
58
|
+
final dbPatterns = [
|
|
59
|
+
'Database', 'Firestore', 'SharedPreferences',
|
|
60
|
+
'Hive', 'sqflite', 'drift', '.collection(',
|
|
61
|
+
'.doc(', '.get()', '.set(', '.delete('
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
for (final pattern in dbPatterns) {
|
|
65
|
+
if (source.contains(pattern)) {
|
|
66
|
+
violations.add(analyzer.createViolation(
|
|
67
|
+
filePath: filePath,
|
|
68
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
69
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
70
|
+
message: 'Service class "$className" should not directly access database - use a Repository instead',
|
|
71
|
+
));
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
super.visitClassDeclaration(node);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
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
|
+
/// C035: Error Logging Context
|
|
10
|
+
/// Include context information in error logging
|
|
11
|
+
class C035ErrorLoggingContextAnalyzer extends BaseAnalyzer {
|
|
12
|
+
@override
|
|
13
|
+
String get ruleId => 'C035';
|
|
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 = _C035Visitor(
|
|
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 _C035Visitor extends RecursiveAstVisitor<void> {
|
|
38
|
+
final String filePath;
|
|
39
|
+
final LineInfo lineInfo;
|
|
40
|
+
final List<Violation> violations;
|
|
41
|
+
final C035ErrorLoggingContextAnalyzer analyzer;
|
|
42
|
+
|
|
43
|
+
_C035Visitor({
|
|
44
|
+
required this.filePath,
|
|
45
|
+
required this.lineInfo,
|
|
46
|
+
required this.violations,
|
|
47
|
+
required this.analyzer,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
@override
|
|
51
|
+
void visitCatchClause(CatchClause node) {
|
|
52
|
+
// Visit statements in the catch block to find log calls
|
|
53
|
+
for (final statement in node.body.statements) {
|
|
54
|
+
_checkLoggingStatement(statement, node);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
super.visitCatchClause(node);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
void _checkLoggingStatement(Statement statement, CatchClause catchNode) {
|
|
61
|
+
if (statement is! ExpressionStatement) return;
|
|
62
|
+
|
|
63
|
+
final expr = statement.expression;
|
|
64
|
+
String? methodName;
|
|
65
|
+
ArgumentList? argList;
|
|
66
|
+
|
|
67
|
+
// Handle both MethodInvocation and FunctionExpressionInvocation
|
|
68
|
+
if (expr is MethodInvocation) {
|
|
69
|
+
methodName = expr.methodName.name.toLowerCase();
|
|
70
|
+
argList = expr.argumentList;
|
|
71
|
+
} else if (expr is FunctionExpressionInvocation) {
|
|
72
|
+
// Handle print(), log() as top-level function calls
|
|
73
|
+
final function = expr.function;
|
|
74
|
+
if (function is Identifier) {
|
|
75
|
+
methodName = function.name.toLowerCase();
|
|
76
|
+
argList = expr.argumentList;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (methodName == null || argList == null) return;
|
|
81
|
+
|
|
82
|
+
// Check if it's a logging call
|
|
83
|
+
if (methodName == 'print' || methodName == 'log' ||
|
|
84
|
+
methodName == 'error' || methodName == 'warning' ||
|
|
85
|
+
methodName == 'info' || methodName == 'debug') {
|
|
86
|
+
|
|
87
|
+
// Check if any argument contains context (interpolation, variables)
|
|
88
|
+
bool hasContext = false;
|
|
89
|
+
|
|
90
|
+
for (final arg in argList.arguments) {
|
|
91
|
+
// Check if argument contains string interpolation (dynamic content)
|
|
92
|
+
if (arg is StringInterpolation) {
|
|
93
|
+
hasContext = true;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
// Check if argument is a variable/identifier (not just string literal)
|
|
97
|
+
if (arg is Identifier) {
|
|
98
|
+
hasContext = true;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
// Check if argument uses the exception variable via toSource
|
|
102
|
+
if (arg is SimpleStringLiteral) {
|
|
103
|
+
// Simple string literals without interpolation are considered lacking context
|
|
104
|
+
// unless they contain $ which would be strange
|
|
105
|
+
final argSource = arg.toSource();
|
|
106
|
+
if (argSource.contains(r'$')) {
|
|
107
|
+
hasContext = true;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Also check if the caught exception is used as a variable (not inside strings)
|
|
114
|
+
final exceptionParam = catchNode.exceptionParameter;
|
|
115
|
+
if (exceptionParam != null) {
|
|
116
|
+
final exceptionName = exceptionParam.name.lexeme;
|
|
117
|
+
// Check if exception is passed as argument (not inside string literal)
|
|
118
|
+
for (final arg in argList.arguments) {
|
|
119
|
+
if (arg is Identifier && arg.name == exceptionName) {
|
|
120
|
+
hasContext = true;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
// Check in string interpolation
|
|
124
|
+
if (arg is StringInterpolation) {
|
|
125
|
+
for (final element in arg.elements) {
|
|
126
|
+
if (element is InterpolationExpression) {
|
|
127
|
+
if (element.expression.toSource().contains(exceptionName)) {
|
|
128
|
+
hasContext = true;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!hasContext) {
|
|
138
|
+
violations.add(analyzer.createViolation(
|
|
139
|
+
filePath: filePath,
|
|
140
|
+
line: analyzer.getLine(lineInfo, statement.offset),
|
|
141
|
+
column: analyzer.getColumn(lineInfo, statement.offset),
|
|
142
|
+
message: 'Error logging should include context information (e.g., error details, userId, requestId)',
|
|
143
|
+
));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
}
|