Skip to content

Commit

Permalink
Merge branch 'master' of github.com:hydephp/develop into 2.x-dev
Browse files Browse the repository at this point in the history
  • Loading branch information
caendesilva committed Jul 7, 2024
2 parents 53a46d5 + 254bbe7 commit ebe55fb
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 92 deletions.
116 changes: 114 additions & 2 deletions monorepo/HydeStan/HydeStan.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ final class HydeStan
const VERSION = '0.0.0-dev';

private array $files;
private array $testFiles;
private array $errors = [];
private int $scannedLines = 0;
private int $aggregateLines = 0;
Expand All @@ -37,10 +38,13 @@ public function __construct(private readonly bool $debug = false)
public function __destruct()
{
$this->console->newline();
$this->console->info(sprintf('HydeStan has exited after scanning %s total (and %s aggregate) lines in %s files. Total expressions analysed: %s',
$this->console->info(sprintf('HydeStan has exited after scanning %s total (and %s aggregate) lines in %s files.',
number_format($this->scannedLines),
number_format($this->aggregateLines),
number_format(count($this->files)),
number_format(count($this->files) + count($this->testFiles)),
));

$this->console->info(sprintf('Total expressions analysed: %s',
number_format(AnalysisStatisticsContainer::getExpressionsAnalysed()),
));

Expand All @@ -64,6 +68,8 @@ public function run(): void
$this->analyseFile($file, $this->getFileContents($file));
}

$this->runTestStan();

$endTime = microtime(true) - $time;
$this->console->info(sprintf('HydeStan has finished in %s seconds (%sms) using %s KB RAM',
number_format($endTime, 2),
Expand Down Expand Up @@ -112,6 +118,21 @@ private function getFiles(): array
return $files;
}

private function getTestFiles(): array
{
$files = [];

$directory = new RecursiveDirectoryIterator(BASE_PATH.'/tests');
$iterator = new RecursiveIteratorIterator($directory);
$regex = new RegexIterator($iterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH);

foreach ($regex as $file) {
$files[] = substr($file[0], strlen(BASE_PATH) + 1);
}

return $files;
}

private function analyseFile(string $file, string $contents): void
{
$fileAnalysers = [
Expand Down Expand Up @@ -160,6 +181,51 @@ public static function addActionsMessage(string $level, string $file, int $lineN
// $template = '::warning file={name},line={line},endLine={endLine},title={title}::{message}';
self::$warnings[] = sprintf("::$level file=%s,line=%s,endLine=%s,title=%s::%s", 'packages/framework/'.str_replace('\\', '/', $file), $lineNumber, $lineNumber, $title, $message);
}

protected function runTestStan(): void
{
$this->console->info('TestStan: Analyzing test files...');

$this->testFiles = $this->getTestFiles();

foreach ($this->testFiles as $file) {
$this->analyseTestFile($file, $this->getFileContents($file));
}

$this->console->info('TestStan: Finished analyzing test files!');
}

private function analyseTestFile(string $file, string $contents): void
{
$fileAnalysers = [
new NoFixMeAnalyser($file, $contents),
new NoUsingAssertEqualsForScalarTypesTestAnalyser($file, $contents),
];

foreach ($fileAnalysers as $analyser) {
if ($this->debug) {
$this->console->debugComment('Running '.$analyser::class);
}

$analyser->run($file, $contents);
AnalysisStatisticsContainer::countedLines(substr_count($contents, "\n"));

foreach (explode("\n", $contents) as $lineNumber => $line) {
$lineAnalysers = [
//
];

foreach ($lineAnalysers as $analyser) {
AnalysisStatisticsContainer::countedLine();
$analyser->run($file, $lineNumber, $line);
$this->aggregateLines++;
}
}
}

$this->scannedLines += substr_count($contents, "\n");
$this->aggregateLines += (substr_count($contents, "\n") * count($fileAnalysers));
}
}

abstract class Analyser
Expand Down Expand Up @@ -215,6 +281,39 @@ public function run(string $file, string $contents): void
}
}

class NoUsingAssertEqualsForScalarTypesTestAnalyser extends FileAnalyser // Todo: Extend line analyser instead? Would allow for checking for more errors after the first error
{
public function run(string $file, string $contents): void
{
$searches = [
"assertEquals('",
];

foreach ($searches as $search) {
AnalysisStatisticsContainer::analysedExpression();

if (str_contains($contents, $search)) {
// Get line number of marker by counting new \n tags before it
$stringBeforeMarker = substr($contents, 0, strpos($contents, $search));
$lineNumber = substr_count($stringBeforeMarker, "\n") + 1;

// Get the line contents
$line = explode("\n", $contents)[$lineNumber - 1];

// Check for false positives
$commonlyStringCastables = ['$article', '$document', 'getXmlElement()', '$url->loc', '$page->markdown', '$post->data(\'author\')'];

if (check_str_contains_any($commonlyStringCastables, $line)) {
continue;
}

// Todo: Does not work when using objects to string cast, false positive, maybe use warning instead of fail
$this->fail(sprintf('Found %s instead assertSame for scalar type in %s on line %s', trim($search, "()'"), $file, $lineNumber));
}
}
}
}

class UnImportedFunctionAnalyser extends FileAnalyser
{
public function run(string $file, string $contents): void
Expand Down Expand Up @@ -319,3 +418,16 @@ public function __construct(string $file, int $lineNumber, string $line);

public function run(string $file, int $lineNumber, string $line): void;
}

function check_str_contains_any(array $searches, string $line): bool
{
$strContainsAny = false;
foreach ($searches as $search) {
AnalysisStatisticsContainer::analysedExpression();
if (str_contains($line, $search)) {
$strContainsAny = true;
}
}

return $strContainsAny;
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,22 @@ public function testDiscoveredShortcodesAreUsedToProcessInput()
{
$processor = new ShortcodeProcessor('>info foo');

$this->assertEquals('<blockquote class="info"><p>foo</p></blockquote>',
$processor->run());
$this->assertSame('<blockquote class="info"><p>foo</p></blockquote>', $processor->run());
}

public function testStringWithoutShortcodeIsNotModified()
{
$processor = new ShortcodeProcessor('foo');

$this->assertEquals('foo', $processor->run());
$this->assertSame('foo', $processor->run());
}

public function testProcessStaticShorthand()
{
$this->assertEquals('<blockquote class="info"><p>foo</p></blockquote>',
ShortcodeProcessor::preprocess('>info foo'));
$this->assertSame(
'<blockquote class="info"><p>foo</p></blockquote>',
ShortcodeProcessor::preprocess('>info foo')
);
}

public function testShortcodesCanBeAddedToProcessor()
Expand All @@ -60,7 +61,7 @@ public static function resolve(string $input): string
});

$this->assertArrayHasKey('foo', $processor->getShortcodes());
$this->assertEquals('bar', $processor->run());
$this->assertSame('bar', $processor->run());
}

public function testShortcodesCanBeAddedToProcessorUsingArray()
Expand All @@ -81,6 +82,6 @@ public static function resolve(string $input): string
}]);

$this->assertArrayHasKey('foo', $processor->getShortcodes());
$this->assertEquals('bar', $processor->run());
$this->assertSame('bar', $processor->run());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public function testServiceInstantiatesXmlElement()
public function testXmlRootElementIsSetToRss20()
{
$service = new RssFeedGenerator();
$this->assertEquals('rss', $service->getXmlElement()->getName());
$this->assertSame('rss', $service->getXmlElement()->getName());
$this->assertEquals('2.0', $service->getXmlElement()->attributes()->version);
}

Expand Down
6 changes: 3 additions & 3 deletions packages/framework/tests/Feature/Support/ProjectFileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@ public function testCanConstruct()
$this->assertSame('foo', $file->path);
}

public function can_make()
public function testCanMake()
{
$this->assertEquals(new ProjectFileTestClass('foo'), ProjectFileTestClass::make('foo'));
}

public function testCanConstructWithNestedPaths()
{
$this->assertEquals('path/to/file.txt', ProjectFileTestClass::make('path/to/file.txt')->path);
$this->assertSame('path/to/file.txt', ProjectFileTestClass::make('path/to/file.txt')->path);
}

public function testAbsolutePathIsNormalizedToRelative()
{
$this->assertEquals('foo', ProjectFileTestClass::make(Hyde::path('foo'))->path);
$this->assertSame('foo', ProjectFileTestClass::make(Hyde::path('foo'))->path);
}

public function testGetNameReturnsNameOfFile()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,126 +31,126 @@ protected function setUp(): void

public function testHelperReturnsStringAsIsIfCurrentIsNotSet()
{
$this->assertEquals('foo/bar.html', Hyde::relativeLink('foo/bar.html'));
$this->assertSame('foo/bar.html', Hyde::relativeLink('foo/bar.html'));
}

public function testHelperInjectsProperNumberOfDoublesSlash()
{
$this->mockCurrentPage('foo/bar.html');
$this->assertEquals('../foo.html', Hyde::relativeLink('foo.html'));
$this->assertSame('../foo.html', Hyde::relativeLink('foo.html'));
}

public function testHelperInjectsProperNumberOfDoublesSlashForDeeplyNestedPaths()
{
$this->mockCurrentPage('foo/bar/baz/qux.html');
$this->assertEquals('../../../foo.html', Hyde::relativeLink('foo.html'));
$this->assertSame('../../../foo.html', Hyde::relativeLink('foo.html'));
}

public function testHelperHandlesDestinationWithoutFileExtension()
{
$this->mockCurrentPage('foo/bar.html');
$this->assertEquals('../foo', Hyde::relativeLink('foo'));
$this->assertSame('../foo', Hyde::relativeLink('foo'));
}

public function testHelperHandlesCurrentWithoutFileExtension()
{
$this->mockCurrentPage('foo/bar');
$this->assertEquals('../foo.html', Hyde::relativeLink('foo.html'));
$this->assertSame('../foo.html', Hyde::relativeLink('foo.html'));
}

public function testHelperHandlesCaseWithoutAnyFileExtensions()
{
$this->mockCurrentPage('foo/bar');
$this->assertEquals('../foo', Hyde::relativeLink('foo'));
$this->assertSame('../foo', Hyde::relativeLink('foo'));
}

public function testHelperHandlesCaseWithMixedFileExtensions()
{
$this->mockCurrentPage('foo/bar.md');
$this->assertEquals('../foo.md', Hyde::relativeLink('foo.md'));
$this->assertSame('../foo.md', Hyde::relativeLink('foo.md'));
$this->mockCurrentPage('foo/bar.txt');
$this->assertEquals('../foo.txt', Hyde::relativeLink('foo.txt'));
$this->assertSame('../foo.txt', Hyde::relativeLink('foo.txt'));
}

public function testHelperHandlesDifferentFileExtensions()
{
$this->mockCurrentPage('foo/bar');
$this->assertEquals('../foo.png', Hyde::relativeLink('foo.png'));
$this->assertEquals('../foo.css', Hyde::relativeLink('foo.css'));
$this->assertEquals('../foo.js', Hyde::relativeLink('foo.js'));
$this->assertSame('../foo.png', Hyde::relativeLink('foo.png'));
$this->assertSame('../foo.css', Hyde::relativeLink('foo.css'));
$this->assertSame('../foo.js', Hyde::relativeLink('foo.js'));
}

public function testHelperReturnsPrettyUrlIfEnabledAndDestinationIsAHtmlFile()
{
self::mockConfig(['hyde.pretty_urls' => true]);
$this->mockCurrentPage('foo/bar.html');
$this->assertEquals('../foo', Hyde::relativeLink('foo.html'));
$this->assertSame('../foo', Hyde::relativeLink('foo.html'));
}

public function testHelperMethodDoesNotRequireCurrentPathToBeHtmlToUsePrettyUrls()
{
self::mockConfig(['hyde.pretty_urls' => true]);
$this->mockCurrentPage('foo/bar');
$this->assertEquals('../foo', Hyde::relativeLink('foo.html'));
$this->assertSame('../foo', Hyde::relativeLink('foo.html'));
}

public function testHelperReturnsDoesNotReturnPrettyUrlIfWhenEnabledButAndDestinationIsNotAHtmlFile()
{
self::mockConfig(['hyde.pretty_urls' => true]);
$this->mockCurrentPage('foo/bar.html');
$this->assertEquals('../foo.png', Hyde::relativeLink('foo.png'));
$this->assertSame('../foo.png', Hyde::relativeLink('foo.png'));
}

public function testHelperRewritesIndexWhenUsingPrettyUrls()
{
self::mockConfig(['hyde.pretty_urls' => true]);
$this->mockCurrentPage('foo.html');
$this->assertEquals('./', Hyde::relativeLink('index.html'));
$this->assertSame('./', Hyde::relativeLink('index.html'));
$this->mockCurrentPage('foo/bar.html');
$this->assertEquals('../', Hyde::relativeLink('index.html'));
$this->assertSame('../', Hyde::relativeLink('index.html'));
$this->mockCurrentPage('foo/bar/baz.html');
$this->assertEquals('../../', Hyde::relativeLink('index.html'));
$this->assertSame('../../', Hyde::relativeLink('index.html'));
}

public function testHelperDoesNotRewriteIndexWhenNotUsingPrettyUrls()
{
self::mockConfig(['hyde.pretty_urls' => false]);
$this->mockCurrentPage('foo.html');
$this->assertEquals('index.html', Hyde::relativeLink('index.html'));
$this->assertSame('index.html', Hyde::relativeLink('index.html'));
$this->mockCurrentPage('foo/bar.html');
$this->assertEquals('../index.html', Hyde::relativeLink('index.html'));
$this->assertSame('../index.html', Hyde::relativeLink('index.html'));
$this->mockCurrentPage('foo/bar/baz.html');
$this->assertEquals('../../index.html', Hyde::relativeLink('index.html'));
$this->assertSame('../../index.html', Hyde::relativeLink('index.html'));
}

public function testHelperRewritesDocumentationPageIndexWhenUsingPrettyUrls()
{
self::mockConfig(['hyde.pretty_urls' => true]);
$this->mockCurrentPage('foo.html');
$this->assertEquals('docs/', Hyde::relativeLink('docs/index.html'));
$this->assertSame('docs/', Hyde::relativeLink('docs/index.html'));
$this->mockCurrentPage('docs.html');
$this->assertEquals('docs/', Hyde::relativeLink('docs/index.html'));
$this->assertSame('docs/', Hyde::relativeLink('docs/index.html'));
$this->mockCurrentPage('foo/bar.html');
$this->assertEquals('../docs/', Hyde::relativeLink('docs/index.html'));
$this->assertSame('../docs/', Hyde::relativeLink('docs/index.html'));
$this->mockCurrentPage('docs/foo.html');
$this->assertEquals('../docs/', Hyde::relativeLink('docs/index.html'));
$this->assertSame('../docs/', Hyde::relativeLink('docs/index.html'));
}

public function testHelperDoesNotRewriteDocumentationPageIndexWhenNotUsingPrettyUrls()
{
self::mockConfig(['hyde.pretty_urls' => false]);
$this->mockCurrentPage('foo.html');
$this->assertEquals('docs/index.html', Hyde::relativeLink('docs/index.html'));
$this->assertSame('docs/index.html', Hyde::relativeLink('docs/index.html'));
$this->mockCurrentPage('docs.html');
$this->assertEquals('docs/index.html', Hyde::relativeLink('docs/index.html'));
$this->assertSame('docs/index.html', Hyde::relativeLink('docs/index.html'));
$this->mockCurrentPage('foo/bar.html');
$this->assertEquals('../docs/index.html', Hyde::relativeLink('docs/index.html'));
$this->assertSame('../docs/index.html', Hyde::relativeLink('docs/index.html'));
$this->mockCurrentPage('docs/foo.html');
$this->assertEquals('../docs/index.html', Hyde::relativeLink('docs/index.html'));
$this->assertSame('../docs/index.html', Hyde::relativeLink('docs/index.html'));
}

public function testHelperDoesNotRewriteAlreadyProcessedLinks()
{
$this->assertEquals('../foo', Hyde::relativeLink('../foo'));
$this->assertSame('../foo', Hyde::relativeLink('../foo'));
}
}
Loading

0 comments on commit ebe55fb

Please sign in to comment.