[TASK] Take .editorconfig into account (#6272)

This commit is contained in:
Sebastian Schreiber 2021-05-06 13:26:28 +02:00 committed by GitHub
parent aca76ddc2a
commit 16d5930435
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1503 additions and 3 deletions

1
.gitattributes vendored
View File

@ -41,3 +41,4 @@ phpstan-for-rector.neon export-ignore
# testing Windows spaces - https://help.github.com/en/github/using-git/configuring-git-to-handle-line-endings
packages/better-php-doc-parser/tests/PhpDocInfo/PhpDocInfoPrinter/FixtureBasic/with_spac.txt text eol=crlf
packages-tests/FileFormatter/ValueObject/Fixture/composer_carriage_return_line_feed.json json eol=crlf

View File

@ -32,6 +32,8 @@
"composer/xdebug-handler": "^2.0",
"danielstjules/stringy": "^3.1",
"doctrine/inflector": "^2.0",
"ergebnis/json-printer": "^3.1",
"idiosyncratic/editorconfig": "^0.1.0",
"jean85/pretty-package-versions": "^1.6",
"nette/caching": "^3.1",
"nette/utils": "^3.2",
@ -48,6 +50,7 @@
"rector/rector-phpunit": "^0.10.8",
"rector/rector-symfony": "^0.10.5",
"sebastian/diff": "^4.0.4",
"shanethehat/pretty-xml": "^1.0",
"symfony/console": "^4.4.8|^5.1",
"symfony/dependency-injection": "^5.1",
"symfony/finder": "^4.4.8|^5.1",
@ -153,7 +156,5 @@
"config": {
"sort-packages": true,
"platform-check": false
},
"minimum-stability": "dev",
"prefer-stable": true
}
}

View File

@ -5,6 +5,9 @@ declare(strict_types=1);
use Composer\Semver\VersionParser;
use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\Rules\English\InflectorFactory;
use Ergebnis\Json\Printer\Printer;
use Ergebnis\Json\Printer\PrinterInterface;
use Idiosyncratic\EditorConfig\EditorConfig;
use Nette\Caching\Cache;
use PhpParser\BuilderFactory;
use PhpParser\Lexer;
@ -21,6 +24,7 @@ use PHPStan\PhpDoc\TypeNodeResolver;
use PHPStan\PhpDocParser\Parser\PhpDocParser;
use PHPStan\PhpDocParser\Parser\TypeParser;
use PHPStan\Reflection\ReflectionProvider;
use PrettyXml\Formatter;
use Rector\BetterPhpDocParser\PhpDocParser\BetterPhpDocParser;
use Rector\BetterPhpDocParser\PhpDocParser\BetterTypeParser;
use Rector\Caching\Cache\NetteCacheFactory;
@ -28,6 +32,8 @@ use Rector\Core\Console\ConsoleApplication;
use Rector\Core\NonPhpFile\Rector\RenameClassNonPhpRector;
use Rector\Core\PhpParser\Parser\NikicPhpParserFactory;
use Rector\Core\PhpParser\Parser\PhpParserLexerFactory;
use Rector\FileFormatter\Contract\EditorConfig\EditorConfigParserInterface;
use Rector\FileFormatter\EditorConfig\EditorConfigIdiosyncraticParser;
use Rector\NodeTypeResolver\DependencyInjection\PHPStanServicesFactory;
use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocator\IntermediateSourceLocator;
use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider;
@ -146,4 +152,13 @@ return static function (ContainerConfigurator $containerConfigurator): void {
->factory([service(PHPStanServicesFactory::class), 'createTypeNodeResolver']);
$services->set(DynamicSourceLocatorProvider::class)
->factory([service(PHPStanServicesFactory::class), 'createDynamicSourceLocatorProvider']);
$services->set(Printer::class);
$services->alias(PrinterInterface::class, Printer::class);
$services->set(Formatter::class);
$services->set(EditorConfig::class);
$services->alias(EditorConfigParserInterface::class, EditorConfigIdiosyncraticParser::class);
};

View File

@ -0,0 +1,3 @@
[composer.json]
indent_size = 1
indent_style = tab

View File

@ -0,0 +1,42 @@
<?php
namespace Rector\Tests\FileFormatter\EditorConfig\EditorConfigIdiosyncraticParser;
use Rector\Core\ValueObject\Application\File;
use Rector\FileFormatter\Contract\EditorConfig\EditorConfigParserInterface;
use Rector\FileFormatter\ValueObject\Indent;
use Rector\FileFormatter\ValueObjectFactory\EditorConfigConfigurationBuilder;
use Rector\Testing\PHPUnit\AbstractTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class EditorConfigIdiosyncraticParserTest extends AbstractTestCase
{
/**
* @var EditorConfigParserInterface
*/
private $editorConfigParser;
protected function setUp(): void
{
$this->boot();
$this->editorConfigParser = $this->getService(EditorConfigParserInterface::class);
}
public function testComposerJsonFile(): void
{
$editorConfigConfigurationBuilder = EditorConfigConfigurationBuilder::anEditorConfigConfiguration();
$editorConfigConfigurationBuilder->withIndent(Indent::createSpaceWithSize(20));
$composerJsonFile = new SmartFileInfo(__DIR__ . '/Fixture/composer.json');
$file = new File($composerJsonFile, $composerJsonFile->getContents());
$editorConfigConfiguration = $this->editorConfigParser->extractConfigurationForFile(
$file,
$editorConfigConfigurationBuilder
);
$this->assertSame('tab', $editorConfigConfiguration->getIndentStyle());
$this->assertSame(1, $editorConfigConfiguration->getIndentSize());
}
}

View File

@ -0,0 +1,5 @@
{
"name": "foo/bar",
"description": "A foo bar baz extension",
"license": "GPL-2.0-or-later"
}

View File

@ -0,0 +1,11 @@
{
"name": "foo/bar",
"description": "A foo bar baz extension",
"license": "GPL-2.0-or-later"
}
-----
{
"name": "foo/bar",
"description": "A foo bar baz extension",
"license": "GPL-2.0-or-later"
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\FileFormatter\Formatter\JsonFileFormatter;
use Iterator;
use Rector\Core\ValueObject\Application\File;
use Rector\FileFormatter\Formatter\JsonFileFormatter;
use Rector\FileFormatter\ValueObject\Indent;
use Rector\FileFormatter\ValueObjectFactory\EditorConfigConfigurationBuilder;
use Rector\Testing\PHPUnit\AbstractTestCase;
use Symplify\EasyTesting\DataProvider\StaticFixtureFinder;
use Symplify\EasyTesting\StaticFixtureSplitter;
use Symplify\SmartFileSystem\SmartFileInfo;
final class JsonFileFormatterTest extends AbstractTestCase
{
/**
* @var JsonFileFormatter
*/
private $jsonFileFormatter;
protected function setUp(): void
{
$this->boot();
$this->jsonFileFormatter = $this->getService(JsonFileFormatter::class);
}
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
/**
* @return Iterator<array<int, SmartFileInfo>>
*/
public function provideData(): Iterator
{
return StaticFixtureFinder::yieldDirectory(__DIR__ . '/Fixture', '*.json');
}
private function doTestFileInfo(SmartFileInfo $smartFileInfo): void
{
$inputFileInfoAndExpected = StaticFixtureSplitter::splitFileInfoToLocalInputAndExpected($smartFileInfo);
$inputFileInfo = $inputFileInfoAndExpected->getInputFileInfo();
$file = new File($inputFileInfo, $inputFileInfo->getContents());
$editorConfigConfigurationBuilder = EditorConfigConfigurationBuilder::anEditorConfigConfiguration();
$editorConfigConfigurationBuilder->withIndent(Indent::createTabWithSize(1));
$this->jsonFileFormatter->format($file, $editorConfigConfigurationBuilder->build());
$this->assertSame($inputFileInfoAndExpected->getExpected(), $file->getFileContent());
}
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0"?>
<catalog>
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</book>
</catalog>
-----
<?xml version="1.0"?>
<catalog>
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</book>
</catalog>

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\FileFormatter\Formatter\XmlFileFormatter;
use Iterator;
use Rector\Core\ValueObject\Application\File;
use Rector\FileFormatter\Formatter\XmlFileFormatter;
use Rector\FileFormatter\ValueObject\Indent;
use Rector\FileFormatter\ValueObjectFactory\EditorConfigConfigurationBuilder;
use Rector\Testing\PHPUnit\AbstractTestCase;
use Symplify\EasyTesting\DataProvider\StaticFixtureFinder;
use Symplify\EasyTesting\StaticFixtureSplitter;
use Symplify\SmartFileSystem\SmartFileInfo;
final class XmlFileFormatterTest extends AbstractTestCase
{
/**
* @var XmlFileFormatter
*/
private $xmlFileFormatter;
protected function setUp(): void
{
$this->boot();
$this->xmlFileFormatter = $this->getService(XmlFileFormatter::class);
}
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
/**
* @return Iterator<array<int, SmartFileInfo>>
*/
public function provideData(): Iterator
{
return StaticFixtureFinder::yieldDirectory(__DIR__ . '/Fixture', '*.xml');
}
private function doTestFileInfo(SmartFileInfo $smartFileInfo): void
{
$inputFileInfoAndExpected = StaticFixtureSplitter::splitFileInfoToLocalInputAndExpected($smartFileInfo);
$inputFileInfo = $inputFileInfoAndExpected->getInputFileInfo();
$file = new File($inputFileInfo, $inputFileInfo->getContents());
$editorConfigConfigurationBuilder = EditorConfigConfigurationBuilder::anEditorConfigConfiguration();
$editorConfigConfigurationBuilder->withIndent(Indent::createTabWithSize(1));
$this->xmlFileFormatter->format($file, $editorConfigConfigurationBuilder->build());
$this->assertSame($inputFileInfoAndExpected->getExpected(), $file->getFileContent());
}
}

View File

@ -0,0 +1,15 @@
martin:
name: Martin
job: Developer
skills:
- python
- perl
- pascal
-----
martin:
name: Martin
job: Developer
skills:
- python
- perl
- pascal

View File

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\FileFormatter\Formatter\YamlFileFormatter;
use Iterator;
use Rector\Core\ValueObject\Application\File;
use Rector\FileFormatter\Formatter\YamlFileFormatter;
use Rector\FileFormatter\ValueObject\Indent;
use Rector\FileFormatter\ValueObjectFactory\EditorConfigConfigurationBuilder;
use Rector\Testing\PHPUnit\AbstractTestCase;
use Symplify\EasyTesting\DataProvider\StaticFixtureFinder;
use Symplify\EasyTesting\StaticFixtureSplitter;
use Symplify\SmartFileSystem\SmartFileInfo;
final class YamlFileFormatterTest extends AbstractTestCase
{
/**
* @var YamlFileFormatter
*/
private $yamlFileFormatter;
protected function setUp(): void
{
$this->boot();
$this->yamlFileFormatter = $this->getService(YamlFileFormatter::class);
}
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
/**
* @return Iterator<array<int, SmartFileInfo>>
*/
public function provideData(): Iterator
{
return StaticFixtureFinder::yieldDirectory(__DIR__ . '/Fixture', '*.yaml');
}
private function doTestFileInfo(SmartFileInfo $smartFileInfo): void
{
$inputFileInfoAndExpected = StaticFixtureSplitter::splitFileInfoToLocalInputAndExpected($smartFileInfo);
$inputFileInfo = $inputFileInfoAndExpected->getInputFileInfo();
$file = new File($inputFileInfo, $inputFileInfo->getContents());
$editorConfigConfigurationBuilder = EditorConfigConfigurationBuilder::anEditorConfigConfiguration();
$editorConfigConfigurationBuilder->withIndent(Indent::createSpaceWithSize(4));
$editorConfigConfigurationBuilder->withInsertFinalNewline(false);
$this->yamlFileFormatter->format($file, $editorConfigConfigurationBuilder->build());
$this->assertSame($inputFileInfoAndExpected->getExpected(), $file->getFileContent());
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Rector\Tests\FileFormatter\ValueObject;
use PHPUnit\Framework\TestCase;
use Rector\FileFormatter\ValueObject\Indent;
use Rector\FileFormatter\ValueObjectFactory\EditorConfigConfigurationBuilder;
use Symplify\PackageBuilder\Configuration\StaticEolConfiguration;
final class EditorConfigConfigurationTest extends TestCase
{
public function testWithFinalNewline(): void
{
$editorConfigConfigurationBuilder = EditorConfigConfigurationBuilder::anEditorConfigConfiguration();
$editorConfigConfiguration = $editorConfigConfigurationBuilder->build();
$this->assertSame(StaticEolConfiguration::getEolChar(), $editorConfigConfiguration->getFinalNewline());
}
public function testWithoutFinalNewline(): void
{
$editorConfigConfigurationBuilder = EditorConfigConfigurationBuilder::anEditorConfigConfiguration();
$editorConfigConfigurationBuilder->withInsertFinalNewline(false);
$editorConfigConfiguration = $editorConfigConfigurationBuilder->build();
$this->assertSame('', $editorConfigConfiguration->getFinalNewline());
}
public function testIndentForTab(): void
{
$editorConfigConfigurationBuilder = EditorConfigConfigurationBuilder::anEditorConfigConfiguration();
$editorConfigConfigurationBuilder->withIndent(Indent::createTabWithSize(4));
$editorConfigConfiguration = $editorConfigConfigurationBuilder->build();
$this->assertSame(' ', $editorConfigConfiguration->getIndent());
}
public function testIndentForSpace(): void
{
$editorConfigConfigurationBuilder = EditorConfigConfigurationBuilder::anEditorConfigConfiguration();
$editorConfigConfigurationBuilder->withIndent(Indent::createSpaceWithSize(10));
$editorConfigConfiguration = $editorConfigConfigurationBuilder->build();
$this->assertSame(' ', $editorConfigConfiguration->getIndent());
}
}

View File

@ -0,0 +1 @@
{ "name": "foo/bar", "description": "A foo bar baz extension", "license": "GPL-2.0-or-later" }

View File

@ -0,0 +1,5 @@
{
"name": "foo/bar",
"description": "A foo bar baz extension",
"license": "GPL-2.0-or-later"
}

View File

@ -0,0 +1,5 @@
{
"name": "foo/bar",
"description": "A foo bar baz extension",
"license": "GPL-2.0-or-later"
}

View File

@ -0,0 +1,5 @@
{
"name": "foo/bar",
"description": "A foo bar baz extension",
"license": "GPL-2.0-or-later"
}

View File

@ -0,0 +1,5 @@
{
"name": "foo/bar",
"description": "A foo bar baz extension",
"license": "GPL-2.0-or-later"
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<catalog>
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</book>
</catalog>

View File

@ -0,0 +1 @@
martin: name: Martin job: Developer skills: - python - perl - pascal

View File

@ -0,0 +1,7 @@
martin:
name: Martin
job: Developer
skills:
- python
- perl
- pascal

View File

@ -0,0 +1,7 @@
martin:
name: Martin
job: Developer
skills:
- python
- perl
- pascal

View File

@ -0,0 +1,104 @@
<?php
namespace Rector\Tests\FileFormatter\ValueObject;
use Generator;
use PHPUnit\Framework\TestCase;
use Rector\FileFormatter\Exception\InvalidIndentSizeException;
use Rector\FileFormatter\Exception\InvalidIndentStyleException;
use Rector\FileFormatter\Exception\ParseIndentException;
use Rector\FileFormatter\ValueObject\Indent;
use Symplify\SmartFileSystem\SmartFileInfo;
final class IndentTest extends TestCase
{
/**
* @dataProvider extractFromFiles
*/
public function testFromFiles(SmartFileInfo $smartFileInfo, string $expectedIndent): void
{
$indent = Indent::fromContent($smartFileInfo->getContents());
$this->assertSame($expectedIndent, $indent->__toString());
}
/**
* @dataProvider provideSizeStyleAndIndentString
*/
public function testFromSizeAndStyle(int $size, string $style, string $string): void
{
$indent = Indent::fromSizeAndStyle($size, $style);
$this->assertSame($string, $indent->__toString());
$this->assertSame($size, $indent->getIndentSize());
$this->assertSame($style, $indent->getIndentStyle());
}
public function testFromSizeAndStyleWithInvalidSizeThrowsException(): void
{
$this->expectException(InvalidIndentSizeException::class);
Indent::createTabWithSize(0);
}
public function testFromSizeAndStyleWithInvalidStyleThrowsException(): void
{
$this->expectException(InvalidIndentStyleException::class);
Indent::fromSizeAndStyle(1, 'invalid');
}
public function testFromInvalidContentThrowsException(): void
{
$this->expectException(ParseIndentException::class);
Indent::fromContent('This is invalid content');
}
/**
* @return Generator<array<string>>
*/
public function extractFromFiles(): Generator
{
yield 'Yaml file with space indentation of size 4' => [
new SmartFileInfo(__DIR__ . '/Fixture/yaml_indentation_space_four.yaml'),
' ',
];
yield 'Yaml file with space indentation of size 2' => [
new SmartFileInfo(__DIR__ . '/Fixture/yaml_indentation_space_two.yaml'),
' ',
];
yield 'Json file with tab indentation of size 2' => [
new SmartFileInfo(__DIR__ . '/Fixture/composer_indentation_tab_two.json'),
' ',
];
yield 'Json file with space indentation of size 6' => [
new SmartFileInfo(__DIR__ . '/Fixture/composer_indentation_space_six.json'),
' ',
];
}
/**
* @return Generator<array{0: int, 1: string, 2: string}>
*/
public function provideSizeStyleAndIndentString(): Generator
{
foreach ($this->sizes() as $size) {
foreach (Indent::CHARACTERS as $style => $character) {
$string = str_repeat($character, $size);
yield [$size, $style, $string];
}
}
}
/**
* @return int[]
*/
private static function sizes(): array
{
return [
'int-one' => 1,
'int-greater-than-one' => 5,
];
}
}

View File

@ -0,0 +1,110 @@
<?php
namespace Rector\Tests\FileFormatter\ValueObject;
use Generator;
use PHPUnit\Framework\TestCase;
use Rector\FileFormatter\Exception\InvalidNewLineStringException;
use Rector\FileFormatter\ValueObject\NewLine;
use Symplify\SmartFileSystem\SmartFileInfo;
final class NewLineTest extends TestCase
{
/**
* @dataProvider extractFromFiles
*/
public function testFromFiles(SmartFileInfo $smartFileInfo, string $expectedNewLine): void
{
$newLine = NewLine::fromContent($smartFileInfo->getContents());
$this->assertSame($expectedNewLine, $newLine->__toString());
}
/**
* @dataProvider provideInvalidNewLineString
*/
public function testFromStringRejectsInvalidNewLineString(string $string): void
{
$this->expectException(InvalidNewLineStringException::class);
NewLine::fromSingleCharacter($string);
}
/**
* @dataProvider provideValidNewLineString
*/
public function testFromStringReturnsNewLine(string $string): void
{
$newLine = NewLine::fromSingleCharacter($string);
$this->assertSame($string, $newLine->__toString());
}
/**
* @dataProvider provideValidNewLineStringFromEditorConfig
*/
public function testFromEditorConfigReturnsNewLine(string $string, string $expected): void
{
$newLine = NewLine::fromEditorConfig($string);
$this->assertSame($expected, $newLine->__toString());
}
/**
* @return Generator<array<string>>
*/
public function provideValidNewLineString(): Generator
{
foreach (["\n", "\r", "\r\n"] as $string) {
yield [$string];
}
}
/**
* @return Generator<array<string>>
*/
public function provideInvalidNewLineString(): Generator
{
foreach (["\t", " \r ", " \r\n ", " \n ", ' ', "\f", "\x0b", "\x85"] as $string) {
yield [$string];
}
}
/**
* @return Generator<array<string>>
*/
public function extractFromFiles(): Generator
{
yield 'Yaml file with carriage return' => [
new SmartFileInfo(__DIR__ . '/Fixture/yaml_carriage_return.yaml'),
"\r",
];
yield 'Xml file with line feed' => [new SmartFileInfo(__DIR__ . '/Fixture/xml_line_feed.xml'), "\n"];
yield 'Json file with line feed' => [new SmartFileInfo(__DIR__ . '/Fixture/composer_line_feed.json'), "\n"];
yield 'Json file with carriage return' => [
new SmartFileInfo(__DIR__ . '/Fixture/composer_carriage_return.json'),
"\r",
];
yield 'Json file with carriage return and line feed' => [
new SmartFileInfo(__DIR__ . '/Fixture/composer_carriage_return_line_feed.json'),
"\r\n",
];
}
/**
* @return Generator<array<string>>
*/
public function provideValidNewLineStringFromEditorConfig(): Generator
{
foreach ([
'lf' => "\n",
'cr' => "\r",
'crlf' => "\r\n",
] as $editorConfig => $string) {
yield [$editorConfig, $string];
}
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Rector\FileFormatter\Contract\EditorConfig;
use Rector\Core\ValueObject\Application\File;
use Rector\FileFormatter\ValueObject\EditorConfigConfiguration;
use Rector\FileFormatter\ValueObjectFactory\EditorConfigConfigurationBuilder;
interface EditorConfigParserInterface
{
/**
* @var string
*/
public const INDENT_STYLE = 'indent_style';
/**
* @var string
*/
public const INDENT_SIZE = 'indent_size';
/**
* @var string
*/
public const END_OF_LINE = 'end_of_line';
/**
* @var string
*/
public const INSERT_FINAL_NEWLINE = 'insert_final_newline';
/**
* @var string
*/
public const TAB_WIDTH = 'tab_width';
public function extractConfigurationForFile(
File $file,
EditorConfigConfigurationBuilder $editorConfigConfigurationBuilder
): EditorConfigConfiguration;
}

View File

@ -0,0 +1,16 @@
<?php
namespace Rector\FileFormatter\Contract\Formatter;
use Rector\Core\ValueObject\Application\File;
use Rector\FileFormatter\ValueObject\EditorConfigConfiguration;
use Rector\FileFormatter\ValueObjectFactory\EditorConfigConfigurationBuilder;
interface FileFormatterInterface
{
public function supports(File $file): bool;
public function format(File $file, EditorConfigConfiguration $editorConfigConfiguration): void;
public function createDefaultEditorConfigConfigurationBuilder(): EditorConfigConfigurationBuilder;
}

View File

@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace Rector\FileFormatter\EditorConfig;
use Idiosyncratic\EditorConfig\EditorConfig;
use Rector\Core\ValueObject\Application\File;
use Rector\FileFormatter\Contract\EditorConfig\EditorConfigParserInterface;
use Rector\FileFormatter\ValueObject\EditorConfigConfiguration;
use Rector\FileFormatter\ValueObjectFactory\EditorConfigConfigurationBuilder;
/**
* @see \Rector\Tests\FileFormatter\EditorConfig\EditorConfigIdiosyncraticParser\EditorConfigIdiosyncraticParserTest
*/
final class EditorConfigIdiosyncraticParser implements EditorConfigParserInterface
{
/**
* @var EditorConfig
*/
private $editorConfig;
public function __construct(EditorConfig $editorConfig)
{
$this->editorConfig = $editorConfig;
}
public function extractConfigurationForFile(
File $file,
EditorConfigConfigurationBuilder $editorConfigConfigurationBuilder
): EditorConfigConfiguration {
$smartFileInfo = $file->getSmartFileInfo();
$configuration = $this->editorConfig->getConfigForPath($smartFileInfo->getRealPath());
if (array_key_exists(self::INDENT_STYLE, $configuration)) {
$indentStyle = (string) $configuration[self::INDENT_STYLE]->getValue();
$editorConfigConfigurationBuilder->withIndentStyle($indentStyle);
}
if (array_key_exists(self::INDENT_SIZE, $configuration)) {
$indentSize = (int) $configuration[self::INDENT_SIZE]->getValue();
$editorConfigConfigurationBuilder->withIndentSize($indentSize);
}
if (array_key_exists(self::END_OF_LINE, $configuration)) {
$endOfLine = (string) $configuration[self::END_OF_LINE]->getValue();
$editorConfigConfigurationBuilder->withEndOfLineFromEditorConfig($endOfLine);
}
if (array_key_exists(self::INSERT_FINAL_NEWLINE, $configuration)) {
$insertFinalNewline = (bool) $configuration[self::INSERT_FINAL_NEWLINE]->getValue();
$editorConfigConfigurationBuilder->withInsertFinalNewline($insertFinalNewline);
}
if (array_key_exists(self::TAB_WIDTH, $configuration)) {
$editorConfigConfigurationBuilder->withIndentSize($configuration[self::TAB_WIDTH]->getValue());
}
return $editorConfigConfigurationBuilder->build();
}
}

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Rector\FileFormatter\Exception;
use InvalidArgumentException;
final class InvalidIndentSizeException extends InvalidArgumentException
{
public static function fromSizeAndMinimumSize(int $size, int $minimumSize): self
{
$message = sprintf('Size %d must be greater than %d', $size, $minimumSize);
return new self($message);
}
}

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Rector\FileFormatter\Exception;
use InvalidArgumentException;
final class InvalidIndentStringException extends InvalidArgumentException
{
public static function fromString(string $string): self
{
$message = sprintf('This is not valid indentation "%s"', $string);
return new self($message);
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Rector\FileFormatter\Exception;
use InvalidArgumentException;
final class InvalidIndentStyleException extends InvalidArgumentException
{
/**
* @param array<int, string> $allowedStyles
*/
public static function fromStyleAndAllowedStyles(string $style, array $allowedStyles): self
{
$message = sprintf('Given style "%s" is not allowed. Allowed are "%s"', $style, implode(' ', $allowedStyles));
return new self($message);
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Rector\FileFormatter\Exception;
use InvalidArgumentException;
final class InvalidNewLineStringException extends InvalidArgumentException
{
public static function fromString(string $string): self
{
return new self(sprintf('"%s" is not a valid new-line character sequence.', $string));
}
public static function create(string $message): self
{
return new self($message);
}
}

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Rector\FileFormatter\Exception;
use UnexpectedValueException;
final class ParseIndentException extends UnexpectedValueException
{
public static function fromString(string $string): self
{
$message = sprintf('The content "%s" could not be parsed', $string);
return new self($message);
}
}

View File

@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
namespace Rector\FileFormatter;
use Rector\Core\Configuration\Option;
use Rector\Core\ValueObject\Application\File;
use Rector\FileFormatter\Contract\EditorConfig\EditorConfigParserInterface;
use Rector\FileFormatter\Contract\Formatter\FileFormatterInterface;
use Rector\FileFormatter\Exception\InvalidNewLineStringException;
use Rector\FileFormatter\Exception\ParseIndentException;
use Rector\FileFormatter\ValueObject\EditorConfigConfiguration;
use Rector\FileFormatter\ValueObject\Indent;
use Rector\FileFormatter\ValueObject\NewLine;
use Rector\FileFormatter\ValueObjectFactory\EditorConfigConfigurationBuilder;
use Symplify\PackageBuilder\Parameter\ParameterProvider;
final class FileFormatter
{
/**
* @var EditorConfigParserInterface
*/
private $editorConfigParser;
/**
* @var FileFormatterInterface[]
*/
private $fileFormatters = [];
/**
* @var ParameterProvider
*/
private $parameterProvider;
/**
* @param FileFormatterInterface[] $fileFormatters
*/
public function __construct(
EditorConfigParserInterface $editorConfigParser,
ParameterProvider $parameterProvider,
array $fileFormatters = [])
{
$this->editorConfigParser = $editorConfigParser;
$this->fileFormatters = $fileFormatters;
$this->parameterProvider = $parameterProvider;
}
/**
* @param File[] $files
*/
public function format(array $files): void
{
foreach ($files as $file) {
if (! $file->hasChanged()) {
continue;
}
foreach ($this->fileFormatters as $fileFormatter) {
if (! $fileFormatter->supports($file)) {
continue;
}
$editorConfigConfigurationBuilder = $fileFormatter->createDefaultEditorConfigConfigurationBuilder();
$this->sniffOriginalFileContent($file, $editorConfigConfigurationBuilder);
$editorConfiguration = $this->createEditorConfiguration($file, $editorConfigConfigurationBuilder);
$fileFormatter->format($file, $editorConfiguration);
}
}
}
private function sniffOriginalFileContent(
File $file,
EditorConfigConfigurationBuilder $editorConfigConfigurationBuilder
): void {
// Try to sniff into the original content to get the indentation and new line
try {
$indent = Indent::fromContent($file->getOriginalFileContent());
$editorConfigConfigurationBuilder->withIndent($indent);
} catch (ParseIndentException $parseIndentException) {
}
try {
$newLine = NewLine::fromContent($file->getOriginalFileContent());
$editorConfigConfigurationBuilder->withNewLine($newLine);
} catch (InvalidNewLineStringException $invalidNewLineStringException) {
}
}
private function createEditorConfiguration(
File $file,
EditorConfigConfigurationBuilder $editorConfigConfigurationBuilder
): EditorConfigConfiguration {
if (! $this->parameterProvider->provideBoolParameter(Option::ENABLE_EDITORCONFIG)) {
return $editorConfigConfigurationBuilder->build();
}
return $this->editorConfigParser->extractConfigurationForFile($file, $editorConfigConfigurationBuilder);
}
}

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Rector\FileFormatter\Formatter;
use Ergebnis\Json\Printer\PrinterInterface;
use Rector\Core\ValueObject\Application\File;
use Rector\FileFormatter\Contract\Formatter\FileFormatterInterface;
use Rector\FileFormatter\ValueObject\EditorConfigConfiguration;
use Rector\FileFormatter\ValueObject\Indent;
use Rector\FileFormatter\ValueObjectFactory\EditorConfigConfigurationBuilder;
/**
* @see \Rector\Tests\FileFormatter\Formatter\JsonFileFormatter\JsonFileFormatterTest
*/
final class JsonFileFormatter implements FileFormatterInterface
{
/**
* @var PrinterInterface
*/
private $jsonPrinter;
public function __construct(PrinterInterface $jsonPrinter)
{
$this->jsonPrinter = $jsonPrinter;
}
public function supports(File $file): bool
{
$smartFileInfo = $file->getSmartFileInfo();
return $smartFileInfo->getExtension() === 'json';
}
public function format(File $file, EditorConfigConfiguration $editorConfigConfiguration): void
{
$newFileContent = $this->jsonPrinter->print(
$file->getFileContent(),
$editorConfigConfiguration->getIndent(),
$editorConfigConfiguration->getNewLine()
);
$newFileContent .= $editorConfigConfiguration->getFinalNewline();
$file->changeFileContent($newFileContent);
}
public function createDefaultEditorConfigConfigurationBuilder(): EditorConfigConfigurationBuilder
{
$editorConfigConfigurationBuilder = EditorConfigConfigurationBuilder::anEditorConfigConfiguration();
$editorConfigConfigurationBuilder->withIndent(Indent::createSpaceWithSize(4));
return $editorConfigConfigurationBuilder;
}
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Rector\FileFormatter\Formatter;
use PrettyXml\Formatter;
use Rector\Core\ValueObject\Application\File;
use Rector\FileFormatter\Contract\Formatter\FileFormatterInterface;
use Rector\FileFormatter\ValueObject\EditorConfigConfiguration;
use Rector\FileFormatter\ValueObject\Indent;
use Rector\FileFormatter\ValueObjectFactory\EditorConfigConfigurationBuilder;
/**
* @see \Rector\Tests\FileFormatter\Formatter\XmlFileFormatter\XmlFileFormatterTest
*/
final class XmlFileFormatter implements FileFormatterInterface
{
/**
* @var Formatter
*/
private $xmlFormatter;
public function __construct(Formatter $xmlFormatter)
{
$this->xmlFormatter = $xmlFormatter;
}
public function supports(File $file): bool
{
$smartFileInfo = $file->getSmartFileInfo();
return $smartFileInfo->getExtension() === 'xml';
}
public function format(File $file, EditorConfigConfiguration $editorConfigConfiguration): void
{
$this->xmlFormatter->setIndentCharacter($editorConfigConfiguration->getIndentStyleCharacter());
$this->xmlFormatter->setIndentSize($editorConfigConfiguration->getIndentSize());
$newFileContent = $this->xmlFormatter->format($file->getFileContent());
$newFileContent .= $editorConfigConfiguration->getFinalNewline();
$file->changeFileContent($newFileContent);
}
public function createDefaultEditorConfigConfigurationBuilder(): EditorConfigConfigurationBuilder
{
$editorConfigConfigurationBuilder = EditorConfigConfigurationBuilder::anEditorConfigConfiguration();
$editorConfigConfigurationBuilder->withIndent(Indent::createTabWithSize(1));
return $editorConfigConfigurationBuilder;
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Rector\FileFormatter\Formatter;
use Rector\Core\ValueObject\Application\File;
use Rector\FileFormatter\Contract\Formatter\FileFormatterInterface;
use Rector\FileFormatter\ValueObject\EditorConfigConfiguration;
use Rector\FileFormatter\ValueObject\Indent;
use Rector\FileFormatter\ValueObjectFactory\EditorConfigConfigurationBuilder;
use Symfony\Component\Yaml\Yaml;
/**
* @see \Rector\Tests\FileFormatter\Formatter\YamlFileFormatter\YamlFileFormatterTest
*/
final class YamlFileFormatter implements FileFormatterInterface
{
public function supports(File $file): bool
{
$smartFileInfo = $file->getSmartFileInfo();
return in_array($smartFileInfo->getExtension(), ['yaml', 'yml'], true);
}
public function format(File $file, EditorConfigConfiguration $editorConfigConfiguration): void
{
$yaml = Yaml::parse($file->getFileContent());
$newFileContent = Yaml::dump($yaml, 99, $editorConfigConfiguration->getIndentSize());
$newFileContent .= $editorConfigConfiguration->getFinalNewline();
$file->changeFileContent($newFileContent);
}
public function createDefaultEditorConfigConfigurationBuilder(): EditorConfigConfigurationBuilder
{
$editorConfigConfigurationBuilder = EditorConfigConfigurationBuilder::anEditorConfigConfiguration();
$editorConfigConfigurationBuilder->withIndent(Indent::createSpaceWithSize(2));
return $editorConfigConfigurationBuilder;
}
}

View File

@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace Rector\FileFormatter\ValueObject;
/**
* @see \Rector\Tests\FileFormatter\ValueObject\EditorConfigConfigurationTest
*/
final class EditorConfigConfiguration
{
/**
* @var NewLine
*/
private $newLine;
/**
* @var bool
*/
private $insertFinalNewline = false;
/**
* @var Indent
*/
private $indent;
public function __construct(Indent $indent, NewLine $newLine, bool $insertFinalNewline)
{
$this->indent = $indent;
$this->newLine = $newLine;
$this->insertFinalNewline = $insertFinalNewline;
}
public function getNewLine(): string
{
return $this->newLine->__toString();
}
public function getFinalNewline(): string
{
return $this->insertFinalNewline ? $this->getNewLine() : '';
}
public function getIndent(): string
{
return $this->indent->__toString();
}
public function getIndentStyleCharacter(): string
{
return $this->indent->getIndentStyleCharacter();
}
public function getIndentStyle(): string
{
return $this->indent->getIndentStyle();
}
public function getIndentSize(): int
{
return $this->indent->getIndentSize();
}
}

View File

@ -0,0 +1,133 @@
<?php
declare(strict_types=1);
namespace Rector\FileFormatter\ValueObject;
use Nette\Utils\Strings;
use Rector\FileFormatter\Exception\InvalidIndentSizeException;
use Rector\FileFormatter\Exception\InvalidIndentStringException;
use Rector\FileFormatter\Exception\InvalidIndentStyleException;
use Rector\FileFormatter\Exception\ParseIndentException;
/**
* @see \Rector\Tests\FileFormatter\ValueObject\IndentTest
*/
final class Indent
{
/**
* @var string[]
*/
public const CHARACTERS = [
self::SPACE => ' ',
self::TAB => "\t",
];
/**
* @var string
*/
private const SPACE = 'space';
/**
* @var string
*/
private const TAB = 'tab';
/**
* @see https://regex101.com/r/A2XiaF/1
* @var string
*/
private const VALID_INDENT_REGEX = '/^( *|\t+)$/';
/**
* @var int
*/
private const MINIMUM_SIZE = 1;
/**
* @see https://regex101.com/r/3HFEjX/1
* @var string
*/
private const PARSE_INDENT_REGEX = '/^(?P<indent>( +|\t+)).*/m';
/**
* @var string
*/
private $string;
private function __construct(string $string)
{
$this->string = $string;
}
public function __toString(): string
{
return $this->string;
}
public static function fromString(string $string): self
{
$validIndent = preg_match(self::VALID_INDENT_REGEX, $string);
if ($validIndent !== 1) {
throw InvalidIndentStringException::fromString($string);
}
return new self($string);
}
public static function createSpaceWithSize(int $size): self
{
return self::fromSizeAndStyle($size, self::SPACE);
}
public static function createTabWithSize(int $size): self
{
return self::fromSizeAndStyle($size, self::TAB);
}
public static function fromSizeAndStyle(int $size, string $style): self
{
if ($size < self::MINIMUM_SIZE) {
throw InvalidIndentSizeException::fromSizeAndMinimumSize($size, self::MINIMUM_SIZE);
}
if (! array_key_exists($style, self::CHARACTERS)) {
throw InvalidIndentStyleException::fromStyleAndAllowedStyles($style, array_keys(self::CHARACTERS));
}
$value = str_repeat(self::CHARACTERS[$style], $size);
return new self($value);
}
public static function fromContent(string $string): self
{
$validIndent = preg_match(self::PARSE_INDENT_REGEX, $string, $match);
if ($validIndent === 1) {
return self::fromString($match['indent']);
}
throw ParseIndentException::fromString($string);
}
public function getIndentSize(): int
{
return strlen($this->string);
}
public function getIndentStyle(): string
{
return $this->startsWithSpace() ? self::SPACE : self::TAB;
}
public function getIndentStyleCharacter(): string
{
return $this->startsWithSpace() ? self::CHARACTERS[self::SPACE] : self::CHARACTERS[self::TAB];
}
private function startsWithSpace(): bool
{
return Strings::startsWith($this->string, ' ');
}
}

View File

@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace Rector\FileFormatter\ValueObject;
use const PHP_EOL;
use Rector\FileFormatter\Exception\InvalidNewLineStringException;
/**
* @see \Rector\Tests\FileFormatter\ValueObject\NewLineTest
*/
final class NewLine
{
/**
* @var string
*/
public const LINE_FEED = 'lf';
/**
* @var string
*/
public const CARRIAGE_RETURN = 'cr';
/**
* @var string
*/
public const CARRIAGE_RETURN_LINE_FEED = 'crlf';
/**
* @var array<string, string>
*/
private const ALLOWED_END_OF_LINE = [
self::LINE_FEED => "\n",
self::CARRIAGE_RETURN => "\r",
self::CARRIAGE_RETURN_LINE_FEED => "\r\n",
];
/**
* @var string
*/
private $string;
private function __construct(string $string)
{
$this->string = $string;
}
public function __toString(): string
{
return $this->string;
}
public static function fromSingleCharacter(string $string): self
{
$validNewLineRegularExpression = '/^(?>\r\n|\n|\r)$/';
$validNewLine = preg_match($validNewLineRegularExpression, $string);
if ($validNewLine !== 1) {
throw InvalidNewLineStringException::fromString($string);
}
return new self($string);
}
public static function fromContent(string $string): self
{
$validNewLineRegularExpression = '/(?P<newLine>\r\n|\n|\r)/';
$validNewLine = preg_match($validNewLineRegularExpression, $string, $match);
if ($validNewLine === 1) {
return self::fromSingleCharacter($match['newLine']);
}
return self::fromSingleCharacter(PHP_EOL);
}
public static function fromEditorConfig(string $endOfLine): self
{
if (! array_key_exists($endOfLine, self::ALLOWED_END_OF_LINE)) {
$allowedEndOfLineValues = array_keys(self::ALLOWED_END_OF_LINE);
$message = sprintf(
'The endOfLine "%s" is not allowed. Allowed are "%s"',
$endOfLine,
implode(',', $allowedEndOfLineValues)
);
throw InvalidNewLineStringException::create($message);
}
return self::fromSingleCharacter(self::ALLOWED_END_OF_LINE[$endOfLine]);
}
}

View File

@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace Rector\FileFormatter\ValueObjectFactory;
use Rector\FileFormatter\ValueObject\EditorConfigConfiguration;
use Rector\FileFormatter\ValueObject\Indent;
use Rector\FileFormatter\ValueObject\NewLine;
final class EditorConfigConfigurationBuilder
{
/**
* @var string
*/
private $indentStyle;
/**
* @var int
*/
private $indentSize;
/**
* @var bool
*/
private $insertFinalNewline = false;
/**
* @var NewLine
*/
private $newLine;
private function __construct()
{
$this->indentStyle = 'space';
$this->indentSize = 2;
$this->newLine = NewLine::fromEditorConfig('lf');
$this->insertFinalNewline = true;
}
public static function anEditorConfigConfiguration(): self
{
return new self();
}
public function withNewLine(NewLine $newLine): self
{
$this->newLine = $newLine;
return $this;
}
public function withIndent(Indent $indent): self
{
$this->indentSize = $indent->getIndentSize();
$this->indentStyle = $indent->getIndentStyle();
return $this;
}
public function withIndentStyle(string $indentStyle): self
{
$this->indentStyle = $indentStyle;
return $this;
}
public function withIndentSize(int $indentSize): self
{
$this->indentSize = $indentSize;
return $this;
}
public function withInsertFinalNewline(bool $insertFinalNewline): self
{
$this->insertFinalNewline = $insertFinalNewline;
return $this;
}
public function withEndOfLineFromEditorConfig(string $endOfLine): self
{
$this->newLine = NewLine::fromEditorConfig($endOfLine);
return $this;
}
public function build(): EditorConfigConfiguration
{
$newLine = $this->newLine;
return new EditorConfigConfiguration(
Indent::fromSizeAndStyle($this->indentSize, $this->indentStyle),
$newLine,
$this->insertFinalNewline
);
}
}

View File

@ -8,6 +8,7 @@ use Rector\Core\Application\FileDecorator\FileDiffFileDecorator;
use Rector\Core\Configuration\Configuration;
use Rector\Core\Contract\Processor\FileProcessorInterface;
use Rector\Core\ValueObject\Application\File;
use Rector\FileFormatter\FileFormatter;
use Symplify\SmartFileSystem\SmartFileSystem;
final class ApplicationFileProcessor
@ -32,6 +33,11 @@ final class ApplicationFileProcessor
*/
private $fileDiffFileDecorator;
/**
* @var FileFormatter
*/
private $fileFormatter;
/**
* @param FileProcessorInterface[] $fileProcessors
*/
@ -39,12 +45,14 @@ final class ApplicationFileProcessor
Configuration $configuration,
SmartFileSystem $smartFileSystem,
FileDiffFileDecorator $fileDiffFileDecorator,
FileFormatter $fileFormatter,
array $fileProcessors = []
) {
$this->fileProcessors = $fileProcessors;
$this->smartFileSystem = $smartFileSystem;
$this->configuration = $configuration;
$this->fileDiffFileDecorator = $fileDiffFileDecorator;
$this->fileFormatter = $fileFormatter;
}
/**
@ -54,6 +62,8 @@ final class ApplicationFileProcessor
{
$this->processFiles($files);
$this->fileFormatter->format($files);
$this->fileDiffFileDecorator->decorate($files);
$this->printFiles($files);

View File

@ -162,4 +162,9 @@ final class Option
* @var string
*/
public const TEMPLATE_TYPE = 'template-type';
/**
* @var string
*/
public const ENABLE_EDITORCONFIG = 'enable_editorconfig';
}

View File

@ -0,0 +1,16 @@
<?php
namespace Rector\Core\Contract\Formatter;
use Rector\Core\ValueObject\Application\File;
use Rector\FileFormatter\ValueObject\EditorConfigConfiguration;
use Rector\FileFormatter\ValueObjectFactory\EditorConfigConfigurationBuilder;
interface FileFormatterInterface
{
public function supports(File $file): bool;
public function format(File $file, EditorConfigConfiguration $editorConfigConfiguration): void;
public function createEditorConfigConfigurationBuilder(): EditorConfigConfigurationBuilder;
}