Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ jobs:
- name: Install dependencies
run: composer install --prefer-dist

# Psalm 6 needs PHP >= 8.1 to run; the analysis target stays 8.0 (phpVersion in psalm.xml)
- name: Static Analysis
if: ${{ matrix.php-versions == '8.0' }}
if: ${{ matrix.php-versions == '8.1' }}
run: |
composer install --prefer-dist -d tools/psalm
tools/psalm/vendor/bin/psalm --no-cache
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,4 @@ Before committing, `make csfix && make psalm && make test` must all pass (CI gat
- Code style is enforced by `.php-cs-fixer.dist.php` — don't hand-format; run `make csfix`.
- The parser supports a configurable target PHP version (`--target-php-version`, `8.0`–`8.5`); when touching `FileVisitor`/analyzer code, consider syntax across that range.
- This tool analyzes other people's code, so the analyzer must tolerate any valid PHP without crashing — prefer surfacing a `ParsingError` over throwing.
- Don't write runtime code (fallback defaults, `assert()` calls, extra variables) whose only purpose is to satisfy Psalm. If a flagged case can't actually happen, use a targeted `@psalm-suppress IssueName` with a one-line comment explaining why, instead of adding behavior that only exists to change what the analyzer infers. If an issue is low-value across the whole codebase (e.g. `ImplicitToStringCast`), suppress it globally in `psalm.xml` rather than working around it at every call site.
9 changes: 9 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?xml version="1.0"?>
<psalm
errorLevel="2"
phpVersion="8.0"
ensureOverrideAttribute="false"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
Expand All @@ -14,4 +16,11 @@
<directory name="vendor" />
</ignoreFiles>
</projectFiles>

<issueHandlers>
<!-- Making library classes final is an API decision for consumers, not a static-analysis one -->
<ClassMustBeFinal errorLevel="suppress" />
<!-- Implicit __toString on concat is idiomatic PHP, not worth requiring an explicit call everywhere -->
<ImplicitToStringCast errorLevel="suppress" />
</issueHandlers>
</psalm>
2 changes: 2 additions & 0 deletions src/Analyzer/FullyQualifiedClassName.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public static function fromString(string $fqcn): self
$className = array_pop($piecesWithoutEmpty);
$namespace = implode('\\', $piecesWithoutEmpty);

// $className can't be null: the regex above rejects an empty or trailing-backslash $fqcn
/** @psalm-suppress PossiblyNullArgument */
return new self(new PatternString($fqcn), new PatternString($namespace), new PatternString($className));
}

Expand Down
11 changes: 9 additions & 2 deletions src/CLI/Baseline.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace Arkitect\CLI;

use Arkitect\Json;
use Arkitect\Rules\Violations;

class Baseline
Expand Down Expand Up @@ -75,8 +76,14 @@ public static function loadFromFile(string $filename): self
throw new \RuntimeException("Baseline file '$filename' not found.");
}

$contents = file_get_contents($filename);

if (false === $contents) {
throw new \RuntimeException("Baseline file '$filename' could not be read.");
}

return new self(
Violations::fromJson(file_get_contents($filename)),
Violations::fromJson($contents),
$filename
);
}
Expand All @@ -87,6 +94,6 @@ public static function save(string $filename, Violations $violations, bool $igno
$violations = $violations->withoutLineNumbers();
}

file_put_contents($filename, json_encode($violations, \JSON_PRETTY_PRINT));
file_put_contents($filename, Json::encode($violations));
}
}
7 changes: 4 additions & 3 deletions src/CLI/Printer/GitlabPrinter.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace Arkitect\CLI\Printer;

use Arkitect\Json;
use Arkitect\Rules\Violation;

class GitlabPrinter implements Printer
Expand Down Expand Up @@ -36,13 +37,13 @@ public function print(array $violationsCollection): string
}
}

return json_encode($allErrors);
return Json::encode($allErrors);
}

private function toKebabCase(string $string): string
{
$string = preg_replace('/[^a-zA-Z0-9]+/', ' ', $string);
$string = preg_replace('/\s+/', ' ', $string);
$string = (string) preg_replace('/[^a-zA-Z0-9]+/', ' ', $string);
$string = (string) preg_replace('/\s+/', ' ', $string);
$string = strtolower(trim($string));
$string = str_replace(' ', '-', $string);

Expand Down
3 changes: 2 additions & 1 deletion src/CLI/Printer/JsonPrinter.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace Arkitect\CLI\Printer;

use Arkitect\Json;
use Arkitect\Rules\Violation;

class JsonPrinter implements Printer
Expand Down Expand Up @@ -36,6 +37,6 @@ public function print(array $violationsCollection): string
'details' => $details,
];

return json_encode($errors);
return Json::encode($errors);
}
}
2 changes: 1 addition & 1 deletion src/CLI/TargetPhpVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public static function latest(): self

public static function create(?string $version): self
{
return new self($version ?? phpversion());
return new self($version ?? \PHP_VERSION);
}

public function get(): string
Expand Down
20 changes: 20 additions & 0 deletions src/Json.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);

namespace Arkitect;

class Json
{
public static function encode(mixed $value): string
{
return json_encode(
$value,
// throw instead of silently returning false, e.g. on unsupported types or exceeded depth
\JSON_THROW_ON_ERROR
// substitute invalid UTF-8 byte sequences instead of failing, since we may encode
// arbitrary strings extracted from source files we don't control
| \JSON_INVALID_UTF8_SUBSTITUTE
| \JSON_PRETTY_PRINT
);
}
}
8 changes: 3 additions & 5 deletions src/PHPUnit/ArchRuleCheckerConstraintAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,8 @@ public function toString(): string
return 'satisfies all architectural constraints';
}

protected function matches(
/** @var ArchRule $rule */
$other,
): bool {
protected function matches(mixed $other): bool
{
$this->runner->check(
ClassSetRules::create($this->classSet, $other),
new VoidProgress(),
Expand All @@ -73,7 +71,7 @@ protected function matches(
return 0 === $violationsCount && 0 === $parsingErrorsCount;
}

protected function failureDescription($other): string
protected function failureDescription(mixed $other): string
{
if ($this->parsingErrors->count() > 0) {
$result = "\n parsing error: ";
Expand Down
3 changes: 3 additions & 0 deletions src/Rules/Violation.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

namespace Arkitect\Rules;

/**
* @psalm-immutable
*/
class Violation implements \JsonSerializable
{
private string $fqcn;
Expand Down
6 changes: 3 additions & 3 deletions tests/Unit/CLI/Printer/JsonPrinterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public function test_print_returns_correct_json_for_empty_violations(): void
$expected = json_encode([
'totalViolations' => 0,
'details' => [],
]);
], \JSON_PRETTY_PRINT);

self::assertSame($expected, $result);
}
Expand Down Expand Up @@ -51,7 +51,7 @@ public function test_print_returns_correct_json_for_single_violation(): void
],
],
],
]);
], \JSON_PRETTY_PRINT);

self::assertSame($expected, $result);
}
Expand Down Expand Up @@ -91,7 +91,7 @@ public function test_print_returns_correct_json_for_multiple_violations(): void
],
],
],
]);
], \JSON_PRETTY_PRINT);

self::assertSame($expected, $result);
}
Expand Down
4 changes: 2 additions & 2 deletions tools/psalm/composer.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"require": {
"vimeo/psalm": "^5.26"
"vimeo/psalm": "^6.16"
},
"config": {
"platform": {
"php": "8.0.30"
"php": "8.1.31"
}
}
}
Loading