From 185af03608a449423b0064e118c2a1c99074698b Mon Sep 17 00:00:00 2001 From: upstream Date: Fri, 2 Oct 2026 16:05:25 +0200 Subject: [PATCH] Upstream dedoc/scramble state of the touched files before c327325c7c --- src/ScrambleServiceProvider.php | 421 ++++++++++++++++++++++++++++++++ 1 file changed, 421 insertions(+) create mode 100644 src/ScrambleServiceProvider.php diff --git a/src/ScrambleServiceProvider.php b/src/ScrambleServiceProvider.php new file mode 100644 index 0000000..9f1d11e --- /dev/null +++ b/src/ScrambleServiceProvider.php @@ -0,0 +1,421 @@ + */ + public $bindings = [ + RouteProviderContract::class => RouteProvider::class, + ]; + + public $singletons = [ + PrettyPrinter::class => PrettyPrinter\Standard::class, + GeneratorConfigCollection::class => GeneratorConfigCollection::class, + CacheableGenerator::class => CacheableGenerator::class, + ]; + + public function configurePackage(Package $package): void + { + $package + ->name('scramble') + ->hasConfigFile() + ->hasCommand(ExportDocumentation::class) + ->hasCommand(AnalyzeDocumentation::class) + ->hasCommand(CacheDocumentation::class) + ->hasCommand(ClearDocumentationCache::class) + ->hasViews('scramble'); + + $this->app->singleton(FileParser::class, function () { + return new FileParser( + (new ParserFactory)->createForHostVersion() + ); + }); + + $this->app->singleton(LazyShallowReflectionIndex::class, function () { + return new LazyShallowReflectionIndex( + // Abort helpers are handled in the extension and these definitions are needed to avoid leaking the + // annotated exceptions to the caller's definitions. + functions: [ + 'abort' => $abortType = new FunctionLikeDefinition(type: new FunctionType('abort', returnType: new VoidType)), + 'abort_if' => $abortType, + 'abort_unless' => $abortType, + 'throw_if' => $throwType = new FunctionLikeDefinition(type: new FunctionType('throw_if', returnType: new VoidType)), + 'throw_unless' => $throwType, + ] + ); + }); + + $this->app->singleton(Index::class, function () { + $index = new Index; + foreach ((require __DIR__.'/../dictionaries/classMap.php') ?: [] as $className => $serializedClassDefinition) { + $index->classesDefinitions[$className] = unserialize($serializedClassDefinition); + } + + $templates = [$tValue = new TemplateType('TValue')]; + $index->functionsDefinitions['tap'] = new ShallowFunctionDefinition( + type: tap(new FunctionType( + name: 'tap', + arguments: [ + 'value' => $tValue, + ], + returnType: $tValue, + ), function (FunctionType $type) use ($templates) { + $type->templates = $templates; + }) + ); + + return $index; + }); + + $this->app->singleton(Infer::class); + + $this->app->when(ExtensionsBroker::class) + ->needs('$extensions') + ->give(function ($app) { + $extensions = array_merge(config('scramble.extensions', []), Scramble::$extensions); + + $inferExtensionsClasses = array_values(array_filter( + $extensions, + fn ($e) => is_a($e, InferExtension::class, true), + )); + + $inferExtensionsClasses = array_merge($inferExtensionsClasses, [ + JsonApiResourceMethodReturnTypeExtension::class, + JsonApiResourceCollectionMethodReturnTypeExtension::class, + AfterJsonApiResourceDefinitionCreatedExtension::class, + AfterEloquentBuilderDefinitionCreatedExtension::class, + AfterEloquentRelationDefinitionCreatedExtension::class, + AfterModelDefinitionCreatedExtension::class, + AfterEloquentCollectionDefinitionCreatedExtension::class, + JsonResourceExtension::class, + ResourceResponseMethodReturnTypeExtension::class, + JsonResponseMethodReturnTypeExtension::class, + ModelExtension::class, + TransformsToResourceCollectionExtension::class, + EloquentBuilderExtension::class, + RequestExtension::class, + AfterJsonResourceDefinitionCreatedExtension::class, + AfterLengthAwarePaginatorDefinitionCreatedExtension::class, + AfterPaginatorDefinitionCreatedExtension::class, + AfterCursorPaginatorDefinitionCreatedExtension::class, + AfterResourceCollectionDefinitionCreatedExtension::class, + AfterAnonymousResourceCollectionDefinitionCreatedExtension::class, + AfterResponseDefinitionCreatedExtension::class, + ]); + + return array_merge( + [ + new PossibleExceptionInfer, + new AbortHelpersExceptionInfer, + + new PaginateMethodsReturnTypeExtension, + new FacadeStaticMethodReturnTypeExtension, + new ArrStaticMethodReturnTypeExtension, + + new ValidatorTypeInfer, + new ResourceCollectionTypeInfer, + new ResponseFactoryTypeInfer, + new StorageResponseTypeInfer, + + new ArrayMergeReturnTypeExtension, + new TranslationReturnTypeExtension, + + /* Keep this extension last, so the trace info is preserved. */ + new TypeTraceInfer, + ], + array_map(function ($class) { + return app($class); + }, $inferExtensionsClasses) + ); + }); + + $this->app->when(IndexBuildingBroker::class) + ->needs('$indexBuilders') + ->give(function () { + $extensions = array_merge(config('scramble.extensions', []), Scramble::$extensions); + + $indexBuilders = array_values(array_filter( + $extensions, + fn ($e) => is_a($e, IndexBuilder::class, true), + )); + + return array_map(function ($class) { + return app($class); + }, array_merge([PaginatorsCandidatesBuilder::class], $indexBuilders)); + }); + + $this->app->bind(TypeTransformer::class, function (Application $application, array $parameters) { + $extensions = array_merge(config('scramble.extensions', []), Scramble::$extensions); + + $typesToSchemaExtensions = array_values(array_filter( + $extensions, + fn ($e) => is_a($e, TypeToSchemaExtension::class, true), + )); + + $exceptionToResponseExtensions = array_values(array_filter( + $extensions, + fn ($e) => is_a($e, ExceptionToResponseExtension::class, true), + )); + + return new TypeTransformer( + $parameters['infer'] ?? $application->make(Infer::class), + $parameters['context'], + typeToSchemaExtensionsClasses: $parameters['typeToSchemaExtensions'] ?? array_merge([ + PlainObjectToSchema::class, + ResponsableTypeToSchema::class, + ArrayableToSchema::class, + EnumToSchema::class, + JsonResourceTypeToSchema::class, + JsonApiResourceTypeToSchema::class, + CollectionToSchema::class, + EloquentCollectionToSchema::class, + ResourceCollectionTypeToSchema::class, + CursorPaginatorTypeToSchema::class, + PaginatorTypeToSchema::class, + LengthAwarePaginatorTypeToSchema::class, + ResponseTypeToSchema::class, + BinaryFileResponseToSchema::class, + StreamedResponseToSchema::class, + ResourceResponseTypeToSchema::class, + PaginatedResourceResponseTypeToSchema::class, + JsonApiAnonymousCollectionTypeToSchema::class, + JsonApiResourceResponseToSchemaExtension::class, + JsonApiPaginatedResourceResponseToSchemaExtension::class, + CarbonInterfaceToSchema::class, + VoidTypeToSchema::class, + ], $typesToSchemaExtensions), + exceptionToResponseExtensionsClasses: $parameters['exceptionToResponseExtensions'] ?? array_merge([ + ValidationExceptionToResponseExtension::class, + AuthorizationExceptionToResponseExtension::class, + AuthenticationExceptionToResponseExtension::class, + HttpExceptionToResponseExtension::class, + NotFoundExceptionToResponseExtension::class, + ], $exceptionToResponseExtensions), + ); + }); + } + + public function bootingPackage() + { + Scramble::configure() + ->useConfig(config('scramble')) + ->withOperationTransformers(function (OperationTransformers $transformers) { + $extensions = array_merge(config('scramble.extensions', []), Scramble::$extensions); + + $operationExtensions = array_values(array_filter( + $extensions, + fn ($e) => is_a($e, OperationExtension::class, true), + )); + + $transformers->append($operationExtensions); + }) + ->withDocumentTransformers([ + AddDocumentTags::class, + CleanupUnusedResponseReferencesTransformer::class, + ]); + + Scramble::infer() + ->configure() + ->buildDefinitionsUsingAstFor([ + Attribute::class, + ]); + + if (Scramble::$defaultRoutesIgnored) { + Scramble::configure()->expose(false); + } + + $this->registerProNudge(); + + $this->app->booted(function () { + $this->registerRoutes(); + }); + } + + private function registerProNudge(): void + { + if (class_exists('Dedoc\ScramblePro\ScrambleProServiceProvider')) { + return; + } + + Scramble::configure() + ->withOperationTransformers([ + LaravelDataReturnTypeNudgeExtension::class, + QueryBuilderUsageNudgeExtension::class, + ]) + ->withParametersExtractors(function (ParametersExtractors $extractors) { + $extractors->append(LaravelDataRequestBodyNudgeExtractor::class); + }); + } + + private function registerRoutes(): void + { + $this->registerDevToolsAssetsRoute(); + + foreach (Scramble::getConfigurationsInstance()->all() as $api => $generatorConfig) { + /** @var Router $router */ + $router = $this->app->get(Router::class); + + if ($generatorConfig->uiRoute) { + $cb = is_callable($generatorConfig->uiRoute) + ? $generatorConfig->uiRoute + : fn ($router, $action) => $router->get($generatorConfig->uiRoute, $action); + + $cb($router, function (CacheableGenerator $generator) use ($api) { + $config = Scramble::getGeneratorConfig($api); + $result = $generator->generate($config); + + return view($config->renderer()->view, [ + /* + * `spec` here is for backward compatibility in case there is a + * stale published view that expects it to exist, will be removed in 1.0 + */ + 'spec' => $result->spec($config->openApiVersion()), + 'config' => $config, + 'result' => $result, + ]); + })->middleware($generatorConfig->get('middleware', [RestrictedDocsAccess::class])); + } + + if ($generatorConfig->documentRoute) { + $cb = is_callable($generatorConfig->documentRoute) + ? $generatorConfig->documentRoute + : fn ($router, $action) => $router->get($generatorConfig->documentRoute, $action); + + $cb($router, function (CacheableGenerator $generator) use ($api) { + $config = Scramble::getGeneratorConfig($api); + + return response()->json($generator($config), options: JSON_PRETTY_PRINT); + })->middleware($generatorConfig->get('middleware', [RestrictedDocsAccess::class])); + } + } + } + + private function registerDevToolsAssetsRoute(): void + { + if (! DevTools::enabled()) { + return; + } + + /** @var Router $router */ + $router = $this->app->get(Router::class); + $middleware = array_values(array_filter( + (array) config('scramble.middleware', [RestrictedDocsAccess::class]), + fn ($middleware) => is_string($middleware), + )); + + $router->get('_scramble/dev-tools/devtools.js', function () { + $path = DevTools::assetPath(); + + abort_unless(is_file($path), 404); + + return response()->file($path, [ + 'Cache-Control' => 'no-store', + 'Content-Type' => 'text/javascript; charset=UTF-8', + ]); + }) + ->name('scramble.dev-tools.asset') + ->middleware($middleware); + } +}