@sun-asterisk/sunlint 1.3.43 → 1.3.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/dart_analyzer/README.md +226 -0
  2. package/dart_analyzer/analysis_options.yaml +66 -0
  3. package/dart_analyzer/bin/sunlint-dart-macos +0 -0
  4. package/dart_analyzer/bin/sunlint_dart_analyzer.dart +124 -0
  5. package/dart_analyzer/lib/analyzer_service.dart +625 -0
  6. package/dart_analyzer/lib/json_rpc_server.dart +275 -0
  7. package/dart_analyzer/lib/models/rule.dart +67 -0
  8. package/dart_analyzer/lib/models/symbol_table.dart +607 -0
  9. package/dart_analyzer/lib/models/violation.dart +69 -0
  10. package/dart_analyzer/lib/rules/base_analyzer.dart +52 -0
  11. package/dart_analyzer/lib/rules/common/C002_no_duplicate_code.dart +344 -0
  12. package/dart_analyzer/lib/rules/common/C003_no_vague_abbreviations.dart +318 -0
  13. package/dart_analyzer/lib/rules/common/C006_function_naming.dart +219 -0
  14. package/dart_analyzer/lib/rules/common/C008_variable_declaration_locality.dart +205 -0
  15. package/dart_analyzer/lib/rules/common/C010_limit_block_nesting.dart +162 -0
  16. package/dart_analyzer/lib/rules/common/C012_command_query_separation.dart +214 -0
  17. package/dart_analyzer/lib/rules/common/C013_no_dead_code.dart +225 -0
  18. package/dart_analyzer/lib/rules/common/C014_dependency_injection.dart +249 -0
  19. package/dart_analyzer/lib/rules/common/C017_constructor_logic.dart +158 -0
  20. package/dart_analyzer/lib/rules/common/C018_no_throw_generic_error.dart +141 -0
  21. package/dart_analyzer/lib/rules/common/C019_log_level_usage.dart +165 -0
  22. package/dart_analyzer/lib/rules/common/C020_unused_imports.dart +128 -0
  23. package/dart_analyzer/lib/rules/common/C021_import_organization.dart +86 -0
  24. package/dart_analyzer/lib/rules/common/C023_no_duplicate_variable.dart +112 -0
  25. package/dart_analyzer/lib/rules/common/C024_no_scatter_hardcoded_constants.dart +79 -0
  26. package/dart_analyzer/lib/rules/common/C029_catch_block_logging.dart +81 -0
  27. package/dart_analyzer/lib/rules/common/C030_use_custom_error_classes.dart +77 -0
  28. package/dart_analyzer/lib/rules/common/C031_validation_separation.dart +90 -0
  29. package/dart_analyzer/lib/rules/common/C033_separate_service_repository.dart +80 -0
  30. package/dart_analyzer/lib/rules/common/C035_error_logging_context.dart +148 -0
  31. package/dart_analyzer/lib/rules/common/C040_centralized_validation.dart +84 -0
  32. package/dart_analyzer/lib/rules/common/C041_no_sensitive_hardcode.dart +103 -0
  33. package/dart_analyzer/lib/rules/common/C042_boolean_name_prefix.dart +105 -0
  34. package/dart_analyzer/lib/rules/common/C043_no_console_or_print.dart +101 -0
  35. package/dart_analyzer/lib/rules/common/C047_no_duplicate_retry_logic.dart +94 -0
  36. package/dart_analyzer/lib/rules/common/C048_no_bypass_architectural_layers.dart +132 -0
  37. package/dart_analyzer/lib/rules/common/C052_parsing_or_data_transformation.dart +95 -0
  38. package/dart_analyzer/lib/rules/common/C060_no_override_superclass.dart +81 -0
  39. package/dart_analyzer/lib/rules/common/C065_one_behavior_per_test.dart +83 -0
  40. package/dart_analyzer/lib/rules/common/C067_no_hardcoded_config.dart +89 -0
  41. package/dart_analyzer/lib/rules/common/C070_no_real_time_tests.dart +99 -0
  42. package/dart_analyzer/lib/rules/common/C072_single_test_behavior.dart +78 -0
  43. package/dart_analyzer/lib/rules/common/C073_validate_required_config_on_startup.dart +82 -0
  44. package/dart_analyzer/lib/rules/common/C075_explicit_return_types.dart +85 -0
  45. package/dart_analyzer/lib/rules/common/C076_explicit_function_types.dart +104 -0
  46. package/dart_analyzer/lib/rules/dart/D001_recommended_lint_rules.dart +309 -0
  47. package/dart_analyzer/lib/rules/dart/D002_dispose_resources.dart +338 -0
  48. package/dart_analyzer/lib/rules/dart/D003_prefer_widgets_over_methods.dart +273 -0
  49. package/dart_analyzer/lib/rules/dart/D004_avoid_shrinkwrap_listview.dart +154 -0
  50. package/dart_analyzer/lib/rules/dart/D005_limit_widget_nesting.dart +265 -0
  51. package/dart_analyzer/lib/rules/dart/D006_prefer_extracting_large_callbacks.dart +135 -0
  52. package/dart_analyzer/lib/rules/dart/D007_prefer_init_first_dispose_last.dart +150 -0
  53. package/dart_analyzer/lib/rules/dart/D008_avoid_long_functions.dart +394 -0
  54. package/dart_analyzer/lib/rules/dart/D009_limit_function_parameters.dart +179 -0
  55. package/dart_analyzer/lib/rules/dart/D010_limit_cyclomatic_complexity.dart +257 -0
  56. package/dart_analyzer/lib/rules/dart/D011_prefer_named_parameters.dart +152 -0
  57. package/dart_analyzer/lib/rules/dart/D012_prefer_named_boolean_parameters.dart +156 -0
  58. package/dart_analyzer/lib/rules/dart/D013_single_public_class.dart +246 -0
  59. package/dart_analyzer/lib/rules/dart/D014_unsafe_collection_access.dart +202 -0
  60. package/dart_analyzer/lib/rules/dart/D015_copywith_all_parameters.dart +125 -0
  61. package/dart_analyzer/lib/rules/dart/D016_project_should_have_tests.dart +134 -0
  62. package/dart_analyzer/lib/rules/dart/D017_pubspec_dependencies_review.dart +187 -0
  63. package/dart_analyzer/lib/rules/dart/D018_remove_commented_code.dart +196 -0
  64. package/dart_analyzer/lib/rules/dart/D019_avoid_single_child_multi_child_widget.dart +161 -0
  65. package/dart_analyzer/lib/rules/dart/D020_limit_if_else_branches.dart +125 -0
  66. package/dart_analyzer/lib/rules/dart/D021_avoid_negated_boolean_checks.dart +227 -0
  67. package/dart_analyzer/lib/rules/dart/D022_use_setstate_correctly.dart +269 -0
  68. package/dart_analyzer/lib/rules/dart/D023_avoid_unnecessary_method_overrides.dart +191 -0
  69. package/dart_analyzer/lib/rules/dart/D024_avoid_unnecessary_stateful_widget.dart +194 -0
  70. package/dart_analyzer/lib/rules/dart/D025_avoid_nested_conditional_expressions.dart +90 -0
  71. package/dart_analyzer/lib/rules/security/S001_backend_auth_communications.dart +155 -0
  72. package/dart_analyzer/lib/rules/security/S002_os_command_injection.dart +159 -0
  73. package/dart_analyzer/lib/rules/security/S003_open_redirect_protection.dart +208 -0
  74. package/dart_analyzer/lib/rules/security/S004_sensitive_data_logging.dart +391 -0
  75. package/dart_analyzer/lib/rules/security/S005_trusted_service_authorization.dart +182 -0
  76. package/dart_analyzer/lib/rules/security/S006_no_default_credentials.dart +208 -0
  77. package/dart_analyzer/lib/rules/security/S007_output_encoding.dart +224 -0
  78. package/dart_analyzer/lib/rules/security/S008_svg_content_sanitization.dart +211 -0
  79. package/dart_analyzer/lib/rules/security/S009_no_insecure_encryption.dart +160 -0
  80. package/dart_analyzer/lib/rules/security/S010_use_csprng.dart +184 -0
  81. package/dart_analyzer/lib/rules/security/S011_ech_tls_config.dart +175 -0
  82. package/dart_analyzer/lib/rules/security/S012_hardcoded_secrets.dart +255 -0
  83. package/dart_analyzer/lib/rules/security/S013_tls_enforcement.dart +148 -0
  84. package/dart_analyzer/lib/rules/security/S014_tls_version_enforcement.dart +117 -0
  85. package/dart_analyzer/lib/rules/security/S015_insecure_tls_certificate.dart +315 -0
  86. package/dart_analyzer/lib/rules/security/S016_no_sensitive_querystring.dart +244 -0
  87. package/dart_analyzer/lib/rules/security/S017_use_parameterized_queries.dart +191 -0
  88. package/dart_analyzer/lib/rules/security/S018_no_sensitive_browser_storage.dart +175 -0
  89. package/dart_analyzer/lib/rules/security/S019_smtp_injection_protection.dart +166 -0
  90. package/dart_analyzer/lib/rules/security/S020_no_eval_dynamic_code.dart +149 -0
  91. package/dart_analyzer/lib/rules/security/S021_referrer_policy.dart +146 -0
  92. package/dart_analyzer/lib/rules/security/S022_escape_output_context.dart +111 -0
  93. package/dart_analyzer/lib/rules/security/S023_no_json_injection.dart +550 -0
  94. package/dart_analyzer/lib/rules/security/S024_xpath_xxe_protection.dart +299 -0
  95. package/dart_analyzer/lib/rules/security/S025_server_side_validation.dart +140 -0
  96. package/dart_analyzer/lib/rules/security/S026_tls_all_connections.dart +196 -0
  97. package/dart_analyzer/lib/rules/security/S027_mtls_certificate_validation.dart +195 -0
  98. package/dart_analyzer/lib/rules/security/S028_file_upload_size_limits.dart +186 -0
  99. package/dart_analyzer/lib/rules/security/S029_csrf_protection.dart +171 -0
  100. package/dart_analyzer/lib/rules/security/S030_directory_browsing_protection.dart +144 -0
  101. package/dart_analyzer/lib/rules/security/S031_secure_session_cookies.dart +118 -0
  102. package/dart_analyzer/lib/rules/security/S032_httponly_session_cookies.dart +114 -0
  103. package/dart_analyzer/lib/rules/security/S033_samesite_session_cookies.dart +120 -0
  104. package/dart_analyzer/lib/rules/security/S034_host_prefix_session_cookies.dart +160 -0
  105. package/dart_analyzer/lib/rules/security/S035_separate_app_hostnames.dart +117 -0
  106. package/dart_analyzer/lib/rules/security/S036_lfi_rfi_protection.dart +188 -0
  107. package/dart_analyzer/lib/rules/security/S037_cache_headers.dart +113 -0
  108. package/dart_analyzer/lib/rules/security/S038_no_version_headers.dart +114 -0
  109. package/dart_analyzer/lib/rules/security/S039_tls_certificate_validation.dart +131 -0
  110. package/dart_analyzer/lib/rules/security/S040_session_fixation_protection.dart +155 -0
  111. package/dart_analyzer/lib/rules/security/S041_session_token_invalidation.dart +201 -0
  112. package/dart_analyzer/lib/rules/security/S042_require_re_authentication_for_long_lived.dart +158 -0
  113. package/dart_analyzer/lib/rules/security/S043_password_changes_invalidate_all_sessions.dart +88 -0
  114. package/dart_analyzer/lib/rules/security/S044_re_authentication_required.dart +119 -0
  115. package/dart_analyzer/lib/rules/security/S045_brute_force_protection.dart +253 -0
  116. package/dart_analyzer/lib/rules/security/S046_jwt_algorithm_allowlist.dart +113 -0
  117. package/dart_analyzer/lib/rules/security/S047_oauth_pkce_protection.dart +124 -0
  118. package/dart_analyzer/lib/rules/security/S048_oauth_redirect_uri_validation.dart +134 -0
  119. package/dart_analyzer/lib/rules/security/S049_short_validity_tokens.dart +145 -0
  120. package/dart_analyzer/lib/rules/security/S050_reference_tokens_entropy.dart +234 -0
  121. package/dart_analyzer/lib/rules/security/S051_password_length_policy.dart +171 -0
  122. package/dart_analyzer/lib/rules/security/S052_weak_otp_entropy.dart +107 -0
  123. package/dart_analyzer/lib/rules/security/S053_generic_error_messages.dart +159 -0
  124. package/dart_analyzer/lib/rules/security/S054_no_default_accounts.dart +141 -0
  125. package/dart_analyzer/lib/rules/security/S055_content_type_validation.dart +324 -0
  126. package/dart_analyzer/lib/rules/security/S056_log_injection_protection.dart +119 -0
  127. package/dart_analyzer/lib/rules/security/S057_utc_logging.dart +114 -0
  128. package/dart_analyzer/lib/rules/security/S058_no_ssrf.dart +175 -0
  129. package/dart_analyzer/lib/rules/security/S059_disable_debug_mode.dart +172 -0
  130. package/dart_analyzer/lib/rules/security/S060_password_minimum_length.dart +170 -0
  131. package/dart_analyzer/lib/symbol_table_extractor.dart +510 -0
  132. package/dart_analyzer/lib/utils/common_utils.dart +26 -0
  133. package/dart_analyzer/pubspec.lock +557 -0
  134. package/dart_analyzer/pubspec.yaml +39 -0
  135. package/dart_analyzer/test/fixtures/complex_code.dart +95 -0
  136. package/docs/GENERATED_FILE_HANDLING_SUMMARY.md +2 -2
  137. package/package.json +3 -2
@@ -0,0 +1,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
+ }