@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,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
+ }