1#![allow(clippy::upper_case_acronyms)]
5#![allow(clippy::enum_variant_names)]
6#![allow(clippy::cmp_owned)]
7
8use anyhow::Result;
9use regex::Regex;
10use std::collections::HashMap;
11
12#[derive(Debug, Clone)]
14pub struct MetaclassInfo {
15 pub name: String,
16 pub metaclass_type: String,
17 pub impact: String,
18 pub attributes_modified: Vec<String>,
19 pub methods_modified: Vec<String>,
20}
21
22#[derive(Debug, Clone)]
24pub struct DecoratorInfo {
25 pub name: String,
26 pub decorator_type: String,
27 pub framework: Option<String>,
28 pub effects: Vec<String>,
29 pub is_factory: bool,
30 pub parameters: Vec<String>,
31}
32
33#[derive(Debug, Clone)]
35pub struct InheritanceInfo {
36 pub class_name: String,
37 pub base_classes: Vec<String>,
38 pub mro: Vec<String>,
39 pub has_diamond_inheritance: bool,
40 pub mixins: Vec<String>,
41 pub metaclass: Option<String>,
42}
43
44#[derive(Debug, Clone)]
46pub struct PythonSecurityAssessment {
47 pub level: SecurityLevel,
48 pub vulnerabilities_detected: Vec<SecurityVulnerability>,
49 pub security_features: Vec<SecurityFeature>,
50 pub recommendations: Vec<String>,
51}
52
53#[derive(Debug, Clone)]
55pub enum SecurityLevel {
56 High, Medium, Low, Vulnerable, }
61
62#[derive(Debug, Clone)]
64pub struct SecurityVulnerability {
65 pub vulnerability_type: VulnerabilityType,
66 pub severity: VulnerabilitySeverity,
67 pub description: String,
68 pub location: String,
69 pub recommendation: String,
70}
71
72#[derive(Debug, Clone)]
74pub enum VulnerabilityType {
75 SqlInjection, CommandInjection, DeserializationAttack, PathTraversal, WeakAuthentication, InsecureDataTransmission, DangerousPickle, UnvalidatedInput, InsecureRandomness, HardcodedSecrets, }
86
87#[derive(Debug, Clone)]
89pub enum VulnerabilitySeverity {
90 Critical,
91 High,
92 Medium,
93 Low,
94 Info,
95}
96
97#[derive(Debug, Clone)]
99pub struct SecurityFeature {
100 pub feature_type: SecurityFeatureType,
101 pub implementation_quality: ImplementationQuality,
102 pub description: String,
103}
104
105#[derive(Debug, Clone)]
107pub enum SecurityFeatureType {
108 Authentication, Authorization, InputValidation, CsrfProtection, DataEncryption, SecureHeaders, RateLimiting, SqlInjectionPrevention, }
117
118#[derive(Debug, Clone)]
120pub enum ImplementationQuality {
121 Excellent,
122 Good,
123 Adequate,
124 Poor,
125 Missing,
126}
127
128#[derive(Debug, Clone)]
130pub struct PythonPerformanceAnalysis {
131 pub overall_score: i32,
132 pub optimizations_detected: Vec<PerformanceOptimization>,
133 pub performance_issues: Vec<PerformanceIssue>,
134 pub recommendations: Vec<String>,
135}
136
137#[derive(Debug, Clone)]
139pub struct PerformanceOptimization {
140 pub optimization_type: OptimizationType,
141 pub impact_level: ImpactLevel,
142 pub description: String,
143 pub best_practices_followed: bool,
144}
145
146#[derive(Debug, Clone)]
148pub enum OptimizationType {
149 ListComprehension, GeneratorUsage, CachingImplementation, DatabaseOptimization, AsyncAwaitUsage, MemoryOptimization, AlgorithmicOptimization, }
157
158#[derive(Debug, Clone)]
160pub struct PerformanceIssue {
161 pub issue_type: PerformanceIssueType,
162 pub severity: IssueSeverity,
163 pub description: String,
164 pub recommendation: String,
165}
166
167#[derive(Debug, Clone)]
169pub enum PerformanceIssueType {
170 InEfficientLoops, MemoryLeaks, BlockingOperations, InefficientQueries, LargeDataStructures, UnoptimizedImports, GilContention, }
178
179#[derive(Debug, Clone)]
181pub enum IssueSeverity {
182 Critical,
183 High,
184 Medium,
185 Low,
186}
187
188#[derive(Debug, Clone)]
190pub enum ImpactLevel {
191 High, Medium, Low, Positive, }
196
197#[derive(Debug, Clone)]
199pub struct PythonFrameworkInfo {
200 pub name: String,
201 pub confidence: f32,
202 pub version_detected: Option<String>,
203 pub features_used: Vec<String>,
204 pub best_practices: Vec<String>,
205 pub framework_specific_analysis: FrameworkSpecificAnalysis,
206}
207
208#[derive(Debug, Clone)]
210pub enum FrameworkSpecificAnalysis {
211 Django(DjangoAnalysis),
212 Flask(FlaskAnalysis),
213 FastAPI(FastAPIAnalysis),
214 Pytest(PytestAnalysis),
215 Celery(CeleryAnalysis),
216}
217
218#[derive(Debug, Clone)]
220pub struct DjangoAnalysis {
221 pub models_analysis: Vec<DjangoModelInfo>,
222 pub views_analysis: Vec<DjangoViewInfo>,
223 pub middleware_usage: Vec<String>,
224 pub security_middleware: Vec<String>,
225 pub signals_usage: Vec<String>,
226 pub admin_customization: bool,
227}
228
229#[derive(Debug, Clone)]
231pub struct DjangoModelInfo {
232 pub name: String,
233 pub fields: Vec<DjangoFieldInfo>,
234 pub relationships: Vec<String>,
235 pub custom_managers: bool,
236 pub meta_options: Vec<String>,
237}
238
239#[derive(Debug, Clone)]
241pub struct DjangoFieldInfo {
242 pub name: String,
243 pub field_type: String,
244 pub constraints: Vec<String>,
245 pub indexes: bool,
246}
247
248#[derive(Debug, Clone)]
250pub struct DjangoViewInfo {
251 pub name: String,
252 pub view_type: DjangoViewType,
253 pub permissions: Vec<String>,
254 pub mixins: Vec<String>,
255}
256
257#[derive(Debug, Clone)]
259pub enum DjangoViewType {
260 FunctionBased,
261 ClassBased,
262 GenericView,
263 ViewSet,
264}
265
266#[derive(Debug, Clone)]
268pub struct FlaskAnalysis {
269 pub blueprints: Vec<FlaskBlueprintInfo>,
270 pub extensions: Vec<String>,
271 pub error_handlers: Vec<String>,
272 pub template_usage: bool,
273 pub session_management: bool,
274}
275
276#[derive(Debug, Clone)]
278pub struct FlaskBlueprintInfo {
279 pub name: String,
280 pub url_prefix: Option<String>,
281 pub routes: Vec<FlaskRouteInfo>,
282}
283
284#[derive(Debug, Clone)]
286pub struct FlaskRouteInfo {
287 pub path: String,
288 pub methods: Vec<String>,
289 pub endpoint: String,
290 pub view_function: String,
291}
292
293#[derive(Debug, Clone)]
295pub struct FastAPIAnalysis {
296 pub router_usage: Vec<FastAPIRouterInfo>,
297 pub dependency_injection: Vec<String>,
298 pub background_tasks: bool,
299 pub websocket_endpoints: Vec<String>,
300 pub middleware: Vec<String>,
301 pub response_models: Vec<String>,
302}
303
304#[derive(Debug, Clone)]
306pub struct FastAPIRouterInfo {
307 pub prefix: Option<String>,
308 pub tags: Vec<String>,
309 pub endpoints: Vec<FastAPIEndpointInfo>,
310}
311
312#[derive(Debug, Clone)]
314pub struct FastAPIEndpointInfo {
315 pub path: String,
316 pub method: String,
317 pub response_model: Option<String>,
318 pub dependencies: Vec<String>,
319}
320
321#[derive(Debug, Clone)]
323pub struct PytestAnalysis {
324 pub fixtures: Vec<PytestFixtureInfo>,
325 pub parametrized_tests: Vec<String>,
326 pub markers: Vec<String>,
327 pub plugins: Vec<String>,
328 pub coverage_setup: bool,
329}
330
331#[derive(Debug, Clone)]
333pub struct PytestFixtureInfo {
334 pub name: String,
335 pub scope: String,
336 pub autouse: bool,
337 pub dependencies: Vec<String>,
338}
339
340#[derive(Debug, Clone)]
342pub struct CeleryAnalysis {
343 pub tasks: Vec<CeleryTaskInfo>,
344 pub brokers: Vec<String>,
345 pub result_backends: Vec<String>,
346 pub routing: Vec<String>,
347 pub monitoring: bool,
348}
349
350#[derive(Debug, Clone)]
352pub struct CeleryTaskInfo {
353 pub name: String,
354 pub task_type: CeleryTaskType,
355 pub retry_policy: Option<String>,
356 pub rate_limit: Option<String>,
357}
358
359#[derive(Debug, Clone)]
361pub enum CeleryTaskType {
362 Regular,
363 Periodic,
364 Chain,
365 Group,
366 Chord,
367}
368
369#[derive(Debug, Clone)]
371pub struct PythonTypeHintAnalysis {
372 pub overall_coverage: f32,
373 pub type_coverage_score: TypeCoverageScore,
374 pub type_hints_detected: Vec<TypeHintInfo>,
375 pub type_safety_issues: Vec<TypeSafetyIssue>,
376 pub modern_type_features: Vec<ModernTypeFeature>,
377 pub recommendations: Vec<String>,
378}
379
380#[derive(Debug, Clone)]
382pub enum TypeCoverageScore {
383 Excellent, Good, Fair, Poor, Minimal, }
389
390#[derive(Debug, Clone)]
392pub struct TypeHintInfo {
393 pub location: String,
394 pub hint_type: TypeHintType,
395 pub complexity: TypeComplexity,
396 pub is_generic: bool,
397 pub has_constraints: bool,
398 pub python_version_required: String,
399}
400
401#[derive(Debug, Clone)]
403pub enum TypeHintType {
404 SimpleType(String), UnionType(Vec<String>), GenericType(GenericTypeInfo), ProtocolType(String), LiteralType(Vec<String>), CallableType(CallableTypeInfo), TypeVarType(TypeVarInfo), OptionalType(String), FinalType(String), TypedDictType(TypedDictInfo), }
415
416#[derive(Debug, Clone)]
418pub struct GenericTypeInfo {
419 pub base_type: String, pub type_parameters: Vec<String>, pub is_covariant: bool,
422 pub is_contravariant: bool,
423}
424
425#[derive(Debug, Clone)]
427pub struct CallableTypeInfo {
428 pub parameter_types: Vec<String>,
429 pub return_type: String,
430 pub is_async: bool,
431}
432
433#[derive(Debug, Clone)]
435pub struct TypeVarInfo {
436 pub name: String,
437 pub bounds: Vec<String>,
438 pub constraints: Vec<String>,
439 pub covariant: bool,
440 pub contravariant: bool,
441}
442
443#[derive(Debug, Clone)]
445pub struct TypedDictInfo {
446 pub name: String,
447 pub fields: Vec<TypedDictField>,
448 pub total: bool, }
450
451#[derive(Debug, Clone)]
453pub struct TypedDictField {
454 pub name: String,
455 pub field_type: String,
456 pub required: bool,
457}
458
459#[derive(Debug, Clone)]
461pub enum TypeComplexity {
462 Simple, Moderate, Complex, Advanced, }
467
468#[derive(Debug, Clone)]
470pub struct TypeSafetyIssue {
471 pub issue_type: TypeSafetyIssueType,
472 pub severity: TypeSafetySeverity,
473 pub location: String,
474 pub description: String,
475 pub recommendation: String,
476}
477
478#[derive(Debug, Clone)]
480pub enum TypeSafetyIssueType {
481 AnyTypeOveruse, MissingTypeHints, InconsistentTypes, TypeIgnoreOveruse, WrongTypeHintSyntax, DeprecatedTypingSyntax, UnreachableCode, TypeVarianceIssue, }
490
491#[derive(Debug, Clone)]
493pub enum TypeSafetySeverity {
494 Error, Warning, Info, }
498
499#[derive(Debug, Clone)]
501pub struct ModernTypeFeature {
502 pub feature_type: ModernTypeFeatureType,
503 pub python_version: String,
504 pub usage_count: usize,
505 pub description: String,
506 pub is_best_practice: bool,
507}
508
509#[derive(Debug, Clone)]
511pub enum ModernTypeFeatureType {
512 PositionalOnlyParams, UnionSyntaxPy310, TypedDict, FinalType, LiteralType, ProtocolType, TypeGuard, OverloadDecorator, GenericAlias, ParamSpec, TypeVarTuple, }
524
525#[derive(Debug, Clone)]
527struct TypeHintPattern {
528 name: String,
529 pattern: Regex,
530 hint_type: String,
531 complexity: TypeComplexity,
532 python_version: String,
533}
534
535#[derive(Debug, Clone)]
537pub struct PythonAsyncAwaitAnalysis {
538 pub overall_async_score: i32,
539 pub async_functions_detected: Vec<AsyncFunctionInfo>,
540 pub await_usage_patterns: Vec<AwaitUsageInfo>,
541 pub concurrency_patterns: Vec<ConcurrencyPatternInfo>,
542 pub async_performance_issues: Vec<AsyncPerformanceIssue>,
543 pub async_security_issues: Vec<AsyncSecurityIssue>,
544 pub modern_async_features: Vec<ModernAsyncFeature>,
545 pub recommendations: Vec<String>,
546}
547
548#[derive(Debug, Clone)]
550pub struct AsyncFunctionInfo {
551 pub name: String,
552 pub function_type: AsyncFunctionType,
553 pub complexity: AsyncComplexity,
554 pub coroutine_type: CoroutineType,
555 pub error_handling: AsyncErrorHandling,
556 pub has_timeout: bool,
557 pub uses_context_manager: bool,
558 pub location: String,
559}
560
561#[derive(Debug, Clone)]
563pub enum AsyncFunctionType {
564 RegularAsync, AsyncGenerator, AsyncContextManager, AsyncIterator, AsyncProperty, AsyncDecorator, }
571
572#[derive(Debug, Clone)]
574pub enum AsyncComplexity {
575 Simple, Moderate, Complex, Advanced, }
580
581#[derive(Debug, Clone)]
583pub enum CoroutineType {
584 Native, Framework(String), Generator, Hybrid, }
589
590#[derive(Debug, Clone)]
592pub enum AsyncErrorHandling {
593 None, Basic, Timeout, Robust, }
598
599#[derive(Debug, Clone)]
601pub struct AwaitUsageInfo {
602 pub location: String,
603 pub context: AwaitContext,
604 pub usage_pattern: AwaitUsagePattern,
605 pub is_valid: bool,
606 pub potential_issues: Vec<AwaitIssue>,
607}
608
609#[derive(Debug, Clone)]
611pub enum AwaitContext {
612 AsyncFunction, AsyncGenerator, AsyncContextManager, AsyncIterator, SyncContext, Comprehension, Lambda, }
620
621#[derive(Debug, Clone)]
623pub enum AwaitUsagePattern {
624 SingleAwait, SequentialAwaits, ConditionalAwait, NestedAwait, GatheredAwait, ConcurrentAwait, }
631
632#[derive(Debug, Clone)]
634pub enum AwaitIssue {
635 IllegalContext, MissingAwait, BlockingCall, SyncInAsync, ResourceLeak, TimeoutMissing, }
642
643#[derive(Debug, Clone)]
645pub struct ConcurrencyPatternInfo {
646 pub pattern_type: ConcurrencyPatternType,
647 pub usage_quality: ConcurrencyUsageQuality,
648 pub performance_impact: AsyncPerformanceImpact,
649 pub location: String,
650 pub best_practices_followed: bool,
651}
652
653#[derive(Debug, Clone)]
655pub enum ConcurrencyPatternType {
656 AsyncioGather, AsyncioWait, AsyncioQueue, AsyncioSemaphore, AsyncioLock, TaskGroup, ConcurrentFutures, AsyncioTimeout, AsyncioEvent, AsyncioCondition, }
667
668#[derive(Debug, Clone)]
670pub enum ConcurrencyUsageQuality {
671 Excellent, Good, Adequate, Poor, Dangerous, }
677
678#[derive(Debug, Clone)]
680pub enum AsyncPerformanceImpact {
681 Positive, Neutral, Negative, Critical, }
686
687#[derive(Debug, Clone)]
689pub struct AsyncPerformanceIssue {
690 pub issue_type: AsyncPerformanceIssueType,
691 pub severity: AsyncIssueSeverity,
692 pub location: String,
693 pub description: String,
694 pub recommendation: String,
695 pub estimated_impact: AsyncPerformanceImpact,
696}
697
698#[derive(Debug, Clone)]
700pub enum AsyncPerformanceIssueType {
701 BlockingIOInAsync, EventLoopBlocking, GILContentionAsync, AwaitInLoop, MissingConcurrency, ResourceLeakAsync, SyncWrapperOveruse, AsyncioSubprocessSync, DatabaseBlockingAsync, SlowAsyncGenerator, }
712
713#[derive(Debug, Clone)]
715pub enum AsyncIssueSeverity {
716 Critical, High, Medium, Low, Info, }
722
723#[derive(Debug, Clone)]
725pub struct AsyncSecurityIssue {
726 pub issue_type: AsyncSecurityIssueType,
727 pub severity: AsyncSecuritySeverity,
728 pub location: String,
729 pub description: String,
730 pub recommendation: String,
731}
732
733#[derive(Debug, Clone)]
735pub enum AsyncSecurityIssueType {
736 AsyncRaceCondition, SharedStateNoLock, AsyncTimeoutVuln, TaskCancellationLeak, AsyncResourceExposure, EventLoopPoisoning, AsyncPickleVuln, ConcurrentModification, }
745
746#[derive(Debug, Clone)]
748pub enum AsyncSecuritySeverity {
749 Critical, High, Medium, Low, Info, }
755
756#[derive(Debug, Clone)]
758pub struct ModernAsyncFeature {
759 pub feature_type: ModernAsyncFeatureType,
760 pub python_version: String,
761 pub usage_count: usize,
762 pub description: String,
763 pub is_best_practice: bool,
764}
765
766#[derive(Debug, Clone)]
768pub enum ModernAsyncFeatureType {
769 AsyncContextManager, TaskGroups, AsyncioTimeout, AsyncIterators, AsyncGenerators, AsyncComprehensions, ContextVars, AsyncioRun, AsyncDecorators, StreamAPI, SubprocessAsync, }
781
782#[derive(Debug, Clone)]
784struct AsyncPattern {
785 name: String,
786 pattern: Regex,
787 pattern_type: String,
788 performance_impact: AsyncPerformanceImpact,
789}
790
791#[derive(Debug, Clone)]
793pub struct PythonPackageDependencyAnalysis {
794 pub overall_health_score: i32,
795 pub requirements_files: Vec<RequirementsFileInfo>,
796 pub dependencies: Vec<RequirementInfo>,
797 pub dependency_issues: Vec<DependencyIssue>,
798 pub virtual_environments: Vec<VirtualEnvironmentInfo>,
799 pub import_analysis: Vec<ImportAnalysisInfo>,
800 pub security_vulnerabilities: Vec<SecurityVulnerabilityInfo>,
801 pub license_analysis: Vec<LicenseInfo>,
802 pub recommendations: Vec<String>,
803}
804
805#[derive(Debug, Clone)]
807pub struct RequirementsFileInfo {
808 pub file_path: String,
809 pub file_type: RequirementsFileType,
810 pub dependencies_count: usize,
811 pub has_version_pins: bool,
812 pub has_hashes: bool,
813 pub uses_constraints: bool,
814 pub quality_score: DependencyQualityScore,
815}
816
817#[derive(Debug, Clone)]
819pub enum RequirementsFileType {
820 RequirementsTxt, PyprojectToml, SetupPy, Pipfile, PipfileLock, PoetryLock, CondaYml, SetupCfg, }
829
830#[derive(Debug, Clone)]
832pub struct RequirementInfo {
833 pub name: String,
834 pub version_spec: String,
835 pub source: RequirementSource,
836 pub is_dev_dependency: bool,
837 pub is_optional: bool,
838 pub extras: Vec<String>,
839 pub markers: Vec<String>,
840 pub metadata: PackageMetadata,
841}
842
843#[derive(Debug, Clone)]
845pub enum RequirementSource {
846 PyPI, Git(String), Local(String), URL(String), VCS(String, String), }
852
853#[derive(Debug, Clone)]
855pub struct PackageMetadata {
856 pub description: String,
857 pub author: String,
858 pub license: String,
859 pub homepage: Option<String>,
860 pub documentation: Option<String>,
861 pub last_updated: Option<String>,
862 pub download_count: Option<u64>,
863 pub maintenance_status: MaintenanceStatus,
864}
865
866#[derive(Debug, Clone)]
868pub enum MaintenanceStatus {
869 Active, Maintained, Stable, Deprecated, Abandoned, Unknown, }
876
877#[derive(Debug, Clone)]
879pub enum DependencyQualityScore {
880 Excellent, Good, Fair, Poor, Critical, }
886
887#[derive(Debug, Clone)]
889pub struct DependencyIssue {
890 pub issue_type: DependencyIssueType,
891 pub severity: DependencyIssueSeverity,
892 pub affected_packages: Vec<String>,
893 pub description: String,
894 pub recommendation: String,
895 pub auto_fixable: bool,
896}
897
898#[derive(Debug, Clone)]
900pub enum DependencyIssueType {
901 VersionConflict, CircularDependency, UnusedDependency, MissingDependency, SecurityVulnerability, LicenseIncompatibility, DeprecatedPackage, UnpinnedVersion, OutdatedVersion, DevDependencyInProd, DuplicateDependency, }
913
914#[derive(Debug, Clone)]
916pub enum DependencyIssueSeverity {
917 Critical, High, Medium, Low, Info, }
923
924#[derive(Debug, Clone)]
926pub struct VirtualEnvironmentInfo {
927 pub env_type: VirtualEnvironmentType,
928 pub location: String,
929 pub python_version: String,
930 pub is_active: bool,
931 pub packages_count: usize,
932 pub env_variables: Vec<EnvironmentVariable>,
933 pub configuration: VirtualEnvironmentConfig,
934}
935
936#[derive(Debug, Clone)]
938pub enum VirtualEnvironmentType {
939 Venv, Virtualenv, Conda, Pipenv, Poetry, Docker, Pyenv, Custom(String), }
948
949#[derive(Debug, Clone)]
951pub struct EnvironmentVariable {
952 pub name: String,
953 pub value: String,
954 pub is_sensitive: bool,
955 pub purpose: EnvironmentVariablePurpose,
956}
957
958#[derive(Debug, Clone)]
960pub enum EnvironmentVariablePurpose {
961 Configuration, Secret, Path, Development, Production, Testing, Unknown, }
969
970#[derive(Debug, Clone)]
972pub struct VirtualEnvironmentConfig {
973 pub isolated: bool,
974 pub system_site_packages: bool,
975 pub pip_version: Option<String>,
976 pub setuptools_version: Option<String>,
977 pub custom_configurations: Vec<String>,
978}
979
980#[derive(Debug, Clone)]
982pub struct ImportAnalysisInfo {
983 pub import_statement: String,
984 pub import_type: ImportType,
985 pub module_category: ModuleCategory,
986 pub usage_count: usize,
987 pub is_unused: bool,
988 pub import_issues: Vec<ImportIssue>,
989 pub optimization_suggestions: Vec<String>,
990}
991
992#[derive(Debug, Clone)]
994pub enum ImportType {
995 StandardImport, FromImport, StarImport, AliasImport, RelativeImport, ConditionalImport, DynamicImport, }
1003
1004#[derive(Debug, Clone)]
1006pub enum ModuleCategory {
1007 StandardLibrary, ThirdParty, Local, BuiltIn, Unknown, }
1013
1014#[derive(Debug, Clone, PartialEq)]
1016pub enum ImportIssue {
1017 CircularImport, StarImportDangerous, UnusedImport, RedundantImport, WrongImportOrder, MissingImport, SlowImport, DeprecatedImport, }
1026
1027#[derive(Debug, Clone)]
1029pub struct SecurityVulnerabilityInfo {
1030 pub cve_id: Option<String>,
1031 pub advisory_id: Option<String>,
1032 pub package_name: String,
1033 pub affected_versions: Vec<String>,
1034 pub fixed_version: Option<String>,
1035 pub severity: SecurityVulnerabilitySeverity,
1036 pub vulnerability_type: VulnerabilityCategory,
1037 pub description: String,
1038 pub references: Vec<String>,
1039 pub published_date: Option<String>,
1040 pub last_modified: Option<String>,
1041}
1042
1043#[derive(Debug, Clone)]
1045pub enum SecurityVulnerabilitySeverity {
1046 Critical, High, Medium, Low, None, Unknown, }
1053
1054#[derive(Debug, Clone)]
1056pub enum VulnerabilityCategory {
1057 CodeExecution, SqlInjection, XSS, CSRF, PathTraversal, Deserialization, Cryptographic, DoS, PrivilegeEscalation, InformationDisclosure, InputValidation, AuthenticationBypass, Other(String), }
1071
1072#[derive(Debug, Clone)]
1074pub struct LicenseInfo {
1075 pub package_name: String,
1076 pub license_type: LicenseType,
1077 pub license_text: Option<String>,
1078 pub compatibility: LicenseCompatibility,
1079 pub commercial_use_allowed: bool,
1080 pub distribution_allowed: bool,
1081 pub modification_allowed: bool,
1082 pub patent_grant: bool,
1083 pub copyleft: bool,
1084}
1085
1086#[derive(Debug, Clone)]
1088pub enum LicenseType {
1089 MIT,
1090 Apache2,
1091 GPL2,
1092 GPL3,
1093 BSD2Clause,
1094 BSD3Clause,
1095 LGPL,
1096 Mozilla,
1097 Unlicense,
1098 Proprietary,
1099 Custom(String),
1100 Unknown,
1101}
1102
1103#[derive(Debug, Clone)]
1105pub enum LicenseCompatibility {
1106 Compatible, ConditionallyCompatible, Incompatible, RequiresReview, Unknown, }
1112
1113#[derive(Debug, Clone)]
1115struct DependencyPattern {
1116 name: String,
1117 pattern: Regex,
1118 #[allow(dead_code)] file_type: RequirementsFileType,
1120 extraction_method: String,
1121}
1122
1123#[derive(Debug, Clone)]
1125pub struct ModernPythonFeatureAnalysis {
1126 pub overall_modernity_score: i32,
1127 pub python_version_detected: PythonVersionDetected,
1128 pub dataclass_features: Vec<DataclassInfo>,
1129 pub context_manager_features: Vec<ContextManagerInfo>,
1130 pub fstring_features: Vec<FStringInfo>,
1131 pub pattern_matching_features: Vec<PatternMatchingInfo>,
1132 pub generator_features: Vec<GeneratorInfo>,
1133 pub decorator_features: Vec<ModernDecoratorInfo>,
1134 pub modern_syntax_features: Vec<ModernSyntaxInfo>,
1135 pub recommendations: Vec<String>,
1136}
1137
1138#[derive(Debug, Clone)]
1140pub struct PythonVersionDetected {
1141 pub minimum_version: String,
1142 pub features_by_version: Vec<VersionFeature>,
1143 pub compatibility_issues: Vec<CompatibilityIssue>,
1144}
1145
1146#[derive(Debug, Clone)]
1148pub struct VersionFeature {
1149 pub feature_name: String,
1150 pub python_version: String,
1151 pub usage_count: usize,
1152 pub is_best_practice: bool,
1153}
1154
1155#[derive(Debug, Clone)]
1157pub struct CompatibilityIssue {
1158 pub issue_type: CompatibilityIssueType,
1159 pub severity: CompatibilitySeverity,
1160 pub feature_name: String,
1161 pub required_version: String,
1162 pub description: String,
1163 pub recommendation: String,
1164}
1165
1166#[derive(Debug, Clone)]
1168pub enum CompatibilityIssueType {
1169 VersionMismatch, DeprecatedFeature, SyntaxError, ImportError, BehaviorChange, }
1175
1176#[derive(Debug, Clone)]
1178pub enum CompatibilitySeverity {
1179 Critical, High, Medium, Low, Info, }
1185
1186#[derive(Debug, Clone)]
1188pub struct DataclassInfo {
1189 pub class_name: String,
1190 pub dataclass_type: DataclassType,
1191 pub fields: Vec<DataclassField>,
1192 pub features_used: Vec<DataclassFeature>,
1193 pub complexity: FeatureComplexity,
1194 pub best_practices_score: i32,
1195 pub recommendations: Vec<String>,
1196}
1197
1198#[derive(Debug, Clone, PartialEq)]
1200pub enum DataclassType {
1201 StandardDataclass, PydanticModel, NamedTuple, AttrsClass, SimpleNamespace, }
1207
1208#[derive(Debug, Clone)]
1210pub struct DataclassField {
1211 pub name: String,
1212 pub field_type: String,
1213 pub has_default: bool,
1214 pub is_optional: bool,
1215 pub validation_rules: Vec<String>,
1216 pub metadata: Vec<String>,
1217}
1218
1219#[derive(Debug, Clone, PartialEq)]
1221pub enum DataclassFeature {
1222 FrozenClass, InitGeneration, ReprGeneration, EqGeneration, OrderGeneration, HashGeneration, SlotsUsage, PostInitProcessing, FieldFactories, KwOnlyFields, }
1233
1234#[derive(Debug, Clone)]
1236pub struct ContextManagerInfo {
1237 pub context_type: ContextManagerType,
1238 pub usage_pattern: ContextUsagePattern,
1239 pub resource_management: ResourceManagementQuality,
1240 pub error_handling: ContextErrorHandling,
1241 pub is_async: bool,
1242 pub nested_level: usize,
1243 pub best_practices_followed: bool,
1244}
1245
1246#[derive(Debug, Clone)]
1248pub enum ContextManagerType {
1249 BuiltInFileManager, CustomContextManager, ContextlibManager, AsyncContextManager, DatabaseConnection, LockManager, TemporaryResource, ExceptionSuppression, }
1258
1259#[derive(Debug, Clone)]
1261pub enum ContextUsagePattern {
1262 SingleContext, MultipleContexts, NestedContexts, AsyncContext, ConditionalContext, LoopContext, }
1269
1270#[derive(Debug, Clone)]
1272pub enum ResourceManagementQuality {
1273 Excellent, Good, Adequate, Poor, Dangerous, }
1279
1280#[derive(Debug, Clone)]
1282pub enum ContextErrorHandling {
1283 Comprehensive, Basic, Minimal, None, }
1288
1289#[derive(Debug, Clone)]
1291pub struct FStringInfo {
1292 pub expression: String,
1293 pub complexity: FStringComplexity,
1294 pub features_used: Vec<FStringFeature>,
1295 pub performance_impact: PerformanceImpact,
1296 pub formatting_quality: FormattingQuality,
1297 pub readability_score: i32,
1298}
1299
1300#[derive(Debug, Clone, PartialEq)]
1302pub enum FStringComplexity {
1303 Simple, Moderate, Complex, Advanced, }
1308
1309#[derive(Debug, Clone, PartialEq)]
1311pub enum FStringFeature {
1312 BasicInterpolation, ExpressionEvaluation, FormatSpecifiers, ConversionFlags, NestedFormatting, MultilineString, RawFString, ComplexExpressions, }
1321
1322#[derive(Debug, Clone)]
1324pub enum PerformanceImpact {
1325 Positive, Neutral, Negative, Critical, }
1330
1331#[derive(Debug, Clone)]
1333pub enum FormattingQuality {
1334 Excellent, Good, Adequate, Poor, Unreadable, }
1340
1341#[derive(Debug, Clone)]
1343pub struct PatternMatchingInfo {
1344 pub match_statement: String,
1345 pub pattern_types: Vec<PatternType>,
1346 pub complexity: PatternComplexity,
1347 pub has_guards: bool,
1348 pub is_exhaustive: bool,
1349 pub performance_characteristics: MatchPerformance,
1350 pub best_practices_score: i32,
1351}
1352
1353#[derive(Debug, Clone)]
1355pub enum PatternType {
1356 LiteralPattern, VariablePattern, WildcardPattern, ValuePattern, GroupPattern, SequencePattern, MappingPattern, ClassPattern, OrPattern, AsPattern, GuardedPattern, }
1368
1369#[derive(Debug, Clone)]
1371pub enum PatternComplexity {
1372 Simple, Moderate, Complex, Advanced, }
1377
1378#[derive(Debug, Clone)]
1380pub enum MatchPerformance {
1381 Optimal, Good, Fair, Poor, Critical, }
1387
1388#[derive(Debug, Clone)]
1390pub struct GeneratorInfo {
1391 pub generator_type: GeneratorType,
1392 pub usage_pattern: GeneratorUsagePattern,
1393 pub memory_efficiency: MemoryEfficiency,
1394 pub complexity: GeneratorComplexity,
1395 pub is_async: bool,
1396 pub yield_analysis: YieldAnalysis,
1397 pub optimization_opportunities: Vec<String>,
1398}
1399
1400#[derive(Debug, Clone)]
1402pub enum GeneratorType {
1403 GeneratorFunction, GeneratorExpression, AsyncGenerator, Comprehension, IteratorProtocol, }
1409
1410#[derive(Debug, Clone)]
1412pub enum GeneratorUsagePattern {
1413 SimpleIteration, DataTransformation, LazyEvaluation, InfiniteSequence, Coroutine, Pipeline, }
1420
1421#[derive(Debug, Clone)]
1423pub enum MemoryEfficiency {
1424 Excellent, Good, Adequate, Poor, Critical, }
1430
1431#[derive(Debug, Clone)]
1433pub enum GeneratorComplexity {
1434 Simple, Moderate, Complex, Advanced, }
1439
1440#[derive(Debug, Clone)]
1442pub struct YieldAnalysis {
1443 pub yield_count: usize,
1444 pub has_yield_from: bool,
1445 pub has_send_values: bool,
1446 pub has_throw_values: bool,
1447 pub has_close_handling: bool,
1448}
1449
1450#[derive(Debug, Clone)]
1452pub struct ModernDecoratorInfo {
1453 pub decorator_name: String,
1454 pub decorator_category: DecoratorCategory,
1455 pub usage_pattern: DecoratorUsagePattern,
1456 pub complexity: DecoratorComplexity,
1457 pub is_factory: bool,
1458 pub is_async: bool,
1459 pub parameters: Vec<String>,
1460 pub best_practices_score: i32,
1461}
1462
1463#[derive(Debug, Clone)]
1465pub enum DecoratorCategory {
1466 BuiltIn, FunctoolsDecorator, AsyncDecorator, DataValidation, WebFramework, Testing, Performance, Custom, }
1475
1476#[derive(Debug, Clone)]
1478pub enum DecoratorUsagePattern {
1479 SingleDecorator, StackedDecorators, ParameterizedDecorator, ConditionalDecorator, DynamicDecorator, }
1485
1486#[derive(Debug, Clone)]
1488pub enum DecoratorComplexity {
1489 Simple, Moderate, Complex, Advanced, }
1494
1495#[derive(Debug, Clone)]
1497pub struct ModernSyntaxInfo {
1498 pub feature_type: ModernSyntaxType,
1499 pub python_version: String,
1500 pub usage_count: usize,
1501 pub complexity: SyntaxComplexity,
1502 pub best_practices_followed: bool,
1503 pub migration_suggestions: Vec<String>,
1504}
1505
1506#[derive(Debug, Clone, PartialEq)]
1508pub enum ModernSyntaxType {
1509 WalrusOperator, PositionalOnlyParams, TypeUnionOperator, ExceptionGroups, GenericTypeHints, StringPrefixChaining, DictUnionOperator, RemovePrefix, ContextVars, }
1519
1520#[derive(Debug, Clone)]
1522pub enum SyntaxComplexity {
1523 Simple, Moderate, Complex, Expert, }
1528
1529#[derive(Debug, Clone)]
1531pub enum FeatureComplexity {
1532 Simple, Moderate, Complex, Expert, }
1537
1538#[derive(Debug, Clone)]
1540struct ModernFeaturePattern {
1541 name: String,
1542 pattern: Regex,
1543 #[allow(dead_code)] feature_type: String,
1545 python_version: String,
1546 complexity: FeatureComplexity,
1547}
1548
1549pub struct PythonAnalyzer {
1551 decorator_patterns: HashMap<String, Vec<DecoratorPattern>>,
1552 metaclass_patterns: HashMap<String, Vec<MetaclassPattern>>,
1553 security_patterns: HashMap<String, Vec<SecurityPattern>>,
1554 performance_patterns: HashMap<String, Vec<PerformancePattern>>,
1555 framework_patterns: HashMap<String, Vec<FrameworkPattern>>,
1556 type_hint_patterns: HashMap<String, Vec<TypeHintPattern>>,
1557 async_patterns: HashMap<String, Vec<AsyncPattern>>,
1558 dependency_patterns: HashMap<String, Vec<DependencyPattern>>,
1559 modern_feature_patterns: HashMap<String, Vec<ModernFeaturePattern>>,
1560}
1561
1562#[derive(Debug, Clone)]
1563struct DecoratorPattern {
1564 name: String,
1565 pattern: Regex,
1566 framework: Option<String>,
1567 effects: Vec<String>,
1568 is_factory: bool,
1569}
1570
1571#[derive(Debug, Clone)]
1572struct MetaclassPattern {
1573 #[allow(dead_code)] name: String,
1575 pattern: Regex,
1576 impact: String,
1577}
1578
1579#[derive(Debug, Clone)]
1580struct SecurityPattern {
1581 #[allow(dead_code)] name: String,
1583 pattern: Regex,
1584 vulnerability_type: VulnerabilityType,
1585 severity: VulnerabilitySeverity,
1586 description: String,
1587}
1588
1589#[derive(Debug, Clone)]
1590struct PerformancePattern {
1591 #[allow(dead_code)] name: String,
1593 pattern: Regex,
1594 optimization_type: OptimizationType,
1595 impact_level: ImpactLevel,
1596 description: String,
1597}
1598
1599#[derive(Debug, Clone)]
1600struct FrameworkPattern {
1601 #[allow(dead_code)] name: String,
1603 pattern: Regex,
1604 framework: String,
1605 features: Vec<String>,
1606 confidence: f32,
1607}
1608
1609impl PythonAnalyzer {
1610 pub fn new() -> Self {
1611 let mut analyzer = Self {
1612 decorator_patterns: HashMap::new(),
1613 metaclass_patterns: HashMap::new(),
1614 security_patterns: HashMap::new(),
1615 performance_patterns: HashMap::new(),
1616 framework_patterns: HashMap::new(),
1617 type_hint_patterns: HashMap::new(),
1618 async_patterns: HashMap::new(),
1619 dependency_patterns: HashMap::new(),
1620 modern_feature_patterns: HashMap::new(),
1621 };
1622 analyzer.initialize_patterns();
1623 analyzer
1624 }
1625
1626 fn initialize_patterns(&mut self) {
1627 let framework_decorators = vec![
1629 DecoratorPattern {
1630 name: "Flask Route".to_string(),
1631 pattern: Regex::new(r"@app\.route\s*\([^)]*\)").unwrap(),
1632 framework: Some("Flask".to_string()),
1633 effects: vec!["URL routing".to_string(), "HTTP method binding".to_string()],
1634 is_factory: true,
1635 },
1636 DecoratorPattern {
1637 name: "Django View".to_string(),
1638 pattern: Regex::new(r"@(?:login_required|permission_required|csrf_exempt)")
1639 .unwrap(),
1640 framework: Some("Django".to_string()),
1641 effects: vec!["Authentication".to_string(), "Authorization".to_string()],
1642 is_factory: false,
1643 },
1644 DecoratorPattern {
1645 name: "FastAPI Route".to_string(),
1646 pattern: Regex::new(r"@app\.(?:get|post|put|delete|patch)\s*\([^)]*\)").unwrap(),
1647 framework: Some("FastAPI".to_string()),
1648 effects: vec!["API endpoint".to_string(), "Request validation".to_string()],
1649 is_factory: true,
1650 },
1651 DecoratorPattern {
1652 name: "Pytest Fixture".to_string(),
1653 pattern: Regex::new(r"@pytest\.fixture\s*(?:\([^)]*\))?").unwrap(),
1654 framework: Some("pytest".to_string()),
1655 effects: vec!["Test setup".to_string(), "Dependency injection".to_string()],
1656 is_factory: true,
1657 },
1658 DecoratorPattern {
1659 name: "SQLAlchemy Event".to_string(),
1660 pattern: Regex::new(r"@event\.listens_for\s*\([^)]*\)").unwrap(),
1661 framework: Some("SQLAlchemy".to_string()),
1662 effects: vec!["Database event handling".to_string()],
1663 is_factory: true,
1664 },
1665 DecoratorPattern {
1666 name: "Celery Task".to_string(),
1667 pattern: Regex::new(r"@(?:celery\.)?task\s*(?:\([^)]*\))?").unwrap(),
1668 framework: Some("Celery".to_string()),
1669 effects: vec![
1670 "Async task execution".to_string(),
1671 "Queue processing".to_string(),
1672 ],
1673 is_factory: true,
1674 },
1675 ];
1676 self.decorator_patterns
1677 .insert("framework".to_string(), framework_decorators);
1678
1679 let pattern_decorators = vec![
1681 DecoratorPattern {
1682 name: "Caching Decorator".to_string(),
1683 pattern: Regex::new(r"@(?:cache|lru_cache|memoize)").unwrap(),
1684 framework: None,
1685 effects: vec![
1686 "Result caching".to_string(),
1687 "Performance optimization".to_string(),
1688 ],
1689 is_factory: false,
1690 },
1691 DecoratorPattern {
1692 name: "Validation Decorator".to_string(),
1693 pattern: Regex::new(r"@(?:validate|validator|check)").unwrap(),
1694 framework: None,
1695 effects: vec!["Input validation".to_string(), "Type checking".to_string()],
1696 is_factory: false,
1697 },
1698 DecoratorPattern {
1699 name: "Authorization Decorator".to_string(),
1700 pattern: Regex::new(r"@(?:requires_auth|authorized|permission)").unwrap(),
1701 framework: None,
1702 effects: vec![
1703 "Access control".to_string(),
1704 "Security enforcement".to_string(),
1705 ],
1706 is_factory: false,
1707 },
1708 DecoratorPattern {
1709 name: "Logging Decorator".to_string(),
1710 pattern: Regex::new(r"@(?:log|trace|audit)").unwrap(),
1711 framework: None,
1712 effects: vec![
1713 "Function logging".to_string(),
1714 "Execution tracing".to_string(),
1715 ],
1716 is_factory: false,
1717 },
1718 ];
1719 self.decorator_patterns
1720 .insert("patterns".to_string(), pattern_decorators);
1721
1722 let metaclass_patterns = vec![
1724 MetaclassPattern {
1725 name: "Registry Metaclass".to_string(),
1726 pattern: Regex::new(r"class\s+\w+\s*\([^)]*metaclass\s*=\s*\w*Registry\w*")
1727 .unwrap(),
1728 impact: "Automatic class registration".to_string(),
1729 },
1730 MetaclassPattern {
1731 name: "Singleton Metaclass".to_string(),
1732 pattern: Regex::new(r"class\s+\w+\s*\([^)]*metaclass\s*=\s*\w*Singleton\w*")
1733 .unwrap(),
1734 impact: "Single instance enforcement".to_string(),
1735 },
1736 MetaclassPattern {
1737 name: "Attribute Injection Metaclass".to_string(),
1738 pattern: Regex::new(r"class\s+\w+\s*\([^)]*metaclass\s*=\s*\w*Inject\w*").unwrap(),
1739 impact: "Dynamic attribute injection".to_string(),
1740 },
1741 MetaclassPattern {
1742 name: "ORM Metaclass".to_string(),
1743 pattern: Regex::new(r"class\s+\w+\s*\([^)]*metaclass\s*=\s*\w*Model\w*").unwrap(),
1744 impact: "Database mapping and validation".to_string(),
1745 },
1746 ];
1747 self.metaclass_patterns
1748 .insert("common".to_string(), metaclass_patterns);
1749
1750 let security_patterns = vec![
1752 SecurityPattern {
1753 name: "SQL Injection Risk".to_string(),
1754 pattern: Regex::new(r#"(?:execute|raw)\s*\(\s*[f"'].*%.*[f"']"#).unwrap(),
1755 vulnerability_type: VulnerabilityType::SqlInjection,
1756 severity: VulnerabilitySeverity::High,
1757 description: "Potential SQL injection via string formatting".to_string(),
1758 },
1759 SecurityPattern {
1760 name: "Command Injection Risk".to_string(),
1761 pattern: Regex::new(r"(?:subprocess|os\.system)\s*\(.*(?:input|request)").unwrap(),
1762 vulnerability_type: VulnerabilityType::CommandInjection,
1763 severity: VulnerabilitySeverity::Critical,
1764 description: "Command injection via user input".to_string(),
1765 },
1766 SecurityPattern {
1767 name: "Unsafe Pickle Usage".to_string(),
1768 pattern: Regex::new(r"pickle\.loads?\s*\(").unwrap(),
1769 vulnerability_type: VulnerabilityType::DeserializationAttack,
1770 severity: VulnerabilitySeverity::High,
1771 description: "Unsafe pickle deserialization".to_string(),
1772 },
1773 SecurityPattern {
1774 name: "Hardcoded Secrets".to_string(),
1775 pattern: Regex::new(r#"(?:password|secret|key|token)\s*=\s*[f"'][^"']*[f"']"#)
1776 .unwrap(),
1777 vulnerability_type: VulnerabilityType::HardcodedSecrets,
1778 severity: VulnerabilitySeverity::Medium,
1779 description: "Hardcoded credentials in source code".to_string(),
1780 },
1781 ];
1782 self.security_patterns
1783 .insert("vulnerabilities".to_string(), security_patterns);
1784
1785 let performance_patterns = vec![
1787 PerformancePattern {
1788 name: "List Comprehension Optimization".to_string(),
1789 pattern: Regex::new(r"\[.*for.*in.*\]").unwrap(),
1790 optimization_type: OptimizationType::ListComprehension,
1791 impact_level: ImpactLevel::Medium,
1792 description: "Efficient list comprehension usage".to_string(),
1793 },
1794 PerformancePattern {
1795 name: "Generator Usage".to_string(),
1796 pattern: Regex::new(r"\(.*for.*in.*\)").unwrap(),
1797 optimization_type: OptimizationType::GeneratorUsage,
1798 impact_level: ImpactLevel::High,
1799 description: "Memory-efficient generator expression".to_string(),
1800 },
1801 PerformancePattern {
1802 name: "Caching Implementation".to_string(),
1803 pattern: Regex::new(r"@(?:lru_cache|cache)").unwrap(),
1804 optimization_type: OptimizationType::CachingImplementation,
1805 impact_level: ImpactLevel::High,
1806 description: "Function result caching".to_string(),
1807 },
1808 PerformancePattern {
1809 name: "Async/Await Usage".to_string(),
1810 pattern: Regex::new(r"async\s+def|await\s+").unwrap(),
1811 optimization_type: OptimizationType::AsyncAwaitUsage,
1812 impact_level: ImpactLevel::High,
1813 description: "Asynchronous programming patterns".to_string(),
1814 },
1815 ];
1816 self.performance_patterns
1817 .insert("optimizations".to_string(), performance_patterns);
1818
1819 let framework_patterns = vec![
1821 FrameworkPattern {
1822 name: "Django Framework".to_string(),
1823 pattern: Regex::new(r"from\s+django|import\s+django").unwrap(),
1824 framework: "Django".to_string(),
1825 features: vec![
1826 "Models".to_string(),
1827 "Views".to_string(),
1828 "Templates".to_string(),
1829 ],
1830 confidence: 0.9,
1831 },
1832 FrameworkPattern {
1833 name: "Flask Framework".to_string(),
1834 pattern: Regex::new(r"from\s+flask|import\s+flask").unwrap(),
1835 framework: "Flask".to_string(),
1836 features: vec![
1837 "Routes".to_string(),
1838 "Blueprints".to_string(),
1839 "Templates".to_string(),
1840 ],
1841 confidence: 0.9,
1842 },
1843 FrameworkPattern {
1844 name: "FastAPI Framework".to_string(),
1845 pattern: Regex::new(r"from\s+fastapi|import\s+fastapi").unwrap(),
1846 framework: "FastAPI".to_string(),
1847 features: vec![
1848 "API Routes".to_string(),
1849 "Dependency Injection".to_string(),
1850 "Validation".to_string(),
1851 ],
1852 confidence: 0.9,
1853 },
1854 FrameworkPattern {
1855 name: "Pytest Framework".to_string(),
1856 pattern: Regex::new(r"import\s+pytest|@pytest").unwrap(),
1857 framework: "Pytest".to_string(),
1858 features: vec![
1859 "Fixtures".to_string(),
1860 "Parametrization".to_string(),
1861 "Markers".to_string(),
1862 ],
1863 confidence: 0.8,
1864 },
1865 ];
1866 self.framework_patterns
1867 .insert("web_frameworks".to_string(), framework_patterns);
1868
1869 let type_hint_patterns = vec![
1871 TypeHintPattern {
1872 name: "Union Type".to_string(),
1873 pattern: Regex::new(r"Union\[([^]]+)\]").unwrap(),
1874 hint_type: "union".to_string(),
1875 complexity: TypeComplexity::Moderate,
1876 python_version: "3.5+".to_string(),
1877 },
1878 TypeHintPattern {
1879 name: "Union Type (PEP 604)".to_string(),
1880 pattern: Regex::new(r"(\w+)\s*\|\s*(\w+)").unwrap(),
1881 hint_type: "union_new".to_string(),
1882 complexity: TypeComplexity::Moderate,
1883 python_version: "3.10+".to_string(),
1884 },
1885 TypeHintPattern {
1886 name: "Optional Type".to_string(),
1887 pattern: Regex::new(r"Optional\[([^]]+)\]").unwrap(),
1888 hint_type: "optional".to_string(),
1889 complexity: TypeComplexity::Moderate,
1890 python_version: "3.5+".to_string(),
1891 },
1892 TypeHintPattern {
1893 name: "Generic List".to_string(),
1894 pattern: Regex::new(r"List\[([^]]+)\]").unwrap(),
1895 hint_type: "generic_list".to_string(),
1896 complexity: TypeComplexity::Complex,
1897 python_version: "3.5+".to_string(),
1898 },
1899 TypeHintPattern {
1900 name: "Generic Dict".to_string(),
1901 pattern: Regex::new(r"Dict\[([^]]+),\s*([^]]+)\]").unwrap(),
1902 hint_type: "generic_dict".to_string(),
1903 complexity: TypeComplexity::Complex,
1904 python_version: "3.5+".to_string(),
1905 },
1906 TypeHintPattern {
1907 name: "Callable Type".to_string(),
1908 pattern: Regex::new(r"Callable\[\[([^]]*)\],\s*([^]]+)\]").unwrap(),
1909 hint_type: "callable".to_string(),
1910 complexity: TypeComplexity::Advanced,
1911 python_version: "3.5+".to_string(),
1912 },
1913 TypeHintPattern {
1914 name: "TypeVar".to_string(),
1915 pattern: Regex::new(r#"TypeVar\s*\(\s*["'](\w+)["']"#).unwrap(),
1916 hint_type: "typevar".to_string(),
1917 complexity: TypeComplexity::Advanced,
1918 python_version: "3.5+".to_string(),
1919 },
1920 TypeHintPattern {
1921 name: "Protocol".to_string(),
1922 pattern: Regex::new(r"class\s+\w+\s*\([^)]*Protocol[^)]*\)").unwrap(),
1923 hint_type: "protocol".to_string(),
1924 complexity: TypeComplexity::Advanced,
1925 python_version: "3.8+".to_string(),
1926 },
1927 TypeHintPattern {
1928 name: "Literal Type".to_string(),
1929 pattern: Regex::new(r"Literal\[([^]]+)\]").unwrap(),
1930 hint_type: "literal".to_string(),
1931 complexity: TypeComplexity::Moderate,
1932 python_version: "3.8+".to_string(),
1933 },
1934 TypeHintPattern {
1935 name: "Final Type".to_string(),
1936 pattern: Regex::new(r"Final\[([^]]+)\]").unwrap(),
1937 hint_type: "final".to_string(),
1938 complexity: TypeComplexity::Moderate,
1939 python_version: "3.8+".to_string(),
1940 },
1941 TypeHintPattern {
1942 name: "TypedDict".to_string(),
1943 pattern: Regex::new(r"class\s+(\w+)\s*\(\s*TypedDict\s*\)").unwrap(),
1944 hint_type: "typeddict".to_string(),
1945 complexity: TypeComplexity::Complex,
1946 python_version: "3.8+".to_string(),
1947 },
1948 TypeHintPattern {
1949 name: "Generic Alias (Python 3.9+)".to_string(),
1950 pattern: Regex::new(r"\b(list|dict|set|tuple)\s*\[([^]]+)\]").unwrap(),
1951 hint_type: "generic_alias".to_string(),
1952 complexity: TypeComplexity::Simple,
1953 python_version: "3.9+".to_string(),
1954 },
1955 ];
1956 self.type_hint_patterns
1957 .insert("type_hints".to_string(), type_hint_patterns);
1958
1959 let async_patterns = vec![
1961 AsyncPattern {
1962 name: "Async Function".to_string(),
1963 pattern: Regex::new(r"async\s+def\s+(\w+)").unwrap(),
1964 pattern_type: "function".to_string(),
1965 performance_impact: AsyncPerformanceImpact::Positive,
1966 },
1967 AsyncPattern {
1968 name: "Async Generator".to_string(),
1969 pattern: Regex::new(r"async\s+def\s+\w+.*yield").unwrap(),
1970 pattern_type: "generator".to_string(),
1971 performance_impact: AsyncPerformanceImpact::Positive,
1972 },
1973 AsyncPattern {
1974 name: "Async Context Manager".to_string(),
1975 pattern: Regex::new(r"async\s+def\s+__aenter__|async\s+def\s+__aexit__").unwrap(),
1976 pattern_type: "context_manager".to_string(),
1977 performance_impact: AsyncPerformanceImpact::Positive,
1978 },
1979 AsyncPattern {
1980 name: "Async Iterator".to_string(),
1981 pattern: Regex::new(r"async\s+def\s+__aiter__|async\s+def\s+__anext__").unwrap(),
1982 pattern_type: "iterator".to_string(),
1983 performance_impact: AsyncPerformanceImpact::Positive,
1984 },
1985 AsyncPattern {
1986 name: "Await Expression".to_string(),
1987 pattern: Regex::new(r"await\s+(\w+[\w\.\(\)]*)+").unwrap(),
1988 pattern_type: "await".to_string(),
1989 performance_impact: AsyncPerformanceImpact::Neutral,
1990 },
1991 AsyncPattern {
1992 name: "Asyncio Gather".to_string(),
1993 pattern: Regex::new(r"asyncio\.gather\s*\(").unwrap(),
1994 pattern_type: "concurrency".to_string(),
1995 performance_impact: AsyncPerformanceImpact::Positive,
1996 },
1997 AsyncPattern {
1998 name: "Asyncio Wait".to_string(),
1999 pattern: Regex::new(r"asyncio\.wait\s*\(").unwrap(),
2000 pattern_type: "concurrency".to_string(),
2001 performance_impact: AsyncPerformanceImpact::Positive,
2002 },
2003 AsyncPattern {
2004 name: "Asyncio Queue".to_string(),
2005 pattern: Regex::new(r"asyncio\.Queue\s*\(").unwrap(),
2006 pattern_type: "concurrency".to_string(),
2007 performance_impact: AsyncPerformanceImpact::Positive,
2008 },
2009 AsyncPattern {
2010 name: "Asyncio Semaphore".to_string(),
2011 pattern: Regex::new(r"asyncio\.Semaphore\s*\(").unwrap(),
2012 pattern_type: "concurrency".to_string(),
2013 performance_impact: AsyncPerformanceImpact::Positive,
2014 },
2015 AsyncPattern {
2016 name: "Asyncio Lock".to_string(),
2017 pattern: Regex::new(r"asyncio\.Lock\s*\(").unwrap(),
2018 pattern_type: "concurrency".to_string(),
2019 performance_impact: AsyncPerformanceImpact::Positive,
2020 },
2021 AsyncPattern {
2022 name: "TaskGroup".to_string(),
2023 pattern: Regex::new(r"asyncio\.TaskGroup\s*\(").unwrap(),
2024 pattern_type: "concurrency".to_string(),
2025 performance_impact: AsyncPerformanceImpact::Positive,
2026 },
2027 AsyncPattern {
2028 name: "Asyncio Timeout".to_string(),
2029 pattern: Regex::new(r"asyncio\.timeout\s*\(").unwrap(),
2030 pattern_type: "timeout".to_string(),
2031 performance_impact: AsyncPerformanceImpact::Positive,
2032 },
2033 AsyncPattern {
2034 name: "Async With Statement".to_string(),
2035 pattern: Regex::new(r"async\s+with\s+").unwrap(),
2036 pattern_type: "context_manager".to_string(),
2037 performance_impact: AsyncPerformanceImpact::Positive,
2038 },
2039 AsyncPattern {
2040 name: "Async For Loop".to_string(),
2041 pattern: Regex::new(r"async\s+for\s+").unwrap(),
2042 pattern_type: "iterator".to_string(),
2043 performance_impact: AsyncPerformanceImpact::Positive,
2044 },
2045 AsyncPattern {
2046 name: "Blocking IO in Async".to_string(),
2047 pattern: Regex::new(
2048 r"(?:open|input|print)\s*\(.*\).*await|await.*(?:open|input|print)\s*\(",
2049 )
2050 .unwrap(),
2051 pattern_type: "performance_issue".to_string(),
2052 performance_impact: AsyncPerformanceImpact::Critical,
2053 },
2054 ];
2055 self.async_patterns
2056 .insert("functions".to_string(), async_patterns);
2057
2058 let concurrency_patterns = vec![
2059 AsyncPattern {
2060 name: "Concurrent Futures".to_string(),
2061 pattern: Regex::new(r"concurrent\.futures").unwrap(),
2062 pattern_type: "concurrency".to_string(),
2063 performance_impact: AsyncPerformanceImpact::Positive,
2064 },
2065 AsyncPattern {
2066 name: "Asyncio Event".to_string(),
2067 pattern: Regex::new(r"asyncio\.Event\s*\(").unwrap(),
2068 pattern_type: "concurrency".to_string(),
2069 performance_impact: AsyncPerformanceImpact::Positive,
2070 },
2071 AsyncPattern {
2072 name: "Asyncio Condition".to_string(),
2073 pattern: Regex::new(r"asyncio\.Condition\s*\(").unwrap(),
2074 pattern_type: "concurrency".to_string(),
2075 performance_impact: AsyncPerformanceImpact::Positive,
2076 },
2077 ];
2078 self.async_patterns
2079 .insert("concurrency".to_string(), concurrency_patterns);
2080
2081 let modern_async_patterns = vec![
2082 AsyncPattern {
2083 name: "Asyncio Run".to_string(),
2084 pattern: Regex::new(r"asyncio\.run\s*\(").unwrap(),
2085 pattern_type: "modern".to_string(),
2086 performance_impact: AsyncPerformanceImpact::Positive,
2087 },
2088 AsyncPattern {
2089 name: "Context Variables".to_string(),
2090 pattern: Regex::new(r"contextvars\.ContextVar").unwrap(),
2091 pattern_type: "modern".to_string(),
2092 performance_impact: AsyncPerformanceImpact::Positive,
2093 },
2094 AsyncPattern {
2095 name: "Async Comprehension".to_string(),
2096 pattern: Regex::new(r"\[.*async\s+for.*\]|\{.*async\s+for.*\}").unwrap(),
2097 pattern_type: "modern".to_string(),
2098 performance_impact: AsyncPerformanceImpact::Positive,
2099 },
2100 ];
2101 self.async_patterns
2102 .insert("modern".to_string(), modern_async_patterns);
2103
2104 let requirements_patterns = vec![
2106 DependencyPattern {
2107 name: "Requirements.txt".to_string(),
2108 pattern: Regex::new(r"(?m)^([a-zA-Z0-9_-]+)([><=!~]+)?([0-9.]+)?").unwrap(),
2109 file_type: RequirementsFileType::RequirementsTxt,
2110 extraction_method: "line_by_line".to_string(),
2111 },
2112 DependencyPattern {
2113 name: "Pyproject.toml Dependencies".to_string(),
2114 pattern: Regex::new(r#"dependencies\s*=\s*\[(.*?)\]"#).unwrap(),
2115 file_type: RequirementsFileType::PyprojectToml,
2116 extraction_method: "toml_array".to_string(),
2117 },
2118 DependencyPattern {
2119 name: "Setup.py Install Requires".to_string(),
2120 pattern: Regex::new(r"install_requires\s*=\s*\[(.*?)\]").unwrap(),
2121 file_type: RequirementsFileType::SetupPy,
2122 extraction_method: "python_list".to_string(),
2123 },
2124 DependencyPattern {
2125 name: "Pipfile Dependencies".to_string(),
2126 pattern: Regex::new(r"\[packages\](.*?)\[").unwrap(),
2127 file_type: RequirementsFileType::Pipfile,
2128 extraction_method: "toml_section".to_string(),
2129 },
2130 DependencyPattern {
2131 name: "Poetry Lock".to_string(),
2132 pattern: Regex::new(r#"name\s*=\s*"([^"]+)""#).unwrap(),
2133 file_type: RequirementsFileType::PoetryLock,
2134 extraction_method: "toml_blocks".to_string(),
2135 },
2136 DependencyPattern {
2137 name: "Conda Environment".to_string(),
2138 pattern: Regex::new(r"dependencies:\s*\n(.*?)(?:\n\w|$)").unwrap(),
2139 file_type: RequirementsFileType::CondaYml,
2140 extraction_method: "yaml_list".to_string(),
2141 },
2142 ];
2143 self.dependency_patterns
2144 .insert("requirements".to_string(), requirements_patterns);
2145
2146 let import_patterns = vec![
2147 DependencyPattern {
2148 name: "Standard Import".to_string(),
2149 pattern: Regex::new(r"(?m)^import\s+([a-zA-Z_][a-zA-Z0-9_.]*)").unwrap(),
2150 file_type: RequirementsFileType::SetupPy, extraction_method: "import_statement".to_string(),
2152 },
2153 DependencyPattern {
2154 name: "From Import".to_string(),
2155 pattern: Regex::new(r"(?m)^from\s+([a-zA-Z_][a-zA-Z0-9_.]*)\s+import").unwrap(),
2156 file_type: RequirementsFileType::SetupPy, extraction_method: "import_statement".to_string(),
2158 },
2159 DependencyPattern {
2160 name: "Star Import".to_string(),
2161 pattern: Regex::new(r"(?m)^from\s+([a-zA-Z_][a-zA-Z0-9_.]*)\s+import\s+\*")
2162 .unwrap(),
2163 file_type: RequirementsFileType::SetupPy, extraction_method: "import_statement".to_string(),
2165 },
2166 DependencyPattern {
2167 name: "Alias Import".to_string(),
2168 pattern: Regex::new(r"(?m)^import\s+([a-zA-Z_][a-zA-Z0-9_.]*)\s+as\s+").unwrap(),
2169 file_type: RequirementsFileType::SetupPy, extraction_method: "import_statement".to_string(),
2171 },
2172 DependencyPattern {
2173 name: "Relative Import".to_string(),
2174 pattern: Regex::new(r"(?m)^from\s+(\.+)([a-zA-Z_][a-zA-Z0-9_.]*)\s+import")
2175 .unwrap(),
2176 file_type: RequirementsFileType::SetupPy, extraction_method: "import_statement".to_string(),
2178 },
2179 ];
2180 self.dependency_patterns
2181 .insert("imports".to_string(), import_patterns);
2182
2183 let dataclass_patterns = vec![
2185 ModernFeaturePattern {
2186 name: "Dataclass Decorator".to_string(),
2187 pattern: Regex::new(r"@dataclass").unwrap(),
2188 feature_type: "dataclass".to_string(),
2189 python_version: "3.7+".to_string(),
2190 complexity: FeatureComplexity::Moderate,
2191 },
2192 ModernFeaturePattern {
2193 name: "Pydantic Model".to_string(),
2194 pattern: Regex::new(r"class\s+\w+\(BaseModel\)").unwrap(),
2195 feature_type: "dataclass".to_string(),
2196 python_version: "3.6+".to_string(),
2197 complexity: FeatureComplexity::Complex,
2198 },
2199 ModernFeaturePattern {
2200 name: "Named Tuple".to_string(),
2201 pattern: Regex::new(r"NamedTuple|namedtuple").unwrap(),
2202 feature_type: "dataclass".to_string(),
2203 python_version: "3.6+".to_string(),
2204 complexity: FeatureComplexity::Simple,
2205 },
2206 ModernFeaturePattern {
2207 name: "Slots Usage".to_string(),
2208 pattern: Regex::new(r"__slots__\s*=").unwrap(),
2209 feature_type: "dataclass".to_string(),
2210 python_version: "3.0+".to_string(),
2211 complexity: FeatureComplexity::Moderate,
2212 },
2213 ];
2214 self.modern_feature_patterns
2215 .insert("dataclass".to_string(), dataclass_patterns);
2216
2217 let context_manager_patterns = vec![
2218 ModernFeaturePattern {
2219 name: "With Statement".to_string(),
2220 pattern: Regex::new(r"(?m)^(\s*)with\s+").unwrap(),
2221 feature_type: "context_manager".to_string(),
2222 python_version: "2.5+".to_string(),
2223 complexity: FeatureComplexity::Simple,
2224 },
2225 ModernFeaturePattern {
2226 name: "Async With".to_string(),
2227 pattern: Regex::new(r"(?m)^(\s*)async\s+with\s+").unwrap(),
2228 feature_type: "context_manager".to_string(),
2229 python_version: "3.5+".to_string(),
2230 complexity: FeatureComplexity::Complex,
2231 },
2232 ModernFeaturePattern {
2233 name: "Context Manager Protocol".to_string(),
2234 pattern: Regex::new(r"__enter__|__exit__").unwrap(),
2235 feature_type: "context_manager".to_string(),
2236 python_version: "2.5+".to_string(),
2237 complexity: FeatureComplexity::Complex,
2238 },
2239 ModernFeaturePattern {
2240 name: "Contextlib Manager".to_string(),
2241 pattern: Regex::new(r"@contextmanager").unwrap(),
2242 feature_type: "context_manager".to_string(),
2243 python_version: "2.5+".to_string(),
2244 complexity: FeatureComplexity::Moderate,
2245 },
2246 ];
2247 self.modern_feature_patterns
2248 .insert("context_manager".to_string(), context_manager_patterns);
2249
2250 let fstring_patterns = vec![
2251 ModernFeaturePattern {
2252 name: "F-String Basic".to_string(),
2253 pattern: Regex::new(r#"f["'][^"']*\{[^}]+\}[^"']*["']"#).unwrap(),
2254 feature_type: "fstring".to_string(),
2255 python_version: "3.6+".to_string(),
2256 complexity: FeatureComplexity::Simple,
2257 },
2258 ModernFeaturePattern {
2259 name: "F-String Complex".to_string(),
2260 pattern: Regex::new(r#"f["'][^"']*\{[^}]*\([^)]*\)[^}]*\}[^"']*["']"#).unwrap(),
2261 feature_type: "fstring".to_string(),
2262 python_version: "3.6+".to_string(),
2263 complexity: FeatureComplexity::Complex,
2264 },
2265 ModernFeaturePattern {
2266 name: "Raw F-String".to_string(),
2267 pattern: Regex::new(r#"rf["']|fr["']"#).unwrap(),
2268 feature_type: "fstring".to_string(),
2269 python_version: "3.6+".to_string(),
2270 complexity: FeatureComplexity::Moderate,
2271 },
2272 ];
2273 self.modern_feature_patterns
2274 .insert("fstring".to_string(), fstring_patterns);
2275
2276 let pattern_matching_patterns = vec![
2277 ModernFeaturePattern {
2278 name: "Match Statement".to_string(),
2279 pattern: Regex::new(r"(?m)^(\s*)match\s+").unwrap(),
2280 feature_type: "pattern_matching".to_string(),
2281 python_version: "3.10+".to_string(),
2282 complexity: FeatureComplexity::Complex,
2283 },
2284 ModernFeaturePattern {
2285 name: "Case Pattern".to_string(),
2286 pattern: Regex::new(r"(?m)^(\s*)case\s+").unwrap(),
2287 feature_type: "pattern_matching".to_string(),
2288 python_version: "3.10+".to_string(),
2289 complexity: FeatureComplexity::Complex,
2290 },
2291 ModernFeaturePattern {
2292 name: "Guard Pattern".to_string(),
2293 pattern: Regex::new(r"case\s+.*\s+if\s+").unwrap(),
2294 feature_type: "pattern_matching".to_string(),
2295 python_version: "3.10+".to_string(),
2296 complexity: FeatureComplexity::Expert,
2297 },
2298 ];
2299 self.modern_feature_patterns
2300 .insert("pattern_matching".to_string(), pattern_matching_patterns);
2301
2302 let generator_patterns = vec![
2303 ModernFeaturePattern {
2304 name: "Generator Function".to_string(),
2305 pattern: Regex::new(r"def\s+\w+.*?yield").unwrap(),
2306 feature_type: "generator".to_string(),
2307 python_version: "2.2+".to_string(),
2308 complexity: FeatureComplexity::Moderate,
2309 },
2310 ModernFeaturePattern {
2311 name: "Generator Expression".to_string(),
2312 pattern: Regex::new(r"\([^)]*for\s+\w+\s+in\s+[^)]*\)").unwrap(),
2313 feature_type: "generator".to_string(),
2314 python_version: "2.4+".to_string(),
2315 complexity: FeatureComplexity::Simple,
2316 },
2317 ModernFeaturePattern {
2318 name: "Async Generator".to_string(),
2319 pattern: Regex::new(r"async\s+def\s+\w+.*?yield").unwrap(),
2320 feature_type: "generator".to_string(),
2321 python_version: "3.6+".to_string(),
2322 complexity: FeatureComplexity::Expert,
2323 },
2324 ModernFeaturePattern {
2325 name: "Yield From".to_string(),
2326 pattern: Regex::new(r"yield\s+from\s+").unwrap(),
2327 feature_type: "generator".to_string(),
2328 python_version: "3.3+".to_string(),
2329 complexity: FeatureComplexity::Complex,
2330 },
2331 ];
2332 self.modern_feature_patterns
2333 .insert("generator".to_string(), generator_patterns);
2334
2335 let modern_syntax_patterns = vec![
2336 ModernFeaturePattern {
2337 name: "Walrus Operator".to_string(),
2338 pattern: Regex::new(r":=").unwrap(),
2339 feature_type: "modern_syntax".to_string(),
2340 python_version: "3.8+".to_string(),
2341 complexity: FeatureComplexity::Moderate,
2342 },
2343 ModernFeaturePattern {
2344 name: "Positional Only Parameters".to_string(),
2345 pattern: Regex::new(r"def\s+\w+\([^)]*,\s*/\s*[,)]").unwrap(),
2346 feature_type: "modern_syntax".to_string(),
2347 python_version: "3.8+".to_string(),
2348 complexity: FeatureComplexity::Complex,
2349 },
2350 ModernFeaturePattern {
2351 name: "Union Type Operator".to_string(),
2352 pattern: Regex::new(r"\w+\s*\|\s*\w+").unwrap(),
2353 feature_type: "modern_syntax".to_string(),
2354 python_version: "3.10+".to_string(),
2355 complexity: FeatureComplexity::Moderate,
2356 },
2357 ModernFeaturePattern {
2358 name: "Dictionary Union".to_string(),
2359 pattern: Regex::new(r"\w+\s*\|\s*\{").unwrap(),
2360 feature_type: "modern_syntax".to_string(),
2361 python_version: "3.9+".to_string(),
2362 complexity: FeatureComplexity::Simple,
2363 },
2364 ModernFeaturePattern {
2365 name: "Generic Type Hints".to_string(),
2366 pattern: Regex::new(r"list\[|dict\[|set\[|tuple\[").unwrap(),
2367 feature_type: "modern_syntax".to_string(),
2368 python_version: "3.9+".to_string(),
2369 complexity: FeatureComplexity::Moderate,
2370 },
2371 ];
2372 self.modern_feature_patterns
2373 .insert("modern_syntax".to_string(), modern_syntax_patterns);
2374 }
2375
2376 pub fn analyze_decorators(&self, content: &str) -> Result<Vec<DecoratorInfo>> {
2378 let mut decorators = Vec::new();
2379
2380 for (category, patterns) in &self.decorator_patterns {
2381 for pattern in patterns {
2382 for captures in pattern.pattern.captures_iter(content) {
2383 let full_match = captures.get(0).unwrap().as_str();
2384
2385 decorators.push(DecoratorInfo {
2386 name: pattern.name.clone(),
2387 decorator_type: category.clone(),
2388 framework: pattern.framework.clone(),
2389 effects: pattern.effects.clone(),
2390 is_factory: pattern.is_factory,
2391 parameters: self.extract_decorator_parameters(full_match),
2392 });
2393 }
2394 }
2395 }
2396
2397 Ok(decorators)
2398 }
2399
2400 pub fn analyze_metaclasses(&self, content: &str) -> Result<Vec<MetaclassInfo>> {
2402 let mut metaclasses = Vec::new();
2403
2404 for (category, patterns) in &self.metaclass_patterns {
2405 for pattern in patterns {
2406 for captures in pattern.pattern.captures_iter(content) {
2407 let full_match = captures.get(0).unwrap().as_str();
2408 let class_name = self.extract_class_name(full_match);
2409
2410 metaclasses.push(MetaclassInfo {
2411 name: class_name,
2412 metaclass_type: category.clone(),
2413 impact: pattern.impact.clone(),
2414 attributes_modified: self.find_modified_attributes(content, full_match),
2415 methods_modified: self.find_modified_methods(content, full_match),
2416 });
2417 }
2418 }
2419 }
2420
2421 Ok(metaclasses)
2422 }
2423
2424 pub fn analyze_inheritance(&self, content: &str) -> Result<Vec<InheritanceInfo>> {
2426 let mut inheritance_info = Vec::new();
2427
2428 let class_pattern = Regex::new(r"class\s+(\w+)\s*\(([^)]*)\)").unwrap();
2429
2430 for captures in class_pattern.captures_iter(content) {
2431 let class_name = captures.get(1).unwrap().as_str().to_string();
2432 let bases_str = captures.get(2).unwrap().as_str();
2433
2434 let base_classes = self.parse_base_classes(bases_str);
2435 let mro = self.calculate_mro(&class_name, &base_classes);
2436 let has_diamond = self.detect_diamond_inheritance(&base_classes);
2437 let mixins = self.identify_mixins(&base_classes);
2438 let metaclass = self.extract_metaclass(bases_str);
2439
2440 inheritance_info.push(InheritanceInfo {
2441 class_name,
2442 base_classes,
2443 mro,
2444 has_diamond_inheritance: has_diamond,
2445 mixins,
2446 metaclass,
2447 });
2448 }
2449
2450 Ok(inheritance_info)
2451 }
2452
2453 fn extract_decorator_parameters(&self, decorator: &str) -> Vec<String> {
2455 let param_pattern = Regex::new(r"\(([^)]*)\)").unwrap();
2456
2457 if let Some(captures) = param_pattern.captures(decorator) {
2458 let params_str = captures.get(1).unwrap().as_str();
2459 params_str
2460 .split(',')
2461 .map(|p| p.trim().to_string())
2462 .filter(|p| !p.is_empty())
2463 .collect()
2464 } else {
2465 Vec::new()
2466 }
2467 }
2468
2469 fn extract_class_name(&self, class_def: &str) -> String {
2471 let name_pattern = Regex::new(r"class\s+(\w+)").unwrap();
2472
2473 if let Some(captures) = name_pattern.captures(class_def) {
2474 captures.get(1).unwrap().as_str().to_string()
2475 } else {
2476 "Unknown".to_string()
2477 }
2478 }
2479
2480 fn find_modified_attributes(&self, _content: &str, _class_def: &str) -> Vec<String> {
2482 vec!["__new__".to_string(), "__init__".to_string()]
2484 }
2485
2486 fn find_modified_methods(&self, _content: &str, _class_def: &str) -> Vec<String> {
2488 vec!["__call__".to_string()]
2490 }
2491
2492 fn parse_base_classes(&self, bases_str: &str) -> Vec<String> {
2494 bases_str
2495 .split(',')
2496 .map(|base| {
2497 let clean_base = base.split('=').next().unwrap_or(base).trim();
2499 clean_base.to_string()
2500 })
2501 .filter(|base| !base.is_empty() && !base.contains("metaclass"))
2502 .collect()
2503 }
2504
2505 fn calculate_mro(&self, class_name: &str, base_classes: &[String]) -> Vec<String> {
2507 let mut mro = vec![class_name.to_string()];
2508 mro.extend(base_classes.iter().cloned());
2509 mro.push("object".to_string());
2510 mro
2511 }
2512
2513 fn detect_diamond_inheritance(&self, base_classes: &[String]) -> bool {
2515 base_classes.len() > 1
2517 }
2518
2519 fn identify_mixins(&self, base_classes: &[String]) -> Vec<String> {
2521 base_classes
2522 .iter()
2523 .filter(|base| base.ends_with("Mixin") || base.ends_with("Mix"))
2524 .cloned()
2525 .collect()
2526 }
2527
2528 fn extract_metaclass(&self, bases_str: &str) -> Option<String> {
2530 let metaclass_pattern = Regex::new(r"metaclass\s*=\s*(\w+)").unwrap();
2531
2532 metaclass_pattern
2533 .captures(bases_str)
2534 .map(|captures| captures.get(1).unwrap().as_str().to_string())
2535 }
2536
2537 pub fn analyze_security(&self, content: &str) -> Result<PythonSecurityAssessment> {
2539 let mut vulnerabilities = Vec::new();
2540 let mut security_features = Vec::new();
2541
2542 for patterns in self.security_patterns.values() {
2544 for pattern in patterns {
2545 for captures in pattern.pattern.captures_iter(content) {
2546 let full_match = captures.get(0).unwrap().as_str();
2547
2548 vulnerabilities.push(SecurityVulnerability {
2549 vulnerability_type: pattern.vulnerability_type.clone(),
2550 severity: pattern.severity.clone(),
2551 description: pattern.description.clone(),
2552 location: full_match.to_string(),
2553 recommendation: self
2554 .get_security_recommendation(&pattern.vulnerability_type),
2555 });
2556 }
2557 }
2558 }
2559
2560 if content.contains("bcrypt") || content.contains("hashlib") {
2562 security_features.push(SecurityFeature {
2563 feature_type: SecurityFeatureType::DataEncryption,
2564 implementation_quality: ImplementationQuality::Good,
2565 description: "Password hashing implementation detected".to_string(),
2566 });
2567 }
2568
2569 if content.contains("@csrf_exempt") || content.contains("CsrfViewMiddleware") {
2570 security_features.push(SecurityFeature {
2571 feature_type: SecurityFeatureType::CsrfProtection,
2572 implementation_quality: ImplementationQuality::Good,
2573 description: "CSRF protection implementation detected".to_string(),
2574 });
2575 }
2576
2577 if content.contains("pydantic") || content.contains("marshmallow") {
2578 security_features.push(SecurityFeature {
2579 feature_type: SecurityFeatureType::InputValidation,
2580 implementation_quality: ImplementationQuality::Good,
2581 description: "Input validation framework detected".to_string(),
2582 });
2583 }
2584
2585 let level = self.determine_security_level(&vulnerabilities, &security_features);
2587 let recommendations =
2588 self.get_security_recommendations(&vulnerabilities, &security_features);
2589
2590 Ok(PythonSecurityAssessment {
2591 level,
2592 vulnerabilities_detected: vulnerabilities,
2593 security_features,
2594 recommendations,
2595 })
2596 }
2597
2598 pub fn analyze_performance(&self, content: &str) -> Result<PythonPerformanceAnalysis> {
2600 let mut optimizations = Vec::new();
2601 let mut issues = Vec::new();
2602
2603 for patterns in self.performance_patterns.values() {
2605 for pattern in patterns {
2606 for _captures in pattern.pattern.captures_iter(content) {
2607 optimizations.push(PerformanceOptimization {
2608 optimization_type: pattern.optimization_type.clone(),
2609 impact_level: pattern.impact_level.clone(),
2610 description: pattern.description.clone(),
2611 best_practices_followed: true,
2612 });
2613 }
2614 }
2615 }
2616
2617 if content.contains("for") && content.contains("for") && content.matches("for").count() > 1
2619 {
2620 issues.push(PerformanceIssue {
2621 issue_type: PerformanceIssueType::InEfficientLoops,
2622 severity: IssueSeverity::Medium,
2623 description: "Nested loops detected - consider optimization".to_string(),
2624 recommendation: "Use list comprehensions or optimize algorithm".to_string(),
2625 });
2626 }
2627
2628 if content.contains("def __del__") {
2629 issues.push(PerformanceIssue {
2630 issue_type: PerformanceIssueType::MemoryLeaks,
2631 severity: IssueSeverity::High,
2632 description: "Manual destructor detected - potential memory management issue"
2633 .to_string(),
2634 recommendation: "Use context managers or weak references".to_string(),
2635 });
2636 }
2637
2638 let overall_score = self.calculate_performance_score(&optimizations, &issues);
2640 let recommendations = self.get_performance_recommendations(&optimizations, &issues);
2641
2642 Ok(PythonPerformanceAnalysis {
2643 overall_score,
2644 optimizations_detected: optimizations,
2645 performance_issues: issues,
2646 recommendations,
2647 })
2648 }
2649
2650 pub fn analyze_frameworks(&self, content: &str) -> Result<Vec<PythonFrameworkInfo>> {
2652 let mut frameworks = Vec::new();
2653
2654 for patterns in self.framework_patterns.values() {
2655 for pattern in patterns {
2656 if pattern.pattern.is_match(content) {
2657 let framework_specific_analysis = match pattern.framework.as_str() {
2658 "Django" => FrameworkSpecificAnalysis::Django(
2659 self.analyze_django_specifics(content),
2660 ),
2661 "Flask" => {
2662 FrameworkSpecificAnalysis::Flask(self.analyze_flask_specifics(content))
2663 }
2664 "FastAPI" => FrameworkSpecificAnalysis::FastAPI(
2665 self.analyze_fastapi_specifics(content),
2666 ),
2667 "Pytest" => FrameworkSpecificAnalysis::Pytest(
2668 self.analyze_pytest_specifics(content),
2669 ),
2670 _ => continue,
2671 };
2672
2673 frameworks.push(PythonFrameworkInfo {
2674 name: pattern.framework.clone(),
2675 confidence: pattern.confidence,
2676 version_detected: None, features_used: pattern.features.clone(),
2678 best_practices: self.get_framework_best_practices(&pattern.framework),
2679 framework_specific_analysis,
2680 });
2681 }
2682 }
2683 }
2684
2685 Ok(frameworks)
2686 }
2687
2688 pub fn analyze_type_hints(&self, content: &str) -> Result<PythonTypeHintAnalysis> {
2690 let mut type_hints_detected = Vec::new();
2691 let mut type_safety_issues = Vec::new();
2692 let mut modern_type_features = Vec::new();
2693
2694 for patterns in self.type_hint_patterns.values() {
2696 for pattern in patterns {
2697 for captures in pattern.pattern.captures_iter(content) {
2698 let full_match = captures.get(0).unwrap().as_str();
2699
2700 let hint_type = self.parse_type_hint_type(&pattern.hint_type, &captures);
2701 let is_generic = self.is_generic_type(&pattern.hint_type);
2702 let has_constraints = self.has_type_constraints(&pattern.hint_type);
2703
2704 type_hints_detected.push(TypeHintInfo {
2705 location: full_match.to_string(),
2706 hint_type,
2707 complexity: pattern.complexity.clone(),
2708 is_generic,
2709 has_constraints,
2710 python_version_required: pattern.python_version.clone(),
2711 });
2712
2713 if pattern.python_version.starts_with("3.8")
2715 || pattern.python_version.starts_with("3.9")
2716 || pattern.python_version.starts_with("3.10")
2717 {
2718 let feature_type = self.get_modern_feature_type(&pattern.hint_type);
2719 if let Some(feature_type) = feature_type {
2720 modern_type_features.push(ModernTypeFeature {
2721 feature_type,
2722 python_version: pattern.python_version.clone(),
2723 usage_count: 1,
2724 description: format!(
2725 "Modern type feature: {name}",
2726 name = pattern.name
2727 ),
2728 is_best_practice: true,
2729 });
2730 }
2731 }
2732 }
2733 }
2734 }
2735
2736 self.detect_type_safety_issues(content, &mut type_safety_issues);
2738
2739 let overall_coverage = self.calculate_type_coverage(content, &type_hints_detected);
2741 let type_coverage_score = self.get_coverage_score(overall_coverage);
2742
2743 let recommendations = self.get_type_hint_recommendations(
2745 &type_hints_detected,
2746 &type_safety_issues,
2747 overall_coverage,
2748 );
2749
2750 Ok(PythonTypeHintAnalysis {
2751 overall_coverage,
2752 type_coverage_score,
2753 type_hints_detected,
2754 type_safety_issues,
2755 modern_type_features,
2756 recommendations,
2757 })
2758 }
2759
2760 pub fn analyze_async_await(&self, content: &str) -> Result<PythonAsyncAwaitAnalysis> {
2762 let mut async_functions_detected = Vec::new();
2763 let mut await_usage_patterns = Vec::new();
2764 let mut concurrency_patterns = Vec::new();
2765 let mut async_performance_issues = Vec::new();
2766 let mut async_security_issues = Vec::new();
2767 let mut modern_async_features = Vec::new();
2768
2769 self.analyze_async_functions(content, &mut async_functions_detected);
2771
2772 self.analyze_await_usage(content, &mut await_usage_patterns);
2774
2775 self.analyze_concurrency_patterns(content, &mut concurrency_patterns);
2777
2778 self.detect_async_performance_issues(content, &mut async_performance_issues);
2780
2781 self.detect_async_security_issues(content, &mut async_security_issues);
2783
2784 self.detect_modern_async_features(content, &mut modern_async_features);
2786
2787 let overall_async_score = self.calculate_async_score(
2789 &async_functions_detected,
2790 &concurrency_patterns,
2791 &async_performance_issues,
2792 &async_security_issues,
2793 );
2794
2795 let recommendations = self.get_async_recommendations(
2797 &async_functions_detected,
2798 &await_usage_patterns,
2799 &concurrency_patterns,
2800 &async_performance_issues,
2801 &async_security_issues,
2802 );
2803
2804 Ok(PythonAsyncAwaitAnalysis {
2805 overall_async_score,
2806 async_functions_detected,
2807 await_usage_patterns,
2808 concurrency_patterns,
2809 async_performance_issues,
2810 async_security_issues,
2811 modern_async_features,
2812 recommendations,
2813 })
2814 }
2815
2816 pub fn analyze_package_dependencies(
2818 &self,
2819 content: &str,
2820 ) -> Result<PythonPackageDependencyAnalysis> {
2821 let mut requirements_files = Vec::new();
2822 let mut dependencies = Vec::new();
2823 let mut dependency_issues = Vec::new();
2824 let mut virtual_environments = Vec::new();
2825 let mut import_analysis = Vec::new();
2826 let mut security_vulnerabilities = Vec::new();
2827 let mut license_analysis = Vec::new();
2828
2829 self.analyze_requirements_files(content, &mut requirements_files, &mut dependencies);
2831
2832 self.analyze_imports(content, &mut import_analysis);
2834
2835 self.detect_dependency_issues(
2837 content,
2838 &dependencies,
2839 &import_analysis,
2840 &mut dependency_issues,
2841 );
2842
2843 self.detect_virtual_environments(content, &mut virtual_environments);
2845
2846 self.scan_security_vulnerabilities(&dependencies, &mut security_vulnerabilities);
2848
2849 self.analyze_licenses(&dependencies, &mut license_analysis);
2851
2852 let overall_health_score = self.calculate_dependency_health_score(
2854 &requirements_files,
2855 &dependencies,
2856 &dependency_issues,
2857 &security_vulnerabilities,
2858 );
2859
2860 let recommendations = self.get_dependency_recommendations(
2862 &requirements_files,
2863 &dependencies,
2864 &dependency_issues,
2865 &import_analysis,
2866 &security_vulnerabilities,
2867 );
2868
2869 Ok(PythonPackageDependencyAnalysis {
2870 overall_health_score,
2871 requirements_files,
2872 dependencies,
2873 dependency_issues,
2874 virtual_environments,
2875 import_analysis,
2876 security_vulnerabilities,
2877 license_analysis,
2878 recommendations,
2879 })
2880 }
2881
2882 fn analyze_requirements_files(
2884 &self,
2885 content: &str,
2886 requirements_files: &mut Vec<RequirementsFileInfo>,
2887 dependencies: &mut Vec<RequirementInfo>,
2888 ) {
2889 for patterns in self.dependency_patterns.values() {
2890 for pattern in patterns {
2891 if pattern.extraction_method == "line_by_line" {
2892 for line in content.lines() {
2894 if let Some(captures) = pattern.pattern.captures(line) {
2895 let package_name = captures.get(1).unwrap().as_str();
2896 let version_spec = captures
2897 .get(2)
2898 .and_then(|m| {
2899 captures.get(3).map(|v| {
2900 let m_str = m.as_str();
2901 let v_str = v.as_str();
2902 format!("{m_str}{v_str}")
2903 })
2904 })
2905 .unwrap_or_else(|| "*".to_string());
2906
2907 dependencies.push(RequirementInfo {
2908 name: package_name.to_string(),
2909 version_spec,
2910 source: RequirementSource::PyPI,
2911 is_dev_dependency: false,
2912 is_optional: false,
2913 extras: Vec::new(),
2914 markers: Vec::new(),
2915 metadata: self.get_package_metadata(package_name),
2916 });
2917 }
2918 }
2919
2920 if !dependencies.is_empty() {
2921 requirements_files.push(RequirementsFileInfo {
2922 file_path: "requirements.txt".to_string(),
2923 file_type: RequirementsFileType::RequirementsTxt,
2924 dependencies_count: dependencies.len(),
2925 has_version_pins: dependencies.iter().any(|d| d.version_spec != "*"),
2926 has_hashes: false,
2927 uses_constraints: false,
2928 quality_score: self.assess_requirements_quality(dependencies),
2929 });
2930 }
2931 } else if pattern.extraction_method == "toml_array"
2932 && content.contains("pyproject.toml")
2933 {
2934 if let Some(captures) = pattern.pattern.captures(content) {
2936 let deps_str = captures.get(1).unwrap().as_str();
2937 for dep in deps_str.split(',') {
2938 let clean_dep = dep.trim().trim_matches('"').trim_matches('\'');
2939 if !clean_dep.is_empty() {
2940 let parts: Vec<&str> =
2941 clean_dep.split(['>', '<', '=', '!', '~']).collect();
2942 let package_name = parts[0].trim();
2943 let version_spec = if parts.len() > 1 {
2944 clean_dep[package_name.len()..].to_string()
2945 } else {
2946 "*".to_string()
2947 };
2948
2949 dependencies.push(RequirementInfo {
2950 name: package_name.to_string(),
2951 version_spec,
2952 source: RequirementSource::PyPI,
2953 is_dev_dependency: false,
2954 is_optional: false,
2955 extras: Vec::new(),
2956 markers: Vec::new(),
2957 metadata: self.get_package_metadata(package_name),
2958 });
2959 }
2960 }
2961
2962 requirements_files.push(RequirementsFileInfo {
2963 file_path: "pyproject.toml".to_string(),
2964 file_type: RequirementsFileType::PyprojectToml,
2965 dependencies_count: dependencies.len(),
2966 has_version_pins: dependencies.iter().any(|d| d.version_spec != "*"),
2967 has_hashes: false,
2968 uses_constraints: false,
2969 quality_score: self.assess_requirements_quality(dependencies),
2970 });
2971 }
2972 }
2973 }
2974 }
2975 }
2976
2977 fn analyze_imports(&self, content: &str, import_analysis: &mut Vec<ImportAnalysisInfo>) {
2979 if let Some(import_patterns) = self.dependency_patterns.get("imports") {
2980 for pattern in import_patterns {
2981 for captures in pattern.pattern.captures_iter(content) {
2982 let module_name = if pattern.name == "Relative Import" && captures.len() > 2 {
2983 format!(
2984 "{}{}",
2985 captures.get(1).unwrap().as_str(),
2986 captures.get(2).unwrap().as_str()
2987 )
2988 } else {
2989 captures.get(1).unwrap().as_str().to_string()
2990 };
2991
2992 let import_type = self.classify_import_type(&pattern.name);
2993 let module_category = self.categorize_module(&module_name);
2994 let usage_count = content.matches(&module_name).count();
2995 let is_unused = usage_count <= 1; let import_issues =
2997 self.detect_import_issues(&pattern.name, &module_name, content);
2998
2999 import_analysis.push(ImportAnalysisInfo {
3000 import_statement: captures.get(0).unwrap().as_str().to_string(),
3001 import_type,
3002 module_category,
3003 usage_count,
3004 is_unused,
3005 import_issues,
3006 optimization_suggestions: self
3007 .get_import_optimization_suggestions(&pattern.name, &module_name),
3008 });
3009 }
3010 }
3011 }
3012 }
3013
3014 fn detect_dependency_issues(
3016 &self,
3017 _content: &str,
3018 dependencies: &[RequirementInfo],
3019 import_analysis: &[ImportAnalysisInfo],
3020 issues: &mut Vec<DependencyIssue>,
3021 ) {
3022 for dep in dependencies {
3024 let is_imported = import_analysis.iter().any(|imp| {
3025 imp.import_statement.contains(&dep.name)
3026 || imp.import_statement.contains(&dep.name.replace("-", "_"))
3027 });
3028
3029 if !is_imported {
3030 issues.push(DependencyIssue {
3031 issue_type: DependencyIssueType::UnusedDependency,
3032 severity: DependencyIssueSeverity::Low,
3033 affected_packages: vec![dep.name.clone()],
3034 description: format!(
3035 "Dependency '{name}' declared but not imported",
3036 name = dep.name
3037 ),
3038 recommendation: "Remove unused dependency or add import statement".to_string(),
3039 auto_fixable: false,
3040 });
3041 }
3042 }
3043
3044 for import in import_analysis {
3046 if matches!(import.module_category, ModuleCategory::ThirdParty) {
3047 let module_root = import
3048 .import_statement
3049 .split_whitespace()
3050 .nth(1)
3051 .unwrap_or("")
3052 .split('.')
3053 .next()
3054 .unwrap_or("");
3055
3056 let is_declared = dependencies.iter().any(|dep| {
3057 dep.name == module_root || dep.name.replace("-", "_") == module_root
3058 });
3059
3060 if !is_declared && !module_root.is_empty() {
3061 issues.push(DependencyIssue {
3062 issue_type: DependencyIssueType::MissingDependency,
3063 severity: DependencyIssueSeverity::High,
3064 affected_packages: vec![module_root.to_string()],
3065 description: format!(
3066 "Module '{module_root}' imported but not declared as dependency"
3067 ),
3068 recommendation: "Add missing dependency to requirements".to_string(),
3069 auto_fixable: true,
3070 });
3071 }
3072 }
3073 }
3074
3075 for dep in dependencies {
3077 if dep.version_spec == "*" || dep.version_spec.is_empty() {
3078 issues.push(DependencyIssue {
3079 issue_type: DependencyIssueType::UnpinnedVersion,
3080 severity: DependencyIssueSeverity::Medium,
3081 affected_packages: vec![dep.name.clone()],
3082 description: format!(
3083 "Dependency '{name}' has no version constraint",
3084 name = dep.name
3085 ),
3086 recommendation: "Pin dependency versions for reproducible builds".to_string(),
3087 auto_fixable: false,
3088 });
3089 }
3090 }
3091
3092 let deprecated_packages = ["imp", "optparse", "platform", "distutils"];
3094 for dep in dependencies {
3095 if deprecated_packages.contains(&dep.name.as_str()) {
3096 issues.push(DependencyIssue {
3097 issue_type: DependencyIssueType::DeprecatedPackage,
3098 severity: DependencyIssueSeverity::Medium,
3099 affected_packages: vec![dep.name.clone()],
3100 description: format!("Package '{name}' is deprecated", name = dep.name),
3101 recommendation: "Consider migrating to modern alternatives".to_string(),
3102 auto_fixable: false,
3103 });
3104 }
3105 }
3106 }
3107
3108 fn detect_virtual_environments(
3110 &self,
3111 content: &str,
3112 virtual_environments: &mut Vec<VirtualEnvironmentInfo>,
3113 ) {
3114 if content.contains("venv") || content.contains("virtualenv") {
3116 virtual_environments.push(VirtualEnvironmentInfo {
3117 env_type: VirtualEnvironmentType::Venv,
3118 location: "./venv".to_string(),
3119 python_version: "3.x".to_string(),
3120 is_active: true,
3121 packages_count: 0,
3122 env_variables: Vec::new(),
3123 configuration: VirtualEnvironmentConfig {
3124 isolated: true,
3125 system_site_packages: false,
3126 pip_version: None,
3127 setuptools_version: None,
3128 custom_configurations: Vec::new(),
3129 },
3130 });
3131 }
3132
3133 if content.contains("conda") || content.contains("environment.yml") {
3134 virtual_environments.push(VirtualEnvironmentInfo {
3135 env_type: VirtualEnvironmentType::Conda,
3136 location: "conda environment".to_string(),
3137 python_version: "3.x".to_string(),
3138 is_active: true,
3139 packages_count: 0,
3140 env_variables: Vec::new(),
3141 configuration: VirtualEnvironmentConfig {
3142 isolated: true,
3143 system_site_packages: false,
3144 pip_version: None,
3145 setuptools_version: None,
3146 custom_configurations: Vec::new(),
3147 },
3148 });
3149 }
3150
3151 if content.contains("pipenv") || content.contains("Pipfile") {
3152 virtual_environments.push(VirtualEnvironmentInfo {
3153 env_type: VirtualEnvironmentType::Pipenv,
3154 location: "pipenv environment".to_string(),
3155 python_version: "3.x".to_string(),
3156 is_active: true,
3157 packages_count: 0,
3158 env_variables: Vec::new(),
3159 configuration: VirtualEnvironmentConfig {
3160 isolated: true,
3161 system_site_packages: false,
3162 pip_version: None,
3163 setuptools_version: None,
3164 custom_configurations: Vec::new(),
3165 },
3166 });
3167 }
3168 }
3169
3170 fn scan_security_vulnerabilities(
3172 &self,
3173 dependencies: &[RequirementInfo],
3174 vulnerabilities: &mut Vec<SecurityVulnerabilityInfo>,
3175 ) {
3176 let known_vulnerabilities = vec![
3178 ("urllib3", "1.25.8", "CVE-2020-26137", "Critical"),
3179 ("requests", "2.19.1", "CVE-2018-18074", "High"),
3180 ("pyyaml", "5.3.1", "CVE-2020-14343", "High"),
3181 ("django", "2.2.12", "CVE-2020-13254", "Medium"),
3182 ("flask", "1.1.1", "CVE-2019-1010083", "Medium"),
3183 ];
3184
3185 for dep in dependencies {
3186 for (vuln_package, vuln_version, cve_id, severity) in &known_vulnerabilities {
3187 if dep.name == *vuln_package {
3188 let severity_enum = match *severity {
3189 "Critical" => SecurityVulnerabilitySeverity::Critical,
3190 "High" => SecurityVulnerabilitySeverity::High,
3191 "Medium" => SecurityVulnerabilitySeverity::Medium,
3192 _ => SecurityVulnerabilitySeverity::Low,
3193 };
3194
3195 vulnerabilities.push(SecurityVulnerabilityInfo {
3196 cve_id: Some(cve_id.to_string()),
3197 advisory_id: None,
3198 package_name: dep.name.clone(),
3199 affected_versions: vec![vuln_version.to_string()],
3200 fixed_version: Some("Latest".to_string()),
3201 severity: severity_enum,
3202 vulnerability_type: VulnerabilityCategory::CodeExecution,
3203 description: format!(
3204 "Security vulnerability in {vuln_package} {vuln_version}"
3205 ),
3206 references: vec![format!(
3207 "https://cve.mitre.org/cgi-bin/cvename.cgi?name={cve_id}"
3208 )],
3209 published_date: Some("2020-01-01".to_string()),
3210 last_modified: Some("2020-01-01".to_string()),
3211 });
3212 }
3213 }
3214 }
3215 }
3216
3217 fn analyze_licenses(
3219 &self,
3220 dependencies: &[RequirementInfo],
3221 license_analysis: &mut Vec<LicenseInfo>,
3222 ) {
3223 for dep in dependencies {
3224 let license_type = self.parse_license_type(&dep.metadata.license);
3226 let compatibility = self.assess_license_compatibility(&license_type);
3227
3228 license_analysis.push(LicenseInfo {
3229 package_name: dep.name.clone(),
3230 license_type: license_type.clone(),
3231 license_text: None,
3232 compatibility,
3233 commercial_use_allowed: self.is_commercial_use_allowed(&license_type),
3234 distribution_allowed: self.is_distribution_allowed(&license_type),
3235 modification_allowed: self.is_modification_allowed(&license_type),
3236 patent_grant: self.has_patent_grant(&license_type),
3237 copyleft: self.is_copyleft(&license_type),
3238 });
3239 }
3240 }
3241
3242 fn get_package_metadata(&self, package_name: &str) -> PackageMetadata {
3244 PackageMetadata {
3246 description: format!("Package: {package_name}"),
3247 author: "Unknown".to_string(),
3248 license: "MIT".to_string(),
3249 homepage: None,
3250 documentation: None,
3251 last_updated: None,
3252 download_count: None,
3253 maintenance_status: MaintenanceStatus::Unknown,
3254 }
3255 }
3256
3257 fn assess_requirements_quality(
3258 &self,
3259 dependencies: &[RequirementInfo],
3260 ) -> DependencyQualityScore {
3261 let pinned_count = dependencies
3262 .iter()
3263 .filter(|d| d.version_spec != "*")
3264 .count();
3265 let pinned_ratio = if dependencies.is_empty() {
3266 0.0
3267 } else {
3268 pinned_count as f32 / dependencies.len() as f32
3269 };
3270
3271 match pinned_ratio {
3272 r if r >= 0.9 => DependencyQualityScore::Excellent,
3273 r if r >= 0.7 => DependencyQualityScore::Good,
3274 r if r >= 0.5 => DependencyQualityScore::Fair,
3275 r if r >= 0.3 => DependencyQualityScore::Poor,
3276 _ => DependencyQualityScore::Critical,
3277 }
3278 }
3279
3280 fn classify_import_type(&self, pattern_name: &str) -> ImportType {
3281 match pattern_name {
3282 "Standard Import" => ImportType::StandardImport,
3283 "From Import" => ImportType::FromImport,
3284 "Star Import" => ImportType::StarImport,
3285 "Alias Import" => ImportType::AliasImport,
3286 "Relative Import" => ImportType::RelativeImport,
3287 _ => ImportType::StandardImport,
3288 }
3289 }
3290
3291 fn categorize_module(&self, module_name: &str) -> ModuleCategory {
3292 let stdlib_modules = vec![
3294 "os",
3295 "sys",
3296 "re",
3297 "json",
3298 "urllib",
3299 "http",
3300 "datetime",
3301 "collections",
3302 "itertools",
3303 "functools",
3304 "pathlib",
3305 "typing",
3306 "asyncio",
3307 "threading",
3308 "multiprocessing",
3309 "subprocess",
3310 "logging",
3311 "unittest",
3312 "sqlite3",
3313 ];
3314
3315 let root_module = module_name.split('.').next().unwrap_or(module_name);
3316
3317 if stdlib_modules.contains(&root_module) {
3318 ModuleCategory::StandardLibrary
3319 } else if root_module.starts_with('.') {
3320 ModuleCategory::Local
3321 } else {
3322 ModuleCategory::ThirdParty
3323 }
3324 }
3325
3326 fn detect_import_issues(
3327 &self,
3328 pattern_name: &str,
3329 module_name: &str,
3330 content: &str,
3331 ) -> Vec<ImportIssue> {
3332 let mut issues = Vec::new();
3333
3334 if pattern_name == "Star Import" {
3335 issues.push(ImportIssue::StarImportDangerous);
3336 }
3337
3338 if content.contains(&format!("from {module_name} import"))
3340 && content.contains(&format!("import {module_name}"))
3341 {
3342 issues.push(ImportIssue::CircularImport);
3343 }
3344
3345 let deprecated_modules = ["imp", "optparse", "platform.dist"];
3347 if deprecated_modules
3348 .iter()
3349 .any(|&dep| module_name.contains(dep))
3350 {
3351 issues.push(ImportIssue::DeprecatedImport);
3352 }
3353
3354 issues
3355 }
3356
3357 fn get_import_optimization_suggestions(
3358 &self,
3359 pattern_name: &str,
3360 module_name: &str,
3361 ) -> Vec<String> {
3362 let mut suggestions = Vec::new();
3363
3364 if pattern_name == "Star Import" {
3365 suggestions.push("Replace star import with specific imports".to_string());
3366 }
3367
3368 if module_name == "pandas" || module_name == "numpy" {
3369 suggestions.push("Consider lazy loading for large libraries".to_string());
3370 }
3371
3372 suggestions
3373 }
3374
3375 fn parse_license_type(&self, license_str: &str) -> LicenseType {
3376 match license_str.to_lowercase().as_str() {
3377 "mit" => LicenseType::MIT,
3378 "apache-2.0" | "apache 2.0" => LicenseType::Apache2,
3379 "gpl-2.0" | "gpl v2" => LicenseType::GPL2,
3380 "gpl-3.0" | "gpl v3" => LicenseType::GPL3,
3381 "bsd-2-clause" => LicenseType::BSD2Clause,
3382 "bsd-3-clause" => LicenseType::BSD3Clause,
3383 "lgpl" => LicenseType::LGPL,
3384 "mozilla" | "mpl-2.0" => LicenseType::Mozilla,
3385 "unlicense" => LicenseType::Unlicense,
3386 _ => LicenseType::Unknown,
3387 }
3388 }
3389
3390 fn assess_license_compatibility(&self, license_type: &LicenseType) -> LicenseCompatibility {
3391 match license_type {
3392 LicenseType::MIT
3393 | LicenseType::Apache2
3394 | LicenseType::BSD2Clause
3395 | LicenseType::BSD3Clause => LicenseCompatibility::Compatible,
3396 LicenseType::GPL2 | LicenseType::GPL3 => LicenseCompatibility::RequiresReview,
3397 LicenseType::LGPL => LicenseCompatibility::ConditionallyCompatible,
3398 _ => LicenseCompatibility::Unknown,
3399 }
3400 }
3401
3402 fn is_commercial_use_allowed(&self, license_type: &LicenseType) -> bool {
3403 !matches!(license_type, LicenseType::GPL2 | LicenseType::GPL3)
3404 }
3405
3406 fn is_distribution_allowed(&self, license_type: &LicenseType) -> bool {
3407 !matches!(license_type, LicenseType::Proprietary)
3408 }
3409
3410 fn is_modification_allowed(&self, license_type: &LicenseType) -> bool {
3411 !matches!(license_type, LicenseType::Proprietary)
3412 }
3413
3414 fn has_patent_grant(&self, license_type: &LicenseType) -> bool {
3415 matches!(license_type, LicenseType::Apache2 | LicenseType::Mozilla)
3416 }
3417
3418 fn is_copyleft(&self, license_type: &LicenseType) -> bool {
3419 matches!(
3420 license_type,
3421 LicenseType::GPL2 | LicenseType::GPL3 | LicenseType::LGPL
3422 )
3423 }
3424
3425 fn calculate_dependency_health_score(
3426 &self,
3427 requirements_files: &[RequirementsFileInfo],
3428 dependencies: &[RequirementInfo],
3429 issues: &[DependencyIssue],
3430 vulnerabilities: &[SecurityVulnerabilityInfo],
3431 ) -> i32 {
3432 let mut score = 100;
3433
3434 for issue in issues {
3436 let deduction = match issue.severity {
3437 DependencyIssueSeverity::Critical => 20,
3438 DependencyIssueSeverity::High => 15,
3439 DependencyIssueSeverity::Medium => 10,
3440 DependencyIssueSeverity::Low => 5,
3441 DependencyIssueSeverity::Info => 2,
3442 };
3443 score -= deduction;
3444 }
3445
3446 for vuln in vulnerabilities {
3448 let deduction = match vuln.severity {
3449 SecurityVulnerabilitySeverity::Critical => 25,
3450 SecurityVulnerabilitySeverity::High => 20,
3451 SecurityVulnerabilitySeverity::Medium => 15,
3452 SecurityVulnerabilitySeverity::Low => 10,
3453 _ => 5,
3454 };
3455 score -= deduction;
3456 }
3457
3458 if !requirements_files.is_empty() {
3460 score += 10;
3461 }
3462
3463 let pinned_deps = dependencies
3464 .iter()
3465 .filter(|d| d.version_spec != "*")
3466 .count();
3467 let pinned_ratio = if dependencies.is_empty() {
3468 0.0
3469 } else {
3470 pinned_deps as f32 / dependencies.len() as f32
3471 };
3472
3473 score += (pinned_ratio * 20.0) as i32;
3474
3475 score.clamp(0, 100)
3476 }
3477
3478 fn get_dependency_recommendations(
3479 &self,
3480 requirements_files: &[RequirementsFileInfo],
3481 dependencies: &[RequirementInfo],
3482 issues: &[DependencyIssue],
3483 import_analysis: &[ImportAnalysisInfo],
3484 vulnerabilities: &[SecurityVulnerabilityInfo],
3485 ) -> Vec<String> {
3486 let mut recommendations = Vec::new();
3487
3488 if requirements_files.is_empty() {
3489 recommendations
3490 .push("Create a requirements.txt file to track dependencies".to_string());
3491 }
3492
3493 let unpinned_count = dependencies
3494 .iter()
3495 .filter(|d| d.version_spec == "*")
3496 .count();
3497 if unpinned_count > 0 {
3498 recommendations.push(format!(
3499 "Pin {unpinned_count} unpinned dependencies for reproducible builds"
3500 ));
3501 }
3502
3503 if !vulnerabilities.is_empty() {
3504 recommendations.push(format!(
3505 "Update {} packages with known security vulnerabilities",
3506 vulnerabilities.len()
3507 ));
3508 }
3509
3510 let unused_imports = import_analysis.iter().filter(|i| i.is_unused).count();
3511 if unused_imports > 0 {
3512 recommendations.push(format!("Remove {unused_imports} unused import statements"));
3513 }
3514
3515 let star_imports = import_analysis
3516 .iter()
3517 .filter(|i| matches!(i.import_type, ImportType::StarImport))
3518 .count();
3519 if star_imports > 0 {
3520 recommendations.push(format!(
3521 "Replace {star_imports} star imports with specific imports"
3522 ));
3523 }
3524
3525 let critical_issues = issues
3526 .iter()
3527 .filter(|i| matches!(i.severity, DependencyIssueSeverity::Critical))
3528 .count();
3529 if critical_issues > 0 {
3530 recommendations.push("Address critical dependency issues immediately".to_string());
3531 }
3532
3533 recommendations
3534 .push("Consider using dependency scanning tools like Safety or Bandit".to_string());
3535 recommendations.push("Set up automated dependency updates with Dependabot".to_string());
3536
3537 recommendations
3538 }
3539
3540 pub fn analyze_modern_features(&self, content: &str) -> Result<ModernPythonFeatureAnalysis> {
3542 let mut dataclass_features = Vec::new();
3543 let mut context_manager_features = Vec::new();
3544 let mut fstring_features = Vec::new();
3545 let mut pattern_matching_features = Vec::new();
3546 let mut generator_features = Vec::new();
3547 let mut decorator_features = Vec::new();
3548 let mut modern_syntax_features = Vec::new();
3549
3550 self.analyze_dataclasses(content, &mut dataclass_features);
3552
3553 self.analyze_context_managers(content, &mut context_manager_features);
3555
3556 self.analyze_fstrings(content, &mut fstring_features);
3558
3559 self.analyze_pattern_matching(content, &mut pattern_matching_features);
3561
3562 self.analyze_generators(content, &mut generator_features);
3564
3565 self.analyze_modern_decorators(content, &mut decorator_features);
3567
3568 self.analyze_modern_syntax(content, &mut modern_syntax_features);
3570
3571 let python_version_detected = self.detect_python_version(content);
3573
3574 let overall_modernity_score = self.calculate_modernity_score(
3576 &dataclass_features,
3577 &context_manager_features,
3578 &fstring_features,
3579 &pattern_matching_features,
3580 &generator_features,
3581 &decorator_features,
3582 &modern_syntax_features,
3583 );
3584
3585 let recommendations = self.get_modern_feature_recommendations(
3587 &dataclass_features,
3588 &context_manager_features,
3589 &fstring_features,
3590 &pattern_matching_features,
3591 &generator_features,
3592 &decorator_features,
3593 &modern_syntax_features,
3594 &python_version_detected,
3595 );
3596
3597 Ok(ModernPythonFeatureAnalysis {
3598 overall_modernity_score,
3599 python_version_detected,
3600 dataclass_features,
3601 context_manager_features,
3602 fstring_features,
3603 pattern_matching_features,
3604 generator_features,
3605 decorator_features,
3606 modern_syntax_features,
3607 recommendations,
3608 })
3609 }
3610
3611 fn analyze_dataclasses(&self, content: &str, dataclass_features: &mut Vec<DataclassInfo>) {
3613 if let Some(patterns) = self.modern_feature_patterns.get("dataclass") {
3614 for pattern in patterns {
3615 for captures in pattern.pattern.captures_iter(content) {
3616 let full_match = captures.get(0).unwrap().as_str();
3617
3618 let dataclass_type = match pattern.name.as_str() {
3619 "Dataclass Decorator" => DataclassType::StandardDataclass,
3620 "Pydantic Model" => DataclassType::PydanticModel,
3621 "Named Tuple" => DataclassType::NamedTuple,
3622 _ => DataclassType::StandardDataclass,
3623 };
3624
3625 let class_name = self.extract_dataclass_name(full_match, content);
3626 let fields = self.analyze_dataclass_fields(content, &class_name);
3627 let features_used = self.detect_dataclass_features(content, &class_name);
3628 let complexity = self.assess_dataclass_complexity(&fields, &features_used);
3629 let best_practices_score =
3630 self.score_dataclass_best_practices(&fields, &features_used);
3631 let recommendations =
3632 self.get_dataclass_recommendations(&features_used, best_practices_score);
3633
3634 dataclass_features.push(DataclassInfo {
3635 class_name,
3636 dataclass_type,
3637 fields,
3638 features_used,
3639 complexity,
3640 best_practices_score,
3641 recommendations,
3642 });
3643 }
3644 }
3645 }
3646 }
3647
3648 fn analyze_context_managers(
3650 &self,
3651 content: &str,
3652 context_features: &mut Vec<ContextManagerInfo>,
3653 ) {
3654 if let Some(patterns) = self.modern_feature_patterns.get("context_manager") {
3655 for pattern in patterns {
3656 for captures in pattern.pattern.captures_iter(content) {
3657 let full_match = captures.get(0).unwrap().as_str();
3658
3659 let context_type = match pattern.name.as_str() {
3660 "With Statement" => ContextManagerType::BuiltInFileManager,
3661 "Async With" => ContextManagerType::AsyncContextManager,
3662 "Context Manager Protocol" => ContextManagerType::CustomContextManager,
3663 "Contextlib Manager" => ContextManagerType::ContextlibManager,
3664 _ => ContextManagerType::BuiltInFileManager,
3665 };
3666
3667 let usage_pattern = self.analyze_context_usage_pattern(content, full_match);
3668 let resource_management =
3669 self.assess_resource_management_quality(content, full_match);
3670 let error_handling = self.assess_context_error_handling(content, full_match);
3671 let is_async = pattern.name.contains("Async");
3672 let nested_level = self.calculate_context_nesting_level(content, full_match);
3673 let best_practices_followed =
3674 self.check_context_best_practices(content, full_match);
3675
3676 context_features.push(ContextManagerInfo {
3677 context_type,
3678 usage_pattern,
3679 resource_management,
3680 error_handling,
3681 is_async,
3682 nested_level,
3683 best_practices_followed,
3684 });
3685 }
3686 }
3687 }
3688 }
3689
3690 fn analyze_fstrings(&self, content: &str, fstring_features: &mut Vec<FStringInfo>) {
3692 if let Some(patterns) = self.modern_feature_patterns.get("fstring") {
3693 for pattern in patterns {
3694 for captures in pattern.pattern.captures_iter(content) {
3695 let expression = captures.get(0).unwrap().as_str().to_string();
3696
3697 let complexity = self.assess_fstring_complexity(&expression);
3698 let features_used = self.detect_fstring_features(&expression);
3699 let performance_impact =
3700 self.assess_fstring_performance(&expression, &features_used);
3701 let formatting_quality = self.assess_formatting_quality(&expression);
3702 let readability_score = self.calculate_fstring_readability(&expression);
3703
3704 fstring_features.push(FStringInfo {
3705 expression,
3706 complexity,
3707 features_used,
3708 performance_impact,
3709 formatting_quality,
3710 readability_score,
3711 });
3712 }
3713 }
3714 }
3715 }
3716
3717 fn analyze_pattern_matching(
3719 &self,
3720 content: &str,
3721 pattern_features: &mut Vec<PatternMatchingInfo>,
3722 ) {
3723 if let Some(_patterns) = self.modern_feature_patterns.get("pattern_matching") {
3724 let match_regex = Regex::new(r"(?m)^(\s*)match\s+[^:]+:(.*?)$").unwrap();
3726 for captures in match_regex.captures_iter(content) {
3727 let match_statement = captures.get(0).unwrap().as_str().to_string();
3728
3729 let pattern_types = self.analyze_match_patterns(&match_statement);
3730 let complexity = self.assess_pattern_complexity(&pattern_types);
3731 let has_guards = match_statement.contains("if ");
3732 let is_exhaustive = self.check_pattern_exhaustiveness(&match_statement);
3733 let performance_characteristics =
3734 self.assess_match_performance(&pattern_types, &match_statement);
3735 let best_practices_score =
3736 self.score_pattern_best_practices(&pattern_types, has_guards, is_exhaustive);
3737
3738 pattern_features.push(PatternMatchingInfo {
3739 match_statement,
3740 pattern_types,
3741 complexity,
3742 has_guards,
3743 is_exhaustive,
3744 performance_characteristics,
3745 best_practices_score,
3746 });
3747 }
3748 }
3749 }
3750
3751 fn analyze_generators(&self, content: &str, generator_features: &mut Vec<GeneratorInfo>) {
3753 if let Some(patterns) = self.modern_feature_patterns.get("generator") {
3754 for pattern in patterns {
3755 for captures in pattern.pattern.captures_iter(content) {
3756 let full_match = captures.get(0).unwrap().as_str();
3757
3758 let generator_type = match pattern.name.as_str() {
3759 "Generator Function" => GeneratorType::GeneratorFunction,
3760 "Generator Expression" => GeneratorType::GeneratorExpression,
3761 "Async Generator" => GeneratorType::AsyncGenerator,
3762 _ => GeneratorType::GeneratorFunction,
3763 };
3764
3765 let usage_pattern = self.classify_generator_usage_pattern(content, full_match);
3766 let memory_efficiency =
3767 self.assess_generator_memory_efficiency(content, full_match);
3768 let complexity = self.assess_generator_complexity(content, full_match);
3769 let is_async = pattern.name.contains("Async");
3770 let yield_analysis = self.analyze_yield_usage(content, full_match);
3771 let optimization_opportunities =
3772 self.identify_generator_optimizations(content, full_match);
3773
3774 generator_features.push(GeneratorInfo {
3775 generator_type,
3776 usage_pattern,
3777 memory_efficiency,
3778 complexity,
3779 is_async,
3780 yield_analysis,
3781 optimization_opportunities,
3782 });
3783 }
3784 }
3785 }
3786 }
3787
3788 fn analyze_modern_decorators(
3790 &self,
3791 content: &str,
3792 decorator_features: &mut Vec<ModernDecoratorInfo>,
3793 ) {
3794 let decorator_regex = Regex::new(r"@(\w+)(?:\([^)]*\))?").unwrap();
3795 for captures in decorator_regex.captures_iter(content) {
3796 let decorator_name = captures.get(1).unwrap().as_str().to_string();
3797 let full_decorator = captures.get(0).unwrap().as_str();
3798
3799 let decorator_category = self.classify_decorator_category(&decorator_name);
3800 let usage_pattern = self.analyze_decorator_usage_pattern(content, full_decorator);
3801 let complexity = self.assess_decorator_complexity(full_decorator);
3802 let is_factory = full_decorator.contains('(');
3803 let is_async = content.contains("async def") && content.contains(&decorator_name);
3804 let parameters = self.extract_decorator_parameters(full_decorator);
3805 let best_practices_score =
3806 self.score_decorator_best_practices(&decorator_category, &usage_pattern);
3807
3808 decorator_features.push(ModernDecoratorInfo {
3809 decorator_name,
3810 decorator_category,
3811 usage_pattern,
3812 complexity,
3813 is_factory,
3814 is_async,
3815 parameters,
3816 best_practices_score,
3817 });
3818 }
3819 }
3820
3821 fn analyze_modern_syntax(&self, content: &str, syntax_features: &mut Vec<ModernSyntaxInfo>) {
3823 if let Some(patterns) = self.modern_feature_patterns.get("modern_syntax") {
3824 for pattern in patterns {
3825 let usage_count = pattern.pattern.find_iter(content).count();
3826 if usage_count > 0 {
3827 let feature_type = match pattern.name.as_str() {
3828 "Walrus Operator" => ModernSyntaxType::WalrusOperator,
3829 "Positional Only Parameters" => ModernSyntaxType::PositionalOnlyParams,
3830 "Union Type Operator" => ModernSyntaxType::TypeUnionOperator,
3831 "Dictionary Union" => ModernSyntaxType::DictUnionOperator,
3832 "Generic Type Hints" => ModernSyntaxType::GenericTypeHints,
3833 _ => ModernSyntaxType::WalrusOperator,
3834 };
3835
3836 let complexity = self.assess_syntax_complexity(content, &pattern.pattern);
3837 let best_practices_followed =
3838 self.check_syntax_best_practices(content, &feature_type);
3839 let migration_suggestions =
3840 self.get_syntax_migration_suggestions(&feature_type, usage_count);
3841
3842 syntax_features.push(ModernSyntaxInfo {
3843 feature_type,
3844 python_version: pattern.python_version.clone(),
3845 usage_count,
3846 complexity,
3847 best_practices_followed,
3848 migration_suggestions,
3849 });
3850 }
3851 }
3852 }
3853 }
3854
3855 fn detect_python_version(&self, content: &str) -> PythonVersionDetected {
3857 let mut features_by_version = Vec::new();
3858 let mut compatibility_issues = Vec::new();
3859 let mut minimum_version = "3.6".to_string(); for patterns in self.modern_feature_patterns.values() {
3863 for pattern in patterns {
3864 if pattern.pattern.is_match(content) {
3865 let version_required = pattern.python_version.clone();
3866
3867 if self.is_newer_version(&version_required, &minimum_version) {
3869 minimum_version = version_required.clone();
3870 }
3871
3872 features_by_version.push(VersionFeature {
3873 feature_name: pattern.name.clone(),
3874 python_version: version_required,
3875 usage_count: pattern.pattern.find_iter(content).count(),
3876 is_best_practice: matches!(
3877 pattern.complexity,
3878 FeatureComplexity::Simple | FeatureComplexity::Moderate
3879 ),
3880 });
3881 }
3882 }
3883 }
3884
3885 if content.contains("print ") && !content.contains("print(") {
3887 compatibility_issues.push(CompatibilityIssue {
3888 issue_type: CompatibilityIssueType::SyntaxError,
3889 severity: CompatibilitySeverity::Critical,
3890 feature_name: "Print Statement".to_string(),
3891 required_version: "2.x".to_string(),
3892 description: "Print statement syntax not supported in Python 3".to_string(),
3893 recommendation: "Use print() function instead".to_string(),
3894 });
3895 }
3896
3897 PythonVersionDetected {
3898 minimum_version,
3899 features_by_version,
3900 compatibility_issues,
3901 }
3902 }
3903
3904 fn extract_dataclass_name(&self, _match: &str, content: &str) -> String {
3906 if let Some(class_match) = Regex::new(r"class\s+(\w+)").unwrap().find(content) {
3908 if let Some(captures) = Regex::new(r"class\s+(\w+)")
3909 .unwrap()
3910 .captures(class_match.as_str())
3911 {
3912 return captures.get(1).unwrap().as_str().to_string();
3913 }
3914 }
3915 "UnknownClass".to_string()
3916 }
3917
3918 fn analyze_dataclass_fields(&self, content: &str, class_name: &str) -> Vec<DataclassField> {
3919 let mut fields = Vec::new();
3921
3922 let field_regex = Regex::new(&format!(r"class\s+{class_name}.*?(\w+):\s*(\w+)")).unwrap();
3924 for captures in field_regex.captures_iter(content) {
3925 if captures.len() >= 3 {
3926 let field_name = captures.get(1).unwrap().as_str().to_string();
3927 let field_type = captures.get(2).unwrap().as_str().to_string();
3928
3929 fields.push(DataclassField {
3930 name: field_name,
3931 field_type,
3932 has_default: false,
3933 is_optional: false,
3934 validation_rules: Vec::new(),
3935 metadata: Vec::new(),
3936 });
3937 }
3938 }
3939
3940 fields
3941 }
3942
3943 fn detect_dataclass_features(&self, content: &str, _class_name: &str) -> Vec<DataclassFeature> {
3944 let mut features = Vec::new();
3945
3946 if content.contains("frozen=True") {
3947 features.push(DataclassFeature::FrozenClass);
3948 }
3949 if content.contains("__slots__") {
3950 features.push(DataclassFeature::SlotsUsage);
3951 }
3952 if content.contains("__post_init__") {
3953 features.push(DataclassFeature::PostInitProcessing);
3954 }
3955 if content.contains("default_factory") {
3956 features.push(DataclassFeature::FieldFactories);
3957 }
3958
3959 features
3960 }
3961
3962 fn assess_dataclass_complexity(
3963 &self,
3964 fields: &[DataclassField],
3965 features: &[DataclassFeature],
3966 ) -> FeatureComplexity {
3967 let complexity_score = fields.len() + features.len() * 2;
3968
3969 match complexity_score {
3970 0..=3 => FeatureComplexity::Simple,
3971 4..=8 => FeatureComplexity::Moderate,
3972 9..=15 => FeatureComplexity::Complex,
3973 _ => FeatureComplexity::Expert,
3974 }
3975 }
3976
3977 fn score_dataclass_best_practices(
3978 &self,
3979 _fields: &[DataclassField],
3980 features: &[DataclassFeature],
3981 ) -> i32 {
3982 let mut score = 60; if features.contains(&DataclassFeature::FrozenClass) {
3986 score += 15; }
3988 if features.contains(&DataclassFeature::SlotsUsage) {
3989 score += 10; }
3991 if features.contains(&DataclassFeature::PostInitProcessing) {
3992 score += 10; }
3994
3995 score.min(100)
3996 }
3997
3998 fn get_dataclass_recommendations(
3999 &self,
4000 features: &[DataclassFeature],
4001 score: i32,
4002 ) -> Vec<String> {
4003 let mut recommendations = Vec::new();
4004
4005 if score < 70 {
4006 recommendations.push("Consider using dataclass best practices".to_string());
4007 }
4008
4009 if !features.contains(&DataclassFeature::SlotsUsage) {
4010 recommendations.push("Consider using __slots__ for memory optimization".to_string());
4011 }
4012
4013 if !features.contains(&DataclassFeature::FrozenClass) {
4014 recommendations.push("Consider making dataclass frozen for immutability".to_string());
4015 }
4016
4017 recommendations
4018 }
4019
4020 #[allow(clippy::too_many_arguments)] fn calculate_modernity_score(
4023 &self,
4024 dataclass_features: &[DataclassInfo],
4025 context_manager_features: &[ContextManagerInfo],
4026 fstring_features: &[FStringInfo],
4027 pattern_matching_features: &[PatternMatchingInfo],
4028 generator_features: &[GeneratorInfo],
4029 decorator_features: &[ModernDecoratorInfo],
4030 modern_syntax_features: &[ModernSyntaxInfo],
4031 ) -> i32 {
4032 let mut score = 50i32; score += (dataclass_features.len() * 8).min(20) as i32;
4036 score += (context_manager_features.len() * 5).min(15) as i32;
4037 score += (fstring_features.len() * 3).min(10) as i32;
4038 score += (pattern_matching_features.len() * 10).min(20) as i32;
4039 score += (generator_features.len() * 4).min(12) as i32;
4040 score += (decorator_features.len() * 2).min(10) as i32;
4041 score += (modern_syntax_features.len() * 3).min(13) as i32;
4042
4043 score.min(100)
4044 }
4045
4046 #[allow(clippy::too_many_arguments)] fn get_modern_feature_recommendations(
4049 &self,
4050 dataclass_features: &[DataclassInfo],
4051 context_manager_features: &[ContextManagerInfo],
4052 fstring_features: &[FStringInfo],
4053 _pattern_matching_features: &[PatternMatchingInfo],
4054 generator_features: &[GeneratorInfo],
4055 _decorator_features: &[ModernDecoratorInfo],
4056 modern_syntax_features: &[ModernSyntaxInfo],
4057 python_version: &PythonVersionDetected,
4058 ) -> Vec<String> {
4059 let mut recommendations = Vec::new();
4060
4061 if dataclass_features.is_empty() {
4062 recommendations.push(
4063 "Consider using @dataclass for data classes instead of manual __init__".to_string(),
4064 );
4065 }
4066
4067 if context_manager_features.is_empty() {
4068 recommendations
4069 .push("Use context managers (with statements) for resource management".to_string());
4070 }
4071
4072 if fstring_features.is_empty() {
4073 recommendations.push(
4074 "Consider using f-strings for string formatting instead of .format()".to_string(),
4075 );
4076 }
4077
4078 if generator_features.is_empty() {
4079 recommendations
4080 .push("Consider using generators for memory-efficient iteration".to_string());
4081 }
4082
4083 if self.is_version_supported("3.10", &python_version.minimum_version) {
4085 recommendations.push(
4086 "Consider using pattern matching (match/case) for complex conditionals".to_string(),
4087 );
4088 }
4089
4090 if self.is_version_supported("3.8", &python_version.minimum_version) {
4091 let has_walrus = modern_syntax_features
4092 .iter()
4093 .any(|f| matches!(f.feature_type, ModernSyntaxType::WalrusOperator));
4094 if !has_walrus {
4095 recommendations.push(
4096 "Consider using walrus operator (:=) for assignment expressions".to_string(),
4097 );
4098 }
4099 }
4100
4101 recommendations
4102 }
4103
4104 fn analyze_context_usage_pattern(
4106 &self,
4107 content: &str,
4108 _context_match: &str,
4109 ) -> ContextUsagePattern {
4110 if content.contains("async with") {
4111 ContextUsagePattern::AsyncContext
4112 } else if content.matches("with").count() > 1 {
4113 ContextUsagePattern::MultipleContexts
4114 } else {
4115 ContextUsagePattern::SingleContext
4116 }
4117 }
4118
4119 fn assess_resource_management_quality(
4121 &self,
4122 content: &str,
4123 _context_match: &str,
4124 ) -> ResourceManagementQuality {
4125 let has_error_handling = content.contains("try") || content.contains("except");
4126 let has_finally = content.contains("finally");
4127 let has_proper_cleanup = content.contains("close()") || content.contains("__exit__");
4128
4129 match (has_error_handling, has_finally, has_proper_cleanup) {
4130 (true, true, true) => ResourceManagementQuality::Excellent,
4131 (true, _, true) => ResourceManagementQuality::Good,
4132 (true, _, _) => ResourceManagementQuality::Adequate,
4133 (false, _, true) => ResourceManagementQuality::Poor,
4134 _ => ResourceManagementQuality::Dangerous,
4135 }
4136 }
4137
4138 fn assess_context_error_handling(
4140 &self,
4141 content: &str,
4142 _context_match: &str,
4143 ) -> ContextErrorHandling {
4144 if content.contains("except") && content.contains("finally") {
4145 ContextErrorHandling::Comprehensive
4146 } else if content.contains("except") {
4147 ContextErrorHandling::Basic
4148 } else if content.contains("try") {
4149 ContextErrorHandling::Minimal
4150 } else {
4151 ContextErrorHandling::None
4152 }
4153 }
4154
4155 fn calculate_context_nesting_level(&self, content: &str, _context_match: &str) -> usize {
4157 let with_pattern = Regex::new(r"\bwith\s+[^:]+:").unwrap();
4159 let with_count = with_pattern.find_iter(content).count();
4160 if with_count > 3 {
4161 3 } else {
4163 with_count
4164 }
4165 }
4166
4167 fn check_context_best_practices(&self, content: &str, _context_match: &str) -> bool {
4169 let has_appropriate_error_handling = content.contains("except");
4171 let not_overly_nested = self.calculate_context_nesting_level(content, _context_match) <= 2;
4172 let has_resource_cleanup = content.contains("close") || content.contains("__exit__");
4173
4174 has_appropriate_error_handling && not_overly_nested && has_resource_cleanup
4175 }
4176
4177 fn assess_fstring_complexity(&self, expression: &str) -> FStringComplexity {
4179 let brace_pattern = Regex::new(r"\{[^}]+\}").unwrap();
4181 let brace_count = brace_pattern.find_iter(expression).count();
4182 let has_function_calls = expression.contains('(');
4183 let has_format_spec = expression.contains(':');
4184
4185 match (brace_count, has_function_calls, has_format_spec) {
4186 (1, false, false) => FStringComplexity::Simple,
4187 (1..=2, _, true) | (1..=2, true, _) => FStringComplexity::Moderate,
4188 (3..=5, _, _) => FStringComplexity::Complex,
4189 _ => FStringComplexity::Advanced,
4190 }
4191 }
4192
4193 fn detect_fstring_features(&self, expression: &str) -> Vec<FStringFeature> {
4194 let mut features = Vec::new();
4195
4196 if expression.contains('{') && expression.contains('}') {
4197 features.push(FStringFeature::BasicInterpolation);
4198 }
4199 if expression.contains('(') {
4200 features.push(FStringFeature::ExpressionEvaluation);
4201 }
4202 if expression.contains(':') {
4203 features.push(FStringFeature::FormatSpecifiers);
4204 }
4205 if expression.contains('!') {
4206 features.push(FStringFeature::ConversionFlags);
4207 }
4208 if expression.starts_with("rf") || expression.starts_with("fr") {
4209 features.push(FStringFeature::RawFString);
4210 }
4211
4212 features
4213 }
4214
4215 fn assess_fstring_performance(
4216 &self,
4217 _expression: &str,
4218 features: &[FStringFeature],
4219 ) -> PerformanceImpact {
4220 if features.contains(&FStringFeature::ComplexExpressions) {
4221 PerformanceImpact::Negative
4222 } else if features.contains(&FStringFeature::ExpressionEvaluation) {
4223 PerformanceImpact::Neutral
4224 } else {
4225 PerformanceImpact::Positive
4226 }
4227 }
4228
4229 fn assess_formatting_quality(&self, expression: &str) -> FormattingQuality {
4230 let length = expression.len();
4231 let complexity = self.assess_fstring_complexity(expression);
4232
4233 match (length, complexity) {
4234 (0..=50, FStringComplexity::Simple) => FormattingQuality::Excellent,
4235 (0..=100, FStringComplexity::Moderate) => FormattingQuality::Good,
4236 (0..=150, FStringComplexity::Complex) => FormattingQuality::Adequate,
4237 (0..=200, _) => FormattingQuality::Poor,
4238 _ => FormattingQuality::Unreadable,
4239 }
4240 }
4241
4242 fn calculate_fstring_readability(&self, expression: &str) -> i32 {
4243 let mut score = 100;
4244
4245 if expression.len() > 100 {
4246 score -= 20;
4247 }
4248 if expression.matches('{').count() > 3 {
4249 score -= 15;
4250 }
4251 if expression.contains("()") {
4252 score -= 10;
4253 }
4254
4255 score.max(0)
4256 }
4257
4258 fn is_newer_version(&self, version1: &str, version2: &str) -> bool {
4260 let v1 = version1.trim_end_matches('+');
4262 let v2 = version2.trim_end_matches('+');
4263 v1 > v2
4264 }
4265
4266 fn is_version_supported(&self, required: &str, current: &str) -> bool {
4267 self.is_newer_version(current, required) || current == required
4269 }
4270
4271 fn analyze_match_patterns(&self, match_statement: &str) -> Vec<PatternType> {
4273 let mut patterns = Vec::new();
4274
4275 if match_statement.contains("case _:") {
4276 patterns.push(PatternType::WildcardPattern);
4277 }
4278 if match_statement.contains("case ") && match_statement.contains("if ") {
4279 patterns.push(PatternType::GuardedPattern);
4280 }
4281 if match_statement.contains("case [") {
4282 patterns.push(PatternType::SequencePattern);
4283 }
4284 if match_statement.contains("case {") {
4285 patterns.push(PatternType::MappingPattern);
4286 }
4287 if match_statement.contains(" | ") {
4288 patterns.push(PatternType::OrPattern);
4289 }
4290
4291 if patterns.is_empty() {
4292 patterns.push(PatternType::LiteralPattern);
4293 }
4294
4295 patterns
4296 }
4297
4298 fn assess_pattern_complexity(&self, patterns: &[PatternType]) -> PatternComplexity {
4299 let complexity_score = patterns.len()
4300 + patterns
4301 .iter()
4302 .map(|p| match p {
4303 PatternType::GuardedPattern => 3,
4304 PatternType::SequencePattern | PatternType::MappingPattern => 2,
4305 PatternType::OrPattern => 2,
4306 _ => 1,
4307 })
4308 .sum::<usize>();
4309
4310 match complexity_score {
4311 0..=3 => PatternComplexity::Simple,
4312 4..=8 => PatternComplexity::Moderate,
4313 9..=15 => PatternComplexity::Complex,
4314 _ => PatternComplexity::Advanced,
4315 }
4316 }
4317
4318 fn check_pattern_exhaustiveness(&self, match_statement: &str) -> bool {
4319 match_statement.contains("case _:") || match_statement.contains("case default:")
4320 }
4321
4322 fn assess_match_performance(
4323 &self,
4324 patterns: &[PatternType],
4325 _match_statement: &str,
4326 ) -> MatchPerformance {
4327 let has_complex_patterns = patterns.iter().any(|p| {
4328 matches!(
4329 p,
4330 PatternType::GuardedPattern
4331 | PatternType::SequencePattern
4332 | PatternType::MappingPattern
4333 )
4334 });
4335
4336 if has_complex_patterns && patterns.len() > 10 {
4337 MatchPerformance::Poor
4338 } else if has_complex_patterns {
4339 MatchPerformance::Fair
4340 } else if patterns.len() <= 5 {
4341 MatchPerformance::Optimal
4342 } else {
4343 MatchPerformance::Good
4344 }
4345 }
4346
4347 fn score_pattern_best_practices(
4348 &self,
4349 patterns: &[PatternType],
4350 has_guards: bool,
4351 is_exhaustive: bool,
4352 ) -> i32 {
4353 let mut score = 70;
4354
4355 if is_exhaustive {
4356 score += 20;
4357 }
4358 if has_guards {
4359 score += 10; }
4361 if patterns.len() <= 5 {
4362 score += 10; }
4364
4365 score.min(100)
4366 }
4367
4368 fn classify_generator_usage_pattern(
4370 &self,
4371 content: &str,
4372 _generator_match: &str,
4373 ) -> GeneratorUsagePattern {
4374 if content.contains("yield from") {
4375 GeneratorUsagePattern::Pipeline
4376 } else if content.contains("while True") {
4377 GeneratorUsagePattern::InfiniteSequence
4378 } else if content.contains("map") || content.contains("filter") {
4379 GeneratorUsagePattern::DataTransformation
4380 } else {
4381 GeneratorUsagePattern::SimpleIteration
4382 }
4383 }
4384
4385 fn assess_generator_memory_efficiency(
4386 &self,
4387 content: &str,
4388 _generator_match: &str,
4389 ) -> MemoryEfficiency {
4390 let has_large_data_structures =
4391 content.contains("list(") || content.contains("[") && content.len() > 100;
4392 let uses_yield_properly = content.contains("yield") && !content.contains("return [");
4393
4394 match (uses_yield_properly, has_large_data_structures) {
4395 (true, false) => MemoryEfficiency::Excellent,
4396 (true, true) => MemoryEfficiency::Good,
4397 (false, false) => MemoryEfficiency::Adequate,
4398 (false, true) => MemoryEfficiency::Poor,
4399 }
4400 }
4401
4402 fn assess_generator_complexity(
4403 &self,
4404 content: &str,
4405 _generator_match: &str,
4406 ) -> GeneratorComplexity {
4407 let yield_pattern = Regex::new(r"\byield\s+").unwrap();
4409 let yield_count = yield_pattern.find_iter(content).count();
4410 let has_complex_logic = content.contains("if") && content.contains("for");
4411 let for_pattern = Regex::new(r"\bfor\s+\w+\s+in\s+").unwrap();
4413 let has_nested_loops = for_pattern.find_iter(content).count() > 1;
4414
4415 match (yield_count, has_complex_logic, has_nested_loops) {
4416 (1, false, false) => GeneratorComplexity::Simple,
4417 (1..=3, _, false) => GeneratorComplexity::Moderate,
4418 (_, true, true) => GeneratorComplexity::Advanced,
4419 _ => GeneratorComplexity::Complex,
4420 }
4421 }
4422
4423 fn analyze_yield_usage(&self, content: &str, _generator_match: &str) -> YieldAnalysis {
4424 let yield_pattern = Regex::new(r"\byield\s+").unwrap();
4426 YieldAnalysis {
4427 yield_count: yield_pattern.find_iter(content).count(),
4428 has_yield_from: content.contains("yield from"),
4429 has_send_values: content.contains(".send("),
4430 has_throw_values: content.contains(".throw("),
4431 has_close_handling: content.contains(".close("),
4432 }
4433 }
4434
4435 fn identify_generator_optimizations(
4436 &self,
4437 content: &str,
4438 _generator_match: &str,
4439 ) -> Vec<String> {
4440 let mut optimizations = Vec::new();
4441
4442 if content.contains("list(") && content.contains("yield") {
4443 optimizations.push("Avoid converting generator to list unless necessary".to_string());
4444 }
4445
4446 if content.matches("for").count() > 2 {
4447 optimizations
4448 .push("Consider breaking complex generator into smaller functions".to_string());
4449 }
4450
4451 if !content.contains("yield from") && content.contains("for") && content.contains("yield") {
4452 optimizations
4453 .push("Consider using 'yield from' for delegating to sub-generators".to_string());
4454 }
4455
4456 optimizations
4457 }
4458
4459 fn classify_decorator_category(&self, decorator_name: &str) -> DecoratorCategory {
4461 match decorator_name {
4462 "property" | "staticmethod" | "classmethod" => DecoratorCategory::BuiltIn,
4463 "wraps" | "singledispatch" | "cache" | "lru_cache" => {
4464 DecoratorCategory::FunctoolsDecorator
4465 }
4466 "app.route" | "api.route" | "route" => DecoratorCategory::WebFramework,
4467 "pytest.fixture" | "pytest.mark" | "unittest.mock" => DecoratorCategory::Testing,
4468 "dataclass" | "validator" => DecoratorCategory::DataValidation,
4469 _ => DecoratorCategory::Custom,
4470 }
4471 }
4472
4473 fn analyze_decorator_usage_pattern(
4474 &self,
4475 content: &str,
4476 decorator: &str,
4477 ) -> DecoratorUsagePattern {
4478 let decorator_context = self.get_decorator_context(content, decorator);
4479
4480 if decorator_context.contains('@') && decorator_context.matches('@').count() > 1 {
4481 DecoratorUsagePattern::StackedDecorators
4482 } else if decorator.contains('(') {
4483 DecoratorUsagePattern::ParameterizedDecorator
4484 } else {
4485 DecoratorUsagePattern::SingleDecorator
4486 }
4487 }
4488
4489 fn get_decorator_context(&self, content: &str, decorator: &str) -> String {
4490 if let Some(pos) = content.find(decorator) {
4492 let start = pos.saturating_sub(100);
4493 let end = (pos + decorator.len() + 100).min(content.len());
4494 content[start..end].to_string()
4495 } else {
4496 decorator.to_string()
4497 }
4498 }
4499
4500 fn assess_decorator_complexity(&self, decorator: &str) -> DecoratorComplexity {
4501 if decorator.contains('(') && decorator.contains(',') {
4502 DecoratorComplexity::Complex
4503 } else if decorator.contains('(') {
4504 DecoratorComplexity::Moderate
4505 } else {
4506 DecoratorComplexity::Simple
4507 }
4508 }
4509
4510 fn score_decorator_best_practices(
4511 &self,
4512 category: &DecoratorCategory,
4513 usage_pattern: &DecoratorUsagePattern,
4514 ) -> i32 {
4515 let mut score = 80;
4516
4517 match category {
4518 DecoratorCategory::BuiltIn => score += 10,
4519 DecoratorCategory::FunctoolsDecorator => score += 15,
4520 DecoratorCategory::Performance => score += 10,
4521 _ => {}
4522 }
4523
4524 match usage_pattern {
4525 DecoratorUsagePattern::SingleDecorator => score += 5,
4526 DecoratorUsagePattern::StackedDecorators => score -= 5, _ => {}
4528 }
4529
4530 score.min(100)
4531 }
4532
4533 fn assess_syntax_complexity(&self, content: &str, pattern: &Regex) -> SyntaxComplexity {
4535 let usage_count = pattern.find_iter(content).count();
4536 let total_lines = content.lines().count();
4537 let usage_density = if total_lines > 0 {
4538 usage_count as f32 / total_lines as f32
4539 } else {
4540 0.0
4541 };
4542
4543 match usage_density {
4544 d if d > 0.1 => SyntaxComplexity::Expert,
4545 d if d > 0.05 => SyntaxComplexity::Complex,
4546 d if d > 0.01 => SyntaxComplexity::Moderate,
4547 _ => SyntaxComplexity::Simple,
4548 }
4549 }
4550
4551 fn check_syntax_best_practices(&self, content: &str, feature_type: &ModernSyntaxType) -> bool {
4552 match feature_type {
4553 ModernSyntaxType::WalrusOperator => {
4554 content.matches(":=").count() <= content.lines().count() / 10
4556 }
4557 ModernSyntaxType::PositionalOnlyParams => {
4558 content.contains("def ") && content.contains("/")
4560 }
4561 ModernSyntaxType::TypeUnionOperator => {
4562 !content.contains("typing.Union") || content.contains(" | ")
4564 }
4565 _ => true, }
4567 }
4568
4569 fn get_syntax_migration_suggestions(
4570 &self,
4571 feature_type: &ModernSyntaxType,
4572 usage_count: usize,
4573 ) -> Vec<String> {
4574 let mut suggestions = Vec::new();
4575
4576 match feature_type {
4577 ModernSyntaxType::WalrusOperator => {
4578 if usage_count > 10 {
4579 suggestions.push(
4580 "Consider if all walrus operator usages improve readability".to_string(),
4581 );
4582 }
4583 suggestions.push(
4584 "Use walrus operator to reduce code duplication in conditions".to_string(),
4585 );
4586 }
4587 ModernSyntaxType::GenericTypeHints => {
4588 suggestions.push(
4589 "Migrate from typing.List/Dict to built-in list/dict for type hints"
4590 .to_string(),
4591 );
4592 }
4593 ModernSyntaxType::TypeUnionOperator => {
4594 suggestions.push(
4595 "Replace typing.Union with | operator for cleaner type hints".to_string(),
4596 );
4597 }
4598 _ => {}
4599 }
4600
4601 suggestions
4602 }
4603
4604 fn analyze_async_functions(&self, content: &str, async_functions: &mut Vec<AsyncFunctionInfo>) {
4606 for patterns in self.async_patterns.values() {
4607 for pattern in patterns {
4608 if pattern.pattern_type == "function"
4609 || pattern.pattern_type == "generator"
4610 || pattern.pattern_type == "context_manager"
4611 || pattern.pattern_type == "iterator"
4612 {
4613 for captures in pattern.pattern.captures_iter(content) {
4614 let full_match = captures.get(0).unwrap().as_str();
4615 let function_name = captures
4616 .get(1)
4617 .map(|m| m.as_str().to_string())
4618 .unwrap_or_else(|| "anonymous".to_string());
4619
4620 let function_type =
4621 self.determine_async_function_type(&pattern.name, full_match);
4622 let complexity = self.assess_async_complexity(content, full_match);
4623 let coroutine_type = self.classify_coroutine_type(content);
4624 let error_handling = self.assess_error_handling(content, full_match);
4625 let has_timeout = self.has_timeout_handling(content, full_match);
4626 let uses_context_manager =
4627 self.uses_async_context_manager(content, full_match);
4628
4629 async_functions.push(AsyncFunctionInfo {
4630 name: function_name,
4631 function_type,
4632 complexity,
4633 coroutine_type,
4634 error_handling,
4635 has_timeout,
4636 uses_context_manager,
4637 location: full_match.to_string(),
4638 });
4639 }
4640 }
4641 }
4642 }
4643 }
4644
4645 fn analyze_await_usage(&self, content: &str, await_patterns: &mut Vec<AwaitUsageInfo>) {
4647 let await_pattern = Regex::new(r"await\s+([^;\n]+)").unwrap();
4648
4649 for captures in await_pattern.captures_iter(content) {
4650 let full_match = captures.get(0).unwrap().as_str();
4651 let await_expr = captures.get(1).unwrap().as_str();
4652
4653 let context = self.determine_await_context(content, full_match);
4654 let usage_pattern = self.classify_await_usage_pattern(content, await_expr);
4655 let is_valid = self.validate_await_usage(&context);
4656 let potential_issues = self.detect_await_issues(content, await_expr, &context);
4657
4658 await_patterns.push(AwaitUsageInfo {
4659 location: full_match.to_string(),
4660 context,
4661 usage_pattern,
4662 is_valid,
4663 potential_issues,
4664 });
4665 }
4666 }
4667
4668 fn analyze_concurrency_patterns(
4670 &self,
4671 content: &str,
4672 concurrency_patterns: &mut Vec<ConcurrencyPatternInfo>,
4673 ) {
4674 for patterns in self.async_patterns.values() {
4675 for pattern in patterns {
4676 if pattern.pattern_type == "concurrency" {
4677 for captures in pattern.pattern.captures_iter(content) {
4678 let full_match = captures.get(0).unwrap().as_str();
4679
4680 let pattern_type = self.map_to_concurrency_pattern_type(&pattern.name);
4681 let usage_quality =
4682 self.assess_concurrency_usage_quality(content, full_match);
4683 let best_practices_followed =
4684 self.check_concurrency_best_practices(content, full_match);
4685
4686 concurrency_patterns.push(ConcurrencyPatternInfo {
4687 pattern_type,
4688 usage_quality,
4689 performance_impact: pattern.performance_impact.clone(),
4690 location: full_match.to_string(),
4691 best_practices_followed,
4692 });
4693 }
4694 }
4695 }
4696 }
4697 }
4698
4699 fn detect_async_performance_issues(
4701 &self,
4702 content: &str,
4703 issues: &mut Vec<AsyncPerformanceIssue>,
4704 ) {
4705 if content.contains("async def")
4707 && (content.contains("time.sleep")
4708 || content.contains("open(")
4709 || content.contains("input("))
4710 {
4711 issues.push(AsyncPerformanceIssue {
4712 issue_type: AsyncPerformanceIssueType::BlockingIOInAsync,
4713 severity: AsyncIssueSeverity::High,
4714 location: "Async function with blocking operations".to_string(),
4715 description: "Blocking I/O operation in async function".to_string(),
4716 recommendation: "Use async I/O operations or run_in_executor".to_string(),
4717 estimated_impact: AsyncPerformanceImpact::Critical,
4718 });
4719 }
4720
4721 let await_loop_pattern = Regex::new(r"for\s+.*?:\s*.*?await\s+").unwrap();
4723 for captures in await_loop_pattern.captures_iter(content) {
4724 let full_match = captures.get(0).unwrap().as_str();
4725 issues.push(AsyncPerformanceIssue {
4726 issue_type: AsyncPerformanceIssueType::AwaitInLoop,
4727 severity: AsyncIssueSeverity::Medium,
4728 location: full_match.to_string(),
4729 description: "Sequential await in loop - consider asyncio.gather()".to_string(),
4730 recommendation:
4731 "Use asyncio.gather() or asyncio.as_completed() for concurrent execution"
4732 .to_string(),
4733 estimated_impact: AsyncPerformanceImpact::Negative,
4734 });
4735 }
4736
4737 let sequential_await_pattern = Regex::new(r"await\s+\w+.*\n.*await\s+\w+").unwrap();
4739 for captures in sequential_await_pattern.captures_iter(content) {
4740 let full_match = captures.get(0).unwrap().as_str();
4741 issues.push(AsyncPerformanceIssue {
4742 issue_type: AsyncPerformanceIssueType::MissingConcurrency,
4743 severity: AsyncIssueSeverity::Medium,
4744 location: full_match.to_string(),
4745 description: "Sequential await calls could be concurrent".to_string(),
4746 recommendation: "Consider using asyncio.gather() for independent operations"
4747 .to_string(),
4748 estimated_impact: AsyncPerformanceImpact::Negative,
4749 });
4750 }
4751 }
4752
4753 fn detect_async_security_issues(&self, content: &str, issues: &mut Vec<AsyncSecurityIssue>) {
4755 let await_pattern = Regex::new(r"await\s+").unwrap();
4757 let wait_for_pattern = Regex::new(r"\basyncio\.wait_for\s*\(").unwrap();
4759 let timeout_pattern = Regex::new(r"\basyncio\.timeout\s*\(").unwrap();
4760 let timeout_count = wait_for_pattern.find_iter(content).count()
4761 + timeout_pattern.find_iter(content).count();
4762 let await_count = await_pattern.find_iter(content).count();
4763
4764 if await_count > timeout_count + 2 {
4765 issues.push(AsyncSecurityIssue {
4766 issue_type: AsyncSecurityIssueType::AsyncTimeoutVuln,
4767 severity: AsyncSecuritySeverity::Medium,
4768 location: "Multiple async operations".to_string(),
4769 description: "Missing timeout handling in async operations".to_string(),
4770 recommendation: "Add timeouts to prevent DoS attacks".to_string(),
4771 });
4772 }
4773
4774 let shared_state_pattern =
4776 Regex::new(r"(?:global|class)\s+\w+.*=.*\n.*async\s+def.*\w+.*=").unwrap();
4777 for captures in shared_state_pattern.captures_iter(content) {
4778 let full_match = captures.get(0).unwrap().as_str();
4779 if !content.contains("asyncio.Lock") && !content.contains("asyncio.Semaphore") {
4780 issues.push(AsyncSecurityIssue {
4781 issue_type: AsyncSecurityIssueType::SharedStateNoLock,
4782 severity: AsyncSecuritySeverity::High,
4783 location: full_match.to_string(),
4784 description: "Shared mutable state without proper locking".to_string(),
4785 recommendation: "Use asyncio.Lock or asyncio.Semaphore for thread safety"
4786 .to_string(),
4787 });
4788 }
4789 }
4790
4791 if content.contains("asyncio.gather")
4793 && !content.contains("asyncio.Lock")
4794 && content.matches("=").count() > 3
4795 {
4796 issues.push(AsyncSecurityIssue {
4797 issue_type: AsyncSecurityIssueType::AsyncRaceCondition,
4798 severity: AsyncSecuritySeverity::Medium,
4799 location: "Concurrent operations".to_string(),
4800 description: "Potential race condition in concurrent operations".to_string(),
4801 recommendation: "Review shared resource access and add synchronization".to_string(),
4802 });
4803 }
4804 }
4805
4806 fn detect_modern_async_features(&self, content: &str, features: &mut Vec<ModernAsyncFeature>) {
4808 let modern_patterns = &[
4809 (
4810 "async with",
4811 ModernAsyncFeatureType::AsyncContextManager,
4812 "3.7+",
4813 ),
4814 (
4815 "asyncio.TaskGroup",
4816 ModernAsyncFeatureType::TaskGroups,
4817 "3.11+",
4818 ),
4819 (
4820 "asyncio.timeout",
4821 ModernAsyncFeatureType::AsyncioTimeout,
4822 "3.11+",
4823 ),
4824 ("async for", ModernAsyncFeatureType::AsyncIterators, "3.7+"),
4825 ("contextvars", ModernAsyncFeatureType::ContextVars, "3.7+"),
4826 ("asyncio.run", ModernAsyncFeatureType::AsyncioRun, "3.7+"),
4827 ];
4828
4829 for (pattern_str, feature_type, version) in modern_patterns {
4830 let count = content.matches(pattern_str).count();
4831 if count > 0 {
4832 features.push(ModernAsyncFeature {
4833 feature_type: feature_type.clone(),
4834 python_version: version.to_string(),
4835 usage_count: count,
4836 description: format!("Modern async feature: {pattern_str}"),
4837 is_best_practice: true,
4838 });
4839 }
4840 }
4841
4842 let async_comp_pattern = Regex::new(r"\[.*async\s+for.*\]|\{.*async\s+for.*\}").unwrap();
4844 let comp_count = async_comp_pattern.find_iter(content).count();
4845 if comp_count > 0 {
4846 features.push(ModernAsyncFeature {
4847 feature_type: ModernAsyncFeatureType::AsyncComprehensions,
4848 python_version: "3.6+".to_string(),
4849 usage_count: comp_count,
4850 description: "Async comprehensions for concurrent iteration".to_string(),
4851 is_best_practice: true,
4852 });
4853 }
4854 }
4855
4856 fn determine_async_function_type(
4858 &self,
4859 pattern_name: &str,
4860 _full_match: &str,
4861 ) -> AsyncFunctionType {
4862 match pattern_name {
4863 "Async Function" => AsyncFunctionType::RegularAsync,
4864 "Async Generator" => AsyncFunctionType::AsyncGenerator,
4865 "Async Context Manager" => AsyncFunctionType::AsyncContextManager,
4866 "Async Iterator" => AsyncFunctionType::AsyncIterator,
4867 _ => AsyncFunctionType::RegularAsync,
4868 }
4869 }
4870
4871 fn assess_async_complexity(&self, _content: &str, function_match: &str) -> AsyncComplexity {
4872 let await_pattern = Regex::new(r"\bawait\s+").unwrap();
4874 let try_pattern = Regex::new(r"\btry\s*:").unwrap();
4875 let gather_pattern = Regex::new(r"\basyncio\.gather\s*\(").unwrap();
4876
4877 let await_count = await_pattern.find_iter(function_match).count();
4878 let try_count = try_pattern.find_iter(function_match).count();
4879 let gather_count = gather_pattern.find_iter(function_match).count();
4880
4881 match (await_count, try_count, gather_count) {
4882 (0..=1, 0, 0) => AsyncComplexity::Simple,
4883 (2..=3, 0..=1, 0..=1) => AsyncComplexity::Moderate,
4884 (4..=6, 1..=2, 0..=2) => AsyncComplexity::Complex,
4885 _ => AsyncComplexity::Advanced,
4886 }
4887 }
4888
4889 fn classify_coroutine_type(&self, content: &str) -> CoroutineType {
4890 if content.contains("asyncio") {
4891 CoroutineType::Framework("asyncio".to_string())
4892 } else if content.contains("trio") {
4893 CoroutineType::Framework("trio".to_string())
4894 } else if content.contains("curio") {
4895 CoroutineType::Framework("curio".to_string())
4896 } else if content.contains("yield from") {
4897 CoroutineType::Generator
4898 } else {
4899 CoroutineType::Native
4900 }
4901 }
4902
4903 fn assess_error_handling(&self, _content: &str, function_match: &str) -> AsyncErrorHandling {
4904 if function_match.contains("asyncio.timeout") || function_match.contains("asyncio.wait_for")
4905 {
4906 AsyncErrorHandling::Robust
4907 } else if function_match.contains("timeout") {
4908 AsyncErrorHandling::Timeout
4909 } else if function_match.contains("try") && function_match.contains("except") {
4910 AsyncErrorHandling::Basic
4911 } else {
4912 AsyncErrorHandling::None
4913 }
4914 }
4915
4916 fn has_timeout_handling(&self, _content: &str, function_match: &str) -> bool {
4917 function_match.contains("timeout") || function_match.contains("asyncio.wait_for")
4918 }
4919
4920 fn uses_async_context_manager(&self, _content: &str, function_match: &str) -> bool {
4921 function_match.contains("async with")
4922 }
4923
4924 fn determine_await_context(&self, content: &str, await_match: &str) -> AwaitContext {
4925 if content.contains("async def") {
4927 if await_match.contains("__aenter__") || await_match.contains("__aexit__") {
4928 AwaitContext::AsyncContextManager
4929 } else if await_match.contains("__aiter__") || await_match.contains("__anext__") {
4930 AwaitContext::AsyncIterator
4931 } else if await_match.contains("yield") {
4932 AwaitContext::AsyncGenerator
4933 } else {
4934 AwaitContext::AsyncFunction
4935 }
4936 } else if await_match.contains("[") && await_match.contains("for") {
4937 AwaitContext::Comprehension
4938 } else if await_match.contains("lambda") {
4939 AwaitContext::Lambda
4940 } else {
4941 AwaitContext::SyncContext
4942 }
4943 }
4944
4945 fn classify_await_usage_pattern(&self, content: &str, await_expr: &str) -> AwaitUsagePattern {
4946 if content.contains("asyncio.gather") {
4947 AwaitUsagePattern::GatheredAwait
4948 } else if await_expr.contains("await") {
4949 AwaitUsagePattern::NestedAwait
4950 } else if content.contains("if") && await_expr.contains("await") {
4951 AwaitUsagePattern::ConditionalAwait
4952 } else if content.matches("await").count() > 1 {
4953 AwaitUsagePattern::SequentialAwaits
4954 } else {
4955 AwaitUsagePattern::SingleAwait
4956 }
4957 }
4958
4959 fn validate_await_usage(&self, context: &AwaitContext) -> bool {
4960 !matches!(
4961 context,
4962 AwaitContext::SyncContext | AwaitContext::Comprehension | AwaitContext::Lambda
4963 )
4964 }
4965
4966 fn detect_await_issues(
4967 &self,
4968 content: &str,
4969 await_expr: &str,
4970 context: &AwaitContext,
4971 ) -> Vec<AwaitIssue> {
4972 let mut issues = Vec::new();
4973
4974 if !self.validate_await_usage(context) {
4975 issues.push(AwaitIssue::IllegalContext);
4976 }
4977
4978 if await_expr.contains("open(") || await_expr.contains("input(") {
4979 issues.push(AwaitIssue::BlockingCall);
4980 }
4981
4982 if !content.contains("timeout") && !content.contains("asyncio.wait_for") {
4983 issues.push(AwaitIssue::TimeoutMissing);
4984 }
4985
4986 issues
4987 }
4988
4989 fn map_to_concurrency_pattern_type(&self, pattern_name: &str) -> ConcurrencyPatternType {
4990 match pattern_name {
4991 "Asyncio Gather" => ConcurrencyPatternType::AsyncioGather,
4992 "Asyncio Wait" => ConcurrencyPatternType::AsyncioWait,
4993 "Asyncio Queue" => ConcurrencyPatternType::AsyncioQueue,
4994 "Asyncio Semaphore" => ConcurrencyPatternType::AsyncioSemaphore,
4995 "Asyncio Lock" => ConcurrencyPatternType::AsyncioLock,
4996 "TaskGroup" => ConcurrencyPatternType::TaskGroup,
4997 "Concurrent Futures" => ConcurrencyPatternType::ConcurrentFutures,
4998 "Asyncio Timeout" => ConcurrencyPatternType::AsyncioTimeout,
4999 "Asyncio Event" => ConcurrencyPatternType::AsyncioEvent,
5000 "Asyncio Condition" => ConcurrencyPatternType::AsyncioCondition,
5001 _ => ConcurrencyPatternType::AsyncioGather,
5002 }
5003 }
5004
5005 fn assess_concurrency_usage_quality(
5006 &self,
5007 content: &str,
5008 pattern_match: &str,
5009 ) -> ConcurrencyUsageQuality {
5010 let has_error_handling = pattern_match.contains("try") || pattern_match.contains("except");
5011 let has_timeout = pattern_match.contains("timeout") || content.contains("asyncio.wait_for");
5012 let has_proper_cleanup =
5013 pattern_match.contains("finally") || pattern_match.contains("async with");
5014
5015 match (has_error_handling, has_timeout, has_proper_cleanup) {
5016 (true, true, true) => ConcurrencyUsageQuality::Excellent,
5017 (true, true, false) | (true, false, true) => ConcurrencyUsageQuality::Good,
5018 (true, false, false) | (false, true, false) | (false, true, true) => {
5019 ConcurrencyUsageQuality::Adequate
5020 }
5021 (false, false, true) => ConcurrencyUsageQuality::Poor,
5022 (false, false, false) => ConcurrencyUsageQuality::Dangerous,
5023 }
5024 }
5025
5026 fn check_concurrency_best_practices(&self, content: &str, _pattern_match: &str) -> bool {
5027 content.contains("async with")
5028 && (content.contains("timeout") || content.contains("asyncio.wait_for"))
5029 && content.contains("try")
5030 }
5031
5032 fn calculate_async_score(
5033 &self,
5034 async_functions: &[AsyncFunctionInfo],
5035 concurrency_patterns: &[ConcurrencyPatternInfo],
5036 performance_issues: &[AsyncPerformanceIssue],
5037 security_issues: &[AsyncSecurityIssue],
5038 ) -> i32 {
5039 let base_score = 50;
5040
5041 let async_bonus = async_functions.len() as i32 * 5;
5043
5044 let concurrency_bonus = concurrency_patterns
5046 .iter()
5047 .map(|p| match p.usage_quality {
5048 ConcurrencyUsageQuality::Excellent => 10,
5049 ConcurrencyUsageQuality::Good => 7,
5050 ConcurrencyUsageQuality::Adequate => 4,
5051 ConcurrencyUsageQuality::Poor => 1,
5052 ConcurrencyUsageQuality::Dangerous => -5,
5053 })
5054 .sum::<i32>();
5055
5056 let performance_penalty = performance_issues
5058 .iter()
5059 .map(|i| match i.severity {
5060 AsyncIssueSeverity::Critical => 20,
5061 AsyncIssueSeverity::High => 15,
5062 AsyncIssueSeverity::Medium => 10,
5063 AsyncIssueSeverity::Low => 5,
5064 AsyncIssueSeverity::Info => 1,
5065 })
5066 .sum::<i32>();
5067
5068 let security_penalty = security_issues
5069 .iter()
5070 .map(|i| match i.severity {
5071 AsyncSecuritySeverity::Critical => 25,
5072 AsyncSecuritySeverity::High => 20,
5073 AsyncSecuritySeverity::Medium => 15,
5074 AsyncSecuritySeverity::Low => 10,
5075 AsyncSecuritySeverity::Info => 5,
5076 })
5077 .sum::<i32>();
5078
5079 (base_score + async_bonus + concurrency_bonus - performance_penalty - security_penalty)
5080 .clamp(0, 100)
5081 }
5082
5083 fn get_async_recommendations(
5084 &self,
5085 async_functions: &[AsyncFunctionInfo],
5086 await_patterns: &[AwaitUsageInfo],
5087 concurrency_patterns: &[ConcurrencyPatternInfo],
5088 performance_issues: &[AsyncPerformanceIssue],
5089 security_issues: &[AsyncSecurityIssue],
5090 ) -> Vec<String> {
5091 let mut recommendations = Vec::new();
5092
5093 if async_functions.is_empty() {
5094 recommendations.push("Consider using async/await for I/O bound operations".to_string());
5095 }
5096
5097 if !performance_issues.is_empty() {
5098 recommendations
5099 .push("Address async performance issues for better efficiency".to_string());
5100 }
5101
5102 if !security_issues.is_empty() {
5103 recommendations.push("Fix async security vulnerabilities".to_string());
5104 }
5105
5106 let has_poor_concurrency = concurrency_patterns.iter().any(|p| {
5107 matches!(
5108 p.usage_quality,
5109 ConcurrencyUsageQuality::Poor | ConcurrencyUsageQuality::Dangerous
5110 )
5111 });
5112
5113 if has_poor_concurrency {
5114 recommendations
5115 .push("Improve concurrency pattern usage with proper error handling".to_string());
5116 }
5117
5118 let has_invalid_await = await_patterns.iter().any(|p| !p.is_valid);
5119 if has_invalid_await {
5120 recommendations.push("Fix invalid await usage in sync contexts".to_string());
5121 }
5122
5123 let missing_timeouts = async_functions.iter().any(|f| !f.has_timeout);
5124 if missing_timeouts {
5125 recommendations.push("Add timeout handling to prevent hanging operations".to_string());
5126 }
5127
5128 recommendations
5129 .push("Use asyncio.gather() for concurrent independent operations".to_string());
5130 recommendations
5131 .push("Implement proper async context managers for resource cleanup".to_string());
5132 recommendations
5133 .push("Consider using Python 3.11+ TaskGroup for structured concurrency".to_string());
5134
5135 recommendations
5136 }
5137
5138 fn parse_type_hint_type(&self, hint_type: &str, captures: ®ex::Captures) -> TypeHintType {
5140 match hint_type {
5141 "union" => {
5142 let types_str = captures.get(1).unwrap().as_str();
5143 let union_types = types_str.split(',').map(|s| s.trim().to_string()).collect();
5144 TypeHintType::UnionType(union_types)
5145 }
5146 "union_new" => {
5147 let type1 = captures.get(1).unwrap().as_str().to_string();
5148 let type2 = captures.get(2).unwrap().as_str().to_string();
5149 TypeHintType::UnionType(vec![type1, type2])
5150 }
5151 "optional" => {
5152 let inner_type = captures.get(1).unwrap().as_str().to_string();
5153 TypeHintType::OptionalType(inner_type)
5154 }
5155 "generic_list" => {
5156 let element_type = captures.get(1).unwrap().as_str().to_string();
5157 TypeHintType::GenericType(GenericTypeInfo {
5158 base_type: "List".to_string(),
5159 type_parameters: vec![element_type],
5160 is_covariant: true,
5161 is_contravariant: false,
5162 })
5163 }
5164 "generic_dict" => {
5165 let key_type = captures.get(1).unwrap().as_str().to_string();
5166 let value_type = captures.get(2).unwrap().as_str().to_string();
5167 TypeHintType::GenericType(GenericTypeInfo {
5168 base_type: "Dict".to_string(),
5169 type_parameters: vec![key_type, value_type],
5170 is_covariant: false,
5171 is_contravariant: false,
5172 })
5173 }
5174 "callable" => {
5175 let params_str = captures.get(1).unwrap().as_str();
5176 let return_type = captures.get(2).unwrap().as_str().to_string();
5177 let parameter_types = if params_str.is_empty() {
5178 Vec::new()
5179 } else {
5180 params_str
5181 .split(',')
5182 .map(|s| s.trim().to_string())
5183 .collect()
5184 };
5185 TypeHintType::CallableType(CallableTypeInfo {
5186 parameter_types,
5187 return_type,
5188 is_async: false,
5189 })
5190 }
5191 "typevar" => {
5192 let var_name = captures.get(1).unwrap().as_str().to_string();
5193 TypeHintType::TypeVarType(TypeVarInfo {
5194 name: var_name,
5195 bounds: Vec::new(),
5196 constraints: Vec::new(),
5197 covariant: false,
5198 contravariant: false,
5199 })
5200 }
5201 "protocol" => TypeHintType::ProtocolType("Protocol".to_string()),
5202 "literal" => {
5203 let values_str = captures.get(1).unwrap().as_str();
5204 let literal_values = values_str
5205 .split(',')
5206 .map(|s| s.trim().to_string())
5207 .collect();
5208 TypeHintType::LiteralType(literal_values)
5209 }
5210 "final" => {
5211 let final_type = captures.get(1).unwrap().as_str().to_string();
5212 TypeHintType::FinalType(final_type)
5213 }
5214 "typeddict" => {
5215 let class_name = captures.get(1).unwrap().as_str().to_string();
5216 TypeHintType::TypedDictType(TypedDictInfo {
5217 name: class_name,
5218 fields: Vec::new(), total: true,
5220 })
5221 }
5222 "generic_alias" => {
5223 let base_type = captures.get(1).unwrap().as_str().to_string();
5224 let element_type = captures.get(2).unwrap().as_str().to_string();
5225 TypeHintType::GenericType(GenericTypeInfo {
5226 base_type,
5227 type_parameters: vec![element_type],
5228 is_covariant: true,
5229 is_contravariant: false,
5230 })
5231 }
5232 _ => TypeHintType::SimpleType("Unknown".to_string()),
5233 }
5234 }
5235
5236 fn is_generic_type(&self, hint_type: &str) -> bool {
5237 matches!(hint_type, "generic_list" | "generic_dict" | "generic_alias")
5238 }
5239
5240 fn has_type_constraints(&self, hint_type: &str) -> bool {
5241 matches!(hint_type, "typevar" | "protocol" | "literal")
5242 }
5243
5244 fn get_modern_feature_type(&self, hint_type: &str) -> Option<ModernTypeFeatureType> {
5245 match hint_type {
5246 "union_new" => Some(ModernTypeFeatureType::UnionSyntaxPy310),
5247 "typeddict" => Some(ModernTypeFeatureType::TypedDict),
5248 "final" => Some(ModernTypeFeatureType::FinalType),
5249 "literal" => Some(ModernTypeFeatureType::LiteralType),
5250 "protocol" => Some(ModernTypeFeatureType::ProtocolType),
5251 "generic_alias" => Some(ModernTypeFeatureType::GenericAlias),
5252 _ => None,
5253 }
5254 }
5255
5256 fn detect_type_safety_issues(&self, content: &str, issues: &mut Vec<TypeSafetyIssue>) {
5257 let any_pattern = Regex::new(r"\bAny\b").unwrap();
5260 let any_count = any_pattern.find_iter(content).count();
5261 if any_count > 5 {
5262 issues.push(TypeSafetyIssue {
5263 issue_type: TypeSafetyIssueType::AnyTypeOveruse,
5264 severity: TypeSafetySeverity::Warning,
5265 location: "Multiple locations".to_string(),
5266 description: format!("Found {any_count} uses of Any type"),
5267 recommendation: "Consider using more specific type hints".to_string(),
5268 });
5269 }
5270
5271 let func_pattern = Regex::new(r"def\s+\w+\s*\([^)]*\)\s*:").unwrap();
5273 let typed_func_pattern = Regex::new(r"def\s+\w+\s*\([^)]*\)\s*->\s*\w+:").unwrap();
5274
5275 let total_functions = func_pattern.find_iter(content).count();
5276 let typed_functions = typed_func_pattern.find_iter(content).count();
5277
5278 if total_functions > typed_functions && total_functions > 0 {
5279 let missing_hints = total_functions - typed_functions;
5280 issues.push(TypeSafetyIssue {
5281 issue_type: TypeSafetyIssueType::MissingTypeHints,
5282 severity: TypeSafetySeverity::Warning,
5283 location: "Function definitions".to_string(),
5284 description: format!("{missing_hints} functions missing return type hints"),
5285 recommendation: "Add return type annotations to functions".to_string(),
5286 });
5287 }
5288
5289 let ignore_pattern = Regex::new(r"#\s*type:\s*ignore").unwrap();
5292 let ignore_count = ignore_pattern.find_iter(content).count();
5293 if ignore_count > 3 {
5294 issues.push(TypeSafetyIssue {
5295 issue_type: TypeSafetyIssueType::TypeIgnoreOveruse,
5296 severity: TypeSafetySeverity::Info,
5297 location: "Multiple locations".to_string(),
5298 description: format!("Found {ignore_count} type: ignore comments"),
5299 recommendation: "Review and fix type issues instead of ignoring them".to_string(),
5300 });
5301 }
5302
5303 if content.contains("typing.List") || content.contains("typing.Dict") {
5305 issues.push(TypeSafetyIssue {
5306 issue_type: TypeSafetyIssueType::DeprecatedTypingSyntax,
5307 severity: TypeSafetySeverity::Info,
5308 location: "Import statements".to_string(),
5309 description: "Using deprecated typing imports".to_string(),
5310 recommendation: "Use built-in generics (list, dict) for Python 3.9+".to_string(),
5311 });
5312 }
5313 }
5314
5315 fn calculate_type_coverage(&self, content: &str, _type_hints: &[TypeHintInfo]) -> f32 {
5316 let func_pattern = Regex::new(r"def\s+\w+").unwrap();
5317 let total_functions = func_pattern.find_iter(content).count();
5318
5319 if total_functions == 0 {
5320 return 0.0;
5321 }
5322
5323 let typed_func_pattern =
5325 Regex::new(r"def\s+\w+\s*\([^)]*:\s*\w+|def\s+\w+\s*\([^)]*\)\s*->\s*\w+").unwrap();
5326 let typed_functions = typed_func_pattern.find_iter(content).count();
5327
5328 (typed_functions as f32 / total_functions as f32) * 100.0
5329 }
5330
5331 fn get_coverage_score(&self, coverage: f32) -> TypeCoverageScore {
5332 match coverage {
5333 score if score >= 90.0 => TypeCoverageScore::Excellent,
5334 score if score >= 70.0 => TypeCoverageScore::Good,
5335 score if score >= 50.0 => TypeCoverageScore::Fair,
5336 score if score >= 30.0 => TypeCoverageScore::Poor,
5337 _ => TypeCoverageScore::Minimal,
5338 }
5339 }
5340
5341 fn get_type_hint_recommendations(
5342 &self,
5343 type_hints: &[TypeHintInfo],
5344 issues: &[TypeSafetyIssue],
5345 coverage: f32,
5346 ) -> Vec<String> {
5347 let mut recommendations = Vec::new();
5348
5349 if coverage < 70.0 {
5350 recommendations.push("Increase type hint coverage for better type safety".to_string());
5351 }
5352
5353 if !issues.is_empty() {
5354 recommendations.push("Address type safety issues identified in the code".to_string());
5355 }
5356
5357 let has_modern_features = type_hints.iter().any(|h| {
5358 h.python_version_required.starts_with("3.8")
5359 || h.python_version_required.starts_with("3.9")
5360 || h.python_version_required.starts_with("3.10")
5361 });
5362
5363 if !has_modern_features {
5364 recommendations.push("Consider using modern Python type features (3.8+)".to_string());
5365 }
5366
5367 let has_complex_types = type_hints.iter().any(|h| {
5368 matches!(
5369 h.complexity,
5370 TypeComplexity::Complex | TypeComplexity::Advanced
5371 )
5372 });
5373
5374 if has_complex_types {
5375 recommendations
5376 .push("Document complex type relationships for maintainability".to_string());
5377 }
5378
5379 if type_hints.iter().any(|h| h.is_generic) {
5380 recommendations
5381 .push("Ensure generic type constraints are properly defined".to_string());
5382 }
5383
5384 recommendations.push("Use type checkers like mypy for static type validation".to_string());
5385 recommendations.push("Consider Protocol types for structural typing".to_string());
5386
5387 recommendations
5388 }
5389
5390 fn get_security_recommendation(&self, vulnerability_type: &VulnerabilityType) -> String {
5392 match vulnerability_type {
5393 VulnerabilityType::SqlInjection => {
5394 "Use parameterized queries or ORM methods".to_string()
5395 }
5396 VulnerabilityType::CommandInjection => {
5397 "Sanitize user input and avoid shell execution".to_string()
5398 }
5399 VulnerabilityType::DeserializationAttack => {
5400 "Use safe deserialization methods like json.loads".to_string()
5401 }
5402 VulnerabilityType::HardcodedSecrets => {
5403 "Use environment variables or secret management".to_string()
5404 }
5405 _ => "Review security implementation".to_string(),
5406 }
5407 }
5408
5409 fn determine_security_level(
5410 &self,
5411 vulnerabilities: &[SecurityVulnerability],
5412 security_features: &[SecurityFeature],
5413 ) -> SecurityLevel {
5414 let critical_vulns = vulnerabilities
5415 .iter()
5416 .filter(|v| matches!(v.severity, VulnerabilitySeverity::Critical))
5417 .count();
5418 let high_vulns = vulnerabilities
5419 .iter()
5420 .filter(|v| matches!(v.severity, VulnerabilitySeverity::High))
5421 .count();
5422
5423 if critical_vulns > 0 {
5424 SecurityLevel::Vulnerable
5425 } else if high_vulns > 2 {
5426 SecurityLevel::Low
5427 } else if security_features.len() > 2 {
5428 SecurityLevel::High
5429 } else {
5430 SecurityLevel::Medium
5431 }
5432 }
5433
5434 fn get_security_recommendations(
5435 &self,
5436 vulnerabilities: &[SecurityVulnerability],
5437 _security_features: &[SecurityFeature],
5438 ) -> Vec<String> {
5439 let mut recommendations = Vec::new();
5440
5441 if !vulnerabilities.is_empty() {
5442 recommendations.push("Address security vulnerabilities identified in code".to_string());
5443 }
5444
5445 recommendations.push("Implement comprehensive input validation".to_string());
5446 recommendations.push("Use secure authentication and authorization".to_string());
5447 recommendations.push("Enable security headers and CSRF protection".to_string());
5448
5449 recommendations
5450 }
5451
5452 fn calculate_performance_score(
5454 &self,
5455 optimizations: &[PerformanceOptimization],
5456 issues: &[PerformanceIssue],
5457 ) -> i32 {
5458 let base_score = 50;
5459 let optimization_bonus = optimizations.len() as i32 * 10;
5460 let issue_penalty = issues
5461 .iter()
5462 .map(|i| match i.severity {
5463 IssueSeverity::Critical => 20,
5464 IssueSeverity::High => 15,
5465 IssueSeverity::Medium => 10,
5466 IssueSeverity::Low => 5,
5467 })
5468 .sum::<i32>();
5469
5470 (base_score + optimization_bonus - issue_penalty).clamp(0, 100)
5471 }
5472
5473 fn get_performance_recommendations(
5474 &self,
5475 _optimizations: &[PerformanceOptimization],
5476 issues: &[PerformanceIssue],
5477 ) -> Vec<String> {
5478 let mut recommendations = Vec::new();
5479
5480 if !issues.is_empty() {
5481 recommendations.push("Address performance issues identified in code".to_string());
5482 }
5483
5484 recommendations.push("Use list comprehensions and generator expressions".to_string());
5485 recommendations.push("Implement caching for expensive operations".to_string());
5486 recommendations.push("Consider async/await for I/O operations".to_string());
5487
5488 recommendations
5489 }
5490
5491 fn analyze_django_specifics(&self, content: &str) -> DjangoAnalysis {
5493 DjangoAnalysis {
5494 models_analysis: self.extract_django_models(content),
5495 views_analysis: self.extract_django_views(content),
5496 middleware_usage: self.extract_django_middleware(content),
5497 security_middleware: self.extract_django_security_middleware(content),
5498 signals_usage: self.extract_django_signals(content),
5499 admin_customization: content.contains("admin.site.register")
5500 || content.contains("ModelAdmin"),
5501 }
5502 }
5503
5504 fn analyze_flask_specifics(&self, content: &str) -> FlaskAnalysis {
5505 FlaskAnalysis {
5506 blueprints: self.extract_flask_blueprints(content),
5507 extensions: self.extract_flask_extensions(content),
5508 error_handlers: self.extract_flask_error_handlers(content),
5509 template_usage: content.contains("render_template"),
5510 session_management: content.contains("session["),
5511 }
5512 }
5513
5514 fn analyze_fastapi_specifics(&self, content: &str) -> FastAPIAnalysis {
5515 FastAPIAnalysis {
5516 router_usage: self.extract_fastapi_routers(content),
5517 dependency_injection: self.extract_fastapi_dependencies(content),
5518 background_tasks: content.contains("BackgroundTasks"),
5519 websocket_endpoints: self.extract_fastapi_websockets(content),
5520 middleware: self.extract_fastapi_middleware(content),
5521 response_models: self.extract_fastapi_response_models(content),
5522 }
5523 }
5524
5525 fn analyze_pytest_specifics(&self, content: &str) -> PytestAnalysis {
5526 PytestAnalysis {
5527 fixtures: self.extract_pytest_fixtures(content),
5528 parametrized_tests: self.extract_pytest_parametrized(content),
5529 markers: self.extract_pytest_markers(content),
5530 plugins: self.extract_pytest_plugins(content),
5531 coverage_setup: content.contains("pytest-cov") || content.contains("coverage"),
5532 }
5533 }
5534
5535 fn extract_django_models(&self, content: &str) -> Vec<DjangoModelInfo> {
5537 let model_pattern = Regex::new(r"class\s+(\w+)\s*\([^)]*Model[^)]*\)").unwrap();
5538 model_pattern
5539 .captures_iter(content)
5540 .map(|captures| {
5541 let model_name = captures.get(1).unwrap().as_str().to_string();
5542 DjangoModelInfo {
5543 name: model_name,
5544 fields: Vec::new(), relationships: Vec::new(),
5546 custom_managers: false,
5547 meta_options: Vec::new(),
5548 }
5549 })
5550 .collect()
5551 }
5552
5553 fn extract_django_views(&self, _content: &str) -> Vec<DjangoViewInfo> {
5554 Vec::new() }
5556
5557 fn extract_django_middleware(&self, _content: &str) -> Vec<String> {
5558 Vec::new() }
5560
5561 fn extract_django_security_middleware(&self, content: &str) -> Vec<String> {
5562 let mut middleware = Vec::new();
5563 if content.contains("SecurityMiddleware") {
5564 middleware.push("SecurityMiddleware".to_string());
5565 }
5566 if content.contains("CsrfViewMiddleware") {
5567 middleware.push("CsrfViewMiddleware".to_string());
5568 }
5569 middleware
5570 }
5571
5572 fn extract_django_signals(&self, content: &str) -> Vec<String> {
5573 let mut signals = Vec::new();
5574 if content.contains("post_save") {
5575 signals.push("post_save".to_string());
5576 }
5577 if content.contains("pre_save") {
5578 signals.push("pre_save".to_string());
5579 }
5580 signals
5581 }
5582
5583 fn extract_flask_blueprints(&self, _content: &str) -> Vec<FlaskBlueprintInfo> {
5584 Vec::new() }
5586
5587 fn extract_flask_extensions(&self, content: &str) -> Vec<String> {
5588 let mut extensions = Vec::new();
5589 if content.contains("Flask-Login") {
5590 extensions.push("Flask-Login".to_string());
5591 }
5592 if content.contains("Flask-SQLAlchemy") {
5593 extensions.push("Flask-SQLAlchemy".to_string());
5594 }
5595 extensions
5596 }
5597
5598 fn extract_flask_error_handlers(&self, _content: &str) -> Vec<String> {
5599 Vec::new() }
5601
5602 fn extract_fastapi_routers(&self, _content: &str) -> Vec<FastAPIRouterInfo> {
5603 Vec::new() }
5605
5606 fn extract_fastapi_dependencies(&self, _content: &str) -> Vec<String> {
5607 Vec::new() }
5609
5610 fn extract_fastapi_websockets(&self, _content: &str) -> Vec<String> {
5611 Vec::new() }
5613
5614 fn extract_fastapi_middleware(&self, _content: &str) -> Vec<String> {
5615 Vec::new() }
5617
5618 fn extract_fastapi_response_models(&self, _content: &str) -> Vec<String> {
5619 Vec::new() }
5621
5622 fn extract_pytest_fixtures(&self, _content: &str) -> Vec<PytestFixtureInfo> {
5623 Vec::new() }
5625
5626 fn extract_pytest_parametrized(&self, _content: &str) -> Vec<String> {
5627 Vec::new() }
5629
5630 fn extract_pytest_markers(&self, _content: &str) -> Vec<String> {
5631 Vec::new() }
5633
5634 fn extract_pytest_plugins(&self, _content: &str) -> Vec<String> {
5635 Vec::new() }
5637
5638 fn get_framework_best_practices(&self, framework: &str) -> Vec<String> {
5639 match framework {
5640 "Django" => vec![
5641 "Use Django ORM instead of raw SQL".to_string(),
5642 "Implement proper authentication and authorization".to_string(),
5643 "Use Django forms for input validation".to_string(),
5644 ],
5645 "Flask" => vec![
5646 "Use blueprints for application modularity".to_string(),
5647 "Implement proper error handling".to_string(),
5648 "Use Flask-WTF for form handling".to_string(),
5649 ],
5650 "FastAPI" => vec![
5651 "Use Pydantic models for request/response validation".to_string(),
5652 "Implement proper dependency injection".to_string(),
5653 "Use async/await for I/O operations".to_string(),
5654 ],
5655 _ => Vec::new(),
5656 }
5657 }
5658
5659 pub fn get_python_recommendations(
5661 &self,
5662 decorators: &[DecoratorInfo],
5663 metaclasses: &[MetaclassInfo],
5664 inheritance: &[InheritanceInfo],
5665 ) -> Vec<String> {
5666 let mut recommendations = Vec::new();
5667
5668 let framework_decorators = decorators.iter().filter(|d| d.framework.is_some()).count();
5670 if framework_decorators > 0 {
5671 recommendations
5672 .push("Consider documenting framework-specific decorator behavior.".to_string());
5673 }
5674
5675 let factory_decorators = decorators.iter().filter(|d| d.is_factory).count();
5676 if factory_decorators > 0 {
5677 recommendations.push(
5678 "Factory decorators detected - ensure proper parameter validation.".to_string(),
5679 );
5680 }
5681
5682 if !metaclasses.is_empty() {
5684 recommendations.push(
5685 "Metaclasses detected - document their behavior and impact on subclasses."
5686 .to_string(),
5687 );
5688 recommendations.push(
5689 "Consider if metaclass functionality could be achieved with simpler patterns."
5690 .to_string(),
5691 );
5692 }
5693
5694 let diamond_inheritance = inheritance
5696 .iter()
5697 .filter(|i| i.has_diamond_inheritance)
5698 .count();
5699 if diamond_inheritance > 0 {
5700 recommendations.push(
5701 "Diamond inheritance detected - verify MRO behavior is as expected.".to_string(),
5702 );
5703 }
5704
5705 let complex_inheritance = inheritance
5706 .iter()
5707 .filter(|i| i.base_classes.len() > 2)
5708 .count();
5709 if complex_inheritance > 0 {
5710 recommendations.push(
5711 "Complex inheritance hierarchies detected - consider composition over inheritance."
5712 .to_string(),
5713 );
5714 }
5715
5716 recommendations
5717 .push("Use type hints for better code documentation and IDE support.".to_string());
5718 recommendations.push("Consider using dataclasses for simple data containers.".to_string());
5719
5720 recommendations
5721 }
5722}
5723
5724impl Default for PythonAnalyzer {
5725 fn default() -> Self {
5726 Self::new()
5727 }
5728}
5729
5730#[cfg(test)]
5731mod tests {
5732 use super::*;
5733
5734 #[test]
5735 fn test_decorator_analysis() {
5736 let analyzer = PythonAnalyzer::new();
5737
5738 let code = "@app.route('/test')\ndef test_view():\n pass";
5739 let decorators = analyzer.analyze_decorators(code).unwrap();
5740
5741 assert!(!decorators.is_empty(), "Should detect decorator in code");
5742
5743 let flask_decorator = decorators
5745 .iter()
5746 .find(|d| d.name == "Flask Route")
5747 .expect("Should detect Flask route decorator");
5748
5749 assert!(
5750 !flask_decorator.parameters.is_empty(),
5751 "Flask route should have path parameter"
5752 );
5753 assert!(
5754 flask_decorator.parameters.contains(&"'/test'".to_string()),
5755 "Should capture the route path"
5756 );
5757 }
5758
5759 #[test]
5760 fn test_metaclass_analysis() {
5761 let analyzer = PythonAnalyzer::new();
5762
5763 let code = "class TestClass(BaseClass, metaclass=RegistryMeta):\n pass";
5764 let metaclasses = analyzer.analyze_metaclasses(code).unwrap();
5765
5766 assert!(!metaclasses.is_empty(), "Should detect metaclass usage");
5767
5768 let test_metaclass = metaclasses
5770 .iter()
5771 .find(|m| m.name == "TestClass")
5772 .expect("Should detect TestClass with metaclass");
5773
5774 assert_eq!(
5776 test_metaclass.metaclass_type, "common",
5777 "Should categorize metaclass pattern correctly"
5778 );
5779 assert!(
5780 !test_metaclass.impact.is_empty(),
5781 "Should analyze metaclass impact"
5782 );
5783 }
5784
5785 #[test]
5786 fn test_inheritance_analysis() {
5787 let analyzer = PythonAnalyzer::new();
5788
5789 let code = "class Child(Parent1, Parent2):\n pass";
5790 let inheritance = analyzer.analyze_inheritance(code).unwrap();
5791
5792 assert!(!inheritance.is_empty(), "Should not be empty");
5793 assert_eq!(inheritance[0].class_name, "Child");
5794 assert_eq!(inheritance[0].base_classes.len(), 2, "Should have 2 items");
5795 }
5796
5797 #[test]
5798 fn test_decorator_parameter_extraction() {
5799 let analyzer = PythonAnalyzer::new();
5800
5801 let decorator = "@app.route('/test', methods=['GET', 'POST'])";
5802 let params = analyzer.extract_decorator_parameters(decorator);
5803
5804 assert!(!params.is_empty(), "Should not be empty");
5805 }
5806
5807 #[test]
5808 fn test_diamond_inheritance_detection() {
5809 let analyzer = PythonAnalyzer::new();
5810
5811 let base_classes = vec!["Parent1".to_string(), "Parent2".to_string()];
5812 assert!(analyzer.detect_diamond_inheritance(&base_classes));
5813
5814 let single_base = vec!["Parent".to_string()];
5815 assert!(!analyzer.detect_diamond_inheritance(&single_base));
5816 }
5817
5818 #[test]
5819 fn test_mixin_identification() {
5820 let analyzer = PythonAnalyzer::new();
5821
5822 let base_classes = vec![
5823 "BaseMixin".to_string(),
5824 "RegularClass".to_string(),
5825 "UtilMix".to_string(),
5826 ];
5827 let mixins = analyzer.identify_mixins(&base_classes);
5828
5829 assert_eq!(mixins.len(), 2, "Should have 2 items");
5830 assert!(mixins.contains(&"BaseMixin".to_string()));
5831 assert!(mixins.contains(&"UtilMix".to_string()));
5832 }
5833
5834 #[test]
5835 fn test_type_hint_analysis() {
5836 let analyzer = PythonAnalyzer::new();
5837
5838 let code = r#"
5839from typing import List, Dict, Union, Optional, Literal, Final
5840from typing_extensions import Protocol
5841
5842def process_data(items: List[str], mapping: Dict[str, int]) -> Optional[str]:
5843 return None
5844
5845def handle_union(value: Union[str, int]) -> str:
5846 return str(value)
5847
5848class MyProtocol(Protocol):
5849 def method(self) -> None: ...
5850
5851CONSTANT: Final[str] = "value"
5852MODE: Literal["read", "write"] = "read"
5853 "#;
5854
5855 let result = analyzer.analyze_type_hints(code).unwrap();
5856
5857 assert!(
5858 !result.type_hints_detected.is_empty(),
5859 "Should not be empty"
5860 );
5861 assert!(result
5862 .type_hints_detected
5863 .iter()
5864 .any(|h| matches!(h.hint_type, TypeHintType::GenericType(_))));
5865 assert!(result
5866 .type_hints_detected
5867 .iter()
5868 .any(|h| matches!(h.hint_type, TypeHintType::UnionType(_))));
5869 assert!(result
5870 .type_hints_detected
5871 .iter()
5872 .any(|h| matches!(h.hint_type, TypeHintType::OptionalType(_))));
5873 assert!(result.overall_coverage > 0.0);
5874 }
5875
5876 #[test]
5877 fn test_modern_type_features() {
5878 let analyzer = PythonAnalyzer::new();
5879
5880 let code = r#"
5881from typing import Final, Literal
5882from typing_extensions import TypedDict
5883
5884class UserDict(TypedDict):
5885 name: str
5886 age: int
5887
5888CONSTANT: Final[int] = 42
5889STATUS: Literal["active", "inactive"] = "active"
5890
5891# Python 3.10+ union syntax
5892def process(value: str | int) -> str | None:
5893 return None
5894 "#;
5895
5896 let result = analyzer.analyze_type_hints(code).unwrap();
5897
5898 assert!(
5899 !result.modern_type_features.is_empty(),
5900 "Should not be empty"
5901 );
5902 assert!(result
5903 .modern_type_features
5904 .iter()
5905 .any(|f| matches!(f.feature_type, ModernTypeFeatureType::TypedDict)));
5906 assert!(result
5907 .modern_type_features
5908 .iter()
5909 .any(|f| matches!(f.feature_type, ModernTypeFeatureType::FinalType)));
5910 assert!(result
5911 .modern_type_features
5912 .iter()
5913 .any(|f| matches!(f.feature_type, ModernTypeFeatureType::LiteralType)));
5914 }
5915
5916 #[test]
5917 fn test_type_safety_issues() {
5918 let analyzer = PythonAnalyzer::new();
5919
5920 let code = r#"
5921from typing import Any
5922
5923def untyped_function():
5924 return "hello"
5925
5926def another_untyped():
5927 pass
5928
5929def bad_any_usage(x: Any, y: Any, z: Any, a: Any, b: Any, c: Any) -> Any:
5930 return x
5931
5932# type: ignore
5933# type: ignore
5934# type: ignore
5935# type: ignore
5936 "#;
5937
5938 let result = analyzer.analyze_type_hints(code).unwrap();
5939
5940 assert!(!result.type_safety_issues.is_empty(), "Should not be empty");
5941 assert!(result
5942 .type_safety_issues
5943 .iter()
5944 .any(|issue| matches!(issue.issue_type, TypeSafetyIssueType::AnyTypeOveruse)));
5945 assert!(result
5946 .type_safety_issues
5947 .iter()
5948 .any(|issue| matches!(issue.issue_type, TypeSafetyIssueType::MissingTypeHints)));
5949 assert!(result
5950 .type_safety_issues
5951 .iter()
5952 .any(|issue| matches!(issue.issue_type, TypeSafetyIssueType::TypeIgnoreOveruse)));
5953 }
5954
5955 #[test]
5956 fn test_type_coverage_calculation() {
5957 let analyzer = PythonAnalyzer::new();
5958
5959 let high_coverage_code = r#"
5961def typed_func1(x: int) -> str:
5962 return str(x)
5963
5964def typed_func2(y: str) -> int:
5965 return len(y)
5966 "#;
5967
5968 let result = analyzer.analyze_type_hints(high_coverage_code).unwrap();
5969 assert!(result.overall_coverage > 50.0);
5970 assert!(matches!(
5971 result.type_coverage_score,
5972 TypeCoverageScore::Good | TypeCoverageScore::Excellent | TypeCoverageScore::Fair
5973 ));
5974
5975 let low_coverage_code = r#"
5977def untyped_func1():
5978 return "hello"
5979
5980def untyped_func2():
5981 return 42
5982
5983def typed_func(x: int) -> str:
5984 return str(x)
5985 "#;
5986
5987 let result = analyzer.analyze_type_hints(low_coverage_code).unwrap();
5988 assert!(result.overall_coverage < 100.0);
5989 }
5990
5991 #[test]
5992 fn test_async_await_analysis() {
5993 let analyzer = PythonAnalyzer::new();
5994 let content = r#"
5995import asyncio
5996
5997async def fetch_data():
5998 await asyncio.sleep(1)
5999 return "data"
6000
6001async def process_items():
6002 results = await asyncio.gather(
6003 fetch_data(),
6004 fetch_data(),
6005 fetch_data()
6006 )
6007 return results
6008
6009async def with_context():
6010 async with asyncio.timeout(5):
6011 return await fetch_data()
6012"#;
6013
6014 let result = analyzer.analyze_async_await(content).unwrap();
6015
6016 assert!(
6018 !result.async_functions_detected.is_empty(),
6019 "Should not be empty"
6020 );
6021 assert!(result.async_functions_detected.len() >= 3);
6022
6023 assert!(
6025 !result.concurrency_patterns.is_empty(),
6026 "Should not be empty"
6027 );
6028
6029 assert!(
6031 !result.modern_async_features.is_empty(),
6032 "Should not be empty"
6033 );
6034
6035 assert!(result.overall_async_score > 50);
6037 }
6038
6039 #[test]
6040 fn test_async_performance_issues() {
6041 let analyzer = PythonAnalyzer::new();
6042 let content = r#"
6043import asyncio
6044import time
6045
6046async def bad_function():
6047 # Blocking operations in async function
6048 time.sleep(1)
6049 with open("file.txt") as f:
6050 data = f.read()
6051
6052 # Sequential awaits that could be concurrent
6053 result1 = await fetch_data()
6054 result2 = await fetch_data()
6055
6056 return result1 + result2
6057
6058async def fetch_data():
6059 await asyncio.sleep(0.1)
6060 return "data"
6061"#;
6062
6063 let result = analyzer.analyze_async_await(content).unwrap();
6064
6065 assert!(
6067 !result.async_performance_issues.is_empty(),
6068 "Should not be empty"
6069 );
6070
6071 let blocking_issues: Vec<_> = result
6073 .async_performance_issues
6074 .iter()
6075 .filter(|issue| {
6076 matches!(
6077 issue.issue_type,
6078 AsyncPerformanceIssueType::BlockingIOInAsync
6079 )
6080 })
6081 .collect();
6082 assert!(!blocking_issues.is_empty(), "Should not be empty");
6083
6084 let concurrency_issues: Vec<_> = result
6086 .async_performance_issues
6087 .iter()
6088 .filter(|issue| {
6089 matches!(
6090 issue.issue_type,
6091 AsyncPerformanceIssueType::MissingConcurrency
6092 )
6093 })
6094 .collect();
6095 assert!(!concurrency_issues.is_empty(), "Should not be empty");
6096 }
6097
6098 #[test]
6099 fn test_async_security_issues() {
6100 let analyzer = PythonAnalyzer::new();
6101 let content = r#"
6102import asyncio
6103
6104shared_data = {}
6105
6106async def unsafe_function():
6107 # No timeout handling
6108 await some_external_service()
6109
6110 # Shared state modification without locking
6111 shared_data["key"] = "value"
6112
6113 # Multiple concurrent operations without proper synchronization
6114 await asyncio.gather(
6115 modify_shared_data(),
6116 modify_shared_data(),
6117 modify_shared_data()
6118 )
6119
6120async def some_external_service():
6121 await asyncio.sleep(1)
6122
6123async def modify_shared_data():
6124 shared_data["counter"] = shared_data.get("counter", 0) + 1
6125"#;
6126
6127 let result = analyzer.analyze_async_await(content).unwrap();
6128
6129 assert!(
6131 !result.async_security_issues.is_empty(),
6132 "Should not be empty"
6133 );
6134
6135 let timeout_issues: Vec<_> = result
6137 .async_security_issues
6138 .iter()
6139 .filter(|issue| matches!(issue.issue_type, AsyncSecurityIssueType::AsyncTimeoutVuln))
6140 .collect();
6141 assert!(!timeout_issues.is_empty(), "Should not be empty");
6142 }
6143
6144 #[test]
6145 fn test_modern_async_features() {
6146 let analyzer = PythonAnalyzer::new();
6147 let content = r#"
6148import asyncio
6149import contextvars
6150
6151async def modern_async():
6152 # Modern async features
6153 async with asyncio.timeout(5):
6154 data = await fetch_data()
6155
6156 # Async comprehension
6157 results = [await process(item) async for item in async_generator()]
6158
6159 # Context variables
6160 context_var = contextvars.ContextVar('user_id')
6161
6162 # TaskGroup (Python 3.11+)
6163 async with asyncio.TaskGroup() as tg:
6164 task1 = tg.create_task(fetch_data())
6165 task2 = tg.create_task(fetch_data())
6166
6167 return results
6168
6169async def async_generator():
6170 for i in range(3):
6171 yield f"item_{i}"
6172
6173async def fetch_data():
6174 return "data"
6175
6176async def process(item):
6177 return f"processed_{item}"
6178
6179if __name__ == "__main__":
6180 asyncio.run(modern_async())
6181"#;
6182
6183 let result = analyzer.analyze_async_await(content).unwrap();
6184
6185 assert!(
6187 !result.modern_async_features.is_empty(),
6188 "Should not be empty"
6189 );
6190
6191 let context_manager_features: Vec<_> = result
6193 .modern_async_features
6194 .iter()
6195 .filter(|f| matches!(f.feature_type, ModernAsyncFeatureType::AsyncContextManager))
6196 .collect();
6197 assert!(!context_manager_features.is_empty(), "Should not be empty");
6198
6199 let task_group_features: Vec<_> = result
6201 .modern_async_features
6202 .iter()
6203 .filter(|f| matches!(f.feature_type, ModernAsyncFeatureType::TaskGroups))
6204 .collect();
6205 assert!(!task_group_features.is_empty(), "Should not be empty");
6206
6207 let asyncio_run_features: Vec<_> = result
6209 .modern_async_features
6210 .iter()
6211 .filter(|f| matches!(f.feature_type, ModernAsyncFeatureType::AsyncioRun))
6212 .collect();
6213 assert!(!asyncio_run_features.is_empty(), "Should not be empty");
6214
6215 assert!(!result.recommendations.is_empty(), "Should not be empty");
6217 }
6218
6219 #[test]
6220 fn test_package_dependency_analysis() {
6221 let analyzer = PythonAnalyzer::new();
6222 let content = r#"
6223import requests
6224import pandas as pd
6225from flask import Flask
6226import numpy
6227from django.db import models
6228
6229django>=3.2.0
6230requests==2.28.1
6231pandas>=1.5.0
6232numpy
6233flask==2.0.1
6234"#;
6235
6236 let result = analyzer.analyze_package_dependencies(content).unwrap();
6237
6238 assert!(!result.dependencies.is_empty(), "Should not be empty");
6240
6241 assert!(!result.import_analysis.is_empty(), "Should not be empty");
6243 assert!(result.import_analysis.len() >= 5);
6244
6245 assert!(result.overall_health_score >= 0);
6247 assert!(result.overall_health_score <= 100);
6248
6249 assert!(!result.dependency_issues.is_empty(), "Should not be empty");
6251
6252 assert!(!result.recommendations.is_empty(), "Should not be empty");
6254 }
6255
6256 #[test]
6257 fn test_dependency_issue_detection() {
6258 let analyzer = PythonAnalyzer::new();
6259 let content = r#"
6260import requests
6261import missing_package
6262from some_package import *
6263
6264# Requirements:
6265requests==2.28.1
6266unused_package==1.0.0
6267imp==1.0.0
6268django
6269"#;
6270
6271 let result = analyzer.analyze_package_dependencies(content).unwrap();
6272
6273 let unused_issues: Vec<_> = result
6275 .dependency_issues
6276 .iter()
6277 .filter(|issue| matches!(issue.issue_type, DependencyIssueType::UnusedDependency))
6278 .collect();
6279 assert!(!unused_issues.is_empty(), "Should not be empty");
6280
6281 let missing_issues: Vec<_> = result
6283 .dependency_issues
6284 .iter()
6285 .filter(|issue| matches!(issue.issue_type, DependencyIssueType::MissingDependency))
6286 .collect();
6287 assert!(!missing_issues.is_empty(), "Should not be empty");
6288
6289 let unpinned_issues: Vec<_> = result
6291 .dependency_issues
6292 .iter()
6293 .filter(|issue| matches!(issue.issue_type, DependencyIssueType::UnpinnedVersion))
6294 .collect();
6295 assert!(!unpinned_issues.is_empty(), "Should not be empty");
6296
6297 let deprecated_issues: Vec<_> = result
6299 .dependency_issues
6300 .iter()
6301 .filter(|issue| matches!(issue.issue_type, DependencyIssueType::DeprecatedPackage))
6302 .collect();
6303 assert!(!deprecated_issues.is_empty(), "Should not be empty");
6304 }
6305
6306 #[test]
6307 fn test_import_analysis() {
6308 let analyzer = PythonAnalyzer::new();
6309 let content = r#"
6310import os
6311import sys
6312import requests
6313import pandas as pd
6314from flask import Flask, render_template
6315from mymodule import function
6316from .relative import local_function
6317from package import *
6318import numpy as np
6319"#;
6320
6321 let result = analyzer.analyze_package_dependencies(content).unwrap();
6322
6323 let stdlib_imports: Vec<_> = result
6325 .import_analysis
6326 .iter()
6327 .filter(|imp| matches!(imp.module_category, ModuleCategory::StandardLibrary))
6328 .collect();
6329 assert!(stdlib_imports.len() >= 2); let third_party_imports: Vec<_> = result
6332 .import_analysis
6333 .iter()
6334 .filter(|imp| matches!(imp.module_category, ModuleCategory::ThirdParty))
6335 .collect();
6336 assert!(third_party_imports.len() >= 3); let star_import_issues: Vec<_> = result
6340 .import_analysis
6341 .iter()
6342 .filter(|imp| {
6343 imp.import_issues
6344 .contains(&ImportIssue::StarImportDangerous)
6345 })
6346 .collect();
6347 assert!(!star_import_issues.is_empty(), "Should not be empty");
6348
6349 let from_imports: Vec<_> = result
6351 .import_analysis
6352 .iter()
6353 .filter(|imp| matches!(imp.import_type, ImportType::FromImport))
6354 .collect();
6355 assert!(!from_imports.is_empty(), "Should not be empty");
6356
6357 let alias_imports: Vec<_> = result
6358 .import_analysis
6359 .iter()
6360 .filter(|imp| matches!(imp.import_type, ImportType::AliasImport))
6361 .collect();
6362 assert!(!alias_imports.is_empty(), "Should not be empty");
6363 }
6364
6365 #[test]
6366 fn test_security_vulnerability_scanning() {
6367 let analyzer = PythonAnalyzer::new();
6368 let content = r#"
6369import urllib3
6370import requests
6371import pyyaml
6372
6373urllib3==1.25.8
6374requests==2.19.1
6375pyyaml==5.3.1
6376"#;
6377
6378 let result = analyzer.analyze_package_dependencies(content).unwrap();
6379
6380 assert!(
6382 !result.security_vulnerabilities.is_empty(),
6383 "Should not be empty"
6384 );
6385
6386 let urllib3_vulns: Vec<_> = result
6388 .security_vulnerabilities
6389 .iter()
6390 .filter(|vuln| vuln.package_name == "urllib3")
6391 .collect();
6392 assert!(!urllib3_vulns.is_empty(), "Should not be empty");
6393
6394 let critical_vulns: Vec<_> = result
6396 .security_vulnerabilities
6397 .iter()
6398 .filter(|vuln| matches!(vuln.severity, SecurityVulnerabilitySeverity::Critical))
6399 .collect();
6400 assert!(!critical_vulns.is_empty(), "Should not be empty");
6401
6402 let vulns_with_cve: Vec<_> = result
6404 .security_vulnerabilities
6405 .iter()
6406 .filter(|vuln| vuln.cve_id.is_some())
6407 .collect();
6408 assert!(!vulns_with_cve.is_empty(), "Should not be empty");
6409 }
6410
6411 #[test]
6412 fn test_virtual_environment_detection() {
6413 let analyzer = PythonAnalyzer::new();
6414 let content = r#"
6415# Virtual environment indicators
6416python -m venv myenv
6417source myenv/bin/activate
6418pip install -r requirements.txt
6419
6420# Conda environment
6421conda create -n myproject python=3.9
6422conda activate myproject
6423
6424# Pipenv
6425pipenv install requests
6426pipenv shell
6427"#;
6428
6429 let result = analyzer.analyze_package_dependencies(content).unwrap();
6430
6431 assert!(
6433 !result.virtual_environments.is_empty(),
6434 "Should not be empty"
6435 );
6436
6437 let venv_envs: Vec<_> = result
6439 .virtual_environments
6440 .iter()
6441 .filter(|env| matches!(env.env_type, VirtualEnvironmentType::Venv))
6442 .collect();
6443 assert!(!venv_envs.is_empty(), "Should not be empty");
6444
6445 let conda_envs: Vec<_> = result
6446 .virtual_environments
6447 .iter()
6448 .filter(|env| matches!(env.env_type, VirtualEnvironmentType::Conda))
6449 .collect();
6450 assert!(!conda_envs.is_empty(), "Should not be empty");
6451
6452 let pipenv_envs: Vec<_> = result
6453 .virtual_environments
6454 .iter()
6455 .filter(|env| matches!(env.env_type, VirtualEnvironmentType::Pipenv))
6456 .collect();
6457 assert!(!pipenv_envs.is_empty(), "Should not be empty");
6458 }
6459
6460 #[test]
6461 fn test_license_analysis() {
6462 let analyzer = PythonAnalyzer::new();
6463 let content = r#"
6464import requests
6465import django
6466import flask
6467
6468requests==2.28.1
6469django==4.1.0
6470flask==2.0.1
6471"#;
6472
6473 let result = analyzer.analyze_package_dependencies(content).unwrap();
6474
6475 assert!(!result.license_analysis.is_empty(), "Should not be empty");
6477
6478 assert_eq!(result.license_analysis.len(), result.dependencies.len());
6480
6481 let compatible_licenses: Vec<_> = result
6483 .license_analysis
6484 .iter()
6485 .filter(|license| matches!(license.compatibility, LicenseCompatibility::Compatible))
6486 .collect();
6487 assert!(!compatible_licenses.is_empty(), "Should not be empty");
6488
6489 for license in &result.license_analysis {
6491 assert!(!license.package_name.is_empty(), "Should not be empty");
6492 assert!(matches!(
6493 license.license_type,
6494 LicenseType::MIT
6495 | LicenseType::Apache2
6496 | LicenseType::BSD2Clause
6497 | LicenseType::BSD3Clause
6498 | LicenseType::Unknown
6499 ));
6500 }
6501 }
6502
6503 #[test]
6504 fn test_modern_features_analysis() {
6505 let analyzer = PythonAnalyzer::new();
6506 let code = r#"
6507 from dataclasses import dataclass
6508 from typing import Optional
6509 import asyncio
6510
6511 @dataclass
6512 class User:
6513 name: str
6514 age: int = 0
6515
6516 async def process_data():
6517 async with asyncio.timeout(10):
6518 data = [x for x in range(100)]
6519 processed = (item * 2 for item in data)
6520 return f"Processed {len(data)} items"
6521
6522 def modern_syntax(value: str | int) -> str:
6523 if (result := calculate(value)) > 10:
6524 return f"Result: {result:.2f}"
6525 return "Too small"
6526
6527 match value:
6528 case int() if value > 100:
6529 print("Large number")
6530 case str():
6531 print("String value")
6532 case _:
6533 print("Other")
6534 "#;
6535
6536 let analysis = analyzer.analyze_modern_features(code).unwrap();
6537
6538 assert!(
6540 !analysis.dataclass_features.is_empty(),
6541 "Should not be empty"
6542 );
6543
6544 assert!(!analysis.fstring_features.is_empty(), "Should not be empty");
6546
6547 assert!(
6549 !analysis.modern_syntax_features.is_empty(),
6550 "Should not be empty"
6551 );
6552
6553 assert!(analysis.overall_modernity_score > 50);
6555 assert!(analysis.overall_modernity_score <= 100);
6556
6557 assert!(analysis
6559 .python_version_detected
6560 .minimum_version
6561 .starts_with("3."));
6562
6563 assert!(!analysis.recommendations.is_empty(), "Should not be empty");
6565 }
6566
6567 #[test]
6568 fn test_dataclass_feature_detection() {
6569 let analyzer = PythonAnalyzer::new();
6570 let code = r#"
6571 from dataclasses import dataclass, field
6572 from typing import List
6573
6574 @dataclass(frozen=True)
6575 class ImmutableUser:
6576 name: str
6577 tags: List[str] = field(default_factory=list)
6578
6579 def __post_init__(self):
6580 object.__setattr__(self, 'processed', True)
6581
6582 class SlottedClass:
6583 __slots__ = ['x', 'y']
6584
6585 def __init__(self, x, y):
6586 self.x = x
6587 self.y = y
6588 "#;
6589
6590 let analysis = analyzer.analyze_modern_features(code).unwrap();
6591
6592 assert!(
6593 !analysis.dataclass_features.is_empty(),
6594 "Should not be empty"
6595 );
6596 let dataclass_info = &analysis.dataclass_features[0];
6597
6598 assert_eq!(
6599 dataclass_info.dataclass_type,
6600 DataclassType::StandardDataclass
6601 );
6602 assert!(dataclass_info
6603 .features_used
6604 .contains(&DataclassFeature::FrozenClass));
6605 assert!(dataclass_info
6606 .features_used
6607 .contains(&DataclassFeature::PostInitProcessing));
6608 assert!(dataclass_info
6609 .features_used
6610 .contains(&DataclassFeature::FieldFactories));
6611 assert!(dataclass_info.best_practices_score > 70);
6612 }
6613
6614 #[test]
6615 fn test_fstring_complexity_analysis() {
6616 let analyzer = PythonAnalyzer::new();
6617 let code = r#"
6618 name = "Alice"
6619 value = 42.5
6620
6621 # Simple f-string
6622 simple = f"Hello {name}"
6623
6624 # Complex f-string with formatting
6625 complex = f"Value: {value:.2f}, Length: {len(name)}"
6626
6627 # Very complex f-string
6628 advanced = f"Result: {calculate_result(value) if value > 0 else 'N/A':.3f}"
6629 "#;
6630
6631 let analysis = analyzer.analyze_modern_features(code).unwrap();
6632
6633 assert!(!analysis.fstring_features.is_empty(), "Should not be empty");
6634 assert!(analysis.fstring_features.len() >= 3);
6635
6636 let complexities: Vec<_> = analysis
6638 .fstring_features
6639 .iter()
6640 .map(|f| &f.complexity)
6641 .collect();
6642
6643 assert!(complexities.contains(&&FStringComplexity::Simple));
6644 assert!(
6645 complexities.contains(&&FStringComplexity::Moderate)
6646 || complexities.contains(&&FStringComplexity::Complex)
6647 );
6648 }
6649
6650 #[test]
6651 fn test_modern_syntax_features() {
6652 let analyzer = PythonAnalyzer::new();
6653 let code = r#"
6654 # Walrus operator (Python 3.8+)
6655 if (length := len(data)) > 10:
6656 print(f"Long data: {length}")
6657
6658 # Union type syntax (Python 3.10+)
6659 def process(value: str | int | None) -> str | None:
6660 return str(value) if value is not None else None
6661
6662 # Positional-only parameters (Python 3.8+)
6663 def func(a, b, /, c, d):
6664 return a + b + c + d
6665
6666 # Modern type hints (Python 3.9+)
6667 data: list[dict[str, int]] = []
6668 mapping: dict[str, set[int]] = {}
6669 "#;
6670
6671 let analysis = analyzer.analyze_modern_features(code).unwrap();
6672
6673 assert!(
6674 !analysis.modern_syntax_features.is_empty(),
6675 "Should not be empty"
6676 );
6677
6678 let syntax_types: Vec<_> = analysis
6679 .modern_syntax_features
6680 .iter()
6681 .map(|s| &s.feature_type)
6682 .collect();
6683
6684 assert!(syntax_types.contains(&&ModernSyntaxType::WalrusOperator));
6685 assert!(syntax_types.contains(&&ModernSyntaxType::TypeUnionOperator));
6686 assert!(syntax_types.contains(&&ModernSyntaxType::PositionalOnlyParams));
6687 assert!(syntax_types.contains(&&ModernSyntaxType::GenericTypeHints));
6688
6689 assert!(analysis.python_version_detected.minimum_version >= "3.10".to_string());
6691 }
6692}