@sun-asterisk/sunlint 1.3.43 → 1.3.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/dart_analyzer/README.md +226 -0
  2. package/dart_analyzer/analysis_options.yaml +66 -0
  3. package/dart_analyzer/bin/sunlint-dart-macos +0 -0
  4. package/dart_analyzer/bin/sunlint_dart_analyzer.dart +124 -0
  5. package/dart_analyzer/lib/analyzer_service.dart +625 -0
  6. package/dart_analyzer/lib/json_rpc_server.dart +275 -0
  7. package/dart_analyzer/lib/models/rule.dart +67 -0
  8. package/dart_analyzer/lib/models/symbol_table.dart +607 -0
  9. package/dart_analyzer/lib/models/violation.dart +69 -0
  10. package/dart_analyzer/lib/rules/base_analyzer.dart +52 -0
  11. package/dart_analyzer/lib/rules/common/C002_no_duplicate_code.dart +344 -0
  12. package/dart_analyzer/lib/rules/common/C003_no_vague_abbreviations.dart +318 -0
  13. package/dart_analyzer/lib/rules/common/C006_function_naming.dart +219 -0
  14. package/dart_analyzer/lib/rules/common/C008_variable_declaration_locality.dart +205 -0
  15. package/dart_analyzer/lib/rules/common/C010_limit_block_nesting.dart +162 -0
  16. package/dart_analyzer/lib/rules/common/C012_command_query_separation.dart +214 -0
  17. package/dart_analyzer/lib/rules/common/C013_no_dead_code.dart +225 -0
  18. package/dart_analyzer/lib/rules/common/C014_dependency_injection.dart +249 -0
  19. package/dart_analyzer/lib/rules/common/C017_constructor_logic.dart +158 -0
  20. package/dart_analyzer/lib/rules/common/C018_no_throw_generic_error.dart +141 -0
  21. package/dart_analyzer/lib/rules/common/C019_log_level_usage.dart +165 -0
  22. package/dart_analyzer/lib/rules/common/C020_unused_imports.dart +128 -0
  23. package/dart_analyzer/lib/rules/common/C021_import_organization.dart +86 -0
  24. package/dart_analyzer/lib/rules/common/C023_no_duplicate_variable.dart +112 -0
  25. package/dart_analyzer/lib/rules/common/C024_no_scatter_hardcoded_constants.dart +79 -0
  26. package/dart_analyzer/lib/rules/common/C029_catch_block_logging.dart +81 -0
  27. package/dart_analyzer/lib/rules/common/C030_use_custom_error_classes.dart +77 -0
  28. package/dart_analyzer/lib/rules/common/C031_validation_separation.dart +90 -0
  29. package/dart_analyzer/lib/rules/common/C033_separate_service_repository.dart +80 -0
  30. package/dart_analyzer/lib/rules/common/C035_error_logging_context.dart +148 -0
  31. package/dart_analyzer/lib/rules/common/C040_centralized_validation.dart +84 -0
  32. package/dart_analyzer/lib/rules/common/C041_no_sensitive_hardcode.dart +103 -0
  33. package/dart_analyzer/lib/rules/common/C042_boolean_name_prefix.dart +105 -0
  34. package/dart_analyzer/lib/rules/common/C043_no_console_or_print.dart +101 -0
  35. package/dart_analyzer/lib/rules/common/C047_no_duplicate_retry_logic.dart +94 -0
  36. package/dart_analyzer/lib/rules/common/C048_no_bypass_architectural_layers.dart +132 -0
  37. package/dart_analyzer/lib/rules/common/C052_parsing_or_data_transformation.dart +95 -0
  38. package/dart_analyzer/lib/rules/common/C060_no_override_superclass.dart +81 -0
  39. package/dart_analyzer/lib/rules/common/C065_one_behavior_per_test.dart +83 -0
  40. package/dart_analyzer/lib/rules/common/C067_no_hardcoded_config.dart +89 -0
  41. package/dart_analyzer/lib/rules/common/C070_no_real_time_tests.dart +99 -0
  42. package/dart_analyzer/lib/rules/common/C072_single_test_behavior.dart +78 -0
  43. package/dart_analyzer/lib/rules/common/C073_validate_required_config_on_startup.dart +82 -0
  44. package/dart_analyzer/lib/rules/common/C075_explicit_return_types.dart +85 -0
  45. package/dart_analyzer/lib/rules/common/C076_explicit_function_types.dart +104 -0
  46. package/dart_analyzer/lib/rules/dart/D001_recommended_lint_rules.dart +309 -0
  47. package/dart_analyzer/lib/rules/dart/D002_dispose_resources.dart +338 -0
  48. package/dart_analyzer/lib/rules/dart/D003_prefer_widgets_over_methods.dart +273 -0
  49. package/dart_analyzer/lib/rules/dart/D004_avoid_shrinkwrap_listview.dart +154 -0
  50. package/dart_analyzer/lib/rules/dart/D005_limit_widget_nesting.dart +265 -0
  51. package/dart_analyzer/lib/rules/dart/D006_prefer_extracting_large_callbacks.dart +135 -0
  52. package/dart_analyzer/lib/rules/dart/D007_prefer_init_first_dispose_last.dart +150 -0
  53. package/dart_analyzer/lib/rules/dart/D008_avoid_long_functions.dart +394 -0
  54. package/dart_analyzer/lib/rules/dart/D009_limit_function_parameters.dart +179 -0
  55. package/dart_analyzer/lib/rules/dart/D010_limit_cyclomatic_complexity.dart +257 -0
  56. package/dart_analyzer/lib/rules/dart/D011_prefer_named_parameters.dart +152 -0
  57. package/dart_analyzer/lib/rules/dart/D012_prefer_named_boolean_parameters.dart +156 -0
  58. package/dart_analyzer/lib/rules/dart/D013_single_public_class.dart +246 -0
  59. package/dart_analyzer/lib/rules/dart/D014_unsafe_collection_access.dart +202 -0
  60. package/dart_analyzer/lib/rules/dart/D015_copywith_all_parameters.dart +125 -0
  61. package/dart_analyzer/lib/rules/dart/D016_project_should_have_tests.dart +134 -0
  62. package/dart_analyzer/lib/rules/dart/D017_pubspec_dependencies_review.dart +187 -0
  63. package/dart_analyzer/lib/rules/dart/D018_remove_commented_code.dart +196 -0
  64. package/dart_analyzer/lib/rules/dart/D019_avoid_single_child_multi_child_widget.dart +161 -0
  65. package/dart_analyzer/lib/rules/dart/D020_limit_if_else_branches.dart +125 -0
  66. package/dart_analyzer/lib/rules/dart/D021_avoid_negated_boolean_checks.dart +227 -0
  67. package/dart_analyzer/lib/rules/dart/D022_use_setstate_correctly.dart +269 -0
  68. package/dart_analyzer/lib/rules/dart/D023_avoid_unnecessary_method_overrides.dart +191 -0
  69. package/dart_analyzer/lib/rules/dart/D024_avoid_unnecessary_stateful_widget.dart +194 -0
  70. package/dart_analyzer/lib/rules/dart/D025_avoid_nested_conditional_expressions.dart +90 -0
  71. package/dart_analyzer/lib/rules/security/S001_backend_auth_communications.dart +155 -0
  72. package/dart_analyzer/lib/rules/security/S002_os_command_injection.dart +159 -0
  73. package/dart_analyzer/lib/rules/security/S003_open_redirect_protection.dart +208 -0
  74. package/dart_analyzer/lib/rules/security/S004_sensitive_data_logging.dart +391 -0
  75. package/dart_analyzer/lib/rules/security/S005_trusted_service_authorization.dart +182 -0
  76. package/dart_analyzer/lib/rules/security/S006_no_default_credentials.dart +208 -0
  77. package/dart_analyzer/lib/rules/security/S007_output_encoding.dart +224 -0
  78. package/dart_analyzer/lib/rules/security/S008_svg_content_sanitization.dart +211 -0
  79. package/dart_analyzer/lib/rules/security/S009_no_insecure_encryption.dart +160 -0
  80. package/dart_analyzer/lib/rules/security/S010_use_csprng.dart +184 -0
  81. package/dart_analyzer/lib/rules/security/S011_ech_tls_config.dart +175 -0
  82. package/dart_analyzer/lib/rules/security/S012_hardcoded_secrets.dart +255 -0
  83. package/dart_analyzer/lib/rules/security/S013_tls_enforcement.dart +148 -0
  84. package/dart_analyzer/lib/rules/security/S014_tls_version_enforcement.dart +117 -0
  85. package/dart_analyzer/lib/rules/security/S015_insecure_tls_certificate.dart +315 -0
  86. package/dart_analyzer/lib/rules/security/S016_no_sensitive_querystring.dart +244 -0
  87. package/dart_analyzer/lib/rules/security/S017_use_parameterized_queries.dart +191 -0
  88. package/dart_analyzer/lib/rules/security/S018_no_sensitive_browser_storage.dart +175 -0
  89. package/dart_analyzer/lib/rules/security/S019_smtp_injection_protection.dart +166 -0
  90. package/dart_analyzer/lib/rules/security/S020_no_eval_dynamic_code.dart +149 -0
  91. package/dart_analyzer/lib/rules/security/S021_referrer_policy.dart +146 -0
  92. package/dart_analyzer/lib/rules/security/S022_escape_output_context.dart +111 -0
  93. package/dart_analyzer/lib/rules/security/S023_no_json_injection.dart +550 -0
  94. package/dart_analyzer/lib/rules/security/S024_xpath_xxe_protection.dart +299 -0
  95. package/dart_analyzer/lib/rules/security/S025_server_side_validation.dart +140 -0
  96. package/dart_analyzer/lib/rules/security/S026_tls_all_connections.dart +196 -0
  97. package/dart_analyzer/lib/rules/security/S027_mtls_certificate_validation.dart +195 -0
  98. package/dart_analyzer/lib/rules/security/S028_file_upload_size_limits.dart +186 -0
  99. package/dart_analyzer/lib/rules/security/S029_csrf_protection.dart +171 -0
  100. package/dart_analyzer/lib/rules/security/S030_directory_browsing_protection.dart +144 -0
  101. package/dart_analyzer/lib/rules/security/S031_secure_session_cookies.dart +118 -0
  102. package/dart_analyzer/lib/rules/security/S032_httponly_session_cookies.dart +114 -0
  103. package/dart_analyzer/lib/rules/security/S033_samesite_session_cookies.dart +120 -0
  104. package/dart_analyzer/lib/rules/security/S034_host_prefix_session_cookies.dart +160 -0
  105. package/dart_analyzer/lib/rules/security/S035_separate_app_hostnames.dart +117 -0
  106. package/dart_analyzer/lib/rules/security/S036_lfi_rfi_protection.dart +188 -0
  107. package/dart_analyzer/lib/rules/security/S037_cache_headers.dart +113 -0
  108. package/dart_analyzer/lib/rules/security/S038_no_version_headers.dart +114 -0
  109. package/dart_analyzer/lib/rules/security/S039_tls_certificate_validation.dart +131 -0
  110. package/dart_analyzer/lib/rules/security/S040_session_fixation_protection.dart +155 -0
  111. package/dart_analyzer/lib/rules/security/S041_session_token_invalidation.dart +201 -0
  112. package/dart_analyzer/lib/rules/security/S042_require_re_authentication_for_long_lived.dart +158 -0
  113. package/dart_analyzer/lib/rules/security/S043_password_changes_invalidate_all_sessions.dart +88 -0
  114. package/dart_analyzer/lib/rules/security/S044_re_authentication_required.dart +119 -0
  115. package/dart_analyzer/lib/rules/security/S045_brute_force_protection.dart +253 -0
  116. package/dart_analyzer/lib/rules/security/S046_jwt_algorithm_allowlist.dart +113 -0
  117. package/dart_analyzer/lib/rules/security/S047_oauth_pkce_protection.dart +124 -0
  118. package/dart_analyzer/lib/rules/security/S048_oauth_redirect_uri_validation.dart +134 -0
  119. package/dart_analyzer/lib/rules/security/S049_short_validity_tokens.dart +145 -0
  120. package/dart_analyzer/lib/rules/security/S050_reference_tokens_entropy.dart +234 -0
  121. package/dart_analyzer/lib/rules/security/S051_password_length_policy.dart +171 -0
  122. package/dart_analyzer/lib/rules/security/S052_weak_otp_entropy.dart +107 -0
  123. package/dart_analyzer/lib/rules/security/S053_generic_error_messages.dart +159 -0
  124. package/dart_analyzer/lib/rules/security/S054_no_default_accounts.dart +141 -0
  125. package/dart_analyzer/lib/rules/security/S055_content_type_validation.dart +324 -0
  126. package/dart_analyzer/lib/rules/security/S056_log_injection_protection.dart +119 -0
  127. package/dart_analyzer/lib/rules/security/S057_utc_logging.dart +114 -0
  128. package/dart_analyzer/lib/rules/security/S058_no_ssrf.dart +175 -0
  129. package/dart_analyzer/lib/rules/security/S059_disable_debug_mode.dart +172 -0
  130. package/dart_analyzer/lib/rules/security/S060_password_minimum_length.dart +170 -0
  131. package/dart_analyzer/lib/symbol_table_extractor.dart +510 -0
  132. package/dart_analyzer/lib/utils/common_utils.dart +26 -0
  133. package/dart_analyzer/pubspec.lock +557 -0
  134. package/dart_analyzer/pubspec.yaml +39 -0
  135. package/dart_analyzer/test/fixtures/complex_code.dart +95 -0
  136. package/docs/GENERATED_FILE_HANDLING_SUMMARY.md +2 -2
  137. package/package.json +3 -2
@@ -0,0 +1,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
+ }