apisec-code-bolt 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. apisec_code_bolt/__init__.py +42 -0
  2. apisec_code_bolt/__main__.py +11 -0
  3. apisec_code_bolt/analysis/__init__.py +96 -0
  4. apisec_code_bolt/analysis/analyzer.py +2309 -0
  5. apisec_code_bolt/analysis/binding_tracker.py +341 -0
  6. apisec_code_bolt/analysis/call_graph.py +1197 -0
  7. apisec_code_bolt/analysis/call_graph_types.py +332 -0
  8. apisec_code_bolt/analysis/call_resolver.py +988 -0
  9. apisec_code_bolt/analysis/capability_tagger.py +322 -0
  10. apisec_code_bolt/analysis/config_scanner.py +197 -0
  11. apisec_code_bolt/analysis/data_flow.py +1883 -0
  12. apisec_code_bolt/analysis/dependency_extractor.py +959 -0
  13. apisec_code_bolt/analysis/flow_analysis.py +1406 -0
  14. apisec_code_bolt/analysis/hof_catalog.py +61 -0
  15. apisec_code_bolt/analysis/integration_detector.py +1399 -0
  16. apisec_code_bolt/analysis/literal_scanner.py +300 -0
  17. apisec_code_bolt/analysis/path_normalizer.py +55 -0
  18. apisec_code_bolt/analysis/read_site_detector.py +310 -0
  19. apisec_code_bolt/analysis/request_patterns.py +162 -0
  20. apisec_code_bolt/analysis/sensitivity_classifier.py +224 -0
  21. apisec_code_bolt/analysis/sink_evidence.py +333 -0
  22. apisec_code_bolt/analysis/url_prefix_resolver.py +338 -0
  23. apisec_code_bolt/cli/__init__.py +5 -0
  24. apisec_code_bolt/cli/exit_codes.py +17 -0
  25. apisec_code_bolt/cli/main.py +1069 -0
  26. apisec_code_bolt/cloud/__init__.py +1 -0
  27. apisec_code_bolt/cloud/apisec_client.py +118 -0
  28. apisec_code_bolt/cloud/client.py +255 -0
  29. apisec_code_bolt/core/__init__.py +75 -0
  30. apisec_code_bolt/core/config.py +528 -0
  31. apisec_code_bolt/core/credentials.py +65 -0
  32. apisec_code_bolt/core/discovery.py +433 -0
  33. apisec_code_bolt/core/log_format.py +115 -0
  34. apisec_code_bolt/core/manifest.py +1009 -0
  35. apisec_code_bolt/core/repo.py +280 -0
  36. apisec_code_bolt/core/state.py +59 -0
  37. apisec_code_bolt/core/telemetry.py +451 -0
  38. apisec_code_bolt/core/types.py +587 -0
  39. apisec_code_bolt/fingerprinting/__init__.py +1 -0
  40. apisec_code_bolt/frameworks/__init__.py +29 -0
  41. apisec_code_bolt/frameworks/_jwt_common.py +50 -0
  42. apisec_code_bolt/frameworks/auth_helpers.py +437 -0
  43. apisec_code_bolt/frameworks/base.py +608 -0
  44. apisec_code_bolt/frameworks/dotnet/__init__.py +17 -0
  45. apisec_code_bolt/frameworks/dotnet/_path_helpers.py +43 -0
  46. apisec_code_bolt/frameworks/dotnet/aspnet_plugin.py +2546 -0
  47. apisec_code_bolt/frameworks/dotnet/grpc_plugin.py +559 -0
  48. apisec_code_bolt/frameworks/dotnet/jwt_config_extractor.py +545 -0
  49. apisec_code_bolt/frameworks/dotnet/legacy_aspnet_plugin.py +732 -0
  50. apisec_code_bolt/frameworks/dotnet/refit_plugin.py +374 -0
  51. apisec_code_bolt/frameworks/dotnet/wcf_plugin.py +1239 -0
  52. apisec_code_bolt/frameworks/java/__init__.py +6 -0
  53. apisec_code_bolt/frameworks/java/_annotations.py +167 -0
  54. apisec_code_bolt/frameworks/java/_constraints.py +128 -0
  55. apisec_code_bolt/frameworks/java/graphql_plugin.py +287 -0
  56. apisec_code_bolt/frameworks/java/jaxrs_plugin.py +748 -0
  57. apisec_code_bolt/frameworks/java/jwt_config_extractor.py +361 -0
  58. apisec_code_bolt/frameworks/java/micronaut_plugin.py +1059 -0
  59. apisec_code_bolt/frameworks/java/spring_plugin.py +1293 -0
  60. apisec_code_bolt/frameworks/js/__init__.py +8 -0
  61. apisec_code_bolt/frameworks/js/express_plugin.py +391 -0
  62. apisec_code_bolt/frameworks/js/fastify_plugin.py +381 -0
  63. apisec_code_bolt/frameworks/js/graphql_plugin.py +198 -0
  64. apisec_code_bolt/frameworks/js/nestjs_plugin.py +423 -0
  65. apisec_code_bolt/frameworks/python/__init__.py +19 -0
  66. apisec_code_bolt/frameworks/python/celery_plugin.py +393 -0
  67. apisec_code_bolt/frameworks/python/click_plugin.py +427 -0
  68. apisec_code_bolt/frameworks/python/django_plugin.py +867 -0
  69. apisec_code_bolt/frameworks/python/fastapi/__init__.py +28 -0
  70. apisec_code_bolt/frameworks/python/fastapi/plugin.py +1390 -0
  71. apisec_code_bolt/frameworks/python/flask_plugin.py +205 -0
  72. apisec_code_bolt/frameworks/python/graphql_plugin.py +274 -0
  73. apisec_code_bolt/frameworks/python/prefect_plugin.py +251 -0
  74. apisec_code_bolt/frameworks/python/webhook_plugin.py +255 -0
  75. apisec_code_bolt/parsing/__init__.py +62 -0
  76. apisec_code_bolt/parsing/base.py +554 -0
  77. apisec_code_bolt/parsing/csharp/__init__.py +5 -0
  78. apisec_code_bolt/parsing/csharp/language_services.py +203 -0
  79. apisec_code_bolt/parsing/csharp/literals.py +72 -0
  80. apisec_code_bolt/parsing/csharp/parser.py +1158 -0
  81. apisec_code_bolt/parsing/csharp/type_resolver.py +568 -0
  82. apisec_code_bolt/parsing/js/__init__.py +5 -0
  83. apisec_code_bolt/parsing/js/language_services.py +118 -0
  84. apisec_code_bolt/parsing/js/parser.py +622 -0
  85. apisec_code_bolt/parsing/jvm/__init__.py +7 -0
  86. apisec_code_bolt/parsing/jvm/language_services.py +270 -0
  87. apisec_code_bolt/parsing/jvm/parser.py +774 -0
  88. apisec_code_bolt/parsing/jvm/type_resolver.py +422 -0
  89. apisec_code_bolt/parsing/python/__init__.py +150 -0
  90. apisec_code_bolt/parsing/python/cbv_extractor.py +606 -0
  91. apisec_code_bolt/parsing/python/constant_resolver.py +500 -0
  92. apisec_code_bolt/parsing/python/cross_file_resolver.py +1054 -0
  93. apisec_code_bolt/parsing/python/dynamic_route_detector.py +532 -0
  94. apisec_code_bolt/parsing/python/expression_utils.py +221 -0
  95. apisec_code_bolt/parsing/python/extraction_types.py +271 -0
  96. apisec_code_bolt/parsing/python/language_services.py +487 -0
  97. apisec_code_bolt/parsing/python/parameter_analyzer.py +789 -0
  98. apisec_code_bolt/parsing/python/parser.py +719 -0
  99. apisec_code_bolt/parsing/python/path_resolver.py +576 -0
  100. apisec_code_bolt/parsing/python/router_registry.py +806 -0
  101. apisec_code_bolt/parsing/python/type_resolver.py +730 -0
  102. apisec_code_bolt/parsing/python/visitors.py +1544 -0
  103. apisec_code_bolt/parsing/services.py +544 -0
  104. apisec_code_bolt/query/__init__.py +1 -0
  105. apisec_code_bolt/query/ast_cache.py +182 -0
  106. apisec_code_bolt/query/executor.py +283 -0
  107. apisec_code_bolt/query/handlers.py +832 -0
  108. apisec_code_bolt-0.1.0.dist-info/METADATA +230 -0
  109. apisec_code_bolt-0.1.0.dist-info/RECORD +111 -0
  110. apisec_code_bolt-0.1.0.dist-info/WHEEL +4 -0
  111. apisec_code_bolt-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,544 @@
1
+ """
2
+ Abstract service protocols for language-agnostic analysis.
3
+
4
+ These protocols define the contracts that language-specific implementations
5
+ must fulfill. Framework plugins depend on these abstractions, NOT on
6
+ language-specific implementations directly.
7
+
8
+ DESIGN PRINCIPLES:
9
+ 1. Framework plugins should ONLY depend on these protocols
10
+ 2. Language-specific implementations live in parsing/{language}/
11
+ 3. This enables adding new languages without changing framework plugins
12
+ 4. DRY: Common concepts are defined once, implemented per-language
13
+
14
+ Example:
15
+ # Framework plugin uses protocol, not implementation
16
+ class FastAPIPlugin(BaseFrameworkPlugin):
17
+ def extract_routes(self, file: ParsedFile, ctx: AnalysisContext):
18
+ # Uses abstract TypeResolver, not Python-specific CrossFileResolver
19
+ model_fields = ctx.type_resolver.get_model_fields("UserRequest")
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from abc import ABC, abstractmethod
25
+ from dataclasses import dataclass, field
26
+ from pathlib import Path
27
+ from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
28
+
29
+ if TYPE_CHECKING:
30
+ from .base import ParsedClass, ParsedFile
31
+
32
+
33
+ # =============================================================================
34
+ # Type Resolution Protocol
35
+ # =============================================================================
36
+
37
+
38
+ @dataclass
39
+ class ResolvedType:
40
+ """
41
+ A resolved type reference.
42
+
43
+ This is the language-agnostic representation of a resolved type.
44
+ """
45
+
46
+ name: str
47
+ qualified_name: str
48
+ file_path: Path | None = None
49
+
50
+ # Type classification
51
+ is_class: bool = False
52
+ is_model: bool = False # Pydantic, dataclass, DTO, etc.
53
+ is_enum: bool = False
54
+ is_external: bool = False # From external package
55
+ external_package: str | None = None
56
+
57
+ # Inheritance info
58
+ base_classes: list[str] = field(default_factory=list)
59
+ all_ancestors: list[str] = field(default_factory=list) # Full chain
60
+ mro: list[str] = field(default_factory=list) # Method Resolution Order
61
+
62
+ # For models: field information
63
+ fields: list[ResolvedField] = field(default_factory=list)
64
+
65
+ # The original parsed class, if available
66
+ definition: ParsedClass | None = None
67
+
68
+
69
+ @dataclass
70
+ class ResolvedField:
71
+ """A resolved field within a model/class."""
72
+
73
+ name: str
74
+ type_annotation: str | None = None
75
+ default_value: str | None = None
76
+ is_required: bool = True
77
+
78
+ # For nested types
79
+ nested_type_name: str | None = None
80
+ nested_type_qualified: str | None = None
81
+
82
+ # Field metadata
83
+ alias: str | None = None
84
+ description: str | None = None
85
+ constraints: dict[str, Any] = field(default_factory=dict)
86
+
87
+ # Source
88
+ defined_in_class: str | None = None # Which class in MRO defines this
89
+
90
+
91
+ @runtime_checkable
92
+ class TypeResolver(Protocol):
93
+ """
94
+ Protocol for cross-file type resolution.
95
+
96
+ Language implementations provide their own resolvers that fulfill
97
+ this contract. Framework plugins depend on this protocol, not on
98
+ language-specific implementations.
99
+
100
+ Implementations:
101
+ - Python: CrossFileResolver (uses import graph, MRO)
102
+ - Java: Would use package/import resolution
103
+ - TypeScript: Would use module resolution
104
+ """
105
+
106
+ def resolve_type(
107
+ self,
108
+ name: str,
109
+ in_file: Path | None = None,
110
+ ) -> ResolvedType | None:
111
+ """
112
+ Resolve a type reference to its definition.
113
+
114
+ Args:
115
+ name: Type name (simple or qualified)
116
+ in_file: File context for import resolution
117
+
118
+ Returns:
119
+ ResolvedType if found, None otherwise
120
+ """
121
+ ...
122
+
123
+ def is_model_type(
124
+ self,
125
+ name: str,
126
+ in_file: Path | None = None,
127
+ ) -> bool:
128
+ """
129
+ Check if a type is a data model (Pydantic, dataclass, DTO, etc.).
130
+
131
+ This is framework-agnostic: each language defines what constitutes
132
+ a "model" (Python: Pydantic/dataclass, Java: POJO/DTO, etc.)
133
+ """
134
+ ...
135
+
136
+ def get_model_fields(
137
+ self,
138
+ model_name: str,
139
+ in_file: Path | None = None,
140
+ include_inherited: bool = True,
141
+ ) -> list[ResolvedField]:
142
+ """
143
+ Get fields for a model type, optionally including inherited fields.
144
+
145
+ Args:
146
+ model_name: Name of the model class
147
+ in_file: File context
148
+ include_inherited: Whether to include fields from base classes
149
+
150
+ Returns:
151
+ List of resolved fields
152
+ """
153
+ ...
154
+
155
+ def is_subclass_of(
156
+ self,
157
+ class_name: str,
158
+ base_name: str,
159
+ in_file: Path | None = None,
160
+ ) -> bool:
161
+ """
162
+ Check if a class inherits from another.
163
+
164
+ Uses the full inheritance chain, not just direct bases.
165
+ """
166
+ ...
167
+
168
+ def get_all_models(self) -> dict[str, ResolvedType]:
169
+ """Get all detected model types in the project."""
170
+ ...
171
+
172
+
173
+ # =============================================================================
174
+ # Constant/Config Resolution Protocol
175
+ # =============================================================================
176
+
177
+
178
+ @dataclass
179
+ class ResolvedConstant:
180
+ """A resolved constant or configuration value."""
181
+
182
+ name: str
183
+ value: str
184
+
185
+ # Source info
186
+ source_file: Path | None = None
187
+ source_line: int = 0
188
+ source_type: str = "" # "literal", "env_default", "config_attr", etc.
189
+
190
+ # Override tracking
191
+ is_default: bool = True # True if can be overridden at runtime
192
+ override_sources: list[str] = field(default_factory=list) # e.g., ["ENV:API_PREFIX"]
193
+
194
+ # Confidence
195
+ confidence: float = 1.0
196
+
197
+
198
+ @runtime_checkable
199
+ class ConstantResolver(Protocol):
200
+ """
201
+ Protocol for resolving constants and configuration values.
202
+
203
+ Language implementations handle their specific patterns:
204
+ - Python: module constants, Pydantic Settings, os.getenv()
205
+ - Java: application.properties, @Value annotations
206
+ - TypeScript: .env, config objects
207
+ """
208
+
209
+ def resolve(
210
+ self,
211
+ expression: str,
212
+ in_file: Path | None = None,
213
+ ) -> ResolvedConstant | None:
214
+ """
215
+ Resolve a constant expression to its value.
216
+
217
+ Handles:
218
+ - Simple names: API_PREFIX
219
+ - Attribute access: config.PREFIX, settings.api.prefix
220
+ - Env var defaults: os.getenv("X", "default")
221
+ """
222
+ ...
223
+
224
+ def resolve_value(
225
+ self,
226
+ expression: str,
227
+ in_file: Path | None = None,
228
+ ) -> str | None:
229
+ """Convenience method to just get the value string."""
230
+ ...
231
+
232
+ def get_all_constants(self) -> dict[str, str]:
233
+ """Get all resolved constants as name -> value mapping."""
234
+ ...
235
+
236
+
237
+ # =============================================================================
238
+ # Path Resolution Protocol
239
+ # =============================================================================
240
+
241
+
242
+ @dataclass
243
+ class ResolvedPath:
244
+ """A resolved route path."""
245
+
246
+ path: str # The resolved path string
247
+
248
+ # Resolution quality
249
+ confidence: float = 1.0
250
+ is_fully_resolved: bool = True
251
+ unresolved_vars: list[str] = field(default_factory=list)
252
+
253
+ # Notes about resolution
254
+ notes: list[str] = field(default_factory=list)
255
+
256
+
257
+ @runtime_checkable
258
+ class PathResolver(Protocol):
259
+ """
260
+ Protocol for resolving computed route paths.
261
+
262
+ Handles dynamic path construction:
263
+ - Python: f-strings, string concatenation
264
+ - Java: String.format(), concatenation
265
+ - TypeScript: template literals
266
+ """
267
+
268
+ def resolve(
269
+ self,
270
+ path_expression: str,
271
+ file_path: Path,
272
+ additional_context: dict[str, str] | None = None,
273
+ ) -> ResolvedPath:
274
+ """
275
+ Resolve a path expression to its final value.
276
+
277
+ Args:
278
+ path_expression: The path expression (may contain variables)
279
+ file_path: Source file for context
280
+ additional_context: Additional variable bindings
281
+
282
+ Returns:
283
+ ResolvedPath with the resolved string and confidence
284
+ """
285
+ ...
286
+
287
+ def get_all_constants(self) -> dict[str, str]:
288
+ """Get all path-related constants."""
289
+ ...
290
+
291
+
292
+ # =============================================================================
293
+ # Router Registry Protocol (Web Frameworks)
294
+ # =============================================================================
295
+
296
+
297
+ @dataclass
298
+ class RouterInfo:
299
+ """Information about a router/controller definition."""
300
+
301
+ name: str # Variable name
302
+ qualified_name: str
303
+ file_path: Path
304
+
305
+ router_type: str # "app", "router", "controller", "blueprint"
306
+ prefix: str = ""
307
+ tags: list[str] = field(default_factory=list)
308
+
309
+
310
+ @runtime_checkable
311
+ class RouterRegistry(Protocol):
312
+ """
313
+ Protocol for tracking router definitions and hierarchies.
314
+
315
+ Web frameworks often have router nesting:
316
+ - FastAPI: app.include_router(router, prefix="/api")
317
+ - Express: app.use("/api", router)
318
+ - Spring: @RequestMapping("/api") on controller
319
+
320
+ This protocol abstracts the concept of router hierarchies.
321
+ """
322
+
323
+ def resolve_path(
324
+ self,
325
+ router_var: str,
326
+ route_path: str,
327
+ file_path: Path,
328
+ ) -> str:
329
+ """
330
+ Resolve full path including router prefixes.
331
+
332
+ Args:
333
+ router_var: The router variable name
334
+ route_path: The route's local path
335
+ file_path: Source file
336
+
337
+ Returns:
338
+ Full path with all prefixes applied
339
+ """
340
+ ...
341
+
342
+ def get_router(self, name: str, file_path: Path) -> RouterInfo | None:
343
+ """Get router info by variable name."""
344
+ ...
345
+
346
+ def get_all_routers(self) -> dict[str, RouterInfo]:
347
+ """Get all registered routers."""
348
+ ...
349
+
350
+ def get_router_dependencies(
351
+ self,
352
+ router_var: str,
353
+ file_path: Path,
354
+ ) -> list[str]:
355
+ """Return accumulated dependency function names for a router.
356
+
357
+ These are the dependency names declared via ``dependencies=[Depends(...)]``
358
+ on ``include_router()`` calls in the ancestry chain plus constructor-level
359
+ dependencies on the routers themselves.
360
+ """
361
+ ...
362
+
363
+
364
+ # =============================================================================
365
+ # Analysis Context
366
+ # =============================================================================
367
+
368
+
369
+ @dataclass
370
+ class AnalysisContext:
371
+ """
372
+ Context object passed to framework plugins during analysis.
373
+
374
+ This replaces the multiple setter methods on plugins with a single,
375
+ immutable context object. All services are accessed through this.
376
+
377
+ DESIGN:
378
+ - Plugins receive context, don't store mutable state
379
+ - Services are language-specific implementations of protocols
380
+ - Framework plugins only depend on protocols, not implementations
381
+
382
+ Usage:
383
+ def extract_routes(self, file: ParsedFile, ctx: AnalysisContext):
384
+ # Resolve a model type
385
+ fields = ctx.type_resolver.get_model_fields("UserRequest", file.path)
386
+
387
+ # Resolve a computed path
388
+ path = ctx.path_resolver.resolve(f_string_expr, file.path)
389
+
390
+ # Get full path with router prefix
391
+ full_path = ctx.router_registry.resolve_path("router", "/users", file.path)
392
+ """
393
+
394
+ # Required services (always available)
395
+ type_resolver: TypeResolver
396
+
397
+ # Optional services (may be None depending on language/framework)
398
+ constant_resolver: ConstantResolver | None = None
399
+ path_resolver: PathResolver | None = None
400
+ router_registry: RouterRegistry | None = None
401
+
402
+ # Project info
403
+ project_root: Path | None = None
404
+ all_parsed_files: list[ParsedFile] = field(default_factory=list)
405
+
406
+ # Language-specific services (escape hatch for edge cases)
407
+ # Access via: ctx.language_services.get("some_python_thing")
408
+ language_services: dict[str, Any] = field(default_factory=dict)
409
+
410
+ def get_service(self, name: str) -> Any | None:
411
+ """Get a language-specific service by name."""
412
+ return self.language_services.get(name)
413
+
414
+
415
+ # =============================================================================
416
+ # Language Services Factory
417
+ # =============================================================================
418
+
419
+
420
+ class LanguageServices(ABC):
421
+ """
422
+ Abstract factory for language-specific analysis services.
423
+
424
+ Each language implements this to provide its own:
425
+ - Type resolver (import/inheritance resolution)
426
+ - Constant resolver (config/env var resolution)
427
+ - Path resolver (string interpolation)
428
+ - Any framework-specific services
429
+
430
+ DESIGN:
431
+ - One implementation per language (PythonLanguageServices, JavaLanguageServices)
432
+ - Built from parsed files during analysis
433
+ - Returns protocol implementations, not concrete classes
434
+ """
435
+
436
+ @abstractmethod
437
+ def build_type_resolver(
438
+ self,
439
+ parsed_files: list[ParsedFile],
440
+ project_root: Path | None = None,
441
+ ) -> TypeResolver:
442
+ """
443
+ Build the type resolver for this language.
444
+
445
+ Args:
446
+ parsed_files: All successfully parsed files
447
+ project_root: Project root directory
448
+
449
+ Returns:
450
+ TypeResolver implementation for this language
451
+ """
452
+ ...
453
+
454
+ @abstractmethod
455
+ def build_constant_resolver(
456
+ self,
457
+ parsed_files: list[ParsedFile],
458
+ project_root: Path | None = None,
459
+ ) -> ConstantResolver | None:
460
+ """
461
+ Build the constant resolver for this language.
462
+
463
+ Returns None if the language doesn't have meaningful constant resolution.
464
+ """
465
+ ...
466
+
467
+ @abstractmethod
468
+ def build_path_resolver(
469
+ self,
470
+ parsed_files: list[ParsedFile],
471
+ project_root: Path | None = None,
472
+ ) -> PathResolver | None:
473
+ """
474
+ Build the path resolver for this language.
475
+
476
+ Returns None if the language doesn't need path resolution.
477
+ """
478
+ ...
479
+
480
+ def build_framework_services(
481
+ self,
482
+ framework: str,
483
+ parsed_files: list[ParsedFile],
484
+ project_root: Path | None = None,
485
+ ) -> dict[str, Any]:
486
+ """
487
+ Build framework-specific services.
488
+
489
+ Override to provide framework-specific services like RouterRegistry.
490
+ Default returns empty dict.
491
+
492
+ Args:
493
+ framework: Framework name (e.g., "fastapi", "flask", "spring")
494
+ parsed_files: All parsed files
495
+ project_root: Project root
496
+
497
+ Returns:
498
+ Dict of service_name -> service_instance
499
+ """
500
+ return {}
501
+
502
+ def build_context(
503
+ self,
504
+ parsed_files: list[ParsedFile],
505
+ project_root: Path | None = None,
506
+ framework: str | None = None,
507
+ ) -> AnalysisContext:
508
+ """
509
+ Build complete analysis context for this language.
510
+
511
+ This is the main entry point. It builds all services and
512
+ packages them into an AnalysisContext.
513
+ """
514
+ type_resolver = self.build_type_resolver(parsed_files, project_root)
515
+ constant_resolver = self.build_constant_resolver(parsed_files, project_root)
516
+ path_resolver = self.build_path_resolver(parsed_files, project_root)
517
+
518
+ # Build framework-specific services
519
+ framework_services = {}
520
+ if framework:
521
+ framework_services = self.build_framework_services(
522
+ framework, parsed_files, project_root
523
+ )
524
+
525
+ # Wire up dependencies between services
526
+ if constant_resolver and path_resolver:
527
+ # Path resolver may need constant resolver
528
+ if hasattr(path_resolver, "set_constant_resolver"):
529
+ path_resolver.set_constant_resolver(constant_resolver)
530
+
531
+ router_registry = framework_services.get("router_registry")
532
+ if router_registry and constant_resolver:
533
+ if hasattr(router_registry, "set_constant_resolver"):
534
+ router_registry.set_constant_resolver(constant_resolver)
535
+
536
+ return AnalysisContext(
537
+ type_resolver=type_resolver,
538
+ constant_resolver=constant_resolver,
539
+ path_resolver=path_resolver,
540
+ router_registry=router_registry,
541
+ project_root=project_root,
542
+ all_parsed_files=parsed_files,
543
+ language_services=framework_services,
544
+ )
@@ -0,0 +1 @@
1
+ """Query API executor for extended analysis."""