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!("Modern type feature: {}", pattern.name),
2725 is_best_practice: true,
2726 });
2727 }
2728 }
2729 }
2730 }
2731 }
2732
2733 self.detect_type_safety_issues(content, &mut type_safety_issues);
2735
2736 let overall_coverage = self.calculate_type_coverage(content, &type_hints_detected);
2738 let type_coverage_score = self.get_coverage_score(overall_coverage);
2739
2740 let recommendations = self.get_type_hint_recommendations(
2742 &type_hints_detected,
2743 &type_safety_issues,
2744 overall_coverage,
2745 );
2746
2747 Ok(PythonTypeHintAnalysis {
2748 overall_coverage,
2749 type_coverage_score,
2750 type_hints_detected,
2751 type_safety_issues,
2752 modern_type_features,
2753 recommendations,
2754 })
2755 }
2756
2757 pub fn analyze_async_await(&self, content: &str) -> Result<PythonAsyncAwaitAnalysis> {
2759 let mut async_functions_detected = Vec::new();
2760 let mut await_usage_patterns = Vec::new();
2761 let mut concurrency_patterns = Vec::new();
2762 let mut async_performance_issues = Vec::new();
2763 let mut async_security_issues = Vec::new();
2764 let mut modern_async_features = Vec::new();
2765
2766 self.analyze_async_functions(content, &mut async_functions_detected);
2768
2769 self.analyze_await_usage(content, &mut await_usage_patterns);
2771
2772 self.analyze_concurrency_patterns(content, &mut concurrency_patterns);
2774
2775 self.detect_async_performance_issues(content, &mut async_performance_issues);
2777
2778 self.detect_async_security_issues(content, &mut async_security_issues);
2780
2781 self.detect_modern_async_features(content, &mut modern_async_features);
2783
2784 let overall_async_score = self.calculate_async_score(
2786 &async_functions_detected,
2787 &concurrency_patterns,
2788 &async_performance_issues,
2789 &async_security_issues,
2790 );
2791
2792 let recommendations = self.get_async_recommendations(
2794 &async_functions_detected,
2795 &await_usage_patterns,
2796 &concurrency_patterns,
2797 &async_performance_issues,
2798 &async_security_issues,
2799 );
2800
2801 Ok(PythonAsyncAwaitAnalysis {
2802 overall_async_score,
2803 async_functions_detected,
2804 await_usage_patterns,
2805 concurrency_patterns,
2806 async_performance_issues,
2807 async_security_issues,
2808 modern_async_features,
2809 recommendations,
2810 })
2811 }
2812
2813 pub fn analyze_package_dependencies(
2815 &self,
2816 content: &str,
2817 ) -> Result<PythonPackageDependencyAnalysis> {
2818 let mut requirements_files = Vec::new();
2819 let mut dependencies = Vec::new();
2820 let mut dependency_issues = Vec::new();
2821 let mut virtual_environments = Vec::new();
2822 let mut import_analysis = Vec::new();
2823 let mut security_vulnerabilities = Vec::new();
2824 let mut license_analysis = Vec::new();
2825
2826 self.analyze_requirements_files(content, &mut requirements_files, &mut dependencies);
2828
2829 self.analyze_imports(content, &mut import_analysis);
2831
2832 self.detect_dependency_issues(
2834 content,
2835 &dependencies,
2836 &import_analysis,
2837 &mut dependency_issues,
2838 );
2839
2840 self.detect_virtual_environments(content, &mut virtual_environments);
2842
2843 self.scan_security_vulnerabilities(&dependencies, &mut security_vulnerabilities);
2845
2846 self.analyze_licenses(&dependencies, &mut license_analysis);
2848
2849 let overall_health_score = self.calculate_dependency_health_score(
2851 &requirements_files,
2852 &dependencies,
2853 &dependency_issues,
2854 &security_vulnerabilities,
2855 );
2856
2857 let recommendations = self.get_dependency_recommendations(
2859 &requirements_files,
2860 &dependencies,
2861 &dependency_issues,
2862 &import_analysis,
2863 &security_vulnerabilities,
2864 );
2865
2866 Ok(PythonPackageDependencyAnalysis {
2867 overall_health_score,
2868 requirements_files,
2869 dependencies,
2870 dependency_issues,
2871 virtual_environments,
2872 import_analysis,
2873 security_vulnerabilities,
2874 license_analysis,
2875 recommendations,
2876 })
2877 }
2878
2879 fn analyze_requirements_files(
2881 &self,
2882 content: &str,
2883 requirements_files: &mut Vec<RequirementsFileInfo>,
2884 dependencies: &mut Vec<RequirementInfo>,
2885 ) {
2886 for patterns in self.dependency_patterns.values() {
2887 for pattern in patterns {
2888 if pattern.extraction_method == "line_by_line" {
2889 for line in content.lines() {
2891 if let Some(captures) = pattern.pattern.captures(line) {
2892 let package_name = captures.get(1).unwrap().as_str();
2893 let version_spec = captures
2894 .get(2)
2895 .and_then(|m| {
2896 captures
2897 .get(3)
2898 .map(|v| format!("{}{}", m.as_str(), v.as_str()))
2899 })
2900 .unwrap_or_else(|| "*".to_string());
2901
2902 dependencies.push(RequirementInfo {
2903 name: package_name.to_string(),
2904 version_spec,
2905 source: RequirementSource::PyPI,
2906 is_dev_dependency: false,
2907 is_optional: false,
2908 extras: Vec::new(),
2909 markers: Vec::new(),
2910 metadata: self.get_package_metadata(package_name),
2911 });
2912 }
2913 }
2914
2915 if !dependencies.is_empty() {
2916 requirements_files.push(RequirementsFileInfo {
2917 file_path: "requirements.txt".to_string(),
2918 file_type: RequirementsFileType::RequirementsTxt,
2919 dependencies_count: dependencies.len(),
2920 has_version_pins: dependencies.iter().any(|d| d.version_spec != "*"),
2921 has_hashes: false,
2922 uses_constraints: false,
2923 quality_score: self.assess_requirements_quality(dependencies),
2924 });
2925 }
2926 } else if pattern.extraction_method == "toml_array"
2927 && content.contains("pyproject.toml")
2928 {
2929 if let Some(captures) = pattern.pattern.captures(content) {
2931 let deps_str = captures.get(1).unwrap().as_str();
2932 for dep in deps_str.split(',') {
2933 let clean_dep = dep.trim().trim_matches('"').trim_matches('\'');
2934 if !clean_dep.is_empty() {
2935 let parts: Vec<&str> =
2936 clean_dep.split(['>', '<', '=', '!', '~']).collect();
2937 let package_name = parts[0].trim();
2938 let version_spec = if parts.len() > 1 {
2939 clean_dep[package_name.len()..].to_string()
2940 } else {
2941 "*".to_string()
2942 };
2943
2944 dependencies.push(RequirementInfo {
2945 name: package_name.to_string(),
2946 version_spec,
2947 source: RequirementSource::PyPI,
2948 is_dev_dependency: false,
2949 is_optional: false,
2950 extras: Vec::new(),
2951 markers: Vec::new(),
2952 metadata: self.get_package_metadata(package_name),
2953 });
2954 }
2955 }
2956
2957 requirements_files.push(RequirementsFileInfo {
2958 file_path: "pyproject.toml".to_string(),
2959 file_type: RequirementsFileType::PyprojectToml,
2960 dependencies_count: dependencies.len(),
2961 has_version_pins: dependencies.iter().any(|d| d.version_spec != "*"),
2962 has_hashes: false,
2963 uses_constraints: false,
2964 quality_score: self.assess_requirements_quality(dependencies),
2965 });
2966 }
2967 }
2968 }
2969 }
2970 }
2971
2972 fn analyze_imports(&self, content: &str, import_analysis: &mut Vec<ImportAnalysisInfo>) {
2974 if let Some(import_patterns) = self.dependency_patterns.get("imports") {
2975 for pattern in import_patterns {
2976 for captures in pattern.pattern.captures_iter(content) {
2977 let module_name = if pattern.name == "Relative Import" && captures.len() > 2 {
2978 format!(
2979 "{}{}",
2980 captures.get(1).unwrap().as_str(),
2981 captures.get(2).unwrap().as_str()
2982 )
2983 } else {
2984 captures.get(1).unwrap().as_str().to_string()
2985 };
2986
2987 let import_type = self.classify_import_type(&pattern.name);
2988 let module_category = self.categorize_module(&module_name);
2989 let usage_count = content.matches(&module_name).count();
2990 let is_unused = usage_count <= 1; let import_issues =
2992 self.detect_import_issues(&pattern.name, &module_name, content);
2993
2994 import_analysis.push(ImportAnalysisInfo {
2995 import_statement: captures.get(0).unwrap().as_str().to_string(),
2996 import_type,
2997 module_category,
2998 usage_count,
2999 is_unused,
3000 import_issues,
3001 optimization_suggestions: self
3002 .get_import_optimization_suggestions(&pattern.name, &module_name),
3003 });
3004 }
3005 }
3006 }
3007 }
3008
3009 fn detect_dependency_issues(
3011 &self,
3012 _content: &str,
3013 dependencies: &[RequirementInfo],
3014 import_analysis: &[ImportAnalysisInfo],
3015 issues: &mut Vec<DependencyIssue>,
3016 ) {
3017 for dep in dependencies {
3019 let is_imported = import_analysis.iter().any(|imp| {
3020 imp.import_statement.contains(&dep.name)
3021 || imp.import_statement.contains(&dep.name.replace("-", "_"))
3022 });
3023
3024 if !is_imported {
3025 issues.push(DependencyIssue {
3026 issue_type: DependencyIssueType::UnusedDependency,
3027 severity: DependencyIssueSeverity::Low,
3028 affected_packages: vec![dep.name.clone()],
3029 description: format!("Dependency '{}' declared but not imported", dep.name),
3030 recommendation: "Remove unused dependency or add import statement".to_string(),
3031 auto_fixable: false,
3032 });
3033 }
3034 }
3035
3036 for import in import_analysis {
3038 if matches!(import.module_category, ModuleCategory::ThirdParty) {
3039 let module_root = import
3040 .import_statement
3041 .split_whitespace()
3042 .nth(1)
3043 .unwrap_or("")
3044 .split('.')
3045 .next()
3046 .unwrap_or("");
3047
3048 let is_declared = dependencies.iter().any(|dep| {
3049 dep.name == module_root || dep.name.replace("-", "_") == module_root
3050 });
3051
3052 if !is_declared && !module_root.is_empty() {
3053 issues.push(DependencyIssue {
3054 issue_type: DependencyIssueType::MissingDependency,
3055 severity: DependencyIssueSeverity::High,
3056 affected_packages: vec![module_root.to_string()],
3057 description: format!(
3058 "Module '{}' imported but not declared as dependency",
3059 module_root
3060 ),
3061 recommendation: "Add missing dependency to requirements".to_string(),
3062 auto_fixable: true,
3063 });
3064 }
3065 }
3066 }
3067
3068 for dep in dependencies {
3070 if dep.version_spec == "*" || dep.version_spec.is_empty() {
3071 issues.push(DependencyIssue {
3072 issue_type: DependencyIssueType::UnpinnedVersion,
3073 severity: DependencyIssueSeverity::Medium,
3074 affected_packages: vec![dep.name.clone()],
3075 description: format!("Dependency '{}' has no version constraint", dep.name),
3076 recommendation: "Pin dependency versions for reproducible builds".to_string(),
3077 auto_fixable: false,
3078 });
3079 }
3080 }
3081
3082 let deprecated_packages = ["imp", "optparse", "platform", "distutils"];
3084 for dep in dependencies {
3085 if deprecated_packages.contains(&dep.name.as_str()) {
3086 issues.push(DependencyIssue {
3087 issue_type: DependencyIssueType::DeprecatedPackage,
3088 severity: DependencyIssueSeverity::Medium,
3089 affected_packages: vec![dep.name.clone()],
3090 description: format!("Package '{}' is deprecated", dep.name),
3091 recommendation: "Consider migrating to modern alternatives".to_string(),
3092 auto_fixable: false,
3093 });
3094 }
3095 }
3096 }
3097
3098 fn detect_virtual_environments(
3100 &self,
3101 content: &str,
3102 virtual_environments: &mut Vec<VirtualEnvironmentInfo>,
3103 ) {
3104 if content.contains("venv") || content.contains("virtualenv") {
3106 virtual_environments.push(VirtualEnvironmentInfo {
3107 env_type: VirtualEnvironmentType::Venv,
3108 location: "./venv".to_string(),
3109 python_version: "3.x".to_string(),
3110 is_active: true,
3111 packages_count: 0,
3112 env_variables: Vec::new(),
3113 configuration: VirtualEnvironmentConfig {
3114 isolated: true,
3115 system_site_packages: false,
3116 pip_version: None,
3117 setuptools_version: None,
3118 custom_configurations: Vec::new(),
3119 },
3120 });
3121 }
3122
3123 if content.contains("conda") || content.contains("environment.yml") {
3124 virtual_environments.push(VirtualEnvironmentInfo {
3125 env_type: VirtualEnvironmentType::Conda,
3126 location: "conda environment".to_string(),
3127 python_version: "3.x".to_string(),
3128 is_active: true,
3129 packages_count: 0,
3130 env_variables: Vec::new(),
3131 configuration: VirtualEnvironmentConfig {
3132 isolated: true,
3133 system_site_packages: false,
3134 pip_version: None,
3135 setuptools_version: None,
3136 custom_configurations: Vec::new(),
3137 },
3138 });
3139 }
3140
3141 if content.contains("pipenv") || content.contains("Pipfile") {
3142 virtual_environments.push(VirtualEnvironmentInfo {
3143 env_type: VirtualEnvironmentType::Pipenv,
3144 location: "pipenv environment".to_string(),
3145 python_version: "3.x".to_string(),
3146 is_active: true,
3147 packages_count: 0,
3148 env_variables: Vec::new(),
3149 configuration: VirtualEnvironmentConfig {
3150 isolated: true,
3151 system_site_packages: false,
3152 pip_version: None,
3153 setuptools_version: None,
3154 custom_configurations: Vec::new(),
3155 },
3156 });
3157 }
3158 }
3159
3160 fn scan_security_vulnerabilities(
3162 &self,
3163 dependencies: &[RequirementInfo],
3164 vulnerabilities: &mut Vec<SecurityVulnerabilityInfo>,
3165 ) {
3166 let known_vulnerabilities = vec![
3168 ("urllib3", "1.25.8", "CVE-2020-26137", "Critical"),
3169 ("requests", "2.19.1", "CVE-2018-18074", "High"),
3170 ("pyyaml", "5.3.1", "CVE-2020-14343", "High"),
3171 ("django", "2.2.12", "CVE-2020-13254", "Medium"),
3172 ("flask", "1.1.1", "CVE-2019-1010083", "Medium"),
3173 ];
3174
3175 for dep in dependencies {
3176 for (vuln_package, vuln_version, cve_id, severity) in &known_vulnerabilities {
3177 if dep.name == *vuln_package {
3178 let severity_enum = match *severity {
3179 "Critical" => SecurityVulnerabilitySeverity::Critical,
3180 "High" => SecurityVulnerabilitySeverity::High,
3181 "Medium" => SecurityVulnerabilitySeverity::Medium,
3182 _ => SecurityVulnerabilitySeverity::Low,
3183 };
3184
3185 vulnerabilities.push(SecurityVulnerabilityInfo {
3186 cve_id: Some(cve_id.to_string()),
3187 advisory_id: None,
3188 package_name: dep.name.clone(),
3189 affected_versions: vec![vuln_version.to_string()],
3190 fixed_version: Some("Latest".to_string()),
3191 severity: severity_enum,
3192 vulnerability_type: VulnerabilityCategory::CodeExecution,
3193 description: format!(
3194 "Security vulnerability in {} {}",
3195 vuln_package, vuln_version
3196 ),
3197 references: vec![format!(
3198 "https://cve.mitre.org/cgi-bin/cvename.cgi?name={}",
3199 cve_id
3200 )],
3201 published_date: Some("2020-01-01".to_string()),
3202 last_modified: Some("2020-01-01".to_string()),
3203 });
3204 }
3205 }
3206 }
3207 }
3208
3209 fn analyze_licenses(
3211 &self,
3212 dependencies: &[RequirementInfo],
3213 license_analysis: &mut Vec<LicenseInfo>,
3214 ) {
3215 for dep in dependencies {
3216 let license_type = self.parse_license_type(&dep.metadata.license);
3218 let compatibility = self.assess_license_compatibility(&license_type);
3219
3220 license_analysis.push(LicenseInfo {
3221 package_name: dep.name.clone(),
3222 license_type: license_type.clone(),
3223 license_text: None,
3224 compatibility,
3225 commercial_use_allowed: self.is_commercial_use_allowed(&license_type),
3226 distribution_allowed: self.is_distribution_allowed(&license_type),
3227 modification_allowed: self.is_modification_allowed(&license_type),
3228 patent_grant: self.has_patent_grant(&license_type),
3229 copyleft: self.is_copyleft(&license_type),
3230 });
3231 }
3232 }
3233
3234 fn get_package_metadata(&self, package_name: &str) -> PackageMetadata {
3236 PackageMetadata {
3238 description: format!("Package: {}", package_name),
3239 author: "Unknown".to_string(),
3240 license: "MIT".to_string(),
3241 homepage: None,
3242 documentation: None,
3243 last_updated: None,
3244 download_count: None,
3245 maintenance_status: MaintenanceStatus::Unknown,
3246 }
3247 }
3248
3249 fn assess_requirements_quality(
3250 &self,
3251 dependencies: &[RequirementInfo],
3252 ) -> DependencyQualityScore {
3253 let pinned_count = dependencies
3254 .iter()
3255 .filter(|d| d.version_spec != "*")
3256 .count();
3257 let pinned_ratio = if dependencies.is_empty() {
3258 0.0
3259 } else {
3260 pinned_count as f32 / dependencies.len() as f32
3261 };
3262
3263 match pinned_ratio {
3264 r if r >= 0.9 => DependencyQualityScore::Excellent,
3265 r if r >= 0.7 => DependencyQualityScore::Good,
3266 r if r >= 0.5 => DependencyQualityScore::Fair,
3267 r if r >= 0.3 => DependencyQualityScore::Poor,
3268 _ => DependencyQualityScore::Critical,
3269 }
3270 }
3271
3272 fn classify_import_type(&self, pattern_name: &str) -> ImportType {
3273 match pattern_name {
3274 "Standard Import" => ImportType::StandardImport,
3275 "From Import" => ImportType::FromImport,
3276 "Star Import" => ImportType::StarImport,
3277 "Alias Import" => ImportType::AliasImport,
3278 "Relative Import" => ImportType::RelativeImport,
3279 _ => ImportType::StandardImport,
3280 }
3281 }
3282
3283 fn categorize_module(&self, module_name: &str) -> ModuleCategory {
3284 let stdlib_modules = vec![
3286 "os",
3287 "sys",
3288 "re",
3289 "json",
3290 "urllib",
3291 "http",
3292 "datetime",
3293 "collections",
3294 "itertools",
3295 "functools",
3296 "pathlib",
3297 "typing",
3298 "asyncio",
3299 "threading",
3300 "multiprocessing",
3301 "subprocess",
3302 "logging",
3303 "unittest",
3304 "sqlite3",
3305 ];
3306
3307 let root_module = module_name.split('.').next().unwrap_or(module_name);
3308
3309 if stdlib_modules.contains(&root_module) {
3310 ModuleCategory::StandardLibrary
3311 } else if root_module.starts_with('.') {
3312 ModuleCategory::Local
3313 } else {
3314 ModuleCategory::ThirdParty
3315 }
3316 }
3317
3318 fn detect_import_issues(
3319 &self,
3320 pattern_name: &str,
3321 module_name: &str,
3322 content: &str,
3323 ) -> Vec<ImportIssue> {
3324 let mut issues = Vec::new();
3325
3326 if pattern_name == "Star Import" {
3327 issues.push(ImportIssue::StarImportDangerous);
3328 }
3329
3330 if content.contains(&format!("from {} import", module_name))
3332 && content.contains(&format!("import {}", module_name))
3333 {
3334 issues.push(ImportIssue::CircularImport);
3335 }
3336
3337 let deprecated_modules = ["imp", "optparse", "platform.dist"];
3339 if deprecated_modules
3340 .iter()
3341 .any(|&dep| module_name.contains(dep))
3342 {
3343 issues.push(ImportIssue::DeprecatedImport);
3344 }
3345
3346 issues
3347 }
3348
3349 fn get_import_optimization_suggestions(
3350 &self,
3351 pattern_name: &str,
3352 module_name: &str,
3353 ) -> Vec<String> {
3354 let mut suggestions = Vec::new();
3355
3356 if pattern_name == "Star Import" {
3357 suggestions.push("Replace star import with specific imports".to_string());
3358 }
3359
3360 if module_name == "pandas" || module_name == "numpy" {
3361 suggestions.push("Consider lazy loading for large libraries".to_string());
3362 }
3363
3364 suggestions
3365 }
3366
3367 fn parse_license_type(&self, license_str: &str) -> LicenseType {
3368 match license_str.to_lowercase().as_str() {
3369 "mit" => LicenseType::MIT,
3370 "apache-2.0" | "apache 2.0" => LicenseType::Apache2,
3371 "gpl-2.0" | "gpl v2" => LicenseType::GPL2,
3372 "gpl-3.0" | "gpl v3" => LicenseType::GPL3,
3373 "bsd-2-clause" => LicenseType::BSD2Clause,
3374 "bsd-3-clause" => LicenseType::BSD3Clause,
3375 "lgpl" => LicenseType::LGPL,
3376 "mozilla" | "mpl-2.0" => LicenseType::Mozilla,
3377 "unlicense" => LicenseType::Unlicense,
3378 _ => LicenseType::Unknown,
3379 }
3380 }
3381
3382 fn assess_license_compatibility(&self, license_type: &LicenseType) -> LicenseCompatibility {
3383 match license_type {
3384 LicenseType::MIT
3385 | LicenseType::Apache2
3386 | LicenseType::BSD2Clause
3387 | LicenseType::BSD3Clause => LicenseCompatibility::Compatible,
3388 LicenseType::GPL2 | LicenseType::GPL3 => LicenseCompatibility::RequiresReview,
3389 LicenseType::LGPL => LicenseCompatibility::ConditionallyCompatible,
3390 _ => LicenseCompatibility::Unknown,
3391 }
3392 }
3393
3394 fn is_commercial_use_allowed(&self, license_type: &LicenseType) -> bool {
3395 !matches!(license_type, LicenseType::GPL2 | LicenseType::GPL3)
3396 }
3397
3398 fn is_distribution_allowed(&self, license_type: &LicenseType) -> bool {
3399 !matches!(license_type, LicenseType::Proprietary)
3400 }
3401
3402 fn is_modification_allowed(&self, license_type: &LicenseType) -> bool {
3403 !matches!(license_type, LicenseType::Proprietary)
3404 }
3405
3406 fn has_patent_grant(&self, license_type: &LicenseType) -> bool {
3407 matches!(license_type, LicenseType::Apache2 | LicenseType::Mozilla)
3408 }
3409
3410 fn is_copyleft(&self, license_type: &LicenseType) -> bool {
3411 matches!(
3412 license_type,
3413 LicenseType::GPL2 | LicenseType::GPL3 | LicenseType::LGPL
3414 )
3415 }
3416
3417 fn calculate_dependency_health_score(
3418 &self,
3419 requirements_files: &[RequirementsFileInfo],
3420 dependencies: &[RequirementInfo],
3421 issues: &[DependencyIssue],
3422 vulnerabilities: &[SecurityVulnerabilityInfo],
3423 ) -> i32 {
3424 let mut score = 100;
3425
3426 for issue in issues {
3428 let deduction = match issue.severity {
3429 DependencyIssueSeverity::Critical => 20,
3430 DependencyIssueSeverity::High => 15,
3431 DependencyIssueSeverity::Medium => 10,
3432 DependencyIssueSeverity::Low => 5,
3433 DependencyIssueSeverity::Info => 2,
3434 };
3435 score -= deduction;
3436 }
3437
3438 for vuln in vulnerabilities {
3440 let deduction = match vuln.severity {
3441 SecurityVulnerabilitySeverity::Critical => 25,
3442 SecurityVulnerabilitySeverity::High => 20,
3443 SecurityVulnerabilitySeverity::Medium => 15,
3444 SecurityVulnerabilitySeverity::Low => 10,
3445 _ => 5,
3446 };
3447 score -= deduction;
3448 }
3449
3450 if !requirements_files.is_empty() {
3452 score += 10;
3453 }
3454
3455 let pinned_deps = dependencies
3456 .iter()
3457 .filter(|d| d.version_spec != "*")
3458 .count();
3459 let pinned_ratio = if dependencies.is_empty() {
3460 0.0
3461 } else {
3462 pinned_deps as f32 / dependencies.len() as f32
3463 };
3464
3465 score += (pinned_ratio * 20.0) as i32;
3466
3467 score.clamp(0, 100)
3468 }
3469
3470 fn get_dependency_recommendations(
3471 &self,
3472 requirements_files: &[RequirementsFileInfo],
3473 dependencies: &[RequirementInfo],
3474 issues: &[DependencyIssue],
3475 import_analysis: &[ImportAnalysisInfo],
3476 vulnerabilities: &[SecurityVulnerabilityInfo],
3477 ) -> Vec<String> {
3478 let mut recommendations = Vec::new();
3479
3480 if requirements_files.is_empty() {
3481 recommendations
3482 .push("Create a requirements.txt file to track dependencies".to_string());
3483 }
3484
3485 let unpinned_count = dependencies
3486 .iter()
3487 .filter(|d| d.version_spec == "*")
3488 .count();
3489 if unpinned_count > 0 {
3490 recommendations.push(format!(
3491 "Pin {} unpinned dependencies for reproducible builds",
3492 unpinned_count
3493 ));
3494 }
3495
3496 if !vulnerabilities.is_empty() {
3497 recommendations.push(format!(
3498 "Update {} packages with known security vulnerabilities",
3499 vulnerabilities.len()
3500 ));
3501 }
3502
3503 let unused_imports = import_analysis.iter().filter(|i| i.is_unused).count();
3504 if unused_imports > 0 {
3505 recommendations.push(format!(
3506 "Remove {} unused import statements",
3507 unused_imports
3508 ));
3509 }
3510
3511 let star_imports = import_analysis
3512 .iter()
3513 .filter(|i| matches!(i.import_type, ImportType::StarImport))
3514 .count();
3515 if star_imports > 0 {
3516 recommendations.push(format!(
3517 "Replace {} star imports with specific imports",
3518 star_imports
3519 ));
3520 }
3521
3522 let critical_issues = issues
3523 .iter()
3524 .filter(|i| matches!(i.severity, DependencyIssueSeverity::Critical))
3525 .count();
3526 if critical_issues > 0 {
3527 recommendations.push("Address critical dependency issues immediately".to_string());
3528 }
3529
3530 recommendations
3531 .push("Consider using dependency scanning tools like Safety or Bandit".to_string());
3532 recommendations.push("Set up automated dependency updates with Dependabot".to_string());
3533
3534 recommendations
3535 }
3536
3537 pub fn analyze_modern_features(&self, content: &str) -> Result<ModernPythonFeatureAnalysis> {
3539 let mut dataclass_features = Vec::new();
3540 let mut context_manager_features = Vec::new();
3541 let mut fstring_features = Vec::new();
3542 let mut pattern_matching_features = Vec::new();
3543 let mut generator_features = Vec::new();
3544 let mut decorator_features = Vec::new();
3545 let mut modern_syntax_features = Vec::new();
3546
3547 self.analyze_dataclasses(content, &mut dataclass_features);
3549
3550 self.analyze_context_managers(content, &mut context_manager_features);
3552
3553 self.analyze_fstrings(content, &mut fstring_features);
3555
3556 self.analyze_pattern_matching(content, &mut pattern_matching_features);
3558
3559 self.analyze_generators(content, &mut generator_features);
3561
3562 self.analyze_modern_decorators(content, &mut decorator_features);
3564
3565 self.analyze_modern_syntax(content, &mut modern_syntax_features);
3567
3568 let python_version_detected = self.detect_python_version(content);
3570
3571 let overall_modernity_score = self.calculate_modernity_score(
3573 &dataclass_features,
3574 &context_manager_features,
3575 &fstring_features,
3576 &pattern_matching_features,
3577 &generator_features,
3578 &decorator_features,
3579 &modern_syntax_features,
3580 );
3581
3582 let recommendations = self.get_modern_feature_recommendations(
3584 &dataclass_features,
3585 &context_manager_features,
3586 &fstring_features,
3587 &pattern_matching_features,
3588 &generator_features,
3589 &decorator_features,
3590 &modern_syntax_features,
3591 &python_version_detected,
3592 );
3593
3594 Ok(ModernPythonFeatureAnalysis {
3595 overall_modernity_score,
3596 python_version_detected,
3597 dataclass_features,
3598 context_manager_features,
3599 fstring_features,
3600 pattern_matching_features,
3601 generator_features,
3602 decorator_features,
3603 modern_syntax_features,
3604 recommendations,
3605 })
3606 }
3607
3608 fn analyze_dataclasses(&self, content: &str, dataclass_features: &mut Vec<DataclassInfo>) {
3610 if let Some(patterns) = self.modern_feature_patterns.get("dataclass") {
3611 for pattern in patterns {
3612 for captures in pattern.pattern.captures_iter(content) {
3613 let full_match = captures.get(0).unwrap().as_str();
3614
3615 let dataclass_type = match pattern.name.as_str() {
3616 "Dataclass Decorator" => DataclassType::StandardDataclass,
3617 "Pydantic Model" => DataclassType::PydanticModel,
3618 "Named Tuple" => DataclassType::NamedTuple,
3619 _ => DataclassType::StandardDataclass,
3620 };
3621
3622 let class_name = self.extract_dataclass_name(full_match, content);
3623 let fields = self.analyze_dataclass_fields(content, &class_name);
3624 let features_used = self.detect_dataclass_features(content, &class_name);
3625 let complexity = self.assess_dataclass_complexity(&fields, &features_used);
3626 let best_practices_score =
3627 self.score_dataclass_best_practices(&fields, &features_used);
3628 let recommendations =
3629 self.get_dataclass_recommendations(&features_used, best_practices_score);
3630
3631 dataclass_features.push(DataclassInfo {
3632 class_name,
3633 dataclass_type,
3634 fields,
3635 features_used,
3636 complexity,
3637 best_practices_score,
3638 recommendations,
3639 });
3640 }
3641 }
3642 }
3643 }
3644
3645 fn analyze_context_managers(
3647 &self,
3648 content: &str,
3649 context_features: &mut Vec<ContextManagerInfo>,
3650 ) {
3651 if let Some(patterns) = self.modern_feature_patterns.get("context_manager") {
3652 for pattern in patterns {
3653 for captures in pattern.pattern.captures_iter(content) {
3654 let full_match = captures.get(0).unwrap().as_str();
3655
3656 let context_type = match pattern.name.as_str() {
3657 "With Statement" => ContextManagerType::BuiltInFileManager,
3658 "Async With" => ContextManagerType::AsyncContextManager,
3659 "Context Manager Protocol" => ContextManagerType::CustomContextManager,
3660 "Contextlib Manager" => ContextManagerType::ContextlibManager,
3661 _ => ContextManagerType::BuiltInFileManager,
3662 };
3663
3664 let usage_pattern = self.analyze_context_usage_pattern(content, full_match);
3665 let resource_management =
3666 self.assess_resource_management_quality(content, full_match);
3667 let error_handling = self.assess_context_error_handling(content, full_match);
3668 let is_async = pattern.name.contains("Async");
3669 let nested_level = self.calculate_context_nesting_level(content, full_match);
3670 let best_practices_followed =
3671 self.check_context_best_practices(content, full_match);
3672
3673 context_features.push(ContextManagerInfo {
3674 context_type,
3675 usage_pattern,
3676 resource_management,
3677 error_handling,
3678 is_async,
3679 nested_level,
3680 best_practices_followed,
3681 });
3682 }
3683 }
3684 }
3685 }
3686
3687 fn analyze_fstrings(&self, content: &str, fstring_features: &mut Vec<FStringInfo>) {
3689 if let Some(patterns) = self.modern_feature_patterns.get("fstring") {
3690 for pattern in patterns {
3691 for captures in pattern.pattern.captures_iter(content) {
3692 let expression = captures.get(0).unwrap().as_str().to_string();
3693
3694 let complexity = self.assess_fstring_complexity(&expression);
3695 let features_used = self.detect_fstring_features(&expression);
3696 let performance_impact =
3697 self.assess_fstring_performance(&expression, &features_used);
3698 let formatting_quality = self.assess_formatting_quality(&expression);
3699 let readability_score = self.calculate_fstring_readability(&expression);
3700
3701 fstring_features.push(FStringInfo {
3702 expression,
3703 complexity,
3704 features_used,
3705 performance_impact,
3706 formatting_quality,
3707 readability_score,
3708 });
3709 }
3710 }
3711 }
3712 }
3713
3714 fn analyze_pattern_matching(
3716 &self,
3717 content: &str,
3718 pattern_features: &mut Vec<PatternMatchingInfo>,
3719 ) {
3720 if let Some(_patterns) = self.modern_feature_patterns.get("pattern_matching") {
3721 let match_regex = Regex::new(r"(?m)^(\s*)match\s+[^:]+:(.*?)$").unwrap();
3723 for captures in match_regex.captures_iter(content) {
3724 let match_statement = captures.get(0).unwrap().as_str().to_string();
3725
3726 let pattern_types = self.analyze_match_patterns(&match_statement);
3727 let complexity = self.assess_pattern_complexity(&pattern_types);
3728 let has_guards = match_statement.contains("if ");
3729 let is_exhaustive = self.check_pattern_exhaustiveness(&match_statement);
3730 let performance_characteristics =
3731 self.assess_match_performance(&pattern_types, &match_statement);
3732 let best_practices_score =
3733 self.score_pattern_best_practices(&pattern_types, has_guards, is_exhaustive);
3734
3735 pattern_features.push(PatternMatchingInfo {
3736 match_statement,
3737 pattern_types,
3738 complexity,
3739 has_guards,
3740 is_exhaustive,
3741 performance_characteristics,
3742 best_practices_score,
3743 });
3744 }
3745 }
3746 }
3747
3748 fn analyze_generators(&self, content: &str, generator_features: &mut Vec<GeneratorInfo>) {
3750 if let Some(patterns) = self.modern_feature_patterns.get("generator") {
3751 for pattern in patterns {
3752 for captures in pattern.pattern.captures_iter(content) {
3753 let full_match = captures.get(0).unwrap().as_str();
3754
3755 let generator_type = match pattern.name.as_str() {
3756 "Generator Function" => GeneratorType::GeneratorFunction,
3757 "Generator Expression" => GeneratorType::GeneratorExpression,
3758 "Async Generator" => GeneratorType::AsyncGenerator,
3759 _ => GeneratorType::GeneratorFunction,
3760 };
3761
3762 let usage_pattern = self.classify_generator_usage_pattern(content, full_match);
3763 let memory_efficiency =
3764 self.assess_generator_memory_efficiency(content, full_match);
3765 let complexity = self.assess_generator_complexity(content, full_match);
3766 let is_async = pattern.name.contains("Async");
3767 let yield_analysis = self.analyze_yield_usage(content, full_match);
3768 let optimization_opportunities =
3769 self.identify_generator_optimizations(content, full_match);
3770
3771 generator_features.push(GeneratorInfo {
3772 generator_type,
3773 usage_pattern,
3774 memory_efficiency,
3775 complexity,
3776 is_async,
3777 yield_analysis,
3778 optimization_opportunities,
3779 });
3780 }
3781 }
3782 }
3783 }
3784
3785 fn analyze_modern_decorators(
3787 &self,
3788 content: &str,
3789 decorator_features: &mut Vec<ModernDecoratorInfo>,
3790 ) {
3791 let decorator_regex = Regex::new(r"@(\w+)(?:\([^)]*\))?").unwrap();
3792 for captures in decorator_regex.captures_iter(content) {
3793 let decorator_name = captures.get(1).unwrap().as_str().to_string();
3794 let full_decorator = captures.get(0).unwrap().as_str();
3795
3796 let decorator_category = self.classify_decorator_category(&decorator_name);
3797 let usage_pattern = self.analyze_decorator_usage_pattern(content, full_decorator);
3798 let complexity = self.assess_decorator_complexity(full_decorator);
3799 let is_factory = full_decorator.contains('(');
3800 let is_async = content.contains("async def") && content.contains(&decorator_name);
3801 let parameters = self.extract_decorator_parameters(full_decorator);
3802 let best_practices_score =
3803 self.score_decorator_best_practices(&decorator_category, &usage_pattern);
3804
3805 decorator_features.push(ModernDecoratorInfo {
3806 decorator_name,
3807 decorator_category,
3808 usage_pattern,
3809 complexity,
3810 is_factory,
3811 is_async,
3812 parameters,
3813 best_practices_score,
3814 });
3815 }
3816 }
3817
3818 fn analyze_modern_syntax(&self, content: &str, syntax_features: &mut Vec<ModernSyntaxInfo>) {
3820 if let Some(patterns) = self.modern_feature_patterns.get("modern_syntax") {
3821 for pattern in patterns {
3822 let usage_count = pattern.pattern.find_iter(content).count();
3823 if usage_count > 0 {
3824 let feature_type = match pattern.name.as_str() {
3825 "Walrus Operator" => ModernSyntaxType::WalrusOperator,
3826 "Positional Only Parameters" => ModernSyntaxType::PositionalOnlyParams,
3827 "Union Type Operator" => ModernSyntaxType::TypeUnionOperator,
3828 "Dictionary Union" => ModernSyntaxType::DictUnionOperator,
3829 "Generic Type Hints" => ModernSyntaxType::GenericTypeHints,
3830 _ => ModernSyntaxType::WalrusOperator,
3831 };
3832
3833 let complexity = self.assess_syntax_complexity(content, &pattern.pattern);
3834 let best_practices_followed =
3835 self.check_syntax_best_practices(content, &feature_type);
3836 let migration_suggestions =
3837 self.get_syntax_migration_suggestions(&feature_type, usage_count);
3838
3839 syntax_features.push(ModernSyntaxInfo {
3840 feature_type,
3841 python_version: pattern.python_version.clone(),
3842 usage_count,
3843 complexity,
3844 best_practices_followed,
3845 migration_suggestions,
3846 });
3847 }
3848 }
3849 }
3850 }
3851
3852 fn detect_python_version(&self, content: &str) -> PythonVersionDetected {
3854 let mut features_by_version = Vec::new();
3855 let mut compatibility_issues = Vec::new();
3856 let mut minimum_version = "3.6".to_string(); for patterns in self.modern_feature_patterns.values() {
3860 for pattern in patterns {
3861 if pattern.pattern.is_match(content) {
3862 let version_required = pattern.python_version.clone();
3863
3864 if self.is_newer_version(&version_required, &minimum_version) {
3866 minimum_version = version_required.clone();
3867 }
3868
3869 features_by_version.push(VersionFeature {
3870 feature_name: pattern.name.clone(),
3871 python_version: version_required,
3872 usage_count: pattern.pattern.find_iter(content).count(),
3873 is_best_practice: matches!(
3874 pattern.complexity,
3875 FeatureComplexity::Simple | FeatureComplexity::Moderate
3876 ),
3877 });
3878 }
3879 }
3880 }
3881
3882 if content.contains("print ") && !content.contains("print(") {
3884 compatibility_issues.push(CompatibilityIssue {
3885 issue_type: CompatibilityIssueType::SyntaxError,
3886 severity: CompatibilitySeverity::Critical,
3887 feature_name: "Print Statement".to_string(),
3888 required_version: "2.x".to_string(),
3889 description: "Print statement syntax not supported in Python 3".to_string(),
3890 recommendation: "Use print() function instead".to_string(),
3891 });
3892 }
3893
3894 PythonVersionDetected {
3895 minimum_version,
3896 features_by_version,
3897 compatibility_issues,
3898 }
3899 }
3900
3901 fn extract_dataclass_name(&self, _match: &str, content: &str) -> String {
3903 if let Some(class_match) = Regex::new(r"class\s+(\w+)").unwrap().find(content) {
3905 if let Some(captures) = Regex::new(r"class\s+(\w+)")
3906 .unwrap()
3907 .captures(class_match.as_str())
3908 {
3909 return captures.get(1).unwrap().as_str().to_string();
3910 }
3911 }
3912 "UnknownClass".to_string()
3913 }
3914
3915 fn analyze_dataclass_fields(&self, content: &str, class_name: &str) -> Vec<DataclassField> {
3916 let mut fields = Vec::new();
3918
3919 let field_regex = Regex::new(&format!(r"class\s+{}.*?(\w+):\s*(\w+)", class_name)).unwrap();
3921 for captures in field_regex.captures_iter(content) {
3922 if captures.len() >= 3 {
3923 let field_name = captures.get(1).unwrap().as_str().to_string();
3924 let field_type = captures.get(2).unwrap().as_str().to_string();
3925
3926 fields.push(DataclassField {
3927 name: field_name,
3928 field_type,
3929 has_default: false,
3930 is_optional: false,
3931 validation_rules: Vec::new(),
3932 metadata: Vec::new(),
3933 });
3934 }
3935 }
3936
3937 fields
3938 }
3939
3940 fn detect_dataclass_features(&self, content: &str, _class_name: &str) -> Vec<DataclassFeature> {
3941 let mut features = Vec::new();
3942
3943 if content.contains("frozen=True") {
3944 features.push(DataclassFeature::FrozenClass);
3945 }
3946 if content.contains("__slots__") {
3947 features.push(DataclassFeature::SlotsUsage);
3948 }
3949 if content.contains("__post_init__") {
3950 features.push(DataclassFeature::PostInitProcessing);
3951 }
3952 if content.contains("default_factory") {
3953 features.push(DataclassFeature::FieldFactories);
3954 }
3955
3956 features
3957 }
3958
3959 fn assess_dataclass_complexity(
3960 &self,
3961 fields: &[DataclassField],
3962 features: &[DataclassFeature],
3963 ) -> FeatureComplexity {
3964 let complexity_score = fields.len() + features.len() * 2;
3965
3966 match complexity_score {
3967 0..=3 => FeatureComplexity::Simple,
3968 4..=8 => FeatureComplexity::Moderate,
3969 9..=15 => FeatureComplexity::Complex,
3970 _ => FeatureComplexity::Expert,
3971 }
3972 }
3973
3974 fn score_dataclass_best_practices(
3975 &self,
3976 _fields: &[DataclassField],
3977 features: &[DataclassFeature],
3978 ) -> i32 {
3979 let mut score = 60; if features.contains(&DataclassFeature::FrozenClass) {
3983 score += 15; }
3985 if features.contains(&DataclassFeature::SlotsUsage) {
3986 score += 10; }
3988 if features.contains(&DataclassFeature::PostInitProcessing) {
3989 score += 10; }
3991
3992 score.min(100)
3993 }
3994
3995 fn get_dataclass_recommendations(
3996 &self,
3997 features: &[DataclassFeature],
3998 score: i32,
3999 ) -> Vec<String> {
4000 let mut recommendations = Vec::new();
4001
4002 if score < 70 {
4003 recommendations.push("Consider using dataclass best practices".to_string());
4004 }
4005
4006 if !features.contains(&DataclassFeature::SlotsUsage) {
4007 recommendations.push("Consider using __slots__ for memory optimization".to_string());
4008 }
4009
4010 if !features.contains(&DataclassFeature::FrozenClass) {
4011 recommendations.push("Consider making dataclass frozen for immutability".to_string());
4012 }
4013
4014 recommendations
4015 }
4016
4017 #[allow(clippy::too_many_arguments)] fn calculate_modernity_score(
4020 &self,
4021 dataclass_features: &[DataclassInfo],
4022 context_manager_features: &[ContextManagerInfo],
4023 fstring_features: &[FStringInfo],
4024 pattern_matching_features: &[PatternMatchingInfo],
4025 generator_features: &[GeneratorInfo],
4026 decorator_features: &[ModernDecoratorInfo],
4027 modern_syntax_features: &[ModernSyntaxInfo],
4028 ) -> i32 {
4029 let mut score = 50i32; score += (dataclass_features.len() * 8).min(20) as i32;
4033 score += (context_manager_features.len() * 5).min(15) as i32;
4034 score += (fstring_features.len() * 3).min(10) as i32;
4035 score += (pattern_matching_features.len() * 10).min(20) as i32;
4036 score += (generator_features.len() * 4).min(12) as i32;
4037 score += (decorator_features.len() * 2).min(10) as i32;
4038 score += (modern_syntax_features.len() * 3).min(13) as i32;
4039
4040 score.min(100)
4041 }
4042
4043 #[allow(clippy::too_many_arguments)] fn get_modern_feature_recommendations(
4046 &self,
4047 dataclass_features: &[DataclassInfo],
4048 context_manager_features: &[ContextManagerInfo],
4049 fstring_features: &[FStringInfo],
4050 _pattern_matching_features: &[PatternMatchingInfo],
4051 generator_features: &[GeneratorInfo],
4052 _decorator_features: &[ModernDecoratorInfo],
4053 modern_syntax_features: &[ModernSyntaxInfo],
4054 python_version: &PythonVersionDetected,
4055 ) -> Vec<String> {
4056 let mut recommendations = Vec::new();
4057
4058 if dataclass_features.is_empty() {
4059 recommendations.push(
4060 "Consider using @dataclass for data classes instead of manual __init__".to_string(),
4061 );
4062 }
4063
4064 if context_manager_features.is_empty() {
4065 recommendations
4066 .push("Use context managers (with statements) for resource management".to_string());
4067 }
4068
4069 if fstring_features.is_empty() {
4070 recommendations.push(
4071 "Consider using f-strings for string formatting instead of .format()".to_string(),
4072 );
4073 }
4074
4075 if generator_features.is_empty() {
4076 recommendations
4077 .push("Consider using generators for memory-efficient iteration".to_string());
4078 }
4079
4080 if self.is_version_supported("3.10", &python_version.minimum_version) {
4082 recommendations.push(
4083 "Consider using pattern matching (match/case) for complex conditionals".to_string(),
4084 );
4085 }
4086
4087 if self.is_version_supported("3.8", &python_version.minimum_version) {
4088 let has_walrus = modern_syntax_features
4089 .iter()
4090 .any(|f| matches!(f.feature_type, ModernSyntaxType::WalrusOperator));
4091 if !has_walrus {
4092 recommendations.push(
4093 "Consider using walrus operator (:=) for assignment expressions".to_string(),
4094 );
4095 }
4096 }
4097
4098 recommendations
4099 }
4100
4101 fn analyze_context_usage_pattern(
4103 &self,
4104 content: &str,
4105 _context_match: &str,
4106 ) -> ContextUsagePattern {
4107 if content.contains("async with") {
4108 ContextUsagePattern::AsyncContext
4109 } else if content.matches("with").count() > 1 {
4110 ContextUsagePattern::MultipleContexts
4111 } else {
4112 ContextUsagePattern::SingleContext
4113 }
4114 }
4115
4116 fn assess_resource_management_quality(
4118 &self,
4119 content: &str,
4120 _context_match: &str,
4121 ) -> ResourceManagementQuality {
4122 let has_error_handling = content.contains("try") || content.contains("except");
4123 let has_finally = content.contains("finally");
4124 let has_proper_cleanup = content.contains("close()") || content.contains("__exit__");
4125
4126 match (has_error_handling, has_finally, has_proper_cleanup) {
4127 (true, true, true) => ResourceManagementQuality::Excellent,
4128 (true, _, true) => ResourceManagementQuality::Good,
4129 (true, _, _) => ResourceManagementQuality::Adequate,
4130 (false, _, true) => ResourceManagementQuality::Poor,
4131 _ => ResourceManagementQuality::Dangerous,
4132 }
4133 }
4134
4135 fn assess_context_error_handling(
4137 &self,
4138 content: &str,
4139 _context_match: &str,
4140 ) -> ContextErrorHandling {
4141 if content.contains("except") && content.contains("finally") {
4142 ContextErrorHandling::Comprehensive
4143 } else if content.contains("except") {
4144 ContextErrorHandling::Basic
4145 } else if content.contains("try") {
4146 ContextErrorHandling::Minimal
4147 } else {
4148 ContextErrorHandling::None
4149 }
4150 }
4151
4152 fn calculate_context_nesting_level(&self, content: &str, _context_match: &str) -> usize {
4154 let with_count = content.matches("with").count();
4156 if with_count > 3 {
4157 3 } else {
4159 with_count
4160 }
4161 }
4162
4163 fn check_context_best_practices(&self, content: &str, _context_match: &str) -> bool {
4165 let has_appropriate_error_handling = content.contains("except");
4167 let not_overly_nested = self.calculate_context_nesting_level(content, _context_match) <= 2;
4168 let has_resource_cleanup = content.contains("close") || content.contains("__exit__");
4169
4170 has_appropriate_error_handling && not_overly_nested && has_resource_cleanup
4171 }
4172
4173 fn assess_fstring_complexity(&self, expression: &str) -> FStringComplexity {
4175 let brace_count = expression.matches('{').count();
4176 let has_function_calls = expression.contains('(');
4177 let has_format_spec = expression.contains(':');
4178
4179 match (brace_count, has_function_calls, has_format_spec) {
4180 (1, false, false) => FStringComplexity::Simple,
4181 (1..=2, _, true) | (1..=2, true, _) => FStringComplexity::Moderate,
4182 (3..=5, _, _) => FStringComplexity::Complex,
4183 _ => FStringComplexity::Advanced,
4184 }
4185 }
4186
4187 fn detect_fstring_features(&self, expression: &str) -> Vec<FStringFeature> {
4188 let mut features = Vec::new();
4189
4190 if expression.contains('{') && expression.contains('}') {
4191 features.push(FStringFeature::BasicInterpolation);
4192 }
4193 if expression.contains('(') {
4194 features.push(FStringFeature::ExpressionEvaluation);
4195 }
4196 if expression.contains(':') {
4197 features.push(FStringFeature::FormatSpecifiers);
4198 }
4199 if expression.contains('!') {
4200 features.push(FStringFeature::ConversionFlags);
4201 }
4202 if expression.starts_with("rf") || expression.starts_with("fr") {
4203 features.push(FStringFeature::RawFString);
4204 }
4205
4206 features
4207 }
4208
4209 fn assess_fstring_performance(
4210 &self,
4211 _expression: &str,
4212 features: &[FStringFeature],
4213 ) -> PerformanceImpact {
4214 if features.contains(&FStringFeature::ComplexExpressions) {
4215 PerformanceImpact::Negative
4216 } else if features.contains(&FStringFeature::ExpressionEvaluation) {
4217 PerformanceImpact::Neutral
4218 } else {
4219 PerformanceImpact::Positive
4220 }
4221 }
4222
4223 fn assess_formatting_quality(&self, expression: &str) -> FormattingQuality {
4224 let length = expression.len();
4225 let complexity = self.assess_fstring_complexity(expression);
4226
4227 match (length, complexity) {
4228 (0..=50, FStringComplexity::Simple) => FormattingQuality::Excellent,
4229 (0..=100, FStringComplexity::Moderate) => FormattingQuality::Good,
4230 (0..=150, FStringComplexity::Complex) => FormattingQuality::Adequate,
4231 (0..=200, _) => FormattingQuality::Poor,
4232 _ => FormattingQuality::Unreadable,
4233 }
4234 }
4235
4236 fn calculate_fstring_readability(&self, expression: &str) -> i32 {
4237 let mut score = 100;
4238
4239 if expression.len() > 100 {
4240 score -= 20;
4241 }
4242 if expression.matches('{').count() > 3 {
4243 score -= 15;
4244 }
4245 if expression.contains("()") {
4246 score -= 10;
4247 }
4248
4249 score.max(0)
4250 }
4251
4252 fn is_newer_version(&self, version1: &str, version2: &str) -> bool {
4254 let v1 = version1.trim_end_matches('+');
4256 let v2 = version2.trim_end_matches('+');
4257 v1 > v2
4258 }
4259
4260 fn is_version_supported(&self, required: &str, current: &str) -> bool {
4261 self.is_newer_version(current, required) || current == required
4263 }
4264
4265 fn analyze_match_patterns(&self, match_statement: &str) -> Vec<PatternType> {
4267 let mut patterns = Vec::new();
4268
4269 if match_statement.contains("case _:") {
4270 patterns.push(PatternType::WildcardPattern);
4271 }
4272 if match_statement.contains("case ") && match_statement.contains("if ") {
4273 patterns.push(PatternType::GuardedPattern);
4274 }
4275 if match_statement.contains("case [") {
4276 patterns.push(PatternType::SequencePattern);
4277 }
4278 if match_statement.contains("case {") {
4279 patterns.push(PatternType::MappingPattern);
4280 }
4281 if match_statement.contains(" | ") {
4282 patterns.push(PatternType::OrPattern);
4283 }
4284
4285 if patterns.is_empty() {
4286 patterns.push(PatternType::LiteralPattern);
4287 }
4288
4289 patterns
4290 }
4291
4292 fn assess_pattern_complexity(&self, patterns: &[PatternType]) -> PatternComplexity {
4293 let complexity_score = patterns.len()
4294 + patterns
4295 .iter()
4296 .map(|p| match p {
4297 PatternType::GuardedPattern => 3,
4298 PatternType::SequencePattern | PatternType::MappingPattern => 2,
4299 PatternType::OrPattern => 2,
4300 _ => 1,
4301 })
4302 .sum::<usize>();
4303
4304 match complexity_score {
4305 0..=3 => PatternComplexity::Simple,
4306 4..=8 => PatternComplexity::Moderate,
4307 9..=15 => PatternComplexity::Complex,
4308 _ => PatternComplexity::Advanced,
4309 }
4310 }
4311
4312 fn check_pattern_exhaustiveness(&self, match_statement: &str) -> bool {
4313 match_statement.contains("case _:") || match_statement.contains("case default:")
4314 }
4315
4316 fn assess_match_performance(
4317 &self,
4318 patterns: &[PatternType],
4319 _match_statement: &str,
4320 ) -> MatchPerformance {
4321 let has_complex_patterns = patterns.iter().any(|p| {
4322 matches!(
4323 p,
4324 PatternType::GuardedPattern
4325 | PatternType::SequencePattern
4326 | PatternType::MappingPattern
4327 )
4328 });
4329
4330 if has_complex_patterns && patterns.len() > 10 {
4331 MatchPerformance::Poor
4332 } else if has_complex_patterns {
4333 MatchPerformance::Fair
4334 } else if patterns.len() <= 5 {
4335 MatchPerformance::Optimal
4336 } else {
4337 MatchPerformance::Good
4338 }
4339 }
4340
4341 fn score_pattern_best_practices(
4342 &self,
4343 patterns: &[PatternType],
4344 has_guards: bool,
4345 is_exhaustive: bool,
4346 ) -> i32 {
4347 let mut score = 70;
4348
4349 if is_exhaustive {
4350 score += 20;
4351 }
4352 if has_guards {
4353 score += 10; }
4355 if patterns.len() <= 5 {
4356 score += 10; }
4358
4359 score.min(100)
4360 }
4361
4362 fn classify_generator_usage_pattern(
4364 &self,
4365 content: &str,
4366 _generator_match: &str,
4367 ) -> GeneratorUsagePattern {
4368 if content.contains("yield from") {
4369 GeneratorUsagePattern::Pipeline
4370 } else if content.contains("while True") {
4371 GeneratorUsagePattern::InfiniteSequence
4372 } else if content.contains("map") || content.contains("filter") {
4373 GeneratorUsagePattern::DataTransformation
4374 } else {
4375 GeneratorUsagePattern::SimpleIteration
4376 }
4377 }
4378
4379 fn assess_generator_memory_efficiency(
4380 &self,
4381 content: &str,
4382 _generator_match: &str,
4383 ) -> MemoryEfficiency {
4384 let has_large_data_structures =
4385 content.contains("list(") || content.contains("[") && content.len() > 100;
4386 let uses_yield_properly = content.contains("yield") && !content.contains("return [");
4387
4388 match (uses_yield_properly, has_large_data_structures) {
4389 (true, false) => MemoryEfficiency::Excellent,
4390 (true, true) => MemoryEfficiency::Good,
4391 (false, false) => MemoryEfficiency::Adequate,
4392 (false, true) => MemoryEfficiency::Poor,
4393 }
4394 }
4395
4396 fn assess_generator_complexity(
4397 &self,
4398 content: &str,
4399 _generator_match: &str,
4400 ) -> GeneratorComplexity {
4401 let yield_count = content.matches("yield").count();
4402 let has_complex_logic = content.contains("if") && content.contains("for");
4403 let has_nested_loops = content.matches("for").count() > 1;
4404
4405 match (yield_count, has_complex_logic, has_nested_loops) {
4406 (1, false, false) => GeneratorComplexity::Simple,
4407 (1..=3, _, false) => GeneratorComplexity::Moderate,
4408 (_, true, true) => GeneratorComplexity::Advanced,
4409 _ => GeneratorComplexity::Complex,
4410 }
4411 }
4412
4413 fn analyze_yield_usage(&self, content: &str, _generator_match: &str) -> YieldAnalysis {
4414 YieldAnalysis {
4415 yield_count: content.matches("yield").count(),
4416 has_yield_from: content.contains("yield from"),
4417 has_send_values: content.contains(".send("),
4418 has_throw_values: content.contains(".throw("),
4419 has_close_handling: content.contains(".close("),
4420 }
4421 }
4422
4423 fn identify_generator_optimizations(
4424 &self,
4425 content: &str,
4426 _generator_match: &str,
4427 ) -> Vec<String> {
4428 let mut optimizations = Vec::new();
4429
4430 if content.contains("list(") && content.contains("yield") {
4431 optimizations.push("Avoid converting generator to list unless necessary".to_string());
4432 }
4433
4434 if content.matches("for").count() > 2 {
4435 optimizations
4436 .push("Consider breaking complex generator into smaller functions".to_string());
4437 }
4438
4439 if !content.contains("yield from") && content.contains("for") && content.contains("yield") {
4440 optimizations
4441 .push("Consider using 'yield from' for delegating to sub-generators".to_string());
4442 }
4443
4444 optimizations
4445 }
4446
4447 fn classify_decorator_category(&self, decorator_name: &str) -> DecoratorCategory {
4449 match decorator_name {
4450 "property" | "staticmethod" | "classmethod" => DecoratorCategory::BuiltIn,
4451 "wraps" | "singledispatch" | "cache" | "lru_cache" => {
4452 DecoratorCategory::FunctoolsDecorator
4453 }
4454 "app.route" | "api.route" | "route" => DecoratorCategory::WebFramework,
4455 "pytest.fixture" | "pytest.mark" | "unittest.mock" => DecoratorCategory::Testing,
4456 "dataclass" | "validator" => DecoratorCategory::DataValidation,
4457 _ => DecoratorCategory::Custom,
4458 }
4459 }
4460
4461 fn analyze_decorator_usage_pattern(
4462 &self,
4463 content: &str,
4464 decorator: &str,
4465 ) -> DecoratorUsagePattern {
4466 let decorator_context = self.get_decorator_context(content, decorator);
4467
4468 if decorator_context.contains('@') && decorator_context.matches('@').count() > 1 {
4469 DecoratorUsagePattern::StackedDecorators
4470 } else if decorator.contains('(') {
4471 DecoratorUsagePattern::ParameterizedDecorator
4472 } else {
4473 DecoratorUsagePattern::SingleDecorator
4474 }
4475 }
4476
4477 fn get_decorator_context(&self, content: &str, decorator: &str) -> String {
4478 if let Some(pos) = content.find(decorator) {
4480 let start = pos.saturating_sub(100);
4481 let end = (pos + decorator.len() + 100).min(content.len());
4482 content[start..end].to_string()
4483 } else {
4484 decorator.to_string()
4485 }
4486 }
4487
4488 fn assess_decorator_complexity(&self, decorator: &str) -> DecoratorComplexity {
4489 if decorator.contains('(') && decorator.contains(',') {
4490 DecoratorComplexity::Complex
4491 } else if decorator.contains('(') {
4492 DecoratorComplexity::Moderate
4493 } else {
4494 DecoratorComplexity::Simple
4495 }
4496 }
4497
4498 fn score_decorator_best_practices(
4499 &self,
4500 category: &DecoratorCategory,
4501 usage_pattern: &DecoratorUsagePattern,
4502 ) -> i32 {
4503 let mut score = 80;
4504
4505 match category {
4506 DecoratorCategory::BuiltIn => score += 10,
4507 DecoratorCategory::FunctoolsDecorator => score += 15,
4508 DecoratorCategory::Performance => score += 10,
4509 _ => {}
4510 }
4511
4512 match usage_pattern {
4513 DecoratorUsagePattern::SingleDecorator => score += 5,
4514 DecoratorUsagePattern::StackedDecorators => score -= 5, _ => {}
4516 }
4517
4518 score.min(100)
4519 }
4520
4521 fn assess_syntax_complexity(&self, content: &str, pattern: &Regex) -> SyntaxComplexity {
4523 let usage_count = pattern.find_iter(content).count();
4524 let total_lines = content.lines().count();
4525 let usage_density = if total_lines > 0 {
4526 usage_count as f32 / total_lines as f32
4527 } else {
4528 0.0
4529 };
4530
4531 match usage_density {
4532 d if d > 0.1 => SyntaxComplexity::Expert,
4533 d if d > 0.05 => SyntaxComplexity::Complex,
4534 d if d > 0.01 => SyntaxComplexity::Moderate,
4535 _ => SyntaxComplexity::Simple,
4536 }
4537 }
4538
4539 fn check_syntax_best_practices(&self, content: &str, feature_type: &ModernSyntaxType) -> bool {
4540 match feature_type {
4541 ModernSyntaxType::WalrusOperator => {
4542 content.matches(":=").count() <= content.lines().count() / 10
4544 }
4545 ModernSyntaxType::PositionalOnlyParams => {
4546 content.contains("def ") && content.contains("/")
4548 }
4549 ModernSyntaxType::TypeUnionOperator => {
4550 !content.contains("typing.Union") || content.contains(" | ")
4552 }
4553 _ => true, }
4555 }
4556
4557 fn get_syntax_migration_suggestions(
4558 &self,
4559 feature_type: &ModernSyntaxType,
4560 usage_count: usize,
4561 ) -> Vec<String> {
4562 let mut suggestions = Vec::new();
4563
4564 match feature_type {
4565 ModernSyntaxType::WalrusOperator => {
4566 if usage_count > 10 {
4567 suggestions.push(
4568 "Consider if all walrus operator usages improve readability".to_string(),
4569 );
4570 }
4571 suggestions.push(
4572 "Use walrus operator to reduce code duplication in conditions".to_string(),
4573 );
4574 }
4575 ModernSyntaxType::GenericTypeHints => {
4576 suggestions.push(
4577 "Migrate from typing.List/Dict to built-in list/dict for type hints"
4578 .to_string(),
4579 );
4580 }
4581 ModernSyntaxType::TypeUnionOperator => {
4582 suggestions.push(
4583 "Replace typing.Union with | operator for cleaner type hints".to_string(),
4584 );
4585 }
4586 _ => {}
4587 }
4588
4589 suggestions
4590 }
4591
4592 fn analyze_async_functions(&self, content: &str, async_functions: &mut Vec<AsyncFunctionInfo>) {
4594 for patterns in self.async_patterns.values() {
4595 for pattern in patterns {
4596 if pattern.pattern_type == "function"
4597 || pattern.pattern_type == "generator"
4598 || pattern.pattern_type == "context_manager"
4599 || pattern.pattern_type == "iterator"
4600 {
4601 for captures in pattern.pattern.captures_iter(content) {
4602 let full_match = captures.get(0).unwrap().as_str();
4603 let function_name = captures
4604 .get(1)
4605 .map(|m| m.as_str().to_string())
4606 .unwrap_or_else(|| "anonymous".to_string());
4607
4608 let function_type =
4609 self.determine_async_function_type(&pattern.name, full_match);
4610 let complexity = self.assess_async_complexity(content, full_match);
4611 let coroutine_type = self.classify_coroutine_type(content);
4612 let error_handling = self.assess_error_handling(content, full_match);
4613 let has_timeout = self.has_timeout_handling(content, full_match);
4614 let uses_context_manager =
4615 self.uses_async_context_manager(content, full_match);
4616
4617 async_functions.push(AsyncFunctionInfo {
4618 name: function_name,
4619 function_type,
4620 complexity,
4621 coroutine_type,
4622 error_handling,
4623 has_timeout,
4624 uses_context_manager,
4625 location: full_match.to_string(),
4626 });
4627 }
4628 }
4629 }
4630 }
4631 }
4632
4633 fn analyze_await_usage(&self, content: &str, await_patterns: &mut Vec<AwaitUsageInfo>) {
4635 let await_pattern = Regex::new(r"await\s+([^;\n]+)").unwrap();
4636
4637 for captures in await_pattern.captures_iter(content) {
4638 let full_match = captures.get(0).unwrap().as_str();
4639 let await_expr = captures.get(1).unwrap().as_str();
4640
4641 let context = self.determine_await_context(content, full_match);
4642 let usage_pattern = self.classify_await_usage_pattern(content, await_expr);
4643 let is_valid = self.validate_await_usage(&context);
4644 let potential_issues = self.detect_await_issues(content, await_expr, &context);
4645
4646 await_patterns.push(AwaitUsageInfo {
4647 location: full_match.to_string(),
4648 context,
4649 usage_pattern,
4650 is_valid,
4651 potential_issues,
4652 });
4653 }
4654 }
4655
4656 fn analyze_concurrency_patterns(
4658 &self,
4659 content: &str,
4660 concurrency_patterns: &mut Vec<ConcurrencyPatternInfo>,
4661 ) {
4662 for patterns in self.async_patterns.values() {
4663 for pattern in patterns {
4664 if pattern.pattern_type == "concurrency" {
4665 for captures in pattern.pattern.captures_iter(content) {
4666 let full_match = captures.get(0).unwrap().as_str();
4667
4668 let pattern_type = self.map_to_concurrency_pattern_type(&pattern.name);
4669 let usage_quality =
4670 self.assess_concurrency_usage_quality(content, full_match);
4671 let best_practices_followed =
4672 self.check_concurrency_best_practices(content, full_match);
4673
4674 concurrency_patterns.push(ConcurrencyPatternInfo {
4675 pattern_type,
4676 usage_quality,
4677 performance_impact: pattern.performance_impact.clone(),
4678 location: full_match.to_string(),
4679 best_practices_followed,
4680 });
4681 }
4682 }
4683 }
4684 }
4685 }
4686
4687 fn detect_async_performance_issues(
4689 &self,
4690 content: &str,
4691 issues: &mut Vec<AsyncPerformanceIssue>,
4692 ) {
4693 if content.contains("async def")
4695 && (content.contains("time.sleep")
4696 || content.contains("open(")
4697 || content.contains("input("))
4698 {
4699 issues.push(AsyncPerformanceIssue {
4700 issue_type: AsyncPerformanceIssueType::BlockingIOInAsync,
4701 severity: AsyncIssueSeverity::High,
4702 location: "Async function with blocking operations".to_string(),
4703 description: "Blocking I/O operation in async function".to_string(),
4704 recommendation: "Use async I/O operations or run_in_executor".to_string(),
4705 estimated_impact: AsyncPerformanceImpact::Critical,
4706 });
4707 }
4708
4709 let await_loop_pattern = Regex::new(r"for\s+.*?:\s*.*?await\s+").unwrap();
4711 for captures in await_loop_pattern.captures_iter(content) {
4712 let full_match = captures.get(0).unwrap().as_str();
4713 issues.push(AsyncPerformanceIssue {
4714 issue_type: AsyncPerformanceIssueType::AwaitInLoop,
4715 severity: AsyncIssueSeverity::Medium,
4716 location: full_match.to_string(),
4717 description: "Sequential await in loop - consider asyncio.gather()".to_string(),
4718 recommendation:
4719 "Use asyncio.gather() or asyncio.as_completed() for concurrent execution"
4720 .to_string(),
4721 estimated_impact: AsyncPerformanceImpact::Negative,
4722 });
4723 }
4724
4725 let sequential_await_pattern = Regex::new(r"await\s+\w+.*\n.*await\s+\w+").unwrap();
4727 for captures in sequential_await_pattern.captures_iter(content) {
4728 let full_match = captures.get(0).unwrap().as_str();
4729 issues.push(AsyncPerformanceIssue {
4730 issue_type: AsyncPerformanceIssueType::MissingConcurrency,
4731 severity: AsyncIssueSeverity::Medium,
4732 location: full_match.to_string(),
4733 description: "Sequential await calls could be concurrent".to_string(),
4734 recommendation: "Consider using asyncio.gather() for independent operations"
4735 .to_string(),
4736 estimated_impact: AsyncPerformanceImpact::Negative,
4737 });
4738 }
4739 }
4740
4741 fn detect_async_security_issues(&self, content: &str, issues: &mut Vec<AsyncSecurityIssue>) {
4743 let await_pattern = Regex::new(r"await\s+").unwrap();
4745 let timeout_count = content.matches("asyncio.wait_for").count()
4746 + content.matches("asyncio.timeout").count();
4747 let await_count = await_pattern.find_iter(content).count();
4748
4749 if await_count > timeout_count + 2 {
4750 issues.push(AsyncSecurityIssue {
4751 issue_type: AsyncSecurityIssueType::AsyncTimeoutVuln,
4752 severity: AsyncSecuritySeverity::Medium,
4753 location: "Multiple async operations".to_string(),
4754 description: "Missing timeout handling in async operations".to_string(),
4755 recommendation: "Add timeouts to prevent DoS attacks".to_string(),
4756 });
4757 }
4758
4759 let shared_state_pattern =
4761 Regex::new(r"(?:global|class)\s+\w+.*=.*\n.*async\s+def.*\w+.*=").unwrap();
4762 for captures in shared_state_pattern.captures_iter(content) {
4763 let full_match = captures.get(0).unwrap().as_str();
4764 if !content.contains("asyncio.Lock") && !content.contains("asyncio.Semaphore") {
4765 issues.push(AsyncSecurityIssue {
4766 issue_type: AsyncSecurityIssueType::SharedStateNoLock,
4767 severity: AsyncSecuritySeverity::High,
4768 location: full_match.to_string(),
4769 description: "Shared mutable state without proper locking".to_string(),
4770 recommendation: "Use asyncio.Lock or asyncio.Semaphore for thread safety"
4771 .to_string(),
4772 });
4773 }
4774 }
4775
4776 if content.contains("asyncio.gather")
4778 && !content.contains("asyncio.Lock")
4779 && content.matches("=").count() > 3
4780 {
4781 issues.push(AsyncSecurityIssue {
4782 issue_type: AsyncSecurityIssueType::AsyncRaceCondition,
4783 severity: AsyncSecuritySeverity::Medium,
4784 location: "Concurrent operations".to_string(),
4785 description: "Potential race condition in concurrent operations".to_string(),
4786 recommendation: "Review shared resource access and add synchronization".to_string(),
4787 });
4788 }
4789 }
4790
4791 fn detect_modern_async_features(&self, content: &str, features: &mut Vec<ModernAsyncFeature>) {
4793 let modern_patterns = &[
4794 (
4795 "async with",
4796 ModernAsyncFeatureType::AsyncContextManager,
4797 "3.7+",
4798 ),
4799 (
4800 "asyncio.TaskGroup",
4801 ModernAsyncFeatureType::TaskGroups,
4802 "3.11+",
4803 ),
4804 (
4805 "asyncio.timeout",
4806 ModernAsyncFeatureType::AsyncioTimeout,
4807 "3.11+",
4808 ),
4809 ("async for", ModernAsyncFeatureType::AsyncIterators, "3.7+"),
4810 ("contextvars", ModernAsyncFeatureType::ContextVars, "3.7+"),
4811 ("asyncio.run", ModernAsyncFeatureType::AsyncioRun, "3.7+"),
4812 ];
4813
4814 for (pattern_str, feature_type, version) in modern_patterns {
4815 let count = content.matches(pattern_str).count();
4816 if count > 0 {
4817 features.push(ModernAsyncFeature {
4818 feature_type: feature_type.clone(),
4819 python_version: version.to_string(),
4820 usage_count: count,
4821 description: format!("Modern async feature: {}", pattern_str),
4822 is_best_practice: true,
4823 });
4824 }
4825 }
4826
4827 let async_comp_pattern = Regex::new(r"\[.*async\s+for.*\]|\{.*async\s+for.*\}").unwrap();
4829 let comp_count = async_comp_pattern.find_iter(content).count();
4830 if comp_count > 0 {
4831 features.push(ModernAsyncFeature {
4832 feature_type: ModernAsyncFeatureType::AsyncComprehensions,
4833 python_version: "3.6+".to_string(),
4834 usage_count: comp_count,
4835 description: "Async comprehensions for concurrent iteration".to_string(),
4836 is_best_practice: true,
4837 });
4838 }
4839 }
4840
4841 fn determine_async_function_type(
4843 &self,
4844 pattern_name: &str,
4845 _full_match: &str,
4846 ) -> AsyncFunctionType {
4847 match pattern_name {
4848 "Async Function" => AsyncFunctionType::RegularAsync,
4849 "Async Generator" => AsyncFunctionType::AsyncGenerator,
4850 "Async Context Manager" => AsyncFunctionType::AsyncContextManager,
4851 "Async Iterator" => AsyncFunctionType::AsyncIterator,
4852 _ => AsyncFunctionType::RegularAsync,
4853 }
4854 }
4855
4856 fn assess_async_complexity(&self, _content: &str, function_match: &str) -> AsyncComplexity {
4857 let await_count = function_match.matches("await").count();
4858 let try_count = function_match.matches("try").count();
4859 let gather_count = function_match.matches("gather").count();
4860
4861 match (await_count, try_count, gather_count) {
4862 (0..=1, 0, 0) => AsyncComplexity::Simple,
4863 (2..=3, 0..=1, 0..=1) => AsyncComplexity::Moderate,
4864 (4..=6, 1..=2, 0..=2) => AsyncComplexity::Complex,
4865 _ => AsyncComplexity::Advanced,
4866 }
4867 }
4868
4869 fn classify_coroutine_type(&self, content: &str) -> CoroutineType {
4870 if content.contains("asyncio") {
4871 CoroutineType::Framework("asyncio".to_string())
4872 } else if content.contains("trio") {
4873 CoroutineType::Framework("trio".to_string())
4874 } else if content.contains("curio") {
4875 CoroutineType::Framework("curio".to_string())
4876 } else if content.contains("yield from") {
4877 CoroutineType::Generator
4878 } else {
4879 CoroutineType::Native
4880 }
4881 }
4882
4883 fn assess_error_handling(&self, _content: &str, function_match: &str) -> AsyncErrorHandling {
4884 if function_match.contains("asyncio.timeout") || function_match.contains("asyncio.wait_for")
4885 {
4886 AsyncErrorHandling::Robust
4887 } else if function_match.contains("timeout") {
4888 AsyncErrorHandling::Timeout
4889 } else if function_match.contains("try") && function_match.contains("except") {
4890 AsyncErrorHandling::Basic
4891 } else {
4892 AsyncErrorHandling::None
4893 }
4894 }
4895
4896 fn has_timeout_handling(&self, _content: &str, function_match: &str) -> bool {
4897 function_match.contains("timeout") || function_match.contains("asyncio.wait_for")
4898 }
4899
4900 fn uses_async_context_manager(&self, _content: &str, function_match: &str) -> bool {
4901 function_match.contains("async with")
4902 }
4903
4904 fn determine_await_context(&self, content: &str, await_match: &str) -> AwaitContext {
4905 if content.contains("async def") {
4907 if await_match.contains("__aenter__") || await_match.contains("__aexit__") {
4908 AwaitContext::AsyncContextManager
4909 } else if await_match.contains("__aiter__") || await_match.contains("__anext__") {
4910 AwaitContext::AsyncIterator
4911 } else if await_match.contains("yield") {
4912 AwaitContext::AsyncGenerator
4913 } else {
4914 AwaitContext::AsyncFunction
4915 }
4916 } else if await_match.contains("[") && await_match.contains("for") {
4917 AwaitContext::Comprehension
4918 } else if await_match.contains("lambda") {
4919 AwaitContext::Lambda
4920 } else {
4921 AwaitContext::SyncContext
4922 }
4923 }
4924
4925 fn classify_await_usage_pattern(&self, content: &str, await_expr: &str) -> AwaitUsagePattern {
4926 if content.contains("asyncio.gather") {
4927 AwaitUsagePattern::GatheredAwait
4928 } else if await_expr.contains("await") {
4929 AwaitUsagePattern::NestedAwait
4930 } else if content.contains("if") && await_expr.contains("await") {
4931 AwaitUsagePattern::ConditionalAwait
4932 } else if content.matches("await").count() > 1 {
4933 AwaitUsagePattern::SequentialAwaits
4934 } else {
4935 AwaitUsagePattern::SingleAwait
4936 }
4937 }
4938
4939 fn validate_await_usage(&self, context: &AwaitContext) -> bool {
4940 !matches!(
4941 context,
4942 AwaitContext::SyncContext | AwaitContext::Comprehension | AwaitContext::Lambda
4943 )
4944 }
4945
4946 fn detect_await_issues(
4947 &self,
4948 content: &str,
4949 await_expr: &str,
4950 context: &AwaitContext,
4951 ) -> Vec<AwaitIssue> {
4952 let mut issues = Vec::new();
4953
4954 if !self.validate_await_usage(context) {
4955 issues.push(AwaitIssue::IllegalContext);
4956 }
4957
4958 if await_expr.contains("open(") || await_expr.contains("input(") {
4959 issues.push(AwaitIssue::BlockingCall);
4960 }
4961
4962 if !content.contains("timeout") && !content.contains("asyncio.wait_for") {
4963 issues.push(AwaitIssue::TimeoutMissing);
4964 }
4965
4966 issues
4967 }
4968
4969 fn map_to_concurrency_pattern_type(&self, pattern_name: &str) -> ConcurrencyPatternType {
4970 match pattern_name {
4971 "Asyncio Gather" => ConcurrencyPatternType::AsyncioGather,
4972 "Asyncio Wait" => ConcurrencyPatternType::AsyncioWait,
4973 "Asyncio Queue" => ConcurrencyPatternType::AsyncioQueue,
4974 "Asyncio Semaphore" => ConcurrencyPatternType::AsyncioSemaphore,
4975 "Asyncio Lock" => ConcurrencyPatternType::AsyncioLock,
4976 "TaskGroup" => ConcurrencyPatternType::TaskGroup,
4977 "Concurrent Futures" => ConcurrencyPatternType::ConcurrentFutures,
4978 "Asyncio Timeout" => ConcurrencyPatternType::AsyncioTimeout,
4979 "Asyncio Event" => ConcurrencyPatternType::AsyncioEvent,
4980 "Asyncio Condition" => ConcurrencyPatternType::AsyncioCondition,
4981 _ => ConcurrencyPatternType::AsyncioGather,
4982 }
4983 }
4984
4985 fn assess_concurrency_usage_quality(
4986 &self,
4987 content: &str,
4988 pattern_match: &str,
4989 ) -> ConcurrencyUsageQuality {
4990 let has_error_handling = pattern_match.contains("try") || pattern_match.contains("except");
4991 let has_timeout = pattern_match.contains("timeout") || content.contains("asyncio.wait_for");
4992 let has_proper_cleanup =
4993 pattern_match.contains("finally") || pattern_match.contains("async with");
4994
4995 match (has_error_handling, has_timeout, has_proper_cleanup) {
4996 (true, true, true) => ConcurrencyUsageQuality::Excellent,
4997 (true, true, false) | (true, false, true) => ConcurrencyUsageQuality::Good,
4998 (true, false, false) | (false, true, false) | (false, true, true) => {
4999 ConcurrencyUsageQuality::Adequate
5000 }
5001 (false, false, true) => ConcurrencyUsageQuality::Poor,
5002 (false, false, false) => ConcurrencyUsageQuality::Dangerous,
5003 }
5004 }
5005
5006 fn check_concurrency_best_practices(&self, content: &str, _pattern_match: &str) -> bool {
5007 content.contains("async with")
5008 && (content.contains("timeout") || content.contains("asyncio.wait_for"))
5009 && content.contains("try")
5010 }
5011
5012 fn calculate_async_score(
5013 &self,
5014 async_functions: &[AsyncFunctionInfo],
5015 concurrency_patterns: &[ConcurrencyPatternInfo],
5016 performance_issues: &[AsyncPerformanceIssue],
5017 security_issues: &[AsyncSecurityIssue],
5018 ) -> i32 {
5019 let base_score = 50;
5020
5021 let async_bonus = async_functions.len() as i32 * 5;
5023
5024 let concurrency_bonus = concurrency_patterns
5026 .iter()
5027 .map(|p| match p.usage_quality {
5028 ConcurrencyUsageQuality::Excellent => 10,
5029 ConcurrencyUsageQuality::Good => 7,
5030 ConcurrencyUsageQuality::Adequate => 4,
5031 ConcurrencyUsageQuality::Poor => 1,
5032 ConcurrencyUsageQuality::Dangerous => -5,
5033 })
5034 .sum::<i32>();
5035
5036 let performance_penalty = performance_issues
5038 .iter()
5039 .map(|i| match i.severity {
5040 AsyncIssueSeverity::Critical => 20,
5041 AsyncIssueSeverity::High => 15,
5042 AsyncIssueSeverity::Medium => 10,
5043 AsyncIssueSeverity::Low => 5,
5044 AsyncIssueSeverity::Info => 1,
5045 })
5046 .sum::<i32>();
5047
5048 let security_penalty = security_issues
5049 .iter()
5050 .map(|i| match i.severity {
5051 AsyncSecuritySeverity::Critical => 25,
5052 AsyncSecuritySeverity::High => 20,
5053 AsyncSecuritySeverity::Medium => 15,
5054 AsyncSecuritySeverity::Low => 10,
5055 AsyncSecuritySeverity::Info => 5,
5056 })
5057 .sum::<i32>();
5058
5059 (base_score + async_bonus + concurrency_bonus - performance_penalty - security_penalty)
5060 .clamp(0, 100)
5061 }
5062
5063 fn get_async_recommendations(
5064 &self,
5065 async_functions: &[AsyncFunctionInfo],
5066 await_patterns: &[AwaitUsageInfo],
5067 concurrency_patterns: &[ConcurrencyPatternInfo],
5068 performance_issues: &[AsyncPerformanceIssue],
5069 security_issues: &[AsyncSecurityIssue],
5070 ) -> Vec<String> {
5071 let mut recommendations = Vec::new();
5072
5073 if async_functions.is_empty() {
5074 recommendations.push("Consider using async/await for I/O bound operations".to_string());
5075 }
5076
5077 if !performance_issues.is_empty() {
5078 recommendations
5079 .push("Address async performance issues for better efficiency".to_string());
5080 }
5081
5082 if !security_issues.is_empty() {
5083 recommendations.push("Fix async security vulnerabilities".to_string());
5084 }
5085
5086 let has_poor_concurrency = concurrency_patterns.iter().any(|p| {
5087 matches!(
5088 p.usage_quality,
5089 ConcurrencyUsageQuality::Poor | ConcurrencyUsageQuality::Dangerous
5090 )
5091 });
5092
5093 if has_poor_concurrency {
5094 recommendations
5095 .push("Improve concurrency pattern usage with proper error handling".to_string());
5096 }
5097
5098 let has_invalid_await = await_patterns.iter().any(|p| !p.is_valid);
5099 if has_invalid_await {
5100 recommendations.push("Fix invalid await usage in sync contexts".to_string());
5101 }
5102
5103 let missing_timeouts = async_functions.iter().any(|f| !f.has_timeout);
5104 if missing_timeouts {
5105 recommendations.push("Add timeout handling to prevent hanging operations".to_string());
5106 }
5107
5108 recommendations
5109 .push("Use asyncio.gather() for concurrent independent operations".to_string());
5110 recommendations
5111 .push("Implement proper async context managers for resource cleanup".to_string());
5112 recommendations
5113 .push("Consider using Python 3.11+ TaskGroup for structured concurrency".to_string());
5114
5115 recommendations
5116 }
5117
5118 fn parse_type_hint_type(&self, hint_type: &str, captures: ®ex::Captures) -> TypeHintType {
5120 match hint_type {
5121 "union" => {
5122 let types_str = captures.get(1).unwrap().as_str();
5123 let union_types = types_str.split(',').map(|s| s.trim().to_string()).collect();
5124 TypeHintType::UnionType(union_types)
5125 }
5126 "union_new" => {
5127 let type1 = captures.get(1).unwrap().as_str().to_string();
5128 let type2 = captures.get(2).unwrap().as_str().to_string();
5129 TypeHintType::UnionType(vec![type1, type2])
5130 }
5131 "optional" => {
5132 let inner_type = captures.get(1).unwrap().as_str().to_string();
5133 TypeHintType::OptionalType(inner_type)
5134 }
5135 "generic_list" => {
5136 let element_type = captures.get(1).unwrap().as_str().to_string();
5137 TypeHintType::GenericType(GenericTypeInfo {
5138 base_type: "List".to_string(),
5139 type_parameters: vec![element_type],
5140 is_covariant: true,
5141 is_contravariant: false,
5142 })
5143 }
5144 "generic_dict" => {
5145 let key_type = captures.get(1).unwrap().as_str().to_string();
5146 let value_type = captures.get(2).unwrap().as_str().to_string();
5147 TypeHintType::GenericType(GenericTypeInfo {
5148 base_type: "Dict".to_string(),
5149 type_parameters: vec![key_type, value_type],
5150 is_covariant: false,
5151 is_contravariant: false,
5152 })
5153 }
5154 "callable" => {
5155 let params_str = captures.get(1).unwrap().as_str();
5156 let return_type = captures.get(2).unwrap().as_str().to_string();
5157 let parameter_types = if params_str.is_empty() {
5158 Vec::new()
5159 } else {
5160 params_str
5161 .split(',')
5162 .map(|s| s.trim().to_string())
5163 .collect()
5164 };
5165 TypeHintType::CallableType(CallableTypeInfo {
5166 parameter_types,
5167 return_type,
5168 is_async: false,
5169 })
5170 }
5171 "typevar" => {
5172 let var_name = captures.get(1).unwrap().as_str().to_string();
5173 TypeHintType::TypeVarType(TypeVarInfo {
5174 name: var_name,
5175 bounds: Vec::new(),
5176 constraints: Vec::new(),
5177 covariant: false,
5178 contravariant: false,
5179 })
5180 }
5181 "protocol" => TypeHintType::ProtocolType("Protocol".to_string()),
5182 "literal" => {
5183 let values_str = captures.get(1).unwrap().as_str();
5184 let literal_values = values_str
5185 .split(',')
5186 .map(|s| s.trim().to_string())
5187 .collect();
5188 TypeHintType::LiteralType(literal_values)
5189 }
5190 "final" => {
5191 let final_type = captures.get(1).unwrap().as_str().to_string();
5192 TypeHintType::FinalType(final_type)
5193 }
5194 "typeddict" => {
5195 let class_name = captures.get(1).unwrap().as_str().to_string();
5196 TypeHintType::TypedDictType(TypedDictInfo {
5197 name: class_name,
5198 fields: Vec::new(), total: true,
5200 })
5201 }
5202 "generic_alias" => {
5203 let base_type = captures.get(1).unwrap().as_str().to_string();
5204 let element_type = captures.get(2).unwrap().as_str().to_string();
5205 TypeHintType::GenericType(GenericTypeInfo {
5206 base_type,
5207 type_parameters: vec![element_type],
5208 is_covariant: true,
5209 is_contravariant: false,
5210 })
5211 }
5212 _ => TypeHintType::SimpleType("Unknown".to_string()),
5213 }
5214 }
5215
5216 fn is_generic_type(&self, hint_type: &str) -> bool {
5217 matches!(hint_type, "generic_list" | "generic_dict" | "generic_alias")
5218 }
5219
5220 fn has_type_constraints(&self, hint_type: &str) -> bool {
5221 matches!(hint_type, "typevar" | "protocol" | "literal")
5222 }
5223
5224 fn get_modern_feature_type(&self, hint_type: &str) -> Option<ModernTypeFeatureType> {
5225 match hint_type {
5226 "union_new" => Some(ModernTypeFeatureType::UnionSyntaxPy310),
5227 "typeddict" => Some(ModernTypeFeatureType::TypedDict),
5228 "final" => Some(ModernTypeFeatureType::FinalType),
5229 "literal" => Some(ModernTypeFeatureType::LiteralType),
5230 "protocol" => Some(ModernTypeFeatureType::ProtocolType),
5231 "generic_alias" => Some(ModernTypeFeatureType::GenericAlias),
5232 _ => None,
5233 }
5234 }
5235
5236 fn detect_type_safety_issues(&self, content: &str, issues: &mut Vec<TypeSafetyIssue>) {
5237 let any_count = content.matches("Any").count();
5239 if any_count > 5 {
5240 issues.push(TypeSafetyIssue {
5241 issue_type: TypeSafetyIssueType::AnyTypeOveruse,
5242 severity: TypeSafetySeverity::Warning,
5243 location: "Multiple locations".to_string(),
5244 description: format!("Found {} uses of Any type", any_count),
5245 recommendation: "Consider using more specific type hints".to_string(),
5246 });
5247 }
5248
5249 let func_pattern = Regex::new(r"def\s+\w+\s*\([^)]*\)\s*:").unwrap();
5251 let typed_func_pattern = Regex::new(r"def\s+\w+\s*\([^)]*\)\s*->\s*\w+:").unwrap();
5252
5253 let total_functions = func_pattern.find_iter(content).count();
5254 let typed_functions = typed_func_pattern.find_iter(content).count();
5255
5256 if total_functions > typed_functions && total_functions > 0 {
5257 let missing_hints = total_functions - typed_functions;
5258 issues.push(TypeSafetyIssue {
5259 issue_type: TypeSafetyIssueType::MissingTypeHints,
5260 severity: TypeSafetySeverity::Warning,
5261 location: "Function definitions".to_string(),
5262 description: format!("{} functions missing return type hints", missing_hints),
5263 recommendation: "Add return type annotations to functions".to_string(),
5264 });
5265 }
5266
5267 let ignore_count = content.matches("# type: ignore").count();
5269 if ignore_count > 3 {
5270 issues.push(TypeSafetyIssue {
5271 issue_type: TypeSafetyIssueType::TypeIgnoreOveruse,
5272 severity: TypeSafetySeverity::Info,
5273 location: "Multiple locations".to_string(),
5274 description: format!("Found {} type: ignore comments", ignore_count),
5275 recommendation: "Review and fix type issues instead of ignoring them".to_string(),
5276 });
5277 }
5278
5279 if content.contains("typing.List") || content.contains("typing.Dict") {
5281 issues.push(TypeSafetyIssue {
5282 issue_type: TypeSafetyIssueType::DeprecatedTypingSyntax,
5283 severity: TypeSafetySeverity::Info,
5284 location: "Import statements".to_string(),
5285 description: "Using deprecated typing imports".to_string(),
5286 recommendation: "Use built-in generics (list, dict) for Python 3.9+".to_string(),
5287 });
5288 }
5289 }
5290
5291 fn calculate_type_coverage(&self, content: &str, _type_hints: &[TypeHintInfo]) -> f32 {
5292 let func_pattern = Regex::new(r"def\s+\w+").unwrap();
5293 let total_functions = func_pattern.find_iter(content).count();
5294
5295 if total_functions == 0 {
5296 return 0.0;
5297 }
5298
5299 let typed_func_pattern =
5301 Regex::new(r"def\s+\w+\s*\([^)]*:\s*\w+|def\s+\w+\s*\([^)]*\)\s*->\s*\w+").unwrap();
5302 let typed_functions = typed_func_pattern.find_iter(content).count();
5303
5304 (typed_functions as f32 / total_functions as f32) * 100.0
5305 }
5306
5307 fn get_coverage_score(&self, coverage: f32) -> TypeCoverageScore {
5308 match coverage {
5309 score if score >= 90.0 => TypeCoverageScore::Excellent,
5310 score if score >= 70.0 => TypeCoverageScore::Good,
5311 score if score >= 50.0 => TypeCoverageScore::Fair,
5312 score if score >= 30.0 => TypeCoverageScore::Poor,
5313 _ => TypeCoverageScore::Minimal,
5314 }
5315 }
5316
5317 fn get_type_hint_recommendations(
5318 &self,
5319 type_hints: &[TypeHintInfo],
5320 issues: &[TypeSafetyIssue],
5321 coverage: f32,
5322 ) -> Vec<String> {
5323 let mut recommendations = Vec::new();
5324
5325 if coverage < 70.0 {
5326 recommendations.push("Increase type hint coverage for better type safety".to_string());
5327 }
5328
5329 if !issues.is_empty() {
5330 recommendations.push("Address type safety issues identified in the code".to_string());
5331 }
5332
5333 let has_modern_features = type_hints.iter().any(|h| {
5334 h.python_version_required.starts_with("3.8")
5335 || h.python_version_required.starts_with("3.9")
5336 || h.python_version_required.starts_with("3.10")
5337 });
5338
5339 if !has_modern_features {
5340 recommendations.push("Consider using modern Python type features (3.8+)".to_string());
5341 }
5342
5343 let has_complex_types = type_hints.iter().any(|h| {
5344 matches!(
5345 h.complexity,
5346 TypeComplexity::Complex | TypeComplexity::Advanced
5347 )
5348 });
5349
5350 if has_complex_types {
5351 recommendations
5352 .push("Document complex type relationships for maintainability".to_string());
5353 }
5354
5355 if type_hints.iter().any(|h| h.is_generic) {
5356 recommendations
5357 .push("Ensure generic type constraints are properly defined".to_string());
5358 }
5359
5360 recommendations.push("Use type checkers like mypy for static type validation".to_string());
5361 recommendations.push("Consider Protocol types for structural typing".to_string());
5362
5363 recommendations
5364 }
5365
5366 fn get_security_recommendation(&self, vulnerability_type: &VulnerabilityType) -> String {
5368 match vulnerability_type {
5369 VulnerabilityType::SqlInjection => {
5370 "Use parameterized queries or ORM methods".to_string()
5371 }
5372 VulnerabilityType::CommandInjection => {
5373 "Sanitize user input and avoid shell execution".to_string()
5374 }
5375 VulnerabilityType::DeserializationAttack => {
5376 "Use safe deserialization methods like json.loads".to_string()
5377 }
5378 VulnerabilityType::HardcodedSecrets => {
5379 "Use environment variables or secret management".to_string()
5380 }
5381 _ => "Review security implementation".to_string(),
5382 }
5383 }
5384
5385 fn determine_security_level(
5386 &self,
5387 vulnerabilities: &[SecurityVulnerability],
5388 security_features: &[SecurityFeature],
5389 ) -> SecurityLevel {
5390 let critical_vulns = vulnerabilities
5391 .iter()
5392 .filter(|v| matches!(v.severity, VulnerabilitySeverity::Critical))
5393 .count();
5394 let high_vulns = vulnerabilities
5395 .iter()
5396 .filter(|v| matches!(v.severity, VulnerabilitySeverity::High))
5397 .count();
5398
5399 if critical_vulns > 0 {
5400 SecurityLevel::Vulnerable
5401 } else if high_vulns > 2 {
5402 SecurityLevel::Low
5403 } else if security_features.len() > 2 {
5404 SecurityLevel::High
5405 } else {
5406 SecurityLevel::Medium
5407 }
5408 }
5409
5410 fn get_security_recommendations(
5411 &self,
5412 vulnerabilities: &[SecurityVulnerability],
5413 _security_features: &[SecurityFeature],
5414 ) -> Vec<String> {
5415 let mut recommendations = Vec::new();
5416
5417 if !vulnerabilities.is_empty() {
5418 recommendations.push("Address security vulnerabilities identified in code".to_string());
5419 }
5420
5421 recommendations.push("Implement comprehensive input validation".to_string());
5422 recommendations.push("Use secure authentication and authorization".to_string());
5423 recommendations.push("Enable security headers and CSRF protection".to_string());
5424
5425 recommendations
5426 }
5427
5428 fn calculate_performance_score(
5430 &self,
5431 optimizations: &[PerformanceOptimization],
5432 issues: &[PerformanceIssue],
5433 ) -> i32 {
5434 let base_score = 50;
5435 let optimization_bonus = optimizations.len() as i32 * 10;
5436 let issue_penalty = issues
5437 .iter()
5438 .map(|i| match i.severity {
5439 IssueSeverity::Critical => 20,
5440 IssueSeverity::High => 15,
5441 IssueSeverity::Medium => 10,
5442 IssueSeverity::Low => 5,
5443 })
5444 .sum::<i32>();
5445
5446 (base_score + optimization_bonus - issue_penalty).clamp(0, 100)
5447 }
5448
5449 fn get_performance_recommendations(
5450 &self,
5451 _optimizations: &[PerformanceOptimization],
5452 issues: &[PerformanceIssue],
5453 ) -> Vec<String> {
5454 let mut recommendations = Vec::new();
5455
5456 if !issues.is_empty() {
5457 recommendations.push("Address performance issues identified in code".to_string());
5458 }
5459
5460 recommendations.push("Use list comprehensions and generator expressions".to_string());
5461 recommendations.push("Implement caching for expensive operations".to_string());
5462 recommendations.push("Consider async/await for I/O operations".to_string());
5463
5464 recommendations
5465 }
5466
5467 fn analyze_django_specifics(&self, content: &str) -> DjangoAnalysis {
5469 DjangoAnalysis {
5470 models_analysis: self.extract_django_models(content),
5471 views_analysis: self.extract_django_views(content),
5472 middleware_usage: self.extract_django_middleware(content),
5473 security_middleware: self.extract_django_security_middleware(content),
5474 signals_usage: self.extract_django_signals(content),
5475 admin_customization: content.contains("admin.site.register")
5476 || content.contains("ModelAdmin"),
5477 }
5478 }
5479
5480 fn analyze_flask_specifics(&self, content: &str) -> FlaskAnalysis {
5481 FlaskAnalysis {
5482 blueprints: self.extract_flask_blueprints(content),
5483 extensions: self.extract_flask_extensions(content),
5484 error_handlers: self.extract_flask_error_handlers(content),
5485 template_usage: content.contains("render_template"),
5486 session_management: content.contains("session["),
5487 }
5488 }
5489
5490 fn analyze_fastapi_specifics(&self, content: &str) -> FastAPIAnalysis {
5491 FastAPIAnalysis {
5492 router_usage: self.extract_fastapi_routers(content),
5493 dependency_injection: self.extract_fastapi_dependencies(content),
5494 background_tasks: content.contains("BackgroundTasks"),
5495 websocket_endpoints: self.extract_fastapi_websockets(content),
5496 middleware: self.extract_fastapi_middleware(content),
5497 response_models: self.extract_fastapi_response_models(content),
5498 }
5499 }
5500
5501 fn analyze_pytest_specifics(&self, content: &str) -> PytestAnalysis {
5502 PytestAnalysis {
5503 fixtures: self.extract_pytest_fixtures(content),
5504 parametrized_tests: self.extract_pytest_parametrized(content),
5505 markers: self.extract_pytest_markers(content),
5506 plugins: self.extract_pytest_plugins(content),
5507 coverage_setup: content.contains("pytest-cov") || content.contains("coverage"),
5508 }
5509 }
5510
5511 fn extract_django_models(&self, content: &str) -> Vec<DjangoModelInfo> {
5513 let model_pattern = Regex::new(r"class\s+(\w+)\s*\([^)]*Model[^)]*\)").unwrap();
5514 model_pattern
5515 .captures_iter(content)
5516 .map(|captures| {
5517 let model_name = captures.get(1).unwrap().as_str().to_string();
5518 DjangoModelInfo {
5519 name: model_name,
5520 fields: Vec::new(), relationships: Vec::new(),
5522 custom_managers: false,
5523 meta_options: Vec::new(),
5524 }
5525 })
5526 .collect()
5527 }
5528
5529 fn extract_django_views(&self, _content: &str) -> Vec<DjangoViewInfo> {
5530 Vec::new() }
5532
5533 fn extract_django_middleware(&self, _content: &str) -> Vec<String> {
5534 Vec::new() }
5536
5537 fn extract_django_security_middleware(&self, content: &str) -> Vec<String> {
5538 let mut middleware = Vec::new();
5539 if content.contains("SecurityMiddleware") {
5540 middleware.push("SecurityMiddleware".to_string());
5541 }
5542 if content.contains("CsrfViewMiddleware") {
5543 middleware.push("CsrfViewMiddleware".to_string());
5544 }
5545 middleware
5546 }
5547
5548 fn extract_django_signals(&self, content: &str) -> Vec<String> {
5549 let mut signals = Vec::new();
5550 if content.contains("post_save") {
5551 signals.push("post_save".to_string());
5552 }
5553 if content.contains("pre_save") {
5554 signals.push("pre_save".to_string());
5555 }
5556 signals
5557 }
5558
5559 fn extract_flask_blueprints(&self, _content: &str) -> Vec<FlaskBlueprintInfo> {
5560 Vec::new() }
5562
5563 fn extract_flask_extensions(&self, content: &str) -> Vec<String> {
5564 let mut extensions = Vec::new();
5565 if content.contains("Flask-Login") {
5566 extensions.push("Flask-Login".to_string());
5567 }
5568 if content.contains("Flask-SQLAlchemy") {
5569 extensions.push("Flask-SQLAlchemy".to_string());
5570 }
5571 extensions
5572 }
5573
5574 fn extract_flask_error_handlers(&self, _content: &str) -> Vec<String> {
5575 Vec::new() }
5577
5578 fn extract_fastapi_routers(&self, _content: &str) -> Vec<FastAPIRouterInfo> {
5579 Vec::new() }
5581
5582 fn extract_fastapi_dependencies(&self, _content: &str) -> Vec<String> {
5583 Vec::new() }
5585
5586 fn extract_fastapi_websockets(&self, _content: &str) -> Vec<String> {
5587 Vec::new() }
5589
5590 fn extract_fastapi_middleware(&self, _content: &str) -> Vec<String> {
5591 Vec::new() }
5593
5594 fn extract_fastapi_response_models(&self, _content: &str) -> Vec<String> {
5595 Vec::new() }
5597
5598 fn extract_pytest_fixtures(&self, _content: &str) -> Vec<PytestFixtureInfo> {
5599 Vec::new() }
5601
5602 fn extract_pytest_parametrized(&self, _content: &str) -> Vec<String> {
5603 Vec::new() }
5605
5606 fn extract_pytest_markers(&self, _content: &str) -> Vec<String> {
5607 Vec::new() }
5609
5610 fn extract_pytest_plugins(&self, _content: &str) -> Vec<String> {
5611 Vec::new() }
5613
5614 fn get_framework_best_practices(&self, framework: &str) -> Vec<String> {
5615 match framework {
5616 "Django" => vec![
5617 "Use Django ORM instead of raw SQL".to_string(),
5618 "Implement proper authentication and authorization".to_string(),
5619 "Use Django forms for input validation".to_string(),
5620 ],
5621 "Flask" => vec![
5622 "Use blueprints for application modularity".to_string(),
5623 "Implement proper error handling".to_string(),
5624 "Use Flask-WTF for form handling".to_string(),
5625 ],
5626 "FastAPI" => vec![
5627 "Use Pydantic models for request/response validation".to_string(),
5628 "Implement proper dependency injection".to_string(),
5629 "Use async/await for I/O operations".to_string(),
5630 ],
5631 _ => Vec::new(),
5632 }
5633 }
5634
5635 pub fn get_python_recommendations(
5637 &self,
5638 decorators: &[DecoratorInfo],
5639 metaclasses: &[MetaclassInfo],
5640 inheritance: &[InheritanceInfo],
5641 ) -> Vec<String> {
5642 let mut recommendations = Vec::new();
5643
5644 let framework_decorators = decorators.iter().filter(|d| d.framework.is_some()).count();
5646 if framework_decorators > 0 {
5647 recommendations
5648 .push("Consider documenting framework-specific decorator behavior.".to_string());
5649 }
5650
5651 let factory_decorators = decorators.iter().filter(|d| d.is_factory).count();
5652 if factory_decorators > 0 {
5653 recommendations.push(
5654 "Factory decorators detected - ensure proper parameter validation.".to_string(),
5655 );
5656 }
5657
5658 if !metaclasses.is_empty() {
5660 recommendations.push(
5661 "Metaclasses detected - document their behavior and impact on subclasses."
5662 .to_string(),
5663 );
5664 recommendations.push(
5665 "Consider if metaclass functionality could be achieved with simpler patterns."
5666 .to_string(),
5667 );
5668 }
5669
5670 let diamond_inheritance = inheritance
5672 .iter()
5673 .filter(|i| i.has_diamond_inheritance)
5674 .count();
5675 if diamond_inheritance > 0 {
5676 recommendations.push(
5677 "Diamond inheritance detected - verify MRO behavior is as expected.".to_string(),
5678 );
5679 }
5680
5681 let complex_inheritance = inheritance
5682 .iter()
5683 .filter(|i| i.base_classes.len() > 2)
5684 .count();
5685 if complex_inheritance > 0 {
5686 recommendations.push(
5687 "Complex inheritance hierarchies detected - consider composition over inheritance."
5688 .to_string(),
5689 );
5690 }
5691
5692 recommendations
5693 .push("Use type hints for better code documentation and IDE support.".to_string());
5694 recommendations.push("Consider using dataclasses for simple data containers.".to_string());
5695
5696 recommendations
5697 }
5698}
5699
5700impl Default for PythonAnalyzer {
5701 fn default() -> Self {
5702 Self::new()
5703 }
5704}
5705
5706#[cfg(test)]
5707mod tests {
5708 use super::*;
5709
5710 #[test]
5711 fn test_decorator_analysis() {
5712 let analyzer = PythonAnalyzer::new();
5713
5714 let code = "@app.route('/test')\ndef test_view():\n pass";
5715 let decorators = analyzer.analyze_decorators(code).unwrap();
5716
5717 assert!(!decorators.is_empty());
5718 assert!(decorators.iter().any(|d| d.name == "Flask Route"));
5719 }
5720
5721 #[test]
5722 fn test_metaclass_analysis() {
5723 let analyzer = PythonAnalyzer::new();
5724
5725 let code = "class TestClass(BaseClass, metaclass=RegistryMeta):\n pass";
5726 let metaclasses = analyzer.analyze_metaclasses(code).unwrap();
5727
5728 assert!(!metaclasses.is_empty());
5729 assert!(metaclasses.iter().any(|m| m.name == "TestClass"));
5730 }
5731
5732 #[test]
5733 fn test_inheritance_analysis() {
5734 let analyzer = PythonAnalyzer::new();
5735
5736 let code = "class Child(Parent1, Parent2):\n pass";
5737 let inheritance = analyzer.analyze_inheritance(code).unwrap();
5738
5739 assert!(!inheritance.is_empty());
5740 assert_eq!(inheritance[0].class_name, "Child");
5741 assert_eq!(inheritance[0].base_classes.len(), 2);
5742 }
5743
5744 #[test]
5745 fn test_decorator_parameter_extraction() {
5746 let analyzer = PythonAnalyzer::new();
5747
5748 let decorator = "@app.route('/test', methods=['GET', 'POST'])";
5749 let params = analyzer.extract_decorator_parameters(decorator);
5750
5751 assert!(!params.is_empty());
5752 }
5753
5754 #[test]
5755 fn test_diamond_inheritance_detection() {
5756 let analyzer = PythonAnalyzer::new();
5757
5758 let base_classes = vec!["Parent1".to_string(), "Parent2".to_string()];
5759 assert!(analyzer.detect_diamond_inheritance(&base_classes));
5760
5761 let single_base = vec!["Parent".to_string()];
5762 assert!(!analyzer.detect_diamond_inheritance(&single_base));
5763 }
5764
5765 #[test]
5766 fn test_mixin_identification() {
5767 let analyzer = PythonAnalyzer::new();
5768
5769 let base_classes = vec![
5770 "BaseMixin".to_string(),
5771 "RegularClass".to_string(),
5772 "UtilMix".to_string(),
5773 ];
5774 let mixins = analyzer.identify_mixins(&base_classes);
5775
5776 assert_eq!(mixins.len(), 2);
5777 assert!(mixins.contains(&"BaseMixin".to_string()));
5778 assert!(mixins.contains(&"UtilMix".to_string()));
5779 }
5780
5781 #[test]
5782 fn test_type_hint_analysis() {
5783 let analyzer = PythonAnalyzer::new();
5784
5785 let code = r#"
5786from typing import List, Dict, Union, Optional, Literal, Final
5787from typing_extensions import Protocol
5788
5789def process_data(items: List[str], mapping: Dict[str, int]) -> Optional[str]:
5790 return None
5791
5792def handle_union(value: Union[str, int]) -> str:
5793 return str(value)
5794
5795class MyProtocol(Protocol):
5796 def method(self) -> None: ...
5797
5798CONSTANT: Final[str] = "value"
5799MODE: Literal["read", "write"] = "read"
5800 "#;
5801
5802 let result = analyzer.analyze_type_hints(code).unwrap();
5803
5804 assert!(!result.type_hints_detected.is_empty());
5805 assert!(result
5806 .type_hints_detected
5807 .iter()
5808 .any(|h| matches!(h.hint_type, TypeHintType::GenericType(_))));
5809 assert!(result
5810 .type_hints_detected
5811 .iter()
5812 .any(|h| matches!(h.hint_type, TypeHintType::UnionType(_))));
5813 assert!(result
5814 .type_hints_detected
5815 .iter()
5816 .any(|h| matches!(h.hint_type, TypeHintType::OptionalType(_))));
5817 assert!(result.overall_coverage > 0.0);
5818 }
5819
5820 #[test]
5821 fn test_modern_type_features() {
5822 let analyzer = PythonAnalyzer::new();
5823
5824 let code = r#"
5825from typing import Final, Literal
5826from typing_extensions import TypedDict
5827
5828class UserDict(TypedDict):
5829 name: str
5830 age: int
5831
5832CONSTANT: Final[int] = 42
5833STATUS: Literal["active", "inactive"] = "active"
5834
5835# Python 3.10+ union syntax
5836def process(value: str | int) -> str | None:
5837 return None
5838 "#;
5839
5840 let result = analyzer.analyze_type_hints(code).unwrap();
5841
5842 assert!(!result.modern_type_features.is_empty());
5843 assert!(result
5844 .modern_type_features
5845 .iter()
5846 .any(|f| matches!(f.feature_type, ModernTypeFeatureType::TypedDict)));
5847 assert!(result
5848 .modern_type_features
5849 .iter()
5850 .any(|f| matches!(f.feature_type, ModernTypeFeatureType::FinalType)));
5851 assert!(result
5852 .modern_type_features
5853 .iter()
5854 .any(|f| matches!(f.feature_type, ModernTypeFeatureType::LiteralType)));
5855 }
5856
5857 #[test]
5858 fn test_type_safety_issues() {
5859 let analyzer = PythonAnalyzer::new();
5860
5861 let code = r#"
5862from typing import Any
5863
5864def untyped_function():
5865 return "hello"
5866
5867def another_untyped():
5868 pass
5869
5870def bad_any_usage(x: Any, y: Any, z: Any, a: Any, b: Any, c: Any) -> Any:
5871 return x
5872
5873# type: ignore
5874# type: ignore
5875# type: ignore
5876# type: ignore
5877 "#;
5878
5879 let result = analyzer.analyze_type_hints(code).unwrap();
5880
5881 assert!(!result.type_safety_issues.is_empty());
5882 assert!(result
5883 .type_safety_issues
5884 .iter()
5885 .any(|issue| matches!(issue.issue_type, TypeSafetyIssueType::AnyTypeOveruse)));
5886 assert!(result
5887 .type_safety_issues
5888 .iter()
5889 .any(|issue| matches!(issue.issue_type, TypeSafetyIssueType::MissingTypeHints)));
5890 assert!(result
5891 .type_safety_issues
5892 .iter()
5893 .any(|issue| matches!(issue.issue_type, TypeSafetyIssueType::TypeIgnoreOveruse)));
5894 }
5895
5896 #[test]
5897 fn test_type_coverage_calculation() {
5898 let analyzer = PythonAnalyzer::new();
5899
5900 let high_coverage_code = r#"
5902def typed_func1(x: int) -> str:
5903 return str(x)
5904
5905def typed_func2(y: str) -> int:
5906 return len(y)
5907 "#;
5908
5909 let result = analyzer.analyze_type_hints(high_coverage_code).unwrap();
5910 assert!(result.overall_coverage > 50.0);
5911 assert!(matches!(
5912 result.type_coverage_score,
5913 TypeCoverageScore::Good | TypeCoverageScore::Excellent | TypeCoverageScore::Fair
5914 ));
5915
5916 let low_coverage_code = r#"
5918def untyped_func1():
5919 return "hello"
5920
5921def untyped_func2():
5922 return 42
5923
5924def typed_func(x: int) -> str:
5925 return str(x)
5926 "#;
5927
5928 let result = analyzer.analyze_type_hints(low_coverage_code).unwrap();
5929 assert!(result.overall_coverage < 100.0);
5930 }
5931
5932 #[test]
5933 fn test_async_await_analysis() {
5934 let analyzer = PythonAnalyzer::new();
5935 let content = r#"
5936import asyncio
5937
5938async def fetch_data():
5939 await asyncio.sleep(1)
5940 return "data"
5941
5942async def process_items():
5943 results = await asyncio.gather(
5944 fetch_data(),
5945 fetch_data(),
5946 fetch_data()
5947 )
5948 return results
5949
5950async def with_context():
5951 async with asyncio.timeout(5):
5952 return await fetch_data()
5953"#;
5954
5955 let result = analyzer.analyze_async_await(content).unwrap();
5956
5957 assert!(!result.async_functions_detected.is_empty());
5959 assert!(result.async_functions_detected.len() >= 3);
5960
5961 assert!(!result.concurrency_patterns.is_empty());
5963
5964 assert!(!result.modern_async_features.is_empty());
5966
5967 assert!(result.overall_async_score > 50);
5969 }
5970
5971 #[test]
5972 fn test_async_performance_issues() {
5973 let analyzer = PythonAnalyzer::new();
5974 let content = r#"
5975import asyncio
5976import time
5977
5978async def bad_function():
5979 # Blocking operations in async function
5980 time.sleep(1)
5981 with open("file.txt") as f:
5982 data = f.read()
5983
5984 # Sequential awaits that could be concurrent
5985 result1 = await fetch_data()
5986 result2 = await fetch_data()
5987
5988 return result1 + result2
5989
5990async def fetch_data():
5991 await asyncio.sleep(0.1)
5992 return "data"
5993"#;
5994
5995 let result = analyzer.analyze_async_await(content).unwrap();
5996
5997 assert!(!result.async_performance_issues.is_empty());
5999
6000 let blocking_issues: Vec<_> = result
6002 .async_performance_issues
6003 .iter()
6004 .filter(|issue| {
6005 matches!(
6006 issue.issue_type,
6007 AsyncPerformanceIssueType::BlockingIOInAsync
6008 )
6009 })
6010 .collect();
6011 assert!(!blocking_issues.is_empty());
6012
6013 let concurrency_issues: Vec<_> = result
6015 .async_performance_issues
6016 .iter()
6017 .filter(|issue| {
6018 matches!(
6019 issue.issue_type,
6020 AsyncPerformanceIssueType::MissingConcurrency
6021 )
6022 })
6023 .collect();
6024 assert!(!concurrency_issues.is_empty());
6025 }
6026
6027 #[test]
6028 fn test_async_security_issues() {
6029 let analyzer = PythonAnalyzer::new();
6030 let content = r#"
6031import asyncio
6032
6033shared_data = {}
6034
6035async def unsafe_function():
6036 # No timeout handling
6037 await some_external_service()
6038
6039 # Shared state modification without locking
6040 shared_data["key"] = "value"
6041
6042 # Multiple concurrent operations without proper synchronization
6043 await asyncio.gather(
6044 modify_shared_data(),
6045 modify_shared_data(),
6046 modify_shared_data()
6047 )
6048
6049async def some_external_service():
6050 await asyncio.sleep(1)
6051
6052async def modify_shared_data():
6053 shared_data["counter"] = shared_data.get("counter", 0) + 1
6054"#;
6055
6056 let result = analyzer.analyze_async_await(content).unwrap();
6057
6058 assert!(!result.async_security_issues.is_empty());
6060
6061 let timeout_issues: Vec<_> = result
6063 .async_security_issues
6064 .iter()
6065 .filter(|issue| matches!(issue.issue_type, AsyncSecurityIssueType::AsyncTimeoutVuln))
6066 .collect();
6067 assert!(!timeout_issues.is_empty());
6068 }
6069
6070 #[test]
6071 fn test_modern_async_features() {
6072 let analyzer = PythonAnalyzer::new();
6073 let content = r#"
6074import asyncio
6075import contextvars
6076
6077async def modern_async():
6078 # Modern async features
6079 async with asyncio.timeout(5):
6080 data = await fetch_data()
6081
6082 # Async comprehension
6083 results = [await process(item) async for item in async_generator()]
6084
6085 # Context variables
6086 context_var = contextvars.ContextVar('user_id')
6087
6088 # TaskGroup (Python 3.11+)
6089 async with asyncio.TaskGroup() as tg:
6090 task1 = tg.create_task(fetch_data())
6091 task2 = tg.create_task(fetch_data())
6092
6093 return results
6094
6095async def async_generator():
6096 for i in range(3):
6097 yield f"item_{i}"
6098
6099async def fetch_data():
6100 return "data"
6101
6102async def process(item):
6103 return f"processed_{item}"
6104
6105if __name__ == "__main__":
6106 asyncio.run(modern_async())
6107"#;
6108
6109 let result = analyzer.analyze_async_await(content).unwrap();
6110
6111 assert!(!result.modern_async_features.is_empty());
6113
6114 let context_manager_features: Vec<_> = result
6116 .modern_async_features
6117 .iter()
6118 .filter(|f| matches!(f.feature_type, ModernAsyncFeatureType::AsyncContextManager))
6119 .collect();
6120 assert!(!context_manager_features.is_empty());
6121
6122 let task_group_features: Vec<_> = result
6124 .modern_async_features
6125 .iter()
6126 .filter(|f| matches!(f.feature_type, ModernAsyncFeatureType::TaskGroups))
6127 .collect();
6128 assert!(!task_group_features.is_empty());
6129
6130 let asyncio_run_features: Vec<_> = result
6132 .modern_async_features
6133 .iter()
6134 .filter(|f| matches!(f.feature_type, ModernAsyncFeatureType::AsyncioRun))
6135 .collect();
6136 assert!(!asyncio_run_features.is_empty());
6137
6138 assert!(!result.recommendations.is_empty());
6140 }
6141
6142 #[test]
6143 fn test_package_dependency_analysis() {
6144 let analyzer = PythonAnalyzer::new();
6145 let content = r#"
6146import requests
6147import pandas as pd
6148from flask import Flask
6149import numpy
6150from django.db import models
6151
6152django>=3.2.0
6153requests==2.28.1
6154pandas>=1.5.0
6155numpy
6156flask==2.0.1
6157"#;
6158
6159 let result = analyzer.analyze_package_dependencies(content).unwrap();
6160
6161 assert!(!result.dependencies.is_empty());
6163
6164 assert!(!result.import_analysis.is_empty());
6166 assert!(result.import_analysis.len() >= 5);
6167
6168 assert!(result.overall_health_score >= 0);
6170 assert!(result.overall_health_score <= 100);
6171
6172 assert!(!result.dependency_issues.is_empty());
6174
6175 assert!(!result.recommendations.is_empty());
6177 }
6178
6179 #[test]
6180 fn test_dependency_issue_detection() {
6181 let analyzer = PythonAnalyzer::new();
6182 let content = r#"
6183import requests
6184import missing_package
6185from some_package import *
6186
6187# Requirements:
6188requests==2.28.1
6189unused_package==1.0.0
6190imp==1.0.0
6191django
6192"#;
6193
6194 let result = analyzer.analyze_package_dependencies(content).unwrap();
6195
6196 let unused_issues: Vec<_> = result
6198 .dependency_issues
6199 .iter()
6200 .filter(|issue| matches!(issue.issue_type, DependencyIssueType::UnusedDependency))
6201 .collect();
6202 assert!(!unused_issues.is_empty());
6203
6204 let missing_issues: Vec<_> = result
6206 .dependency_issues
6207 .iter()
6208 .filter(|issue| matches!(issue.issue_type, DependencyIssueType::MissingDependency))
6209 .collect();
6210 assert!(!missing_issues.is_empty());
6211
6212 let unpinned_issues: Vec<_> = result
6214 .dependency_issues
6215 .iter()
6216 .filter(|issue| matches!(issue.issue_type, DependencyIssueType::UnpinnedVersion))
6217 .collect();
6218 assert!(!unpinned_issues.is_empty());
6219
6220 let deprecated_issues: Vec<_> = result
6222 .dependency_issues
6223 .iter()
6224 .filter(|issue| matches!(issue.issue_type, DependencyIssueType::DeprecatedPackage))
6225 .collect();
6226 assert!(!deprecated_issues.is_empty());
6227 }
6228
6229 #[test]
6230 fn test_import_analysis() {
6231 let analyzer = PythonAnalyzer::new();
6232 let content = r#"
6233import os
6234import sys
6235import requests
6236import pandas as pd
6237from flask import Flask, render_template
6238from mymodule import function
6239from .relative import local_function
6240from package import *
6241import numpy as np
6242"#;
6243
6244 let result = analyzer.analyze_package_dependencies(content).unwrap();
6245
6246 let stdlib_imports: Vec<_> = result
6248 .import_analysis
6249 .iter()
6250 .filter(|imp| matches!(imp.module_category, ModuleCategory::StandardLibrary))
6251 .collect();
6252 assert!(stdlib_imports.len() >= 2); let third_party_imports: Vec<_> = result
6255 .import_analysis
6256 .iter()
6257 .filter(|imp| matches!(imp.module_category, ModuleCategory::ThirdParty))
6258 .collect();
6259 assert!(third_party_imports.len() >= 3); let star_import_issues: Vec<_> = result
6263 .import_analysis
6264 .iter()
6265 .filter(|imp| {
6266 imp.import_issues
6267 .contains(&ImportIssue::StarImportDangerous)
6268 })
6269 .collect();
6270 assert!(!star_import_issues.is_empty());
6271
6272 let from_imports: Vec<_> = result
6274 .import_analysis
6275 .iter()
6276 .filter(|imp| matches!(imp.import_type, ImportType::FromImport))
6277 .collect();
6278 assert!(!from_imports.is_empty());
6279
6280 let alias_imports: Vec<_> = result
6281 .import_analysis
6282 .iter()
6283 .filter(|imp| matches!(imp.import_type, ImportType::AliasImport))
6284 .collect();
6285 assert!(!alias_imports.is_empty());
6286 }
6287
6288 #[test]
6289 fn test_security_vulnerability_scanning() {
6290 let analyzer = PythonAnalyzer::new();
6291 let content = r#"
6292import urllib3
6293import requests
6294import pyyaml
6295
6296urllib3==1.25.8
6297requests==2.19.1
6298pyyaml==5.3.1
6299"#;
6300
6301 let result = analyzer.analyze_package_dependencies(content).unwrap();
6302
6303 assert!(!result.security_vulnerabilities.is_empty());
6305
6306 let urllib3_vulns: Vec<_> = result
6308 .security_vulnerabilities
6309 .iter()
6310 .filter(|vuln| vuln.package_name == "urllib3")
6311 .collect();
6312 assert!(!urllib3_vulns.is_empty());
6313
6314 let critical_vulns: Vec<_> = result
6316 .security_vulnerabilities
6317 .iter()
6318 .filter(|vuln| matches!(vuln.severity, SecurityVulnerabilitySeverity::Critical))
6319 .collect();
6320 assert!(!critical_vulns.is_empty());
6321
6322 let vulns_with_cve: Vec<_> = result
6324 .security_vulnerabilities
6325 .iter()
6326 .filter(|vuln| vuln.cve_id.is_some())
6327 .collect();
6328 assert!(!vulns_with_cve.is_empty());
6329 }
6330
6331 #[test]
6332 fn test_virtual_environment_detection() {
6333 let analyzer = PythonAnalyzer::new();
6334 let content = r#"
6335# Virtual environment indicators
6336python -m venv myenv
6337source myenv/bin/activate
6338pip install -r requirements.txt
6339
6340# Conda environment
6341conda create -n myproject python=3.9
6342conda activate myproject
6343
6344# Pipenv
6345pipenv install requests
6346pipenv shell
6347"#;
6348
6349 let result = analyzer.analyze_package_dependencies(content).unwrap();
6350
6351 assert!(!result.virtual_environments.is_empty());
6353
6354 let venv_envs: Vec<_> = result
6356 .virtual_environments
6357 .iter()
6358 .filter(|env| matches!(env.env_type, VirtualEnvironmentType::Venv))
6359 .collect();
6360 assert!(!venv_envs.is_empty());
6361
6362 let conda_envs: Vec<_> = result
6363 .virtual_environments
6364 .iter()
6365 .filter(|env| matches!(env.env_type, VirtualEnvironmentType::Conda))
6366 .collect();
6367 assert!(!conda_envs.is_empty());
6368
6369 let pipenv_envs: Vec<_> = result
6370 .virtual_environments
6371 .iter()
6372 .filter(|env| matches!(env.env_type, VirtualEnvironmentType::Pipenv))
6373 .collect();
6374 assert!(!pipenv_envs.is_empty());
6375 }
6376
6377 #[test]
6378 fn test_license_analysis() {
6379 let analyzer = PythonAnalyzer::new();
6380 let content = r#"
6381import requests
6382import django
6383import flask
6384
6385requests==2.28.1
6386django==4.1.0
6387flask==2.0.1
6388"#;
6389
6390 let result = analyzer.analyze_package_dependencies(content).unwrap();
6391
6392 assert!(!result.license_analysis.is_empty());
6394
6395 assert_eq!(result.license_analysis.len(), result.dependencies.len());
6397
6398 let compatible_licenses: Vec<_> = result
6400 .license_analysis
6401 .iter()
6402 .filter(|license| matches!(license.compatibility, LicenseCompatibility::Compatible))
6403 .collect();
6404 assert!(!compatible_licenses.is_empty());
6405
6406 for license in &result.license_analysis {
6408 assert!(!license.package_name.is_empty());
6409 assert!(matches!(
6410 license.license_type,
6411 LicenseType::MIT
6412 | LicenseType::Apache2
6413 | LicenseType::BSD2Clause
6414 | LicenseType::BSD3Clause
6415 | LicenseType::Unknown
6416 ));
6417 }
6418 }
6419
6420 #[test]
6421 fn test_modern_features_analysis() {
6422 let analyzer = PythonAnalyzer::new();
6423 let code = r#"
6424 from dataclasses import dataclass
6425 from typing import Optional
6426 import asyncio
6427
6428 @dataclass
6429 class User:
6430 name: str
6431 age: int = 0
6432
6433 async def process_data():
6434 async with asyncio.timeout(10):
6435 data = [x for x in range(100)]
6436 processed = (item * 2 for item in data)
6437 return f"Processed {len(data)} items"
6438
6439 def modern_syntax(value: str | int) -> str:
6440 if (result := calculate(value)) > 10:
6441 return f"Result: {result:.2f}"
6442 return "Too small"
6443
6444 match value:
6445 case int() if value > 100:
6446 print("Large number")
6447 case str():
6448 print("String value")
6449 case _:
6450 print("Other")
6451 "#;
6452
6453 let analysis = analyzer.analyze_modern_features(code).unwrap();
6454
6455 assert!(!analysis.dataclass_features.is_empty());
6457
6458 assert!(!analysis.fstring_features.is_empty());
6460
6461 assert!(!analysis.modern_syntax_features.is_empty());
6463
6464 assert!(analysis.overall_modernity_score > 50);
6466 assert!(analysis.overall_modernity_score <= 100);
6467
6468 assert!(analysis
6470 .python_version_detected
6471 .minimum_version
6472 .starts_with("3."));
6473
6474 assert!(!analysis.recommendations.is_empty());
6476 }
6477
6478 #[test]
6479 fn test_dataclass_feature_detection() {
6480 let analyzer = PythonAnalyzer::new();
6481 let code = r#"
6482 from dataclasses import dataclass, field
6483 from typing import List
6484
6485 @dataclass(frozen=True)
6486 class ImmutableUser:
6487 name: str
6488 tags: List[str] = field(default_factory=list)
6489
6490 def __post_init__(self):
6491 object.__setattr__(self, 'processed', True)
6492
6493 class SlottedClass:
6494 __slots__ = ['x', 'y']
6495
6496 def __init__(self, x, y):
6497 self.x = x
6498 self.y = y
6499 "#;
6500
6501 let analysis = analyzer.analyze_modern_features(code).unwrap();
6502
6503 assert!(!analysis.dataclass_features.is_empty());
6504 let dataclass_info = &analysis.dataclass_features[0];
6505
6506 assert_eq!(
6507 dataclass_info.dataclass_type,
6508 DataclassType::StandardDataclass
6509 );
6510 assert!(dataclass_info
6511 .features_used
6512 .contains(&DataclassFeature::FrozenClass));
6513 assert!(dataclass_info
6514 .features_used
6515 .contains(&DataclassFeature::PostInitProcessing));
6516 assert!(dataclass_info
6517 .features_used
6518 .contains(&DataclassFeature::FieldFactories));
6519 assert!(dataclass_info.best_practices_score > 70);
6520 }
6521
6522 #[test]
6523 fn test_fstring_complexity_analysis() {
6524 let analyzer = PythonAnalyzer::new();
6525 let code = r#"
6526 name = "Alice"
6527 value = 42.5
6528
6529 # Simple f-string
6530 simple = f"Hello {name}"
6531
6532 # Complex f-string with formatting
6533 complex = f"Value: {value:.2f}, Length: {len(name)}"
6534
6535 # Very complex f-string
6536 advanced = f"Result: {calculate_result(value) if value > 0 else 'N/A':.3f}"
6537 "#;
6538
6539 let analysis = analyzer.analyze_modern_features(code).unwrap();
6540
6541 assert!(!analysis.fstring_features.is_empty());
6542 assert!(analysis.fstring_features.len() >= 3);
6543
6544 let complexities: Vec<_> = analysis
6546 .fstring_features
6547 .iter()
6548 .map(|f| &f.complexity)
6549 .collect();
6550
6551 assert!(complexities.contains(&&FStringComplexity::Simple));
6552 assert!(
6553 complexities.contains(&&FStringComplexity::Moderate)
6554 || complexities.contains(&&FStringComplexity::Complex)
6555 );
6556 }
6557
6558 #[test]
6559 fn test_modern_syntax_features() {
6560 let analyzer = PythonAnalyzer::new();
6561 let code = r#"
6562 # Walrus operator (Python 3.8+)
6563 if (length := len(data)) > 10:
6564 print(f"Long data: {length}")
6565
6566 # Union type syntax (Python 3.10+)
6567 def process(value: str | int | None) -> str | None:
6568 return str(value) if value is not None else None
6569
6570 # Positional-only parameters (Python 3.8+)
6571 def func(a, b, /, c, d):
6572 return a + b + c + d
6573
6574 # Modern type hints (Python 3.9+)
6575 data: list[dict[str, int]] = []
6576 mapping: dict[str, set[int]] = {}
6577 "#;
6578
6579 let analysis = analyzer.analyze_modern_features(code).unwrap();
6580
6581 assert!(!analysis.modern_syntax_features.is_empty());
6582
6583 let syntax_types: Vec<_> = analysis
6584 .modern_syntax_features
6585 .iter()
6586 .map(|s| &s.feature_type)
6587 .collect();
6588
6589 assert!(syntax_types.contains(&&ModernSyntaxType::WalrusOperator));
6590 assert!(syntax_types.contains(&&ModernSyntaxType::TypeUnionOperator));
6591 assert!(syntax_types.contains(&&ModernSyntaxType::PositionalOnlyParams));
6592 assert!(syntax_types.contains(&&ModernSyntaxType::GenericTypeHints));
6593
6594 assert!(analysis.python_version_detected.minimum_version >= "3.10".to_string());
6596 }
6597}