Passed
Branch master (0511c6)
by Caen
06:24 queued 03:11
created

documentMethod()   F

Complexity

Conditions 29
Paths > 20000

Size

Total Lines 175
Code Lines 116

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 29
eloc 116
nc 110592
nop 5
dl 0
loc 175
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @internal
7
 */
8
9
use Hyde\Pages\Concerns\HydePage;
10
use Illuminate\Support\Str;
11
12
require_once __DIR__.'/../../../vendor/autoload.php';
13
14
echo "\033[32mHydePHP method DocGen\033[0m\n\n";
15
16
$basePath = realpath(__DIR__.'/../../../docs/_data/partials/hyde-pages-api');
17
18
$matrix = [
19
    [
20
        'class' => HydePage::class,
21
        'instanceVariableName' => '$page',
22
        'outputFile' => "$basePath/hyde-page-methods.md",
23
24
    ],
25
    [
26
        'class' => \Hyde\Pages\Concerns\BaseMarkdownPage::class,
27
        'instanceVariableName' => '$page',
28
        'outputFile' => "$basePath/base-markdown-page-methods.md",
29
    ],
30
    [
31
        'class' => \Hyde\Framework\Concerns\InteractsWithFrontMatter::class,
32
        'instanceVariableName' => '$page',
33
        'outputFile' => "$basePath/interacts-with-front-matter-methods.md",
34
    ],
35
    [
36
        'class' => \Hyde\Pages\InMemoryPage::class,
37
        'instanceVariableName' => '$page',
38
        'outputFile' => "$basePath/in-memory-page-methods.md",
39
    ],
40
    [
41
        'class' => \Hyde\Pages\BladePage::class,
42
        'instanceVariableName' => '$page',
43
        'outputFile' => "$basePath/blade-page-methods.md",
44
    ],
45
    [
46
        'class' => \Hyde\Pages\MarkdownPage::class,
47
        'instanceVariableName' => '$page',
48
        'outputFile' => "$basePath/markdown-page-methods.md",
49
    ],
50
    [
51
        'class' => \Hyde\Pages\MarkdownPost::class,
52
        'instanceVariableName' => '$page',
53
        'outputFile' => "$basePath/markdown-post-methods.md",
54
    ],
55
    [
56
        'class' => \Hyde\Pages\DocumentationPage::class,
57
        'instanceVariableName' => '$page',
58
        'outputFile' => "$basePath/documentation-page-methods.md",
59
    ],
60
    [
61
        'class' => \Hyde\Pages\HtmlPage::class,
62
        'instanceVariableName' => '$page',
63
        'outputFile' => "$basePath/html-page-methods.md",
64
    ],
65
    [
66
        'class' => \Hyde\Foundation\HydeKernel::class,
67
        'instanceVariableName' => '$hyde',
68
        'outputFile' => "$basePath/hyde-kernel-base-methods.md",
69
        'facadeName' => 'Hyde',
70
    ],
71
    // HandlesFoundationCollections
72
    [
73
        'class' => \Hyde\Foundation\Concerns\HandlesFoundationCollections::class,
74
        'instanceVariableName' => '$hyde',
75
        'outputFile' => "$basePath/hyde-kernel-foundation-methods.md",
76
        'facadeName' => 'Hyde',
77
    ],
78
    // ImplementsStringHelpers
79
    [
80
        'class' => \Hyde\Foundation\Concerns\ImplementsStringHelpers::class,
81
        'instanceVariableName' => '$hyde',
82
        'outputFile' => "$basePath/hyde-kernel-string-methods.md",
83
        'facadeName' => 'Hyde',
84
    ],
85
    // ForwardsHyperlinks
86
    [
87
        'class' => \Hyde\Foundation\Concerns\ForwardsHyperlinks::class,
88
        'instanceVariableName' => '$hyde',
89
        'outputFile' => "$basePath/hyde-kernel-hyperlink-methods.md",
90
        'facadeName' => 'Hyde',
91
    ],
92
    // ForwardsFilesystem
93
    [
94
        'class' => \Hyde\Foundation\Concerns\ForwardsFilesystem::class,
95
        'instanceVariableName' => '$hyde',
96
        'outputFile' => "$basePath/hyde-kernel-filesystem-methods.md",
97
        'facadeName' => 'Hyde',
98
    ],
99
    // ManagesHydeKernel
100
    [
101
        'class' => \Hyde\Foundation\Concerns\ManagesHydeKernel::class,
102
        'instanceVariableName' => '$hyde',
103
        'outputFile' => "$basePath/hyde-kernel-kernel-methods.md",
104
        'facadeName' => 'Hyde',
105
    ],
106
    // ManagesExtensions
107
    [
108
        'class' => \Hyde\Foundation\Concerns\ManagesExtensions::class,
109
        'instanceVariableName' => '$hyde',
110
        'outputFile' => "$basePath/hyde-kernel-extensions-methods.md",
111
        'facadeName' => 'Hyde',
112
    ],
113
    // ManagesViewData
114
    [
115
        'class' => \Hyde\Foundation\Concerns\ManagesViewData::class,
116
        'instanceVariableName' => '$hyde',
117
        'outputFile' => "$basePath/hyde-kernel-view-methods.md",
118
        'facadeName' => 'Hyde',
119
    ],
120
    // BootsHydeKernel
121
    [
122
        'class' => \Hyde\Foundation\Concerns\BootsHydeKernel::class,
123
        'instanceVariableName' => '$hyde',
124
        'outputFile' => "$basePath/hyde-kernel-boot-methods.md",
125
        'facadeName' => 'Hyde',
126
    ],
127
];
128
$timeStart = microtime(true);
129
130
foreach ($matrix as $key => $options) {
131
    if ($key > 0) {
132
        echo "\n";
133
    }
134
    generate($options);
135
}
136
137
// Assemble end time in milliseconds
138
$timeEnd = microtime(true);
139
$time = number_format(($timeEnd - $timeStart) * 1000, 2);
140
echo "\n\033[32mAll done in $time ms!\n\033[0m";
141
142
// Helpers
143
144
function generate(array $options): void
145
{
146
    $timeStart = microtime(true);
147
148
    $class = $options['class'];
149
    $instanceVariableName = $options['instanceVariableName'];
150
    $outputFile = $options['outputFile'];
151
    $facadeName = $options['facadeName'] ?? null; // If the class has a facade we use that instead of instance variable names
152
153
    echo "\033[33mGenerating documentation for $class...\033[0m";
154
155
    $reflection = new ReflectionClass($class);
156
157
    $methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);
158
    $fileName = (new ReflectionClass($class))->getFileName();
159
160
    // Remove methods defined in traits or parent classes
161
    $methods = array_filter($methods, function (ReflectionMethod $method) use ($fileName) {
162
        return $method->getFileName() === $fileName;
163
    });
164
165
    // Split methods into static and non-static
166
167
    $staticMethods = array_filter($methods, function (ReflectionMethod $method) {
168
        return $method->isStatic();
169
    });
170
171
    $instanceMethods = array_filter($methods, function (ReflectionMethod $method) {
172
        return ! $method->isStatic();
173
    });
174
175
    $output = [];
176
177
    // Generate static methods
178
    foreach ($staticMethods as $method) {
179
        documentMethod($method, $output, $class, $instanceVariableName, $facadeName);
180
    }
181
182
    // Generate instance methods
183
    foreach ($instanceMethods as $method) {
184
        documentMethod($method, $output, $class, $instanceVariableName, $facadeName);
185
    }
186
187
    // Assemble end time in milliseconds
188
    $timeEnd = microtime(true);
189
    $time = number_format(($timeEnd - $timeStart) * 1000, 2);
190
    $metadata = sprintf('Generated by HydePHP DocGen script at %s in %sms', date('Y-m-d H:i:s'), $time);
191
192
    // Join the output
193
    $text = implode("\n", $output);
194
    $startMarker = '<!-- Start generated docs for '.$class.' -->';
195
    $metadataMarker = "<!-- $metadata -->";
196
    $endMarker = '<!-- End generated docs for '.$class.' -->';
197
    $classKebabName = Str::kebab(class_basename($class));
198
    $baseName = basename($outputFile, '.md');
199
    $text = "<section id=\"$baseName\">\n\n$startMarker\n$metadataMarker\n\n$text\n$endMarker\n\n</section>\n";
200
201
    // Run any post-processing
202
    $text = postProcess($text);
203
204
    // Output the documentation
205
    // echo $text;
206
207
    // Check if any changes were made compared to the existing file (excluding metadata markers)
208
    if (file_exists($outputFile)) {
209
        $existingFile = file_get_contents($outputFile);
210
        $existingFile = preg_replace('/<!-- Generated by HydePHP DocGen script at .* in .*ms -->/', '', $existingFile);
211
        $compareText = preg_replace('/<!-- Generated by HydePHP DocGen script at .* in .*ms -->/', '', $text);
212
        if ($existingFile === $compareText) {
213
            echo "\n\033[37mNo changes made to $outputFile\033[0m\n";
214
215
            return;
216
        }
217
    }
218
219
    // Save the documentation to a file
220
    file_put_contents($outputFile, $text);
221
222
    echo "\n\033[0m Convents saved to ".realpath($outputFile)."\n";
223
}
224
225
function documentMethod(ReflectionMethod $method, array &$output, string $class, string $instanceVariableName, ?string $facadeName = null): void
226
{
227
    $template = <<<'MARKDOWN'
228
    #### `{{ $methodName }}()`
229
    
230
    {{ $description }}
231
    
232
    ```php
233
    // torchlight! {"lineNumbers": false}
234
    {{ $signature }}({{ $argList }}): {{ $returnType }}
235
    ```
236
237
    MARKDOWN;
238
239
    $staticSignatureTemplate = '{{ $className }}::{{ $methodName }}';
240
    $instanceSignatureTemplate = '{{ $instanceVariableName }}->{{ $methodName }}';
241
    $facadeSignatureTemplate = '{{ $facadeName }}::{{ $methodName }}';
242
243
    $signatureTemplate = $method->isStatic() ? $staticSignatureTemplate : $instanceSignatureTemplate;
244
245
    if ($facadeName !== null) {
246
        $signatureTemplate = $facadeSignatureTemplate;
247
    }
248
249
    if ($method->getName() === '__construct') {
250
        $signatureTemplate = '{{ $instanceVariableName }} = new {{ $className }}';
251
    }
252
253
    $methodName = $method->getName();
254
255
    // If method uses inheritdoc, use the parent method's docblock
256
    if ($method->getDocComment() !== false && str_contains(strtolower($method->getDocComment()), '@inheritdoc')) {
257
        try {
258
            $parentMethod = $method->getPrototype();
259
            $docComment = $parentMethod->getDocComment();
260
        } catch (ReflectionException $e) {
261
            // if method is for constructor, getPrototype() will throw an exception,
262
            // so we check if exception is for constructor and if so, we use the parent class's constructor
263
            if ($method->getName() === '__construct') {
264
                $parentClass = $method->getDeclaringClass()->getParentClass();
265
                $parentMethod = $parentClass->getMethod('__construct');
266
                $docComment = $parentMethod->getDocComment();
267
            } else {
268
                $docComment = null;
269
            }
270
        }
271
    } else {
272
        $docComment = $method->getDocComment();
273
    }
274
275
    $PHPDocs = parsePHPDocs($docComment ?: '');
276
    $description = $PHPDocs['description'];
277
278
    $className = class_basename($class);
279
280
    $parameters = array_map(function (ReflectionParameter $parameter) {
281
        $name = '$'.$parameter->getName();
282
        if ($parameter->getType()) {
283
            if ($parameter->getType() instanceof ReflectionUnionType) {
284
                $type = implode('|', array_map(function (ReflectionNamedType $type) {
285
                    return $type->getName();
286
                }, $parameter->getType()->getTypes()));
287
            } else {
288
                $type = $parameter->getType()->getName();
289
            }
290
        } else {
291
            $type = 'mixed';
292
        }
293
294
        return trim($type.' '.$name);
295
    }, $method->getParameters());
296
297
    // If return is union type
298
    if ($method->getReturnType() instanceof ReflectionUnionType) {
299
        $returnType = implode('|', array_map(function (ReflectionNamedType $type) {
300
            return $type->getName();
301
        }, $method->getReturnType()->getTypes()));
302
    } else {
303
        $returnType = $method->getReturnType() ? $method->getReturnType()->getName() : 'void';
304
    }
305
306
    // If higher specificity return type is provided in docblock, use that instead
307
    if (isset($PHPDocs['properties']['return'])) {
308
        $returnValue = $PHPDocs['properties']['return'];
309
        // If there is a description, put it in a comment
310
        if (str_contains($returnValue, ' ')) {
311
            $exploded = explode(' ', $returnValue, 2);
312
            // If is not generic
313
            if (! str_contains($exploded[0], '<')) {
314
                $type = $exploded[0];
315
                $comment = ' // '.$exploded[1];
316
                $returnValue = $type;
317
            } else {
318
                $comment = null;
319
            }
320
        } else {
321
            $comment = null;
322
        }
323
        $returnType = $returnValue.($comment ?? '');
324
    }
325
326
    $parameterDocs = [];
327
    // Map docblock params
328
    if (isset($PHPDocs['properties']['params'])) {
329
        $newParams = array_map(function (string $param) use (&$parameterDocs) {
330
            $param = str_replace('  ', ' ', trim($param));
331
            $comment = $param;
332
            $param = explode(' ', $param, 3);
333
            $type = $param[0];
334
            $name = $param[1];
335
            if (isset($param[2])) {
336
                $parameterDocs[$type] = $comment;
337
            }
338
339
            return trim($type.' '.$name);
340
        }, $PHPDocs['properties']['params']);
341
    }
342
    // If higher specificity argument types are provided in docblock, merge them with the actual types
343
    if (isset($newParams)) {
344
        foreach ($newParams as $index => $newParam) {
345
            if (isset($parameters[$index])) {
346
                $parameters[$index] = $newParam;
347
            }
348
        }
349
    }
350
351
    $argList = implode(', ', $parameters);
352
353
    $before = null;
354
    $beforeSignature = null;
355
    if ($parameterDocs) {
356
        if (count($parameterDocs) > 1) {
357
            foreach ($parameterDocs as $type => $param) {
358
                $name = explode(' ', $param, 3)[1];
359
                $desc = explode(' ', $param, 3)[2];
360
                $before .= "- **Parameter $name:** $desc \n";
361
            }
362
        } else {
363
            $param = array_values($parameterDocs)[0];
364
            $beforeSignature = "/** @param $param */";
365
        }
366
    }
367
368
    $signature = ($beforeSignature ? $beforeSignature."\n" : '').str_replace(
369
        ['{{ $instanceVariableName }}', '{{ $methodName }}', '{{ $className }}'],
370
        [$instanceVariableName, $methodName, $className],
371
        $signatureTemplate
372
    );
373
374
    $description = $description.($before ? "\n".$before : '');
375
    $replacements = [
376
        '{{ $signature }}' => $signature,
377
        '{{ $methodName }}' => e($methodName),
378
        '{{ $description }}' => e($description),
379
        '{{ $className }}' => e($className),
380
        '{{ $argList }}' => e($argList),
381
        '{{ $returnType }}' => $returnType,
382
        '{{ $facadeName }}' => $facadeName,
383
    ];
384
    $markdown = str_replace(array_keys($replacements), array_values($replacements), $template);
385
386
    // Throws
387
    if (isset($PHPDocs['properties']['throws'])) {
388
        $markdown .= "\n";
389
        foreach ($PHPDocs['properties']['throws'] as $throw) {
390
            $markdown .= e("- **Throws:** $throw\n");
391
        }
392
    }
393
394
    // Debug breakpoint
395
    if (str_contains($markdown, 'foo')) {
396
        // dd($markdown);
397
    }
398
399
    $output[] = $markdown;
400
}
401
402
function parsePHPDocs(string $comment): array
403
{
404
    // Normalize
405
    $comment = array_map(function (string $line) {
406
        return trim($line, " \t*/");
407
    }, explode("\n", $comment));
408
409
    $description = '';
410
    $properties = [];
411
412
    // Parse
413
    foreach ($comment as $line) {
414
        if (str_starts_with($line, '@')) {
415
            $propertyName = substr($line, 1, strpos($line, ' ') - 1);
416
            $propertyValue = substr($line, strpos($line, ' ') + 1);
417
            // If property allows multiple we add to subarray
418
            if ($propertyName === 'return') {
419
                $properties[$propertyName] = $propertyValue;
420
            } else {
421
                $name = str_ends_with($propertyName, 's') ? $propertyName : $propertyName.'s';
422
                $properties[$name][] = $propertyValue;
423
            }
424
        } else {
425
            $shouldAddNewline = empty($line);
426
            $description .= ($shouldAddNewline ? "\n\n" : '').ltrim($line.' ');
427
        }
428
    }
429
430
    return [
431
        'description' => trim($description) ?: 'No description provided.',
432
        'properties' => $properties,
433
    ];
434
}
435
436
function postProcess(string $text): string
437
{
438
    // Unescape escaped code that will be escaped again
439
    $replace = ['`&lt;' => '`<', '&gt;`' => '>`'];
440
    $text = str_replace(array_keys($replace), array_values($replace), $text);
441
442
    // Trim trailing whitespace
443
    $text = implode("\n", array_map(function (string $line) {
444
        return rtrim($line);
445
    }, explode("\n", $text)));
446
447
    return $text;
448
}
449