44
55use Desilva \Console \Console ;
66
7+ require_once __DIR__ .'/includes/contracts.php ' ;
8+ require_once __DIR__ .'/includes/helpers.php ' ;
9+
710/**
8- * @internal
11+ * @internal Custom static analysis tool for the HydePHP Development Monorepo.
912 */
1013final class HydeStan
1114{
12- const VERSION = '0.0.0-dev ' ;
15+ private const FILE_ANALYSERS = [
16+ NoFixMeAnalyser::class,
17+ UnImportedFunctionAnalyser::class,
18+ ];
19+
20+ private const TEST_FILE_ANALYSERS = [
21+ NoFixMeAnalyser::class,
22+ NoUsingAssertEqualsForScalarTypesTestAnalyser::class,
23+ ];
24+
25+ private const LINE_ANALYSERS = [
26+ NoTestReferenceAnalyser::class,
27+ NoHtmlExtensionInHydePHPLinksAnalyser::class,
28+ NoExtraWhitespaceInCompressedPhpDocAnalyser::class,
29+ ];
1330
1431 private array $ files ;
1532 private array $ testFiles ;
@@ -31,7 +48,7 @@ public function __construct(private readonly bool $debug = false)
3148
3249 $ this ->console = new Console ();
3350
34- $ this ->console ->info (sprintf ( 'HydeStan v%s is running! ' , self :: VERSION ) );
51+ $ this ->console ->info ('HydeStan is running! ' );
3552 $ this ->console ->newline ();
3653 }
3754
@@ -105,64 +122,38 @@ public function addErrors(array $errors): void
105122
106123 private function getFiles (): array
107124 {
108- $ files = [];
109-
110- $ directory = new RecursiveDirectoryIterator (BASE_PATH .'/src ' );
111- $ iterator = new RecursiveIteratorIterator ($ directory );
112- $ regex = new RegexIterator ($ iterator , '/^.+\.php$/i ' , RecursiveRegexIterator::GET_MATCH );
113-
114- foreach ($ regex as $ file ) {
115- $ files [] = substr ($ file [0 ], strlen (BASE_PATH ) + 1 );
116- }
117-
118- return $ files ;
125+ return recursiveFileFinder ('src ' );
119126 }
120127
121128 private function getTestFiles (): array
122129 {
123- $ files = [];
124-
125- $ directory = new RecursiveDirectoryIterator (BASE_PATH .'/tests ' );
126- $ iterator = new RecursiveIteratorIterator ($ directory );
127- $ regex = new RegexIterator ($ iterator , '/^.+\.php$/i ' , RecursiveRegexIterator::GET_MATCH );
128-
129- foreach ($ regex as $ file ) {
130- $ files [] = substr ($ file [0 ], strlen (BASE_PATH ) + 1 );
131- }
132-
133- return $ files ;
130+ return recursiveFileFinder ('tests ' );
134131 }
135132
136133 private function analyseFile (string $ file , string $ contents ): void
137134 {
138- $ fileAnalysers = [
139- new NoFixMeAnalyser ($ file , $ contents ),
140- new UnImportedFunctionAnalyser ($ file , $ contents ),
141- ];
135+ foreach (self ::FILE_ANALYSERS as $ fileAnalyserClass ) {
136+ $ fileAnalyser = new $ fileAnalyserClass ($ file , $ contents );
142137
143- foreach ($ fileAnalysers as $ analyser ) {
144138 if ($ this ->debug ) {
145- $ this ->console ->debugComment ('Running ' .$ analyser ::class);
139+ $ this ->console ->debugComment ('Running ' .$ fileAnalyser ::class);
146140 }
147141
148- $ analyser ->run ($ file , $ contents );
142+ $ fileAnalyser ->run ($ file , $ contents );
149143 AnalysisStatisticsContainer::countedLines (substr_count ($ contents , "\n" ));
150144
151145 foreach (explode ("\n" , $ contents ) as $ lineNumber => $ line ) {
152- $ lineAnalysers = [
153- new NoTestReferenceAnalyser ($ file , $ lineNumber , $ line ),
154- ];
155-
156- foreach ($ lineAnalysers as $ analyser ) {
146+ foreach (self ::LINE_ANALYSERS as $ lineAnalyserClass ) {
147+ $ lineAnalyser = new $ lineAnalyserClass ($ file , $ lineNumber , $ line );
157148 AnalysisStatisticsContainer::countedLine ();
158- $ analyser ->run ($ file , $ lineNumber , $ line );
149+ $ lineAnalyser ->run ($ file , $ lineNumber , $ line );
159150 $ this ->aggregateLines ++;
160151 }
161152 }
162153 }
163154
164155 $ this ->scannedLines += substr_count ($ contents , "\n" );
165- $ this ->aggregateLines += (substr_count ($ contents , "\n" ) * count ($ fileAnalysers ));
156+ $ this ->aggregateLines += (substr_count ($ contents , "\n" ) * count (self :: FILE_ANALYSERS ));
166157 }
167158
168159 private function getFileContents (string $ file ): string
@@ -197,58 +188,23 @@ protected function runTestStan(): void
197188
198189 private function analyseTestFile (string $ file , string $ contents ): void
199190 {
200- $ fileAnalysers = [
201- new NoFixMeAnalyser ($ file , $ contents ),
202- new NoUsingAssertEqualsForScalarTypesTestAnalyser ($ file , $ contents ),
203- ];
191+ foreach (self ::TEST_FILE_ANALYSERS as $ fileAnalyserClass ) {
192+ $ fileAnalyser = new $ fileAnalyserClass ($ file , $ contents );
204193
205- foreach ($ fileAnalysers as $ analyser ) {
206194 if ($ this ->debug ) {
207- $ this ->console ->debugComment ('Running ' .$ analyser ::class);
195+ $ this ->console ->debugComment ('Running ' .$ fileAnalyser ::class);
208196 }
209197
210- $ analyser ->run ($ file , $ contents );
198+ $ fileAnalyser ->run ($ file , $ contents );
211199 AnalysisStatisticsContainer::countedLines (substr_count ($ contents , "\n" ));
212200
213201 foreach (explode ("\n" , $ contents ) as $ lineNumber => $ line ) {
214- $ lineAnalysers = [
215- //
216- ];
217-
218- foreach ($ lineAnalysers as $ analyser ) {
219- AnalysisStatisticsContainer::countedLine ();
220- $ analyser ->run ($ file , $ lineNumber , $ line );
221- $ this ->aggregateLines ++;
222- }
202+ // No line analysers defined for test files in the original code
223203 }
224204 }
225205
226206 $ this ->scannedLines += substr_count ($ contents , "\n" );
227- $ this ->aggregateLines += (substr_count ($ contents , "\n" ) * count ($ fileAnalysers ));
228- }
229- }
230-
231- abstract class Analyser
232- {
233- protected function fail (string $ error ): void
234- {
235- HydeStan::getInstance ()->addError ($ error );
236- }
237- }
238-
239- abstract class FileAnalyser extends Analyser implements FileAnalyserContract
240- {
241- public function __construct (protected string $ file , protected string $ contents )
242- {
243- //
244- }
245- }
246-
247- abstract class LineAnalyser extends Analyser implements LineAnalyserContract
248- {
249- public function __construct (protected string $ file , protected int $ lineNumber , protected string $ line )
250- {
251- //
207+ $ this ->aggregateLines += (substr_count ($ contents , "\n" ) * count (self ::TEST_FILE_ANALYSERS ));
252208 }
253209}
254210
@@ -281,6 +237,40 @@ public function run(string $file, string $contents): void
281237 }
282238}
283239
240+ class NoHtmlExtensionInHydePHPLinksAnalyser extends LineAnalyser
241+ {
242+ public function run (string $ file , int $ lineNumber , string $ line ): void
243+ {
244+ AnalysisStatisticsContainer::analysedExpressions (1 );
245+
246+ if (str_contains ($ line , 'https://hydephp.com/ ' ) && str_contains ($ line , '.html ' )) {
247+ AnalysisStatisticsContainer::analysedExpressions (1 );
248+
249+ $ this ->fail (sprintf ('HTML extension used in URL at %s ' ,
250+ fileLink (BASE_PATH .'/packages/framework/ ' .$ file , $ lineNumber + 1 )
251+ ));
252+
253+ HydeStan::addActionsMessage ('warning ' , $ file , $ lineNumber + 1 , 'HydeStan: NoHtmlExtensionError ' , 'URL contains .html extension. Consider removing it. ' );
254+ }
255+ }
256+ }
257+
258+ class NoExtraWhitespaceInCompressedPhpDocAnalyser extends LineAnalyser
259+ {
260+ public function run (string $ file , int $ lineNumber , string $ line ): void
261+ {
262+ AnalysisStatisticsContainer::analysedExpressions (1 );
263+
264+ if (str_contains ($ line , '/** ' )) {
265+ $ this ->fail (sprintf ('Extra whitespace in compressed PHPDoc comment at %s ' ,
266+ fileLink (BASE_PATH .'/packages/framework/ ' .$ file , $ lineNumber + 1 )
267+ ));
268+
269+ HydeStan::addActionsMessage ('warning ' , $ file , $ lineNumber + 1 , 'HydeStan: ExtraWhitespaceInPhpDocError ' , 'Extra whitespace found in compressed PHPDoc comment. ' );
270+ }
271+ }
272+ }
273+
284274class NoUsingAssertEqualsForScalarTypesTestAnalyser extends FileAnalyser // Todo: Extend line analyser instead? Would allow for checking for more errors after the first error
285275{
286276 public function run (string $ file , string $ contents ): void
@@ -370,7 +360,7 @@ public function run(string $file, string $contents): void
370360 foreach ($ calledFunctions as $ calledFunction ) {
371361 AnalysisStatisticsContainer::analysedExpression ();
372362 if (! in_array ($ calledFunction , $ functionImports )) {
373- echo ("Found unimported function ' $ calledFunction' in " . realpath (__DIR__ .'/../../packages/framework/ ' .$ file )). "\n" ;
363+ echo sprintf ("Found unimported function ' $ calledFunction' in %s \n" , realpath (__DIR__ .'/../../packages/framework/ ' .$ file ));
374364 }
375365 }
376366 }
@@ -384,80 +374,11 @@ public function run(string $file, int $lineNumber, string $line): void
384374
385375 if (str_starts_with ($ line , ' * @see ' ) && str_ends_with ($ line , 'Test ' )) {
386376 AnalysisStatisticsContainer::analysedExpressions (1 );
387- $ this ->fail (sprintf ('Test class %s is referenced in %s:%s ' , trim (substr ($ line , 7 )),
388- realpath (__DIR__ .'/../../packages/framework/ ' .$ file ) ?: $ file , $ lineNumber + 1 ));
389- }
390- }
391- }
392-
393- class AnalysisStatisticsContainer
394- {
395- private static int $ linesCounted = 0 ;
396- private static float $ expressionsAnalysed = 0 ;
397-
398- public static function countedLine (): void
399- {
400- self ::$ linesCounted ++;
401- }
402-
403- public static function countedLines (int $ count ): void
404- {
405- self ::$ linesCounted += $ count ;
406- }
407-
408- public static function analysedExpression (): void
409- {
410- self ::$ expressionsAnalysed ++;
411- }
412-
413- public static function analysedExpressions (float $ countOrEstimate ): void
414- {
415- self ::$ expressionsAnalysed += $ countOrEstimate ;
416- }
417-
418- public static function getLinesCounted (): int
419- {
420- return self ::$ linesCounted ;
421- }
422-
423- public static function getExpressionsAnalysed (): int
424- {
425- return (int ) round (self ::$ expressionsAnalysed );
426- }
427- }
428-
429- interface FileAnalyserContract
430- {
431- public function __construct (string $ file , string $ contents );
432-
433- public function run (string $ file , string $ contents ): void ;
434- }
435-
436- interface LineAnalyserContract
437- {
438- public function __construct (string $ file , int $ lineNumber , string $ line );
439-
440- public function run (string $ file , int $ lineNumber , string $ line ): void ;
441- }
442-
443- function check_str_contains_any (array $ searches , string $ line ): bool
444- {
445- $ strContainsAny = false ;
446- foreach ($ searches as $ search ) {
447- AnalysisStatisticsContainer::analysedExpression ();
448- if (str_contains ($ line , $ search )) {
449- $ strContainsAny = true ;
377+ $ this ->fail (sprintf ('Test class %s is referenced in %s:%s ' ,
378+ trim (substr ($ line , 7 )),
379+ realpath (__DIR__ .'/../../packages/framework/ ' .$ file ) ?: $ file ,
380+ $ lineNumber + 1
381+ ));
450382 }
451383 }
452-
453- return $ strContainsAny ;
454- }
455-
456- function fileLink (string $ file , ?int $ line = null ): string
457- {
458- $ path = (realpath (__DIR__ .'/../../packages/framework/ ' .$ file ) ?: $ file ).($ line ? ': ' .$ line : '' );
459- $ trim = strlen (getcwd ()) + 2 ;
460- $ path = substr ($ path , $ trim );
461-
462- return str_replace ('\\' , '/ ' , $ path );
463384}
0 commit comments