Completed
Push — master ( aeeeb2...74d969 )
by Colin
03:28
created

FencedCode   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 198
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 4

Test Coverage

Coverage 77.78%

Importance

Changes 0
Metric Value
wmc 21
lcom 2
cbo 4
dl 0
loc 198
ccs 42
cts 54
cp 0.7778
rs 10
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A getInfo() 0 4 1
A getInfoWords() 0 4 1
A getChar() 0 4 1
A setChar() 0 6 1
A getLength() 0 4 1
A setLength() 0 6 1
A getOffset() 0 4 1
A setOffset() 0 6 1
A canContain() 0 4 1
A isCode() 0 4 1
A matchesNextLine() 0 15 3
A finalize() 0 13 2
A handleRemainingContents() 0 18 4
A shouldLastLineBeBlank() 0 4 1
1
<?php
2
3
/*
4
 * This file is part of the league/commonmark package.
5
 *
6
 * (c) Colin O'Dell <[email protected]>
7
 *
8
 * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
9
 *  - (c) John MacFarlane
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace League\CommonMark\Block\Element;
16
17
use League\CommonMark\ContextInterface;
18
use League\CommonMark\Cursor;
19
use League\CommonMark\Util\RegexHelper;
20
21
class FencedCode extends AbstractStringContainerBlock
22
{
23
    /**
24
     * @var string
25
     */
26
    protected $info;
27
28
    /**
29
     * @var int
30
     */
31
    protected $length;
32
33
    /**
34
     * @var string
35
     */
36
    protected $char;
37
38
    /**
39
     * @var int
40
     */
41
    protected $offset;
42
43
    /**
44
     * @param int    $length
45
     * @param string $char
46
     * @param int    $offset
47
     */
48 111
    public function __construct(int $length, string $char, int $offset)
49
    {
50 111
        parent::__construct();
51
52 111
        $this->length = $length;
53 111
        $this->char = $char;
54 111
        $this->offset = $offset;
55 111
    }
56
57
    /**
58
     * @return string
59
     */
60
    public function getInfo(): string
61
    {
62
        return $this->info;
63
    }
64
65
    /**
66
     * @return string[]
67
     */
68 111
    public function getInfoWords(): array
69
    {
70 111
        return \preg_split('/\s+/', $this->info);
71
    }
72
73
    /**
74
     * @return string
75
     */
76 105
    public function getChar(): string
77
    {
78 105
        return $this->char;
79
    }
80
81
    /**
82
     * @param string $char
83
     *
84
     * @return $this
85
     */
86
    public function setChar(string $char): self
87
    {
88
        $this->char = $char;
89
90
        return $this;
91
    }
92
93
    /**
94
     * @return int
95
     */
96 93
    public function getLength(): int
97
    {
98 93
        return $this->length;
99
    }
100
101
    /**
102
     * @param int $length
103
     *
104
     * @return $this
105
     */
106 87
    public function setLength(int $length): self
107
    {
108 87
        $this->length = $length;
109
110 87
        return $this;
111
    }
112
113
    /**
114
     * @return int
115
     */
116
    public function getOffset(): int
117
    {
118
        return $this->offset;
119
    }
120
121
    /**
122
     * @param int $offset
123
     *
124
     * @return $this
125
     */
126
    public function setOffset(int $offset): self
127
    {
128
        $this->offset = $offset;
129
130
        return $this;
131
    }
132
133
    /**
134
     * Returns true if this block can contain the given block as a child node
135
     *
136
     * @param AbstractBlock $block
137
     *
138
     * @return bool
139
     */
140
    public function canContain(AbstractBlock $block): bool
141
    {
142
        return false;
143
    }
144
145
    /**
146
     * Whether this is a code block
147
     *
148
     * @return bool
149
     */
150 99
    public function isCode(): bool
151
    {
152 99
        return true;
153
    }
154
155 99
    public function matchesNextLine(Cursor $cursor): bool
156
    {
157 99
        if ($this->length === -1) {
158 15
            if ($cursor->isBlank()) {
159 9
                $this->lastLineBlank = true;
160
            }
161
162 15
            return false;
163
        }
164
165
        // Skip optional spaces of fence offset
166 99
        $cursor->match('/^ {0,' . $this->offset . '}/');
167
168 99
        return true;
169
    }
170
171 111
    public function finalize(ContextInterface $context, int $endLineNumber)
172
    {
173 111
        parent::finalize($context, $endLineNumber);
174
175
        // first line becomes info string
176 111
        $this->info = RegexHelper::unescape(trim($this->strings->first()));
177
178 111
        if ($this->strings->count() === 1) {
179 12
            $this->finalStringContents = '';
180
        } else {
181 99
            $this->finalStringContents = \implode("\n", $this->strings->slice(1)) . "\n";
182
        }
183 111
    }
184
185
    /**
186
     * @param ContextInterface $context
187
     * @param Cursor           $cursor
188
     */
189 105
    public function handleRemainingContents(ContextInterface $context, Cursor $cursor)
190
    {
191
        /** @var FencedCode $container */
192 105
        $container = $context->getContainer();
193
194
        // check for closing code fence
195 105
        if ($cursor->getIndent() <= 3 && $cursor->getNextNonSpaceCharacter() === $container->getChar()) {
196 93
            $match = RegexHelper::matchAll('/^(?:`{3,}|~{3,})(?= *$)/', $cursor->getLine(), $cursor->getNextNonSpacePosition());
197 93
            if (\strlen($match[0]) >= $container->getLength()) {
198
                // don't add closing fence to container; instead, close it:
199 87
                $this->setLength(-1); // -1 means we've passed closer
200
201 87
                return;
202
            }
203
        }
204
205 105
        $context->getTip()->addLine($cursor->getRemainder());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class League\CommonMark\Block\Element\AbstractBlock as the method addLine() does only exist in the following sub-classes of League\CommonMark\Block\Element\AbstractBlock: League\CommonMark\Block\...actStringContainerBlock, League\CommonMark\Block\Element\FencedCode, League\CommonMark\Block\Element\Heading, League\CommonMark\Block\Element\HtmlBlock, League\CommonMark\Block\Element\IndentedCode, League\CommonMark\Block\Element\Paragraph. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
206 105
    }
207
208
    /**
209
     * @param Cursor $cursor
210
     * @param int    $currentLineNumber
211
     *
212
     * @return bool
213
     */
214 105
    public function shouldLastLineBeBlank(Cursor $cursor, int $currentLineNumber): bool
215
    {
216 105
        return false;
217
    }
218
}
219