1+ <?php
2+ /**
3+ * Copyright © Magento, Inc. All rights reserved.
4+ * See COPYING.txt for license details.
5+ */
6+ declare (strict_types=1 );
7+
8+ namespace Magento \TestFramework \CodingStandard \Tool ;
9+
10+ use Magento \TestFramework \CodingStandard \ToolInterface ;
11+ use SebastianBergmann \FileIterator \Facade ;
12+ use SebastianBergmann \PHPCPD \Detector \Detector ;
13+ use SebastianBergmann \PHPCPD \Detector \Strategy \DefaultStrategy ;
14+ use SebastianBergmann \PHPCPD \Log \PMD ;
15+ use SebastianBergmann \PHPCPD \Log \Text ;
16+ use Symfony \Component \Finder \Finder ;
17+
18+ /**
19+ * PHP Copy Paste Detector tool wrapper
20+ */
21+ class LiveCodePhpcpdRunner implements ToolInterface, BlacklistInterface
22+ {
23+ /**
24+ * Minimum number of equal lines to identify a copy paste snippet
25+ */
26+ private const MIN_LINES = 13 ;
27+
28+ /**
29+ * Destination file to write inspection report to
30+ *
31+ * @var string
32+ */
33+ private $ reportFile ;
34+
35+ /**
36+ * List of paths to be excluded from tool run
37+ *
38+ * @var array
39+ */
40+ private $ blacklist ;
41+
42+ /**
43+ * @param string $reportFile
44+ */
45+ public function __construct (string $ reportFile )
46+ {
47+ $ this ->reportFile = $ reportFile ;
48+ }
49+
50+ /**
51+ * @inheritdoc
52+ */
53+ public function setBlackList (array $ blackList ): void
54+ {
55+ $ this ->blacklist = $ blackList ;
56+ }
57+
58+ /**
59+ * Whether the tool can be run in the current environment
60+ *
61+ * @SuppressWarnings(PHPMD.UnusedLocalVariable)
62+ *
63+ * @return bool
64+ */
65+ public function canRun (): bool
66+ {
67+ return class_exists (Detector::class)
68+ && class_exists (Facade::class)
69+ && class_exists (Finder::class);
70+ }
71+
72+ /**
73+ * Run tool for files specified
74+ *
75+ * @param array $whiteList Files/directories to be inspected
76+ * @return bool
77+ */
78+ public function run (array $ whiteList ): bool
79+ {
80+ $ clones = (new Detector (new DefaultStrategy ()))->copyPasteDetection (
81+ (new Facade ())->getFilesAsArray (
82+ $ whiteList ,
83+ '' ,
84+ '' ,
85+ $ this ->getExclude ()
86+ ),
87+ self ::MIN_LINES
88+ );
89+
90+ (new PMD ($ this ->reportFile ))->processClones ($ clones );
91+ (new Text )->printResult ($ clones , false );
92+
93+ return count ($ clones ) === 0 ;
94+ }
95+
96+ /**
97+ * Get exclude params from blacklist
98+ *
99+ * @return string[]
100+ */
101+ private function getExclude (): array
102+ {
103+ $ exclude = [];
104+ $ blacklistedDirs = [];
105+ $ blacklistedFileNames = [];
106+ $ blacklistedPatterns = [];
107+ foreach ($ this ->blacklist as $ file ) {
108+ $ file = trim ($ file );
109+ if (!$ file ) {
110+ continue ;
111+ }
112+ $ realPath = realpath (BP . '/ ' . $ file );
113+ if ($ realPath === false ) {
114+ $ ext = pathinfo ($ file , PATHINFO_EXTENSION );
115+ if ($ ext != '' ) {
116+ $ blacklistedFileNames [] = $ file ;
117+ } else {
118+ $ blacklistedPatterns [] = $ file ;
119+ }
120+ continue ;
121+ }
122+
123+ $ exclude [] = [$ realPath ];
124+ $ blacklistedDirs [] = $ file ;
125+ }
126+
127+ foreach ($ blacklistedPatterns as $ pattern ) {
128+ $ files = $ this ->find ($ pattern , false , $ blacklistedDirs );
129+ if (empty ($ files )) {
130+ continue ;
131+ }
132+ $ exclude [] = $ files ;
133+ }
134+
135+
136+ foreach ($ blacklistedFileNames as $ fileName ) {
137+ $ files = $ this ->find ($ fileName , true , $ blacklistedDirs );
138+ if (empty ($ files )) {
139+ continue ;
140+ }
141+ $ exclude [] = $ files ;
142+ }
143+
144+ return array_unique (array_merge (...$ exclude ));
145+ }
146+
147+ /**
148+ * Find all files by pattern
149+ *
150+ * @param string $pattern
151+ * @param bool $searchFiles
152+ * @param array $excludePaths
153+ * @return array
154+ */
155+ private function find (string $ pattern , bool $ searchFiles , array $ excludePaths ): array
156+ {
157+ $ finder = new Finder ();
158+ $ finder ->in (BP );
159+ $ finder ->notPath ($ excludePaths );
160+ if ($ searchFiles ) {
161+ $ finder ->files ();
162+ $ finder ->name ($ pattern );
163+ } else {
164+ $ finder ->path ($ pattern );
165+ }
166+
167+ $ result = [];
168+ foreach ($ finder as $ file ) {
169+ $ result [] = $ file ->getRealPath ();
170+ }
171+
172+ return $ result ;
173+ }
174+ }
0 commit comments