Passed
Push — master ( 22acbe...a6e730 )
by Théo
02:08
created

Php   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 109
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 19
dl 0
loc 109
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
C compactContent() 0 43 8
D compactAnnotations() 0 45 10
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the box project.
7
 *
8
 * (c) Kevin Herrera <[email protected]>
9
 *     Théo Fidry <[email protected]>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14
15
namespace KevinGH\Box\Compactor;
16
17
use Doctrine\Common\Annotations\DocLexer;
18
use Exception;
19
use Herrera\Annotations\Convert\ToString;
20
use Herrera\Annotations\Tokenizer;
21
use Herrera\Annotations\Tokens;
22
23
/**
24
 * A PHP source code compactor copied from Composer.
25
 *
26
 * @author Kevin Herrera <[email protected]>
27
 * @author Fabien Potencier <[email protected]>
28
 * @author Jordi Boggiano <[email protected]>
29
 *
30
 * @see https://github.com/composer/composer/blob/a8df30c09be550bffc37ba540fb7c7f0383c3944/src/Composer/Compiler.php#L214
31
 */
32
final class Php extends FileExtensionCompactor
33
{
34
    private $converter;
35
    private $tokenizer;
36
37
    /**
38
     * {@inheritdoc}
39
     */
40
    public function __construct(Tokenizer $tokenizer, array $extensions = ['php'])
41
    {
42
        parent::__construct($extensions);
43
44
        $this->converter = new ToString();
45
        $this->tokenizer = $tokenizer;
46
    }
47
48
    /**
49
     * {@inheritdoc}
50
     */
51
    protected function compactContent(string $contents): string
52
    {
53
        // TODO: refactor this piece of code
54
        // - strip down blank spaces
55
        // - remove useless spaces
56
        // - strip down comments except Doctrine style annotations unless whitelisted -> BC break to document;
57
        //   Alternatively provide an easy way to strip down all "regular" annotations such as @package, @param
58
        //   & co.
59
        // - completely remove comments & docblocks if empty
60
        // TODO regarding the doc: it current has its own `annotations` entry. Maybe it would be best to
61
        // include it as a sub element of `compactors`
62
        $output = '';
63
64
        foreach (token_get_all($contents) as $token) {
65
            if (is_string($token)) {
66
                $output .= $token;
67
            } elseif (in_array($token[0], [T_COMMENT, T_DOC_COMMENT], true)) {
68
                if ($this->tokenizer && (false !== strpos($token[1], '@'))) {
69
                    try {
70
                        $output .= $this->compactAnnotations($token[1]);
71
                    } catch (Exception $exception) {
72
                        $output .= $token[1];
73
                    }
74
                } else {
75
                    $output .= str_repeat("\n", substr_count($token[1], "\n"));
76
                }
77
            } elseif (T_WHITESPACE === $token[0]) {
78
                // reduce wide spaces
79
                $whitespace = preg_replace('{[ \t]+}', ' ', $token[1]);
80
81
                // normalize newlines to \n
82
                $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace);
83
84
                // trim leading spaces
85
                $whitespace = preg_replace('{\n +}', "\n", $whitespace);
86
87
                $output .= $whitespace;
88
            } else {
89
                $output .= $token[1];
90
            }
91
        }
92
93
        return $output;
94
    }
95
96
    private function compactAnnotations(string $docblock): string
97
    {
98
        $annotations = [];
99
        $index = -1;
100
        $inside = 0;
101
        $tokens = $this->tokenizer->parse($docblock);
102
103
        if (empty($tokens)) {
104
            return str_repeat("\n", substr_count($docblock, "\n"));
105
        }
106
107
        foreach ($tokens as $token) {
108
            if ((0 === $inside) && (DocLexer::T_AT === $token[0])) {
109
                ++$index;
110
            } elseif (DocLexer::T_OPEN_PARENTHESIS === $token[0]) {
111
                ++$inside;
112
            } elseif (DocLexer::T_CLOSE_PARENTHESIS === $token[0]) {
113
                --$inside;
114
            }
115
116
            if (!isset($annotations[$index])) {
117
                $annotations[$index] = [];
118
            }
119
120
            $annotations[$index][] = $token;
121
        }
122
123
        $breaks = substr_count($docblock, "\n");
124
        $docblock = '/**';
125
126
        foreach ($annotations as $annotation) {
127
            $annotation = new Tokens($annotation);
128
            $docblock .= "\n".$this->converter->convert($annotation);
129
        }
130
131
        $breaks -= count($annotations);
132
133
        if ($breaks > 0) {
134
            $docblock .= str_repeat("\n", $breaks - 1);
135
            $docblock .= "\n*/";
136
        } else {
137
            $docblock .= ' */';
138
        }
139
140
        return $docblock;
141
    }
142
}
143