Passed
Pull Request — main (#1074)
by
unknown
02:36
created

TableOfContentsGenerator::generate()   B

Complexity

Conditions 8
Paths 12

Size

Total Lines 57
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 8.0021

Importance

Changes 0
Metric Value
cc 8
eloc 30
c 0
b 0
f 0
nc 12
nop 1
dl 0
loc 57
ccs 30
cts 31
cp 0.9677
crap 8.0021
rs 8.1954

How to fix   Long Method   

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
 * This file is part of the league/commonmark package.
7
 *
8
 * (c) Colin O'Dell <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace League\CommonMark\Extension\TableOfContents;
15
16
use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
17
use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock;
18
use League\CommonMark\Extension\CommonMark\Node\Block\ListData;
19
use League\CommonMark\Extension\CommonMark\Node\Block\ListItem;
20
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
21
use League\CommonMark\Extension\CommonMark\Node\Inline\Strong;
22
use League\CommonMark\Extension\HeadingPermalink\HeadingPermalink;
23
use League\CommonMark\Extension\TableOfContents\Node\TableOfContents;
24
use League\CommonMark\Extension\TableOfContents\Node\TableOfContentsWrapper;
25
use League\CommonMark\Extension\TableOfContents\Normalizer\AsIsNormalizerStrategy;
26
use League\CommonMark\Extension\TableOfContents\Normalizer\FlatNormalizerStrategy;
27
use League\CommonMark\Extension\TableOfContents\Normalizer\NormalizerStrategyInterface;
28
use League\CommonMark\Extension\TableOfContents\Normalizer\RelativeNormalizerStrategy;
29
use League\CommonMark\Node\Block\AbstractBlock;
30
use League\CommonMark\Node\Block\Document;
31
use League\CommonMark\Node\Inline\Text;
32
use League\CommonMark\Node\NodeIterator;
33
use League\CommonMark\Node\RawMarkupContainerInterface;
34
use League\CommonMark\Node\StringContainerHelper;
35
use League\Config\Exception\InvalidConfigurationException;
36
37
final class TableOfContentsGenerator implements TableOfContentsGeneratorInterface
38
{
39
    public const STYLE_BULLET  = ListBlock::TYPE_BULLET;
40
    public const STYLE_ORDERED = ListBlock::TYPE_ORDERED;
41
42
    public const NORMALIZE_DISABLED = 'as-is';
43
    public const NORMALIZE_RELATIVE = 'relative';
44
    public const NORMALIZE_FLAT     = 'flat';
45
46
    /** @psalm-readonly */
47
    private string $style;
48
49
    /** @psalm-readonly */
50
    private string $normalizationStrategy;
51
52
    /** @psalm-readonly */
53
    private int $minHeadingLevel;
54
55
    /** @psalm-readonly */
56
    private int $maxHeadingLevel;
57
58
    /** @psalm-readonly */
59
    private string $fragmentPrefix;
60
61
    /** @psalm-readonly */
62
    private string $label;
63
64 70
    public function __construct(string $style, string $normalizationStrategy, int $minHeadingLevel, int $maxHeadingLevel, string $fragmentPrefix, string $label = '')
65
    {
66 70
        $this->style                 = $style;
67 70
        $this->normalizationStrategy = $normalizationStrategy;
68 70
        $this->minHeadingLevel       = $minHeadingLevel;
69 70
        $this->maxHeadingLevel       = $maxHeadingLevel;
70 70
        $this->fragmentPrefix        = $fragmentPrefix;
71 70
        $this->label                 = $label;
72
73 70
        if ($fragmentPrefix !== '') {
74 70
            $this->fragmentPrefix .= '-';
75
        }
76
    }
77
78
    /**
79
     * If there is a table of contents, returns either a `TableOfContents` or
80
     * `TableOfContentsWrapper` node object
81
     */
82 70
    public function generate(Document $document): ?AbstractBlock
83
    {
84 70
        $toc = $this->createToc($document);
85
86 70
        $normalizer = $this->getNormalizer($toc);
87
88 70
        $firstHeading = null;
89
90 70
        foreach ($this->getHeadingLinks($document) as $headingLink) {
91 62
            $heading = $headingLink->parent();
92
            // Make sure this is actually tied to a heading
93 62
            if (! $heading instanceof Heading) {
94
                continue;
95
            }
96
97
            // Skip any headings outside the configured min/max levels
98 62
            if ($heading->getLevel() < $this->minHeadingLevel || $heading->getLevel() > $this->maxHeadingLevel) {
99 4
                continue;
100
            }
101
102
            // Keep track of the first heading we see - we might need this later
103 62
            $firstHeading ??= $heading;
104
105
            // Keep track of the start and end lines
106 62
            $toc->setStartLine($firstHeading->getStartLine());
107 62
            $toc->setEndLine($heading->getEndLine());
108
109
            // Create the new link
110 62
            $link = new Link('#' . $this->fragmentPrefix . $headingLink->getSlug(), StringContainerHelper::getChildText($heading, [RawMarkupContainerInterface::class]));
111
112 62
            $listItem = new ListItem($toc->getListData());
113 62
            $listItem->setStartLine($heading->getStartLine());
114 62
            $listItem->setEndLine($heading->getEndLine());
115 62
            $listItem->appendChild($link);
116
117
            // Add it to the correct place
118 62
            $normalizer->addItem($heading->getLevel(), $listItem);
119
        }
120
121
        // Don't add the TOC if no headings were present
122 70
        if (! $toc->hasChildren() || $firstHeading === null) {
123 8
            return null;
124
        }
125
126 62
        if ($this->label !== '') {
127 4
            $label = new Strong();
128 4
            $label->appendChild(new Text($this->label));
129 4
            $wrapper = new TableOfContentsWrapper();
130 4
            $wrapper->appendChild($label);
131 4
            $wrapper->appendChild($toc);
132 4
            $wrapper->setStartLine($toc->getStartLine());
133 4
            $wrapper->setEndLine($toc->getEndLine());
134
135 4
            return $wrapper;
136
        }
137
138 58
        return $toc;
139
    }
140
141 70
    private function createToc(Document $document): TableOfContents
142
    {
143 70
        $listData = new ListData();
144
145 70
        if ($this->style === self::STYLE_BULLET) {
146 66
            $listData->type = ListBlock::TYPE_BULLET;
147 4
        } elseif ($this->style === self::STYLE_ORDERED) {
148 4
            $listData->type = ListBlock::TYPE_ORDERED;
149
        } else {
150
            throw new InvalidConfigurationException(\sprintf('Invalid table of contents list style: "%s"', $this->style));
151
        }
152
153 70
        $toc = new TableOfContents($listData);
154
155 70
        $toc->setStartLine($document->getStartLine());
156 70
        $toc->setEndLine($document->getEndLine());
157
158 70
        return $toc;
159
    }
160
161
    /**
162
     * @return iterable<HeadingPermalink>
163
     */
164 70
    private function getHeadingLinks(Document $document): iterable
165
    {
166 70
        foreach ($document->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) {
167 70
            if (! $node instanceof Heading) {
168 70
                continue;
169
            }
170
171 62
            foreach ($node->children() as $child) {
172 62
                if ($child instanceof HeadingPermalink) {
173 62
                    yield $child;
174
                }
175
            }
176
        }
177
    }
178
179 70
    private function getNormalizer(TableOfContents $toc): NormalizerStrategyInterface
180
    {
181 70
        switch ($this->normalizationStrategy) {
182
            case self::NORMALIZE_DISABLED:
183 4
                return new AsIsNormalizerStrategy($toc);
184
            case self::NORMALIZE_RELATIVE:
185 62
                return new RelativeNormalizerStrategy($toc);
186
            case self::NORMALIZE_FLAT:
187 4
                return new FlatNormalizerStrategy($toc);
188
            default:
189
                throw new InvalidConfigurationException(\sprintf('Invalid table of contents normalization strategy: "%s"', $this->normalizationStrategy));
190
        }
191
    }
192
}
193