Completed
Pull Request — master (#130)
by Greg
04:26
created

BespokeDocBlockParser   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 201
Duplicated Lines 20.4 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 31
lcom 1
cbo 4
dl 41
loc 201
rs 9.8
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A parse() 0 5 1
A processGenericTag() 0 4 1
A processCommandTag() 0 11 2
A processAlternateDescriptionTag() 0 4 1
A processArgumentTag() 0 10 3
A processOptionTag() 7 7 2
A addOptionOrArgumentTag() 7 7 1
A processDefaultTag() 16 16 4
A processUsageTag() 0 10 1
A processAliases() 0 4 1
A processReturnTag() 0 9 2
B parseDocBlock() 0 27 6
A processDescriptionAndHelp() 0 23 3
A processAllTags() 11 11 3

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
namespace Consolidation\AnnotatedCommand\Parser\Internal;
3
4
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
5
use Consolidation\AnnotatedCommand\Parser\DefaultsWithDescriptions;
6
7
/**
8
 * Given a class and method name, parse the annotations in the
9
 * DocBlock comment, and provide accessor methods for all of
10
 * the elements that are needed to create an annotated Command.
11
 */
12
class BespokeDocBlockParser extends AbstractCommandDocBlockParser
13
{
14
    /**
15
     * Parse the docBlock comment for this command, and set the
16
     * fields of this class with the data thereby obtained.
17
     */
18
    public function parse()
19
    {
20
        $doc = $this->reflection->getDocComment();
21
        $this->parseDocBlock($doc);
22
    }
23
24
    /**
25
     * Save any tag that we do not explicitly recognize in the
26
     * 'otherAnnotations' map.
27
     */
28
    protected function processGenericTag($tag)
29
    {
30
        $this->commandInfo->addAnnotation($tag->getTag(), $tag->getContent());
31
    }
32
33
    /**
34
     * Set the name of the command from a @command or @name annotation.
35
     */
36
    protected function processCommandTag($tag)
37
    {
38
        if (!$tag->hasWordAndDescription($matches)) {
0 ignored issues
show
Bug introduced by
The variable $matches does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
39
            throw new \Exception('Could not determine command name from tag ' . (string)$tag);
40
        }
41
        $commandName = $matches['word'];
42
        $this->commandInfo->setName($commandName);
43
        // We also store the name in the 'other annotations' so that is is
44
        // possible to determine if the method had a @command annotation.
45
        $this->commandInfo->addAnnotation($tag->getTag(), $commandName);
46
    }
47
48
    /**
49
     * The @description and @desc annotations may be used in
50
     * place of the synopsis (which we call 'description').
51
     * This is discouraged.
52
     *
53
     * @deprecated
54
     */
55
    protected function processAlternateDescriptionTag($tag)
56
    {
57
        $this->commandInfo->setDescription($tag->getContent());
58
    }
59
60
    /**
61
     * Store the data from a @arg annotation in our argument descriptions.
62
     */
63
    protected function processArgumentTag($tag)
64
    {
65
        if (!$this->pregMatchNameAndDescription((string)$tag->getContent(), $match)) {
66
            return;
67
        }
68
        if ($match['name'] == $this->optionParamName()) {
69
            return;
70
        }
71
        $this->addOptionOrArgumentTag($tag, $this->commandInfo->arguments(), $match);
72
    }
73
74
    /**
75
     * Store the data from an @option annotation in our option descriptions.
76
     */
77 View Code Duplication
    protected function processOptionTag($tag)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
78
    {
79
        if (!$this->pregMatchOptionNameAndDescription((string)$tag->getContent(), $match)) {
80
            return;
81
        }
82
        $this->addOptionOrArgumentTag($tag, $this->commandInfo->options(), $match);
83
    }
84
85 View Code Duplication
    protected function addOptionOrArgumentTag($tag, DefaultsWithDescriptions $set, $nameAndDescription)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
86
    {
87
        $variableName = $this->commandInfo->findMatchingOption($nameAndDescription['name']);
88
        $desc = $nameAndDescription['description'];
89
        $description = static::removeLineBreaks($desc);
90
        $set->add($variableName, $description);
91
    }
92
93
    /**
94
     * Store the data from a @default annotation in our argument or option store,
95
     * as appropriate.
96
     */
97 View Code Duplication
    protected function processDefaultTag($tag)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
98
    {
99
        if (!$this->pregMatchNameAndDescription((string)$tag->getContent(), $match)) {
100
            return;
101
        }
102
        $variableName = $match['name'];
103
        $defaultValue = $this->interpretDefaultValue($match['description']);
104
        if ($this->commandInfo->arguments()->exists($variableName)) {
105
            $this->commandInfo->arguments()->setDefaultValue($variableName, $defaultValue);
106
            return;
107
        }
108
        $variableName = $this->commandInfo->findMatchingOption($variableName);
109
        if ($this->commandInfo->options()->exists($variableName)) {
110
            $this->commandInfo->options()->setDefaultValue($variableName, $defaultValue);
111
        }
112
    }
113
114
    /**
115
     * Store the data from a @usage annotation in our example usage list.
116
     */
117
    protected function processUsageTag($tag)
118
    {
119
        $lines = explode("\n", $tag->getContent());
120
        $usage = trim(array_shift($lines));
121
        $description = static::removeLineBreaks(implode("\n", array_map(function ($line) {
122
            return trim($line);
123
        }, $lines)));
124
125
        $this->commandInfo->setExampleUsage($usage, $description);
126
    }
127
128
    /**
129
     * Process the comma-separated list of aliases
130
     */
131
    protected function processAliases($tag)
132
    {
133
        $this->commandInfo->setAliases((string)$tag->getContent());
134
    }
135
136
    /**
137
     * Store the data from a @return annotation in our argument descriptions.
138
     */
139
    protected function processReturnTag($tag)
140
    {
141
        if (!$tag->hasWordAndDescription($matches)) {
0 ignored issues
show
Bug introduced by
The variable $matches does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
142
            throw new \Exception('Could not determine return type from tag ' . (string)$tag);
143
        }
144
        // TODO: look at namespace and `use` statments to make returnType a fqdn
145
        $returnType = $matches['word'];
146
        $this->commandInfo->setReturnType($returnType);
147
    }
148
149
    private function parseDocBlock($doc)
150
    {
151
        if (empty($doc)) {
152
            return;
153
        }
154
155
        $tagFactory = new TagFactory();
156
        $lines = [];
157
158
        foreach (explode("\n", $doc) as $row) {
159
            // Remove trailing whitespace and leading space + '*'s
160
            $row = rtrim($row);
161
            $row = preg_replace('#^[ \t]*\**#', '', $row);
162
163
            // Throw out the /** and */ lines ('*' trimmed from beginning)
164
            if ($row == '/**' || $row == '/') {
165
                continue;
166
            }
167
168
            if (!$tagFactory->parseLine($row)) {
169
                $lines[] = $row;
170
            }
171
        }
172
173
        $this->processDescriptionAndHelp($lines);
174
        $this->processAllTags($tagFactory->getTags());
175
    }
176
177
    protected function processDescriptionAndHelp($lines)
178
    {
179
        // Trim all of the lines individually.
180
        $lines =
181
            array_map(
182
                function ($line) {
183
                    return trim($line);
184
                },
185
                $lines
186
            );
187
188
        // Everything up to the first blank line goes in the description.
189
        $description = array_shift($lines);
190
        while (!empty($lines) && !empty(trim($lines[0]))) {
191
            $description .= ' ' . array_shift($lines);
192
        }
193
194
        // Everything else goes in the help.
195
        $help = trim(implode(PHP_EOL, $lines));
196
197
        $this->commandInfo->setDescription($description);
198
        $this->commandInfo->setHelp($help);
199
    }
200
201 View Code Duplication
    protected function processAllTags($tags)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
202
    {
203
        // Iterate over all of the tags, and process them as necessary.
204
        foreach ($tags as $tag) {
205
            $processFn = [$this, 'processGenericTag'];
206
            if (array_key_exists($tag->getTag(), $this->tagProcessors)) {
207
                $processFn = [$this, $this->tagProcessors[$tag->getTag()]];
208
            }
209
            $processFn($tag);
210
        }
211
    }
212
}
213