-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
Support baseline configuration
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> | ||
<xs:annotation> | ||
<xs:appinfo source="https://github.com/Roave/BackwardCompatibilityCheck"/> | ||
|
||
<xs:documentation source="https://github.com/Roave/BackwardCompatibilityCheck"> | ||
This schema file defines the structure for the XML configuration file of roave/backward-compatibility-check. | ||
</xs:documentation> | ||
</xs:annotation> | ||
|
||
<xs:element name="roave-bc-check" type="bcCheckType" /> | ||
|
||
<xs:complexType name="bcCheckType"> | ||
<xs:sequence> | ||
<xs:element name="baseline" type="baselineType" minOccurs="0" /> | ||
</xs:sequence> | ||
</xs:complexType> | ||
|
||
<xs:complexType name="baselineType"> | ||
<xs:sequence> | ||
<xs:element name="ignored-regex" minOccurs="0" maxOccurs="unbounded" type="ignore-pattern" /> | ||
</xs:sequence> | ||
</xs:complexType> | ||
|
||
<xs:simpleType name="ignore-pattern"> | ||
<xs:restriction base="xs:string"> | ||
<xs:pattern value="#.+#" /> | ||
</xs:restriction> | ||
</xs:simpleType> | ||
</xs:schema> |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Roave\BackwardCompatibility; | ||
|
||
use function array_values; | ||
use function preg_match; | ||
|
||
/** @psalm-immutable */ | ||
final class Baseline | ||
{ | ||
/** @psalm-param list<non-empty-string> $ignoredChanges */ | ||
private function __construct(private readonly array $ignoredChanges = []) | ||
{ | ||
} | ||
|
||
public static function empty(): self | ||
{ | ||
return new self(); | ||
} | ||
|
||
/** @psalm-param list<non-empty-string> $ignoredChanges */ | ||
public static function fromList(string ...$ignoredChanges): self | ||
{ | ||
return new self(array_values($ignoredChanges)); | ||
} | ||
|
||
public function ignores(Change $change): bool | ||
{ | ||
$changeDescription = $change->__toString(); | ||
|
||
foreach ($this->ignoredChanges as $ignoredChangeRegex) { | ||
if (preg_match($ignoredChangeRegex, $changeDescription) === 1) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Roave\BackwardCompatibility\Command; | ||
|
||
use Psl\Str; | ||
use Psl\Type; | ||
use Roave\BackwardCompatibility\Configuration\Configuration; | ||
use Roave\BackwardCompatibility\Configuration\ParseConfigurationFile; | ||
use Roave\BackwardCompatibility\Configuration\ParseXmlConfigurationFile; | ||
use Symfony\Component\Console\Output\OutputInterface; | ||
|
||
/** @internal */ | ||
final class DetermineConfigurationFromFilesystem | ||
{ | ||
public function __construct( | ||
private readonly ParseConfigurationFile $parser = new ParseXmlConfigurationFile(), | ||
) { | ||
} | ||
|
||
public function __invoke( | ||
string $currentDirectory, | ||
OutputInterface $stdErr, | ||
): Configuration { | ||
$configuration = $this->parser->parse($currentDirectory); | ||
|
||
if ($configuration->filename !== null) { | ||
$stdErr->writeln(Str\format( | ||
'Using "%s" as configuration file', | ||
Type\string()->coerce($configuration->filename), | ||
)); | ||
} | ||
|
||
return $configuration; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Roave\BackwardCompatibility\Configuration; | ||
|
||
use Roave\BackwardCompatibility\Baseline; | ||
|
||
/** @psalm-immutable */ | ||
final class Configuration | ||
{ | ||
private function __construct( | ||
public readonly Baseline $baseline, | ||
public readonly string|null $filename, | ||
) { | ||
} | ||
|
||
public static function default(): self | ||
{ | ||
return new self(Baseline::empty(), null); | ||
} | ||
|
||
public static function fromFile(Baseline $baseline, string $filename): self | ||
{ | ||
return new self($baseline, $filename); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Roave\BackwardCompatibility\Configuration; | ||
|
||
use LibXMLError; | ||
use RuntimeException; | ||
|
||
use function sprintf; | ||
use function trim; | ||
|
||
use const PHP_EOL; | ||
|
||
/** @internal */ | ||
final class InvalidConfigurationStructure extends RuntimeException | ||
{ | ||
/** @param list<LibXMLError> $errors */ | ||
public static function fromLibxmlErrors(array $errors): self | ||
{ | ||
$message = 'The provided configuration is invalid, errors:' . PHP_EOL; | ||
Check warning on line 21 in src/Configuration/InvalidConfigurationStructure.php
|
||
|
||
foreach ($errors as $error) { | ||
$message .= sprintf( | ||
' - [Line %d] %s' . PHP_EOL, | ||
Check warning on line 25 in src/Configuration/InvalidConfigurationStructure.php
|
||
$error->line, | ||
trim($error->message), | ||
Check warning on line 27 in src/Configuration/InvalidConfigurationStructure.php
|
||
); | ||
} | ||
|
||
return new self($message); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Roave\BackwardCompatibility\Configuration; | ||
|
||
interface ParseConfigurationFile | ||
{ | ||
/** @throws InvalidConfigurationStructure When an incorrect file was found on the directory. */ | ||
public function parse(string $currentDirectory): Configuration; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Roave\BackwardCompatibility\Configuration; | ||
|
||
use DOMDocument; | ||
use Psl\File; | ||
use Roave\BackwardCompatibility\Baseline; | ||
use SimpleXMLElement; | ||
|
||
use function assert; | ||
use function libxml_get_errors; | ||
use function libxml_use_internal_errors; | ||
|
||
/** @internal */ | ||
final class ParseXmlConfigurationFile implements ParseConfigurationFile | ||
{ | ||
private const CONFIGURATION_FILENAME = '.roave-backward-compatibility-check.xml'; | ||
|
||
private const SCHEMA = __DIR__ . '/../../Resources/schema.xsd'; | ||
|
||
public function parse(string $currentDirectory): Configuration | ||
{ | ||
$filename = $currentDirectory . '/' . self::CONFIGURATION_FILENAME; | ||
|
||
try { | ||
$xmlContents = File\read($filename); | ||
|
||
$this->validateStructure($xmlContents); | ||
} catch (File\Exception\InvalidArgumentException) { | ||
return Configuration::default(); | ||
} | ||
|
||
$configuration = new SimpleXMLElement($xmlContents); | ||
|
||
return Configuration::fromFile( | ||
$this->parseBaseline($configuration), | ||
$filename, | ||
); | ||
} | ||
|
||
private function validateStructure(string $xmlContents): void | ||
{ | ||
$previousConfiguration = libxml_use_internal_errors(true); | ||
|
||
$xmlDocument = new DOMDocument(); | ||
$xmlDocument->loadXML($xmlContents); | ||
|
||
$configurationIsValid = $xmlDocument->schemaValidate(self::SCHEMA); | ||
|
||
$parsingErrors = libxml_get_errors(); | ||
libxml_use_internal_errors($previousConfiguration); | ||
Check warning on line 53 in src/Configuration/ParseXmlConfigurationFile.php
|
||
|
||
if ($configurationIsValid) { | ||
return; | ||
} | ||
|
||
throw InvalidConfigurationStructure::fromLibxmlErrors($parsingErrors); | ||
} | ||
|
||
private function parseBaseline(SimpleXMLElement $element): Baseline | ||
{ | ||
$ignoredItems = []; | ||
|
||
foreach ($element->xpath('baseline/ignored-regex') ?? [] as $node) { | ||
$ignoredItem = (string) $node; | ||
|
||
assert($ignoredItem !== ''); | ||
$ignoredItems[] = $ignoredItem; | ||
} | ||
|
||
return Baseline::fromList(...$ignoredItems); | ||
} | ||
} |