@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,170 @@
|
|
|
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
|
+
/// S060: Enforce minimum password length of 8 characters, recommend 15+
|
|
10
|
+
/// Detect weak password length requirements
|
|
11
|
+
class S060PasswordMinimumLengthAnalyzer extends BaseAnalyzer {
|
|
12
|
+
@override
|
|
13
|
+
String get ruleId => 'S060';
|
|
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
|
+
final visitor = _S060Visitor(
|
|
24
|
+
filePath: filePath,
|
|
25
|
+
lineInfo: lineInfo,
|
|
26
|
+
violations: violations,
|
|
27
|
+
analyzer: this,
|
|
28
|
+
);
|
|
29
|
+
unit.accept(visitor);
|
|
30
|
+
return violations;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class _S060Visitor extends RecursiveAstVisitor<void> {
|
|
35
|
+
final String filePath;
|
|
36
|
+
final LineInfo lineInfo;
|
|
37
|
+
final List<Violation> violations;
|
|
38
|
+
final S060PasswordMinimumLengthAnalyzer analyzer;
|
|
39
|
+
|
|
40
|
+
_S060Visitor({
|
|
41
|
+
required this.filePath,
|
|
42
|
+
required this.lineInfo,
|
|
43
|
+
required this.violations,
|
|
44
|
+
required this.analyzer,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
@override
|
|
48
|
+
void visitBinaryExpression(BinaryExpression node) {
|
|
49
|
+
final source = node.toSource().toLowerCase();
|
|
50
|
+
|
|
51
|
+
// Check for password length validation with weak minimum
|
|
52
|
+
if (source.contains('password') && source.contains('length')) {
|
|
53
|
+
// Check for comparisons with values less than 8
|
|
54
|
+
final right = node.rightOperand;
|
|
55
|
+
if (right is IntegerLiteral) {
|
|
56
|
+
final value = right.value ?? 0;
|
|
57
|
+
final operator = node.operator.lexeme;
|
|
58
|
+
|
|
59
|
+
// password.length < 8 or password.length <= 7 or password.length >= 4
|
|
60
|
+
if ((operator == '<' && value <= 8) ||
|
|
61
|
+
(operator == '<=' && value < 8) ||
|
|
62
|
+
(operator == '>=' && value < 8) ||
|
|
63
|
+
(operator == '>' && value < 7) ||
|
|
64
|
+
(operator == '==' && value < 8)) {
|
|
65
|
+
violations.add(analyzer.createViolation(
|
|
66
|
+
filePath: filePath,
|
|
67
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
68
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
69
|
+
message:
|
|
70
|
+
'Password minimum length below 8 characters - NIST recommends 8+ minimum',
|
|
71
|
+
));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
super.visitBinaryExpression(node);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@override
|
|
80
|
+
void visitNamedExpression(NamedExpression node) {
|
|
81
|
+
final name = node.name.label.name.toLowerCase();
|
|
82
|
+
final expression = node.expression;
|
|
83
|
+
|
|
84
|
+
// Check for minLength in password context
|
|
85
|
+
if (name == 'minlength' || name == 'min' || name == 'minimumlength') {
|
|
86
|
+
if (expression is IntegerLiteral) {
|
|
87
|
+
final value = expression.value ?? 0;
|
|
88
|
+
if (value < 8) {
|
|
89
|
+
// Check if this is in password context
|
|
90
|
+
AstNode? current = node.parent;
|
|
91
|
+
int depth = 0;
|
|
92
|
+
while (current != null && depth < 10) {
|
|
93
|
+
final parentSource = current.toSource().toLowerCase();
|
|
94
|
+
if (parentSource.contains('password') ||
|
|
95
|
+
parentSource.contains('passwd') ||
|
|
96
|
+
parentSource.contains('pwd')) {
|
|
97
|
+
violations.add(analyzer.createViolation(
|
|
98
|
+
filePath: filePath,
|
|
99
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
100
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
101
|
+
message:
|
|
102
|
+
'Password minLength is $value - increase to at least 8',
|
|
103
|
+
));
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
current = current.parent;
|
|
107
|
+
depth++;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
super.visitNamedExpression(node);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@override
|
|
117
|
+
void visitVariableDeclaration(VariableDeclaration node) {
|
|
118
|
+
final name = node.name.lexeme.toLowerCase();
|
|
119
|
+
final initializer = node.initializer;
|
|
120
|
+
|
|
121
|
+
// Check for password min length constants
|
|
122
|
+
if ((name.contains('password') && name.contains('min')) ||
|
|
123
|
+
name == 'minpasswordlength' ||
|
|
124
|
+
name == 'min_password_length') {
|
|
125
|
+
if (initializer is IntegerLiteral) {
|
|
126
|
+
final value = initializer.value ?? 0;
|
|
127
|
+
if (value < 8) {
|
|
128
|
+
violations.add(analyzer.createViolation(
|
|
129
|
+
filePath: filePath,
|
|
130
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
131
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
132
|
+
message:
|
|
133
|
+
'Password minimum length constant is $value - increase to at least 8',
|
|
134
|
+
));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
super.visitVariableDeclaration(node);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@override
|
|
143
|
+
void visitMethodInvocation(MethodInvocation node) {
|
|
144
|
+
final methodName = node.methodName.name.toLowerCase();
|
|
145
|
+
final source = node.toSource().toLowerCase();
|
|
146
|
+
|
|
147
|
+
// Check for password validation methods with weak length check
|
|
148
|
+
if ((methodName.contains('validate') || methodName.contains('check')) &&
|
|
149
|
+
source.contains('password')) {
|
|
150
|
+
// Look for length checks in arguments
|
|
151
|
+
for (final arg in node.argumentList.arguments) {
|
|
152
|
+
if (arg is IntegerLiteral) {
|
|
153
|
+
final value = arg.value ?? 0;
|
|
154
|
+
// If small number in password validation, likely weak min length
|
|
155
|
+
if (value > 0 && value < 8) {
|
|
156
|
+
violations.add(analyzer.createViolation(
|
|
157
|
+
filePath: filePath,
|
|
158
|
+
line: analyzer.getLine(lineInfo, node.offset),
|
|
159
|
+
column: analyzer.getColumn(lineInfo, node.offset),
|
|
160
|
+
message:
|
|
161
|
+
'Password validation with length $value - minimum should be 8',
|
|
162
|
+
));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
super.visitMethodInvocation(node);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,510 @@
|
|
|
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/symbol_table.dart';
|
|
6
|
+
|
|
7
|
+
/// Extracts symbol table information from a Dart AST
|
|
8
|
+
/// Provides a normalized structure compatible with SunLint's TypeScript symbol tables
|
|
9
|
+
class SymbolTableExtractor extends RecursiveAstVisitor<void> {
|
|
10
|
+
final String filePath;
|
|
11
|
+
final String fileName;
|
|
12
|
+
final LineInfo lineInfo;
|
|
13
|
+
final DateTime startTime;
|
|
14
|
+
|
|
15
|
+
final List<ImportInfo> _imports = [];
|
|
16
|
+
final List<ExportInfo> _exports = [];
|
|
17
|
+
final List<FunctionInfo> _functions = [];
|
|
18
|
+
final List<ClassInfo> _classes = [];
|
|
19
|
+
final List<MixinInfo> _mixins = [];
|
|
20
|
+
final List<ExtensionInfo> _extensions = [];
|
|
21
|
+
final List<EnumInfo> _enums = [];
|
|
22
|
+
final List<VariableInfo> _variables = [];
|
|
23
|
+
final List<ConstantInfo> _constants = [];
|
|
24
|
+
final List<FunctionCallInfo> _functionCalls = [];
|
|
25
|
+
final List<MethodCallInfo> _methodCalls = [];
|
|
26
|
+
|
|
27
|
+
SymbolTableExtractor({
|
|
28
|
+
required this.filePath,
|
|
29
|
+
required this.fileName,
|
|
30
|
+
required this.lineInfo,
|
|
31
|
+
}) : startTime = DateTime.now();
|
|
32
|
+
|
|
33
|
+
/// Extract symbol table from a compilation unit
|
|
34
|
+
SymbolTable extract(CompilationUnit unit) {
|
|
35
|
+
unit.accept(this);
|
|
36
|
+
|
|
37
|
+
final analysisTime =
|
|
38
|
+
DateTime.now().difference(startTime).inMilliseconds;
|
|
39
|
+
|
|
40
|
+
return SymbolTable(
|
|
41
|
+
filePath: filePath,
|
|
42
|
+
fileName: fileName,
|
|
43
|
+
imports: _imports,
|
|
44
|
+
exports: _exports,
|
|
45
|
+
functions: _functions,
|
|
46
|
+
classes: _classes,
|
|
47
|
+
mixins: _mixins,
|
|
48
|
+
extensions: _extensions,
|
|
49
|
+
enums: _enums,
|
|
50
|
+
variables: _variables,
|
|
51
|
+
constants: _constants,
|
|
52
|
+
functionCalls: _functionCalls,
|
|
53
|
+
methodCalls: _methodCalls,
|
|
54
|
+
lastModified: DateTime.now().millisecondsSinceEpoch,
|
|
55
|
+
analysisTime: analysisTime,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Get line number from offset
|
|
60
|
+
int _getLine(int offset) {
|
|
61
|
+
return lineInfo.getLocation(offset).lineNumber;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Get column from offset
|
|
65
|
+
int _getColumn(int offset) {
|
|
66
|
+
return lineInfo.getLocation(offset).columnNumber;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Extract documentation comment if present
|
|
70
|
+
String? _getDocumentation(AnnotatedNode node) {
|
|
71
|
+
final comment = node.documentationComment;
|
|
72
|
+
if (comment == null) return null;
|
|
73
|
+
|
|
74
|
+
return comment.tokens.map((t) => t.lexeme).join('\n');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ============================================================
|
|
78
|
+
// Import/Export Visitors
|
|
79
|
+
// ============================================================
|
|
80
|
+
|
|
81
|
+
@override
|
|
82
|
+
void visitImportDirective(ImportDirective node) {
|
|
83
|
+
final uri = node.uri.stringValue ?? '';
|
|
84
|
+
final prefix = node.prefix?.name;
|
|
85
|
+
|
|
86
|
+
final showNames = <String>[];
|
|
87
|
+
final hideNames = <String>[];
|
|
88
|
+
|
|
89
|
+
for (final combinator in node.combinators) {
|
|
90
|
+
if (combinator is ShowCombinator) {
|
|
91
|
+
showNames.addAll(combinator.shownNames.map((e) => e.name));
|
|
92
|
+
} else if (combinator is HideCombinator) {
|
|
93
|
+
hideNames.addAll(combinator.hiddenNames.map((e) => e.name));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
_imports.add(ImportInfo(
|
|
98
|
+
uri: uri,
|
|
99
|
+
prefix: prefix,
|
|
100
|
+
showNames: showNames,
|
|
101
|
+
hideNames: hideNames,
|
|
102
|
+
line: _getLine(node.offset),
|
|
103
|
+
isDeferred: node.deferredKeyword != null,
|
|
104
|
+
));
|
|
105
|
+
|
|
106
|
+
super.visitImportDirective(node);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@override
|
|
110
|
+
void visitExportDirective(ExportDirective node) {
|
|
111
|
+
final uri = node.uri.stringValue ?? '';
|
|
112
|
+
|
|
113
|
+
final showNames = <String>[];
|
|
114
|
+
final hideNames = <String>[];
|
|
115
|
+
|
|
116
|
+
for (final combinator in node.combinators) {
|
|
117
|
+
if (combinator is ShowCombinator) {
|
|
118
|
+
showNames.addAll(combinator.shownNames.map((e) => e.name));
|
|
119
|
+
} else if (combinator is HideCombinator) {
|
|
120
|
+
hideNames.addAll(combinator.hiddenNames.map((e) => e.name));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
_exports.add(ExportInfo(
|
|
125
|
+
uri: uri,
|
|
126
|
+
showNames: showNames,
|
|
127
|
+
hideNames: hideNames,
|
|
128
|
+
line: _getLine(node.offset),
|
|
129
|
+
));
|
|
130
|
+
|
|
131
|
+
super.visitExportDirective(node);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ============================================================
|
|
135
|
+
// Function Visitors
|
|
136
|
+
// ============================================================
|
|
137
|
+
|
|
138
|
+
@override
|
|
139
|
+
void visitFunctionDeclaration(FunctionDeclaration node) {
|
|
140
|
+
final functionExpression = node.functionExpression;
|
|
141
|
+
final parameters = _extractParameters(functionExpression.parameters);
|
|
142
|
+
|
|
143
|
+
_functions.add(FunctionInfo(
|
|
144
|
+
name: node.name.lexeme,
|
|
145
|
+
returnType: node.returnType?.toSource(),
|
|
146
|
+
parameters: parameters,
|
|
147
|
+
line: _getLine(node.offset),
|
|
148
|
+
column: _getColumn(node.offset),
|
|
149
|
+
isAsync: functionExpression.body.isAsynchronous,
|
|
150
|
+
isGenerator: functionExpression.body.isGenerator,
|
|
151
|
+
isExternal: node.externalKeyword != null,
|
|
152
|
+
documentation: _getDocumentation(node),
|
|
153
|
+
));
|
|
154
|
+
|
|
155
|
+
super.visitFunctionDeclaration(node);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ============================================================
|
|
159
|
+
// Class Visitors
|
|
160
|
+
// ============================================================
|
|
161
|
+
|
|
162
|
+
@override
|
|
163
|
+
void visitClassDeclaration(ClassDeclaration node) {
|
|
164
|
+
final methods = <MethodInfo>[];
|
|
165
|
+
final fields = <FieldInfo>[];
|
|
166
|
+
final constructors = <ConstructorInfo>[];
|
|
167
|
+
|
|
168
|
+
// Extract class members
|
|
169
|
+
for (final member in node.members) {
|
|
170
|
+
if (member is MethodDeclaration) {
|
|
171
|
+
methods.add(_extractMethod(member));
|
|
172
|
+
} else if (member is FieldDeclaration) {
|
|
173
|
+
fields.addAll(_extractFields(member));
|
|
174
|
+
} else if (member is ConstructorDeclaration) {
|
|
175
|
+
constructors.add(_extractConstructor(member));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Extract superclass
|
|
180
|
+
final superclass = node.extendsClause?.superclass.name2.lexeme;
|
|
181
|
+
|
|
182
|
+
// Extract interfaces
|
|
183
|
+
final interfaces = node.implementsClause?.interfaces
|
|
184
|
+
.map((e) => e.name2.lexeme)
|
|
185
|
+
.toList() ??
|
|
186
|
+
[];
|
|
187
|
+
|
|
188
|
+
// Extract mixins
|
|
189
|
+
final mixins = node.withClause?.mixinTypes
|
|
190
|
+
.map((e) => e.name2.lexeme)
|
|
191
|
+
.toList() ??
|
|
192
|
+
[];
|
|
193
|
+
|
|
194
|
+
// Extract type parameters
|
|
195
|
+
final typeParameters = node.typeParameters?.typeParameters
|
|
196
|
+
.map((e) => e.name.lexeme)
|
|
197
|
+
.toList() ??
|
|
198
|
+
[];
|
|
199
|
+
|
|
200
|
+
_classes.add(ClassInfo(
|
|
201
|
+
name: node.name.lexeme,
|
|
202
|
+
superclass: superclass,
|
|
203
|
+
interfaces: interfaces,
|
|
204
|
+
mixins: mixins,
|
|
205
|
+
typeParameters: typeParameters,
|
|
206
|
+
methods: methods,
|
|
207
|
+
fields: fields,
|
|
208
|
+
constructors: constructors,
|
|
209
|
+
line: _getLine(node.offset),
|
|
210
|
+
column: _getColumn(node.offset),
|
|
211
|
+
isAbstract: node.abstractKeyword != null,
|
|
212
|
+
isSealed: node.sealedKeyword != null,
|
|
213
|
+
isFinal: node.finalKeyword != null,
|
|
214
|
+
isBase: node.baseKeyword != null,
|
|
215
|
+
isInterface: node.interfaceKeyword != null,
|
|
216
|
+
isMixin: node.mixinKeyword != null,
|
|
217
|
+
documentation: _getDocumentation(node),
|
|
218
|
+
));
|
|
219
|
+
|
|
220
|
+
// Don't call super - we've already processed members
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ============================================================
|
|
224
|
+
// Mixin Visitors
|
|
225
|
+
// ============================================================
|
|
226
|
+
|
|
227
|
+
@override
|
|
228
|
+
void visitMixinDeclaration(MixinDeclaration node) {
|
|
229
|
+
final methods = <MethodInfo>[];
|
|
230
|
+
final fields = <FieldInfo>[];
|
|
231
|
+
|
|
232
|
+
for (final member in node.members) {
|
|
233
|
+
if (member is MethodDeclaration) {
|
|
234
|
+
methods.add(_extractMethod(member));
|
|
235
|
+
} else if (member is FieldDeclaration) {
|
|
236
|
+
fields.addAll(_extractFields(member));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
final onTypes = node.onClause?.superclassConstraints
|
|
241
|
+
.map((e) => e.name2.lexeme)
|
|
242
|
+
.toList() ??
|
|
243
|
+
[];
|
|
244
|
+
|
|
245
|
+
final interfaces = node.implementsClause?.interfaces
|
|
246
|
+
.map((e) => e.name2.lexeme)
|
|
247
|
+
.toList() ??
|
|
248
|
+
[];
|
|
249
|
+
|
|
250
|
+
_mixins.add(MixinInfo(
|
|
251
|
+
name: node.name.lexeme,
|
|
252
|
+
onTypes: onTypes,
|
|
253
|
+
interfaces: interfaces,
|
|
254
|
+
methods: methods,
|
|
255
|
+
fields: fields,
|
|
256
|
+
line: _getLine(node.offset),
|
|
257
|
+
column: _getColumn(node.offset),
|
|
258
|
+
documentation: _getDocumentation(node),
|
|
259
|
+
));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ============================================================
|
|
263
|
+
// Extension Visitors
|
|
264
|
+
// ============================================================
|
|
265
|
+
|
|
266
|
+
@override
|
|
267
|
+
void visitExtensionDeclaration(ExtensionDeclaration node) {
|
|
268
|
+
final methods = <MethodInfo>[];
|
|
269
|
+
final fields = <FieldInfo>[];
|
|
270
|
+
|
|
271
|
+
for (final member in node.members) {
|
|
272
|
+
if (member is MethodDeclaration) {
|
|
273
|
+
methods.add(_extractMethod(member));
|
|
274
|
+
} else if (member is FieldDeclaration) {
|
|
275
|
+
fields.addAll(_extractFields(member));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
_extensions.add(ExtensionInfo(
|
|
280
|
+
name: node.name?.lexeme,
|
|
281
|
+
onType: node.extendedType.toSource(),
|
|
282
|
+
methods: methods,
|
|
283
|
+
fields: fields,
|
|
284
|
+
line: _getLine(node.offset),
|
|
285
|
+
column: _getColumn(node.offset),
|
|
286
|
+
documentation: _getDocumentation(node),
|
|
287
|
+
));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ============================================================
|
|
291
|
+
// Enum Visitors
|
|
292
|
+
// ============================================================
|
|
293
|
+
|
|
294
|
+
@override
|
|
295
|
+
void visitEnumDeclaration(EnumDeclaration node) {
|
|
296
|
+
final values =
|
|
297
|
+
node.constants.map((e) => e.name.lexeme).toList();
|
|
298
|
+
|
|
299
|
+
final methods = <MethodInfo>[];
|
|
300
|
+
final fields = <FieldInfo>[];
|
|
301
|
+
|
|
302
|
+
for (final member in node.members) {
|
|
303
|
+
if (member is MethodDeclaration) {
|
|
304
|
+
methods.add(_extractMethod(member));
|
|
305
|
+
} else if (member is FieldDeclaration) {
|
|
306
|
+
fields.addAll(_extractFields(member));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
_enums.add(EnumInfo(
|
|
311
|
+
name: node.name.lexeme,
|
|
312
|
+
values: values,
|
|
313
|
+
methods: methods,
|
|
314
|
+
fields: fields,
|
|
315
|
+
line: _getLine(node.offset),
|
|
316
|
+
column: _getColumn(node.offset),
|
|
317
|
+
documentation: _getDocumentation(node),
|
|
318
|
+
));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ============================================================
|
|
322
|
+
// Variable Visitors
|
|
323
|
+
// ============================================================
|
|
324
|
+
|
|
325
|
+
@override
|
|
326
|
+
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
|
|
327
|
+
for (final variable in node.variables.variables) {
|
|
328
|
+
final isConst = node.variables.isConst;
|
|
329
|
+
|
|
330
|
+
if (isConst) {
|
|
331
|
+
_constants.add(ConstantInfo(
|
|
332
|
+
name: variable.name.lexeme,
|
|
333
|
+
type: node.variables.type?.toSource(),
|
|
334
|
+
value: variable.initializer?.toSource() ?? '',
|
|
335
|
+
line: _getLine(variable.offset),
|
|
336
|
+
column: _getColumn(variable.offset),
|
|
337
|
+
documentation: _getDocumentation(node),
|
|
338
|
+
));
|
|
339
|
+
} else {
|
|
340
|
+
_variables.add(VariableInfo(
|
|
341
|
+
name: variable.name.lexeme,
|
|
342
|
+
type: node.variables.type?.toSource(),
|
|
343
|
+
line: _getLine(variable.offset),
|
|
344
|
+
column: _getColumn(variable.offset),
|
|
345
|
+
isFinal: node.variables.isFinal,
|
|
346
|
+
isConst: false,
|
|
347
|
+
isLate: node.variables.isLate,
|
|
348
|
+
initialValue: variable.initializer?.toSource(),
|
|
349
|
+
documentation: _getDocumentation(node),
|
|
350
|
+
));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
super.visitTopLevelVariableDeclaration(node);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ============================================================
|
|
358
|
+
// Function Call Visitors
|
|
359
|
+
// ============================================================
|
|
360
|
+
|
|
361
|
+
@override
|
|
362
|
+
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
|
|
363
|
+
final functionName = node.function.toSource();
|
|
364
|
+
|
|
365
|
+
_functionCalls.add(FunctionCallInfo(
|
|
366
|
+
functionName: functionName,
|
|
367
|
+
arguments: _extractArguments(node.argumentList),
|
|
368
|
+
line: _getLine(node.offset),
|
|
369
|
+
column: _getColumn(node.offset),
|
|
370
|
+
));
|
|
371
|
+
|
|
372
|
+
super.visitFunctionExpressionInvocation(node);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
@override
|
|
376
|
+
void visitMethodInvocation(MethodInvocation node) {
|
|
377
|
+
final target = node.target?.toSource() ?? '';
|
|
378
|
+
final methodName = node.methodName.name;
|
|
379
|
+
|
|
380
|
+
if (target.isEmpty) {
|
|
381
|
+
// This is a function call, not a method call
|
|
382
|
+
_functionCalls.add(FunctionCallInfo(
|
|
383
|
+
functionName: methodName,
|
|
384
|
+
arguments: _extractArguments(node.argumentList),
|
|
385
|
+
line: _getLine(node.offset),
|
|
386
|
+
column: _getColumn(node.offset),
|
|
387
|
+
isConditional: node.isNullAware,
|
|
388
|
+
));
|
|
389
|
+
} else {
|
|
390
|
+
_methodCalls.add(MethodCallInfo(
|
|
391
|
+
target: target,
|
|
392
|
+
methodName: methodName,
|
|
393
|
+
arguments: _extractArguments(node.argumentList),
|
|
394
|
+
line: _getLine(node.offset),
|
|
395
|
+
column: _getColumn(node.offset),
|
|
396
|
+
isNullAware: node.isNullAware,
|
|
397
|
+
isCascade: node.isCascaded,
|
|
398
|
+
));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
super.visitMethodInvocation(node);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ============================================================
|
|
405
|
+
// Helper Methods
|
|
406
|
+
// ============================================================
|
|
407
|
+
|
|
408
|
+
/// Extract method info from a MethodDeclaration
|
|
409
|
+
MethodInfo _extractMethod(MethodDeclaration node) {
|
|
410
|
+
final parameters = _extractParameters(node.parameters);
|
|
411
|
+
|
|
412
|
+
return MethodInfo(
|
|
413
|
+
name: node.name.lexeme,
|
|
414
|
+
returnType: node.returnType?.toSource(),
|
|
415
|
+
parameters: parameters,
|
|
416
|
+
line: _getLine(node.offset),
|
|
417
|
+
column: _getColumn(node.offset),
|
|
418
|
+
isStatic: node.isStatic,
|
|
419
|
+
isAsync: node.body.isAsynchronous,
|
|
420
|
+
isGenerator: node.body.isGenerator,
|
|
421
|
+
isGetter: node.isGetter,
|
|
422
|
+
isSetter: node.isSetter,
|
|
423
|
+
isOperator: node.isOperator,
|
|
424
|
+
isAbstract: node.isAbstract,
|
|
425
|
+
isExternal: node.externalKeyword != null,
|
|
426
|
+
documentation: _getDocumentation(node),
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/// Extract fields from a FieldDeclaration
|
|
431
|
+
List<FieldInfo> _extractFields(FieldDeclaration node) {
|
|
432
|
+
return node.fields.variables.map((variable) {
|
|
433
|
+
return FieldInfo(
|
|
434
|
+
name: variable.name.lexeme,
|
|
435
|
+
type: node.fields.type?.toSource(),
|
|
436
|
+
line: _getLine(variable.offset),
|
|
437
|
+
column: _getColumn(variable.offset),
|
|
438
|
+
isStatic: node.isStatic,
|
|
439
|
+
isFinal: node.fields.isFinal,
|
|
440
|
+
isConst: node.fields.isConst,
|
|
441
|
+
isLate: node.fields.isLate,
|
|
442
|
+
initialValue: variable.initializer?.toSource(),
|
|
443
|
+
documentation: _getDocumentation(node),
|
|
444
|
+
);
|
|
445
|
+
}).toList();
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/// Extract constructor info from a ConstructorDeclaration
|
|
449
|
+
ConstructorInfo _extractConstructor(ConstructorDeclaration node) {
|
|
450
|
+
final parameters = _extractParameters(node.parameters);
|
|
451
|
+
|
|
452
|
+
return ConstructorInfo(
|
|
453
|
+
name: node.name?.lexeme,
|
|
454
|
+
parameters: parameters,
|
|
455
|
+
line: _getLine(node.offset),
|
|
456
|
+
column: _getColumn(node.offset),
|
|
457
|
+
isConst: node.constKeyword != null,
|
|
458
|
+
isFactory: node.factoryKeyword != null,
|
|
459
|
+
isExternal: node.externalKeyword != null,
|
|
460
|
+
redirectedConstructor: node.redirectedConstructor?.toSource(),
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/// Extract parameters from a FormalParameterList
|
|
465
|
+
List<ParameterInfo> _extractParameters(FormalParameterList? parameterList) {
|
|
466
|
+
if (parameterList == null) return [];
|
|
467
|
+
|
|
468
|
+
return parameterList.parameters.map((param) {
|
|
469
|
+
String name;
|
|
470
|
+
String? type;
|
|
471
|
+
String? defaultValue;
|
|
472
|
+
|
|
473
|
+
if (param is SimpleFormalParameter) {
|
|
474
|
+
name = param.name?.lexeme ?? '';
|
|
475
|
+
type = param.type?.toSource();
|
|
476
|
+
} else if (param is DefaultFormalParameter) {
|
|
477
|
+
final innerParam = param.parameter;
|
|
478
|
+
if (innerParam is SimpleFormalParameter) {
|
|
479
|
+
name = innerParam.name?.lexeme ?? '';
|
|
480
|
+
type = innerParam.type?.toSource();
|
|
481
|
+
} else {
|
|
482
|
+
name = innerParam.name?.lexeme ?? '';
|
|
483
|
+
}
|
|
484
|
+
defaultValue = param.defaultValue?.toSource();
|
|
485
|
+
} else if (param is FieldFormalParameter) {
|
|
486
|
+
name = param.name.lexeme;
|
|
487
|
+
type = param.type?.toSource();
|
|
488
|
+
} else if (param is SuperFormalParameter) {
|
|
489
|
+
name = param.name.lexeme;
|
|
490
|
+
type = param.type?.toSource();
|
|
491
|
+
} else {
|
|
492
|
+
name = param.name?.lexeme ?? '';
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return ParameterInfo(
|
|
496
|
+
name: name,
|
|
497
|
+
type: type,
|
|
498
|
+
isRequired: param.isRequired,
|
|
499
|
+
isNamed: param.isNamed,
|
|
500
|
+
isOptional: param.isOptional,
|
|
501
|
+
defaultValue: defaultValue,
|
|
502
|
+
);
|
|
503
|
+
}).toList();
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/// Extract arguments from an ArgumentList
|
|
507
|
+
List<String> _extractArguments(ArgumentList arguments) {
|
|
508
|
+
return arguments.arguments.map((arg) => arg.toSource()).toList();
|
|
509
|
+
}
|
|
510
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class CommonUtils {
|
|
2
|
+
static bool isCodeLine(int lineNumber, List<String> sourceLines) {
|
|
3
|
+
if (lineNumber < 1 || lineNumber > sourceLines.length) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
final line = sourceLines[lineNumber - 1].trim();
|
|
8
|
+
|
|
9
|
+
// Skip blank lines
|
|
10
|
+
if (line.isEmpty) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Skip comment-only lines
|
|
15
|
+
if (line.startsWith('//')) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Skip multi-line comment markers
|
|
20
|
+
if (line.startsWith('/*') || line.endsWith('*/')) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|