@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,158 @@
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
+ /// C017: Constructor Logic
10
+ /// Do not put business logic inside constructors
11
+ class C017ConstructorLogicAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'C017';
14
+
15
+ /// Maximum allowed statements in constructor
16
+ static const int maxConstructorStatements = 5;
17
+
18
+ /// Patterns that indicate business logic
19
+ static final Set<String> businessLogicPatterns = {
20
+ 'fetch',
21
+ 'load',
22
+ 'save',
23
+ 'delete',
24
+ 'update',
25
+ 'create',
26
+ 'process',
27
+ 'calculate',
28
+ 'validate',
29
+ 'send',
30
+ 'notify',
31
+ 'http',
32
+ 'api',
33
+ };
34
+
35
+ @override
36
+ List<Violation> analyze({
37
+ required CompilationUnit unit,
38
+ required String filePath,
39
+ required Rule rule,
40
+ required LineInfo lineInfo,
41
+ }) {
42
+ final violations = <Violation>[];
43
+
44
+ final visitor = _ConstructorLogicVisitor(
45
+ filePath: filePath,
46
+ lineInfo: lineInfo,
47
+ violations: violations,
48
+ analyzer: this,
49
+ );
50
+
51
+ unit.accept(visitor);
52
+
53
+ return violations;
54
+ }
55
+ }
56
+
57
+ class _ConstructorLogicVisitor extends RecursiveAstVisitor<void> {
58
+ final String filePath;
59
+ final LineInfo lineInfo;
60
+ final List<Violation> violations;
61
+ final C017ConstructorLogicAnalyzer analyzer;
62
+
63
+ _ConstructorLogicVisitor({
64
+ required this.filePath,
65
+ required this.lineInfo,
66
+ required this.violations,
67
+ required this.analyzer,
68
+ });
69
+
70
+ @override
71
+ void visitConstructorDeclaration(ConstructorDeclaration node) {
72
+ if (node.body is BlockFunctionBody) {
73
+ final body = node.body as BlockFunctionBody;
74
+ final statements = body.block.statements;
75
+
76
+ // Check for too many statements
77
+ if (statements.length > C017ConstructorLogicAnalyzer.maxConstructorStatements) {
78
+ violations.add(analyzer.createViolation(
79
+ filePath: filePath,
80
+ line: analyzer.getLine(lineInfo, node.offset),
81
+ column: analyzer.getColumn(lineInfo, node.offset),
82
+ message:
83
+ 'Constructor has ${statements.length} statements (max: ${C017ConstructorLogicAnalyzer.maxConstructorStatements}) - consider using init method',
84
+ metadata: {
85
+ 'statementCount': statements.length,
86
+ 'maxStatements': C017ConstructorLogicAnalyzer.maxConstructorStatements,
87
+ 'issue': 'too_many_statements',
88
+ },
89
+ ));
90
+ }
91
+
92
+ // Check for business logic calls
93
+ final logicFinder = _BusinessLogicFinder(
94
+ filePath: filePath,
95
+ lineInfo: lineInfo,
96
+ violations: violations,
97
+ analyzer: analyzer,
98
+ );
99
+ body.block.accept(logicFinder);
100
+ }
101
+
102
+ super.visitConstructorDeclaration(node);
103
+ }
104
+ }
105
+
106
+ class _BusinessLogicFinder extends RecursiveAstVisitor<void> {
107
+ final String filePath;
108
+ final LineInfo lineInfo;
109
+ final List<Violation> violations;
110
+ final C017ConstructorLogicAnalyzer analyzer;
111
+
112
+ _BusinessLogicFinder({
113
+ required this.filePath,
114
+ required this.lineInfo,
115
+ required this.violations,
116
+ required this.analyzer,
117
+ });
118
+
119
+ @override
120
+ void visitMethodInvocation(MethodInvocation node) {
121
+ final methodName = node.methodName.name.toLowerCase();
122
+
123
+ for (final pattern in C017ConstructorLogicAnalyzer.businessLogicPatterns) {
124
+ if (methodName.contains(pattern)) {
125
+ violations.add(analyzer.createViolation(
126
+ filePath: filePath,
127
+ line: analyzer.getLine(lineInfo, node.offset),
128
+ column: analyzer.getColumn(lineInfo, node.offset),
129
+ message:
130
+ 'Business logic method "${node.methodName.name}" should not be called in constructor',
131
+ metadata: {
132
+ 'method': node.methodName.name,
133
+ 'pattern': pattern,
134
+ 'issue': 'business_logic_in_constructor',
135
+ },
136
+ ));
137
+ break;
138
+ }
139
+ }
140
+
141
+ super.visitMethodInvocation(node);
142
+ }
143
+
144
+ @override
145
+ void visitAwaitExpression(AwaitExpression node) {
146
+ violations.add(analyzer.createViolation(
147
+ filePath: filePath,
148
+ line: analyzer.getLine(lineInfo, node.offset),
149
+ column: analyzer.getColumn(lineInfo, node.offset),
150
+ message: 'Await in constructor indicates async business logic - consider using factory or init method',
151
+ metadata: {
152
+ 'issue': 'await_in_constructor',
153
+ },
154
+ ));
155
+
156
+ super.visitAwaitExpression(node);
157
+ }
158
+ }
@@ -0,0 +1,141 @@
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
+ /// C018: No Throw Generic Error
10
+ /// Always provide detailed messages and context for errors
11
+ class C018NoThrowGenericErrorAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'C018';
14
+
15
+ /// Generic exception types that should be avoided
16
+ static final Set<String> genericExceptions = {
17
+ 'Exception',
18
+ 'Error',
19
+ 'StateError',
20
+ 'ArgumentError',
21
+ };
22
+
23
+ /// Minimum message length for meaningful error messages
24
+ static const int minMessageLength = 10;
25
+
26
+ @override
27
+ List<Violation> analyze({
28
+ required CompilationUnit unit,
29
+ required String filePath,
30
+ required Rule rule,
31
+ required LineInfo lineInfo,
32
+ }) {
33
+ final violations = <Violation>[];
34
+
35
+ final visitor = _GenericErrorVisitor(
36
+ filePath: filePath,
37
+ lineInfo: lineInfo,
38
+ violations: violations,
39
+ analyzer: this,
40
+ );
41
+
42
+ unit.accept(visitor);
43
+
44
+ return violations;
45
+ }
46
+ }
47
+
48
+ class _GenericErrorVisitor extends RecursiveAstVisitor<void> {
49
+ final String filePath;
50
+ final LineInfo lineInfo;
51
+ final List<Violation> violations;
52
+ final C018NoThrowGenericErrorAnalyzer analyzer;
53
+
54
+ _GenericErrorVisitor({
55
+ required this.filePath,
56
+ required this.lineInfo,
57
+ required this.violations,
58
+ required this.analyzer,
59
+ });
60
+
61
+ @override
62
+ void visitThrowExpression(ThrowExpression node) {
63
+ final expression = node.expression;
64
+
65
+ String? typeName;
66
+ NodeList<Expression>? args;
67
+
68
+ // Handle both InstanceCreationExpression and MethodInvocation
69
+ // In Dart, `throw Exception('msg')` can be parsed as MethodInvocation
70
+ // when not using 'new' keyword
71
+ if (expression is InstanceCreationExpression) {
72
+ typeName = expression.constructorName.type.name2.lexeme;
73
+ args = expression.argumentList.arguments;
74
+ } else if (expression is MethodInvocation) {
75
+ // For calls like Exception('message') or Error()
76
+ typeName = expression.methodName.name;
77
+ args = expression.argumentList.arguments;
78
+ }
79
+
80
+ if (typeName == null || args == null) {
81
+ super.visitThrowExpression(node);
82
+ return;
83
+ }
84
+
85
+ // Check if it's a generic exception
86
+ if (C018NoThrowGenericErrorAnalyzer.genericExceptions.contains(typeName)) {
87
+ // Check if message is provided and meaningful
88
+ if (args.isEmpty) {
89
+ violations.add(analyzer.createViolation(
90
+ filePath: filePath,
91
+ line: analyzer.getLine(lineInfo, node.offset),
92
+ column: analyzer.getColumn(lineInfo, node.offset),
93
+ message: 'throw $typeName() without message - provide a descriptive error message',
94
+ metadata: {
95
+ 'exceptionType': typeName,
96
+ 'issue': 'no_message',
97
+ },
98
+ ));
99
+ } else {
100
+ final firstArg = args.first;
101
+ if (firstArg is StringLiteral) {
102
+ final message = firstArg.stringValue ?? '';
103
+ if (message.length < C018NoThrowGenericErrorAnalyzer.minMessageLength) {
104
+ violations.add(analyzer.createViolation(
105
+ filePath: filePath,
106
+ line: analyzer.getLine(lineInfo, node.offset),
107
+ column: analyzer.getColumn(lineInfo, node.offset),
108
+ message:
109
+ 'throw $typeName with short message "$message" - provide more context',
110
+ metadata: {
111
+ 'exceptionType': typeName,
112
+ 'message': message,
113
+ 'issue': 'short_message',
114
+ },
115
+ ));
116
+ }
117
+ }
118
+ }
119
+
120
+ // Suggest using custom exception
121
+ if (typeName == 'Exception' || typeName == 'Error') {
122
+ final isException = typeName == 'Exception';
123
+ violations.add(analyzer.createViolation(
124
+ filePath: filePath,
125
+ line: analyzer.getLine(lineInfo, node.offset),
126
+ column: analyzer.getColumn(lineInfo, node.offset),
127
+ message:
128
+ 'Avoid throwing generic ${isException ? 'Exception' : 'Error'} - use a specific ${isException ? 'Exception' : 'Error'} type',
129
+ metadata: {
130
+ 'exceptionType': typeName,
131
+ 'issue': 'generic_exception',
132
+ 'suggestion':
133
+ 'Create a custom ${isException ? 'Exception' : 'Error'} class',
134
+ },
135
+ ));
136
+ }
137
+ }
138
+
139
+ super.visitThrowExpression(node);
140
+ }
141
+ }
@@ -0,0 +1,165 @@
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
+ /// C019: Log Level Usage
10
+ /// Do not use error log level for non-critical errors
11
+ class C019LogLevelUsageAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'C019';
14
+
15
+ /// Keywords that indicate non-critical issues
16
+ static final Set<String> nonCriticalKeywords = {
17
+ 'not found',
18
+ 'invalid',
19
+ 'unauthorized',
20
+ 'forbidden',
21
+ 'validation failed',
22
+ 'bad request',
23
+ 'cache miss',
24
+ 'retry',
25
+ 'fallback',
26
+ 'user error',
27
+ 'input error',
28
+ 'validation',
29
+ 'missing parameter',
30
+ 'exceed',
31
+ 'limit',
32
+ };
33
+
34
+ /// Keywords that indicate legitimate errors
35
+ static final Set<String> legitimateErrorKeywords = {
36
+ 'exception',
37
+ 'crash',
38
+ 'fatal',
39
+ 'critical',
40
+ 'emergency',
41
+ 'database',
42
+ 'connection',
43
+ 'timeout',
44
+ 'security breach',
45
+ 'system error',
46
+ 'memory',
47
+ 'disk space',
48
+ 'internal server error',
49
+ 'unhandled exception',
50
+ 'stack overflow',
51
+ };
52
+
53
+ /// Error logger patterns
54
+ static final RegExp errorLogPattern = RegExp(
55
+ r'\.(error|e)\s*\(',
56
+ caseSensitive: false,
57
+ );
58
+
59
+ @override
60
+ List<Violation> analyze({
61
+ required CompilationUnit unit,
62
+ required String filePath,
63
+ required Rule rule,
64
+ required LineInfo lineInfo,
65
+ }) {
66
+ final violations = <Violation>[];
67
+
68
+ final visitor = _LogLevelVisitor(
69
+ filePath: filePath,
70
+ lineInfo: lineInfo,
71
+ violations: violations,
72
+ analyzer: this,
73
+ );
74
+
75
+ unit.accept(visitor);
76
+
77
+ return violations;
78
+ }
79
+ }
80
+
81
+ class _LogLevelVisitor extends RecursiveAstVisitor<void> {
82
+ final String filePath;
83
+ final LineInfo lineInfo;
84
+ final List<Violation> violations;
85
+ final C019LogLevelUsageAnalyzer analyzer;
86
+
87
+ _LogLevelVisitor({
88
+ required this.filePath,
89
+ required this.lineInfo,
90
+ required this.violations,
91
+ required this.analyzer,
92
+ });
93
+
94
+ @override
95
+ void visitMethodInvocation(MethodInvocation node) {
96
+ final methodName = node.methodName.name.toLowerCase();
97
+
98
+ // Check if it's an error log call
99
+ if (methodName == 'error' || methodName == 'e') {
100
+ final args = node.argumentList.arguments;
101
+
102
+ if (args.isNotEmpty) {
103
+ final firstArg = args.first;
104
+ String? messageContent;
105
+
106
+ if (firstArg is SimpleStringLiteral) {
107
+ // Simple string like 'hello world'
108
+ messageContent = firstArg.value.toLowerCase();
109
+ } else if (firstArg is StringInterpolation) {
110
+ // Interpolated string like 'hello $name'
111
+ // Extract text elements only
112
+ final buffer = StringBuffer();
113
+ for (final element in firstArg.elements) {
114
+ if (element is InterpolationString) {
115
+ buffer.write(element.value);
116
+ }
117
+ }
118
+ messageContent = buffer.toString().toLowerCase();
119
+ } else if (firstArg is AdjacentStrings) {
120
+ // Adjacent strings like 'hello' 'world'
121
+ final buffer = StringBuffer();
122
+ for (final str in firstArg.strings) {
123
+ if (str is SimpleStringLiteral) {
124
+ buffer.write(str.value);
125
+ }
126
+ }
127
+ messageContent = buffer.toString().toLowerCase();
128
+ }
129
+
130
+ if (messageContent != null) {
131
+ // Check for non-critical keywords
132
+ for (final keyword in C019LogLevelUsageAnalyzer.nonCriticalKeywords) {
133
+ if (messageContent.contains(keyword)) {
134
+ // But skip if it also contains legitimate error keywords
135
+ bool hasLegitimateError = false;
136
+ for (final legit in C019LogLevelUsageAnalyzer.legitimateErrorKeywords) {
137
+ if (messageContent.contains(legit)) {
138
+ hasLegitimateError = true;
139
+ break;
140
+ }
141
+ }
142
+
143
+ if (!hasLegitimateError) {
144
+ violations.add(analyzer.createViolation(
145
+ filePath: filePath,
146
+ line: analyzer.getLine(lineInfo, node.offset),
147
+ column: analyzer.getColumn(lineInfo, node.offset),
148
+ message:
149
+ 'log.error() used for non-critical issue "$keyword" - consider using log.warn()',
150
+ metadata: {
151
+ 'keyword': keyword,
152
+ 'suggestion': 'Use log.warn() or log.info() instead',
153
+ },
154
+ ));
155
+ break;
156
+ }
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
162
+
163
+ super.visitMethodInvocation(node);
164
+ }
165
+ }
@@ -0,0 +1,128 @@
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
+ /// C020: Unused Imports
10
+ /// Do not import unused modules or symbols
11
+ class C020UnusedImportsAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'C020';
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
+ // Collect all imports
25
+ final imports = <ImportDirective>[];
26
+ final importedPrefixes = <String, ImportDirective>{};
27
+ final showClauses = <String, ImportDirective>{};
28
+
29
+ for (final directive in unit.directives) {
30
+ if (directive is ImportDirective) {
31
+ imports.add(directive);
32
+
33
+ // Track prefixed imports
34
+ final prefix = directive.prefix?.name;
35
+ if (prefix != null) {
36
+ importedPrefixes[prefix] = directive;
37
+ }
38
+
39
+ // Track show clauses
40
+ for (final combinator in directive.combinators) {
41
+ if (combinator is ShowCombinator) {
42
+ for (final name in combinator.shownNames) {
43
+ showClauses[name.name] = directive;
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+
50
+ // Collect all identifiers used in the file
51
+ final usedIdentifiers = <String>{};
52
+ final usedPrefixes = <String>{};
53
+
54
+ final visitor = _IdentifierCollector(
55
+ usedIdentifiers: usedIdentifiers,
56
+ usedPrefixes: usedPrefixes,
57
+ );
58
+ unit.accept(visitor);
59
+
60
+ // Check for unused prefixed imports
61
+ for (final entry in importedPrefixes.entries) {
62
+ if (!usedPrefixes.contains(entry.key)) {
63
+ violations.add(createViolation(
64
+ filePath: filePath,
65
+ line: getLine(lineInfo, entry.value.offset),
66
+ column: getColumn(lineInfo, entry.value.offset),
67
+ message: 'Import with prefix "${entry.key}" is never used',
68
+ metadata: {
69
+ 'prefix': entry.key,
70
+ 'import': entry.value.uri.stringValue ?? '',
71
+ 'issue': 'unused_prefix',
72
+ },
73
+ ));
74
+ }
75
+ }
76
+
77
+ // Check for unused show clause symbols
78
+ for (final entry in showClauses.entries) {
79
+ if (!usedIdentifiers.contains(entry.key)) {
80
+ violations.add(createViolation(
81
+ filePath: filePath,
82
+ line: getLine(lineInfo, entry.value.offset),
83
+ column: getColumn(lineInfo, entry.value.offset),
84
+ message: 'Symbol "${entry.key}" from import is never used',
85
+ metadata: {
86
+ 'symbol': entry.key,
87
+ 'import': entry.value.uri.stringValue ?? '',
88
+ 'issue': 'unused_symbol',
89
+ },
90
+ ));
91
+ }
92
+ }
93
+
94
+ return violations;
95
+ }
96
+ }
97
+
98
+ class _IdentifierCollector extends RecursiveAstVisitor<void> {
99
+ final Set<String> usedIdentifiers;
100
+ final Set<String> usedPrefixes;
101
+
102
+ _IdentifierCollector({
103
+ required this.usedIdentifiers,
104
+ required this.usedPrefixes,
105
+ });
106
+
107
+ @override
108
+ void visitSimpleIdentifier(SimpleIdentifier node) {
109
+ usedIdentifiers.add(node.name);
110
+ super.visitSimpleIdentifier(node);
111
+ }
112
+
113
+ @override
114
+ void visitPrefixedIdentifier(PrefixedIdentifier node) {
115
+ usedPrefixes.add(node.prefix.name);
116
+ usedIdentifiers.add(node.identifier.name);
117
+ super.visitPrefixedIdentifier(node);
118
+ }
119
+
120
+ @override
121
+ void visitNamedType(NamedType node) {
122
+ if (node.importPrefix != null) {
123
+ usedPrefixes.add(node.importPrefix!.name.lexeme);
124
+ }
125
+ usedIdentifiers.add(node.name2.lexeme);
126
+ super.visitNamedType(node);
127
+ }
128
+ }
@@ -0,0 +1,86 @@
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
+ /// C021: Import Organization
10
+ /// Enforce organized imports with grouping and sorting
11
+ class C021ImportOrganizationAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'C021';
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 = _C021Visitor(
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 _C021Visitor extends RecursiveAstVisitor<void> {
38
+ final String filePath;
39
+ final LineInfo lineInfo;
40
+ final List<Violation> violations;
41
+ final C021ImportOrganizationAnalyzer analyzer;
42
+
43
+ String _lastImportType = '';
44
+
45
+ _C021Visitor({
46
+ required this.filePath,
47
+ required this.lineInfo,
48
+ required this.violations,
49
+ required this.analyzer,
50
+ });
51
+
52
+ @override
53
+ void visitImportDirective(ImportDirective node) {
54
+ // Check import organization
55
+ final uri = node.uri.stringValue ?? '';
56
+
57
+ // Track import order for validation
58
+ // Dart imports should follow: dart:, package:, relative
59
+ if (uri.startsWith('dart:')) {
60
+ if (_lastImportType == 'package' || _lastImportType == 'relative') {
61
+ violations.add(analyzer.createViolation(
62
+ filePath: filePath,
63
+ line: analyzer.getLine(lineInfo, node.offset),
64
+ column: analyzer.getColumn(lineInfo, node.offset),
65
+ message: 'dart: imports should come before package: and relative imports',
66
+ ));
67
+ }
68
+ _lastImportType = 'dart';
69
+ } else if (uri.startsWith('package:')) {
70
+ if (_lastImportType == 'relative') {
71
+ violations.add(analyzer.createViolation(
72
+ filePath: filePath,
73
+ line: analyzer.getLine(lineInfo, node.offset),
74
+ column: analyzer.getColumn(lineInfo, node.offset),
75
+ message: 'package: imports should come before relative imports',
76
+ ));
77
+ }
78
+ _lastImportType = 'package';
79
+ } else {
80
+ _lastImportType = 'relative';
81
+ }
82
+
83
+ super.visitImportDirective(node);
84
+ }
85
+
86
+ }