Skip to content

Commit

Permalink
Add rule about file and directory
Browse files Browse the repository at this point in the history
  • Loading branch information
VincentLanglet committed Jan 19, 2024
1 parent 222d478 commit 7314c90
Show file tree
Hide file tree
Showing 61 changed files with 413 additions and 19 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"composer-runtime-api": "^2.0.0",
"symfony/console": "^5.4.9 || ^6.0 || ^7.0",
"symfony/finder": "^5.4 || ^6.0 || ^7.0",
"symfony/string": "^5.4 || ^6.0 || ^7.0",
"twig/twig": "^2.14.0 || ^3.0.5",
"webmozart/assert": "^1.10"
},
Expand Down
74 changes: 74 additions & 0 deletions src/File/FileHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace TwigCsFixer\File;

final class FileHelper
{
/**
* @param array<string> $ignoredDir
*/
public static function getFileName(
string $absolutePath,
?string $baseDir = null,
array $ignoredDir = []
): ?string {
$split = self::splitPath($absolutePath, $baseDir, $ignoredDir);
if ([] === $split) {
return null;
}

return end($split);
}

/**
* @param array<string> $ignoredDir
*
* @return list<string>
*/
public static function getDirectories(
string $absolutePath,
?string $baseDir = null,
array $ignoredDir = []
): array {
$split = self::splitPath($absolutePath, $baseDir, $ignoredDir);
array_pop($split);

return $split;
}

/**
* @param array<string> $ignoredDir
*
* @return list<string>
*/
private static function splitPath(
string $absolutePath,
?string $baseDir = null,
array $ignoredDir = []
): array {
$baseDir = trim($baseDir ?? '', '/');

if ('' === $baseDir) {
$baseDirPosition = 0;
$baseDir = '/';
} else {
$baseDir = '/'.$baseDir.'/';
$baseDirPosition = strrpos($absolutePath, $baseDir);
if (false === $baseDirPosition) {
return [];
}
}

$path = substr($absolutePath, $baseDirPosition + \strlen($baseDir));
foreach ($ignoredDir as $ignoredDirectory) {
$ignoredDirectory = trim($ignoredDirectory, '/').'/';
if (str_starts_with($path, $ignoredDirectory)) {
return [];
}
}

return explode('/', $path);
}
}
68 changes: 68 additions & 0 deletions src/Rules/File/DirectoryNameRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace TwigCsFixer\Rules\File;

use Symfony\Component\String\UnicodeString;
use TwigCsFixer\File\FileHelper;
use TwigCsFixer\Rules\AbstractRule;
use TwigCsFixer\Rules\ConfigurableRuleInterface;

final class DirectoryNameRule extends AbstractRule implements ConfigurableRuleInterface
{
public const SNAKE_CASE = 'snake_case';
public const CAMEL_CASE = 'camelCase';
public const PASCAL_CASE = 'PascalCase';
public const KEBAB_CASE = 'kebab-case';

/**
* @param self::* $case
* @param array<string> $ignoredSubDirectories
*/
public function __construct(
private string $case = self::SNAKE_CASE,
private ?string $baseDirectory = null,
private array $ignoredSubDirectories = [],
) {
}

public function getConfiguration(): array
{
return [
'case' => $this->case,
'baseDirectory' => $this->baseDirectory,
'ignoredSubDirectories' => $this->ignoredSubDirectories,
];
}

protected function process(int $tokenPosition, array $tokens): void
{
if (0 !== $tokenPosition) {
return;
}

$token = $tokens[$tokenPosition];
$directories = FileHelper::getDirectories(
$token->getFilename(),
$this->baseDirectory,
$this->ignoredSubDirectories,
);

foreach ($directories as $directory) {
$expected = match ($this->case) {
self::SNAKE_CASE => (new UnicodeString($directory))->snake()->toString(),
self::CAMEL_CASE => (new UnicodeString($directory))->camel()->toString(),
self::PASCAL_CASE => ucfirst((new UnicodeString($directory))->camel()->toString()),
self::KEBAB_CASE => (new UnicodeString($directory))->snake()->replace('_', '-')->toString(),
};

if ($expected !== $directory) {
$this->addError(
sprintf('The directory name must use %s ; expected %s.', $this->case, $expected),
$token,
);
}
}
}
}
70 changes: 70 additions & 0 deletions src/Rules/File/FileNameRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

namespace TwigCsFixer\Rules\File;

use Symfony\Component\String\UnicodeString;
use TwigCsFixer\File\FileHelper;
use TwigCsFixer\Rules\AbstractRule;
use TwigCsFixer\Rules\ConfigurableRuleInterface;

final class FileNameRule extends AbstractRule implements ConfigurableRuleInterface
{
public const SNAKE_CASE = 'snake_case';
public const CAMEL_CASE = 'camelCase';
public const PASCAL_CASE = 'PascalCase';
public const KEBAB_CASE = 'kebab-case';

/**
* @param self::* $case
* @param array<string> $ignoredSubDirectories
*/
public function __construct(
private string $case = self::SNAKE_CASE,
private ?string $baseDirectory = null,
private array $ignoredSubDirectories = [],
) {
}

public function getConfiguration(): array
{
return [
'case' => $this->case,
'baseDirectory' => $this->baseDirectory,
'ignoredSubDirectories' => $this->ignoredSubDirectories,
];
}

protected function process(int $tokenPosition, array $tokens): void
{
if (0 !== $tokenPosition) {
return;
}

$token = $tokens[$tokenPosition];
$fileName = FileHelper::getFileName(
$token->getFilename(),
$this->baseDirectory,
$this->ignoredSubDirectories,
);
if (null === $fileName) {
return;
}
$fileName = explode('.', $fileName)[0]; // Avoid conflict with some extensions

$expected = match ($this->case) {
self::SNAKE_CASE => (new UnicodeString($fileName))->snake()->toString(),
self::CAMEL_CASE => (new UnicodeString($fileName))->camel()->toString(),
self::PASCAL_CASE => ucfirst((new UnicodeString($fileName))->camel()->toString()),
self::KEBAB_CASE => (new UnicodeString($fileName))->snake()->replace('_', '-')->toString(),
};

if ($expected !== $fileName) {
$this->addError(
sprintf('The file name must use %s ; expected %s.', $this->case, $expected),
$token,
);
}
}
}
26 changes: 26 additions & 0 deletions src/Standard/Symfony.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace TwigCsFixer\Standard;

use TwigCsFixer\Rules\File\DirectoryNameRule;
use TwigCsFixer\Rules\File\FileNameRule;

/**
* Standard from Symfony.
*
* @see https://twig.symfony.com/doc/3.x/coding_standards.html
* @see https://symfony.com/doc/current/templates.html#template-naming
*/
final class Symfony implements StandardInterface
{
public function getRules(): array
{
return [
...(new Twig())->getRules(),
new FileNameRule(baseDirectory: 'templates', ignoredSubDirectories: ['bundles']),
new DirectoryNameRule(baseDirectory: 'templates', ignoredSubDirectories: ['bundles']),
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use TwigCsFixer\Rules\Delimiter\BlockNameSpacingRule;
use TwigCsFixer\Tests\Rules\AbstractRuleTestCase;

final class BlockNameSpacingTest extends AbstractRuleTestCase
final class BlockNameSpacingRuleTest extends AbstractRuleTestCase
{
public function testRule(): void
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use TwigCsFixer\Rules\Delimiter\DelimiterSpacingRule;
use TwigCsFixer\Tests\Rules\AbstractRuleTestCase;

final class DelimiterSpacingTest extends AbstractRuleTestCase
final class DelimiterSpacingRuleTest extends AbstractRuleTestCase
{
public function testRule(): void
{
Expand Down
71 changes: 71 additions & 0 deletions tests/Rules/File/DirectoryName/DirectoryNameRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

namespace TwigCsFixer\Tests\Rules\File\DirectoryName;

use TwigCsFixer\Rules\File\DirectoryNameRule;
use TwigCsFixer\Tests\Rules\AbstractRuleTestCase;

final class DirectoryNameRuleTest extends AbstractRuleTestCase
{
public function testRule(): void
{
$this->checkRule(new DirectoryNameRule(baseDirectory: 'templates'), []);
}

public function testRuleValidTemplatesDirectory(): void
{
$this->checkRule(
new DirectoryNameRule(baseDirectory: 'templates'),
[],
__DIR__.'/templates/directory_name_rule_test/DirectoryNameRuleTest.twig'
);
}

public function testRuleInvalidTemplatesDirectory(): void
{
$this->checkRule(
new DirectoryNameRule(baseDirectory: 'templates'),
['DirectoryName.Error:1:1'],
__DIR__.'/templates/directoryNameRuleTest/DirectoryNameRuleTest.twig'
);
}

public function testRulePascalCase(): void
{
$this->checkRule(new DirectoryNameRule(DirectoryNameRule::PASCAL_CASE, baseDirectory: 'File'), []);
}

public function testRuleInvalidDirectory(): void
{
$this->checkRule(new DirectoryNameRule(baseDirectory: 'File'), ['DirectoryName.Error:1:1']);
}

public function testRuleKebabCase(): void
{
$this->checkRule(
new DirectoryNameRule(DirectoryNameRule::KEBAB_CASE, baseDirectory: 'templates'),
[],
__DIR__.'/templates/directory-name-rule-test/DirectoryNameRuleTest.twig',
);
}

public function testRuleCamelCase(): void
{
$this->checkRule(
new DirectoryNameRule(DirectoryNameRule::CAMEL_CASE, baseDirectory: 'templates'),
[],
__DIR__.'/templates/directoryNameRuleTest/DirectoryNameRuleTest.twig',
);
}

public function testRuleIgnoredDirectory(): void
{
$this->checkRule(
new DirectoryNameRule(baseDirectory: 'templates', ignoredSubDirectories: ['bundles']),
[],
__DIR__.'/templates/bundles/directoryNameRuleTest/DirectoryNameRuleTest.twig'
);
}
}
1 change: 1 addition & 0 deletions tests/Rules/File/DirectoryName/DirectoryNameRuleTest.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Nothing
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Nothing
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Nothing
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Nothing
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Nothing
43 changes: 43 additions & 0 deletions tests/Rules/File/FileName/FileNameRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace TwigCsFixer\Tests\Rules\File\FileName;

use TwigCsFixer\Rules\File\FileNameRule;
use TwigCsFixer\Tests\Rules\AbstractRuleTestCase;

final class FileNameRuleTest extends AbstractRuleTestCase
{
public function testRule(): void
{
$this->checkRule(new FileNameRule(), [
'FileName.Error:1:1',
]);
}

public function testRulePascalCase(): void
{
$this->checkRule(new FileNameRule(FileNameRule::PASCAL_CASE), []);
}

public function testRuleKebabCase(): void
{
$this->checkRule(new FileNameRule(FileNameRule::KEBAB_CASE), [], __DIR__.'/file-name-rule-test.twig');
}

public function testRuleCamelCase(): void
{
$this->checkRule(new FileNameRule(FileNameRule::CAMEL_CASE), [], __DIR__.'/fileNameRuleTest.camel.twig');
}

public function testRuleValidFile(): void
{
$this->checkRule(new FileNameRule(), [], __DIR__.'/file_name_rule_test.twig');
}

public function testRuleValidFileWithDot(): void
{
$this->checkRule(new FileNameRule(), [], __DIR__.'/file_name_rule_test.withDot.twig');
}
}
1 change: 1 addition & 0 deletions tests/Rules/File/FileName/FileNameRuleTest.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Nothing
1 change: 1 addition & 0 deletions tests/Rules/File/FileName/file-name-rule-test.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Nothing
1 change: 1 addition & 0 deletions tests/Rules/File/FileName/fileNameRuleTest.camel.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Nothing
Loading

0 comments on commit 7314c90

Please sign in to comment.