Completed
Push — master ( fbf2a7...00045a )
by Colin
02:48
created

src/Block/Element/FencedCode.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
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