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