The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── LICENSE
├── README.md
├── apigen.neon
├── bin
    └── apigen
├── composer.json
├── composer.lock
└── src
    ├── Analyzer.php
    ├── Analyzer
        ├── AnalyzeResult.php
        ├── AnalyzeState.php
        ├── AnalyzeTask.php
        ├── AnalyzeTaskHandler.php
        ├── AnalyzeTaskHandlerFactory.php
        ├── BodySkippingLexer.php
        ├── Filter.php
        ├── IdentifierKind.php
        ├── NameContextFrame.php
        └── NodeVisitors
        │   └── PhpDocResolver.php
    ├── ApiGen.php
    ├── Bootstrap.php
    ├── Helpers.php
    ├── Index
        ├── FileIndex.php
        ├── Index.php
        └── NamespaceIndex.php
    ├── Indexer.php
    ├── Info
        ├── AliasInfo.php
        ├── AliasReferenceInfo.php
        ├── ClassInfo.php
        ├── ClassLikeInfo.php
        ├── ClassLikeReferenceInfo.php
        ├── ConstantInfo.php
        ├── ConstantReferenceInfo.php
        ├── ElementInfo.php
        ├── EnumCaseInfo.php
        ├── EnumInfo.php
        ├── ErrorInfo.php
        ├── ErrorKind.php
        ├── Expr
        │   ├── ArgExprInfo.php
        │   ├── ArrayExprInfo.php
        │   ├── ArrayItemExprInfo.php
        │   ├── BinaryOpExprInfo.php
        │   ├── BooleanExprInfo.php
        │   ├── ClassConstantFetchExprInfo.php
        │   ├── ConstantFetchExprInfo.php
        │   ├── DimFetchExprInfo.php
        │   ├── FloatExprInfo.php
        │   ├── IntegerExprInfo.php
        │   ├── NewExprInfo.php
        │   ├── NullExprInfo.php
        │   ├── NullSafePropertyFetchExprInfo.php
        │   ├── PropertyFetchExprInfo.php
        │   ├── StringExprInfo.php
        │   ├── TernaryExprInfo.php
        │   └── UnaryOpExprInfo.php
        ├── ExprInfo.php
        ├── FunctionInfo.php
        ├── FunctionReferenceInfo.php
        ├── GenericParameterInfo.php
        ├── GenericParameterVariance.php
        ├── InterfaceInfo.php
        ├── MemberInfo.php
        ├── MemberReferenceInfo.php
        ├── MethodInfo.php
        ├── MethodReferenceInfo.php
        ├── MissingInfo.php
        ├── NameInfo.php
        ├── ParameterInfo.php
        ├── PropertyInfo.php
        ├── PropertyReferenceInfo.php
        ├── TraitInfo.php
        └── Traits
        │   ├── HasGenericParameters.php
        │   ├── HasLineLocation.php
        │   ├── HasTags.php
        │   └── HasVisibility.php
    ├── Locator.php
    ├── Renderer.php
    ├── Renderer
        ├── Filter.php
        ├── Latte
        │   ├── LatteCascadingLoader.php
        │   ├── LatteEngineFactory.php
        │   ├── LatteExtension.php
        │   ├── LatteFunctions.php
        │   ├── LattePreNode.php
        │   ├── LatteRenderTask.php
        │   ├── LatteRenderTaskContext.php
        │   ├── LatteRenderTaskHandler.php
        │   ├── LatteRenderTaskHandlerFactory.php
        │   ├── LatteRenderTaskType.php
        │   ├── LatteRenderer.php
        │   └── Template
        │   │   ├── ClassLikeTemplate.php
        │   │   ├── ConfigParameters.php
        │   │   ├── FunctionTemplate.php
        │   │   ├── IndexTemplate.php
        │   │   ├── LayoutParameters.php
        │   │   ├── NamespaceTemplate.php
        │   │   ├── SitemapTemplate.php
        │   │   ├── SourceTemplate.php
        │   │   ├── TreeTemplate.php
        │   │   ├── assets
        │   │       ├── main.css
        │   │       └── main.js
        │   │   ├── blocks
        │   │       ├── @index.latte
        │   │       ├── alias.latte
        │   │       ├── aliasLink.latte
        │   │       ├── aliasSummary.latte
        │   │       ├── autoBreakingLine.latte
        │   │       ├── classLikeDescription.latte
        │   │       ├── classLikeKind.latte
        │   │       ├── classLikeLink.latte
        │   │       ├── classLikeLinks.latte
        │   │       ├── classLikeReference.latte
        │   │       ├── classLikeSignature.latte
        │   │       ├── classLikeSignatureTable.latte
        │   │       ├── classTree.latte
        │   │       ├── constant.latte
        │   │       ├── constantInherited.latte
        │   │       ├── constantInheritedSummary.latte
        │   │       ├── constantSummary.latte
        │   │       ├── description.latte
        │   │       ├── elementSummary.latte
        │   │       ├── elementSummaryGroup.latte
        │   │       ├── enumCase.latte
        │   │       ├── enumCaseSummary.latte
        │   │       ├── expr.latte
        │   │       ├── functionDescription.latte
        │   │       ├── functionLinks.latte
        │   │       ├── genericParameters.latte
        │   │       ├── head.latte
        │   │       ├── layout.latte
        │   │       ├── member.latte
        │   │       ├── memberDescription.latte
        │   │       ├── memberLink.latte
        │   │       ├── memberSourceLink.latte
        │   │       ├── memberVisibility.latte
        │   │       ├── menu.latte
        │   │       ├── menuElements.latte
        │   │       ├── menuGroup.latte
        │   │       ├── method.latte
        │   │       ├── methodInherited.latte
        │   │       ├── methodInheritedSummary.latte
        │   │       ├── methodRelation.latte
        │   │       ├── methodSignature.latte
        │   │       ├── methodSummary.latte
        │   │       ├── methodUsed.latte
        │   │       ├── methodUsedSummary.latte
        │   │       ├── namespaceLinks.latte
        │   │       ├── navbar.latte
        │   │       ├── parameter.latte
        │   │       ├── property.latte
        │   │       ├── propertyInherited.latte
        │   │       ├── propertyInheritedSummary.latte
        │   │       ├── propertySummary.latte
        │   │       ├── propertyUsed.latte
        │   │       ├── propertyUsedSummary.latte
        │   │       ├── search.latte
        │   │       ├── source.latte
        │   │       └── type.latte
        │   │   └── pages
        │   │       ├── classLike.latte
        │   │       ├── function.latte
        │   │       ├── index.latte
        │   │       ├── namespace.latte
        │   │       ├── sitemap.latte
        │   │       ├── source.latte
        │   │       └── tree.latte
        ├── SourceHighlighter.php
        └── UrlGenerator.php
    ├── Scheduler.php
    ├── Scheduler
        ├── ExecScheduler.php
        ├── ForkScheduler.php
        ├── SchedulerFactory.php
        ├── SimpleScheduler.php
        ├── WorkerScheduler.php
        └── worker.php
    └── Task
        ├── Task.php
        ├── TaskHandler.php
        └── TaskHandlerFactory.php


/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2010 David Grudl <https://davidgrudl.com>
 4 | & ApiGen Contributors <https://github.com/ApiGen/ApiGen/graphs/contributors>
 5 | 
 6 | Permission is hereby granted, free of charge, to any person obtaining a copy
 7 | of this software and associated documentation files (the "Software"), to deal
 8 | in the Software without restriction, including without limitation the rights
 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 | 
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 | 
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | # Smart and Readable Documentation for PHP projects
  2 | 
  3 | ApiGen is easy to use and modern API doc generator **supporting all PHP 8.3 features**.
  4 | 
  5 | 
  6 | ## Features
  7 | 
  8 | - phpDoc
  9 |   - [all types supported by PHPStan](https://phpstan.org/writing-php-code/phpdoc-types)
 10 |   - [generic class declarations](https://phpstan.org/blog/generics-in-php-using-phpdocs)
 11 |   - [local type aliases](https://phpstan.org/writing-php-code/phpdoc-types#local-type-aliases)
 12 | - PHP 8.3
 13 |   - [typed class constants](https://wiki.php.net/rfc/typed_class_constants)
 14 | - PHP 8.2
 15 |   - [constants in traits](https://wiki.php.net/rfc/constants_in_traits)
 16 |   - [fetch enum properties in const expressions](https://wiki.php.net/rfc/fetch_property_in_const_expressions)
 17 |   - [disjunctive normal form types](https://wiki.php.net/rfc/dnf_types)
 18 |   - [readonly classes](https://wiki.php.net/rfc/readonly_classes)
 19 |   - [true](https://wiki.php.net/rfc/true-type), [false and null types](https://wiki.php.net/rfc/null-false-standalone-types)
 20 | - PHP 8.1
 21 |   - [enums](https://wiki.php.net/rfc/enumerations)
 22 |   - [pure intersection types](https://wiki.php.net/rfc/pure-intersection-types)
 23 |   - [never type](https://wiki.php.net/rfc/noreturn_type)
 24 |   - [final class constants](https://wiki.php.net/rfc/final_class_const)
 25 |   - [new in initializers](https://wiki.php.net/rfc/new_in_initializers)
 26 |   - [readonly properties](https://wiki.php.net/rfc/readonly_properties_v2)
 27 | - PHP 8.0
 28 |   - [constructor property promotion](https://wiki.php.net/rfc/constructor_promotion)
 29 |   - [union types](https://wiki.php.net/rfc/union_types_v2)
 30 |   - [mixed type](https://wiki.php.net/rfc/mixed_type_v2)
 31 |   - [static return type](https://wiki.php.net/rfc/static_return_type)
 32 | - PHP 7.4
 33 |   - [typed properties](https://wiki.php.net/rfc/typed_properties_v2)
 34 | - PHP 7.2
 35 |   - [object type](https://wiki.php.net/rfc/object-typehint)
 36 | - PHP 7.1
 37 |   - [nullable types](https://wiki.php.net/rfc/nullable_types)
 38 |   - [iterable type](https://wiki.php.net/rfc/iterable)
 39 |   - [void type](https://wiki.php.net/rfc/void_return_type)
 40 |   - [class constant visibility](https://wiki.php.net/rfc/class_const_visibility)
 41 | - PHP 7.0
 42 |   - [scalar types](https://wiki.php.net/rfc/scalar_type_hints_v5)
 43 |   - [return types](https://wiki.php.net/rfc/return_types)
 44 | - PHP 5.6
 45 |   - [constant scalar expressions](https://wiki.php.net/rfc/const_scalar_exprs)
 46 |   - [variadic functions](https://wiki.php.net/rfc/variadics)
 47 | - PHP 5.4
 48 |   - [traits](https://wiki.php.net/rfc/horizontalreuse)
 49 |   - [callable type](https://wiki.php.net/rfc/callable)
 50 |   - [binary integer notation](https://wiki.php.net/rfc/binnotation4ints)
 51 | 
 52 | 
 53 | ## Built on Shoulders of Giants
 54 | 
 55 | - [nikic/php-parser](https://github.com/nikic/PHP-Parser)
 56 | - [phpstan/phpdoc-parser](https://github.com/phpstan/phpdoc-parser)
 57 | - [latte/latte](https://github.com/nette/latte)
 58 | - [league/commonmark](https://github.com/thephpleague/commonmark)
 59 | 
 60 | 
 61 | ## Install
 62 | 
 63 | ### With Docker
 64 | 
 65 | ApiGen is available as [apigen/apigen](https://hub.docker.com/r/apigen/apigen) Docker image which you can directly use.
 66 | 
 67 | ```bash
 68 | docker run --rm --interactive --tty --volume "$PWD:$PWD" --workdir "$PWD" \
 69 |   apigen/apigen:edge \
 70 |   src --output docs
 71 | ```
 72 | 
 73 | 
 74 | ### With Phar
 75 | 
 76 | This will install ApiGen phar binary to `tools/apigen`.
 77 | 
 78 | ```bash
 79 | mkdir -p tools
 80 | curl -L https://github.com/ApiGen/ApiGen/releases/latest/download/apigen.phar -o tools/apigen
 81 | chmod +x tools/apigen
 82 | tools/apigen src --output docs
 83 | ```
 84 | 
 85 | 
 86 | ### With Composer
 87 | 
 88 | This will install ApiGen to `tools/apigen` directory with executable entry point available in `tools/apigen/bin/apigen`.
 89 | 
 90 | ```bash
 91 | composer create-project --no-dev apigen/apigen:^7.0@alpha tools/apigen
 92 | tools/apigen/bin/apigen src --output docs
 93 | ```
 94 | 
 95 | 
 96 | ## Usage
 97 | 
 98 | Generate API docs by passing source directories and destination option:
 99 | 
100 | ```bash
101 | apigen src --output docs
102 | ```
103 | 
104 | 
105 | ## Configuration
106 | 
107 | ApiGen can be configured with `apigen.neon` configuration file.
108 | 
109 | ```neon
110 | parameters:
111 |   # string[], passed as arguments in CLI, e.g. ['src']
112 |   paths: []
113 | 
114 |   # string[], --include in CLI, included files mask, e.g. ['*.php']
115 |   include: ['*.php']
116 | 
117 |   # string[], --exclude in CLI, excluded files mask, e.g. ['tests/**']
118 |   exclude: []
119 | 
120 |   # bool, should protected members be excluded?
121 |   excludeProtected: false
122 | 
123 |   # bool, should private members be excluded?
124 |   excludePrivate: true
125 | 
126 |   # string[], list of tags used for excluding class-likes and members
127 |   excludeTagged: ['internal']
128 | 
129 |   # string, --output in CLI
130 |   outputDir: '%workingDir%/api'
131 | 
132 |   # string | null, --theme in CLI
133 |   themeDir: null
134 | 
135 |   # string, --title in CLI
136 |   title: 'API Documentation'
137 | 
138 |   # string, --base-url in CLI
139 |   baseUrl: ''
140 | 
141 |   # int, --workers in CLI, number of processes that will be forked for parallel rendering
142 |   workerCount: 8
143 | 
144 |   # string, --memory-limit in CLI
145 |   memoryLimit: '512M'
146 | ```
147 | 


--------------------------------------------------------------------------------
/apigen.neon:
--------------------------------------------------------------------------------
  1 | parameters:
  2 | 	# string[], passed as arguments in CLI, e.g. ['src']
  3 | 	paths: []
  4 | 
  5 | 	# string[], --include in CLI, included files mask, e.g. ['*.php']
  6 | 	include: ['*.php']
  7 | 
  8 | 	# string[], --exclude in CLI, excluded files mask, e.g. ['tests/**']
  9 | 	exclude: []
 10 | 
 11 | 	# bool, should protected members be excluded?
 12 | 	excludeProtected: false
 13 | 
 14 | 	# bool, should private members be excluded?
 15 | 	excludePrivate: true
 16 | 
 17 | 	# string[], list of tags used for excluding class-likes and members
 18 | 	excludeTagged: ['internal']
 19 | 
 20 | 	# string, --output in CLI
 21 | 	outputDir: '%workingDir%/api'
 22 | 
 23 | 	# string | null
 24 | 	themeDir: null
 25 | 
 26 | 	# string, --title in CLI
 27 | 	title: ''
 28 | 
 29 | 	# string, --base-url in CLI
 30 | 	baseUrl: ''
 31 | 
 32 | 	# int, --workers in CLI
 33 | 	workerCount: 8
 34 | 
 35 | 	# string, --memory-limit in CLI
 36 | 	memoryLimit: '512M'
 37 | 
 38 | 
 39 | services:
 40 | 	# ApiGen
 41 | 	apigen:
 42 | 		factory: ApiGen\ApiGen(paths: %paths%, include: %include%, exclude: %exclude%)
 43 | 
 44 | 	# Locator
 45 | 	locator:
 46 | 		factory: ApiGen\Locator::create(projectDir: %workingDir%)
 47 | 
 48 | 	# Scheduler
 49 | 	scheduler.factory:
 50 | 		factory: ApiGen\Scheduler\SchedulerFactory(workerCount: %workerCount%)
 51 | 
 52 | 	# Analyzer
 53 | 	analyze.phpDocResolver:
 54 | 		factory: ApiGen\Analyzer\NodeVisitors\PhpDocResolver
 55 | 
 56 | 	analyzer.nodeTraverser:
 57 | 		type: PhpParser\NodeTraverserInterface
 58 | 		factory: PhpParser\NodeTraverser
 59 | 		setup!:
 60 | 			- addVisitor(@PhpParser\NodeVisitor\NameResolver)
 61 | 			- addVisitor(@ApiGen\Analyzer\NodeVisitors\PhpDocResolver)
 62 | 
 63 | 	analyzer.filter:
 64 | 		factory: ApiGen\Analyzer\Filter(excludeProtected: %excludeProtected%, excludePrivate: %excludePrivate%, excludeTagged: %excludeTagged%)
 65 | 
 66 | 	analyzer.taskHandlerFactory:
 67 | 		implement: ApiGen\Analyzer\AnalyzeTaskHandlerFactory
 68 | 
 69 | 	analyzer:
 70 | 		factory: ApiGen\Analyzer
 71 | 
 72 | 	# Indexer
 73 | 	indexer:
 74 | 		factory: ApiGen\Indexer
 75 | 
 76 | 	# Renderer
 77 | 	renderer.filter:
 78 | 		factory: ApiGen\Renderer\Filter
 79 | 
 80 | 	renderer.urlGenerator:
 81 | 		factory: ApiGen\Renderer\UrlGenerator(ApiGen\Helpers::baseDir(%paths%), %baseUrl%)
 82 | 
 83 | 	renderer.sourceHighlighter:
 84 | 		factory: ApiGen\Renderer\SourceHighlighter
 85 | 
 86 | 	renderer.latte.extension:
 87 | 		factory: ApiGen\Renderer\Latte\LatteExtension
 88 | 
 89 | 	renderer.latte.functions:
 90 | 		factory: ApiGen\Renderer\Latte\LatteFunctions
 91 | 
 92 | 	renderer.latte.engineFactory:
 93 | 		factory: ApiGen\Renderer\Latte\LatteEngineFactory(tempDir: %tempDir%, themeDir: %themeDir%)
 94 | 
 95 | 	renderer.latte.engine:
 96 | 		factory: @ApiGen\Renderer\Latte\LatteEngineFactory::create()
 97 | 
 98 | 	renderer.taskHandlerFactory:
 99 | 		implement: ApiGen\Renderer\Latte\LatteRenderTaskHandlerFactory
100 | 		arguments:
101 | 			outputDir: %outputDir%
102 | 
103 | 	renderer:
104 | 		type: ApiGen\Renderer
105 | 		factory: ApiGen\Renderer\Latte\LatteRenderer(outputDir: %outputDir%, title: %title%, version: %version%)
106 | 
107 | 	# league/commonmark
108 | 	commonMark:
109 | 		type: League\CommonMark\ConverterInterface
110 | 		factory: League\CommonMark\GithubFlavoredMarkdownConverter
111 | 
112 | 	# nikic/php-parser
113 | 	phpParser.nameResolver:
114 | 		factory: PhpParser\NodeVisitor\NameResolver
115 | 
116 | 	phpParser.nameContext:
117 | 		factory: @PhpParser\NodeVisitor\NameResolver::getNameContext()
118 | 
119 | 	phpParser.lexer:
120 | 		type: PhpParser\Lexer
121 | 		factory: ApiGen\Analyzer\BodySkippingLexer
122 | 
123 | 	phpParser.phpVersion:
124 | 		type: PhpParser\PhpVersion
125 | 		factory: PhpParser\PhpVersion::getHostVersion()
126 | 
127 | 	phpParser:
128 | 		type: PhpParser\Parser
129 | 		factory: PhpParser\Parser\Php8
130 | 
131 | 	# phpstan/phpdoc-parser
132 | 	phpDocParser.typeParser:
133 | 		factory: PHPStan\PhpDocParser\Parser\TypeParser
134 | 
135 | 	phpDocParser.constExprParser:
136 | 		factory: PHPStan\PhpDocParser\Parser\ConstExprParser
137 | 
138 | 	phpDocParser.lexer:
139 | 		factory: PHPStan\PhpDocParser\Lexer\Lexer
140 | 
141 | 	phpDocParser:
142 | 		factory: PHPStan\PhpDocParser\Parser\PhpDocParser(requireWhitespaceBeforeDescription: true)
143 | 
144 | 	# symfony/console
145 | 	symfonyConsole.output:
146 | 		type: Symfony\Component\Console\Style\OutputStyle
147 | 		imported: true
148 | 


--------------------------------------------------------------------------------
/bin/apigen:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env php
 2 | <?php declare(strict_types = 1);
 3 | 
 4 | use ApiGen\Bootstrap;
 5 | use Composer\InstalledVersions;
 6 | use Symfony\Component\Console\Command\Command;
 7 | use Symfony\Component\Console\Input\InputArgument;
 8 | use Symfony\Component\Console\Input\InputInterface;
 9 | use Symfony\Component\Console\Input\InputOption;
10 | use Symfony\Component\Console\Output\ConsoleOutputInterface;
11 | use Symfony\Component\Console\SingleCommandApplication;
12 | use Symfony\Component\Console\Style\SymfonyStyle;
13 | 
14 | 
15 | if (is_file(__DIR__ . '/../vendor/autoload.php')) {
16 | 	require __DIR__ . '/../vendor/autoload.php';
17 | 
18 | } elseif (is_file(__DIR__ . '/../../../autoload.php')) {
19 | 	require __DIR__ . '/../../../autoload.php';
20 | 
21 | } else {
22 | 	fwrite(STDERR, "ERROR: unable to find autoloader\n");
23 | 	exit(1);
24 | }
25 | 
26 | Bootstrap::configureErrorHandling();
27 | 
28 | $app = new SingleCommandApplication('ApiGen');
29 | $app->setVersion(InstalledVersions::getPrettyVersion('apigen/apigen'));
30 | 
31 | $app->addArgument('path', InputArgument::IS_ARRAY, 'path directory to analyze');
32 | $app->addOption('config', 'c', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'path to config file');
33 | $app->addOption('include', 'i', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'includes files mask, e.g. *.php');
34 | $app->addOption('exclude', 'e', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'excluded files mask, e.g. tests/**');
35 | $app->addOption('working-dir', null, InputOption::VALUE_REQUIRED, 'project directory (containing composer.json), defaults to current working directory');
36 | $app->addOption('temp', null, InputOption::VALUE_REQUIRED, 'temp directory, defaults to sys_get_temp_dir() . \'/apigen\'');
37 | $app->addOption('workers', 'w', InputOption::VALUE_REQUIRED, 'worker count');
38 | $app->addOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'memory limit');
39 | $app->addOption('title', 't', InputOption::VALUE_REQUIRED, 'title');
40 | $app->addOption('base-url', null, InputOption::VALUE_REQUIRED, 'base URL');
41 | $app->addOption('output', 'o', InputOption::VALUE_REQUIRED, 'output directory');
42 | $app->addOption('theme', null, InputOption::VALUE_REQUIRED, 'theme directory');
43 | 
44 | $app->setCode(function (InputInterface $input, ConsoleOutputInterface $output): int {
45 | 	$paths = $input->getArgument('path');
46 | 	$options = $input->getOptions();
47 | 
48 | 	$optionsMapping = [
49 | 		'include' => 'include',
50 | 		'exclude' => 'exclude',
51 | 		'working-dir' => 'workingDir',
52 | 		'temp' => 'tempDir',
53 | 		'workers' => 'workerCount',
54 | 		'memory-limit' => 'memoryLimit',
55 | 		'title' => 'title',
56 | 		'base-url' => 'baseUrl',
57 | 		'output' => 'outputDir',
58 | 		'theme' => 'themeDir',
59 | 	];
60 | 
61 | 	$numericOptions = [
62 | 		'workers' => true,
63 | 	];
64 | 
65 | 	$parameters = [];
66 | 	foreach ($optionsMapping as $optionKey => $parameterKey) {
67 | 		$optionValue = $options[$optionKey];
68 | 		if (is_string($optionValue)) {
69 | 			$parameters[$parameterKey] = isset($numericOptions[$optionKey]) ? (int) $optionValue : $optionValue;
70 | 
71 | 		} elseif (is_array($optionValue) && count($optionValue) > 0) {
72 | 			$parameters[$parameterKey] = $optionValue;
73 | 		}
74 | 	}
75 | 
76 | 	if (count($paths) > 0) {
77 | 		$parameters['paths'] = $paths;
78 | 	}
79 | 
80 | 	$style = new SymfonyStyle($input, $output);
81 | 	$apiGen = Bootstrap::createApiGen($style, $parameters, $options['config']);
82 | 
83 | 	return $apiGen->generate() ? Command::SUCCESS : Command::FAILURE;
84 | });
85 | 
86 | $app->run();
87 | 


--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
 1 | {
 2 | 	"name": "apigen/apigen",
 3 | 	"description": "PHP source code API generator.",
 4 | 	"license": "MIT",
 5 | 	"authors": [
 6 | 		{"name": "ApiGen Contributors", "homepage": "https://github.com/apigen/apigen/graphs/contributors"},
 7 | 		{"name": "Jaroslav Hanslík", "homepage": "https://github.com/kukulich"},
 8 | 		{"name": "Ondřej Nešpor", "homepage": "https://github.com/andrewsville"},
 9 | 		{"name": "David Grudl", "homepage": "https://davidgrudl.com"}
10 | 	],
11 | 	"bin": ["bin/apigen"],
12 | 	"require": {
13 | 		"php": "^8.1",
14 | 		"ext-ctype": "*",
15 | 		"ext-json": "*",
16 | 		"ext-mbstring": "*",
17 | 		"ext-tokenizer": "*",
18 | 		"composer-runtime-api": "^2.0",
19 | 		"jetbrains/phpstorm-stubs": "^2024.2",
20 | 		"latte/latte": "^3.0",
21 | 		"league/commonmark": "^2.3",
22 | 		"nette/di": "^3.1",
23 | 		"nette/finder": "^3.0",
24 | 		"nette/schema": "^1.2",
25 | 		"nette/utils": "^4.0",
26 | 		"nikic/php-parser": "^5.3",
27 | 		"phpstan/php-8-stubs": "^0.4.0",
28 | 		"phpstan/phpdoc-parser": "^1.16",
29 | 		"symfony/console": "^6.4"
30 | 	},
31 | 	"require-dev": {
32 | 		"nette/neon": "^3.4",
33 | 		"nette/tester": "^2.4",
34 | 		"phpstan/phpstan": "^1.9",
35 | 		"tracy/tracy": "^2.9"
36 | 	},
37 | 	"replace": {
38 | 		"symfony/polyfill-ctype": "*",
39 | 		"symfony/polyfill-mbstring": "*",
40 | 		"symfony/polyfill-php80": "*"
41 | 	},
42 | 	"autoload": {
43 | 		"psr-4": {
44 | 			"ApiGen\\": "src"
45 | 		}
46 | 	},
47 | 	"autoload-dev": {
48 | 		"psr-4": {
49 | 			"ApiGenTests\\": "tests"
50 | 		}
51 | 	},
52 | 	"config": {
53 | 		"platform-check": true,
54 | 		"sort-packages": true,
55 | 		"platform": {
56 | 			"php": "8.1"
57 | 		}
58 | 	},
59 | 	"scripts": {
60 | 		"check": [
61 | 			"@check:phpstan",
62 | 			"@check:tests"
63 | 		],
64 | 		"check:tests": "tester -C tests",
65 | 		"check:phpstan": "phpstan analyse -vvv"
66 | 	}
67 | }
68 | 


--------------------------------------------------------------------------------
/src/Analyzer.php:
--------------------------------------------------------------------------------
  1 | <?php declare(strict_types = 1);
  2 | 
  3 | namespace ApiGen;
  4 | 
  5 | use ApiGen\Analyzer\AnalyzeResult;
  6 | use ApiGen\Analyzer\AnalyzeState;
  7 | use ApiGen\Analyzer\AnalyzeTask;
  8 | use ApiGen\Analyzer\AnalyzeTaskHandlerFactory;
  9 | use ApiGen\Info\ClassLikeInfo;
 10 | use ApiGen\Info\ClassLikeReferenceInfo;
 11 | use ApiGen\Info\ErrorInfo;
 12 | use ApiGen\Info\ErrorKind;
 13 | use ApiGen\Info\FunctionInfo;
 14 | use ApiGen\Info\MissingInfo;
 15 | use ApiGen\Info\NameInfo;
 16 | use ApiGen\Scheduler\SchedulerFactory;
 17 | use Symfony\Component\Console\Helper\ProgressBar;
 18 | 
 19 | use function count;
 20 | use function implode;
 21 | 
 22 | 
 23 | class Analyzer
 24 | {
 25 | 	public function __construct(
 26 | 		protected SchedulerFactory $schedulerFactory,
 27 | 		protected Locator $locator,
 28 | 	) {
 29 | 	}
 30 | 
 31 | 
 32 | 	/**
 33 | 	 * @param string[] $files indexed by []
 34 | 	 */
 35 | 	public function analyze(ProgressBar $progressBar, array $files): AnalyzeResult
 36 | 	{
 37 | 		$scheduler = $this->schedulerFactory->create(AnalyzeTaskHandlerFactory::class, context: null);
 38 | 		$state = new AnalyzeState($progressBar, $scheduler);
 39 | 
 40 | 		foreach ($files as $file) {
 41 | 			$this->scheduleFile($state, $file, primary: true);
 42 | 		}
 43 | 
 44 | 		/** @var AnalyzeTask $task */
 45 | 		foreach ($scheduler->process() as $task => $result) {
 46 | 			$this->processTaskResult($result, $state);
 47 | 			$progressBar->setMessage($task->sourceFile);
 48 | 			$progressBar->advance();
 49 | 		}
 50 | 
 51 | 		foreach ($state->missing as $missing) {
 52 | 			$referencedBy = $state->classLikes[$missing->referencedBy->fullLower] ?? $state->functions[$missing->referencedBy->fullLower];
 53 | 
 54 | 			if ($referencedBy->primary) {
 55 | 				$state->errors[ErrorKind::MissingSymbol->name][] = $this->createMissingSymbolError($missing, $referencedBy);
 56 | 			}
 57 | 		}
 58 | 
 59 | 		return new AnalyzeResult($state->classLikes + $state->missing, $state->functions, $state->errors);
 60 | 	}
 61 | 
 62 | 
 63 | 	protected function scheduleFile(AnalyzeState $state, string $file, bool $primary): void
 64 | 	{
 65 | 		$file = Helpers::realPath($file);
 66 | 
 67 | 		if (isset($state->files[$file])) {
 68 | 			return;
 69 | 		}
 70 | 
 71 | 		$state->files[$file] = true;
 72 | 		$state->progressBar->setMaxSteps(count($state->files));
 73 | 		$state->scheduler->schedule(new AnalyzeTask($file, $primary));
 74 | 	}
 75 | 
 76 | 
 77 | 	/**
 78 | 	 * @param  array<ClassLikeInfo | FunctionInfo | ClassLikeReferenceInfo | ErrorInfo> $result
 79 | 	 */
 80 | 	protected function processTaskResult(array $result, AnalyzeState $state): void
 81 | 	{
 82 | 		foreach ($result as $info) {
 83 | 			match (true) {
 84 | 				$info instanceof ClassLikeReferenceInfo => $this->processClassLikeReference($state, $info),
 85 | 				$info instanceof ClassLikeInfo => $this->processClassLike($state, $info),
 86 | 				$info instanceof FunctionInfo => $this->processFunction($state, $info),
 87 | 				$info instanceof ErrorInfo => $this->processError($state, $info),
 88 | 			};
 89 | 		}
 90 | 	}
 91 | 
 92 | 
 93 | 	protected function processClassLikeReference(AnalyzeState $state, ClassLikeReferenceInfo $info): void
 94 | 	{
 95 | 		if ($state->prevName !== null && !isset($state->classLikes[$info->fullLower]) && !isset($state->missing[$info->fullLower])) {
 96 | 			$name = new NameInfo($info->full, $info->fullLower);
 97 | 			$state->missing[$info->fullLower] = new MissingInfo($name, $state->prevName);
 98 | 
 99 | 			if (($file = $this->locator->locate($info)) !== null) {
100 | 				$this->scheduleFile($state, $file, primary: false);
101 | 			}
102 | 		}
103 | 	}
104 | 
105 | 
106 | 	protected function processClassLike(AnalyzeState $state, ClassLikeInfo $info): void
107 | 	{
108 | 		$existing = $state->classLikes[$info->name->fullLower] ?? null;
109 | 
110 | 		if ($existing === null || ($info->primary && !$existing->primary)) {
111 | 			unset($state->missing[$info->name->fullLower]);
112 | 			$state->classLikes[$info->name->fullLower] = $info;
113 | 			$state->prevName = $info->name;
114 | 
115 | 		} elseif ($info->primary) {
116 | 			$state->errors[ErrorKind::DuplicateSymbol->name][] = $this->createDuplicateSymbolError($info, $existing);
117 | 			$state->prevName = null;
118 | 
119 | 		} else {
120 | 			$state->prevName = null;
121 | 		}
122 | 	}
123 | 
124 | 
125 | 	protected function processFunction(AnalyzeState $state, FunctionInfo $info): void
126 | 	{
127 | 		$existing = $state->functions[$info->name->fullLower] ?? null;
128 | 
129 | 		if ($existing === null || ($info->primary && !$existing->primary)) {
130 | 			$state->functions[$info->name->fullLower] = $info;
131 | 			$state->prevName = $info->name;
132 | 
133 | 		} elseif ($info->primary) {
134 | 			$state->errors[ErrorKind::DuplicateSymbol->name][] = $this->createDuplicateSymbolError($info, $existing);
135 | 			$state->prevName = null;
136 | 
137 | 		} else {
138 | 			$state->prevName = null;
139 | 		}
140 | 	}
141 | 
142 | 
143 | 	protected function processError(AnalyzeState $state, ErrorInfo $info): void
144 | 	{
145 | 		$state->errors[$info->kind->name][] = $info;
146 | 		$state->prevName = null;
147 | 	}
148 | 
149 | 
150 | 	protected function createMissingSymbolError(MissingInfo $dependency, ClassLikeInfo | FunctionInfo $referencedBy): ErrorInfo
151 | 	{
152 | 		return new ErrorInfo(ErrorKind::MissingSymbol, implode("\n", [
153 | 			"Missing {$dependency->name->full}",
154 | 			"referenced by {$referencedBy->name->full}",
155 | 		]));
156 | 	}
157 | 
158 | 
159 | 	protected function createDuplicateSymbolError(ClassLikeInfo | FunctionInfo $info, ClassLikeInfo | FunctionInfo $first): ErrorInfo
160 | 	{
161 | 		return new ErrorInfo(ErrorKind::DuplicateSymbol, implode("\n", [
162 | 			"Multiple definitions of {$info->name->full}.",
163 | 			"The first definition was found in {$first->file} on line {$first->startLine}",
164 | 			"and then another one was found in {$info->file} on line {$info->startLine}",
165 | 		]));
166 | 	}
167 | }
168 | 


--------------------------------------------------------------------------------
/src/Analyzer/AnalyzeResult.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Analyzer;
 4 | 
 5 | use ApiGen\Info\ClassLikeInfo;
 6 | use ApiGen\Info\ErrorInfo;
 7 | use ApiGen\Info\FunctionInfo;
 8 | 
 9 | 
10 | class AnalyzeResult
11 | {
12 | 	/**
13 | 	 * @param ClassLikeInfo[] $classLike indexed by [classLikeName]
14 | 	 * @param FunctionInfo[]  $function  indexed by [functionName]
15 | 	 * @param ErrorInfo[][]   $error     indexed by [errorKind][]
16 | 	 */
17 | 	public function __construct(
18 | 		public array $classLike,
19 | 		public array $function,
20 | 		public array $error,
21 | 	) {
22 | 	}
23 | }
24 | 


--------------------------------------------------------------------------------
/src/Analyzer/AnalyzeState.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Analyzer;
 4 | 
 5 | use ApiGen\Info\ClassLikeInfo;
 6 | use ApiGen\Info\ClassLikeReferenceInfo;
 7 | use ApiGen\Info\ErrorInfo;
 8 | use ApiGen\Info\FunctionInfo;
 9 | use ApiGen\Info\MissingInfo;
10 | use ApiGen\Info\NameInfo;
11 | use ApiGen\Scheduler;
12 | use Symfony\Component\Console\Helper\ProgressBar;
13 | 
14 | 
15 | class AnalyzeState
16 | {
17 | 	/** @var true[] indexed by [path] */
18 | 	public array $files = [];
19 | 
20 | 	/** @var MissingInfo[] indexed by [classLikeName] */
21 | 	public array $missing = [];
22 | 
23 | 	/** @var ClassLikeInfo[] indexed by [classLikeName] */
24 | 	public array $classLikes = [];
25 | 
26 | 	/** @var FunctionInfo[] indexed by [functionName] */
27 | 	public array $functions = [];
28 | 
29 | 	/** @var ErrorInfo[][] indexed by [errorKind][] */
30 | 	public array $errors = [];
31 | 
32 | 	/** @var NameInfo|null */
33 | 	public ?NameInfo $prevName = null;
34 | 
35 | 
36 | 	/**
37 | 	 * @param  Scheduler<AnalyzeTask, array<ClassLikeInfo | FunctionInfo | ClassLikeReferenceInfo | ErrorInfo>>   $scheduler
38 | 	 */
39 | 	public function __construct(
40 | 		public ProgressBar $progressBar,
41 | 		public Scheduler $scheduler,
42 | 	) {
43 | 	}
44 | }
45 | 


--------------------------------------------------------------------------------
/src/Analyzer/AnalyzeTask.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Analyzer;
 4 | 
 5 | use ApiGen\Task\Task;
 6 | 
 7 | 
 8 | class AnalyzeTask implements Task
 9 | {
10 | 	public function __construct(
11 | 		public string $sourceFile,
12 | 		public bool $primary,
13 | 	) {
14 | 	}
15 | }
16 | 


--------------------------------------------------------------------------------
/src/Analyzer/AnalyzeTaskHandlerFactory.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Analyzer;
 4 | 
 5 | use ApiGen\Task\TaskHandlerFactory;
 6 | 
 7 | 
 8 | /**
 9 |  * @extends TaskHandlerFactory<null, AnalyzeTaskHandler>
10 |  */
11 | interface AnalyzeTaskHandlerFactory extends TaskHandlerFactory
12 | {
13 | 	/**
14 | 	 * @param  null $context
15 | 	 */
16 | 	public function create(mixed $context): AnalyzeTaskHandler;
17 | }
18 | 


--------------------------------------------------------------------------------
/src/Analyzer/BodySkippingLexer.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Analyzer;
 4 | 
 5 | use PhpParser\ErrorHandler;
 6 | use PhpParser\Lexer;
 7 | use PhpParser\Token;
 8 | 
 9 | use function count;
10 | 
11 | use const T_CURLY_OPEN;
12 | use const T_DOLLAR_OPEN_CURLY_BRACES;
13 | use const T_FUNCTION;
14 | use const T_USE;
15 | use const T_WHITESPACE;
16 | 
17 | 
18 | class BodySkippingLexer extends Lexer
19 | {
20 | 	private const CURLY_BRACE_OPEN = 0x7B;
21 | 	private const CURLY_BRACE_CLOSE = 0x7D;
22 | 	private const SEMICOLON = 0x3B;
23 | 
24 | 
25 | 	/**
26 | 	 * @param  list<Token> $tokens
27 | 	 */
28 | 	protected function postprocessTokens(array &$tokens, ErrorHandler $errorHandler): void
29 | 	{
30 | 		parent::postprocessTokens($tokens, $errorHandler);
31 | 
32 | 		$tokenCount = count($tokens);
33 | 		for ($i = 0; $i < $tokenCount; $i++) { // looking for function start
34 | 			if ($tokens[$i]->id === T_FUNCTION && $tokens[$i - 2]->id !== T_USE) {
35 | 				for ($i++; $i < $tokenCount; $i++) { // looking for opening curly brace of function body or semicolon
36 | 					switch ($tokens[$i]->id) {
37 | 						case self::SEMICOLON:
38 | 							continue 3; // look for next function
39 | 
40 | 						case self::CURLY_BRACE_OPEN:
41 | 							break 2;
42 | 					}
43 | 				}
44 | 
45 | 				for ($i++, $level = 0; $i < $tokenCount; $i++) { // looking for closing curly brace of function body
46 | 					switch ($tokens[$i]->id) {
47 | 						case T_WHITESPACE:
48 | 							continue 2;
49 | 
50 | 						case self::CURLY_BRACE_OPEN:
51 | 						case T_CURLY_OPEN:
52 | 						case T_DOLLAR_OPEN_CURLY_BRACES:
53 | 							$level++;
54 | 							break;
55 | 
56 | 						case self::CURLY_BRACE_CLOSE:
57 | 							if ($level === 0) {
58 | 								continue 3; // look for next function
59 | 							}
60 | 
61 | 							$level--;
62 | 							break;
63 | 					}
64 | 
65 | 					$tokens[$i] = new Token(T_WHITESPACE, ' '); // @phpstan-ignore parameterByRef.type
66 | 				}
67 | 			}
68 | 		}
69 | 	}
70 | }
71 | 


--------------------------------------------------------------------------------
/src/Analyzer/Filter.php:
--------------------------------------------------------------------------------
  1 | <?php declare(strict_types = 1);
  2 | 
  3 | namespace ApiGen\Analyzer;
  4 | 
  5 | use ApiGen\Info\ClassLikeInfo;
  6 | use ApiGen\Info\FunctionInfo;
  7 | use ApiGen\Info\MemberInfo;
  8 | use PhpParser\Node;
  9 | use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
 10 | 
 11 | 
 12 | class Filter
 13 | {
 14 | 	/** @var int */
 15 | 	protected int $excludedVisibilityMask = 0;
 16 | 
 17 | 
 18 | 	/**
 19 | 	 * @param string[] $excludeTagged indexed by []
 20 | 	 */
 21 | 	public function __construct(
 22 | 		bool $excludeProtected,
 23 | 		bool $excludePrivate,
 24 | 		protected array $excludeTagged,
 25 | 	) {
 26 | 		$this->excludedVisibilityMask |= ($excludeProtected ? Node\Stmt\Class_::MODIFIER_PROTECTED : 0);
 27 | 		$this->excludedVisibilityMask |= ($excludePrivate ? Node\Stmt\Class_::MODIFIER_PRIVATE : 0);
 28 | 	}
 29 | 
 30 | 
 31 | 	public function filterClassLikeNode(Node\Stmt\ClassLike $node): bool
 32 | 	{
 33 | 		return true;
 34 | 	}
 35 | 
 36 | 
 37 | 	/**
 38 | 	 * @param PhpDocTagValueNode[][] $tags indexed by [tagName][]
 39 | 	 */
 40 | 	public function filterClassLikeTags(array $tags): bool
 41 | 	{
 42 | 		foreach ($this->excludeTagged as $tag) {
 43 | 			if (isset($tags[$tag])) {
 44 | 				return false;
 45 | 			}
 46 | 		}
 47 | 
 48 | 		return true;
 49 | 	}
 50 | 
 51 | 
 52 | 	public function filterClassLikeInfo(ClassLikeInfo $info): bool
 53 | 	{
 54 | 		return true;
 55 | 	}
 56 | 
 57 | 
 58 | 	public function filterFunctionNode(Node\Stmt\Function_ $node): bool
 59 | 	{
 60 | 		return true;
 61 | 	}
 62 | 
 63 | 
 64 | 	/**
 65 | 	 * @param PhpDocTagValueNode[][] $tags indexed by [tagName][]
 66 | 	 */
 67 | 	public function filterFunctionTags(array $tags): bool
 68 | 	{
 69 | 		foreach ($this->excludeTagged as $tag) {
 70 | 			if (isset($tags[$tag])) {
 71 | 				return false;
 72 | 			}
 73 | 		}
 74 | 
 75 | 		return true;
 76 | 	}
 77 | 
 78 | 
 79 | 	public function filterFunctionInfo(FunctionInfo $info): bool
 80 | 	{
 81 | 		return true;
 82 | 	}
 83 | 
 84 | 
 85 | 	public function filterConstantNode(Node\Stmt\ClassConst $node): bool
 86 | 	{
 87 | 		return ($node->flags & $this->excludedVisibilityMask) === 0;
 88 | 	}
 89 | 
 90 | 
 91 | 	public function filterPropertyNode(Node\Stmt\Property $node): bool
 92 | 	{
 93 | 		return ($node->flags & $this->excludedVisibilityMask) === 0;
 94 | 	}
 95 | 
 96 | 
 97 | 	public function filterPromotedPropertyNode(Node\Param $node): bool
 98 | 	{
 99 | 		return ($node->flags & $this->excludedVisibilityMask) === 0;
100 | 	}
101 | 
102 | 
103 | 	public function filterMethodNode(Node\Stmt\ClassMethod $node): bool
104 | 	{
105 | 		return ($node->flags & $this->excludedVisibilityMask) === 0;
106 | 	}
107 | 
108 | 
109 | 	public function filterEnumCaseNode(Node\Stmt\EnumCase $node): bool
110 | 	{
111 | 		return true;
112 | 	}
113 | 
114 | 
115 | 	/**
116 | 	 * @param PhpDocTagValueNode[][] $tags indexed by [tagName][]
117 | 	 */
118 | 	public function filterMemberTags(array $tags): bool
119 | 	{
120 | 		foreach ($this->excludeTagged as $tag) {
121 | 			if (isset($tags[$tag])) {
122 | 				return false;
123 | 			}
124 | 		}
125 | 
126 | 		return true;
127 | 	}
128 | 
129 | 
130 | 	public function filterMemberInfo(ClassLikeInfo $classLike, MemberInfo $member): bool
131 | 	{
132 | 		return true;
133 | 	}
134 | }
135 | 


--------------------------------------------------------------------------------
/src/Analyzer/IdentifierKind.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Analyzer;
 4 | 
 5 | 
 6 | enum IdentifierKind
 7 | {
 8 | 	case Keyword;
 9 | 	case ClassLike;
10 | 	case Generic;
11 | 	case Alias;
12 | }
13 | 


--------------------------------------------------------------------------------
/src/Analyzer/NameContextFrame.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Analyzer;
 4 | 
 5 | use ApiGen\Info\AliasReferenceInfo;
 6 | use ApiGen\Info\ClassLikeReferenceInfo;
 7 | 
 8 | 
 9 | class NameContextFrame
10 | {
11 | 	/** @var null|NameContextFrame */
12 | 	public ?NameContextFrame $parent = null;
13 | 
14 | 	/** @var null|ClassLikeReferenceInfo */
15 | 	public ?ClassLikeReferenceInfo $scope = null;
16 | 
17 | 	/** @var true[] indexed by [name] */
18 | 	public array $genericParameters = [];
19 | 
20 | 	/** @var AliasReferenceInfo[] indexed by [name]  */
21 | 	public array $aliases = [];
22 | 
23 | 
24 | 	public function __construct(?NameContextFrame $parent)
25 | 	{
26 | 		$this->parent = $parent;
27 | 		$this->scope = $parent?->scope;
28 | 		$this->genericParameters = $parent->genericParameters ?? [];
29 | 		$this->aliases = $parent->aliases ?? [];
30 | 	}
31 | }
32 | 


--------------------------------------------------------------------------------
/src/ApiGen.php:
--------------------------------------------------------------------------------
  1 | <?php declare(strict_types = 1);
  2 | 
  3 | namespace ApiGen;
  4 | 
  5 | use ApiGen\Analyzer\AnalyzeResult;
  6 | use ApiGen\Index\Index;
  7 | use ApiGen\Info\ErrorKind;
  8 | use Nette\Utils\Finder;
  9 | use Symfony\Component\Console\Helper\ProgressBar;
 10 | use Symfony\Component\Console\Style\OutputStyle;
 11 | 
 12 | use function array_column;
 13 | use function array_slice;
 14 | use function count;
 15 | use function hrtime;
 16 | use function implode;
 17 | use function is_dir;
 18 | use function is_file;
 19 | use function memory_get_peak_usage;
 20 | use function memory_reset_peak_usage;
 21 | use function sprintf;
 22 | 
 23 | use const PHP_VERSION_ID;
 24 | 
 25 | 
 26 | class ApiGen
 27 | {
 28 | 	/**
 29 | 	 * @param string[] $paths   indexed by []
 30 | 	 * @param string[] $include indexed by []
 31 | 	 * @param string[] $exclude indexed by []
 32 | 	 */
 33 | 	public function __construct(
 34 | 		protected OutputStyle $output,
 35 | 		protected Analyzer $analyzer,
 36 | 		protected Indexer $indexer,
 37 | 		protected Renderer $renderer,
 38 | 		protected array $paths,
 39 | 		protected array $include,
 40 | 		protected array $exclude,
 41 | 	) {
 42 | 	}
 43 | 
 44 | 
 45 | 	public function generate(): bool
 46 | 	{
 47 | 		$files = $this->findFiles();
 48 | 
 49 | 		PHP_VERSION_ID >= 80200 && memory_reset_peak_usage();
 50 | 		$analyzeTime = -hrtime(true);
 51 | 		$analyzeResult = $this->analyze($files);
 52 | 		$analyzeTime += hrtime(true);
 53 | 		$analyzeMemory = memory_get_peak_usage();
 54 | 
 55 | 		PHP_VERSION_ID >= 80200 && memory_reset_peak_usage();
 56 | 		$indexTime = -hrtime(true);
 57 | 		$index = $this->index($analyzeResult);
 58 | 		$indexTime += hrtime(true);
 59 | 		$indexMemory = memory_get_peak_usage();
 60 | 
 61 | 		PHP_VERSION_ID >= 80200 && memory_reset_peak_usage();
 62 | 		$renderTime = -hrtime(true);
 63 | 		$this->render($index);
 64 | 		$renderTime += hrtime(true);
 65 | 		$renderMemory = memory_get_peak_usage();
 66 | 
 67 | 		$this->performance($analyzeTime, $analyzeMemory, $indexTime, $indexMemory, $renderTime, $renderMemory);
 68 | 		return $this->finish($analyzeResult);
 69 | 	}
 70 | 
 71 | 
 72 | 	/**
 73 | 	 * @return string[] list of files, indexed by []
 74 | 	 */
 75 | 	protected function findFiles(): array
 76 | 	{
 77 | 		$files = [];
 78 | 		$dirs = [];
 79 | 
 80 | 		foreach ($this->paths as $path) {
 81 | 			if (is_file($path)) {
 82 | 				$files[] = $path;
 83 | 
 84 | 			} elseif (is_dir($path)) {
 85 | 				$dirs[] = $path;
 86 | 
 87 | 			} else {
 88 | 				$this->output->error(sprintf('Path "%s" does not exist.', $path));
 89 | 			}
 90 | 		}
 91 | 
 92 | 		if (count($dirs) > 0) {
 93 | 			$finder = Finder::findFiles($this->include)
 94 | 				->exclude($this->exclude)
 95 | 				->from($dirs);
 96 | 
 97 | 			foreach ($finder as $file => $_) {
 98 | 				$files[] = $file;
 99 | 			}
100 | 		}
101 | 
102 | 		if (count($files) === 0) {
103 | 			throw new \RuntimeException('No source files found.');
104 | 
105 | 		} elseif ($this->output->isDebug()) {
106 | 			$this->output->text('<info>Matching source files:</info>');
107 | 			$this->output->newLine();
108 | 			$this->output->listing($files);
109 | 
110 | 		} elseif ($this->output->isVerbose()) {
111 | 			$this->output->text(sprintf('Found %d source files.', count($files)));
112 | 			$this->output->newLine();
113 | 		}
114 | 
115 | 		return $files;
116 | 	}
117 | 
118 | 
119 | 	/**
120 | 	 * @param string[] $files indexed by []
121 | 	 */
122 | 	protected function analyze(array $files): AnalyzeResult
123 | 	{
124 | 		$progressBar = $this->createProgressBar('Analyzing');
125 | 		$result = $this->analyzer->analyze($progressBar, $files);
126 | 
127 | 		if ($progressBar->getMaxSteps() === $progressBar->getProgress()) {
128 | 			$progressBar->setMessage('done');
129 | 			$progressBar->finish();
130 | 		}
131 | 
132 | 		$this->output->newLine(2);
133 | 
134 | 		return $result;
135 | 	}
136 | 
137 | 
138 | 	protected function index(AnalyzeResult $analyzeResult): Index
139 | 	{
140 | 		$index = new Index();
141 | 
142 | 		foreach ($analyzeResult->classLike as $info) {
143 | 			$this->indexer->indexFile($index, $info->file, $info->primary);
144 | 			$this->indexer->indexNamespace($index, $info->name->namespace, $info->name->namespaceLower, $info->primary, $info->isDeprecated());
145 | 			$this->indexer->indexClassLike($index, $info);
146 | 		}
147 | 
148 | 		foreach ($analyzeResult->function as $info) {
149 | 			$this->indexer->indexFile($index, $info->file, $info->primary);
150 | 			$this->indexer->indexNamespace($index, $info->name->namespace, $info->name->namespaceLower, $info->primary, $info->isDeprecated());
151 | 			$this->indexer->indexFunction($index, $info);
152 | 		}
153 | 
154 | 		$this->indexer->postProcess($index);
155 | 		return $index;
156 | 	}
157 | 
158 | 
159 | 	protected function render(Index $index): void
160 | 	{
161 | 		$progressBar = $this->createProgressBar('Rendering');
162 | 		$this->renderer->render($progressBar, $index);
163 | 
164 | 		if ($progressBar->getMaxSteps() === $progressBar->getProgress()) {
165 | 			$progressBar->setMessage('done');
166 | 			$progressBar->finish();
167 | 		}
168 | 
169 | 		$this->output->newLine(2);
170 | 	}
171 | 
172 | 
173 | 	protected function createProgressBar(string $label): ProgressBar
174 | 	{
175 | 		$progressBar = $this->output->createProgressBar();
176 | 		$progressBar->setFormat(" <fg=green>$label</> %current%/%max% %bar% %percent:3s%% %message%");
177 | 		$progressBar->setBarCharacter("\u{2588}");
178 | 		$progressBar->setProgressCharacter('_');
179 | 		$progressBar->setEmptyBarCharacter('_');
180 | 
181 | 		return $progressBar;
182 | 	}
183 | 
184 | 
185 | 	protected function performance(float $analyzeTime, int $analyzeMemory, float $indexTime, int $indexMemory, float $renderTime, int $renderMemory): void
186 | 	{
187 | 		if ($this->output->isVeryVerbose()) {
188 | 			$lines = [
189 | 				'Analyze time' => sprintf('%6.0f ms', $analyzeTime / 1e6),
190 | 				'Index time' => sprintf('%6.0f ms', $indexTime / 1e6),
191 | 				'Render time' => sprintf('%6.0f ms', $renderTime / 1e6),
192 | 				'' => '',
193 | 				'Analyze peak memory' => sprintf('%6.0f MB', $analyzeMemory / 1e6),
194 | 				'Index peak memory' => sprintf('%6.0f MB', $indexMemory / 1e6),
195 | 				'Render peak memory' => sprintf('%6.0f MB', $renderMemory / 1e6),
196 | 			];
197 | 
198 | 			foreach ($lines as $label => $value) {
199 | 				$this->output->text(sprintf('<info>%-20s</info> %s', $label, $value));
200 | 			}
201 | 		}
202 | 	}
203 | 
204 | 
205 | 	protected function finish(AnalyzeResult $analyzeResult): bool
206 | 	{
207 | 		if (count($analyzeResult->error) === 0) {
208 | 			$this->output->success('Finished OK');
209 | 			return true;
210 | 		}
211 | 
212 | 		$hasError = false;
213 | 		foreach ($analyzeResult->error as $errorKind => $errorGroup) {
214 | 			$errorLines = array_column($errorGroup, 'message');
215 | 
216 | 			if (!$this->output->isVerbose() && count($errorLines) > 5) {
217 | 				$errorLines = array_slice($errorLines, 0, 5);
218 | 				$errorLines[] = '...';
219 | 				$errorLines[] = sprintf('and %d more (use --verbose to show all)', count($errorGroup) - 5);
220 | 			}
221 | 
222 | 			if ($errorKind === ErrorKind::InternalError->name) {
223 | 				$hasError = true;
224 | 				$this->output->error(implode("\n\n", $errorLines));
225 | 
226 | 			} else {
227 | 				$this->output->warning(implode("\n\n", $errorLines));
228 | 			}
229 | 		}
230 | 
231 | 		if ($hasError) {
232 | 			$this->output->error('Finished with errors');
233 | 			return false;
234 | 		}
235 | 
236 | 		$this->output->success('Finished with warnings');
237 | 		return true;
238 | 	}
239 | }
240 | 


--------------------------------------------------------------------------------
/src/Bootstrap.php:
--------------------------------------------------------------------------------
  1 | <?php declare(strict_types = 1);
  2 | 
  3 | namespace ApiGen;
  4 | 
  5 | use Composer\InstalledVersions;
  6 | use ErrorException;
  7 | use Nette\DI\Compiler;
  8 | use Nette\DI\Config\Loader;
  9 | use Nette\DI\Container;
 10 | use Nette\DI\ContainerLoader;
 11 | use Nette\DI\Extensions\ExtensionsExtension;
 12 | use Nette\DI\Helpers as DIHelpers;
 13 | use Nette\Schema\Expect;
 14 | use Nette\Schema\Helpers as SchemaHelpers;
 15 | use Nette\Schema\Processor;
 16 | use Nette\Utils\FileSystem;
 17 | use Symfony\Component\Console\Style\OutputStyle;
 18 | 
 19 | use function array_keys;
 20 | use function array_map;
 21 | use function assert;
 22 | use function count;
 23 | use function dirname;
 24 | use function error_reporting;
 25 | use function getcwd;
 26 | use function ini_set;
 27 | use function is_array;
 28 | use function is_file;
 29 | use function is_int;
 30 | use function method_exists;
 31 | use function set_error_handler;
 32 | use function str_starts_with;
 33 | use function sys_get_temp_dir;
 34 | 
 35 | use const E_ALL;
 36 | use const E_DEPRECATED;
 37 | use const E_USER_DEPRECATED;
 38 | use const PHP_RELEASE_VERSION;
 39 | use const PHP_VERSION_ID;
 40 | 
 41 | 
 42 | class Bootstrap
 43 | {
 44 | 	public static function configureErrorHandling(): void
 45 | 	{
 46 | 		error_reporting(E_ALL);
 47 | 		ini_set('display_errors', 'stderr');
 48 | 		ini_set('log_errors', '0');
 49 | 
 50 | 		set_error_handler(function (int $severity, string $message, string $file, int $line): bool {
 51 | 			if (error_reporting() & $severity && $severity !== E_DEPRECATED && $severity !== E_USER_DEPRECATED) {
 52 | 				throw new ErrorException($message, 0, $severity, $file, $line);
 53 | 
 54 | 			} else {
 55 | 				return false;
 56 | 			}
 57 | 		});
 58 | 	}
 59 | 
 60 | 
 61 | 	/**
 62 | 	 * @param  mixed[]  $parameters  indexed by [parameterName]
 63 | 	 * @param  string[] $configPaths indexed by []
 64 | 	 */
 65 | 	public static function createApiGen(OutputStyle $output, array $parameters, array $configPaths): ApiGen
 66 | 	{
 67 | 		$workingDir = getcwd();
 68 | 		$tempDir = sys_get_temp_dir() . '/apigen';
 69 | 		$version = InstalledVersions::getPrettyVersion('apigen/apigen');
 70 | 
 71 | 		if ($workingDir === false) {
 72 | 			throw new \RuntimeException('Unable to get current working directory.');
 73 | 		}
 74 | 
 75 | 		$autoDiscoveryPath = "$workingDir/apigen.neon";
 76 | 		if (count($configPaths) === 0 && is_file($autoDiscoveryPath)) {
 77 | 			$output->text("Using configuration file $autoDiscoveryPath.\n");
 78 | 			$configPaths[] = $autoDiscoveryPath;
 79 | 		}
 80 | 
 81 | 		$config = self::mergeConfigs(
 82 | 			['parameters' => ['workingDir' => $workingDir, 'tempDir' => $tempDir, 'version' => $version]],
 83 | 			self::loadConfig(__DIR__ . '/../apigen.neon'),
 84 | 			...array_map(self::loadConfig(...), $configPaths),
 85 | 			...[['parameters' => self::resolvePaths($parameters, $workingDir)]],
 86 | 		);
 87 | 
 88 | 		$parameters = $config['parameters'];
 89 | 		unset($config['parameters']);
 90 | 
 91 | 		self::validateParameters($parameters);
 92 | 		$parameters = DIHelpers::expand($parameters, $parameters);
 93 | 		$containerLoader = new ContainerLoader($parameters['tempDir'], autoRebuild: true);
 94 | 
 95 | 		$containerGenerator = function (Compiler $compiler) use ($config, $parameters): void {
 96 | 			$compiler->addExtension('extensions', new ExtensionsExtension);
 97 | 			$compiler->addConfig($config);
 98 | 			$compiler->setDynamicParameterNames(array_keys($parameters));
 99 | 		};
100 | 
101 | 		$containerKey = [
102 | 			$config,
103 | 			PHP_VERSION_ID - PHP_RELEASE_VERSION,
104 | 		];
105 | 
106 | 		/** @var class-string<Container> $containerClassName */
107 | 		$containerClassName = $containerLoader->load($containerGenerator, $containerKey);
108 | 
109 | 		$container = new $containerClassName($parameters);
110 | 		$container->addService('symfonyConsole.output', $output);
111 | 		$container->initialize();
112 | 		ini_set('memory_limit', $container->getParameter('memoryLimit'));
113 | 
114 | 		return $container->getByType(ApiGen::class);
115 | 	}
116 | 
117 | 
118 | 	/**
119 | 	 * @param  mixed[] $parameters indexed by [parameterName]
120 | 	 */
121 | 	protected static function validateParameters(array $parameters): void
122 | 	{
123 | 		$schema = Expect::structure([
124 | 			// input
125 | 			'paths' => Expect::listOf('string')->min(1),
126 | 			'include' => Expect::listOf('string'),
127 | 			'exclude' => Expect::listOf('string'),
128 | 
129 | 			// analysis
130 | 			'excludeProtected' => Expect::bool(),
131 | 			'excludePrivate' => Expect::bool(),
132 | 			'excludeTagged' => Expect::listOf('string'),
133 | 
134 | 			// output
135 | 			'outputDir' => Expect::string(),
136 | 			'themeDir' => Expect::string()->nullable(),
137 | 			'title' => Expect::string(),
138 | 			'version' => Expect::string(),
139 | 			'baseUrl' => Expect::string(),
140 | 
141 | 			// system
142 | 			'workingDir' => Expect::string(),
143 | 			'tempDir' => Expect::string(),
144 | 			'workerCount' => Expect::int()->min(1),
145 | 			'memoryLimit' => Expect::string(),
146 | 		]);
147 | 
148 | 		(new Processor)->process($schema, $parameters);
149 | 	}
150 | 
151 | 
152 | 	/**
153 | 	 * @param  mixed[][] $configs indexed by [][configKey]
154 | 	 * @return mixed[] indexed by [configKey]
155 | 	 */
156 | 	protected static function mergeConfigs(array...$configs): array
157 | 	{
158 | 		$mergedConfig = [];
159 | 
160 | 		foreach ($configs as $config) {
161 | 			foreach ($config['parameters'] ?? [] as $key => $value) {
162 | 				if (is_array($value)) {
163 | 					$config['parameters'][$key][SchemaHelpers::PreventMerging] = true;
164 | 				}
165 | 			}
166 | 
167 | 			$mergedConfig = SchemaHelpers::merge($config, $mergedConfig);
168 | 			assert(is_array($mergedConfig));
169 | 		}
170 | 
171 | 		return $mergedConfig;
172 | 	}
173 | 
174 | 
175 | 	/**
176 | 	 * @return mixed[] indexed by [configKey]
177 | 	 */
178 | 	protected static function loadConfig(string $path): array
179 | 	{
180 | 		$data = (new Loader)->load($path);
181 | 		$data['parameters'] = self::resolvePaths($data['parameters'] ?? [], Helpers::realPath(dirname($path)));
182 | 
183 | 		return $data;
184 | 	}
185 | 
186 | 
187 | 	/**
188 | 	 * @param  mixed[] $parameters indexed by [parameterName]
189 | 	 * @return mixed[] indexed by [parameterName]
190 | 	 */
191 | 	protected static function resolvePaths(array $parameters, string $base): array
192 | 	{
193 | 		foreach (['tempDir', 'workingDir', 'outputDir', 'themeDir'] as $parameterKey) {
194 | 			if (isset($parameters[$parameterKey])) {
195 | 				$parameters[$parameterKey] = self::resolvePath($parameters[$parameterKey], $base);
196 | 			}
197 | 		}
198 | 
199 | 		foreach ($parameters['paths'] ?? [] as $i => $path) {
200 | 			if (is_int($i)) {
201 | 				$parameters['paths'][$i] = self::resolvePath($parameters['paths'][$i], $base);
202 | 			}
203 | 		}
204 | 
205 | 		return $parameters;
206 | 	}
207 | 
208 | 
209 | 	protected static function resolvePath(string $path, string $base): string
210 | 	{
211 | 		return (FileSystem::isAbsolute($path) || str_starts_with($path, '%'))
212 | 			? $path
213 | 			: FileSystem::joinPaths($base, $path);
214 | 	}
215 | }
216 | 


--------------------------------------------------------------------------------
/src/Helpers.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen;
 4 | 
 5 | use ReflectionClass;
 6 | 
 7 | use function array_map;
 8 | use function array_shift;
 9 | use function assert;
10 | use function count;
11 | use function realpath;
12 | use function str_starts_with;
13 | use function strlen;
14 | use function substr;
15 | 
16 | use const DIRECTORY_SEPARATOR;
17 | 
18 | 
19 | class Helpers
20 | {
21 | 	/**
22 | 	 * @param string[] $paths indexed by [], non-empty
23 | 	 */
24 | 	public static function baseDir(array $paths): string
25 | 	{
26 | 		assert(count($paths) > 0);
27 | 
28 | 		$paths = array_map(self::realPath(...), $paths);
29 | 		$first = array_shift($paths);
30 | 		$j = 0;
31 | 
32 | 		for ($i = 0; $i < strlen($first); $i++) {
33 | 			foreach ($paths as $path) {
34 | 				if (!isset($path[$i]) || $path[$i] !== $first[$i]) {
35 | 					return substr($first, 0, $j);
36 | 
37 | 				} elseif ($first[$i] === DIRECTORY_SEPARATOR) {
38 | 					$j = $i;
39 | 				}
40 | 			}
41 | 		}
42 | 
43 | 		return $first;
44 | 	}
45 | 
46 | 
47 | 	public static function realPath(string $path): string
48 | 	{
49 | 		if (str_starts_with($path, 'phar://')) {
50 | 			return $path;
51 | 		}
52 | 
53 | 		$realPath = realpath($path);
54 | 
55 | 		if ($realPath === false) {
56 | 			throw new \RuntimeException("File $path does not exist.");
57 | 		}
58 | 
59 | 		return $realPath;
60 | 	}
61 | 
62 | 
63 | 	/**
64 | 	 * @param class-string $name
65 | 	 */
66 | 	public static function classLikePath(string $name): string
67 | 	{
68 | 		$reflection = new ReflectionClass($name);
69 | 		$path = $reflection->getFileName();
70 | 
71 | 		if ($path === false) {
72 | 			throw new \RuntimeException("Class-like $name has no path.");
73 | 		}
74 | 
75 | 		return $path;
76 | 	}
77 | }
78 | 


--------------------------------------------------------------------------------
/src/Index/FileIndex.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Index;
 4 | 
 5 | use ApiGen\Info\ClassLikeInfo;
 6 | use ApiGen\Info\FunctionInfo;
 7 | 
 8 | 
 9 | class FileIndex
10 | {
11 | 	/** @var ClassLikeInfo[] indexed by [classLikeName] */
12 | 	public array $classLike = [];
13 | 
14 | 	/** @var FunctionInfo[] indexed by [functionName] */
15 | 	public array $function = [];
16 | 
17 | 
18 | 	public function __construct(
19 | 		public string $path,
20 | 		public bool $primary,
21 | 	) {
22 | 	}
23 | }
24 | 


--------------------------------------------------------------------------------
/src/Index/Index.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Index;
 4 | 
 5 | use ApiGen\Info\ClassInfo;
 6 | use ApiGen\Info\ClassLikeInfo;
 7 | use ApiGen\Info\EnumInfo;
 8 | use ApiGen\Info\FunctionInfo;
 9 | use ApiGen\Info\InterfaceInfo;
10 | use ApiGen\Info\TraitInfo;
11 | 
12 | 
13 | class Index
14 | {
15 | 	/** @var FileIndex[] indexed by [filePath] */
16 | 	public array $files = [];
17 | 
18 | 	/** @var NamespaceIndex[] indexed by [namespaceName] */
19 | 	public array $namespace = [];
20 | 
21 | 	/** @var ClassLikeInfo[] indexed by [classLikeName] */
22 | 	public array $classLike = [];
23 | 
24 | 	/** @var ClassInfo[] indexed by [className] */
25 | 	public array $class = [];
26 | 
27 | 	/** @var InterfaceInfo[] indexed by [interfaceName] */
28 | 	public array $interface = [];
29 | 
30 | 	/** @var TraitInfo[] indexed by [traitName] */
31 | 	public array $trait = [];
32 | 
33 | 	/** @var EnumInfo[] indexed by [enumName] */
34 | 	public array $enum = [];
35 | 
36 | 	/** @var FunctionInfo[] indexed by [functionName] */
37 | 	public array $function = [];
38 | 
39 | 	/** @var ClassInfo[][] indexed by [classLikeName][classLikeName] */
40 | 	public array $classExtends = [];
41 | 
42 | 	/** @var ClassInfo[][] indexed by [classLikeName][classLikeName] */
43 | 	public array $classImplements = [];
44 | 
45 | 	/** @var ClassInfo[][] indexed by [classLikeName][classLikeName] */
46 | 	public array $classUses = [];
47 | 
48 | 	/** @var InterfaceInfo[][] indexed by [classLikeName][classLikeName] */
49 | 	public array $interfaceExtends = [];
50 | 
51 | 	/** @var EnumInfo[][] indexed by [classLikeName][classLikeName] */
52 | 	public array $enumImplements = [];
53 | 
54 | 	/** @var ClassLikeInfo[][] indexed by [classLikeName][classLikeName] classExtends + classImplements + classUses + interfaceExtends + enumImplements */
55 | 	public array $dag = [];
56 | 
57 | 	/** @var ClassLikeInfo[][] indexed by [classLikeName][classLikeName], e.g. ['a']['b'] means that B instance of A */
58 | 	public array $instanceOf = [];
59 | 
60 | 	/** @var ClassLikeInfo[][] indexed by [constantName][] */
61 | 	public array $constants = [];
62 | 
63 | 	/** @var ClassLikeInfo[][] indexed by [propertyName][] */
64 | 	public array $properties = [];
65 | 
66 | 	/** @var ClassLikeInfo[][] indexed by [methodName][] */
67 | 	public array $methods = [];
68 | 
69 | 	/** @var ClassLikeInfo[][][] indexed by [classLikeName][methodName], e.g. ['a']['b'] = [C] means method A::b is overriding C::b */
70 | 	public array $methodOverrides = [];
71 | 
72 | 	/** @var ClassLikeInfo[][][] indexed by [classLikeName][methodName][], e.g. ['c']['b'] = [A] means method C::b is overridden by A::b */
73 | 	public array $methodOverriddenBy = [];
74 | 
75 | 	/** @var ClassLikeInfo[][][] indexed by [classLikeName][methodName], e.g. ['a']['b'] = [C] means method A::b is implementing C::b */
76 | 	public array $methodImplements = [];
77 | 
78 | 	/** @var ClassLikeInfo[][][] indexed by [classLikeName][methodName][], e.g. ['c']['b'] = [A] means method C::b is implemented by A::b */
79 | 	public array $methodImplementedBy = [];
80 | }
81 | 


--------------------------------------------------------------------------------
/src/Index/NamespaceIndex.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Index;
 4 | 
 5 | use ApiGen\Info\ClassInfo;
 6 | use ApiGen\Info\ElementInfo;
 7 | use ApiGen\Info\EnumInfo;
 8 | use ApiGen\Info\FunctionInfo;
 9 | use ApiGen\Info\InterfaceInfo;
10 | use ApiGen\Info\NameInfo;
11 | use ApiGen\Info\TraitInfo;
12 | 
13 | 
14 | class NamespaceIndex implements ElementInfo
15 | {
16 | 	/** @var ClassInfo[] indexed by [classShortName] (excludes exceptions) */
17 | 	public array $class = [];
18 | 
19 | 	/** @var InterfaceInfo[] indexed by [interfaceShortName] */
20 | 	public array $interface = [];
21 | 
22 | 	/** @var TraitInfo[] indexed by [traitShortName] */
23 | 	public array $trait = [];
24 | 
25 | 	/** @var EnumInfo[] indexed by [enumShortName] */
26 | 	public array $enum = [];
27 | 
28 | 	/** @var ClassInfo[] indexed by [exceptionShortName] */
29 | 	public array $exception = [];
30 | 
31 | 	/** @var FunctionInfo[] indexed by [functionShortName] */
32 | 	public array $function = [];
33 | 
34 | 	/** @var NamespaceIndex[] indexed by [namespaceShortName] */
35 | 	public array $children = [];
36 | 
37 | 
38 | 	public function __construct(
39 | 		public NameInfo $name,
40 | 		public bool $primary,
41 | 		public bool $deprecated,
42 | 	) {
43 | 	}
44 | 
45 | 
46 | 	public function isDeprecated(): bool
47 | 	{
48 | 		return $this->deprecated;
49 | 	}
50 | }
51 | 


--------------------------------------------------------------------------------
/src/Info/AliasInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | use ApiGen\Info\Traits\HasLineLocation;
 6 | use PHPStan\PhpDocParser\Ast\Type\TypeNode;
 7 | 
 8 | 
 9 | class AliasInfo
10 | {
11 | 	use HasLineLocation;
12 | 
13 | 
14 | 	public function __construct(
15 | 		public string $name,
16 | 		public TypeNode $type,
17 | 	) {
18 | 	}
19 | }
20 | 


--------------------------------------------------------------------------------
/src/Info/AliasReferenceInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | use function strtolower;
 6 | 
 7 | 
 8 | class AliasReferenceInfo
 9 | {
10 | 	/** @var ClassLikeReferenceInfo */
11 | 	public ClassLikeReferenceInfo $classLike;
12 | 
13 | 	/** @var string e.g. 'DatabaseOptions' */
14 | 	public string $alias;
15 | 
16 | 	/** @var string e.g. 'databaseoptions' */
17 | 	public string $aliasLower;
18 | 
19 | 
20 | 	public function __construct(
21 | 		ClassLikeReferenceInfo $classLike,
22 | 		string $alias,
23 | 		?string $aliasLower = null,
24 | 	) {
25 | 		$this->classLike = $classLike;
26 | 		$this->alias = $alias;
27 | 		$this->aliasLower = $aliasLower ?? strtolower($alias);
28 | 	}
29 | }
30 | 


--------------------------------------------------------------------------------
/src/Info/ClassInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | 
 6 | class ClassInfo extends ClassLikeInfo
 7 | {
 8 | 	/** @var bool */
 9 | 	public bool $abstract = false;
10 | 
11 | 	/** @var bool */
12 | 	public bool $final = false;
13 | 
14 | 	/** @var bool */
15 | 	public bool $readOnly = false;
16 | 
17 | 	/** @var ClassLikeReferenceInfo|null */
18 | 	public ?ClassLikeReferenceInfo $extends = null;
19 | 
20 | 	/** @var ClassLikeReferenceInfo[] indexed by [classLikeName] */
21 | 	public array $implements = [];
22 | 
23 | 	/** @var ClassLikeReferenceInfo[] indexed by [classLikeName] */
24 | 	public array $uses = [];
25 | }
26 | 


--------------------------------------------------------------------------------
/src/Info/ClassLikeInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | use ApiGen\Index\Index;
 6 | use ApiGen\Info\Traits\HasGenericParameters;
 7 | use ApiGen\Info\Traits\HasLineLocation;
 8 | use ApiGen\Info\Traits\HasTags;
 9 | 
10 | 
11 | abstract class ClassLikeInfo implements ElementInfo
12 | {
13 | 	use HasTags;
14 | 	use HasLineLocation;
15 | 	use HasGenericParameters;
16 | 
17 | 
18 | 	/** @var string|null */
19 | 	public ?string $file = null;
20 | 
21 | 	/** @var ConstantInfo[] indexed by [constantName] */
22 | 	public array $constants = [];
23 | 
24 | 	/** @var PropertyInfo[] indexed by [propertyName] */
25 | 	public array $properties = [];
26 | 
27 | 	/** @var MethodInfo[] indexed by [methodName] */
28 | 	public array $methods = [];
29 | 
30 | 	/** @var ClassLikeReferenceInfo[] indexed by [classLikeName] */
31 | 	public array $mixins = [];
32 | 
33 | 	/** @var AliasInfo[] indexed by [aliasName] */
34 | 	public array $aliases = [];
35 | 
36 | 
37 | 	public function __construct(
38 | 		public NameInfo $name,
39 | 		public bool $primary,
40 | 	) {
41 | 	}
42 | 
43 | 
44 | 	public function isInstanceOf(Index $index, string $type): bool
45 | 	{
46 | 		return isset($index->instanceOf[$type][$this->name->fullLower]);
47 | 	}
48 | 
49 | 
50 | 	public function isThrowable(Index $index): bool
51 | 	{
52 | 		return $this->isInstanceOf($index, 'throwable');
53 | 	}
54 | }
55 | 


--------------------------------------------------------------------------------
/src/Info/ClassLikeReferenceInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | use ApiGen\Index\Index;
 6 | use PHPStan\PhpDocParser\Ast\Type\TypeNode;
 7 | 
 8 | use function strtolower;
 9 | 
10 | 
11 | class ClassLikeReferenceInfo
12 | {
13 | 	/** @var string e.g. 'ApiGen\Info\Traits\HasName' */
14 | 	public string $full;
15 | 
16 | 	/** @var string e.g. 'apigen\info\traits\hasname' */
17 | 	public string $fullLower;
18 | 
19 | 	/** @var TypeNode[] */
20 | 	public array $genericArgs = [];
21 | 
22 | 
23 | 	public function __construct(string $full, ?string $fullLower = null)
24 | 	{
25 | 		$this->full = $full;
26 | 		$this->fullLower = $fullLower ?? strtolower($full);
27 | 	}
28 | 
29 | 
30 | 	public function resolve(Index $index, ?ClassLikeInfo $scope = null): ?ClassLikeInfo
31 | 	{
32 | 		if ($this->fullLower === 'self' || $this->fullLower === 'static') {
33 | 			return $scope;
34 | 		}
35 | 
36 | 		if ($this->fullLower === 'parent' && $scope instanceof ClassInfo && $scope->extends !== null) {
37 | 			return $index->classLike[$scope->extends->fullLower] ?? null;
38 | 		}
39 | 
40 | 		return $index->classLike[$this->fullLower] ?? null;
41 | 	}
42 | }
43 | 


--------------------------------------------------------------------------------
/src/Info/ConstantInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | use PHPStan\PhpDocParser\Ast\Type\TypeNode;
 6 | 
 7 | 
 8 | class ConstantInfo extends MemberInfo
 9 | {
10 | 	/** @var TypeNode|null */
11 | 	public ?TypeNode $type = null;
12 | 
13 | 	/** @var ExprInfo */
14 | 	public ExprInfo $value;
15 | 
16 | 	/** @var bool */
17 | 	public bool $final = false;
18 | 
19 | 
20 | 	public function __construct(string $name, ExprInfo $value)
21 | 	{
22 | 		parent::__construct($name);
23 | 		$this->value = $value;
24 | 	}
25 | }
26 | 


--------------------------------------------------------------------------------
/src/Info/ConstantReferenceInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 | 
3 | namespace ApiGen\Info;
4 | 
5 | 
6 | class ConstantReferenceInfo extends MemberReferenceInfo
7 | {
8 | }
9 | 


--------------------------------------------------------------------------------
/src/Info/ElementInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | 
 6 | /**
 7 |  * @property-read bool $primary
 8 |  */
 9 | interface ElementInfo
10 | {
11 | 	public function isDeprecated(): bool;
12 | }
13 | 


--------------------------------------------------------------------------------
/src/Info/EnumCaseInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | 
 6 | class EnumCaseInfo extends MemberInfo
 7 | {
 8 | 	public function __construct(
 9 | 		string $name,
10 | 		public ?ExprInfo $value,
11 | 	) {
12 | 		parent::__construct($name);
13 | 	}
14 | }
15 | 


--------------------------------------------------------------------------------
/src/Info/EnumInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | 
 6 | class EnumInfo extends ClassLikeInfo
 7 | {
 8 | 	/** @var string|null */
 9 | 	public ?string $scalarType = null;
10 | 
11 | 	/** @var ClassLikeReferenceInfo[] indexed by [classLikeName] */
12 | 	public array $implements = [];
13 | 
14 | 	/** @var ClassLikeReferenceInfo[] indexed by [classLikeName] */
15 | 	public array $uses = [];
16 | 
17 | 	/** @var EnumCaseInfo[] indexed by [caseName] */
18 | 	public array $cases = [];
19 | }
20 | 


--------------------------------------------------------------------------------
/src/Info/ErrorInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | 
 6 | class ErrorInfo
 7 | {
 8 | 	public function __construct(
 9 | 		public ErrorKind $kind,
10 | 		public string $message,
11 | 	) {
12 | 	}
13 | }
14 | 


--------------------------------------------------------------------------------
/src/Info/ErrorKind.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | 
 6 | enum ErrorKind
 7 | {
 8 | 	case InternalError;
 9 | 	case SyntaxError;
10 | 	case InvalidEncoding;
11 | 	case MissingSymbol;
12 | 	case DuplicateSymbol;
13 | }
14 | 


--------------------------------------------------------------------------------
/src/Info/Expr/ArgExprInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Expr;
 4 | 
 5 | use ApiGen\Info\ExprInfo;
 6 | 
 7 | 
 8 | class ArgExprInfo
 9 | {
10 | 	public function __construct(
11 | 		public ?string $name,
12 | 		public ExprInfo $value,
13 | 	) {
14 | 	}
15 | }
16 | 


--------------------------------------------------------------------------------
/src/Info/Expr/ArrayExprInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Expr;
 4 | 
 5 | use ApiGen\Info\ExprInfo;
 6 | 
 7 | 
 8 | class ArrayExprInfo implements ExprInfo
 9 | {
10 | 	/**
11 | 	 * @param ArrayItemExprInfo[] $items
12 | 	 */
13 | 	public function __construct(
14 | 		public array $items,
15 | 	) {
16 | 	}
17 | }
18 | 


--------------------------------------------------------------------------------
/src/Info/Expr/ArrayItemExprInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Expr;
 4 | 
 5 | use ApiGen\Info\ExprInfo;
 6 | 
 7 | 
 8 | class ArrayItemExprInfo
 9 | {
10 | 	public function __construct(
11 | 		public ?ExprInfo $key,
12 | 		public ExprInfo $value,
13 | 	) {
14 | 	}
15 | }
16 | 


--------------------------------------------------------------------------------
/src/Info/Expr/BinaryOpExprInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Expr;
 4 | 
 5 | use ApiGen\Info\ExprInfo;
 6 | 
 7 | 
 8 | class BinaryOpExprInfo implements ExprInfo
 9 | {
10 | 	public function __construct(
11 | 		public string $op,
12 | 		public ExprInfo $left,
13 | 		public ExprInfo $right,
14 | 	) {
15 | 	}
16 | }
17 | 


--------------------------------------------------------------------------------
/src/Info/Expr/BooleanExprInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Expr;
 4 | 
 5 | use ApiGen\Info\ExprInfo;
 6 | 
 7 | 
 8 | class BooleanExprInfo implements ExprInfo
 9 | {
10 | 	public function __construct(
11 | 		public bool $value,
12 | 	) {
13 | 	}
14 | 
15 | 
16 | 	public function toString(): string
17 | 	{
18 | 		return $this->value ? 'true' : 'false';
19 | 	}
20 | }
21 | 


--------------------------------------------------------------------------------
/src/Info/Expr/ClassConstantFetchExprInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Expr;
 4 | 
 5 | use ApiGen\Info\ClassLikeReferenceInfo;
 6 | use ApiGen\Info\ExprInfo;
 7 | 
 8 | 
 9 | class ClassConstantFetchExprInfo implements ExprInfo
10 | {
11 | 	public function __construct(
12 | 		public ClassLikeReferenceInfo $classLike,
13 | 		public string $name,
14 | 	) {
15 | 	}
16 | }
17 | 


--------------------------------------------------------------------------------
/src/Info/Expr/ConstantFetchExprInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Expr;
 4 | 
 5 | use ApiGen\Info\ExprInfo;
 6 | 
 7 | 
 8 | class ConstantFetchExprInfo implements ExprInfo
 9 | {
10 | 	public function __construct(
11 | 		public string $name,
12 | 	) {
13 | 	}
14 | }
15 | 


--------------------------------------------------------------------------------
/src/Info/Expr/DimFetchExprInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Expr;
 4 | 
 5 | use ApiGen\Info\ExprInfo;
 6 | 
 7 | 
 8 | class DimFetchExprInfo implements ExprInfo
 9 | {
10 | 	public function __construct(
11 | 		public ExprInfo $expr,
12 | 		public ExprInfo $dim,
13 | 	) {
14 | 	}
15 | }
16 | 


--------------------------------------------------------------------------------
/src/Info/Expr/FloatExprInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Expr;
 4 | 
 5 | use ApiGen\Info\ExprInfo;
 6 | 
 7 | use function is_finite;
 8 | use function json_encode;
 9 | use function str_contains;
10 | 
11 | use const JSON_THROW_ON_ERROR;
12 | 
13 | 
14 | class FloatExprInfo implements ExprInfo
15 | {
16 | 	public function __construct(
17 | 		public float $value,
18 | 		public string $raw,
19 | 	) {
20 | 	}
21 | 
22 | 
23 | 	public function toString(): string
24 | 	{
25 | 		if (!is_finite($this->value)) {
26 | 			return (string) $this->value;
27 | 		}
28 | 
29 | 		$json = json_encode($this->value, JSON_THROW_ON_ERROR);
30 | 		return str_contains($json, '.') ? $json : "$json.0";
31 | 	}
32 | }
33 | 


--------------------------------------------------------------------------------
/src/Info/Expr/IntegerExprInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Expr;
 4 | 
 5 | use ApiGen\Info\ExprInfo;
 6 | 
 7 | use function abs;
 8 | use function base_convert;
 9 | 
10 | 
11 | class IntegerExprInfo implements ExprInfo
12 | {
13 | 	public function __construct(
14 | 		public int $value,
15 | 		public int $base,
16 | 		public string $raw,
17 | 	) {
18 | 	}
19 | 
20 | 
21 | 	public function toString(): string
22 | 	{
23 | 		if ($this->base === 10) {
24 | 			return (string) $this->value;
25 | 		}
26 | 
27 | 		$sign = $this->value < 0 ? '-' : '';
28 | 		$base = [2 => '0b', 8 => '0', 16 => '0x'][$this->base];
29 | 		return $sign . $base . base_convert((string) abs($this->value), 10, $this->base);
30 | 	}
31 | }
32 | 


--------------------------------------------------------------------------------
/src/Info/Expr/NewExprInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Expr;
 4 | 
 5 | use ApiGen\Info\ClassLikeReferenceInfo;
 6 | use ApiGen\Info\ExprInfo;
 7 | 
 8 | 
 9 | class NewExprInfo implements ExprInfo
10 | {
11 | 	/**
12 | 	 * @param ArgExprInfo[] $args
13 | 	 */
14 | 	public function __construct(
15 | 		public ClassLikeReferenceInfo $classLike,
16 | 		public array $args,
17 | 	) {
18 | 	}
19 | }
20 | 


--------------------------------------------------------------------------------
/src/Info/Expr/NullExprInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Expr;
 4 | 
 5 | use ApiGen\Info\ExprInfo;
 6 | 
 7 | 
 8 | class NullExprInfo implements ExprInfo
 9 | {
10 | 	public function toString(): string
11 | 	{
12 | 		return 'null';
13 | 	}
14 | }
15 | 


--------------------------------------------------------------------------------
/src/Info/Expr/NullSafePropertyFetchExprInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Expr;
 4 | 
 5 | use ApiGen\Info\ExprInfo;
 6 | 
 7 | 
 8 | class NullSafePropertyFetchExprInfo implements ExprInfo
 9 | {
10 | 	public function __construct(
11 | 		public ExprInfo $expr,
12 | 		public ExprInfo|string $property,
13 | 	) {
14 | 	}
15 | }
16 | 


--------------------------------------------------------------------------------
/src/Info/Expr/PropertyFetchExprInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Expr;
 4 | 
 5 | use ApiGen\Info\ExprInfo;
 6 | 
 7 | 
 8 | class PropertyFetchExprInfo implements ExprInfo
 9 | {
10 | 	public function __construct(
11 | 		public ExprInfo $expr,
12 | 		public ExprInfo|string $property,
13 | 	) {
14 | 	}
15 | }
16 | 


--------------------------------------------------------------------------------
/src/Info/Expr/StringExprInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Expr;
 4 | 
 5 | use ApiGen\Info\ExprInfo;
 6 | 
 7 | use function mb_ord;
 8 | use function ord;
 9 | use function preg_match;
10 | use function preg_replace_callback;
11 | use function sprintf;
12 | use function strlen;
13 | 
14 | 
15 | class StringExprInfo implements ExprInfo
16 | {
17 | 	public function __construct(
18 | 		public string $value,
19 | 		public ?string $raw = null,
20 | 	) {
21 | 	}
22 | 
23 | 
24 | 	public function toString(): string
25 | 	{
26 | 		$utf8 = (bool) preg_match('##u', $this->value);
27 | 		$pattern = $utf8 ? '#[\p{C}"\'\\\\]#u' : '#[\x00-\x1F\x7F-\xFF"\'\\\\]#';
28 | 		$special = ["\r" => '\r', "\n" => '\n', "\t" => '\t', '\\' => '\\\\', '"' => '\\"', '\'' => '\''];
29 | 
30 | 		$s = preg_replace_callback(
31 | 			$pattern,
32 | 			function ($m) use ($special) {
33 | 				if (isset($special[$m[0]])) {
34 | 					return $special[$m[0]];
35 | 
36 | 				} elseif (strlen($m[0]) === 1) {
37 | 					return sprintf('\x%02X', ord($m[0]));
38 | 
39 | 				} else {
40 | 					return sprintf('\u{%X}', mb_ord($m[0]));
41 | 				}
42 | 			},
43 | 			$this->value,
44 | 			-1,
45 | 			$count,
46 | 		);
47 | 
48 | 		$quote = $count === 0 ? "'" : '"';
49 | 		return $quote . $s . $quote;
50 | 	}
51 | }
52 | 


--------------------------------------------------------------------------------
/src/Info/Expr/TernaryExprInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Expr;
 4 | 
 5 | use ApiGen\Info\ExprInfo;
 6 | 
 7 | 
 8 | class TernaryExprInfo implements ExprInfo
 9 | {
10 | 	public function __construct(
11 | 		public ExprInfo $condition,
12 | 		public ?ExprInfo $if,
13 | 		public ExprInfo $else,
14 | 	) {
15 | 	}
16 | }
17 | 


--------------------------------------------------------------------------------
/src/Info/Expr/UnaryOpExprInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Expr;
 4 | 
 5 | use ApiGen\Info\ExprInfo;
 6 | 
 7 | 
 8 | class UnaryOpExprInfo implements ExprInfo
 9 | {
10 | 	public function __construct(
11 | 		public string $op,
12 | 		public ExprInfo $expr,
13 | 	) {
14 | 	}
15 | }
16 | 


--------------------------------------------------------------------------------
/src/Info/ExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 | 
3 | namespace ApiGen\Info;
4 | 
5 | 
6 | interface ExprInfo
7 | {
8 | }
9 | 


--------------------------------------------------------------------------------
/src/Info/FunctionInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | use ApiGen\Info\Traits\HasGenericParameters;
 6 | use ApiGen\Info\Traits\HasLineLocation;
 7 | use ApiGen\Info\Traits\HasTags;
 8 | use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
 9 | use PHPStan\PhpDocParser\Ast\Type\TypeNode;
10 | 
11 | 
12 | class FunctionInfo implements ElementInfo
13 | {
14 | 	use HasTags;
15 | 	use HasLineLocation;
16 | 	use HasGenericParameters;
17 | 
18 | 
19 | 	/** @var string|null */
20 | 	public ?string $file = null;
21 | 
22 | 	/** @var ParameterInfo[] indexed by [parameterName] */
23 | 	public array $parameters = [];
24 | 
25 | 	/** @var TypeNode|null */
26 | 	public ?TypeNode $returnType = null;
27 | 
28 | 	/** @var PhpDocTextNode[] indexed by [] */
29 | 	public array $returnDescription = [];
30 | 
31 | 	/** @var bool */
32 | 	public bool $byRef = false;
33 | 
34 | 
35 | 	public function __construct(
36 | 		public NameInfo $name,
37 | 		public bool $primary,
38 | 	) {
39 | 	}
40 | }
41 | 


--------------------------------------------------------------------------------
/src/Info/FunctionReferenceInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | use function strtolower;
 6 | 
 7 | 
 8 | class FunctionReferenceInfo
 9 | {
10 | 	/** @var string e.g. 'Tracy\dump' */
11 | 	public string $full;
12 | 
13 | 	/** @var string e.g. 'tracy\dump' */
14 | 	public string $fullLower;
15 | 
16 | 
17 | 	public function __construct(string $full, ?string $fullLower = null)
18 | 	{
19 | 		$this->full = $full;
20 | 		$this->fullLower = $fullLower ?? strtolower($full);
21 | 	}
22 | }
23 | 


--------------------------------------------------------------------------------
/src/Info/GenericParameterInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | use PHPStan\PhpDocParser\Ast\Type\TypeNode;
 6 | 
 7 | 
 8 | class GenericParameterInfo
 9 | {
10 | 	public function __construct(
11 | 		public string $name,
12 | 		public GenericParameterVariance $variance,
13 | 		public ?TypeNode $bound = null,
14 | 		public ?TypeNode $default = null,
15 | 		public string $description = '',
16 | 	) {
17 | 	}
18 | }
19 | 


--------------------------------------------------------------------------------
/src/Info/GenericParameterVariance.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | 
 6 | enum GenericParameterVariance
 7 | {
 8 | 	case Invariant;
 9 | 	case Covariant;
10 | 	case Contravariant;
11 | }
12 | 


--------------------------------------------------------------------------------
/src/Info/InterfaceInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | 
 6 | class InterfaceInfo extends ClassLikeInfo
 7 | {
 8 | 	/** @var ClassLikeReferenceInfo[] indexed by [classLikeName] */
 9 | 	public array $extends = [];
10 | }
11 | 


--------------------------------------------------------------------------------
/src/Info/MemberInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | use ApiGen\Index\Index;
 6 | use ApiGen\Info\Traits\HasLineLocation;
 7 | use ApiGen\Info\Traits\HasTags;
 8 | use ApiGen\Info\Traits\HasVisibility;
 9 | use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
10 | 
11 | 
12 | abstract class MemberInfo
13 | {
14 | 	use HasTags;
15 | 	use HasLineLocation;
16 | 	use HasVisibility;
17 | 
18 | 
19 | 	/** @var string */
20 | 	public string $name;
21 | 
22 | 	/** @var bool */
23 | 	public bool $magic = false;
24 | 
25 | 
26 | 	public function __construct(string $name)
27 | 	{
28 | 		$this->name = $name;
29 | 	}
30 | 
31 | 
32 | 	/**
33 | 	 * @return PhpDocTextNode[] indexed by []
34 | 	 */
35 | 	public function getEffectiveDescription(Index $index, ClassLikeInfo $classLike): array
36 | 	{
37 | 		return $this->description;
38 | 	}
39 | }
40 | 


--------------------------------------------------------------------------------
/src/Info/MemberReferenceInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | 
 6 | abstract class MemberReferenceInfo
 7 | {
 8 | 	public function __construct(
 9 | 		public ClassLikeReferenceInfo $classLike,
10 | 		public string $name,
11 | 	) {
12 | 	}
13 | }
14 | 


--------------------------------------------------------------------------------
/src/Info/MethodInfo.php:
--------------------------------------------------------------------------------
  1 | <?php declare(strict_types = 1);
  2 | 
  3 | namespace ApiGen\Info;
  4 | 
  5 | use ApiGen\Index\Index;
  6 | use ApiGen\Info\Traits\HasGenericParameters;
  7 | use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
  8 | use PHPStan\PhpDocParser\Ast\Type\TypeNode;
  9 | 
 10 | use function count;
 11 | use function strtolower;
 12 | 
 13 | 
 14 | class MethodInfo extends MemberInfo
 15 | {
 16 | 	use HasGenericParameters;
 17 | 
 18 | 
 19 | 	/** @var string */
 20 | 	public string $nameLower;
 21 | 
 22 | 	/** @var ParameterInfo[] indexed by [parameterName] */
 23 | 	public array $parameters = [];
 24 | 
 25 | 	/** @var TypeNode|null */
 26 | 	public ?TypeNode $returnType = null;
 27 | 
 28 | 	/** @var PhpDocTextNode[] indexed by [] */
 29 | 	public array $returnDescription = [];
 30 | 
 31 | 	/** @var bool */
 32 | 	public bool $byRef = false;
 33 | 
 34 | 	/** @var bool */
 35 | 	public bool $static = false;
 36 | 
 37 | 	/** @var bool */
 38 | 	public bool $abstract = false;
 39 | 
 40 | 	/** @var bool */
 41 | 	public bool $final = false;
 42 | 
 43 | 
 44 | 	public function __construct(string $name)
 45 | 	{
 46 | 		parent::__construct($name);
 47 | 		$this->nameLower = strtolower($name);
 48 | 	}
 49 | 
 50 | 
 51 | 	/**
 52 | 	 * @return PhpDocTextNode[] indexed by []
 53 | 	 */
 54 | 	public function getEffectiveDescription(Index $index, ClassLikeInfo $classLike): array
 55 | 	{
 56 | 		if (count($this->description) > 0) {
 57 | 			return $this->description;
 58 | 		}
 59 | 
 60 | 		foreach ($this->ancestors($index, $classLike) as $ancestor) {
 61 | 			$description = $ancestor->methods[$this->nameLower]->getEffectiveDescription($index, $ancestor);
 62 | 
 63 | 			if (count($description) > 0) {
 64 | 				return $description;
 65 | 			}
 66 | 		}
 67 | 
 68 | 		return [];
 69 | 	}
 70 | 
 71 | 
 72 | 	/**
 73 | 	 * @return PhpDocTextNode[] indexed by []
 74 | 	 */
 75 | 	public function getEffectiveReturnDescription(Index $index, ClassLikeInfo $classLike): array
 76 | 	{
 77 | 		if (count($this->returnDescription) > 0) {
 78 | 			return $this->returnDescription;
 79 | 		}
 80 | 
 81 | 		foreach ($this->ancestors($index, $classLike) as $ancestor) {
 82 | 			$description = $ancestor->methods[$this->nameLower]->getEffectiveReturnDescription($index, $ancestor);
 83 | 
 84 | 			if (count($description) > 0) {
 85 | 				return $description;
 86 | 			}
 87 | 		}
 88 | 
 89 | 		return [];
 90 | 	}
 91 | 
 92 | 
 93 | 	/**
 94 | 	 * @return iterable<ClassLikeInfo>
 95 | 	 */
 96 | 	public function ancestors(Index $index, ClassLikeInfo $classLike): iterable
 97 | 	{
 98 | 		yield from $index->methodOverrides[$classLike->name->fullLower][$this->nameLower] ?? [];
 99 | 		yield from $index->methodImplements[$classLike->name->fullLower][$this->nameLower] ?? [];
100 | 	}
101 | }
102 | 


--------------------------------------------------------------------------------
/src/Info/MethodReferenceInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | use function strtolower;
 6 | 
 7 | 
 8 | class MethodReferenceInfo extends MemberReferenceInfo
 9 | {
10 | 	public string $nameLower;
11 | 
12 | 	public function __construct(
13 | 		ClassLikeReferenceInfo $classLike,
14 | 		string $name,
15 | 	) {
16 | 		parent::__construct($classLike, $name);
17 | 		$this->nameLower = strtolower($name);
18 | 	}
19 | }
20 | 


--------------------------------------------------------------------------------
/src/Info/MissingInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | 
 6 | class MissingInfo extends ClassLikeInfo
 7 | {
 8 | 	public function __construct(
 9 | 		NameInfo $name,
10 | 		public NameInfo $referencedBy,
11 | 	) {
12 | 		parent::__construct(
13 | 			$name,
14 | 			primary: false,
15 | 		);
16 | 	}
17 | }
18 | 


--------------------------------------------------------------------------------
/src/Info/NameInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | use function strrpos;
 6 | use function strtolower;
 7 | use function substr;
 8 | 
 9 | 
10 | class NameInfo
11 | {
12 | 	/** @var string e.g. 'ApiGen\Info\Traits\HasName' */
13 | 	public string $full;
14 | 
15 | 	/** @var string e.g. 'apigen\info\traits\hasname' */
16 | 	public string $fullLower;
17 | 
18 | 	/** @var string e.g. 'HasName' */
19 | 	public string $short;
20 | 
21 | 	/** @var string e.g. 'hasname' */
22 | 	public string $shortLower;
23 | 
24 | 	/** @var string e.g. 'ApiGen\Info\Traits' */
25 | 	public string $namespace;
26 | 
27 | 	/** @var string e.g. 'apigen\info\traits' */
28 | 	public string $namespaceLower;
29 | 
30 | 
31 | 	public function __construct(string $full, ?string $fullLower = null)
32 | 	{
33 | 		$pos = strrpos($full, '\\');
34 | 
35 | 		$this->full = $full;
36 | 		$this->fullLower = $fullLower ?? strtolower($full);
37 | 
38 | 		$this->short = $pos === false ? $full : substr($full, $pos + 1);
39 | 		$this->shortLower = strtolower($this->short);
40 | 
41 | 		$this->namespace = $pos === false ? '' : substr($full, 0, $pos);
42 | 		$this->namespaceLower = strtolower($this->namespace);
43 | 	}
44 | }
45 | 


--------------------------------------------------------------------------------
/src/Info/ParameterInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | use ApiGen\Index\Index;
 6 | use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
 7 | use PHPStan\PhpDocParser\Ast\Type\TypeNode;
 8 | 
 9 | use function array_values;
10 | use function count;
11 | 
12 | 
13 | class ParameterInfo
14 | {
15 | 	/** @var string */
16 | 	public string $name;
17 | 
18 | 	/** @var int */
19 | 	public int $position;
20 | 
21 | 	/** @var PhpDocTextNode[] indexed by [] */
22 | 	public array $description = [];
23 | 
24 | 	/** @var TypeNode|null */
25 | 	public ?TypeNode $type = null;
26 | 
27 | 	/** @var bool */
28 | 	public bool $byRef = false;
29 | 
30 | 	/** @var bool */
31 | 	public bool $variadic = false;
32 | 
33 | 	/** @var ExprInfo|null */
34 | 	public ?ExprInfo $default = null;
35 | 
36 | 
37 | 	public function __construct(string $name, int $position)
38 | 	{
39 | 		$this->name = $name;
40 | 		$this->position = $position;
41 | 	}
42 | 
43 | 
44 | 	/**
45 | 	 * @return PhpDocTextNode[] indexed by []
46 | 	 */
47 | 	public function getEffectiveDescription(Index $index, ClassLikeInfo $classLike, MethodInfo $method): array
48 | 	{
49 | 		if (count($this->description) > 0) {
50 | 			return $this->description;
51 | 		}
52 | 
53 | 		foreach ($method->ancestors($index, $classLike) as $ancestor) {
54 | 			$ancestorMethod = $ancestor->methods[$method->nameLower];
55 | 			$ancestorParameter = array_values($ancestorMethod->parameters)[$this->position] ?? null;
56 | 			$description = $ancestorParameter?->getEffectiveDescription($index, $ancestor, $ancestorMethod) ?? [];
57 | 
58 | 			if (count($description) > 0) {
59 | 				return $description;
60 | 			}
61 | 		}
62 | 
63 | 		return [];
64 | 	}
65 | }
66 | 


--------------------------------------------------------------------------------
/src/Info/PropertyInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | use PHPStan\PhpDocParser\Ast\Type\TypeNode;
 6 | 
 7 | 
 8 | class PropertyInfo extends MemberInfo
 9 | {
10 | 	/** @var ExprInfo|null */
11 | 	public ?ExprInfo $default = null;
12 | 
13 | 	/** @var TypeNode|null */
14 | 	public ?TypeNode $type = null;
15 | 
16 | 	/** @var bool */
17 | 	public bool $static = false;
18 | 
19 | 	/** @var bool */
20 | 	public bool $readOnly = false;
21 | 
22 | 	/** @var bool */
23 | 	public bool $writeOnly = false;
24 | }
25 | 


--------------------------------------------------------------------------------
/src/Info/PropertyReferenceInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 | 
3 | namespace ApiGen\Info;
4 | 
5 | 
6 | class PropertyReferenceInfo extends MemberReferenceInfo
7 | {
8 | }
9 | 


--------------------------------------------------------------------------------
/src/Info/TraitInfo.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info;
 4 | 
 5 | 
 6 | class TraitInfo extends ClassLikeInfo
 7 | {
 8 | 	/** @var ClassLikeReferenceInfo[] indexed by [classLikeName] */
 9 | 	public array $uses = [];
10 | }
11 | 


--------------------------------------------------------------------------------
/src/Info/Traits/HasGenericParameters.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Traits;
 4 | 
 5 | use ApiGen\Info\GenericParameterInfo;
 6 | 
 7 | 
 8 | trait HasGenericParameters
 9 | {
10 | 	/** @var GenericParameterInfo[] indexed by [parameterName] */
11 | 	public array $genericParameters = [];
12 | }
13 | 


--------------------------------------------------------------------------------
/src/Info/Traits/HasLineLocation.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Traits;
 4 | 
 5 | 
 6 | trait HasLineLocation
 7 | {
 8 | 	/** @var int|null */
 9 | 	public ?int $startLine = null;
10 | 
11 | 	/** @var int|null */
12 | 	public ?int $endLine = null;
13 | }
14 | 


--------------------------------------------------------------------------------
/src/Info/Traits/HasTags.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Traits;
 4 | 
 5 | use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
 6 | use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
 7 | 
 8 | 
 9 | trait HasTags
10 | {
11 | 	/** @var PhpDocTextNode[] indexed by [] */
12 | 	public array $description = [];
13 | 
14 | 	/** @var PhpDocTagValueNode[][] indexed by [tagName][] */
15 | 	public array $tags = [];
16 | 
17 | 
18 | 	public function isDeprecated(): bool
19 | 	{
20 | 		return isset($this->tags['deprecated']);
21 | 	}
22 | 
23 | 
24 | 	public function isInternal(): bool
25 | 	{
26 | 		return isset($this->tags['internal']);
27 | 	}
28 | }
29 | 


--------------------------------------------------------------------------------
/src/Info/Traits/HasVisibility.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Info\Traits;
 4 | 
 5 | 
 6 | trait HasVisibility
 7 | {
 8 | 	/** @var bool */
 9 | 	public bool $public = false;
10 | 
11 | 	/** @var bool */
12 | 	public bool $protected = false;
13 | 
14 | 	/** @var bool */
15 | 	public bool $private = false;
16 | }
17 | 


--------------------------------------------------------------------------------
/src/Locator.php:
--------------------------------------------------------------------------------
  1 | <?php declare(strict_types = 1);
  2 | 
  3 | namespace ApiGen;
  4 | 
  5 | use ApiGen\Info\ClassLikeReferenceInfo;
  6 | use Composer\Autoload\ClassLoader;
  7 | use JetBrains\PHPStormStub\PhpStormStubsMap;
  8 | use League;
  9 | use Nette\Utils\FileSystem;
 10 | use Nette\Utils\Json;
 11 | use PHPStan\Php8StubsMap;
 12 | use Symfony\Component\Console\Style\OutputStyle;
 13 | 
 14 | use function dirname;
 15 | use function implode;
 16 | use function is_dir;
 17 | use function is_file;
 18 | use function strtolower;
 19 | 
 20 | use const PHP_VERSION_ID;
 21 | 
 22 | 
 23 | class Locator
 24 | {
 25 | 	/**
 26 | 	 * @param string[] $stubsMap indexed by [classLikeName]
 27 | 	 */
 28 | 	public function __construct(
 29 | 		protected array $stubsMap,
 30 | 		protected ClassLoader $classLoader,
 31 | 	) {
 32 | 	}
 33 | 
 34 | 
 35 | 	public static function create(OutputStyle $output, string $projectDir): self
 36 | 	{
 37 | 		return new self(
 38 | 			self::createStubsMap(),
 39 | 			self::createComposerClassLoader($output, $projectDir),
 40 | 		);
 41 | 	}
 42 | 
 43 | 
 44 | 	/**
 45 | 	 * @return string[] indexed by [classLikeName]
 46 | 	 */
 47 | 	protected static function createStubsMap(): array
 48 | 	{
 49 | 		$stubsMap = [];
 50 | 
 51 | 		$phpStormStubsDir = dirname(Helpers::classLikePath(PhpStormStubsMap::class));
 52 | 		foreach (PhpStormStubsMap::CLASSES as $class => $path) {
 53 | 			$stubsMap[strtolower($class)] = "$phpStormStubsDir/$path";
 54 | 		}
 55 | 
 56 | 		$phpStanStubsDir = dirname(Helpers::classLikePath(Php8StubsMap::class));
 57 | 		foreach ((new Php8StubsMap(PHP_VERSION_ID))->classes as $class => $path) {
 58 | 			$stubsMap[$class] = "$phpStanStubsDir/$path";
 59 | 		}
 60 | 
 61 | 		return $stubsMap;
 62 | 	}
 63 | 
 64 | 
 65 | 	protected static function createComposerClassLoader(OutputStyle $output, string $projectDir): ClassLoader
 66 | 	{
 67 | 		$loader = new ClassLoader();
 68 | 		$composerJsonPath = "$projectDir/composer.json";
 69 | 
 70 | 		if (!is_file($composerJsonPath)) {
 71 | 			$output->warning(implode("\n", [
 72 | 				"Unable to use Composer autoloader for finding dependencies because file",
 73 | 				"$composerJsonPath does not exist. Use --working-dir to specify directory where composer.json is located",
 74 | 			]));
 75 | 
 76 | 			return $loader;
 77 | 		}
 78 | 
 79 | 		$composerJson = Json::decode(FileSystem::read($composerJsonPath), forceArrays: true);
 80 | 		$vendorDir = FileSystem::joinPaths($projectDir, $composerJson['config']['vendor-dir'] ?? 'vendor');
 81 | 
 82 | 		if (!is_dir($vendorDir)) {
 83 | 			$output->warning(implode("\n", [
 84 | 				"Unable to use Composer autoloader for finding dependencies because directory",
 85 | 				"$vendorDir does not exist. Run composer install to install dependencies.",
 86 | 			]));
 87 | 
 88 | 			return $loader;
 89 | 		}
 90 | 
 91 | 		$output->text("Using Composer autoloader for finding dependencies in $vendorDir.\n");
 92 | 		$loader->addClassMap(require "$vendorDir/composer/autoload_classmap.php");
 93 | 
 94 | 		foreach (require "$vendorDir/composer/autoload_namespaces.php" as $prefix => $paths) {
 95 | 			$loader->set($prefix, $paths);
 96 | 		}
 97 | 
 98 | 		foreach (require "$vendorDir/composer/autoload_psr4.php" as $prefix => $paths) {
 99 | 			$loader->setPsr4($prefix, $paths);
100 | 		}
101 | 
102 | 		return $loader;
103 | 	}
104 | 
105 | 
106 | 	public function locate(ClassLikeReferenceInfo $name): ?string
107 | 	{
108 | 		return $this->classLoader->findFile($name->full) ?: $this->stubsMap[$name->fullLower] ?? null;
109 | 	}
110 | }
111 | 


--------------------------------------------------------------------------------
/src/Renderer.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen;
 4 | 
 5 | use ApiGen\Index\Index;
 6 | use Symfony\Component\Console\Helper\ProgressBar;
 7 | 
 8 | 
 9 | interface Renderer
10 | {
11 | 	public function render(ProgressBar $progressBar, Index $index): void;
12 | }
13 | 


--------------------------------------------------------------------------------
/src/Renderer/Filter.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer;
 4 | 
 5 | use ApiGen\Index\FileIndex;
 6 | use ApiGen\Index\NamespaceIndex;
 7 | use ApiGen\Info\ClassLikeInfo;
 8 | use ApiGen\Info\FunctionInfo;
 9 | use ApiGen\Info\MissingInfo;
10 | 
11 | 
12 | class Filter
13 | {
14 | 	public function filterTreePage(): bool
15 | 	{
16 | 		return true;
17 | 	}
18 | 
19 | 
20 | 	public function filterSitemapPage(): bool
21 | 	{
22 | 		return true;
23 | 	}
24 | 
25 | 
26 | 	public function filterNamespacePage(NamespaceIndex $namespace): bool
27 | 	{
28 | 		return true;
29 | 	}
30 | 
31 | 
32 | 	public function filterClassLikePage(ClassLikeInfo $classLike): bool
33 | 	{
34 | 		return !$classLike instanceof MissingInfo;
35 | 	}
36 | 
37 | 
38 | 	public function filterFunctionPage(FunctionInfo $function): bool
39 | 	{
40 | 		return true;
41 | 	}
42 | 
43 | 
44 | 	public function filterSourcePage(FileIndex $file): bool
45 | 	{
46 | 		return $file->primary;
47 | 	}
48 | }
49 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteCascadingLoader.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer\Latte;
 4 | 
 5 | use Latte;
 6 | 
 7 | use function is_file;
 8 | 
 9 | 
10 | class LatteCascadingLoader implements Latte\Loader
11 | {
12 | 	/**
13 | 	 * @param string[] $baseDirs
14 | 	 */
15 | 	public function __construct(
16 | 		protected array $baseDirs,
17 | 		protected Latte\Loader $inner = new Latte\Loaders\FileLoader(),
18 | 	) {
19 | 	}
20 | 
21 | 
22 | 	public function getContent(string $name): string
23 | 	{
24 | 		foreach ($this->baseDirs as $baseDir) {
25 | 			$path = $baseDir . '/' . $name;
26 | 
27 | 			if (is_file($path)) {
28 | 				return $this->inner->getContent($path);
29 | 			}
30 | 		}
31 | 
32 | 		throw new Latte\RuntimeException("Missing template file '$name'.");
33 | 	}
34 | 
35 | 
36 | 	public function isExpired(string $name, int $time): bool
37 | 	{
38 | 		foreach ($this->baseDirs as $baseDir) {
39 | 			$path = $baseDir . '/' . $name;
40 | 
41 | 			if (is_file($path)) {
42 | 				return $this->inner->isExpired($path, $time);
43 | 			}
44 | 		}
45 | 
46 | 		throw new Latte\RuntimeException("Missing template file '$name'.");
47 | 	}
48 | 
49 | 
50 | 	public function getReferredName(string $name, string $referringName): string
51 | 	{
52 | 		return $this->inner->getReferredName($name, $referringName);
53 | 	}
54 | 
55 | 
56 | 	public function getUniqueId(string $name): string
57 | 	{
58 | 		foreach ($this->baseDirs as $baseDir) {
59 | 			$path = $baseDir . '/' . $name;
60 | 
61 | 			if (is_file($path)) {
62 | 				return $this->inner->getUniqueId($path);
63 | 			}
64 | 		}
65 | 
66 | 		throw new Latte\RuntimeException("Missing template file '$name'.");
67 | 	}
68 | }
69 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteEngineFactory.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer\Latte;
 4 | 
 5 | use Latte;
 6 | use Throwable;
 7 | 
 8 | use function array_filter;
 9 | 
10 | 
11 | class LatteEngineFactory
12 | {
13 | 	public function __construct(
14 | 		protected LatteExtension $extension,
15 | 		protected ?string $tempDir,
16 | 		protected ?string $themeDir,
17 | 	) {
18 | 	}
19 | 
20 | 
21 | 	public function create(): Latte\Engine
22 | 	{
23 | 		$latte = new Latte\Engine();
24 | 		$latte->setStrictTypes();
25 | 		$latte->setExceptionHandler(fn(Throwable $e) => throw $e);
26 | 		$latte->setTempDirectory($this->tempDir);
27 | 		$latte->setLoader(new LatteCascadingLoader(array_filter([$this->themeDir, __DIR__ . '/Template'])));
28 | 		$latte->addExtension($this->extension);
29 | 
30 | 		return $latte;
31 | 	}
32 | }
33 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteExtension.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer\Latte;
 4 | 
 5 | use ApiGen\Renderer\Filter;
 6 | use ApiGen\Renderer\UrlGenerator;
 7 | use Latte;
 8 | 
 9 | 
10 | class LatteExtension extends Latte\Extension
11 | {
12 | 	public function __construct(
13 | 		protected LatteFunctions $functions,
14 | 		protected Filter $filter,
15 | 		protected UrlGenerator $url,
16 | 	) {
17 | 	}
18 | 
19 | 
20 | 	public function getFunctions(): array
21 | 	{
22 | 		return [
23 | 			'isClass' => $this->functions->isClass(...),
24 | 			'isInterface' => $this->functions->isInterface(...),
25 | 			'isTrait' => $this->functions->isTrait(...),
26 | 			'isEnum' => $this->functions->isEnum(...),
27 | 
28 | 			'textWidth' => $this->functions->textWidth(...),
29 | 			'htmlWidth' => $this->functions->htmlWidth(...),
30 | 			'highlight' => $this->functions->highlight(...),
31 | 			'shortDescription' => $this->functions->shortDescription(...),
32 | 			'longDescription' => $this->functions->longDescription(...),
33 | 
34 | 			'treePageExists' => $this->filter->filterTreePage(...),
35 | 			'namespacePageExists' => $this->filter->filterNamespacePage(...),
36 | 			'classLikePageExists' => $this->filter->filterClassLikePage(...),
37 | 			'functionPageExists' => $this->filter->filterFunctionPage(...),
38 | 			'sourcePageExists' => $this->filter->filterSourcePage(...),
39 | 
40 | 			'elementName' => $this->functions->elementName(...),
41 | 			'elementShortDescription' => $this->functions->elementShortDescription(...),
42 | 			'elementPageExists' => $this->functions->elementPageExists(...),
43 | 			'elementUrl' => $this->functions->elementUrl(...),
44 | 
45 | 			'relativePath' => $this->url->getRelativePath(...),
46 | 			'assetUrl' => $this->url->getAssetUrl(...),
47 | 			'indexUrl' => $this->url->getIndexUrl(...),
48 | 			'treeUrl' => $this->url->getTreeUrl(...),
49 | 			'namespaceUrl' => $this->url->getNamespaceUrl(...),
50 | 			'classLikeUrl' => $this->url->getClassLikeUrl(...),
51 | 			'classLikeSourceUrl' => $this->url->getClassLikeSourceUrl(...),
52 | 			'memberUrl' => $this->url->getMemberUrl(...),
53 | 			'memberAnchor' => $this->url->getMemberAnchor(...),
54 | 			'memberSourceUrl' => $this->url->getMemberSourceUrl(...),
55 | 			'aliasUrl' => $this->url->getAliasUrl(...),
56 | 			'aliasAnchor' => $this->url->getAliasAnchor(...),
57 | 			'aliasSourceUrl' => $this->url->getAliasSourceUrl(...),
58 | 			'functionUrl' => $this->url->getFunctionUrl(...),
59 | 			'functionSourceUrl' => $this->url->getFunctionSourceUrl(...),
60 | 			'parameterAnchor' => $this->url->getParameterAnchor(...),
61 | 			'sourceUrl' => $this->url->getSourceUrl(...),
62 | 		];
63 | 	}
64 | 
65 | 
66 | 	public function getTags(): array
67 | 	{
68 | 		return [
69 | 			'pre' => LattePreNode::create(...),
70 | 		];
71 | 	}
72 | }
73 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/LattePreNode.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer\Latte;
 4 | 
 5 | use Generator;
 6 | use Latte;
 7 | use Latte\Compiler\Node;
 8 | use Latte\Compiler\Nodes\AreaNode;
 9 | use Latte\Compiler\Nodes\TextNode;
10 | use Latte\Compiler\NodeTraverser;
11 | use Latte\Compiler\PrintContext;
12 | use Latte\Compiler\Tag;
13 | use Latte\Compiler\TemplateParser;
14 | use Nette\Utils\Strings;
15 | 
16 | use function assert;
17 | 
18 | 
19 | class LattePreNode extends Latte\Compiler\Nodes\StatementNode
20 | {
21 | 	public function __construct(
22 | 		public AreaNode $content,
23 | 	) {
24 | 	}
25 | 
26 | 
27 | 	/**
28 | 	 * @return Generator<int, null, array{AreaNode, ?Tag}, self>
29 | 	 */
30 | 	public static function create(Tag $tag, TemplateParser $parser): Generator
31 | 	{
32 | 		[$content] = yield;
33 | 		$transformed = (new NodeTraverser)->traverse($content, self::removeWhitespace(...));
34 | 		assert($transformed instanceof AreaNode);
35 | 
36 | 		return new self($transformed);
37 | 	}
38 | 
39 | 
40 | 	public function print(PrintContext $context): string
41 | 	{
42 | 		return $this->content->print($context);
43 | 	}
44 | 
45 | 
46 | 	public function &getIterator(): Generator
47 | 	{
48 | 		yield $this->content;
49 | 	}
50 | 
51 | 
52 | 	protected static function removeWhitespace(Node $node): Node
53 | 	{
54 | 		return $node instanceof TextNode ? new TextNode(Strings::replace($node->content, '#[\n\t]++#')) : $node;
55 | 	}
56 | }
57 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteRenderTask.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer\Latte;
 4 | 
 5 | use ApiGen\Task\Task;
 6 | 
 7 | 
 8 | class LatteRenderTask implements Task
 9 | {
10 | 	public function __construct(
11 | 		public LatteRenderTaskType $type,
12 | 		public string $key = '',
13 | 	) {
14 | 	}
15 | }
16 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteRenderTaskContext.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer\Latte;
 4 | 
 5 | use ApiGen\Index\Index;
 6 | use ApiGen\Renderer\Latte\Template\ConfigParameters;
 7 | 
 8 | 
 9 | class LatteRenderTaskContext
10 | {
11 | 	public function __construct(
12 | 		public Index $index,
13 | 		public ConfigParameters $config,
14 | 	) {
15 | 	}
16 | }
17 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteRenderTaskHandler.php:
--------------------------------------------------------------------------------
  1 | <?php declare(strict_types = 1);
  2 | 
  3 | namespace ApiGen\Renderer\Latte;
  4 | 
  5 | use ApiGen\Index\Index;
  6 | use ApiGen\Renderer\Filter;
  7 | use ApiGen\Renderer\Latte\Template\ClassLikeTemplate;
  8 | use ApiGen\Renderer\Latte\Template\ConfigParameters;
  9 | use ApiGen\Renderer\Latte\Template\FunctionTemplate;
 10 | use ApiGen\Renderer\Latte\Template\IndexTemplate;
 11 | use ApiGen\Renderer\Latte\Template\LayoutParameters;
 12 | use ApiGen\Renderer\Latte\Template\NamespaceTemplate;
 13 | use ApiGen\Renderer\Latte\Template\SitemapTemplate;
 14 | use ApiGen\Renderer\Latte\Template\SourceTemplate;
 15 | use ApiGen\Renderer\Latte\Template\TreeTemplate;
 16 | use ApiGen\Renderer\UrlGenerator;
 17 | use ApiGen\Task\Task;
 18 | use ApiGen\Task\TaskHandler;
 19 | use Latte;
 20 | use Nette\Utils\FileSystem;
 21 | use Nette\Utils\Json;
 22 | use ReflectionClass;
 23 | 
 24 | use function array_key_first;
 25 | use function assert;
 26 | use function basename;
 27 | use function lcfirst;
 28 | use function sprintf;
 29 | use function substr;
 30 | 
 31 | 
 32 | /**
 33 |  * @implements TaskHandler<LatteRenderTask, string>
 34 |  */
 35 | class LatteRenderTaskHandler implements TaskHandler
 36 | {
 37 | 	protected Index $index;
 38 | 
 39 | 	protected ConfigParameters $config;
 40 | 
 41 | 	public function __construct(
 42 | 		protected Latte\Engine $latte,
 43 | 		protected UrlGenerator $urlGenerator,
 44 | 		protected Filter $filter,
 45 | 		protected string $outputDir,
 46 | 		mixed $context,
 47 | 	) {
 48 | 		assert($context instanceof LatteRenderTaskContext);
 49 | 		$this->index = $context->index;
 50 | 		$this->config = $context->config;
 51 | 	}
 52 | 
 53 | 
 54 | 	/**
 55 | 	 * @param  LatteRenderTask $task
 56 | 	 */
 57 | 	public function handle(Task $task): string
 58 | 	{
 59 | 		return match ($task->type) {
 60 | 			LatteRenderTaskType::Asset => $this->copyAsset($task->key),
 61 | 
 62 | 			LatteRenderTaskType::ElementsJs => $this->renderElementsJs(),
 63 | 			LatteRenderTaskType::Index => $this->renderIndex(),
 64 | 			LatteRenderTaskType::Tree => $this->renderTree(),
 65 | 			LatteRenderTaskType::Sitemap => $this->renderSitemap(),
 66 | 
 67 | 			LatteRenderTaskType::Namespace => $this->renderNamespace($task->key),
 68 | 			LatteRenderTaskType::ClassLike => $this->renderClassLike($task->key),
 69 | 			LatteRenderTaskType::Function => $this->renderFunction($task->key),
 70 | 			LatteRenderTaskType::Source => $this->renderSource($task->key),
 71 | 		};
 72 | 	}
 73 | 
 74 | 
 75 | 	protected function copyAsset(string $key): string
 76 | 	{
 77 | 		$assetPath = $this->urlGenerator->getAssetPath(basename($key));
 78 | 		FileSystem::copy($key, "$this->outputDir/$assetPath");
 79 | 
 80 | 		return $assetPath;
 81 | 	}
 82 | 
 83 | 
 84 | 	protected function renderElementsJs(): string
 85 | 	{
 86 | 		$elements = [];
 87 | 
 88 | 		foreach ($this->index->namespace as $namespace) {
 89 | 			if ($this->filter->filterNamespacePage($namespace)) {
 90 | 				$elements['namespace'][] = [$namespace->name->full, $this->urlGenerator->getNamespaceUrl($namespace)];
 91 | 			}
 92 | 		}
 93 | 
 94 | 		foreach ($this->index->classLike as $classLike) {
 95 | 			if (!$this->filter->filterClassLikePage($classLike)) {
 96 | 				continue;
 97 | 			}
 98 | 
 99 | 			$members = [];
100 | 
101 | 			foreach ($classLike->constants as $constant) {
102 | 				$members['constant'][] = [$constant->name, $this->urlGenerator->getMemberAnchor($constant)];
103 | 			}
104 | 
105 | 			foreach ($classLike->properties as $property) {
106 | 				$members['property'][] = [$property->name, $this->urlGenerator->getMemberAnchor($property)];
107 | 			}
108 | 
109 | 			foreach ($classLike->methods as $method) {
110 | 				$members['method'][] = [$method->name, $this->urlGenerator->getMemberAnchor($method)];
111 | 			}
112 | 
113 | 			$elements['classLike'][] = [$classLike->name->full, $this->urlGenerator->getClassLikeUrl($classLike), $members];
114 | 		}
115 | 
116 | 		foreach ($this->index->function as $function) {
117 | 			if ($this->filter->filterFunctionPage($function)) {
118 | 				$elements['function'][] = [$function->name->full, $this->urlGenerator->getFunctionUrl($function)];
119 | 			}
120 | 		}
121 | 
122 | 		$js = sprintf('window.ApiGen?.resolveElements(%s)', Json::encode($elements));
123 | 		$assetPath = $this->urlGenerator->getAssetPath('elements.js');
124 | 		FileSystem::write("$this->outputDir/$assetPath", $js);
125 | 
126 | 		return $assetPath;
127 | 	}
128 | 
129 | 
130 | 	protected function renderIndex(): string
131 | 	{
132 | 		return $this->renderTemplate($this->urlGenerator->getIndexPath(), new IndexTemplate(
133 | 			index: $this->index,
134 | 			config: $this->config,
135 | 			layout: new LayoutParameters(activePage: 'index'),
136 | 		));
137 | 	}
138 | 
139 | 
140 | 	protected function renderTree(): string
141 | 	{
142 | 		return $this->renderTemplate($this->urlGenerator->getTreePath(), new TreeTemplate(
143 | 			index: $this->index,
144 | 			config: $this->config,
145 | 			layout: new LayoutParameters(activePage: 'tree'),
146 | 		));
147 | 	}
148 | 
149 | 
150 | 	protected function renderSitemap(): string
151 | 	{
152 | 		return $this->renderTemplate($this->urlGenerator->getSitemapPath(), new SitemapTemplate(
153 | 			index: $this->index,
154 | 			config: $this->config,
155 | 		));
156 | 	}
157 | 
158 | 
159 | 	protected function renderNamespace(string $key): string
160 | 	{
161 | 		$info = $this->index->namespace[$key];
162 | 
163 | 		return $this->renderTemplate($this->urlGenerator->getNamespacePath($info), new NamespaceTemplate(
164 | 			index: $this->index,
165 | 			config: $this->config,
166 | 			layout: new LayoutParameters('namespace', activeNamespace: $info, noindex: !$info->primary),
167 | 			namespace: $info,
168 | 		));
169 | 	}
170 | 
171 | 
172 | 	protected function renderClassLike(string $key): string
173 | 	{
174 | 		$info = $this->index->classLike[$key];
175 | 		$activeNamespace = $this->index->namespace[$info->name->namespaceLower];
176 | 
177 | 		return $this->renderTemplate($this->urlGenerator->getClassLikePath($info), new ClassLikeTemplate(
178 | 			index: $this->index,
179 | 			config: $this->config,
180 | 			layout: new LayoutParameters('classLike', $activeNamespace, $info, noindex: !$info->primary),
181 | 			classLike: $info,
182 | 		));
183 | 	}
184 | 
185 | 
186 | 	protected function renderFunction(string $key): string
187 | 	{
188 | 		$info = $this->index->function[$key];
189 | 		$activeNamespace = $this->index->namespace[$info->name->namespaceLower];
190 | 
191 | 		return $this->renderTemplate($this->urlGenerator->getFunctionPath($info), new FunctionTemplate(
192 | 			index: $this->index,
193 | 			config: $this->config,
194 | 			layout: new LayoutParameters('function', $activeNamespace, $info, noindex: !$info->primary),
195 | 			function: $info,
196 | 		));
197 | 	}
198 | 
199 | 
200 | 	protected function renderSource(string $key): string
201 | 	{
202 | 		$info = $this->index->files[$key];
203 | 		$activeElement = $info->classLike[array_key_first($info->classLike)] ?? $info->function[array_key_first($info->function)] ?? null;
204 | 		$activeNamespace = $activeElement ? $this->index->namespace[$activeElement->name->namespaceLower] : null;
205 | 
206 | 		return $this->renderTemplate($this->urlGenerator->getSourcePath($info->path), new SourceTemplate(
207 | 			index: $this->index,
208 | 			config: $this->config,
209 | 			layout: new LayoutParameters('source', $activeNamespace, $activeElement, noindex: !$info->primary),
210 | 			path: $info->path,
211 | 		));
212 | 	}
213 | 
214 | 
215 | 	protected function renderTemplate(string $outputPath, object $template): string
216 | 	{
217 | 		$className = (new ReflectionClass($template))->getShortName();
218 | 		$lattePath = 'pages/' . lcfirst(substr($className, 0, -8)) . '.latte';
219 | 		FileSystem::write("$this->outputDir/$outputPath", $this->latte->renderToString($lattePath, $template));
220 | 
221 | 		return $outputPath;
222 | 	}
223 | }
224 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteRenderTaskHandlerFactory.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer\Latte;
 4 | 
 5 | use ApiGen\Task\TaskHandlerFactory;
 6 | 
 7 | 
 8 | /**
 9 |  * @extends TaskHandlerFactory<LatteRenderTaskContext, LatteRenderTaskHandler>
10 |  */
11 | interface LatteRenderTaskHandlerFactory extends TaskHandlerFactory
12 | {
13 | 	/**
14 | 	 * @param  LatteRenderTaskContext $context
15 | 	 */
16 | 	public function create(mixed $context): LatteRenderTaskHandler;
17 | }
18 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteRenderTaskType.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer\Latte;
 4 | 
 5 | 
 6 | enum LatteRenderTaskType
 7 | {
 8 | 	case Asset;
 9 | 	case ElementsJs;
10 | 	case Index;
11 | 	case Tree;
12 | 	case Sitemap;
13 | 	case Namespace;
14 | 	case ClassLike;
15 | 	case Function;
16 | 	case Source;
17 | }
18 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteRenderer.php:
--------------------------------------------------------------------------------
  1 | <?php declare(strict_types = 1);
  2 | 
  3 | namespace ApiGen\Renderer\Latte;
  4 | 
  5 | use ApiGen\Index\Index;
  6 | use ApiGen\Renderer;
  7 | use ApiGen\Renderer\Filter;
  8 | use ApiGen\Renderer\Latte\Template\ConfigParameters;
  9 | use ApiGen\Scheduler;
 10 | use ApiGen\Scheduler\SchedulerFactory;
 11 | use Nette\Utils\FileSystem;
 12 | use Nette\Utils\Finder;
 13 | use Symfony\Component\Console\Helper\ProgressBar;
 14 | 
 15 | 
 16 | class LatteRenderer implements Renderer
 17 | {
 18 | 	public function __construct(
 19 | 		protected SchedulerFactory $schedulerFactory,
 20 | 		protected Filter $filter,
 21 | 		protected string $title,
 22 | 		protected string $version,
 23 | 		protected string $outputDir,
 24 | 	) {
 25 | 	}
 26 | 
 27 | 
 28 | 	public function render(ProgressBar $progressBar, Index $index): void
 29 | 	{
 30 | 		$this->prepareOutputDir();
 31 | 		$scheduler = $this->createScheduler($index);
 32 | 
 33 | 		foreach ($this->getRenderTasks($index) as $task) {
 34 | 			$scheduler->schedule($task);
 35 | 			$progressBar->setMaxSteps($progressBar->getMaxSteps() + 1);
 36 | 		}
 37 | 
 38 | 		foreach ($scheduler->process() as $path) {
 39 | 			$progressBar->setMessage($path);
 40 | 			$progressBar->advance();
 41 | 		}
 42 | 	}
 43 | 
 44 | 
 45 | 	protected function prepareOutputDir(): void
 46 | 	{
 47 | 		FileSystem::delete($this->outputDir);
 48 | 		FileSystem::createDir($this->outputDir);
 49 | 	}
 50 | 
 51 | 
 52 | 	/**
 53 | 	 * @return Scheduler<LatteRenderTask, string>
 54 | 	 */
 55 | 	protected function createScheduler(Index $index): Scheduler
 56 | 	{
 57 | 		$context = new LatteRenderTaskContext($index, new ConfigParameters($this->title, $this->version));
 58 | 		return $this->schedulerFactory->create(LatteRenderTaskHandlerFactory::class, $context);
 59 | 	}
 60 | 
 61 | 
 62 | 	/**
 63 | 	 * @return iterable<LatteRenderTask>
 64 | 	 */
 65 | 	protected function getRenderTasks(Index $index): iterable
 66 | 	{
 67 | 		yield from $this->getAssetsCopyTasks();
 68 | 		yield from $this->getUnitRenderTasks();
 69 | 		yield from $this->getNamespaceRenderTasks($index);
 70 | 		yield from $this->getClassLikeRenderTasks($index);
 71 | 		yield from $this->getFunctionRenderTasks($index);
 72 | 		yield from $this->getSourceRenderTasks($index);
 73 | 	}
 74 | 
 75 | 
 76 | 	/**
 77 | 	 * @return iterable<LatteRenderTask>
 78 | 	 */
 79 | 	protected function getAssetsCopyTasks(): iterable
 80 | 	{
 81 | 		foreach (Finder::findFiles(__DIR__ . '/Template/assets/*') as $asset) {
 82 | 			yield new LatteRenderTask(LatteRenderTaskType::Asset, $asset->getPathname());
 83 | 		}
 84 | 	}
 85 | 
 86 | 
 87 | 	/**
 88 | 	 * @return iterable<LatteRenderTask>
 89 | 	 */
 90 | 	protected function getUnitRenderTasks(): iterable
 91 | 	{
 92 | 		yield new LatteRenderTask(LatteRenderTaskType::ElementsJs);
 93 | 		yield new LatteRenderTask(LatteRenderTaskType::Index);
 94 | 
 95 | 		if ($this->filter->filterTreePage()) {
 96 | 			yield new LatteRenderTask(LatteRenderTaskType::Tree);
 97 | 		}
 98 | 
 99 | 		if ($this->filter->filterSitemapPage()) {
100 | 			yield new LatteRenderTask(LatteRenderTaskType::Sitemap);
101 | 		}
102 | 	}
103 | 
104 | 
105 | 	/**
106 | 	 * @return iterable<LatteRenderTask>
107 | 	 */
108 | 	protected function getNamespaceRenderTasks(Index $index): iterable
109 | 	{
110 | 		foreach ($index->namespace as $namespaceKey => $namespace) {
111 | 			if ($this->filter->filterNamespacePage($namespace)) {
112 | 				yield new LatteRenderTask(LatteRenderTaskType::Namespace, $namespaceKey);
113 | 			}
114 | 		}
115 | 	}
116 | 
117 | 
118 | 	/**
119 | 	 * @return iterable<LatteRenderTask>
120 | 	 */
121 | 	protected function getClassLikeRenderTasks(Index $index): iterable
122 | 	{
123 | 		foreach ($index->classLike as $classLikeKey => $classLike) {
124 | 			if ($this->filter->filterClassLikePage($classLike)) {
125 | 				yield new LatteRenderTask(LatteRenderTaskType::ClassLike, $classLikeKey);
126 | 			}
127 | 		}
128 | 	}
129 | 
130 | 
131 | 	/**
132 | 	 * @return iterable<LatteRenderTask>
133 | 	 */
134 | 	protected function getFunctionRenderTasks(Index $index): iterable
135 | 	{
136 | 		foreach ($index->function as $functionKey => $function) {
137 | 			if ($this->filter->filterFunctionPage($function)) {
138 | 				yield new LatteRenderTask(LatteRenderTaskType::Function, $functionKey);
139 | 			}
140 | 		}
141 | 	}
142 | 
143 | 
144 | 	/**
145 | 	 * @return iterable<LatteRenderTask>
146 | 	 */
147 | 	protected function getSourceRenderTasks(Index $index): iterable
148 | 	{
149 | 		foreach ($index->files as $fileKey => $file) {
150 | 			if ($this->filter->filterSourcePage($file)) {
151 | 				yield new LatteRenderTask(LatteRenderTaskType::Source, $fileKey);
152 | 			}
153 | 		}
154 | 	}
155 | }
156 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/ClassLikeTemplate.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer\Latte\Template;
 4 | 
 5 | use ApiGen\Index\Index;
 6 | use ApiGen\Info\ClassLikeInfo;
 7 | 
 8 | 
 9 | class ClassLikeTemplate
10 | {
11 | 	public function __construct(
12 | 		public Index $index,
13 | 		public ConfigParameters $config,
14 | 		public LayoutParameters $layout,
15 | 		public ClassLikeInfo $classLike,
16 | 	) {
17 | 	}
18 | }
19 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/ConfigParameters.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer\Latte\Template;
 4 | 
 5 | 
 6 | class ConfigParameters
 7 | {
 8 | 	public function __construct(
 9 | 		public string $title,
10 | 		public string $version,
11 | 	) {
12 | 	}
13 | }
14 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/FunctionTemplate.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer\Latte\Template;
 4 | 
 5 | use ApiGen\Index\Index;
 6 | use ApiGen\Info\FunctionInfo;
 7 | 
 8 | 
 9 | class FunctionTemplate
10 | {
11 | 	public function __construct(
12 | 		public Index $index,
13 | 		public ConfigParameters $config,
14 | 		public LayoutParameters $layout,
15 | 		public FunctionInfo $function,
16 | 	) {
17 | 	}
18 | }
19 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/IndexTemplate.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer\Latte\Template;
 4 | 
 5 | use ApiGen\Index\Index;
 6 | 
 7 | 
 8 | class IndexTemplate
 9 | {
10 | 	public function __construct(
11 | 		public Index $index,
12 | 		public ConfigParameters $config,
13 | 		public LayoutParameters $layout,
14 | 	) {
15 | 	}
16 | }
17 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/LayoutParameters.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer\Latte\Template;
 4 | 
 5 | use ApiGen\Index\NamespaceIndex;
 6 | use ApiGen\Info\ElementInfo;
 7 | 
 8 | 
 9 | class LayoutParameters
10 | {
11 | 	public function __construct(
12 | 		public string $activePage,
13 | 		public ?NamespaceIndex $activeNamespace = null,
14 | 		public ?ElementInfo $activeElement = null,
15 | 		public bool $noindex = false,
16 | 	) {
17 | 	}
18 | }
19 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/NamespaceTemplate.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer\Latte\Template;
 4 | 
 5 | use ApiGen\Index\Index;
 6 | use ApiGen\Index\NamespaceIndex;
 7 | 
 8 | 
 9 | class NamespaceTemplate
10 | {
11 | 	public function __construct(
12 | 		public Index $index,
13 | 		public ConfigParameters $config,
14 | 		public LayoutParameters $layout,
15 | 		public NamespaceIndex $namespace,
16 | 	) {
17 | 	}
18 | }
19 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/SitemapTemplate.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer\Latte\Template;
 4 | 
 5 | use ApiGen\Index\Index;
 6 | 
 7 | 
 8 | class SitemapTemplate
 9 | {
10 | 	public function __construct(
11 | 		public Index $index,
12 | 		public ConfigParameters $config,
13 | 	) {
14 | 	}
15 | }
16 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/SourceTemplate.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer\Latte\Template;
 4 | 
 5 | use ApiGen\Index\Index;
 6 | 
 7 | 
 8 | class SourceTemplate
 9 | {
10 | 	public function __construct(
11 | 		public Index $index,
12 | 		public ConfigParameters $config,
13 | 		public LayoutParameters $layout,
14 | 		public string $path,
15 | 	) {
16 | 	}
17 | }
18 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/TreeTemplate.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Renderer\Latte\Template;
 4 | 
 5 | use ApiGen\Index\Index;
 6 | 
 7 | 
 8 | class TreeTemplate
 9 | {
10 | 	public function __construct(
11 | 		public Index $index,
12 | 		public ConfigParameters $config,
13 | 		public LayoutParameters $layout,
14 | 	) {
15 | 	}
16 | }
17 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/@index.latte:
--------------------------------------------------------------------------------
 1 | {import 'alias.latte'}
 2 | {import 'aliasLink.latte'}
 3 | {import 'aliasSummary.latte'}
 4 | {import 'autoBreakingLine.latte'}
 5 | {import 'classLikeDescription.latte'}
 6 | {import 'classLikeKind.latte'}
 7 | {import 'classLikeLink.latte'}
 8 | {import 'classLikeLinks.latte'}
 9 | {import 'classLikeReference.latte'}
10 | {import 'classLikeSignature.latte'}
11 | {import 'classLikeSignatureTable.latte'}
12 | {import 'classTree.latte'}
13 | {import 'constant.latte'}
14 | {import 'constantInherited.latte'}
15 | {import 'constantInheritedSummary.latte'}
16 | {import 'constantSummary.latte'}
17 | {import 'description.latte'}
18 | {import 'elementSummary.latte'}
19 | {import 'elementSummaryGroup.latte'}
20 | {import 'enumCase.latte'}
21 | {import 'enumCaseSummary.latte'}
22 | {import 'expr.latte'}
23 | {import 'functionDescription.latte'}
24 | {import 'functionLinks.latte'}
25 | {import 'genericParameters.latte'}
26 | {import 'head.latte'}
27 | {import 'layout.latte'}
28 | {import 'member.latte'}
29 | {import 'memberDescription.latte'}
30 | {import 'memberLink.latte'}
31 | {import 'memberSourceLink.latte'}
32 | {import 'memberVisibility.latte'}
33 | {import 'menu.latte'}
34 | {import 'menuElements.latte'}
35 | {import 'menuGroup.latte'}
36 | {import 'method.latte'}
37 | {import 'methodInherited.latte'}
38 | {import 'methodInheritedSummary.latte'}
39 | {import 'methodRelation.latte'}
40 | {import 'methodSignature.latte'}
41 | {import 'methodSummary.latte'}
42 | {import 'methodUsed.latte'}
43 | {import 'methodUsedSummary.latte'}
44 | {import 'namespaceLinks.latte'}
45 | {import 'navbar.latte'}
46 | {import 'parameter.latte'}
47 | {import 'property.latte'}
48 | {import 'propertyInherited.latte'}
49 | {import 'propertyInheritedSummary.latte'}
50 | {import 'propertySummary.latte'}
51 | {import 'propertyUsed.latte'}
52 | {import 'propertyUsedSummary.latte'}
53 | {import 'search.latte'}
54 | {import 'source.latte'}
55 | {import 'type.latte'}
56 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/alias.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define alias, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\AliasInfo $alias}
 6 | 	<tr data-order="{$alias->name}" id="{aliasAnchor($alias)}">
 7 | 		<td class="table-cell table-shrink">
 8 | 			<code class="nowrap">
 9 | 				<a n:tag-if="$classLike->file && sourcePageExists($index->files[$classLike->file])" href="{aliasSourceUrl($classLike, $alias)}" title="Go to source code">
10 | 					{$alias->name}
11 | 				</a>
12 | 			</code>
13 | 		</td>
14 | 
15 | 		<td class="table-cell">
16 | 			<code class="nowrap">{include type, type: $alias->type, scope: $classLike}</code>
17 | 		</td>
18 | 
19 | 		<td class="table-anchor">
20 | 			<a href="#{aliasAnchor($alias)}">#</a>
21 | 		</td>
22 | 	</tr>
23 | {/define}
24 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/aliasLink.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define aliasLink, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\AliasInfo $alias, ?string $text = null}
 6 | 	{pre}
 7 | 		<a n:tag-if="classLikePageExists($classLike)" href="{aliasUrl($classLike, $alias)}">
 8 | 			<span title="{$alias->type}">{if $text}{$text}{else}{$alias->name}{/if}</span>
 9 | 		</a>
10 | 	{/pre}
11 | {/define}
12 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/aliasSummary.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define aliasSummary, ApiGen\Info\ClassLikeInfo $classLike}
 6 | 	{if $classLike->aliases}
 7 | 		<table class="table" id="aliases">
 8 | 			<tr>
 9 | 				<th class="table-heading sortable" colspan="3" title="Switch between natural and alphabetical order">Type aliases</th>
10 | 			</tr>
11 | 
12 | 			{foreach $classLike->aliases as $alias}
13 | 				{include alias, classLike: $classLike, alias: $alias}
14 | 			{/foreach}
15 | 		</table>
16 | 	{/if}
17 | {/define}
18 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/autoBreakingLine.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define autoBreakingLine, iterable $items, int $maxWidth, string $indent = ''}
 6 | 	{pre}
 7 | 		{var int $width = textWidth($indent)}
 8 | 		{var Latte\Runtime\HtmlStringable[] $segments = []}
 9 | 
10 | 		{capture $before}{include before}{/capture}
11 | 		{var $width = $width + htmlWidth($before)}
12 | 
13 | 		{foreach $items as $item}
14 | 			{capture $segment}{include item, item: $item}{sep}{include sep}{/sep}{/capture}
15 | 			{var $width = $width + htmlWidth($segment) + ($iterator->last ? 0 : 1)}
16 | 			{var $segments[] = $segment}
17 | 		{/foreach}
18 | 
19 | 		{capture $after}{include after}{/capture}
20 | 		{var $width = $width + htmlWidth($after)}
21 | 
22 | 		{if $width <= $maxWidth}
23 | 			{$before}
24 | 			{foreach $segments as $segment}
25 | 				{$segment}
26 | 				{sep} {/sep}
27 | 			{/foreach}
28 | 			{$after}
29 | 		{else}
30 | 			{$before}{="\n$indent\t"}
31 | 			{foreach $segments as $segment}
32 | 				{$segment}
33 | 				{last}{include sep}{/last}
34 | 				{sep}{="\n$indent\t"}{/sep}
35 | 			{/foreach}
36 | 			{="\n$indent"}{$after}
37 | 		{/if}
38 | 	{/pre}
39 | {/define}
40 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/classLikeDescription.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define classLikeDescription, ApiGen\Info\ClassLikeInfo $classLike}
 6 | 	{if $classLike->description}
 7 | 		<div class="classLikeDescription">
 8 | 			{include description, $classLike->description, $classLike}
 9 | 		</div>
10 | 	{/if}
11 | {/define}
12 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/classLikeKind.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define classLikeKind, ApiGen\Info\ClassLikeInfo $classLike}
 6 | 	{pre}
 7 | 		{if isInterface($classLike)}Interface
 8 | 		{elseif isTrait($classLike)}Trait
 9 | 		{elseif isClass($classLike)}Class
10 | 		{elseif isEnum($classLike)}Enum
11 | 		{else}{/if}
12 | 	{/pre}
13 | {/define}
14 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/classLikeLink.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define classLikeLink, ApiGen\Info\ClassLikeInfo $classLike, bool $short = false, ?string $text = null}
 6 | 	{pre}
 7 | 		{var bool $active = $classLike === $layout->activeElement}
 8 | 		<a n:tag-if="!$active && classLikePageExists($classLike)" href="{classLikeUrl($classLike)}">
 9 | 			<b n:tag-if="$active">
10 | 			<span n:class="$classLike->isDeprecated() ? deprecated, $active ? active" n:attr="title: ($short || $text) ? $classLike->name->full">
11 | 				{if $text}{$text}{elseif $short}{$classLike->name->short}{else}{$classLike->name->full}{/if}
12 | 			</span>
13 | 			</b>
14 | 		</a>
15 | 	{/pre}
16 | {/define}
17 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/classLikeLinks.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define classLikeLinks, ApiGen\Info\ClassLikeInfo $classLike}
 6 | 	{pre}
 7 | 		{if $classLike->name->namespaceLower}{include namespaceLinks, $index->namespace[$classLike->name->namespaceLower]}\{/if}
 8 | 		<a href="{classLikeUrl($classLike)}">{$classLike->name->short}</a>
 9 | 	{/pre}
10 | {/define}
11 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/classLikeReference.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define classLikeReference, ApiGen\Info\ClassLikeReferenceInfo $ref, bool $short = true}
 6 | 	{pre}
 7 | 		{include classLikeLink, $index->classLike[$ref->fullLower], short: $short}
 8 | 		{if $ref->genericArgs}
 9 | 			&lt;
10 | 			{foreach $ref->genericArgs as $arg}
11 | 				{include type, $arg}{sep}, {/sep}
12 | 			{/foreach}
13 | 			&gt;
14 | 		{/if}
15 | 	{/pre}
16 | {/define}
17 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/classLikeSignature.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define classLikeSignature, ApiGen\Info\ClassLikeInfo $classLike}
 6 | 	{include classLikeLink, $classLike}
 7 | 
 8 | 	{if isInterface($classLike) && $classLike->extends}
 9 | 		extends
10 | 		{foreach $classLike->extends as $ref}
11 | 			{include classLikeReference, $ref}{sep}, {/sep}
12 | 		{/foreach}
13 | 	{/if}
14 | 
15 | 	{if (isClass($classLike) || isEnum($classLike)) && $classLike->implements}
16 | 		implements
17 | 		{foreach $classLike->implements as $ref}
18 | 			{include classLikeReference, $ref}{sep}, {/sep}
19 | 		{/foreach}
20 | 	{/if}
21 | 
22 | 	{if (isClass($classLike) || isEnum($classLike)) && $classLike->uses}
23 | 		uses
24 | 		{foreach $classLike->uses as $ref}
25 | 			{include classLikeReference, $ref}{sep}, {/sep}
26 | 		{/foreach}
27 | 	{/if}
28 | {/define}
29 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/classLikeSignatureTable.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define classLikeSignatureTable, ApiGen\Info\ClassLikeInfo $classLike}
 6 | 	<table class="classLikeSignatureTable">
 7 | 		{if isClass($classLike)}
 8 | 			{if $classLike->abstract}<tr><th><code>abstract</code></th></tr>{/if}
 9 | 			{if $classLike->final}<tr><th><code>final</code></th></tr>{/if}
10 | 			{if $classLike->readOnly}<tr><th><code>readonly</code></th></tr>{/if}
11 | 			{include classLikeSignatureTableRow, 'extends', $classLike->extends ? [$classLike->extends] : []}
12 | 			{include classLikeSignatureTableRow, 'implements', $classLike->implements}
13 | 			{include classLikeSignatureTableRow, 'uses', $classLike->uses}
14 | 
15 | 		{elseif isInterface($classLike)}
16 | 			{include classLikeSignatureTableRow, 'extends', $classLike->extends}
17 | 
18 | 		{elseif isTrait($classLike)}
19 | 			{include classLikeSignatureTableRow, 'uses', $classLike->uses}
20 | 
21 | 		{elseif isEnum($classLike)}
22 | 			{include classLikeSignatureTableRow, 'implements', $classLike->implements}
23 | 			{include classLikeSignatureTableRow, 'uses', $classLike->uses}
24 | 		{/if}
25 | 	</table>
26 | {/define}
27 | 
28 | {define classLikeSignatureTableRow, string $label, ApiGen\Info\ClassLikeReferenceInfo[] $refs}
29 | 	{if $refs}
30 | 		<tr>
31 | 			<th><code>{$label}</code></th>
32 | 			<td>
33 | 				<code n:pre>
34 | 					{foreach $refs as $ref}
35 | 						{include classLikeReference, $ref}{sep}, {/sep}
36 | 					{/foreach}
37 | 				</code>
38 | 			</td>
39 | 		</tr>
40 | 	{/if}
41 | {/define}
42 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/classTree.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define classTree, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\ClassLikeInfo[][] $children}
 6 | 	{if isClass($classLike) && $classLike->extends}
 7 | 		{include this, classLike: $index->classLike[$classLike->extends->fullLower], children: [$classLike->extends->fullLower => [$classLike]] + $children}
 8 | 	{else}
 9 | 		{include classTreeList, classLikes: [$classLike], children: $children}
10 | 	{/if}
11 | {/define}
12 | 
13 | {define classTreeList, ApiGen\Info\ClassLikeInfo[] $classLikes, ApiGen\Info\ClassLikeInfo[][] $children}
14 | 	<ul class="classTree">
15 | 		{foreach $classLikes as $classLike}
16 | 			{include classTreeItem, classLike: $classLike, children: $children}
17 | 		{/foreach}
18 | 	</ul>
19 | {/define}
20 | 
21 | {define local classTreeItem, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\ClassLikeInfo[][] $children}
22 | 	<li>
23 | 		{include classLikeSignature, $classLike}
24 | 		{if !empty($children[$classLike->name->fullLower])}
25 | 			{include classTreeList, classLikes: $children[$classLike->name->fullLower], children: $children}
26 | 		{/if}
27 | 	</li>
28 | {/define}
29 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/constant.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define constant, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\ConstantInfo $constant}
 6 | 	{embed member, $constant}
 7 | 		{block cells}
 8 | 			<td class="table-cell table-shrink">
 9 | 				<code class="nowrap">
10 | 					{if $constant->final}final{/if}
11 | 					{include memberVisibility, $constant}
12 | 				</code>
13 | 			</td>
14 | 
15 | 			<td class="table-cell table-shrink" n:pre>
16 | 				<code class="nowrap" n:if="$constant->type">{include type, $constant->type, scope: $classLike}</code>
17 | 			</td>
18 | 
19 | 			<td class="table-cell">
20 | 				<code n:pre>
21 | 					{embed memberSourceLink, classLike: $classLike, member: $constant}{block content}{$constant->name}{/block}{/embed}
22 | 					 = {include expr, expr: $constant->value, scope: $classLike}
23 | 				</code>
24 | 
25 | 				{embed memberDescription, classLike: $classLike, member: $constant}
26 | 					{block details}{* TODO: other tags (uses, internal...) *}{/block}
27 | 				{/embed}
28 | 			</td>
29 | 		{/block}
30 | 	{/embed}
31 | {/define}
32 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/constantInherited.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define constantInherited ApiGen\Info\ClassLikeInfo $from, array $overriden}
 6 | 	{try}
 7 | 		<table class="table">
 8 | 			<tr>
 9 | 				<th class="table-heading">Constants inherited from {include classLikeLink, $from}</th>
10 | 			</tr>
11 | 			<tr>
12 | 				<td class="table-cell">
13 | 					<code n:pre>
14 | 						{foreach $from->constants as $constantName => $constant}
15 | 							{skipIf isset($overriden[$constantName])}
16 | 							{embed memberLink, $from, $constant}{block content}<code>{$constant->name}</code>{/block}{/embed}
17 | 							{sep}, {/sep}
18 | 						{else}
19 | 							{rollback}
20 | 						{/foreach}
21 | 					</code>
22 | 				</td>
23 | 			</tr>
24 | 		</table>
25 | 	{/try}
26 | 
27 | 	{include constantInheritedSummary, $from, $overriden}
28 | {/define}
29 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/constantInheritedSummary.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define constantInheritedSummary, ApiGen\Info\ClassLikeInfo $classLike, array $overriden}
 6 | 	{if isClass($classLike)}
 7 | 		{if $classLike->extends}
 8 | 			{include constantInherited, $index->classLike[$classLike->extends->fullLower], $overriden + $classLike->constants}
 9 | 		{/if}
10 | 
11 | 		{foreach $classLike->implements as $interface}
12 | 			{include constantInherited, $index->classLike[$interface->fullLower], $overriden + $classLike->constants}
13 | 		{/foreach}
14 | 
15 | 	{elseif isInterface($classLike)}
16 | 		{foreach $classLike->extends as $parent}
17 | 			{include constantInherited, $index->classLike[$parent->fullLower], $overriden + $classLike->constants}
18 | 		{/foreach}
19 | 	{/if}
20 | {/define}
21 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/constantSummary.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define constantSummary, ApiGen\Info\ClassLikeInfo $classLike}
 6 | 	{if $classLike->constants}
 7 | 		<table class="table" id="constants">
 8 | 			<tr>
 9 | 				<th class="table-heading sortable" colspan="4" title="Switch between natural and alphabetical order">Constants</th>
10 | 			</tr>
11 | 
12 | 			{foreach $classLike->constants as $constant}
13 | 				{include constant, classLike: $classLike, constant: $constant}
14 | 			{/foreach}
15 | 		</table>
16 | 	{/if}
17 | {/define}
18 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/description.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 | 
5 | {define description, string $description, ?ApiGen\Info\ClassLikeInfo $scope}
6 | 	<div n:if="$description" class="description">{longDescription($index, $scope, $description)}</div>
7 | {/define}
8 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/elementSummary.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define elementSummary}
 6 | 	<table class="table elementSummary">
 7 | 		{include elementSummaryContent}
 8 | 	</table>
 9 | {/define}
10 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/elementSummaryGroup.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define elementSummaryGroup, string $heading, ApiGen\Info\ElementInfo[] $elements, bool $showDescription = true, bool $onlyPrimary = false}
 6 | 	{try}
 7 | 		<tr>
 8 | 			<th class="table-heading" colspan="2">{$heading}</th>
 9 | 		</tr>
10 | 
11 | 		{foreach $elements as $element}
12 | 			{skipIf $onlyPrimary && !$element->primary}
13 | 			{skipIf !elementPageExists($element)}
14 | 			<tr>
15 | 				<td class="table-cell" colspan="{$showDescription ? 1 : 2}"><a href="{elementUrl($element)}" n:class="$element->isDeprecated() ? deprecated">{elementName($element)}</a></td>
16 | 				<td class="table-cell" n:if="$showDescription">{elementShortDescription($index, null, $element)}</td>
17 | 			</tr>
18 | 		{else}
19 | 			{rollback}
20 | 		{/foreach}
21 | 
22 | 		<tr>
23 | 			<td class="table-spacer" colspan="2"></td>
24 | 		</tr>
25 | 	{/try}
26 | {/define}
27 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/enumCase.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define enumCase, ApiGen\Info\EnumInfo $enum, ApiGen\Info\EnumCaseInfo $enumCase}
 6 | 	{embed member, $enumCase}
 7 | 		{block cells}
 8 | 			<td class="table-cell">
 9 | 				<code n:pre>
10 | 					{embed memberSourceLink, classLike: $enum, member: $enumCase}{block content}{$enumCase->name}{/block}{/embed}
11 | 					{if $enumCase->value} = {include expr, expr: $enumCase->value, scope: $enum}{/if}
12 | 				</code>
13 | 
14 | 				{embed memberDescription, classLike: $enum, member: $enumCase}
15 | 					{block details}{* TODO: other tags (uses, internal...) *}{/block}
16 | 				{/embed}
17 | 			</td>
18 | 		{/block}
19 | 	{/embed}
20 | {/define}
21 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/enumCaseSummary.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define enumCaseSummary, ApiGen\Info\EnumInfo $enum}
 6 | 	{if $enum->cases}
 7 | 		<table class="table" id="cases">
 8 | 			<tr>
 9 | 				<th class="table-heading sortable" colspan="2" title="Switch between natural and alphabetical order">Cases</th>
10 | 			</tr>
11 | 
12 | 			{foreach $enum->cases as $case}
13 | 				{include enumCase, enum: $enum, enumCase: $case}
14 | 			{/foreach}
15 | 		</table>
16 | 	{/if}
17 | {/define}
18 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/expr.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define expr, ApiGen\Info\ExprInfo $expr, ?ApiGen\Info\ClassLikeInfo $scope, string $indent = ''}
 6 | 	{pre}
 7 | 		{if $expr instanceof ApiGen\Info\Expr\BooleanExprInfo || $expr instanceof ApiGen\Info\Expr\NullExprInfo}
 8 | 			<span class="php-kw">{$expr->toString()}</span>
 9 | 		{elseif $expr instanceof ApiGen\Info\Expr\IntegerExprInfo || $expr instanceof ApiGen\Info\Expr\FloatExprInfo}
10 | 			<span class="php-num">{$expr->raw}</span>
11 | 		{elseif $expr instanceof ApiGen\Info\Expr\StringExprInfo}
12 | 			<span class="php-str">{$expr->raw ?? $expr->toString()}</span>
13 | 		{elseif $expr instanceof ApiGen\Info\Expr\ArrayExprInfo}
14 | 			{embed autoBreakingLine, items: $expr->items, maxWidth: 100, indent: $indent}
15 | 				{block before}{="["}{/block}
16 | 				{block after}{="]"}{/block}
17 | 				{block item}
18 | 					{if $item->key}{include expr, expr: $item->key, scope: $scope} =&gt; {/if}
19 | 					{include expr, expr: $item->value, scope: $scope, indent: "\t$indent"}
20 | 				{/block}
21 | 				{block sep},{/block}
22 | 			{/embed}
23 | 		{elseif $expr instanceof ApiGen\Info\Expr\ClassConstantFetchExprInfo}
24 | 			{if $expr->classLike->fullLower === 'self' && $scope}
25 | 				{var $memberClassLike = $scope}
26 | 				{include classLikeLink, classLike: $memberClassLike, text: $expr->classLike->fullLower}
27 | 			{elseif $expr->classLike->fullLower === 'parent' && $scope && isClass($scope) && $scope->extends}
28 | 				{var $memberClassLike = $index->classLike[$scope->extends->fullLower]}
29 | 				{include classLikeLink, classLike: $memberClassLike, text: $expr->classLike->fullLower}
30 | 			{elseif isset($index->classLike[$expr->classLike->fullLower])}
31 | 				{var $memberClassLike = $index->classLike[$expr->classLike->fullLower]}
32 | 				{include classLikeLink, classLike: $memberClassLike, short: true}
33 | 			{else}
34 | 				{var $memberClassLike = null}
35 | 				{$expr->classLike->full}
36 | 			{/if}
37 | 			::
38 | 			{if $memberClassLike !== null && isset($memberClassLike->constants[$expr->name])}
39 | 				{var $member = $memberClassLike->constants[$expr->name]}
40 | 				{embed memberLink, $memberClassLike, $member}{block content}{$member->name}{/block}{/embed}
41 | 			{elseif $memberClassLike !== null && isEnum($memberClassLike) && isset($memberClassLike->cases[$expr->name])}
42 | 				{var $member = $memberClassLike->cases[$expr->name]}
43 | 				{embed memberLink, $memberClassLike, $member}{block content}{$member->name}{/block}{/embed}
44 | 			{else}
45 | 				{$expr->name}
46 | 			{/if}
47 | 		{elseif $expr instanceof ApiGen\Info\Expr\ConstantFetchExprInfo}
48 | 			{$expr->name}
49 | 		{elseif $expr instanceof ApiGen\Info\Expr\UnaryOpExprInfo}
50 | 			{$expr->op}
51 | 			{include this, expr: $expr->expr, scope: $scope}
52 | 		{elseif $expr instanceof ApiGen\Info\Expr\BinaryOpExprInfo}
53 | 			{include this, expr: $expr->left, scope: $scope}
54 | 			{=" "}{$expr->op}{=" "}
55 | 			{include this, expr: $expr->right, scope: $scope}
56 | 		{elseif $expr instanceof ApiGen\Info\Expr\TernaryExprInfo}
57 | 			{include this, expr: $expr->condition, scope: $scope}
58 | 			{if $expr->if} ? {include this, expr: $expr->if, scope: $scope} : {else} ?: {/if}
59 | 			{include this, expr: $expr->else, scope: $scope}
60 | 		{elseif $expr instanceof ApiGen\Info\Expr\DimFetchExprInfo}
61 | 			{include this, expr: $expr->expr, scope: $scope}
62 | 			[{include this, expr: $expr->dim, scope: $scope}]
63 | 		{elseif $expr instanceof ApiGen\Info\Expr\PropertyFetchExprInfo}
64 | 			{include this, expr: $expr->expr, scope: $scope}
65 | 			-&gt;
66 | 			{if is_string($expr->property)}
67 | 				{$expr->property}
68 | 			{else}
69 | 				{{include this, expr: $expr->property, scope: $scope}}
70 | 			{/if}
71 | 		{elseif $expr instanceof ApiGen\Info\Expr\NullSafePropertyFetchExprInfo}
72 | 			{include this, expr: $expr->expr, scope: $scope}
73 | 			?-&gt;
74 | 			{if is_string($expr->property)}
75 | 				{$expr->property}
76 | 			{else}
77 | 				{{include this, expr: $expr->property, scope: $scope}}
78 | 			{/if}
79 | 		{elseif $expr instanceof ApiGen\Info\Expr\NewExprInfo}
80 | 			{var $classLike = $index->classLike[$expr->classLike->fullLower]}
81 | 			<span class="php-kw">new</span> {include classLikeLink, classLike: $classLike, short: true}
82 | 			(
83 | 			{foreach $expr->args as $arg}
84 | 				{if $arg->name}{$arg->name}: {/if}
85 | 				{include this, expr: $arg->value, scope: $scope}
86 | 				{sep}, {/sep}
87 | 			{/foreach}
88 | 			)
89 | 		{else}
90 | 			ERROR
91 | 		{/if}
92 | 	{/pre}
93 | {/define}
94 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/functionDescription.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define functionDescription, ApiGen\Info\FunctionInfo $function}
 6 | 	{if $function->description}
 7 | 		<div class="classLikeDescription">
 8 | 			{include description, $function->description, null}
 9 | 		</div>
10 | 	{/if}
11 | {/define}
12 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/functionLinks.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define functionLinks, ApiGen\Info\FunctionInfo $function}
 6 | 	{pre}
 7 | 		{if $function->name->namespaceLower}{include namespaceLinks, $index->namespace[$function->name->namespaceLower]}\{/if}
 8 | 		<a href="{functionUrl($function)}">{$function->name->short}</a>
 9 | 	{/pre}
10 | {/define}
11 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/genericParameters.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define genericParameters, ApiGen\Info\GenericParameterInfo[] $genericParameters}
 6 | 	{pre}
 7 | 		{if $genericParameters}
 8 | 			&lt;
 9 | 			{foreach $genericParameters as $genericParameter}
10 | 				{include genericParameter, $genericParameter}
11 | 				{sep}, {/sep}
12 | 			{/foreach}
13 | 			&gt;
14 | 		{/if}
15 | 	{/pre}
16 | {/define}
17 | 
18 | {define genericParameter, ApiGen\Info\GenericParameterInfo $genericParameter}
19 | 	{pre}
20 | 		{include genericParameterVariance, $genericParameter->variance}
21 | 		<span title="{$genericParameter->description}">{$genericParameter->name}</span>
22 | 		{if $genericParameter->bound} is {include type, $genericParameter->bound}{/if}
23 | 		{if $genericParameter->default} = {include type, $genericParameter->default}{/if}
24 | 	{/pre}
25 | {/define}
26 | 
27 | {define genericParameterVariance, ApiGen\Info\GenericParameterVariance $variance}
28 | 	{pre}
29 | 		{if $variance === ApiGen\Info\GenericParameterVariance::Covariant}<span title="covariant">out</span>{=' '}
30 | 		{elseif $variance === ApiGen\Info\GenericParameterVariance::Contravariant}<span title="contravariant">in</span>{=' '}
31 | 		{/if}
32 | 	{/pre}
33 | {/define}
34 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/head.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 | 
5 | {define head}{/define}
6 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/layout.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define layout}
 6 | <!doctype html>
 7 | <html lang="en">
 8 | <head>
 9 | 	<meta charset="utf-8">
10 | 	<meta name="viewport" content="width=device-width">
11 | 	{if $layout->noindex}<meta name="robots" content="noindex">{/if}
12 | 	<title>{ifset title}{include title|spaceless} | {/ifset}{$config->title} API</title>
13 | 	<link rel="stylesheet" href="{assetUrl('main.css')}">
14 | 	<script src="{assetUrl('main.js')}" defer></script>
15 | 	{include head}
16 | </head>
17 | 
18 | <body class="layout">
19 | 	<div class="layout-aside">
20 | 		{include menu}
21 | 	</div>
22 | 
23 | 	<div class="layout-main">
24 | 		<div class="layout-navbar">
25 | 			{include navbar}
26 | 		</div>
27 | 
28 | 		<div class="layout-content">
29 | 			{include content}
30 | 		</div>
31 | 
32 | 		<div class="layout-footer">
33 | 			{$config->title} API documentation generated by <a href="https://github.com/ApiGen/ApiGen">ApiGen {$config->version}</a>
34 | 		</div>
35 | 	</div>
36 | 
37 | 	<div class="layout-rest">
38 | 		<div class="navbar"></div>
39 | 	</div>
40 | </body>
41 | </html>
42 | {/define}
43 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/member.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define member, ApiGen\Info\MemberInfo $member}
 6 | 	<tr class="expandable collapsed" data-order="{$member->name}" id="{memberAnchor($member)}">
 7 | 		{include cells}
 8 | 		<td class="table-anchor">
 9 | 			<a href="#{memberAnchor($member)}">#</a>
10 | 		</td>
11 | 	</tr>
12 | {/define}
13 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/memberDescription.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define memberDescription, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\MemberInfo $member}
 6 | 	{var $description = $member->getEffectiveDescription($index, $classLike)}
 7 | 
 8 | 	<div n:if="$description" class="expandable-collapsedView">
 9 | 		{shortDescription($index, $classLike, $description)}
10 | 	</div>
11 | 
12 | 	<div class="expandable-expandedView">
13 | 		{include description, $description, $classLike}
14 | 		{block details}{/block}
15 | 	</div>
16 | {/define}
17 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/memberLink.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 | 
5 | {define memberLink, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\MemberInfo $member}
6 | 	<a n:pre n:tag-if="classLikePageExists($classLike)" href="{memberUrl($classLike, $member)}" n:class="$member->isDeprecated() ? deprecated">{include content}</a>
7 | {/define}
8 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/memberSourceLink.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define memberSourceLink, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\MemberInfo $member}
 6 | 	{pre}
 7 | 		<a n:tag-if="$classLike->file && sourcePageExists($index->files[$classLike->file])" href="{memberSourceUrl($classLike, $member)}" title="Go to source code">
 8 | 			<span n:tag-if="$member->isDeprecated()" class="deprecated">{include content}</span>
 9 | 		</a>
10 | 	{/pre}
11 | {/define}
12 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/memberVisibility.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 | 
5 | {define memberVisibility, ApiGen\Info\MemberInfo $member}
6 | 	{if $member->protected}protected{elseif $member->private}private{else}public{/if}
7 | {/define}
8 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/menu.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define menu}
 6 | 	<div class="menu">
 7 | 		{ifset $index->namespace['']}
 8 | 			<div class="menu-section">
 9 | 				<h3>Namespaces</h3>
10 | 				{include menuGroup, namespace: $index->namespace['']}
11 | 			</div>
12 | 		{/ifset}
13 | 
14 | 		{if $layout->activeNamespace}
15 | 			{include menuElements, elements: $layout->activeNamespace->class, heading: 'Classes', onlyPrimary: $layout->activeNamespace->primary}
16 | 			{include menuElements, elements: $layout->activeNamespace->interface, heading: 'Interfaces', onlyPrimary: $layout->activeNamespace->primary}
17 | 			{include menuElements, elements: $layout->activeNamespace->trait, heading: 'Traits', onlyPrimary: $layout->activeNamespace->primary}
18 | 			{include menuElements, elements: $layout->activeNamespace->enum, heading: 'Enums', onlyPrimary: $layout->activeNamespace->primary}
19 | 			{include menuElements, elements: $layout->activeNamespace->exception, heading: 'Exceptions', onlyPrimary: $layout->activeNamespace->primary}
20 | 			{include menuElements, elements: $layout->activeNamespace->function, heading: 'Functions', onlyPrimary: $layout->activeNamespace->primary}
21 | 		{/if}
22 | 	</div>
23 | {/define}
24 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/menuElements.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define menuElements, ApiGen\Info\ElementInfo[] $elements, string $heading, bool $onlyPrimary = true}
 6 | 	{try}
 7 | 		<div class="menu-section">
 8 | 			<h3>{$heading}</h3>
 9 | 			<ul>
10 | 				{foreach $elements as $element}
11 | 					{skipIf $onlyPrimary && !$element->primary}
12 | 					{skipIf !elementPageExists($element)}
13 | 					<li n:class="$element === $layout->activeElement ? active">
14 | 						<a n:class="$element->isDeprecated() ? deprecated" href="{elementUrl($element)}">
15 | 							{elementName($element)}
16 | 						</a>
17 | 					</li>
18 | 				{else}
19 | 					{rollback}
20 | 				{/foreach}
21 | 			</ul>
22 | 		</div>
23 | 	{/try}
24 | {/define}
25 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/menuGroup.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define menuGroup, ApiGen\Index\NamespaceIndex $namespace, int $level = 0}
 6 | 	<ul>
 7 | 		{foreach $namespace->children as $child}
 8 | 			{var bool $isActive = $layout->activeNamespace && str_starts_with("{$layout->activeNamespace->name->fullLower}\\", "{$child->name->fullLower}\\")}
 9 | 			{var bool $isCollapsed = $level > 0 && !$isActive}
10 | 			{skipIf !$child->primary && !$isActive}
11 | 			{skipIf !namespacePageExists($child)}
12 | 			<li n:class="menuGroup-item, $isActive ? active">
13 | 				<a href="{namespaceUrl($child)}" n:class="$child->deprecated ? deprecated">{$child->name->short ?: 'none'}</a>
14 | 				{if !$isCollapsed && $child->children && $child->name->short !== ''}
15 | 					{include this, namespace: $child, level: $level + 1}
16 | 				{/if}
17 | 			</li>
18 | 		{/foreach}
19 | 	</ul>
20 | {/define}
21 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/method.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define method, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\MethodInfo $method}
 6 | 	{embed member, $method}
 7 | 		{block cells}
 8 | 			<td class="table-cell table-shrink">
 9 | 				<code class="nowrap">
10 | 					{if !isInterface($classLike) && $method->abstract}abstract{elseif $method->final}final{/if}
11 | 					{include memberVisibility, $method}
12 | 					{if $method->static}static{/if}
13 | 					{if $method->byRef}&amp;{/if}
14 | 				</code>
15 | 			</td>
16 | 
17 | 			<td class="table-cell">
18 | 				<code>{include methodSignature, classLike: $classLike, method: $method}</code>
19 | 
20 | 				{embed memberDescription, classLike: $classLike, member: $method}
21 | 					{block details}
22 | 						{try}
23 | 							<h4>Deprecated</h4>
24 | 							<div>
25 | 								{foreach $method->tags['deprecated'] ?? [] as $tag}
26 | 									{skipIf !$tag->description}
27 | 									{longDescription($index, $classLike, $tag->description)}{sep}<br>{/sep}
28 | 								{else}
29 | 									{rollback}
30 | 								{/foreach}
31 | 							</div>
32 | 						{/try}
33 | 
34 | 						{try}
35 | 							<h4>Parameters</h4>
36 | 							<table>
37 | 								{foreach $method->parameters as $parameter}
38 | 									{var string $description = $parameter->getEffectiveDescription($index, $classLike, $method)}
39 | 									{skipIf !$description}
40 | 									<tr>
41 | 										<td>{if $parameter->variadic}...{/if}<var>${$parameter->name}</var>&nbsp;&nbsp;</td>
42 | 										<td>{longDescription($index, $classLike, $description)}</td>
43 | 									</tr>
44 | 								{else}
45 | 									{rollback}
46 | 								{/foreach}
47 | 							</table>
48 | 						{/try}
49 | 
50 | 						{var string $returnDescription = $method->getEffectiveReturnDescription($index, $classLike)}
51 | 						{if $returnDescription}
52 | 							<h4>Returns</h4>
53 | 							<div>{longDescription($index, $classLike, $returnDescription)}</div>
54 | 						{/if}
55 | 
56 | 						{if !empty($method->tags['throws'])}
57 | 							<h4>Throws</h4>
58 | 							<table>
59 | 								{foreach $method->tags['throws'] as $tag}
60 | 									<tr>
61 | 										<td><code>{include type, type: $tag->type, scope: $classLike, short: false}</code>&nbsp;</td>
62 | 										<td>{longDescription($index, $classLike, $tag->description)}</td>
63 | 									</tr>
64 | 								{/foreach}
65 | 							</table>
66 | 						{/if}
67 | 
68 | 						{* TODO: other tags (uses, internal...) *}
69 | 
70 | 						{include methodRelation, title: 'Overrides', method: $method, related: $index->methodOverrides[$classLike->name->fullLower][$method->nameLower] ?? []}
71 | 						{include methodRelation, title: 'Overridden by', method: $method, related: $index->methodOverriddenBy[$classLike->name->fullLower][$method->nameLower] ?? []}
72 | 						{include methodRelation, title: 'Implements', method: $method, related: $index->methodImplements[$classLike->name->fullLower][$method->nameLower] ?? []}
73 | 						{include methodRelation, title: 'Implemented by', method: $method, related: $index->methodImplementedBy[$classLike->name->fullLower][$method->nameLower] ?? []}
74 | 					{/block}
75 | 				{/embed}
76 | 			</td>
77 | 		{/block}
78 | 	{/embed}
79 | {/define}
80 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/methodInherited.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define methodInherited, ApiGen\Info\ClassLikeInfo $from, array $overriden}
 6 | 	{try}
 7 | 		<table class="table">
 8 | 			<tr>
 9 | 				<th class="table-heading">Methods inherited from {include classLikeLink, $from}</th>
10 | 			</tr>
11 | 			<tr>
12 | 				<td class="table-cell">
13 | 					<code n:pre>
14 | 						{foreach $from->methods as $methodName => $method}
15 | 							{skipIf isset($overriden[$methodName]) || $method->private}
16 | 							{embed memberLink, $from, $method}{block content}{$method->name}(){/block}{/embed}
17 | 							{sep}, {/sep}
18 | 						{else}
19 | 							{rollback}
20 | 						{/foreach}
21 | 					</code>
22 | 				</td>
23 | 			</tr>
24 | 		</table>
25 | 	{/try}
26 | 
27 | 	{include methodInheritedSummary, $from, $overriden}
28 | {/define}
29 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/methodInheritedSummary.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define methodInheritedSummary, ApiGen\Info\ClassLikeInfo $classLike, array $overriden}
 6 | 	{if isClass($classLike)}
 7 | 		{if $classLike->extends}
 8 | 			{include methodInherited, $index->classLike[$classLike->extends->fullLower], $overriden + $classLike->methods}
 9 | 		{/if}
10 | 	{elseif isInterface($classLike)}
11 | 		{foreach $classLike->extends as $parent}
12 | 			{include methodInherited, $index->classLike[$parent->fullLower], $overriden + $classLike->methods}
13 | 		{/foreach}
14 | 	{/if}
15 | {/define}
16 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/methodRelation.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define methodRelation, ApiGen\Info\MethodInfo $method, string $title, ApiGen\Info\ClassLikeInfo[] $related}
 6 | 	{if $related}
 7 | 		<h4>{$title}</h4>
 8 | 		<ul>
 9 | 			{foreach $related as $classLike}
10 | 				<li>
11 | 					{embed memberLink, $classLike, $method}
12 | 						{block content}<code>{$classLike->name->full}::{$method->name}()</code>{/block}
13 | 					{/embed}
14 | 				</li>
15 | 			{/foreach}
16 | 		</ul>
17 | 	{/if}
18 | {/define}
19 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/methodSignature.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define methodSignature, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\MethodInfo $method}
 6 | 	{pre}
 7 | 		{embed autoBreakingLine, items: $method->parameters, maxWidth: 120}
 8 | 			{block before}
 9 | 				{embed memberSourceLink, classLike: $classLike, member: $method}{block content}{$method->name}{/block}{/embed}
10 | 				{include genericParameters, $method->genericParameters}
11 | 				(
12 | 			{/block}
13 | 			{block after}){if $method->returnType}: {include type, type: $method->returnType, scope: $classLike}{/if}{/block}
14 | 			{block item}{include parameter, classLike: $classLike, parameter: $item}{/block}
15 | 			{block sep},{/block}
16 | 		{/embed}
17 | 	{/pre}
18 | {/define}
19 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/methodSummary.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define methodSummary, ApiGen\Info\ClassLikeInfo $classLike}
 6 | 	{if $classLike->methods}
 7 | 		<table class="table" id="methods">
 8 | 			<tr>
 9 | 				<th class="table-heading sortable" colspan="3" title="Switch between natural and alphabetical order">Methods</th>
10 | 			</tr>
11 | 
12 | 			{foreach $classLike->methods as $method}
13 | 				{include method, classLike: $classLike, method: $method}
14 | 			{/foreach}
15 | 		</table>
16 | 	{/if}
17 | {/define}
18 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/methodUsed.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define methodUsed, ApiGen\Info\ClassLikeInfo $from, array $overriden}
 6 | 	{try}
 7 | 		<table class="table" n:if="isTrait($from)">
 8 | 			<tr>
 9 | 				<th class="table-heading">Methods used from {include classLikeLink, $from}</th>
10 | 			</tr>
11 | 			<tr>
12 | 				<td class="table-cell">
13 | 					<code n:pre>
14 | 						{foreach $from->methods as $methodName => $method}
15 | 							{skipIf isset($overriden[$methodName])}
16 | 							{embed memberLink, $from, $method}{block content}{$method->name}(){/block}{/embed}
17 | 							{sep}, {/sep}
18 | 						{else}
19 | 							{rollback}
20 | 						{/foreach}
21 | 					</code>
22 | 				</td>
23 | 			</tr>
24 | 		</table>
25 | 	{/try}
26 | 
27 | 	{include methodUsedSummary, $from, $overriden}
28 | {/define}
29 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/methodUsedSummary.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define methodUsedSummary, ApiGen\Info\ClassLikeInfo $classLike, array $overriden}
 6 | 	{if isClass($classLike)}
 7 | 		{if $classLike->extends}
 8 | 			{include methodUsed, $index->classLike[$classLike->extends->fullLower], $overriden + $classLike->methods}
 9 | 		{/if}
10 | 
11 | 		{foreach $classLike->uses as $use}
12 | 			{include methodUsed, $index->classLike[$use->fullLower], $overriden + $classLike->methods}
13 | 		{/foreach}
14 | 
15 | 	{elseif isInterface($classLike)}
16 | 		{foreach $classLike->extends as $parent}
17 | 			{include methodUsed, $index->classLike[$parent->fullLower], $overriden + $classLike->methods}
18 | 		{/foreach}
19 | 
20 | 	{elseif isTrait($classLike) || isEnum($classLike)}
21 | 		{foreach $classLike->uses as $use}
22 | 			{include methodUsed, $index->classLike[$use->fullLower], $overriden + $classLike->methods}
23 | 		{/foreach}
24 | 	{/if}
25 | {/define}
26 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/namespaceLinks.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define namespaceLinks, ApiGen\Index\NamespaceIndex $namespace}
 6 | 	{pre}
 7 | 		{if $namespace->name->namespaceLower !== ''}
 8 | 			{include this, $index->namespace[$namespace->name->namespaceLower]}\
 9 | 		{/if}
10 | 		<a n:tag-if="namespacePageExists($namespace)" href="{namespaceUrl($namespace)}">{$namespace->name->short}</a>
11 | 	{/pre}
12 | {/define}
13 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/navbar.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define navbar}
 6 | 	<nav class="navbar">
 7 | 		<div class="navbar-left">
 8 | 			<ul class="navbar-links">
 9 | 				<li n:class="$layout->activePage === 'index' ? active">
10 | 					<a href="{indexUrl()}">Overview</a>
11 | 				</li>
12 | 				<li n:class="$layout->activePage === 'namespace' ? active">
13 | 					<span n:tag-if="!$layout->activeNamespace">
14 | 						<a n:tag-if="$layout->activeNamespace && namespacePageExists($layout->activeNamespace)" href="{namespaceUrl($layout->activeNamespace)}">Namespace</a>
15 | 					</span>
16 | 				</li>
17 | 				<li n:class="$layout->activeElement ? active" n:pre>
18 | 					<span n:tag-if="!$layout->activeElement">
19 | 						<a n:tag-if="$layout->activeElement" href="{elementUrl($layout->activeElement)}">
20 | 							{if $layout->activeElement instanceof ApiGen\Info\ClassLikeInfo}{include classLikeKind, $layout->activeElement}
21 | 							{elseif $layout->activeElement instanceof ApiGen\Info\FunctionInfo}Function
22 | 							{else}Class
23 | 							{/if}
24 | 						</a>
25 | 					</span>
26 | 				</li>
27 | 				<li n:if="treePageExists()" n:class="$layout->activePage === 'tree' ? active">
28 | 					<a href="{treeUrl()}">Tree</a>
29 | 				</li>
30 | 			</ul>
31 | 		</div>
32 | 		<div class="navbar-right">
33 | 			{include search}
34 | 		</div>
35 | 	</nav>
36 | {/define}
37 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/parameter.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define parameter, ApiGen\Info\ParameterInfo $parameter, ?ApiGen\Info\ClassLikeInfo $classLike = null}
 6 | 	<span n:pre>
 7 | 		{if $parameter->type}{include type, type: $parameter->type, scope: $classLike} {/if}
 8 | 		{if $parameter->byRef}&amp;{/if}{if $parameter->variadic}...{/if}<var>${$parameter->name}</var>
 9 | 		{if $parameter->default} = {include expr, expr: $parameter->default, scope: $classLike}{/if}
10 | 	</span>
11 | {/define}
12 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/property.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define property, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\PropertyInfo $property}
 6 | 	{embed member, $property}
 7 | 		{block cells}
 8 | 			<td class="table-cell table-shrink">
 9 | 				<code class="nowrap">
10 | 					{include memberVisibility, $property}
11 | 					{if $property->static}static{/if}
12 | 					{if $property->readOnly}readonly{elseif $property->writeOnly}writeonly{/if}
13 | 				</code>
14 | 			</td>
15 | 
16 | 			<td class="table-cell table-shrink">
17 | 				<code class="nowrap">{if $property->type}{include type, $property->type, scope: $classLike}{/if}</code>
18 | 			</td>
19 | 
20 | 			<td class="table-cell">
21 | 				<code n:pre>
22 | 					<var>{embed memberSourceLink, $classLike, $property}{block content}${$property->name}{/block}{/embed}</var>
23 | 					{if $property->default} = {include expr, expr: $property->default, scope: $classLike}{/if}
24 | 				</code>
25 | 
26 | 				{embed memberDescription, classLike: $classLike, member: $property}}
27 | 					{block details}{* TODO: other tags (uses, internal...) *}{/block}
28 | 				{/embed}
29 | 			</td>
30 | 		{/block}
31 | 	{/embed}
32 | {/define}
33 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/propertyInherited.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define propertyInherited, ApiGen\Info\ClassLikeInfo $from, array $overriden}
 6 | 	{try}
 7 | 		<table class="table">
 8 | 			<tr>
 9 | 				<th class="table-heading">Properties inherited from {include classLikeLink, $from}</th>
10 | 			</tr>
11 | 			<tr>
12 | 				<td class="table-cell">
13 | 					<code n:pre>
14 | 						{foreach $from->properties as $propertyName => $property}
15 | 							{skipIf isset($overriden[$propertyName])}
16 | 							<var>{embed memberLink, $from, $property}{block content}${$property->name}{/block}{/embed}</var>
17 | 							{sep}, {/sep}
18 | 						{else}
19 | 							{rollback}
20 | 						{/foreach}
21 | 					</code>
22 | 				</td>
23 | 			</tr>
24 | 		</table>
25 | 	{/try}
26 | 
27 | 	{include propertyInheritedSummary, $from, $overriden}
28 | {/define}
29 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/propertyInheritedSummary.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define propertyInheritedSummary, ApiGen\Info\ClassLikeInfo $classLike, array $overriden}
 6 | 	{if isClass($classLike)}
 7 | 		{if $classLike->extends}
 8 | 			{include propertyInherited, $index->classLike[$classLike->extends->fullLower], $overriden + $classLike->properties}
 9 | 		{/if}
10 | 	{elseif isInterface($classLike)}
11 | 		{foreach $classLike->extends as $parent}
12 | 			{include propertyInherited, $index->classLike[$parent->fullLower], $overriden + $classLike->properties}
13 | 		{/foreach}
14 | 	{/if}
15 | {/define}
16 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/propertySummary.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define propertySummary, ApiGen\Info\ClassLikeInfo $classLike}
 6 | 	{if $classLike->properties}
 7 | 		<table class="table" id="properties">
 8 | 			<tr>
 9 | 				<th class="table-heading sortable" colspan="4" title="Switch between natural and alphabetical order">Properties</th>
10 | 			</tr>
11 | 
12 | 			{foreach $classLike->properties as $property}
13 | 				{include property, classLike: $classLike, property: $property}
14 | 			{/foreach}
15 | 		</table>
16 | 	{/if}
17 | {/define}
18 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/propertyUsed.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define propertyUsed, ApiGen\Info\ClassLikeInfo $from, array $overriden}
 6 | 	{try}
 7 | 		<table class="table" n:if="isTrait($from)">
 8 | 			<tr>
 9 | 				<th class="table-heading">Properties used from {include classLikeLink, $from}</th>
10 | 			</tr>
11 | 			<tr>
12 | 				<td class="table-cell">
13 | 					<code n:pre>
14 | 						{foreach $from->properties as $propertyName => $property}
15 | 							{skipIf isset($overriden[$propertyName])}
16 | 							<var>{embed memberLink, $from, $property}{block content}${$property->name}{/block}{/embed}</var>
17 | 							{sep}, {/sep}
18 | 						{else}
19 | 							{rollback}
20 | 						{/foreach}
21 | 					</code>
22 | 				</td>
23 | 			</tr>
24 | 		</table>
25 | 	{/try}
26 | 
27 | 	{include propertyUsedSummary, $from, $overriden}
28 | {/define}
29 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/propertyUsedSummary.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define propertyUsedSummary, ApiGen\Info\ClassLikeInfo $classLike, array $overriden}
 6 | 	{if isClass($classLike)}
 7 | 		{if $classLike->extends}
 8 | 			{include propertyUsed, $index->classLike[$classLike->extends->fullLower], $overriden + $classLike->properties}
 9 | 		{/if}
10 | 
11 | 		{foreach $classLike->uses as $use}
12 | 			{include propertyUsed, $index->classLike[$use->fullLower], $overriden + $classLike->properties}
13 | 		{/foreach}
14 | 
15 | 	{elseif isInterface($classLike)}
16 | 		{foreach $classLike->extends as $parent}
17 | 			{include propertyUsed, $index->classLike[$parent->fullLower], $overriden + $classLike->properties}
18 | 		{/foreach}
19 | 
20 | 	{elseif isTrait($classLike) || isEnum($classLike)}
21 | 		{foreach $classLike->uses as $use}
22 | 			{include propertyUsed, $index->classLike[$use->fullLower], $overriden + $classLike->properties}
23 | 		{/foreach}
24 | 	{/if}
25 | {/define}
26 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/search.latte:
--------------------------------------------------------------------------------
 1 | {varType ApiGen\Index\Index $index}
 2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
 3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
 4 | 
 5 | {define search}
 6 | 	<div class="search" data-elements="{assetUrl('elements.js')}">
 7 | 		<input class="search-input" placeholder="Search class, function or namespace" autofocus>
 8 | 		<ul class="search-results"></ul>
 9 | 	</div>
10 | {/define}
11 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/source.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 | 
5 | {define source, string $path}
6 | 	<table class="source">{highlight($path)}</table>
7 | {/define}
8 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/type.latte:
--------------------------------------------------------------------------------
  1 | {varType ApiGen\Index\Index $index}
  2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
  3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
  4 | 
  5 | {define type, PHPStan\PhpDocParser\Ast\Type\TypeNode $type, ?ApiGen\Info\ClassLikeInfo $scope, bool $brackets = false, bool $short = true}
  6 | 	{pre}
  7 | 		{if $type instanceof PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode}
  8 | 			{var ApiGen\Analyzer\IdentifierKind $kind = $type->getAttribute('kind')}
  9 | 			{if $kind === ApiGen\Analyzer\IdentifierKind::ClassLike}
 10 | 				{var ApiGen\Info\ClassLikeReferenceInfo $ref = $type->getAttribute('classLikeReference')}
 11 | 				{include classLikeLink, $index->classLike[$ref->fullLower], $short}
 12 | 			{elseif $kind === ApiGen\Analyzer\IdentifierKind::Alias}
 13 | 				{var ApiGen\Info\AliasReferenceInfo $ref = $type->getAttribute('aliasReference')}
 14 | 				{var ApiGen\Info\ClassLikeInfo $classLike = $index->classLike[$ref->classLike->fullLower]}
 15 | 				{var ApiGen\Info\AliasInfo $alias = $classLike->aliases[$ref->aliasLower] ?? null}
 16 | 				{if $alias !== null}
 17 | 					{include aliasLink, classLike: $classLike, alias: $alias}
 18 | 				{else}
 19 | 					<span title="Alias">{$type}</span>
 20 | 				{/if}
 21 | 			{elseif $kind === ApiGen\Analyzer\IdentifierKind::Keyword && $type->name === 'parent' && $scope && isClass($scope) && $scope->extends}
 22 | 				{include classLikeLink, $index->classLike[$scope->extends->fullLower], text: 'parent'}
 23 | 			{elseif $kind === ApiGen\Analyzer\IdentifierKind::Keyword && $type->name === 'self' && $scope}
 24 | 				{include classLikeLink, $scope, text: 'self'}
 25 | 			{else}
 26 | 				{$type}
 27 | 			{/if}
 28 | 
 29 | 		{elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode}
 30 | 			{include this, type: $type->type, scope: $scope, brackets: true, short: $short}[]
 31 | 
 32 | 		{elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode}
 33 | 			{$type->kind}{
 34 | 			{foreach $type->items as $item}
 35 | 				{if $item->keyName}
 36 | 					{$item->keyName}
 37 | 					{if $item->optional}?{/if}{=": "}
 38 | 				{/if}
 39 | 				{include this, type: $item->valueType, scope: $scope, brackets: true, short: $short}
 40 | 				{sep}, {/sep}
 41 | 			{/foreach}
 42 | 			{if !$type->sealed}{if $type->items}, {/if}...{/if}
 43 | 			}
 44 | 
 45 | 		{elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode}
 46 | 			object{
 47 | 			{foreach $type->items as $item}
 48 | 				{$item->keyName}
 49 | 				{if $item->optional}?{/if}{=": "}
 50 | 				{include this, type: $item->valueType, scope: $scope, brackets: true, short: $short}
 51 | 				{sep}, {/sep}
 52 | 			{/foreach}
 53 | 			}
 54 | 
 55 | 		{elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\NullableTypeNode}
 56 | 			?{include this, type: $type->type, scope: $scope, brackets: true, short: $short}
 57 | 
 58 | 		{elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\UnionTypeNode}
 59 | 			{if $brackets}({/if}
 60 | 			{foreach $type->types as $innerType}
 61 | 				{include this, type: $innerType, scope: $scope, brackets: true, short: $short}
 62 | 				{sep}|{/sep}
 63 | 			{/foreach}
 64 | 			{if $brackets}){/if}
 65 | 
 66 | 		{elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode}
 67 | 			{if $brackets}({/if}
 68 | 			{foreach $type->types as $innerType}
 69 | 				{include this, type: $innerType, scope: $scope, brackets: true, short: $short}
 70 | 				{sep}&amp;{/sep}
 71 | 			{/foreach}
 72 | 			{if $brackets}){/if}
 73 | 
 74 | 		{elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\GenericTypeNode}
 75 | 			{include this, type: $type->type, scope: $scope, short: $short}
 76 | 			&lt;
 77 | 			{foreach $type->genericTypes as $idx => $genericType}
 78 | 				{var $variance = $type->variances[$idx] ?? null}
 79 | 				{if $variance === PHPStan\PhpDocParser\Ast\Type\GenericTypeNode::VARIANCE_BIVARIANT}
 80 | 					*
 81 | 				{elseif $variance === PHPStan\PhpDocParser\Ast\Type\GenericTypeNode::VARIANCE_COVARIANT}
 82 | 					<span title="covariant">out</span>{=' '}
 83 | 					{include this, type: $genericType, scope: $scope, short: $short}
 84 | 				{elseif $variance === PHPStan\PhpDocParser\Ast\Type\GenericTypeNode::VARIANCE_CONTRAVARIANT}
 85 | 					<span title="contravariant">in</span>{=' '}
 86 | 					{include this, type: $genericType, scope: $scope, short: $short}
 87 | 				{else}
 88 | 					{include this, type: $genericType, scope: $scope, short: $short}
 89 | 				{/if}
 90 | 				{sep}, {/sep}
 91 | 			{/foreach}
 92 | 			&gt;
 93 | 
 94 | 		{elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\CallableTypeNode}
 95 | 			{if $brackets}({/if}
 96 | 			{include this, type: $type->identifier, scope: $scope, short: $short}
 97 | 			(
 98 | 			{foreach $type->parameters as $parameter}
 99 | 				{include this, type: $parameter->type, scope: $scope, short: $short}
100 | 				{if $parameter->isReference}&{/if}{if $parameter->isVariadic}...{/if}
101 | 				{if $parameter->parameterName && $parameter->type} {/if}
102 | 				{$parameter->parameterName}
103 | 				{sep}, {/sep}
104 | 			{/foreach}
105 | 			):&nbsp;
106 | 			{include this, type: $type->returnType, scope: $scope, brackets: true, short: $short}
107 | 			{if $brackets}){/if}
108 | 
109 | 		{elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\ThisTypeNode}
110 | 			{$type}
111 | 
112 | 		{elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\ConstTypeNode}
113 | 			{var ApiGen\Info\ExprInfo $expr = $type->constExpr->getAttribute('info')}
114 | 			{include expr, expr: $expr, scope: $scope}
115 | 
116 | 		{elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode}
117 | 			{include this, type: $type->type, scope: $scope, short: $short}
118 | 			[{include this, type: $type->offset, scope: $scope, short: $short}]
119 | 
120 | 		{elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode}
121 | 			(
122 | 			{include this, type: $type->subjectType, scope: $scope, short: $short}
123 | 			{if $type->negated} is not {else} is {/if}
124 | 			{include this, type: $type->targetType, scope: $scope, short: $short}
125 | 			&nbsp;?&nbsp;
126 | 			{include this, type: $type->if, scope: $scope, short: $short}
127 | 			&nbsp;:&nbsp;
128 | 			{include this, type: $type->else, scope: $scope, short: $short}
129 | 			)
130 | 
131 | 		{elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode}
132 | 			(
133 | 			{$type->parameterName}
134 | 			{if $type->negated} is not {else} is {/if}
135 | 			{include this, type: $type->targetType, scope: $scope, short: $short}
136 | 			&nbsp;?&nbsp;
137 | 			{include this, type: $type->if, scope: $scope, short: $short}
138 | 			&nbsp;:&nbsp;
139 | 			{include this, type: $type->else, scope: $scope, short: $short}
140 | 			)
141 | 
142 | 		{else}
143 | 			UNRESOLVED
144 | 		{/if}
145 | 	{/pre}
146 | {/define}
147 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/pages/classLike.latte:
--------------------------------------------------------------------------------
 1 | {templateType ApiGen\Renderer\Latte\Template\ClassLikeTemplate}
 2 | {import '../blocks/@index.latte'}
 3 | 
 4 | {embed layout}
 5 | 	{block title}
 6 | 		{if $classLike->isDeprecated()}Deprecated {/if}
 7 | 		{include classLikeKind, $classLike}
 8 | 		{$classLike->name->full}
 9 | 	{/block}
10 | 
11 | 	{block content}
12 | 		<h1 n:class="$classLike->isDeprecated() ? deprecated">
13 | 			{include classLikeKind, $classLike} {include classLikeLinks, $classLike}
14 | 		</h1>
15 | 
16 | 		{if $classLike->genericParameters}
17 | 			<h2><code>{include genericParameters, $classLike->genericParameters}</code></h2>
18 | 		{/if}
19 | 
20 | 		{include classLikeSignatureTable, $classLike}
21 | 		{include classLikeDescription, $classLike}
22 | 
23 | 		{if !empty($index->dag[$classLike->name->fullLower]) || (isClass($classLike) && $classLike->extends) || (isInterface($classLike) && $classLike->extends)}
24 | 			<div class="classLikeSection">
25 | 				{include classTree, $classLike, $index->dag}
26 | 			</div>
27 | 		{/if}
28 | 
29 | 		<div class="classLikeSection">
30 | 			{if $classLike->file && sourcePageExists($index->files[$classLike->file])}<b>Located at</b> <a href="{classLikeSourceUrl($classLike)}" title="Go to source code">{relativePath($classLike->file)}</a><br>{/if}
31 | 		</div>
32 | 
33 | 		{include aliasSummary, $classLike}
34 | 
35 | 		{if isEnum($classLike)}
36 | 			{include enumCaseSummary, $classLike}
37 | 		{/if}
38 | 
39 | 		{include methodSummary, $classLike}
40 | 		{include methodInheritedSummary, $classLike, []}
41 | 		{include methodUsedSummary, $classLike, []}
42 | 
43 | 		{include constantSummary, $classLike}
44 | 		{include constantInheritedSummary, $classLike, []}
45 | 
46 | 		{include propertySummary, $classLike}
47 | 		{include propertyInheritedSummary, $classLike, []}
48 | 		{include propertyUsedSummary, $classLike, []}
49 | 	{/block}
50 | {/embed}
51 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/pages/function.latte:
--------------------------------------------------------------------------------
 1 | {templateType ApiGen\Renderer\Latte\Template\FunctionTemplate}
 2 | {import '../blocks/@index.latte'}
 3 | 
 4 | {embed layout}
 5 | 	{block title}
 6 | 		{if $function->isDeprecated()}Deprecated {/if}
 7 | 		{$function->name->full}
 8 | 	{/block}
 9 | 
10 | 	{block content}
11 | 		<h1 n:class="$function->isDeprecated() ? deprecated">
12 | 			{include functionLinks, $function}
13 | 		</h1>
14 | 
15 | 		{if $function->genericParameters}
16 | 			<h2><code>{include genericParameters, $function->genericParameters}</code></h2>
17 | 		{/if}
18 | 
19 | 		{include functionDescription, $function}
20 | 
21 | 		<div class="classLikeSection" n:if="$function->file && sourcePageExists($index->files[$function->file])">
22 | 			<b>Located at</b> <a href="{functionSourceUrl($function)}" title="Go to source code">{relativePath($function->file)}</a>
23 | 		</div>
24 | 
25 | 		<table class="table" n:if="$function->parameters">
26 | 			<tr>
27 | 				<th class="table-heading" colspan="4">Parameters</th>
28 | 			</tr>
29 | 
30 | 			{foreach $function->parameters as $parameter}
31 | 				<tr id="{parameterAnchor($parameter)}">
32 | 					<td class="table-cell table-shrink">
33 | 						<code class="nowrap">{if $parameter->type}{include type, $parameter->type}{/if}</code>
34 | 					</td>
35 | 					<td class="table-cell table-shrink">
36 | 						<code class="nowrap" n:pre>
37 | 							{if $parameter->byRef}&amp;{/if}{if $parameter->variadic}...{/if}<var>${$parameter->name}</var>
38 | 							{if $parameter->default} = {include expr, expr: $parameter->default}{/if}
39 | 						</code>
40 | 					</td>
41 | 					<td class="table-cell">{longDescription($index, null, $parameter->description)}</td>
42 | 					<td class="table-anchor">
43 | 						<a href="#{parameterAnchor($parameter)}">#</a>
44 | 					</td>
45 | 				</tr>
46 | 			{/foreach}
47 | 		</table>
48 | 
49 | 		<table class="table" n:if="$function->byRef || $function->returnType || $function->returnDescription">
50 | 			<tr>
51 | 				<th class="table-heading" colspan="2">Returns</th>
52 | 			</tr>
53 | 
54 | 			<tr>
55 | 				<td class="table-cell table-shrink" n:if="$function->byRef || $function->returnType">
56 | 					<code class="nowrap">{if $function->byRef}&amp;{/if}{include type, $function->returnType}</code>
57 | 				</td>
58 | 				<td class="table-cell" n:if="$function->returnDescription">
59 | 					{longDescription($index, null, $function->returnDescription)}
60 | 				</td>
61 | 			</tr>
62 | 		</table>
63 | 
64 | 		<table class="table" n:ifset="$function->tags[throws]">
65 | 			<tr>
66 | 				<th class="table-heading" colspan="2">Throws</th>
67 | 			</tr>
68 | 
69 | 			{foreach $function->tags[throws] as $tag}
70 | 				<tr>
71 | 					<td class="table-cell table-shrink"><code class="nowrap">{include type, type: $tag->type, short: false}</code></td>
72 | 					<td class="table-cell" n:if="$tag->description">{longDescription($index, $tag->description)}</td>
73 | 				</tr>
74 | 			{/foreach}
75 | 		</table>
76 | 	{/block}
77 | {/embed}
78 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/pages/index.latte:
--------------------------------------------------------------------------------
 1 | {templateType ApiGen\Renderer\Latte\Template\TreeTemplate}
 2 | {import '../blocks/@index.latte'}
 3 | 
 4 | {embed layout}
 5 | 	{block content}
 6 | 		<h1>{$config->title} API documentation</h1>
 7 | 
 8 | 		{embed elementSummary}
 9 | 			{block elementSummaryContent}
10 | 				{include elementSummaryGroup, heading: 'Namespaces', elements: $index->namespace, showDescription: false, onlyPrimary: true}
11 | 			{/block}
12 | 		{/embed}
13 | 	{/block}
14 | {/embed}
15 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/pages/namespace.latte:
--------------------------------------------------------------------------------
 1 | {templateType ApiGen\Renderer\Latte\Template\NamespaceTemplate}
 2 | {import '../blocks/@index.latte'}
 3 | 
 4 | {embed layout}
 5 | 	{block title}{if $namespace->name->full !== ''}Namespace {$namespace->name->full}{else}No namespace{/if}{/block}
 6 | 
 7 | 	{block content}
 8 | 		<h1>{if $namespace->name->full !== ''}Namespace {include namespaceLinks, $namespace}{else}No namespace{/if}</h1>
 9 | 
10 | 		{embed elementSummary}
11 | 			{block elementSummaryContent}
12 | 				{include elementSummaryGroup, heading: 'Classes', elements: $namespace->class, onlyPrimary: $namespace->primary}
13 | 				{include elementSummaryGroup, heading: 'Interfaces', elements: $namespace->interface, onlyPrimary: $namespace->primary}
14 | 				{include elementSummaryGroup, heading: 'Traits', elements: $namespace->trait, onlyPrimary: $namespace->primary}
15 | 				{include elementSummaryGroup, heading: 'Enums', elements: $namespace->enum, onlyPrimary: $namespace->primary}
16 | 				{include elementSummaryGroup, heading: 'Exceptions', elements: $namespace->exception, onlyPrimary: $namespace->primary}
17 | 				{include elementSummaryGroup, heading: 'Functions', elements: $namespace->function, onlyPrimary: $namespace->primary}
18 | 				{include elementSummaryGroup, heading: 'Namespaces', elements: $namespace->children, showDescription: false, onlyPrimary: $namespace->primary}
19 | 			{/block}
20 | 		{/embed}
21 | 	{/block}
22 | {/embed}
23 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/pages/sitemap.latte:
--------------------------------------------------------------------------------
 1 | {templateType ApiGen\Renderer\Latte\Template\SitemapTemplate}
 2 | {contentType 'application/xml'}
 3 | <?xml version="1.0" encoding="UTF-8"?>
 4 | <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
 5 | 	<url><loc>{indexUrl()}</loc></url>
 6 | 
 7 | 	{if treePageExists()}
 8 | 		<url><loc>{treeUrl()}</loc></url>
 9 | 	{/if}
10 | 
11 | 	{foreach $index->namespace as $namespace}
12 | 		{if namespacePageExists($namespace) && $namespace->primary}
13 | 			<url><loc>{namespaceUrl($namespace)}</loc></url>
14 | 		{/if}
15 | 	{/foreach}
16 | 
17 | 	{foreach $index->classLike as $classLike}
18 | 		{if classLikePageExists($classLike) && $classLike->primary}
19 | 			<url><loc>{classLikeUrl($classLike)}</loc></url>
20 | 		{/if}
21 | 	{/foreach}
22 | 
23 | 	{foreach $index->function as $function}
24 | 		{if functionPageExists($function) && $function->primary}
25 | 			<url><loc>{functionUrl($function)}</loc></url>
26 | 		{/if}
27 | 	{/foreach}
28 | 
29 | 	{foreach $index->files as $file}
30 | 		{if sourcePageExists($file) && $file->primary}
31 | 			<url><loc>{sourceUrl($file->path, null, null)}</loc></url>
32 | 		{/if}
33 | 	{/foreach}
34 | </urlset>
35 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/pages/source.latte:
--------------------------------------------------------------------------------
 1 | {templateType ApiGen\Renderer\Latte\Template\SourceTemplate}
 2 | {import '../blocks/@index.latte'}
 3 | 
 4 | {embed layout}
 5 | 	{block title}File {relativePath($path)}{/block}
 6 | 
 7 | 	{block content}
 8 | 		{include source, $path}
 9 | 	{/block}
10 | {/embed}
11 | 


--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/pages/tree.latte:
--------------------------------------------------------------------------------
 1 | {templateType ApiGen\Renderer\Latte\Template\TreeTemplate}
 2 | {import '../blocks/@index.latte'}
 3 | 
 4 | {embed layout}
 5 | 	{block title}Tree{/block}
 6 | 
 7 | 	{block content}
 8 | 		<h1>{include title}</h1>
 9 | 
10 | 		{ifset $index->classExtends['']}
11 | 			<h3>Classes</h3>
12 | 			{include classTreeList, $index->classExtends[''], $index->classExtends}
13 | 		{/ifset}
14 | 
15 | 		{if $index->interface}
16 | 			<h3>Interfaces</h3>
17 | 			{include classTreeList, $index->interface, []}
18 | 		{/if}
19 | 
20 | 		{if $index->trait}
21 | 			<h3>Traits</h3>
22 | 			{include classTreeList, $index->trait, []}
23 | 		{/if}
24 | 
25 | 		{if $index->enum}
26 | 			<h3>Enums</h3>
27 | 			{include classTreeList, $index->enum, []}
28 | 		{/if}
29 | 	{/block}
30 | {/embed}
31 | 


--------------------------------------------------------------------------------
/src/Renderer/SourceHighlighter.php:
--------------------------------------------------------------------------------
  1 | <?php declare(strict_types = 1);
  2 | 
  3 | namespace ApiGen\Renderer;
  4 | 
  5 | use Nette\Utils\FileSystem;
  6 | use PhpToken;
  7 | use Symfony\Component\Console\Style\OutputStyle;
  8 | 
  9 | use function count;
 10 | use function explode;
 11 | use function htmlspecialchars;
 12 | use function sprintf;
 13 | use function strlen;
 14 | use function strtolower;
 15 | use function strval;
 16 | use function substr_count;
 17 | 
 18 | use const TOKEN_PARSE;
 19 | use const T_ABSTRACT;
 20 | use const T_ARRAY;
 21 | use const T_AS;
 22 | use const T_BREAK;
 23 | use const T_CASE;
 24 | use const T_CATCH;
 25 | use const T_CLASS;
 26 | use const T_CLONE;
 27 | use const T_CLOSE_TAG;
 28 | use const T_COMMENT;
 29 | use const T_CONST;
 30 | use const T_CONSTANT_ENCAPSED_STRING;
 31 | use const T_CONTINUE;
 32 | use const T_DECLARE;
 33 | use const T_DEFAULT;
 34 | use const T_DNUMBER;
 35 | use const T_DO;
 36 | use const T_DOC_COMMENT;
 37 | use const T_ECHO;
 38 | use const T_ELSE;
 39 | use const T_ELSEIF;
 40 | use const T_EMPTY;
 41 | use const T_ENCAPSED_AND_WHITESPACE;
 42 | use const T_ENDDECLARE;
 43 | use const T_ENDFOR;
 44 | use const T_ENDFOREACH;
 45 | use const T_ENDIF;
 46 | use const T_ENDSWITCH;
 47 | use const T_ENDWHILE;
 48 | use const T_ENUM;
 49 | use const T_EVAL;
 50 | use const T_EXIT;
 51 | use const T_EXTENDS;
 52 | use const T_FINAL;
 53 | use const T_FINALLY;
 54 | use const T_FN;
 55 | use const T_FOR;
 56 | use const T_FOREACH;
 57 | use const T_FUNCTION;
 58 | use const T_GLOBAL;
 59 | use const T_GOTO;
 60 | use const T_HALT_COMPILER;
 61 | use const T_IF;
 62 | use const T_IMPLEMENTS;
 63 | use const T_INCLUDE;
 64 | use const T_INCLUDE_ONCE;
 65 | use const T_INLINE_HTML;
 66 | use const T_INSTANCEOF;
 67 | use const T_INSTEADOF;
 68 | use const T_INTERFACE;
 69 | use const T_ISSET;
 70 | use const T_LIST;
 71 | use const T_LNUMBER;
 72 | use const T_LOGICAL_AND;
 73 | use const T_LOGICAL_OR;
 74 | use const T_LOGICAL_XOR;
 75 | use const T_MATCH;
 76 | use const T_NAMESPACE;
 77 | use const T_NEW;
 78 | use const T_OPEN_TAG;
 79 | use const T_OPEN_TAG_WITH_ECHO;
 80 | use const T_PRINT;
 81 | use const T_PRIVATE;
 82 | use const T_PROTECTED;
 83 | use const T_PUBLIC;
 84 | use const T_READONLY;
 85 | use const T_REQUIRE;
 86 | use const T_REQUIRE_ONCE;
 87 | use const T_RETURN;
 88 | use const T_STATIC;
 89 | use const T_STRING;
 90 | use const T_SWITCH;
 91 | use const T_THROW;
 92 | use const T_TRAIT;
 93 | use const T_TRY;
 94 | use const T_UNSET;
 95 | use const T_USE;
 96 | use const T_VAR;
 97 | use const T_VARIABLE;
 98 | use const T_WHILE;
 99 | use const T_WHITESPACE;
100 | use const T_YIELD;
101 | use const T_YIELD_FROM;
102 | 
103 | 
104 | class SourceHighlighter
105 | {
106 | 	public const PHP_TAG = 'php-tag';
107 | 	public const PHP_KEYWORD = 'php-kw';
108 | 	public const PHP_NUMBER = 'php-num';
109 | 	public const PHP_STRING = 'php-str';
110 | 	public const PHP_VARIABLE = 'php-var';
111 | 	public const PHP_COMMENT = 'php-comment';
112 | 
113 | 	/** @var string[] indexed by [tokenId] */
114 | 	public array $tokenClass = [
115 | 		T_OPEN_TAG => self::PHP_TAG,
116 | 		T_OPEN_TAG_WITH_ECHO => self::PHP_TAG,
117 | 		T_CLOSE_TAG => self::PHP_TAG,
118 | 		T_INCLUDE => self::PHP_KEYWORD,
119 | 		T_INCLUDE_ONCE => self::PHP_KEYWORD,
120 | 		T_REQUIRE => self::PHP_KEYWORD,
121 | 		T_REQUIRE_ONCE => self::PHP_KEYWORD,
122 | 		T_LOGICAL_OR => self::PHP_KEYWORD,
123 | 		T_LOGICAL_XOR => self::PHP_KEYWORD,
124 | 		T_LOGICAL_AND => self::PHP_KEYWORD,
125 | 		T_PRINT => self::PHP_KEYWORD,
126 | 		T_YIELD => self::PHP_KEYWORD,
127 | 		T_YIELD_FROM => self::PHP_KEYWORD,
128 | 		T_INSTANCEOF => self::PHP_KEYWORD,
129 | 		T_NEW => self::PHP_KEYWORD,
130 | 		T_CLONE => self::PHP_KEYWORD,
131 | 		T_ELSEIF => self::PHP_KEYWORD,
132 | 		T_ELSE => self::PHP_KEYWORD,
133 | 		T_EVAL => self::PHP_KEYWORD,
134 | 		T_EXIT => self::PHP_KEYWORD,
135 | 		T_IF => self::PHP_KEYWORD,
136 | 		T_ENDIF => self::PHP_KEYWORD,
137 | 		T_ECHO => self::PHP_KEYWORD,
138 | 		T_DO => self::PHP_KEYWORD,
139 | 		T_WHILE => self::PHP_KEYWORD,
140 | 		T_ENDWHILE => self::PHP_KEYWORD,
141 | 		T_FOR => self::PHP_KEYWORD,
142 | 		T_ENDFOR => self::PHP_KEYWORD,
143 | 		T_FOREACH => self::PHP_KEYWORD,
144 | 		T_ENDFOREACH => self::PHP_KEYWORD,
145 | 		T_DECLARE => self::PHP_KEYWORD,
146 | 		T_ENDDECLARE => self::PHP_KEYWORD,
147 | 		T_AS => self::PHP_KEYWORD,
148 | 		T_SWITCH => self::PHP_KEYWORD,
149 | 		T_ENDSWITCH => self::PHP_KEYWORD,
150 | 		T_CASE => self::PHP_KEYWORD,
151 | 		T_DEFAULT => self::PHP_KEYWORD,
152 | 		T_BREAK => self::PHP_KEYWORD,
153 | 		T_CONTINUE => self::PHP_KEYWORD,
154 | 		T_GOTO => self::PHP_KEYWORD,
155 | 		T_FUNCTION => self::PHP_KEYWORD,
156 | 		T_FN => self::PHP_KEYWORD,
157 | 		T_CONST => self::PHP_KEYWORD,
158 | 		T_RETURN => self::PHP_KEYWORD,
159 | 		T_CATCH => self::PHP_KEYWORD,
160 | 		T_TRY => self::PHP_KEYWORD,
161 | 		T_FINALLY => self::PHP_KEYWORD,
162 | 		T_THROW => self::PHP_KEYWORD,
163 | 		T_USE => self::PHP_KEYWORD,
164 | 		T_INSTEADOF => self::PHP_KEYWORD,
165 | 		T_GLOBAL => self::PHP_KEYWORD,
166 | 		T_STATIC => self::PHP_KEYWORD,
167 | 		T_ABSTRACT => self::PHP_KEYWORD,
168 | 		T_FINAL => self::PHP_KEYWORD,
169 | 		T_PRIVATE => self::PHP_KEYWORD,
170 | 		T_PROTECTED => self::PHP_KEYWORD,
171 | 		T_PUBLIC => self::PHP_KEYWORD,
172 | 		T_VAR => self::PHP_KEYWORD,
173 | 		T_UNSET => self::PHP_KEYWORD,
174 | 		T_ISSET => self::PHP_KEYWORD,
175 | 		T_EMPTY => self::PHP_KEYWORD,
176 | 		T_HALT_COMPILER => self::PHP_KEYWORD,
177 | 		T_CLASS => self::PHP_KEYWORD,
178 | 		T_TRAIT => self::PHP_KEYWORD,
179 | 		T_INTERFACE => self::PHP_KEYWORD,
180 | 		T_EXTENDS => self::PHP_KEYWORD,
181 | 		T_IMPLEMENTS => self::PHP_KEYWORD,
182 | 		T_LIST => self::PHP_KEYWORD,
183 | 		T_ARRAY => self::PHP_KEYWORD,
184 | 		T_NAMESPACE => self::PHP_KEYWORD,
185 | 		T_ENUM => self::PHP_KEYWORD,
186 | 		T_READONLY => self::PHP_KEYWORD,
187 | 		T_MATCH => self::PHP_KEYWORD,
188 | 		T_LNUMBER => self::PHP_NUMBER,
189 | 		T_DNUMBER => self::PHP_NUMBER,
190 | 		T_CONSTANT_ENCAPSED_STRING => self::PHP_STRING,
191 | 		T_ENCAPSED_AND_WHITESPACE => self::PHP_STRING,
192 | 		T_VARIABLE => self::PHP_VARIABLE,
193 | 		T_COMMENT => self::PHP_COMMENT,
194 | 		T_DOC_COMMENT => self::PHP_COMMENT,
195 | 	];
196 | 
197 | 	/** @var string[] indexed by [identifierName] */
198 | 	public array $identifierClass = [
199 | 		'true' => self::PHP_KEYWORD,
200 | 		'false' => self::PHP_KEYWORD,
201 | 		'null' => self::PHP_KEYWORD,
202 | 	];
203 | 
204 | 
205 | 	public function __construct(
206 | 		protected OutputStyle $output,
207 | 	) {
208 | 	}
209 | 
210 | 
211 | 	public function highlight(string $path): string
212 | 	{
213 | 		$source = FileSystem::read($path);
214 | 		$align = strlen(strval(1 + substr_count($source, "\n")));
215 | 		$lineStart = "<tr id=\"%1\$d\" class=\"source-line\"><td><a class=\"source-lineNum\" href=\"#%1\$d\">%1\${$align}d: </a></td><td>";
216 | 		$lineEnd = '</td></tr>';
217 | 
218 | 		$line = 1;
219 | 		$out = sprintf($lineStart, $line);
220 | 
221 | 		foreach ($this->tokenize($path, $source) as $id => $text) {
222 | 			if ($text === "\n") {
223 | 				$out .= $lineEnd . sprintf($lineStart, ++$line);
224 | 
225 | 			} else {
226 | 				$html = htmlspecialchars($text);
227 | 				$class = $this->tokenClass[$id] ?? ($id === T_STRING ? $this->identifierClass[strtolower($text)] ?? null : null);
228 | 				$out .= $class ? "<span class=\"{$class}\">{$html}</span>" : $html;
229 | 			}
230 | 		}
231 | 
232 | 		return $out . $lineEnd;
233 | 	}
234 | 
235 | 
236 | 	/**
237 | 	 * @return iterable<int, string>
238 | 	 */
239 | 	protected function tokenize(string $path, string $source): iterable
240 | 	{
241 | 		try {
242 | 			$tokens = PhpToken::tokenize($source, TOKEN_PARSE);
243 | 
244 | 		} catch (\ParseError $e) {
245 | 			$this->output->newLine();
246 | 			$this->output->warning(sprintf("Parse error in %s:%d\n%s", $path, $e->getLine(), $e->getMessage()));
247 | 			$tokens = [new PhpToken(T_INLINE_HTML, $source)];
248 | 		}
249 | 
250 | 		foreach ($tokens as $token) {
251 | 			$lines = explode("\n", $token->text);
252 | 			$lastLine = count($lines) - 1;
253 | 
254 | 			foreach ($lines as $i => $line) {
255 | 				yield $token->id => $line;
256 | 
257 | 				if ($i !== $lastLine) {
258 | 					yield T_WHITESPACE => "\n";
259 | 				}
260 | 			}
261 | 		}
262 | 	}
263 | }
264 | 


--------------------------------------------------------------------------------
/src/Renderer/UrlGenerator.php:
--------------------------------------------------------------------------------
  1 | <?php declare(strict_types = 1);
  2 | 
  3 | namespace ApiGen\Renderer;
  4 | 
  5 | use ApiGen\Index\NamespaceIndex;
  6 | use ApiGen\Info\AliasInfo;
  7 | use ApiGen\Info\ClassLikeInfo;
  8 | use ApiGen\Info\ConstantInfo;
  9 | use ApiGen\Info\EnumCaseInfo;
 10 | use ApiGen\Info\FunctionInfo;
 11 | use ApiGen\Info\MemberInfo;
 12 | use ApiGen\Info\MethodInfo;
 13 | use ApiGen\Info\ParameterInfo;
 14 | use ApiGen\Info\PropertyInfo;
 15 | 
 16 | use function assert;
 17 | use function get_debug_type;
 18 | use function sprintf;
 19 | use function str_starts_with;
 20 | use function strlen;
 21 | use function strrpos;
 22 | use function strtr;
 23 | use function substr;
 24 | 
 25 | use const DIRECTORY_SEPARATOR;
 26 | 
 27 | 
 28 | class UrlGenerator
 29 | {
 30 | 	public function __construct(
 31 | 		protected string $baseDir,
 32 | 		protected string $baseUrl,
 33 | 	) {
 34 | 	}
 35 | 
 36 | 
 37 | 	public function getRelativePath(string $path): string
 38 | 	{
 39 | 		if (str_starts_with($path, $this->baseDir)) {
 40 | 			return substr($path, strlen($this->baseDir) + 1);
 41 | 
 42 | 		} else {
 43 | 			throw new \LogicException("{$path} does not start with {$this->baseDir}");
 44 | 		}
 45 | 	}
 46 | 
 47 | 
 48 | 	public function getAssetUrl(string $name): string
 49 | 	{
 50 | 		return $this->baseUrl . $this->getAssetPath($name);
 51 | 	}
 52 | 
 53 | 
 54 | 	public function getAssetPath(string $name): string
 55 | 	{
 56 | 		return "assets/$name";
 57 | 	}
 58 | 
 59 | 
 60 | 	public function getIndexUrl(): string
 61 | 	{
 62 | 		return $this->baseUrl . $this->getIndexPath();
 63 | 	}
 64 | 
 65 | 
 66 | 	public function getIndexPath(): string
 67 | 	{
 68 | 		return 'index.html';
 69 | 	}
 70 | 
 71 | 
 72 | 	public function getTreeUrl(): string
 73 | 	{
 74 | 		return $this->baseUrl . $this->getTreePath();
 75 | 	}
 76 | 
 77 | 
 78 | 	public function getTreePath(): string
 79 | 	{
 80 | 		return 'tree.html';
 81 | 	}
 82 | 
 83 | 
 84 | 	public function getSitemapPath(): string
 85 | 	{
 86 | 		return 'sitemap.xml';
 87 | 	}
 88 | 
 89 | 
 90 | 	public function getSitemapUrl(): string
 91 | 	{
 92 | 		return $this->baseUrl . $this->getSitemapPath();
 93 | 	}
 94 | 
 95 | 
 96 | 	public function getNamespaceUrl(NamespaceIndex $namespace): string
 97 | 	{
 98 | 		return $this->baseUrl . $this->getNamespacePath($namespace);
 99 | 	}
100 | 
101 | 
102 | 	public function getNamespacePath(NamespaceIndex $namespace): string
103 | 	{
104 | 		return 'namespace-' . strtr($namespace->name->full ?: 'none', '\\', '.') . '.html';
105 | 	}
106 | 
107 | 
108 | 	public function getClassLikeUrl(ClassLikeInfo $classLike): string
109 | 	{
110 | 		return $this->baseUrl . $this->getClassLikePath($classLike);
111 | 	}
112 | 
113 | 
114 | 	public function getClassLikePath(ClassLikeInfo $classLike): string
115 | 	{
116 | 		return strtr($classLike->name->full, '\\', '.') . '.html';
117 | 	}
118 | 
119 | 
120 | 	public function getClassLikeSourceUrl(ClassLikeInfo $classLike): string
121 | 	{
122 | 		assert($classLike->file !== null);
123 | 		return $this->getSourceUrl($classLike->file, $classLike->startLine, null); // intentionally not passing endLine
124 | 	}
125 | 
126 | 
127 | 	public function getMemberUrl(ClassLikeInfo $classLike, MemberInfo $member): string
128 | 	{
129 | 		return $this->getClassLikeUrl($classLike) . '#' . $this->getMemberAnchor($member);
130 | 	}
131 | 
132 | 
133 | 	public function getMemberAnchor(MemberInfo $member): string
134 | 	{
135 | 		if ($member instanceof ConstantInfo || $member instanceof EnumCaseInfo) {
136 | 			return $member->name;
137 | 
138 | 		} elseif ($member instanceof PropertyInfo) {
139 | 			return '
#39; . $member->name;
140 | 
141 | 		} elseif ($member instanceof MethodInfo) {
142 | 			return '_' . $member->name;
143 | 
144 | 		} else {
145 | 			throw new \LogicException(sprintf('Unexpected member type %s', get_debug_type($member)));
146 | 		}
147 | 	}
148 | 
149 | 
150 | 	public function getMemberSourceUrl(ClassLikeInfo $classLike, MemberInfo $member): string
151 | 	{
152 | 		assert($classLike->file !== null);
153 | 		return $this->getSourceUrl($classLike->file, $member->startLine, $member->endLine);
154 | 	}
155 | 
156 | 
157 | 	public function getAliasUrl(ClassLikeInfo $classLike, AliasInfo $alias): string
158 | 	{
159 | 		return $this->getClassLikeUrl($classLike) . '#' . $this->getAliasAnchor($alias);
160 | 	}
161 | 
162 | 
163 | 	public function getAliasAnchor(AliasInfo $alias): string
164 | 	{
165 | 		return '~' . $alias->name;
166 | 	}
167 | 
168 | 
169 | 	public function getAliasSourceUrl(ClassLikeInfo $classLike, AliasInfo $alias): string
170 | 	{
171 | 		assert($classLike->file !== null);
172 | 		return $this->getSourceUrl($classLike->file, $alias->startLine, $alias->endLine);
173 | 	}
174 | 
175 | 
176 | 	public function getFunctionUrl(FunctionInfo $function): string
177 | 	{
178 | 		return $this->baseUrl . $this->getFunctionPath($function);
179 | 	}
180 | 
181 | 
182 | 	public function getFunctionPath(FunctionInfo $function): string
183 | 	{
184 | 		return 'function-' . strtr($function->name->full, '\\', '.') . '.html';
185 | 	}
186 | 
187 | 
188 | 	public function getFunctionSourceUrl(FunctionInfo $function): string
189 | 	{
190 | 		assert($function->file !== null);
191 | 		return $this->getSourceUrl($function->file, $function->startLine, $function->endLine);
192 | 	}
193 | 
194 | 
195 | 	public function getParameterAnchor(ParameterInfo $parameter): string
196 | 	{
197 | 		return '
#39; . $parameter->name;
198 | 	}
199 | 
200 | 
201 | 	public function getSourceUrl(string $path, ?int $startLine, ?int $endLine): string
202 | 	{
203 | 		if ($startLine === null) {
204 | 			$fragment = '';
205 | 
206 | 		} elseif ($endLine === null || $endLine === $startLine) {
207 | 			$fragment = "#$startLine";
208 | 
209 | 		} else {
210 | 			$fragment = "#$startLine-$endLine";
211 | 		}
212 | 
213 | 		return $this->baseUrl . $this->getSourcePath($path) . $fragment;
214 | 	}
215 | 
216 | 
217 | 	public function getSourcePath(string $path): string
218 | 	{
219 | 		$relativePath = $this->getRelativePath($path);
220 | 		$relativePathWithoutExtension = substr($relativePath, 0, strrpos($relativePath, '.') ?: null);
221 | 		return 'source-' . strtr($relativePathWithoutExtension, DIRECTORY_SEPARATOR, '.') . '.html';
222 | 	}
223 | }
224 | 


--------------------------------------------------------------------------------
/src/Scheduler.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen;
 4 | 
 5 | use ApiGen\Task\Task;
 6 | 
 7 | 
 8 | /**
 9 |  * @template TTask of Task
10 |  * @template TResult
11 |  */
12 | interface Scheduler
13 | {
14 | 	/**
15 | 	 * @param  TTask $task
16 | 	 */
17 | 	public function schedule(Task $task): void;
18 | 
19 | 
20 | 	/**
21 | 	 * @return iterable<TTask, TResult>
22 | 	 */
23 | 	public function process(): iterable;
24 | }
25 | 


--------------------------------------------------------------------------------
/src/Scheduler/ExecScheduler.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Scheduler;
 4 | 
 5 | use ApiGen\Helpers;
 6 | use ApiGen\Task\Task;
 7 | use ApiGen\Task\TaskHandler;
 8 | use ApiGen\Task\TaskHandlerFactory;
 9 | use Composer\Autoload\ClassLoader;
10 | use Nette\DI\Container;
11 | 
12 | use function dirname;
13 | use function proc_close;
14 | use function proc_open;
15 | use function sprintf;
16 | use function var_export;
17 | 
18 | use const PHP_BINARY;
19 | use const PHP_OS_FAMILY;
20 | use const STDERR;
21 | 
22 | 
23 | /**
24 |  * @template TTask of Task
25 |  * @template TResult
26 |  * @template TContext
27 |  * @extends  WorkerScheduler<TTask, TResult>
28 |  */
29 | class ExecScheduler extends WorkerScheduler
30 | {
31 | 	/** @var resource[] $workers indexed by [workerId] */
32 | 	protected array $workers = [];
33 | 
34 | 
35 | 	/**
36 | 	 * @param  class-string<Container>                                                 $containerClass
37 | 	 * @param  array<string, mixed>                                                    $containerParameters
38 | 	 * @param  class-string<TaskHandlerFactory<TContext, TaskHandler<TTask, TResult>>> $handlerFactoryClass
39 | 	 * @param  TContext                                                                $context
40 | 	 */
41 | 	public function __construct(
42 | 		protected string $containerClass,
43 | 		protected array $containerParameters,
44 | 		protected string $handlerFactoryClass,
45 | 		protected mixed $context,
46 | 		int $workerCount,
47 | 	) {
48 | 		parent::__construct($workerCount);
49 | 	}
50 | 
51 | 
52 | 	protected function start(): void
53 | 	{
54 | 		$command = [
55 | 			PHP_BINARY,
56 | 			'--run',
57 | 			sprintf('require %s;', var_export(__DIR__ . '/worker.php', return: true)),
58 | 			'--',
59 | 			dirname(Helpers::classLikePath(ClassLoader::class), 2) . '/autoload.php',
60 | 			Helpers::classLikePath($this->containerClass),
61 | 			$this->containerClass,
62 | 			$this->handlerFactoryClass,
63 | 		];
64 | 
65 | 		$descriptors = [
66 | 			PHP_OS_FAMILY === 'Windows' ? ['socket'] : ['pipe', 'r'],
67 | 			PHP_OS_FAMILY === 'Windows' ? ['socket'] : ['pipe', 'w'],
68 | 			STDERR,
69 | 		];
70 | 
71 | 		for ($workerId = 0; $workerId < $this->workerCount; $workerId++) {
72 | 			$workerProcess = proc_open($command, $descriptors, $pipes);
73 | 
74 | 			if ($workerProcess === false) {
75 | 				throw new \RuntimeException('Failed to start worker process, try running ApiGen with --workers 1');
76 | 			}
77 | 
78 | 			$this->workers[$workerId] = $workerProcess;
79 | 			$this->workerWritableStreams[$workerId] = $pipes[0];
80 | 			$this->workerReadableStreams[$workerId] = $pipes[1];
81 | 			self::writeMessage($this->workerWritableStreams[$workerId], $this->containerParameters);
82 | 			self::writeMessage($this->workerWritableStreams[$workerId], $this->context);
83 | 		}
84 | 	}
85 | 
86 | 
87 | 	protected function stop(): void
88 | 	{
89 | 		foreach ($this->workers as $worker) {
90 | 			if (proc_close($worker) !== 0) {
91 | 				throw new \RuntimeException('Worker process crashed, try running ApiGen with --workers 1');
92 | 			}
93 | 		}
94 | 	}
95 | }
96 | 


--------------------------------------------------------------------------------
/src/Scheduler/ForkScheduler.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Scheduler;
 4 | 
 5 | use ApiGen\Task\Task;
 6 | use ApiGen\Task\TaskHandler;
 7 | 
 8 | use function fclose;
 9 | use function pcntl_fork;
10 | use function pcntl_waitpid;
11 | use function pcntl_wexitstatus;
12 | use function pcntl_wifexited;
13 | use function pcntl_wifsignaled;
14 | use function pcntl_wtermsig;
15 | use function stream_socket_pair;
16 | 
17 | use const STREAM_IPPROTO_IP;
18 | use const STREAM_PF_UNIX;
19 | use const STREAM_SOCK_STREAM;
20 | 
21 | 
22 | /**
23 |  * @template TTask of Task
24 |  * @template TResult
25 |  * @extends  WorkerScheduler<TTask, TResult>
26 |  */
27 | class ForkScheduler extends WorkerScheduler
28 | {
29 | 	/** @var int[] $workers indexed by [workerId] */
30 | 	protected array $workers = [];
31 | 
32 | 
33 | 	/**
34 | 	 * @param TaskHandler<TTask, TResult> $handler
35 | 	 */
36 | 	public function __construct(
37 | 		protected TaskHandler $handler,
38 | 		int $workerCount,
39 | 	) {
40 | 		parent::__construct($workerCount);
41 | 	}
42 | 
43 | 
44 | 	protected function start(): void
45 | 	{
46 | 		for ($workerId = 0; $workerId < $this->workerCount; $workerId++) {
47 | 			$pair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
48 | 
49 | 			if ($pair === false) {
50 | 				throw new \RuntimeException('Failed to create socket pair, try running ApiGen with --workers 1');
51 | 			}
52 | 
53 | 			$pid = pcntl_fork();
54 | 
55 | 			if ($pid < 0) {
56 | 				throw new \RuntimeException('Failed to fork process, try running ApiGen with --workers 1');
57 | 
58 | 			} elseif ($pid === 0) {
59 | 				fclose($pair[0]);
60 | 				self::workerLoop($this->handler, $pair[1], $pair[1]);
61 | 				exit(0);
62 | 
63 | 			} else {
64 | 				fclose($pair[1]);
65 | 				$this->workers[$workerId] = $pid;
66 | 				$this->workerReadableStreams[$workerId] = $pair[0];
67 | 				$this->workerWritableStreams[$workerId] = $pair[0];
68 | 			}
69 | 		}
70 | 	}
71 | 
72 | 
73 | 	protected function stop(): void
74 | 	{
75 | 		foreach ($this->workerWritableStreams as $stream) {
76 | 			fclose($stream);
77 | 		}
78 | 
79 | 		foreach ($this->workers as $pid) {
80 | 			pcntl_waitpid($pid, $status);
81 | 
82 | 			if (pcntl_wifexited($status)) {
83 | 				if (($exitCode = pcntl_wexitstatus($status)) !== 0) {
84 | 					throw new \RuntimeException("Worker with PID $pid exited with code $exitCode, try running ApiGen with --workers 1");
85 | 				}
86 | 
87 | 			} elseif (pcntl_wifsignaled($status)) {
88 | 				$signal = pcntl_wtermsig($status);
89 | 				throw new \RuntimeException("Worker with PID $pid was killed by signal $signal, try running ApiGen with --workers 1");
90 | 
91 | 			} else {
92 | 				throw new \LogicException('Invalid worker state');
93 | 			}
94 | 		}
95 | 	}
96 | }
97 | 


--------------------------------------------------------------------------------
/src/Scheduler/SchedulerFactory.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Scheduler;
 4 | 
 5 | use ApiGen\Scheduler;
 6 | use ApiGen\Task\Task;
 7 | use ApiGen\Task\TaskHandler;
 8 | use ApiGen\Task\TaskHandlerFactory;
 9 | use Nette\DI\Container;
10 | 
11 | use function extension_loaded;
12 | use function function_exists;
13 | 
14 | use const PHP_SAPI;
15 | 
16 | 
17 | class SchedulerFactory
18 | {
19 | 	public function __construct(
20 | 		protected Container $container,
21 | 		protected int $workerCount,
22 | 	) {
23 | 	}
24 | 
25 | 
26 | 	/**
27 | 	 * @template TTask of Task
28 | 	 * @template TResult
29 | 	 * @template TContext
30 | 	 *
31 | 	 * @param    class-string<TaskHandlerFactory<TContext, TaskHandler<TTask, TResult>>> $handlerFactoryType
32 | 	 * @param    TContext                                                                $context
33 | 	 * @return   Scheduler<TTask, TResult>
34 | 	 */
35 | 	public function create(string $handlerFactoryType, mixed $context): Scheduler
36 | 	{
37 | 		if ($this->workerCount > 1 && PHP_SAPI === 'cli') {
38 | 			if (extension_loaded('pcntl')) {
39 | 				$handler = $this->createHandler($handlerFactoryType, $context);
40 | 				return new ForkScheduler($handler, $this->workerCount);
41 | 
42 | 			} elseif (function_exists('proc_open')) {
43 | 				return new ExecScheduler($this->container::class, $this->container->getParameters(), $handlerFactoryType, $context, $this->workerCount);
44 | 			}
45 | 		}
46 | 
47 | 		$handler = $this->createHandler($handlerFactoryType, $context);
48 | 		return new SimpleScheduler($handler);
49 | 	}
50 | 
51 | 
52 | 	/**
53 | 	 * @template TTask of Task
54 | 	 * @template TResult
55 | 	 * @template TContext
56 | 	 *
57 | 	 * @param    class-string<TaskHandlerFactory<TContext, TaskHandler<TTask, TResult>>> $handlerFactoryType
58 | 	 * @param    TContext                                                                $context
59 | 	 * @return   TaskHandler<TTask, TResult>
60 | 	 */
61 | 	private function createHandler(string $handlerFactoryType, mixed $context): TaskHandler
62 | 	{
63 | 		$factory = $this->container->getByType($handlerFactoryType);
64 | 		return $factory->create($context);
65 | 	}
66 | }
67 | 


--------------------------------------------------------------------------------
/src/Scheduler/SimpleScheduler.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Scheduler;
 4 | 
 5 | use ApiGen\Scheduler;
 6 | use ApiGen\Task\Task;
 7 | use ApiGen\Task\TaskHandler;
 8 | use SplQueue;
 9 | 
10 | 
11 | /**
12 |  * @template   TTask of Task
13 |  * @template   TResult
14 |  * @implements Scheduler<TTask, TResult>
15 |  */
16 | class SimpleScheduler implements Scheduler
17 | {
18 | 	/** @var SplQueue<TTask>  */
19 | 	protected SplQueue $tasks;
20 | 
21 | 
22 | 	/**
23 | 	 * @param TaskHandler<TTask, TResult> $handler
24 | 	 */
25 | 	public function __construct(
26 | 		protected TaskHandler $handler,
27 | 	) {
28 | 		$this->tasks = new SplQueue();
29 | 	}
30 | 
31 | 
32 | 	/**
33 | 	 * @param  TTask $task
34 | 	 */
35 | 	public function schedule(Task $task): void
36 | 	{
37 | 		$this->tasks->enqueue($task);
38 | 	}
39 | 
40 | 
41 | 	/**
42 | 	 * @return iterable<TTask, TResult>
43 | 	 */
44 | 	public function process(): iterable
45 | 	{
46 | 		while (!$this->tasks->isEmpty()) {
47 | 			$task = $this->tasks->dequeue();
48 | 			$result = $this->handler->handle($task);
49 | 			yield $task => $result;
50 | 		}
51 | 	}
52 | }
53 | 


--------------------------------------------------------------------------------
/src/Scheduler/WorkerScheduler.php:
--------------------------------------------------------------------------------
  1 | <?php declare(strict_types = 1);
  2 | 
  3 | namespace ApiGen\Scheduler;
  4 | 
  5 | use ApiGen\Scheduler;
  6 | use ApiGen\Task\Task;
  7 | use ApiGen\Task\TaskHandler;
  8 | use SplQueue;
  9 | 
 10 | use function array_fill_keys;
 11 | use function array_key_first;
 12 | use function array_keys;
 13 | use function base64_decode;
 14 | use function base64_encode;
 15 | use function count;
 16 | use function extension_loaded;
 17 | use function fgets;
 18 | use function fwrite;
 19 | use function igbinary_serialize;
 20 | use function igbinary_unserialize;
 21 | use function serialize;
 22 | use function stream_select;
 23 | use function strlen;
 24 | use function unserialize;
 25 | 
 26 | 
 27 | /**
 28 |  * @template   TTask of Task
 29 |  * @template   TResult
 30 |  * @implements Scheduler<TTask, TResult>
 31 |  */
 32 | abstract class WorkerScheduler implements Scheduler
 33 | {
 34 | 	protected const WORKER_CAPACITY_LIMIT = 8;
 35 | 
 36 | 	/** @var SplQueue<TTask> queue of tasks which needs to be sent to workers */
 37 | 	protected SplQueue $tasks;
 38 | 
 39 | 	/** @var int total number of pending tasks (including those already sent to workers) */
 40 | 	protected int $pendingTaskCount = 0;
 41 | 
 42 | 	/** @var resource[] indexed by [workerId] */
 43 | 	protected array $workerReadableStreams = [];
 44 | 
 45 | 	/** @var resource[] indexed by [workerId] */
 46 | 	protected array $workerWritableStreams = [];
 47 | 
 48 | 
 49 | 	public function __construct(
 50 | 		protected int $workerCount,
 51 | 	) {
 52 | 		$this->tasks = new SplQueue();
 53 | 	}
 54 | 
 55 | 
 56 | 	/**
 57 | 	 * @param resource $stream
 58 | 	 */
 59 | 	public static function writeMessage($stream, mixed $message): void
 60 | 	{
 61 | 		$serialized = extension_loaded('igbinary')
 62 | 			? igbinary_serialize($message) ?? throw new \LogicException('Failed to serialize message.')
 63 | 			: serialize($message);
 64 | 
 65 | 		$line = base64_encode($serialized) . "\n";
 66 | 
 67 | 		if (fwrite($stream, $line) !== strlen($line)) {
 68 | 			throw new \RuntimeException('Failed to write message to stream.');
 69 | 		}
 70 | 	}
 71 | 
 72 | 
 73 | 	/**
 74 | 	 * @param resource $stream
 75 | 	 */
 76 | 	public static function readMessage($stream): mixed
 77 | 	{
 78 | 		$line = fgets($stream);
 79 | 
 80 | 		if ($line === false) {
 81 | 			return null;
 82 | 		}
 83 | 
 84 | 		$serialized = base64_decode($line, strict: true);
 85 | 
 86 | 		if ($serialized === false) {
 87 | 			throw new \RuntimeException('Failed to decode message.');
 88 | 		}
 89 | 
 90 | 		return extension_loaded('igbinary')
 91 | 			? igbinary_unserialize($serialized)
 92 | 			: unserialize($serialized);
 93 | 	}
 94 | 
 95 | 
 96 | 	/**
 97 | 	 * @template T2 of Task
 98 | 	 * @template R2
 99 | 	 *
100 | 	 * @param TaskHandler<T2, R2> $handler
101 | 	 * @param resource            $inputStream
102 | 	 * @param resource            $outputStream
103 | 	 */
104 | 	public static function workerLoop(TaskHandler $handler, $inputStream, $outputStream): void
105 | 	{
106 | 		while (($task = self::readMessage($inputStream)) !== null) {
107 | 			$result = $handler->handle($task);
108 | 			self::writeMessage($outputStream, [$task, $result]);
109 | 		}
110 | 	}
111 | 
112 | 
113 | 	/**
114 | 	 * @param  TTask $task
115 | 	 */
116 | 	public function schedule(Task $task): void
117 | 	{
118 | 		$this->tasks->enqueue($task);
119 | 		$this->pendingTaskCount++;
120 | 	}
121 | 
122 | 
123 | 	/**
124 | 	 * @return iterable<TTask, TResult>
125 | 	 */
126 | 	public function process(): iterable
127 | 	{
128 | 		try {
129 | 			$this->start();
130 | 
131 | 			$idleWorkers = array_fill_keys(array_keys($this->workerWritableStreams), self::WORKER_CAPACITY_LIMIT);
132 | 
133 | 			while ($this->pendingTaskCount > 0) {
134 | 				while (count($idleWorkers) > 0 && !$this->tasks->isEmpty()) {
135 | 					$idleWorkerId = array_key_first($idleWorkers);
136 | 					$idleWorkerCapacity = $idleWorkers[$idleWorkerId];
137 | 					self::writeMessage($this->workerWritableStreams[$idleWorkerId], $this->tasks->dequeue());
138 | 					unset($idleWorkers[$idleWorkerId]);
139 | 
140 | 					if ($idleWorkerCapacity > 1) {
141 | 						$idleWorkers[$idleWorkerId] = $idleWorkerCapacity - 1;
142 | 					}
143 | 				}
144 | 
145 | 				$readable = $this->workerReadableStreams;
146 | 				$writable = null;
147 | 				$except = null;
148 | 				$changedCount = stream_select($readable, $writable, $except, null);
149 | 
150 | 				if ($changedCount === false || $changedCount === 0) {
151 | 					throw new \RuntimeException('stream_select() failed.');
152 | 				}
153 | 
154 | 				foreach ($readable as $workerId => $stream) {
155 | 					[$task, $result] = self::readMessage($stream) ?? throw new \RuntimeException('Failed to read message from worker.');
156 | 					$idleWorkers[$workerId] = ($idleWorkers[$workerId] ?? 0) + 1;
157 | 					$this->pendingTaskCount--;
158 | 					yield $task => $result;
159 | 				}
160 | 			}
161 | 
162 | 		} finally {
163 | 			$this->pendingTaskCount = 0;
164 | 			$this->stop();
165 | 		}
166 | 	}
167 | 
168 | 
169 | 	abstract protected function start(): void;
170 | 
171 | 
172 | 	abstract protected function stop(): void;
173 | }
174 | 


--------------------------------------------------------------------------------
/src/Scheduler/worker.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Scheduler;
 4 | 
 5 | use ApiGen\Bootstrap;
 6 | use ApiGen\Task\Task;
 7 | use ApiGen\Task\TaskHandler;
 8 | use ApiGen\Task\TaskHandlerFactory;
 9 | use Nette\DI\Container;
10 | use Symfony\Component\Console\Input\ArgvInput;
11 | use Symfony\Component\Console\Output\StreamOutput;
12 | use Symfony\Component\Console\Style\SymfonyStyle;
13 | 
14 | use function ini_set;
15 | 
16 | use const STDERR;
17 | use const STDIN;
18 | use const STDOUT;
19 | 
20 | 
21 | if (count($argv) !== 5) {
22 | 	throw new \RuntimeException('Invalid number of arguments.');
23 | }
24 | 
25 | /** @var string $autoloadPath */
26 | $autoloadPath = $argv[1];
27 | 
28 | /** @var string $containerClassPath */
29 | $containerClassPath = $argv[2];
30 | 
31 | /** @var class-string<Container> $containerClassName */
32 | $containerClassName = $argv[3];
33 | 
34 | /** @var class-string<TaskHandlerFactory<mixed, TaskHandler<Task, mixed>>> $handlerFactoryClassName */
35 | $handlerFactoryClassName = $argv[4];
36 | 
37 | require $autoloadPath;
38 | Bootstrap::configureErrorHandling();
39 | 
40 | require $containerClassPath;
41 | 
42 | $containerParameters = WorkerScheduler::readMessage(STDIN);
43 | $container = new $containerClassName($containerParameters);
44 | $container->addService('symfonyConsole.output', new SymfonyStyle(new ArgvInput(), new StreamOutput(STDERR)));
45 | ini_set('memory_limit', $container->getParameter('memoryLimit'));
46 | 
47 | $context = WorkerScheduler::readMessage(STDIN);
48 | $handlerFactory = $container->getByType($handlerFactoryClassName);
49 | $handler = $handlerFactory->create($context);
50 | WorkerScheduler::workerLoop($handler, STDIN, STDOUT);
51 | 


--------------------------------------------------------------------------------
/src/Task/Task.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 | 
3 | namespace ApiGen\Task;
4 | 
5 | 
6 | interface Task
7 | {
8 | }
9 | 


--------------------------------------------------------------------------------
/src/Task/TaskHandler.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Task;
 4 | 
 5 | 
 6 | /**
 7 |  * @template-contravariant T of Task
 8 |  * @template-covariant     R
 9 |  */
10 | interface TaskHandler
11 | {
12 | 	/**
13 | 	 * @param  T $task
14 | 	 * @return R
15 | 	 */
16 | 	public function handle(Task $task): mixed;
17 | }
18 | 


--------------------------------------------------------------------------------
/src/Task/TaskHandlerFactory.php:
--------------------------------------------------------------------------------
 1 | <?php declare(strict_types = 1);
 2 | 
 3 | namespace ApiGen\Task;
 4 | 
 5 | 
 6 | /**
 7 |  * @template           TContext
 8 |  * @template-covariant THandler of TaskHandler<never, mixed>
 9 |  */
10 | interface TaskHandlerFactory
11 | {
12 | 	/**
13 | 	 * @param  TContext $context
14 | 	 * @return THandler
15 | 	 */
16 | 	public function create(mixed $context): TaskHandler;
17 | }
18 | 


--------------------------------------------------------------------------------