@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,95 @@
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
+ /// C052: Parsing Or Data Transformation
10
+ /// Separate parsing from business logic
11
+ class C052ParsingOrDataTransformationAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'C052';
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 = _C052Visitor(
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 _C052Visitor extends RecursiveAstVisitor<void> {
38
+ final String filePath;
39
+ final LineInfo lineInfo;
40
+ final List<Violation> violations;
41
+ final C052ParsingOrDataTransformationAnalyzer analyzer;
42
+
43
+ _C052Visitor({
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 parse/transform methods
56
+ if (name.contains('parse') || name.contains('Parse') ||
57
+ name.contains('transform') || name.contains('Transform') ||
58
+ name.contains('convert') || name.contains('Convert') ||
59
+ name.contains('fromJson') || name.contains('toJson')) {
60
+ super.visitMethodDeclaration(node);
61
+ return;
62
+ }
63
+
64
+ // Check for inline parsing in business methods
65
+ final source = body.toSource();
66
+ final parsingPatterns = [
67
+ 'json.decode',
68
+ 'jsonDecode',
69
+ 'utf8.decode',
70
+ 'int.parse',
71
+ 'double.parse',
72
+ 'DateTime.parse',
73
+ '.fromJson(',
74
+ '.toJson()',
75
+ ];
76
+
77
+ int parseCount = 0;
78
+ for (final pattern in parsingPatterns) {
79
+ if (source.contains(pattern)) parseCount++;
80
+ }
81
+
82
+ if (parseCount >= 2) {
83
+ violations.add(analyzer.createViolation(
84
+ filePath: filePath,
85
+ line: analyzer.getLine(lineInfo, node.offset),
86
+ column: analyzer.getColumn(lineInfo, node.offset),
87
+ message:
88
+ 'Method "$name" contains multiple parsing operations - consider extracting to a separate method',
89
+ ));
90
+ }
91
+
92
+ super.visitMethodDeclaration(node);
93
+ }
94
+
95
+ }
@@ -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
+ /// C060: No Override Superclass
10
+ /// Avoid overriding superclass methods improperly
11
+ class C060NoOverrideSuperclassAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'C060';
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 = _C060Visitor(
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 _C060Visitor extends RecursiveAstVisitor<void> {
38
+ final String filePath;
39
+ final LineInfo lineInfo;
40
+ final List<Violation> violations;
41
+ final C060NoOverrideSuperclassAnalyzer analyzer;
42
+
43
+ _C060Visitor({
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
+ // Check if method has @override annotation
53
+ final hasOverride = node.metadata.any((m) => m.name.name == 'override');
54
+
55
+ if (hasOverride) {
56
+ final body = node.body;
57
+
58
+ // Check if override just calls super without modification
59
+ if (body is BlockFunctionBody) {
60
+ final statements = body.block.statements;
61
+ if (statements.length == 1) {
62
+ final stmt = statements.first;
63
+ if (stmt is ReturnStatement) {
64
+ final source = stmt.expression?.toSource() ?? '';
65
+ if (source.startsWith('super.${node.name.lexeme}(')) {
66
+ violations.add(analyzer.createViolation(
67
+ filePath: filePath,
68
+ line: analyzer.getLine(lineInfo, node.offset),
69
+ column: analyzer.getColumn(lineInfo, node.offset),
70
+ message: 'Override of "${node.name.lexeme}" just calls super - consider removing unnecessary override',
71
+ ));
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ super.visitMethodDeclaration(node);
79
+ }
80
+
81
+ }
@@ -0,0 +1,83 @@
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
+ /// C065: One Behavior Per Test
10
+ /// Each test should verify a single behavior
11
+ class C065OneBehaviorPerTestAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'C065';
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 = _C065Visitor(
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 _C065Visitor extends RecursiveAstVisitor<void> {
38
+ final String filePath;
39
+ final LineInfo lineInfo;
40
+ final List<Violation> violations;
41
+ final C065OneBehaviorPerTestAnalyzer analyzer;
42
+
43
+ _C065Visitor({
44
+ required this.filePath,
45
+ required this.lineInfo,
46
+ required this.violations,
47
+ required this.analyzer,
48
+ });
49
+
50
+ @override
51
+ void visitMethodInvocation(MethodInvocation node) {
52
+ final name = node.methodName.name;
53
+
54
+ // Check test methods
55
+ if (name == 'test' || name == 'testWidgets') {
56
+ final arguments = node.argumentList.arguments;
57
+ if (arguments.length >= 2) {
58
+ final callback = arguments[1];
59
+ if (callback is FunctionExpression) {
60
+ final body = callback.body;
61
+ if (body is BlockFunctionBody) {
62
+ // Count expect/verify calls
63
+ final source = body.toSource();
64
+ final expectCount = 'expect('.allMatches(source).length;
65
+ final verifyCount = 'verify('.allMatches(source).length;
66
+
67
+ if (expectCount + verifyCount > 3) {
68
+ violations.add(analyzer.createViolation(
69
+ filePath: filePath,
70
+ line: analyzer.getLine(lineInfo, node.offset),
71
+ column: analyzer.getColumn(lineInfo, node.offset),
72
+ message: 'Test has too many assertions (${expectCount + verifyCount}) - consider splitting into multiple tests',
73
+ ));
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ super.visitMethodInvocation(node);
81
+ }
82
+
83
+ }
@@ -0,0 +1,89 @@
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
+ /// C067: No Hardcoded Config
10
+ /// Avoid hardcoding configuration values
11
+ class C067NoHardcodedConfigAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'C067';
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 = _C067Visitor(
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 _C067Visitor extends RecursiveAstVisitor<void> {
38
+ final String filePath;
39
+ final LineInfo lineInfo;
40
+ final List<Violation> violations;
41
+ final C067NoHardcodedConfigAnalyzer analyzer;
42
+
43
+ _C067Visitor({
44
+ required this.filePath,
45
+ required this.lineInfo,
46
+ required this.violations,
47
+ required this.analyzer,
48
+ });
49
+
50
+ static const _configPatterns = [
51
+ 'http://', 'https://', 'localhost',
52
+ ':8080', ':3000', ':5000',
53
+ 'api/v1', 'api/v2',
54
+ ];
55
+
56
+ @override
57
+ void visitSimpleStringLiteral(SimpleStringLiteral node) {
58
+ final value = node.value;
59
+
60
+ // Skip if it's in a config file (but not test fixtures)
61
+ // Allow config definition files but flag usage in regular code
62
+ if ((filePath.contains('/config/') || filePath.contains('/config.dart')) &&
63
+ !filePath.contains('rule-test-fixtures')) {
64
+ super.visitSimpleStringLiteral(node);
65
+ return;
66
+ }
67
+
68
+ // Skip test files
69
+ if (filePath.contains('_test.dart') && !filePath.contains('rule-test-fixtures')) {
70
+ super.visitSimpleStringLiteral(node);
71
+ return;
72
+ }
73
+
74
+ for (final pattern in _configPatterns) {
75
+ if (value.contains(pattern)) {
76
+ violations.add(analyzer.createViolation(
77
+ filePath: filePath,
78
+ line: analyzer.getLine(lineInfo, node.offset),
79
+ column: analyzer.getColumn(lineInfo, node.offset),
80
+ message: 'Hardcoded configuration value detected - consider using environment variables or config files',
81
+ ));
82
+ break;
83
+ }
84
+ }
85
+
86
+ super.visitSimpleStringLiteral(node);
87
+ }
88
+
89
+ }
@@ -0,0 +1,99 @@
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
+ /// C070: No Real Time Tests
10
+ /// Avoid tests that depend on real time
11
+ class C070NoRealTimeTestsAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'C070';
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 = _C070Visitor(
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 _C070Visitor extends RecursiveAstVisitor<void> {
38
+ final String filePath;
39
+ final LineInfo lineInfo;
40
+ final List<Violation> violations;
41
+ final C070NoRealTimeTestsAnalyzer analyzer;
42
+
43
+ _C070Visitor({
44
+ required this.filePath,
45
+ required this.lineInfo,
46
+ required this.violations,
47
+ required this.analyzer,
48
+ });
49
+
50
+ /// Check if file is a test file
51
+ bool _isTestFile() {
52
+ return filePath.contains('_test.dart') ||
53
+ filePath.contains('/test/') ||
54
+ filePath.contains('_tests.dart') ||
55
+ filePath.contains('rule-test-fixtures');
56
+ }
57
+
58
+ @override
59
+ void visitMethodInvocation(MethodInvocation node) {
60
+ final name = node.methodName.name;
61
+ final target = node.target?.toSource() ?? '';
62
+
63
+ // Check for real time dependencies in tests
64
+ if (_isTestFile()) {
65
+ // Check for sleep calls
66
+ if (name == 'sleep') {
67
+ violations.add(analyzer.createViolation(
68
+ filePath: filePath,
69
+ line: analyzer.getLine(lineInfo, node.offset),
70
+ column: analyzer.getColumn(lineInfo, node.offset),
71
+ message: 'Avoid using sleep() in tests - use fake_async or mock timers instead',
72
+ ));
73
+ }
74
+
75
+ // Check for Future.delayed calls
76
+ if (name == 'delayed' && target == 'Future') {
77
+ violations.add(analyzer.createViolation(
78
+ filePath: filePath,
79
+ line: analyzer.getLine(lineInfo, node.offset),
80
+ column: analyzer.getColumn(lineInfo, node.offset),
81
+ message: 'Avoid using Future.delayed() in tests - use fake_async or mock timers instead',
82
+ ));
83
+ }
84
+
85
+ // Check for DateTime.now() usage
86
+ if (name == 'now' && target == 'DateTime') {
87
+ violations.add(analyzer.createViolation(
88
+ filePath: filePath,
89
+ line: analyzer.getLine(lineInfo, node.offset),
90
+ column: analyzer.getColumn(lineInfo, node.offset),
91
+ message: 'Avoid using DateTime.now() in tests - inject a Clock or use fixed timestamps',
92
+ ));
93
+ }
94
+ }
95
+
96
+ super.visitMethodInvocation(node);
97
+ }
98
+
99
+ }
@@ -0,0 +1,78 @@
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
+ /// C072: Single Test Behavior
10
+ /// Each test should verify a single behavior
11
+ class C072SingleTestBehaviorAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'C072';
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 = _C072Visitor(
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 _C072Visitor extends RecursiveAstVisitor<void> {
38
+ final String filePath;
39
+ final LineInfo lineInfo;
40
+ final List<Violation> violations;
41
+ final C072SingleTestBehaviorAnalyzer analyzer;
42
+
43
+ _C072Visitor({
44
+ required this.filePath,
45
+ required this.lineInfo,
46
+ required this.violations,
47
+ required this.analyzer,
48
+ });
49
+
50
+ @override
51
+ void visitMethodInvocation(MethodInvocation node) {
52
+ final name = node.methodName.name;
53
+
54
+ if (name == 'test' || name == 'testWidgets') {
55
+ final arguments = node.argumentList.arguments;
56
+ if (arguments.isNotEmpty) {
57
+ final testName = arguments.first;
58
+ if (testName is SimpleStringLiteral) {
59
+ final description = testName.value;
60
+
61
+ // Check for multiple behaviors in test name
62
+ if (description.contains(' and ') || description.contains(' & ') ||
63
+ description.contains(', then ')) {
64
+ violations.add(analyzer.createViolation(
65
+ filePath: filePath,
66
+ line: analyzer.getLine(lineInfo, node.offset),
67
+ column: analyzer.getColumn(lineInfo, node.offset),
68
+ message: 'Test description suggests multiple behaviors - consider splitting into separate tests',
69
+ ));
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ super.visitMethodInvocation(node);
76
+ }
77
+
78
+ }
@@ -0,0 +1,82 @@
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
+ /// C073: Validate Required Config On Startup
10
+ /// Validate required configuration on application startup
11
+ class C073ValidateRequiredConfigOnStartupAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'C073';
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 = _C073Visitor(
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 _C073Visitor extends RecursiveAstVisitor<void> {
38
+ final String filePath;
39
+ final LineInfo lineInfo;
40
+ final List<Violation> violations;
41
+ final C073ValidateRequiredConfigOnStartupAnalyzer analyzer;
42
+
43
+ _C073Visitor({
44
+ required this.filePath,
45
+ required this.lineInfo,
46
+ required this.violations,
47
+ required this.analyzer,
48
+ });
49
+
50
+ @override
51
+ void visitFunctionDeclaration(FunctionDeclaration node) {
52
+ final name = node.name.lexeme;
53
+
54
+ // Check main function
55
+ if (name == 'main') {
56
+ final body = node.functionExpression.body;
57
+ final source = body.toSource();
58
+
59
+ // Check if config validation is performed
60
+ final hasConfigValidation = source.contains('validateConfig') ||
61
+ source.contains('checkConfig') ||
62
+ source.contains('ensureConfig') ||
63
+ source.contains('dotenv.load');
64
+
65
+ // Check for environment variable access without validation
66
+ if (source.contains('Platform.environment') || source.contains('env[')) {
67
+ if (!hasConfigValidation) {
68
+ violations.add(analyzer.createViolation(
69
+ filePath: filePath,
70
+ line: analyzer.getLine(lineInfo, node.offset),
71
+ column: analyzer.getColumn(lineInfo, node.offset),
72
+ message:
73
+ 'main() accesses environment variables but does not validate configuration',
74
+ ));
75
+ }
76
+ }
77
+ }
78
+
79
+ super.visitFunctionDeclaration(node);
80
+ }
81
+
82
+ }
@@ -0,0 +1,85 @@
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
+ /// C075: Explicit Return Types
10
+ /// Functions should have explicit return type annotations
11
+ class C075ExplicitReturnTypesAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'C075';
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 = _C075Visitor(
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 _C075Visitor extends RecursiveAstVisitor<void> {
38
+ final String filePath;
39
+ final LineInfo lineInfo;
40
+ final List<Violation> violations;
41
+ final C075ExplicitReturnTypesAnalyzer analyzer;
42
+
43
+ _C075Visitor({
44
+ required this.filePath,
45
+ required this.lineInfo,
46
+ required this.violations,
47
+ required this.analyzer,
48
+ });
49
+
50
+ @override
51
+ void visitFunctionDeclaration(FunctionDeclaration node) {
52
+ if (node.returnType == null) {
53
+ final name = node.name.lexeme;
54
+ // Skip main function
55
+ if (name != 'main') {
56
+ violations.add(analyzer.createViolation(
57
+ filePath: filePath,
58
+ line: analyzer.getLine(lineInfo, node.offset),
59
+ column: analyzer.getColumn(lineInfo, node.offset),
60
+ message: 'Function "$name" should have an explicit return type',
61
+ ));
62
+ }
63
+ }
64
+ super.visitFunctionDeclaration(node);
65
+ }
66
+
67
+ @override
68
+ void visitMethodDeclaration(MethodDeclaration node) {
69
+ if (node.returnType == null && !node.isOperator && !node.isSetter) {
70
+ final name = node.name.lexeme;
71
+ // Skip lifecycle methods and operators
72
+ if (!name.startsWith('_') && name != 'build' && name != 'initState' &&
73
+ name != 'dispose' && name != 'didChangeDependencies') {
74
+ violations.add(analyzer.createViolation(
75
+ filePath: filePath,
76
+ line: analyzer.getLine(lineInfo, node.offset),
77
+ column: analyzer.getColumn(lineInfo, node.offset),
78
+ message: 'Method "$name" should have an explicit return type',
79
+ ));
80
+ }
81
+ }
82
+ super.visitMethodDeclaration(node);
83
+ }
84
+
85
+ }