Passed
Pull Request — main (#1074)
by
unknown
02:31
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
     * @psalm-return TableOfContents|TableOfContentsWrapper
83
     */
84 70
    public function generate(Document $document): ?AbstractBlock
85
    {
86 70
        $toc = $this->createToc($document);
87
88 70
        $normalizer = $this->getNormalizer($toc);
89
90 70
        $firstHeading = null;
91
92 70
        foreach ($this->getHeadingLinks($document) as $headingLink) {
93 62
            $heading = $headingLink->parent();
94
            // Make sure this is actually tied to a heading
95 62
            if (! $heading instanceof Heading) {
96
                continue;
97
            }
98
99
            // Skip any headings outside the configured min/max levels
100 62
            if ($heading->getLevel() < $this->minHeadingLevel || $heading->getLevel() > $this->maxHeadingLevel) {
101 4
                continue;
102
            }
103
104
            // Keep track of the first heading we see - we might need this later
105 62
            $firstHeading ??= $heading;
106
107
            // Keep track of the start and end lines
108 62
            $toc->setStartLine($firstHeading->getStartLine());
109 62
            $toc->setEndLine($heading->getEndLine());
110
111
            // Create the new link
112 62
            $link = new Link('#' . $this->fragmentPrefix . $headingLink->getSlug(), StringContainerHelper::getChildText($heading, [RawMarkupContainerInterface::class]));
113
114 62
            $listItem = new ListItem($toc->getListData());
115 62
            $listItem->setStartLine($heading->getStartLine());
116 62
            $listItem->setEndLine($heading->getEndLine());
117 62
            $listItem->appendChild($link);
118
119
            // Add it to the correct place
120 62
            $normalizer->addItem($heading->getLevel(), $listItem);
121
        }
122
123
        // Don't add the TOC if no headings were present
124 70
        if (! $toc->hasChildren() || $firstHeading === null) {
125 8
            return null;
126
        }
127
128 62
        if ($this->label !== '') {
129 4
            $label = new Strong();
130 4
            $label->appendChild(new Text($this->label));
131 4
            $wrapper = new TableOfContentsWrapper();
132 4
            $wrapper->appendChild($label);
133 4
            $wrapper->appendChild($toc);
134 4
            $wrapper->setStartLine($toc->getStartLine());
135 4
            $wrapper->setEndLine($toc->getEndLine());
136
137 4
            return $wrapper;
138
        }
139
140 58
        return $toc;
141
    }
142
143 70
    private function createToc(Document $document): TableOfContents
144
    {
145 70
        $listData = new ListData();
146
147 70
        if ($this->style === self::STYLE_BULLET) {
148 66
            $listData->type = ListBlock::TYPE_BULLET;
149 4
        } elseif ($this->style === self::STYLE_ORDERED) {
150 4
            $listData->type = ListBlock::TYPE_ORDERED;
151
        } else {
152
            throw new InvalidConfigurationException(\sprintf('Invalid table of contents list style: "%s"', $this->style));
153
        }
154
155 70
        $toc = new TableOfContents($listData);
156
157 70
        $toc->setStartLine($document->getStartLine());
158 70
        $toc->setEndLine($document->getEndLine());
159
160 70
        return $toc;
161
    }
162
163
    /**
164
     * @return iterable<HeadingPermalink>
165
     */
166 70
    private function getHeadingLinks(Document $document): iterable
167
    {
168 70
        foreach ($document->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) {
169 70
            if (! $node instanceof Heading) {
170 70
                continue;
171
            }
172
173 62
            foreach ($node->children() as $child) {
174 62
                if ($child instanceof HeadingPermalink) {
175 62
                    yield $child;
176
                }
177
            }
178
        }
179
    }
180
181 70
    private function getNormalizer(TableOfContents $toc): NormalizerStrategyInterface
182
    {
183 70
        switch ($this->normalizationStrategy) {
184 35
            case self::NORMALIZE_DISABLED:
185 4
                return new AsIsNormalizerStrategy($toc);
186 33
            case self::NORMALIZE_RELATIVE:
187 62
                return new RelativeNormalizerStrategy($toc);
188 2
            case self::NORMALIZE_FLAT:
189 4
                return new FlatNormalizerStrategy($toc);
190
            default:
191
                throw new InvalidConfigurationException(\sprintf('Invalid table of contents normalization strategy: "%s"', $this->normalizationStrategy));
192
        }
193
    }
194
}
195