@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.
- package/dart_analyzer/README.md +226 -0
- package/dart_analyzer/analysis_options.yaml +66 -0
- package/dart_analyzer/bin/sunlint-dart-macos +0 -0
- package/dart_analyzer/bin/sunlint_dart_analyzer.dart +124 -0
- package/dart_analyzer/lib/analyzer_service.dart +625 -0
- package/dart_analyzer/lib/json_rpc_server.dart +275 -0
- package/dart_analyzer/lib/models/rule.dart +67 -0
- package/dart_analyzer/lib/models/symbol_table.dart +607 -0
- package/dart_analyzer/lib/models/violation.dart +69 -0
- package/dart_analyzer/lib/rules/base_analyzer.dart +52 -0
- package/dart_analyzer/lib/rules/common/C002_no_duplicate_code.dart +344 -0
- package/dart_analyzer/lib/rules/common/C003_no_vague_abbreviations.dart +318 -0
- package/dart_analyzer/lib/rules/common/C006_function_naming.dart +219 -0
- package/dart_analyzer/lib/rules/common/C008_variable_declaration_locality.dart +205 -0
- package/dart_analyzer/lib/rules/common/C010_limit_block_nesting.dart +162 -0
- package/dart_analyzer/lib/rules/common/C012_command_query_separation.dart +214 -0
- package/dart_analyzer/lib/rules/common/C013_no_dead_code.dart +225 -0
- package/dart_analyzer/lib/rules/common/C014_dependency_injection.dart +249 -0
- package/dart_analyzer/lib/rules/common/C017_constructor_logic.dart +158 -0
- package/dart_analyzer/lib/rules/common/C018_no_throw_generic_error.dart +141 -0
- package/dart_analyzer/lib/rules/common/C019_log_level_usage.dart +165 -0
- package/dart_analyzer/lib/rules/common/C020_unused_imports.dart +128 -0
- package/dart_analyzer/lib/rules/common/C021_import_organization.dart +86 -0
- package/dart_analyzer/lib/rules/common/C023_no_duplicate_variable.dart +112 -0
- package/dart_analyzer/lib/rules/common/C024_no_scatter_hardcoded_constants.dart +79 -0
- package/dart_analyzer/lib/rules/common/C029_catch_block_logging.dart +81 -0
- package/dart_analyzer/lib/rules/common/C030_use_custom_error_classes.dart +77 -0
- package/dart_analyzer/lib/rules/common/C031_validation_separation.dart +90 -0
- package/dart_analyzer/lib/rules/common/C033_separate_service_repository.dart +80 -0
- package/dart_analyzer/lib/rules/common/C035_error_logging_context.dart +148 -0
- package/dart_analyzer/lib/rules/common/C040_centralized_validation.dart +84 -0
- package/dart_analyzer/lib/rules/common/C041_no_sensitive_hardcode.dart +103 -0
- package/dart_analyzer/lib/rules/common/C042_boolean_name_prefix.dart +105 -0
- package/dart_analyzer/lib/rules/common/C043_no_console_or_print.dart +101 -0
- package/dart_analyzer/lib/rules/common/C047_no_duplicate_retry_logic.dart +94 -0
- package/dart_analyzer/lib/rules/common/C048_no_bypass_architectural_layers.dart +132 -0
- package/dart_analyzer/lib/rules/common/C052_parsing_or_data_transformation.dart +95 -0
- package/dart_analyzer/lib/rules/common/C060_no_override_superclass.dart +81 -0
- package/dart_analyzer/lib/rules/common/C065_one_behavior_per_test.dart +83 -0
- package/dart_analyzer/lib/rules/common/C067_no_hardcoded_config.dart +89 -0
- package/dart_analyzer/lib/rules/common/C070_no_real_time_tests.dart +99 -0
- package/dart_analyzer/lib/rules/common/C072_single_test_behavior.dart +78 -0
- package/dart_analyzer/lib/rules/common/C073_validate_required_config_on_startup.dart +82 -0
- package/dart_analyzer/lib/rules/common/C075_explicit_return_types.dart +85 -0
- package/dart_analyzer/lib/rules/common/C076_explicit_function_types.dart +104 -0
- package/dart_analyzer/lib/rules/dart/D001_recommended_lint_rules.dart +309 -0
- package/dart_analyzer/lib/rules/dart/D002_dispose_resources.dart +338 -0
- package/dart_analyzer/lib/rules/dart/D003_prefer_widgets_over_methods.dart +273 -0
- package/dart_analyzer/lib/rules/dart/D004_avoid_shrinkwrap_listview.dart +154 -0
- package/dart_analyzer/lib/rules/dart/D005_limit_widget_nesting.dart +265 -0
- package/dart_analyzer/lib/rules/dart/D006_prefer_extracting_large_callbacks.dart +135 -0
- package/dart_analyzer/lib/rules/dart/D007_prefer_init_first_dispose_last.dart +150 -0
- package/dart_analyzer/lib/rules/dart/D008_avoid_long_functions.dart +394 -0
- package/dart_analyzer/lib/rules/dart/D009_limit_function_parameters.dart +179 -0
- package/dart_analyzer/lib/rules/dart/D010_limit_cyclomatic_complexity.dart +257 -0
- package/dart_analyzer/lib/rules/dart/D011_prefer_named_parameters.dart +152 -0
- package/dart_analyzer/lib/rules/dart/D012_prefer_named_boolean_parameters.dart +156 -0
- package/dart_analyzer/lib/rules/dart/D013_single_public_class.dart +246 -0
- package/dart_analyzer/lib/rules/dart/D014_unsafe_collection_access.dart +202 -0
- package/dart_analyzer/lib/rules/dart/D015_copywith_all_parameters.dart +125 -0
- package/dart_analyzer/lib/rules/dart/D016_project_should_have_tests.dart +134 -0
- package/dart_analyzer/lib/rules/dart/D017_pubspec_dependencies_review.dart +187 -0
- package/dart_analyzer/lib/rules/dart/D018_remove_commented_code.dart +196 -0
- package/dart_analyzer/lib/rules/dart/D019_avoid_single_child_multi_child_widget.dart +161 -0
- package/dart_analyzer/lib/rules/dart/D020_limit_if_else_branches.dart +125 -0
- package/dart_analyzer/lib/rules/dart/D021_avoid_negated_boolean_checks.dart +227 -0
- package/dart_analyzer/lib/rules/dart/D022_use_setstate_correctly.dart +269 -0
- package/dart_analyzer/lib/rules/dart/D023_avoid_unnecessary_method_overrides.dart +191 -0
- package/dart_analyzer/lib/rules/dart/D024_avoid_unnecessary_stateful_widget.dart +194 -0
- package/dart_analyzer/lib/rules/dart/D025_avoid_nested_conditional_expressions.dart +90 -0
- package/dart_analyzer/lib/rules/security/S001_backend_auth_communications.dart +155 -0
- package/dart_analyzer/lib/rules/security/S002_os_command_injection.dart +159 -0
- package/dart_analyzer/lib/rules/security/S003_open_redirect_protection.dart +208 -0
- package/dart_analyzer/lib/rules/security/S004_sensitive_data_logging.dart +391 -0
- package/dart_analyzer/lib/rules/security/S005_trusted_service_authorization.dart +182 -0
- package/dart_analyzer/lib/rules/security/S006_no_default_credentials.dart +208 -0
- package/dart_analyzer/lib/rules/security/S007_output_encoding.dart +224 -0
- package/dart_analyzer/lib/rules/security/S008_svg_content_sanitization.dart +211 -0
- package/dart_analyzer/lib/rules/security/S009_no_insecure_encryption.dart +160 -0
- package/dart_analyzer/lib/rules/security/S010_use_csprng.dart +184 -0
- package/dart_analyzer/lib/rules/security/S011_ech_tls_config.dart +175 -0
- package/dart_analyzer/lib/rules/security/S012_hardcoded_secrets.dart +255 -0
- package/dart_analyzer/lib/rules/security/S013_tls_enforcement.dart +148 -0
- package/dart_analyzer/lib/rules/security/S014_tls_version_enforcement.dart +117 -0
- package/dart_analyzer/lib/rules/security/S015_insecure_tls_certificate.dart +315 -0
- package/dart_analyzer/lib/rules/security/S016_no_sensitive_querystring.dart +244 -0
- package/dart_analyzer/lib/rules/security/S017_use_parameterized_queries.dart +191 -0
- package/dart_analyzer/lib/rules/security/S018_no_sensitive_browser_storage.dart +175 -0
- package/dart_analyzer/lib/rules/security/S019_smtp_injection_protection.dart +166 -0
- package/dart_analyzer/lib/rules/security/S020_no_eval_dynamic_code.dart +149 -0
- package/dart_analyzer/lib/rules/security/S021_referrer_policy.dart +146 -0
- package/dart_analyzer/lib/rules/security/S022_escape_output_context.dart +111 -0
- package/dart_analyzer/lib/rules/security/S023_no_json_injection.dart +550 -0
- package/dart_analyzer/lib/rules/security/S024_xpath_xxe_protection.dart +299 -0
- package/dart_analyzer/lib/rules/security/S025_server_side_validation.dart +140 -0
- package/dart_analyzer/lib/rules/security/S026_tls_all_connections.dart +196 -0
- package/dart_analyzer/lib/rules/security/S027_mtls_certificate_validation.dart +195 -0
- package/dart_analyzer/lib/rules/security/S028_file_upload_size_limits.dart +186 -0
- package/dart_analyzer/lib/rules/security/S029_csrf_protection.dart +171 -0
- package/dart_analyzer/lib/rules/security/S030_directory_browsing_protection.dart +144 -0
- package/dart_analyzer/lib/rules/security/S031_secure_session_cookies.dart +118 -0
- package/dart_analyzer/lib/rules/security/S032_httponly_session_cookies.dart +114 -0
- package/dart_analyzer/lib/rules/security/S033_samesite_session_cookies.dart +120 -0
- package/dart_analyzer/lib/rules/security/S034_host_prefix_session_cookies.dart +160 -0
- package/dart_analyzer/lib/rules/security/S035_separate_app_hostnames.dart +117 -0
- package/dart_analyzer/lib/rules/security/S036_lfi_rfi_protection.dart +188 -0
- package/dart_analyzer/lib/rules/security/S037_cache_headers.dart +113 -0
- package/dart_analyzer/lib/rules/security/S038_no_version_headers.dart +114 -0
- package/dart_analyzer/lib/rules/security/S039_tls_certificate_validation.dart +131 -0
- package/dart_analyzer/lib/rules/security/S040_session_fixation_protection.dart +155 -0
- package/dart_analyzer/lib/rules/security/S041_session_token_invalidation.dart +201 -0
- package/dart_analyzer/lib/rules/security/S042_require_re_authentication_for_long_lived.dart +158 -0
- package/dart_analyzer/lib/rules/security/S043_password_changes_invalidate_all_sessions.dart +88 -0
- package/dart_analyzer/lib/rules/security/S044_re_authentication_required.dart +119 -0
- package/dart_analyzer/lib/rules/security/S045_brute_force_protection.dart +253 -0
- package/dart_analyzer/lib/rules/security/S046_jwt_algorithm_allowlist.dart +113 -0
- package/dart_analyzer/lib/rules/security/S047_oauth_pkce_protection.dart +124 -0
- package/dart_analyzer/lib/rules/security/S048_oauth_redirect_uri_validation.dart +134 -0
- package/dart_analyzer/lib/rules/security/S049_short_validity_tokens.dart +145 -0
- package/dart_analyzer/lib/rules/security/S050_reference_tokens_entropy.dart +234 -0
- package/dart_analyzer/lib/rules/security/S051_password_length_policy.dart +171 -0
- package/dart_analyzer/lib/rules/security/S052_weak_otp_entropy.dart +107 -0
- package/dart_analyzer/lib/rules/security/S053_generic_error_messages.dart +159 -0
- package/dart_analyzer/lib/rules/security/S054_no_default_accounts.dart +141 -0
- package/dart_analyzer/lib/rules/security/S055_content_type_validation.dart +324 -0
- package/dart_analyzer/lib/rules/security/S056_log_injection_protection.dart +119 -0
- package/dart_analyzer/lib/rules/security/S057_utc_logging.dart +114 -0
- package/dart_analyzer/lib/rules/security/S058_no_ssrf.dart +175 -0
- package/dart_analyzer/lib/rules/security/S059_disable_debug_mode.dart +172 -0
- package/dart_analyzer/lib/rules/security/S060_password_minimum_length.dart +170 -0
- package/dart_analyzer/lib/symbol_table_extractor.dart +510 -0
- package/dart_analyzer/lib/utils/common_utils.dart +26 -0
- package/dart_analyzer/pubspec.lock +557 -0
- package/dart_analyzer/pubspec.yaml +39 -0
- package/dart_analyzer/test/fixtures/complex_code.dart +95 -0
- package/docs/GENERATED_FILE_HANDLING_SUMMARY.md +2 -2
- 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
|
+
}
|