@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,104 @@
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
+ /// C076: Explicit Function Types
10
+ /// Function parameters should have explicit type annotations
11
+ class C076ExplicitFunctionTypesAnalyzer extends BaseAnalyzer {
12
+ @override
13
+ String get ruleId => 'C076';
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 = _C076Visitor(
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 _C076Visitor extends RecursiveAstVisitor<void> {
38
+ final String filePath;
39
+ final LineInfo lineInfo;
40
+ final List<Violation> violations;
41
+ final C076ExplicitFunctionTypesAnalyzer analyzer;
42
+
43
+ _C076Visitor({
44
+ required this.filePath,
45
+ required this.lineInfo,
46
+ required this.violations,
47
+ required this.analyzer,
48
+ });
49
+
50
+ @override
51
+ void visitFormalParameterList(FormalParameterList node) {
52
+ // Skip lambda/closure parameters - they're inferable from context
53
+ if (_isLambdaParameter(node)) {
54
+ super.visitFormalParameterList(node);
55
+ return;
56
+ }
57
+
58
+ for (final param in node.parameters) {
59
+ _checkParameter(param);
60
+ }
61
+ super.visitFormalParameterList(node);
62
+ }
63
+
64
+ bool _isLambdaParameter(FormalParameterList node) {
65
+ // Check if this parameter list belongs to a FunctionExpression (lambda/closure)
66
+ final parent = node.parent;
67
+ return parent is FunctionExpression;
68
+ }
69
+
70
+ void _checkParameter(FormalParameter param) {
71
+ // Handle SimpleFormalParameter (regular parameters)
72
+ if (param is SimpleFormalParameter) {
73
+ if (param.type == null && param.name != null) {
74
+ _reportViolation(param.name!.lexeme, param.offset);
75
+ }
76
+ }
77
+ // Handle DefaultFormalParameter (named and optional positional parameters)
78
+ else if (param is DefaultFormalParameter) {
79
+ final innerParam = param.parameter;
80
+ // Check the inner parameter (could be SimpleFormalParameter or FieldFormalParameter)
81
+ if (innerParam is SimpleFormalParameter) {
82
+ if (innerParam.type == null && innerParam.name != null) {
83
+ _reportViolation(innerParam.name!.lexeme, innerParam.offset);
84
+ }
85
+ }
86
+ // FieldFormalParameter (this.field) inherits type from field, so skip
87
+ }
88
+ // FieldFormalParameter at top level (this.field) - skip, inherits type
89
+ // FunctionTypedFormalParameter - already has type definition
90
+ }
91
+
92
+ void _reportViolation(String name, int offset) {
93
+ // Skip underscore (unused) parameters
94
+ if (!name.startsWith('_')) {
95
+ violations.add(analyzer.createViolation(
96
+ filePath: filePath,
97
+ line: analyzer.getLine(lineInfo, offset),
98
+ column: analyzer.getColumn(lineInfo, offset),
99
+ message: 'Parameter "$name" should have an explicit type annotation',
100
+ ));
101
+ }
102
+ }
103
+
104
+ }
@@ -0,0 +1,309 @@
1
+ import 'dart:io';
2
+
3
+ import 'package:analyzer/dart/ast/ast.dart';
4
+ import 'package:analyzer/source/line_info.dart';
5
+ import 'package:path/path.dart' as path;
6
+ import 'package:yaml/yaml.dart';
7
+
8
+ import '../../models/rule.dart';
9
+ import '../../models/violation.dart';
10
+ import '../base_analyzer.dart';
11
+
12
+ /// D001: Recommended Lint Rules Should Be Enabled
13
+ /// Ensures that analysis_options.yaml includes recommended lint packages
14
+ /// (flutter_lints, very_good_analysis, or lints) and critical rules are enabled
15
+ class D001RecommendedLintRulesAnalyzer extends BaseAnalyzer {
16
+ @override
17
+ String get ruleId => 'D001';
18
+
19
+ // Recommended lint packages that provide good defaults
20
+ static const _recommendedPackages = [
21
+ 'flutter_lints',
22
+ 'very_good_analysis',
23
+ 'lints',
24
+ 'pedantic',
25
+ 'effective_dart',
26
+ ];
27
+
28
+ // Critical rules that should not be disabled
29
+ static const _criticalRules = [
30
+ // Error-level rules - these catch real bugs
31
+ 'avoid_empty_else',
32
+ 'avoid_print',
33
+ 'avoid_returning_null_for_future',
34
+ 'avoid_type_to_string',
35
+ 'avoid_types_as_parameter_names',
36
+ 'cancel_subscriptions',
37
+ 'close_sinks',
38
+ 'empty_statements',
39
+ 'hash_and_equals',
40
+ 'no_duplicate_case_values',
41
+ 'throw_in_finally',
42
+ 'unnecessary_statements',
43
+ 'unrelated_type_equality_checks',
44
+ 'valid_regexps',
45
+ ];
46
+
47
+ // Rules that are highly recommended but can be disabled with justification
48
+ // Reserved for future use when we add warnings for disabling recommended rules
49
+ // ignore: unused_field
50
+ static const _recommendedRules = [
51
+ 'always_declare_return_types',
52
+ 'annotate_overrides',
53
+ 'await_only_futures',
54
+ 'camel_case_types',
55
+ 'curly_braces_in_flow_control_structures',
56
+ 'empty_catches',
57
+ 'exhaustive_cases',
58
+ 'file_names',
59
+ 'prefer_const_constructors',
60
+ 'prefer_final_fields',
61
+ 'prefer_is_empty',
62
+ 'prefer_is_not_empty',
63
+ ];
64
+
65
+ // Track which projects we've already checked to avoid duplicate checks
66
+ static final Set<String> _checkedProjects = {};
67
+
68
+ // Track visited files to prevent infinite loops in include chains
69
+ final Set<String> _visitedFiles = {};
70
+
71
+ @override
72
+ List<Violation> analyze({
73
+ required CompilationUnit unit,
74
+ required String filePath,
75
+ required Rule rule,
76
+ required LineInfo lineInfo,
77
+ }) {
78
+ final violations = <Violation>[];
79
+
80
+ // This rule analyzes analysis_options.yaml, not Dart files
81
+ // We need to find and analyze the analysis_options.yaml in the project
82
+ final projectRoot = _findProjectRoot(filePath);
83
+ if (projectRoot == null) {
84
+ return violations;
85
+ }
86
+
87
+ // Performance optimization: Skip if we've already checked this project
88
+ if (_checkedProjects.contains(projectRoot)) {
89
+ return violations;
90
+ }
91
+
92
+ // Mark as checked immediately
93
+ _checkedProjects.add(projectRoot);
94
+
95
+ final analysisOptionsPath = path.join(projectRoot, 'analysis_options.yaml');
96
+ final analysisOptionsFile = File(analysisOptionsPath);
97
+
98
+ if (!analysisOptionsFile.existsSync()) {
99
+ // No analysis_options.yaml - this is a violation
100
+ violations.add(createViolation(
101
+ filePath: filePath,
102
+ line: 1,
103
+ column: 1,
104
+ message:
105
+ 'Missing analysis_options.yaml. Add one with recommended lint rules (flutter_lints or very_good_analysis)',
106
+ ));
107
+ return violations;
108
+ }
109
+
110
+ // Parse and analyze the YAML file
111
+ try {
112
+ final content = analysisOptionsFile.readAsStringSync();
113
+ final yaml = loadYaml(content);
114
+
115
+ if (yaml is! YamlMap) {
116
+ violations.add(createViolation(
117
+ filePath: analysisOptionsPath,
118
+ line: 1,
119
+ column: 1,
120
+ message: 'Invalid analysis_options.yaml format',
121
+ ));
122
+ return violations;
123
+ }
124
+
125
+ // Check for include statement (recommended packages)
126
+ // Follow include chains recursively
127
+ final hasRecommendedPackage = _checkForRecommendedPackage(
128
+ yaml,
129
+ content,
130
+ projectRoot,
131
+ analysisOptionsPath,
132
+ );
133
+ if (!hasRecommendedPackage) {
134
+ violations.add(createViolation(
135
+ filePath: analysisOptionsPath,
136
+ line: 1,
137
+ column: 1,
138
+ message:
139
+ 'analysis_options.yaml should include a recommended lint package. '
140
+ 'Add: include: package:flutter_lints/flutter.yaml or package:very_good_analysis/analysis_options.yaml',
141
+ ));
142
+ }
143
+
144
+ // Check for disabled critical rules
145
+ final disabledCriticalRules =
146
+ _findDisabledCriticalRules(yaml, content, analysisOptionsPath);
147
+ violations.addAll(disabledCriticalRules);
148
+ } catch (e) {
149
+ // YAML parsing error - skip this file
150
+ }
151
+
152
+ return violations;
153
+ }
154
+
155
+ String? _findProjectRoot(String filePath) {
156
+ var dir = Directory(path.dirname(filePath));
157
+
158
+ while (dir.path != dir.parent.path) {
159
+ final pubspec = File(path.join(dir.path, 'pubspec.yaml'));
160
+ if (pubspec.existsSync()) {
161
+ return dir.path;
162
+ }
163
+ dir = dir.parent;
164
+ }
165
+
166
+ return null;
167
+ }
168
+
169
+ bool _checkForRecommendedPackage(
170
+ YamlMap yaml,
171
+ String content,
172
+ String projectRoot,
173
+ String currentFilePath,
174
+ ) {
175
+ // Prevent infinite loops in circular includes
176
+ if (_visitedFiles.contains(currentFilePath)) {
177
+ return false;
178
+ }
179
+ _visitedFiles.add(currentFilePath);
180
+
181
+ // Check direct include statement
182
+ final include = yaml['include'];
183
+ if (include != null && include is String) {
184
+ // Check if it's a package include with recommended package
185
+ if (include.startsWith('package:')) {
186
+ for (final pkg in _recommendedPackages) {
187
+ if (include.contains(pkg)) {
188
+ return true;
189
+ }
190
+ }
191
+ }
192
+
193
+ // Follow local file includes
194
+ if (!include.startsWith('package:')) {
195
+ final includedFile = _resolveIncludePath(include, projectRoot, currentFilePath);
196
+ if (includedFile != null && includedFile.existsSync()) {
197
+ try {
198
+ final includedContent = includedFile.readAsStringSync();
199
+ final includedYaml = loadYaml(includedContent);
200
+
201
+ if (includedYaml is YamlMap) {
202
+ // Recursively check the included file
203
+ final foundInIncluded = _checkForRecommendedPackage(
204
+ includedYaml,
205
+ includedContent,
206
+ projectRoot,
207
+ includedFile.path,
208
+ );
209
+ if (foundInIncluded) {
210
+ return true;
211
+ }
212
+ }
213
+ } catch (e) {
214
+ // Failed to parse included file, skip it
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ // Also check if content has package: in include (regex fallback)
221
+ final includePattern = RegExp(r'include:\s*package:(\w+)');
222
+ final match = includePattern.firstMatch(content);
223
+ if (match != null) {
224
+ final packageName = match.group(1);
225
+ if (packageName != null &&
226
+ _recommendedPackages
227
+ .any((pkg) => pkg.replaceAll('_', '').contains(packageName))) {
228
+ return true;
229
+ }
230
+ }
231
+
232
+ return false;
233
+ }
234
+
235
+ File? _resolveIncludePath(String includePath, String projectRoot, String currentFilePath) {
236
+ // Handle absolute paths
237
+ if (path.isAbsolute(includePath)) {
238
+ return File(includePath);
239
+ }
240
+
241
+ // Handle relative paths (relative to current file's directory)
242
+ final currentDir = path.dirname(currentFilePath);
243
+ final resolvedPath = path.normalize(path.join(currentDir, includePath));
244
+
245
+ return File(resolvedPath);
246
+ }
247
+
248
+ List<Violation> _findDisabledCriticalRules(
249
+ YamlMap yaml, String content, String filePath) {
250
+ final violations = <Violation>[];
251
+
252
+ final linter = yaml['linter'];
253
+ if (linter == null || linter is! YamlMap) {
254
+ return violations;
255
+ }
256
+
257
+ final rules = linter['rules'];
258
+ if (rules == null) {
259
+ return violations;
260
+ }
261
+
262
+ if (rules is YamlMap) {
263
+ // Rules as map: { rule_name: false }
264
+ for (final rule in _criticalRules) {
265
+ final ruleValue = rules[rule];
266
+ if (ruleValue == false) {
267
+ final lineNumber = _findLineNumber(content, rule);
268
+ violations.add(createViolation(
269
+ filePath: filePath,
270
+ line: lineNumber,
271
+ column: 1,
272
+ message:
273
+ 'Critical lint rule "$rule" should not be disabled. It helps catch real bugs.',
274
+ ));
275
+ }
276
+ }
277
+ } else if (rules is YamlList) {
278
+ // Rules as list with negation: [ avoid_print: false ]
279
+ for (final item in rules) {
280
+ if (item is YamlMap) {
281
+ for (final rule in _criticalRules) {
282
+ if (item.containsKey(rule) && item[rule] == false) {
283
+ final lineNumber = _findLineNumber(content, rule);
284
+ violations.add(createViolation(
285
+ filePath: filePath,
286
+ line: lineNumber,
287
+ column: 1,
288
+ message:
289
+ 'Critical lint rule "$rule" should not be disabled. It helps catch real bugs.',
290
+ ));
291
+ }
292
+ }
293
+ }
294
+ }
295
+ }
296
+
297
+ return violations;
298
+ }
299
+
300
+ int _findLineNumber(String content, String searchText) {
301
+ final lines = content.split('\n');
302
+ for (var i = 0; i < lines.length; i++) {
303
+ if (lines[i].contains(searchText)) {
304
+ return i + 1;
305
+ }
306
+ }
307
+ return 1;
308
+ }
309
+ }