Passed
Push — master ( f61cfd...1a5b97 )
by William
02:43
created

ContextGenerator::readWords()   C

Complexity

Conditions 11
Paths 190

Size

Total Lines 58
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 11.0036

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 32
c 1
b 0
f 0
nc 190
nop 1
dl 0
loc 58
ccs 31
cts 32
cp 0.9688
crap 11.0036
rs 6.5666

How to fix   Long Method    Complexity   

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
namespace PhpMyAdmin\SqlParser\Tools;
6
7
use function array_map;
8
use function array_merge;
9
use function array_slice;
10
use function basename;
11
use function count;
12
use function dirname;
13
use function file;
14
use function file_put_contents;
15
use function implode;
16
use function ksort;
17
use function preg_match;
18
use function round;
19
use function scandir;
20
use function sort;
21
use function sprintf;
22
use function str_repeat;
23
use function str_replace;
24
use function str_split;
25
use function strlen;
26
use function strstr;
27
use function strtoupper;
28
use function substr;
29
use function trim;
30
31
use const FILE_IGNORE_NEW_LINES;
32
use const FILE_SKIP_EMPTY_LINES;
33
use const SORT_STRING;
34
35
/**
36
 * Used for context generation.
37
 */
38
class ContextGenerator
39
{
40
    /**
41
     * Labels and flags that may be used when defining keywords.
42
     *
43
     * @var array<string, int>
44
     */
45
    public static $LABELS_FLAGS = [
46
        '(R)' => 2, // reserved
47
        '(D)' => 8, // data type
48
        '(K)' => 16, // keyword
49
        '(F)' => 32, // function name
50
    ];
51
52
    /**
53
     * Documentation links for each context.
54
     *
55
     * @var array<string, string>
56
     */
57
    public static $LINKS = [
58
        'MySql50000' => 'https://dev.mysql.com/doc/refman/5.0/en/keywords.html',
59
        'MySql50100' => 'https://dev.mysql.com/doc/refman/5.1/en/keywords.html',
60
        'MySql50500' => 'https://dev.mysql.com/doc/refman/5.5/en/keywords.html',
61
        'MySql50600' => 'https://dev.mysql.com/doc/refman/5.6/en/keywords.html',
62
        'MySql50700' => 'https://dev.mysql.com/doc/refman/5.7/en/keywords.html',
63
        'MySql80000' => 'https://dev.mysql.com/doc/refman/8.0/en/keywords.html',
64
        'MariaDb100000' => 'https://mariadb.com/kb/en/reserved-words/',
65
        'MariaDb100100' => 'https://mariadb.com/kb/en/reserved-words/',
66
        'MariaDb100200' => 'https://mariadb.com/kb/en/reserved-words/',
67
        'MariaDb100300' => 'https://mariadb.com/kb/en/reserved-words/',
68
        'MariaDb100400' => 'https://mariadb.com/kb/en/reserved-words/',
69
        'MariaDb100500' => 'https://mariadb.com/kb/en/reserved-words/',
70
        'MariaDb100600' => 'https://mariadb.com/kb/en/reserved-words/',
71
    ];
72
73
    /**
74
     * The template of a context.
75
     *
76
     * Parameters:
77
     *     1 - name
78
     *     2 - class
79
     *     3 - link
80
     *     4 - keywords array
81
     */
82
    public const TEMPLATE = <<<'PHP'
83
<?php
84
85
declare(strict_types=1);
86
87
namespace PhpMyAdmin\SqlParser\Contexts;
88
89
use PhpMyAdmin\SqlParser\Context;
90
use PhpMyAdmin\SqlParser\Token;
91
92
/**
93
 * Context for %1$s.
94
 *
95
 * This class was auto-generated from tools/contexts/*.txt.
96
 * Use tools/run_generators.sh for update.
97
 *
98
 * @see %3$s
99
 */
100
class %2$s extends Context
101
{
102
    /**
103
     * List of keywords.
104
     *
105
     * The value associated to each keyword represents its flags.
106
     *
107
     * @see Token::FLAG_KEYWORD_RESERVED Token::FLAG_KEYWORD_COMPOSED
108
     *      Token::FLAG_KEYWORD_DATA_TYPE Token::FLAG_KEYWORD_KEY
109
     *      Token::FLAG_KEYWORD_FUNCTION
110
     *
111
     * @var array<string,int>
112
     * @phpstan-var non-empty-array<non-empty-string,Token::FLAG_KEYWORD_*|int>
113
     */
114
    public static $KEYWORDS = [
115
%4$s    ];
116
}
117
118
PHP;
119
120
    /**
121
     * Sorts an array of words.
122
     *
123
     * @param array<int, array<int, array<int, string>>> $arr
124
     *
125
     * @return array<int, array<int, array<int, string>>>
126
     */
127 12
    public static function sortWords(array &$arr)
128
    {
129 12
        ksort($arr);
130 12
        foreach ($arr as &$wordsByLen) {
131 12
            ksort($wordsByLen);
132 12
            foreach ($wordsByLen as &$words) {
133 12
                sort($words, SORT_STRING);
134
            }
135
        }
136
137 12
        return $arr;
138
    }
139
140
    /**
141
     * Reads a list of words and sorts it by type, length and keyword.
142
     *
143
     * @param string[] $files
144
     *
145
     * @return array<int, array<int, array<int, string>>>
146
     */
147 8
    public static function readWords(array $files)
148
    {
149 8
        $words = [];
150 8
        foreach ($files as $file) {
151 8
            $words = array_merge($words, file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES));
152
        }
153
154
        /** @var array<string, int> $types */
155 8
        $types = [];
156
157 8
        for ($i = 0, $count = count($words); $i !== $count; ++$i) {
158 8
            $type = 1;
159 8
            $value = trim($words[$i]);
160
161
            // Reserved, data types, keys, functions, etc. keywords.
162 8
            foreach (static::$LABELS_FLAGS as $label => $flags) {
163 8
                if (strstr($value, $label) === false) {
164 8
                    continue;
165
                }
166
167 8
                $type |= $flags;
168 8
                $value = trim(str_replace($label, '', $value));
169
            }
170
171
            // Composed keyword.
172 8
            if (strstr($value, ' ') !== false) {
173 8
                $type |= 2; // Reserved keyword.
174 8
                $type |= 4; // Composed keyword.
175
            }
176
177 8
            $len = strlen($words[$i]);
178 8
            if ($len === 0) {
179
                continue;
180
            }
181
182 8
            $value = strtoupper($value);
183 8
            if (! isset($types[$value])) {
184 8
                $types[$value] = $type;
185
            } else {
186 8
                $types[$value] |= $type;
187
            }
188
        }
189
190 8
        $ret = [];
191 8
        foreach ($types as $word => $type) {
192 8
            $len = strlen($word);
193 8
            if (! isset($ret[$type])) {
194 8
                $ret[$type] = [];
195
            }
196
197 8
            if (! isset($ret[$type][$len])) {
198 8
                $ret[$type][$len] = [];
199
            }
200
201 8
            $ret[$type][$len][] = $word;
202
        }
203
204 8
        return static::sortWords($ret);
205
    }
206
207
    /**
208
     * Prints an array of a words in PHP format.
209
     *
210
     * @param array<int, array<int, array<int, string>>> $words  the list of words to be formatted
211
     * @param int                                        $spaces the number of spaces that starts every line
212
     * @param int                                        $line   the length of a line
213
     *
214
     * @return string
215
     */
216 4
    public static function printWords($words, $spaces = 8, $line = 140)
217
    {
218 4
        $typesCount = count($words);
219 4
        $ret = '';
220 4
        $j = 0;
221
222 4
        foreach ($words as $type => $wordsByType) {
223 4
            foreach ($wordsByType as $len => $wordsByLen) {
224 4
                $count = round(($line - $spaces) / ($len + 9)); // strlen("'' => 1, ") = 9
225 4
                $i = 0;
226
227 4
                foreach ($wordsByLen as $word) {
228 4
                    if ($i === 0) {
229 4
                        $ret .= str_repeat(' ', $spaces);
230
                    }
231
232 4
                    $ret .= sprintf('\'%s\' => %s, ', $word, $type);
233 4
                    if (++$i !== $count && ++$i <= $count) {
234 4
                        continue;
235
                    }
236
237 4
                    $ret .= "\n";
238 4
                    $i = 0;
239
                }
240
241 4
                if ($i === 0) {
242 4
                    continue;
243
                }
244
245 4
                $ret .= "\n";
246
            }
247
248 4
            if (++$j >= $typesCount) {
249 4
                continue;
250
            }
251
252 4
            $ret .= "\n";
253
        }
254
255
        // Trim trailing spaces and return.
256 4
        return str_replace(" \n", "\n", $ret);
257
    }
258
259
    /**
260
     * Generates a context's class.
261
     *
262
     * @param array<string, string|array<int, array<int, array<int, string>>>> $options the options for this context
263
     * @psalm-param array{
264
     *   name: string,
265
     *   class: string,
266
     *   link: string,
267
     *   keywords: array<int, array<int, array<int, string>>>
268
     * } $options
269
     *
270
     * @return string
271
     */
272 4
    public static function generate($options)
273
    {
274 4
        if (isset($options['keywords'])) {
275 4
            $options['keywords'] = static::printWords($options['keywords']);
0 ignored issues
show
Bug introduced by
It seems like $options['keywords'] can also be of type string; however, parameter $words of PhpMyAdmin\SqlParser\Too...Generator::printWords() does only seem to accept array<integer,array<inte...array<integer,string>>>, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

275
            $options['keywords'] = static::printWords(/** @scrutinizer ignore-type */ $options['keywords']);
Loading history...
276
        }
277
278 4
        return sprintf(self::TEMPLATE, $options['name'], $options['class'], $options['link'], $options['keywords']);
279
    }
280
281
    /**
282
     * Formats context name.
283
     *
284
     * @param string $name name to format
285
     *
286
     * @return string
287
     */
288 4
    public static function formatName($name)
289
    {
290
        /* Split name and version */
291 4
        $parts = [];
292 4
        if (preg_match('/([^[0-9]*)([0-9]*)/', $name, $parts) === false) {
293
            return $name;
294
        }
295
296
        /* Format name */
297 4
        $base = $parts[1];
298 1
        switch ($base) {
299 4
            case 'MySql':
300 4
                $base = 'MySQL';
301 4
                break;
302 4
            case 'MariaDb':
303 4
                $base = 'MariaDB';
304 4
                break;
305
        }
306
307
        /* Parse version to array */
308 4
        $versionString = $parts[2];
309 4
        if (strlen($versionString) % 2 === 1) {
310 4
            $versionString = '0' . $versionString;
311
        }
312
313 4
        $version = array_map('intval', str_split($versionString, 2));
0 ignored issues
show
Bug introduced by
It seems like str_split($versionString, 2) can also be of type true; however, parameter $array of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

313
        $version = array_map('intval', /** @scrutinizer ignore-type */ str_split($versionString, 2));
Loading history...
314
        /* Remove trailing zero */
315 4
        if ($version[count($version) - 1] === 0) {
316 4
            $version = array_slice($version, 0, count($version) - 1);
317
        }
318
319
        /* Create name */
320 4
        return $base . ' ' . implode('.', $version);
321
    }
322
323
    /**
324
     * Builds a test.
325
     *
326
     * Reads the input file, generates the data and writes it back.
327
     *
328
     * @param string $input  the input file
329
     * @param string $output the output directory
330
     *
331
     * @return void
332
     */
333
    public static function build($input, $output)
334
    {
335
        /**
336
         * The directory that contains the input file.
337
         *
338
         * Used to include common files.
339
         *
340
         * @var string
341
         */
342
        $directory = dirname($input) . '/';
343
344
        /**
345
         * The name of the file that contains the context.
346
         */
347
        $file = basename($input);
348
349
        /**
350
         * The name of the context.
351
         *
352
         * @var string
353
         */
354
        $name = substr($file, 0, -4);
355
356
        /**
357
         * The name of the class that defines this context.
358
         *
359
         * @var string
360
         */
361
        $class = 'Context' . $name;
362
363
        /**
364
         * The formatted name of this context.
365
         */
366
        $formattedName = static::formatName($name);
367
368
        file_put_contents(
369
            $output . '/' . $class . '.php',
370
            static::generate(
371
                [
0 ignored issues
show
Bug introduced by
array('name' => $formatt..., $directory . $file))) of type array<string,array<integ...teger,string>>>|string> is incompatible with the type array<string,array<integ...teger,string>>>|string> expected by parameter $options of PhpMyAdmin\SqlParser\Too...xtGenerator::generate(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

371
                /** @scrutinizer ignore-type */ [
Loading history...
372
                    'name' => $formattedName,
373
                    'class' => $class,
374
                    'link' => static::$LINKS[$name],
375
                    'keywords' => static::readWords(
376
                        [
377
                            $directory . '_common.txt',
378
                            $directory . '_functions' . $file,
379
                            $directory . $file,
380
                        ]
381
                    ),
382
                ]
383
            )
384
        );
385
    }
386
387
    /**
388
     * Generates recursively all tests preserving the directory structure.
389
     *
390
     * @param string $input  the input directory
391
     * @param string $output the output directory
392
     *
393
     * @return void
394
     */
395
    public static function buildAll($input, $output)
396
    {
397
        $files = scandir($input);
398
399
        foreach ($files as $file) {
400
            // Skipping current and parent directories.
401
            if (($file[0] === '.') || ($file[0] === '_')) {
402
                continue;
403
            }
404
405
            // Building the context.
406
            echo sprintf("Building context for %s...\n", $file);
407
            static::build($input . '/' . $file, $output);
408
        }
409
    }
410
}
411