Skip to content

Commit

Permalink
[analyzer] add file analyzer (#5)
Browse files Browse the repository at this point in the history
* chore(tests): add fixture path helper

* chore(tests): add syntax error fixture

* feat(analyzer): add file analyzer
  • Loading branch information
imdhemy committed Mar 23, 2024
1 parent 058e146 commit d9de704
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 1 deletion.
10 changes: 10 additions & 0 deletions src/Analyzer/AnalyzerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Symblaze\MareScan\Analyzer;

interface AnalyzerInterface
{
public function analyze(array $data): array;
}
64 changes: 64 additions & 0 deletions src/Analyzer/FileAnalyzer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace Symblaze\MareScan\Analyzer;

use Symblaze\MareScan\Exception\InspectionError;
use Symblaze\MareScan\Exception\ParserError;
use Symblaze\MareScan\Inspector\InspectorInterface;
use Symblaze\MareScan\Inspector\TypeCompatibility\MissingDeclareStrictTypesInspector;
use Symblaze\MareScan\Parser\ParserInterface;

final readonly class FileAnalyzer implements AnalyzerInterface
{
public function __construct(private ParserInterface $parser)
{
}

public function analyze(array $data): array
{
$filePath = $data['path'] ?? null;

if (! is_string($filePath) || ! file_exists($filePath)) {
return [];
}

$content = (string)file_get_contents($filePath);
try {
$statements = $this->parser->parse($content);
} catch (ParserError $e) {
return [$this->parserErrorToResult($e, $filePath)];
}

$firstStatement = $statements[0] ?? null;
$inspector = new MissingDeclareStrictTypesInspector($firstStatement);
try {
$inspector->inspect();
} catch (InspectionError $e) {
return [$this->inspectionErrorToResult($e, $filePath, $inspector)];
}

return [];
}

private function parserErrorToResult(ParserError $e, string $filePath): array
{
return [
'severity' => 'error',
'message' => $e->getMessage(),
'file' => $filePath,
'description' => 'Parser error occurred while parsing the file',
];
}

private function inspectionErrorToResult(InspectionError $e, string $filePath, InspectorInterface $inspector): array
{
return [
'severity' => 'warning',
'message' => $e->getMessage(),
'file' => $filePath,
'description' => $inspector->description(),
];
}
}
106 changes: 106 additions & 0 deletions tests/Analyzer/FileAnalyzerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

declare(strict_types=1);

namespace Symblaze\MareScan\Tests\Analyzer;

use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use Symblaze\MareScan\Analyzer\FileAnalyzer;
use Symblaze\MareScan\Inspector\TypeCompatibility\MissingDeclareStrictTypesInspector;
use Symblaze\MareScan\Parser\ParserBuilder;
use Symblaze\MareScan\Tests\TestCase;

#[CoversClass(FileAnalyzer::class)]
final class FileAnalyzerTest extends TestCase
{
#[Test]
public function it_can_detect_syntax_error(): void
{
$filePath = $this->fixturePath('misc/syntax_error.stub');
$parser = ParserBuilder::init()->build();
$sut = new FileAnalyzer($parser);
$data = [
'path' => $filePath,
'type' => 'file',
];

$result = $sut->analyze($data);

$expected = [
[
'severity' => 'error',
'description' => 'Parser error occurred while parsing the file',
'message' => 'Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE on line 3',
'file' => $filePath,
],
];
$this->assertEqualsCanonicalizing($expected, $result);
}

#[Test]
public function it_can_detect_missing_strict_types_declaration(): void
{
$filePath = $this->fixturePath('type_compatibility/missing_declare_strict_types.php');
$parser = ParserBuilder::init()->build();
$data = [
'path' => $filePath,
'type' => 'file',
];
$sut = new FileAnalyzer($parser);

$result = $sut->analyze($data);

$inspector = new MissingDeclareStrictTypesInspector();
$expected = [
[
'severity' => 'warning',
'message' => 'Strict types declaration is missing',
'file' => $filePath,
'description' => $inspector->description(),
],
];
$this->assertEqualsCanonicalizing($expected, $result);
}

#[Test]
public function it_can_detect_invalid_declare_strict_types(): void
{
$filePath = $this->fixturePath('type_compatibility/invalid_declare_strict_types.php');
$parser = ParserBuilder::init()->build();
$data = [
'path' => $filePath,
'type' => 'file',
];
$sut = new FileAnalyzer($parser);

$result = $sut->analyze($data);

$inspector = new MissingDeclareStrictTypesInspector();
$expected = [
[
'severity' => 'warning',
'message' => 'Strict types declaration is missing',
'file' => $filePath,
'description' => $inspector->description(),
],
];
$this->assertEqualsCanonicalizing($expected, $result);
}

#[Test]
public function it_can_detect_valid_declare_strict_types(): void
{
$filePath = $this->fixturePath('type_compatibility/valid_declare_strict_types.php');
$parser = ParserBuilder::init()->build();
$data = [
'path' => $filePath,
'type' => 'file',
];
$sut = new FileAnalyzer($parser);

$result = $sut->analyze($data);

$this->assertEmpty($result);
}
}
10 changes: 9 additions & 1 deletion tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@ abstract class TestCase extends PHPUnitTestCase
{
protected function fixture(string $path): string
{
$contents = file_get_contents(__DIR__.'/fixtures/'.$path);
$contents = file_get_contents($this->fixturePath($path));
assert(is_string($contents), 'Fixture not found.');

return $contents;
}

protected function fixturePath(string $path): string
{
$realpath = realpath(__DIR__.'/fixtures/'.$path);
assert(is_string($realpath), 'Fixture not found.');

return $realpath;
}
}
3 changes: 3 additions & 0 deletions tests/fixtures/misc/syntax_error.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

$a = '

0 comments on commit d9de704

Please sign in to comment.