Skip to content
Open
32 changes: 14 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,8 @@ return (new PhpCsFixer\Config())
->registerCustomFixers([new BlockStringFixer()])
->setRiskyAllowed(true)
->setRules([
BlockStringFixer::NAME => [
'formatters' => [
BlockStringFixer::NAME => BlockStringFixer::config(
[

// 1️⃣ SimpleLineFormatter
// Normalizes indentation of any block not explicitly configured below
Expand Down Expand Up @@ -186,8 +186,8 @@ return (new PhpCsFixer\Config())
),
),

],
],
]
),
]);

```
Expand Down Expand Up @@ -288,10 +288,6 @@ Extending this class makes sense in two situations:
2. Or if, for whatever reason, the [`CodecInterface`] concept does not work for you and you want to write
something from scratch.

### [AbstractCodecFormatter](./src/Formatter/AbstractCodecFormatter.php)

_Deprecated in favour of [`AbstractStringFormatter`]._

### [AbstractStringFormatter](./src/Formatter/AbstractStringFormatter.php)

This formatter base class is aware of string interpolation – it passes content through a codec before and after
Expand Down Expand Up @@ -324,9 +320,9 @@ final class MyFormatter extends AbstractStringFormatter
return (new PhpCsFixer\Config())
->registerCustomFixers([new BlockStringFixer()])
->setRules([
BlockStringFixer::NAME => [
BlockStringFixer::NAME => BlockStringFixer::config([
'TEXT' => new MyFormatter(),
],
]),
]);
```

Expand All @@ -341,12 +337,12 @@ Example:
return (new PhpCsFixer\Config())
->registerCustomFixers([new BlockStringFixer()])
->setRules([
BlockStringFixer::NAME => [
BlockStringFixer::NAME => BlockStringFixer::config([
'JSON' => new ChainFormatter(
new CliPipeFormatter('v1', ['cmd' => 'some-old-tool']),
new SimpleLineFormatter(4, "\t"),
),
],
]),
]);
```

Expand All @@ -361,7 +357,7 @@ Example:
return (new PhpCsFixer\Config())
->registerCustomFixers([new BlockStringFixer()])
->setRules([
BlockStringFixer::NAME => [
BlockStringFixer::NAME => BlockStringFixer::config([
'J' => new CliPipeFormatter(
// Either a version as a string, or the command to get the version (as an array).
versionValueOrCommand: '1.0',
Expand All @@ -372,7 +368,7 @@ return (new PhpCsFixer\Config())
// A normalizer for handling end-of-line characters.
lineEndingNormalizer: null
)
],
]),
]);
```

Expand All @@ -393,7 +389,7 @@ Example:
return (new PhpCsFixer\Config())
->registerCustomFixers([new BlockStringFixer()])
->setRules([
BlockStringFixer::NAME => [
BlockStringFixer::NAME => BlockStringFixer::config([
'JSON' => new DockerPipeFormatter(
// The docker image; might contain url, tag or even the digest.
image: 'ghcr.io/jqlang/jq',
Expand All @@ -408,7 +404,7 @@ return (new PhpCsFixer\Config())
// A normalizer for handling end-of-line characters.
lineEndingNormalizer: null,
)
],
]),
]);
```

Expand All @@ -422,7 +418,7 @@ Example:
return (new PhpCsFixer\Config())
->registerCustomFixers([new BlockStringFixer()])
->setRules([
BlockStringFixer::NAME => [
BlockStringFixer::NAME => BlockStringFixer::config([
'TEXT' => new SimpleLineFormatter(
// The number of spaces defining one indentation level in your project.
indentSize: 4,
Expand All @@ -433,7 +429,7 @@ return (new PhpCsFixer\Config())
// A normalizer for handling end-of-line characters.
lineEndingNormalizer: null,
)
],
]),
]);
```

Expand Down
14 changes: 14 additions & 0 deletions src/CacheFingerprintableInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php declare(strict_types=1);

namespace uuf6429\PhpCsFixerBlockstring;

interface CacheFingerprintableInterface
{
/**
* This method should return a unique representation of the object and its state, to be used as a cache identifier.
* **Important:** Make to return simple values (null, scalar or arrays) only. Avoid closure, objects etc.
*
* @return mixed
*/
public function getCacheFingerprint();
}
37 changes: 28 additions & 9 deletions src/Fixer/BlockStringFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
use const T_START_HEREDOC;

/**
* @phpstan-type TFormatterConfig array{formatters: array<0|non-empty-string, AbstractFormatter>}
* @phpstan-type TFormatters array<0|non-empty-string, AbstractFormatter>
* @phpstan-type TDeserializedConfig array{formatters: TFormatters}
* @phpstan-type TSerializedConfig array{formatters?: string}
*
* @implements ConfigurableFixerInterface<TFormatterConfig, TFormatterConfig>
* @implements ConfigurableFixerInterface<TSerializedConfig, TDeserializedConfig>
*/
final class BlockStringFixer implements FixerInterface, ConfigurableFixerInterface
{
Expand All @@ -28,9 +30,9 @@ final class BlockStringFixer implements FixerInterface, ConfigurableFixerInterfa
private ?FixerConfigurationResolverInterface $configurationDefinition = null;

/**
* @var null|TFormatterConfig
* @var TDeserializedConfig
*/
private ?array $configuration = null;
private array $configuration;

public function isRisky(): bool
{
Expand Down Expand Up @@ -77,16 +79,22 @@ public function getConfigurationDefinition(): FixerConfigurationResolverInterfac

public function configure(array $configuration): void
{
$formatters = $configuration['formatters'] ?? 'a:0:{}';
if (($formatters = @unserialize($formatters, ['allowed_classes' => true])) === false) {
throw new InvalidArgumentException(
'BlockStringFixer configuration is not valid. '
. 'Did you set it up in your PHP CS Fixer config with `BlockStringFixer::config()`?'
);
}

// @phpstan-ignore assign.propertyType
$this->configuration = $this->getConfigurationDefinition()->resolve($configuration);
$this->configuration = [
'formatters' => $formatters,
];
}

public function fix(SplFileInfo $file, Tokens $tokens): void
{
if ($this->configuration === null) {
throw new InvalidArgumentException("Configuration for fixer {$this->getName()} is required.");
}

if (0 < $tokens->count() && $this->isCandidate($tokens) && $this->supports($file)) {
$blockStringStream = TokenStream::fromPhpCsFixerTokens($tokens);
while (($blockString = $blockStringStream->next()) !== null) {
Expand All @@ -100,4 +108,15 @@ public function fix(SplFileInfo $file, Tokens $tokens): void
}
}
}

/**
* @param TFormatters $formatters
* @return TSerializedConfig
*/
public static function config(array $formatters): array
{
return [
'formatters' => serialize($formatters),
];
}
}
12 changes: 0 additions & 12 deletions src/Formatter/AbstractCodecFormatter.php

This file was deleted.

28 changes: 22 additions & 6 deletions src/Formatter/AbstractFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace uuf6429\PhpCsFixerBlockstring\Formatter;

use uuf6429\PhpCsFixerBlockstring\BlockString\BlockString;
use uuf6429\PhpCsFixerBlockstring\CacheFingerprintableInterface;
use uuf6429\PhpCsFixerBlockstring\InterpolationCodec\CodecInterface;

/**
Expand All @@ -16,24 +17,39 @@
* 2. Or if, for whatever reason, the {@see CodecInterface} concept does not work for you and you want to write
* something from scratch.
*/
abstract class AbstractFormatter
abstract class AbstractFormatter implements CacheFingerprintableInterface
{
/**
* @var mixed
* @readonly
*/
protected string $version;
private $cacheFingerprint;

/**
* @param string $version A string representing a version of this formatter, used for caching purposes.
* For example, if the formatting algorithm/logic is changed, the version should also be different.
* @param mixed $cacheFingerprint A unique representation of the formatter logic and its configuration, used for
* caching purposes. For example, if the formatter executes some cli command with a specific version, the
* fingerprint should contain:
* - the formatter class (to distguish from other formatters)
* - the cli command name (since it's a setting of the formatter)
* - the cli command version (in case the cli command gets updated at some point)
*
* **Important:** Make sure that the fingerprint contains simple values (null, scalar or arrays).
*/
public function __construct(string $version)
public function __construct($cacheFingerprint)
{
$this->version = $version;
$this->cacheFingerprint = $cacheFingerprint;
}

/**
* Format the provided BlockString accordingly and return a new one.
*/
abstract public function formatBlock(BlockString $blockString): BlockString;

/**
* @return mixed
*/
final public function getCacheFingerprint()
{
return $this->cacheFingerprint;
}
}
21 changes: 14 additions & 7 deletions src/Formatter/AbstractStringFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@
* return (new PhpCsFixer\Config())
* ->registerCustomFixers([new BlockStringFixer()])
* ->setRules([
* BlockStringFixer::NAME => [
* BlockStringFixer::NAME => BlockStringFixer::config([
* 'TEXT' => new MyFormatter(),
* ],
* ]),
* ]);
* ```
*/
Expand All @@ -66,16 +66,23 @@ abstract class AbstractStringFormatter extends AbstractFormatter
*/
private NormalizerInterface $lineEndingNormalizer;

/**
* @param mixed $cacheFingerprint {@see AbstractFormatter::__construct()}
*/
public function __construct(
string $version,
?CodecInterface $interpolationCodec=null,
?NormalizerInterface $lineEndingNormalizer=null
$cacheFingerprint,
?CodecInterface $interpolationCodec = null,
?NormalizerInterface $lineEndingNormalizer = null
) {
parent::__construct($version);

$this->objectIndex = self::$objectCounter++;
$this->interpolationCodec = $interpolationCodec ?? new PlainStringCodec();
$this->lineEndingNormalizer = $lineEndingNormalizer ?? new DefaultNormalizer(DefaultNormalizer::NO_CHANGE, DefaultNormalizer::NO_CHANGE);

parent::__construct([
$cacheFingerprint,
$this->interpolationCodec->getCacheFingerprint(),
$this->lineEndingNormalizer->getCacheFingerprint(),
]);
}

final public function formatBlock(BlockString $blockString): BlockString
Expand Down
11 changes: 7 additions & 4 deletions src/Formatter/ChainFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
* return (new PhpCsFixer\Config())
* ->registerCustomFixers([new BlockStringFixer()])
* ->setRules([
* BlockStringFixer::NAME => [
* BlockStringFixer::NAME => BlockStringFixer::config([
* 'JSON' => new ChainFormatter(
* new CliPipeFormatter('v1', ['cmd' => 'some-old-tool']),
* new SimpleLineFormatter(4, "\t"),
* ),
* ],
* ]),
* ]);
* ```
*/
Expand All @@ -35,9 +35,12 @@ final class ChainFormatter extends AbstractFormatter
*/
public function __construct(AbstractFormatter ...$formatters)
{
parent::__construct('1.0');

$this->formatters = array_values($formatters);

parent::__construct([
self::class,
array_map(static fn(AbstractFormatter $formatter) => $formatter->getCacheFingerprint(), $this->formatters),
]);
}

public function formatBlock(BlockString $blockString): BlockString
Expand Down
17 changes: 12 additions & 5 deletions src/Formatter/CliPipeFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* return (new PhpCsFixer\Config())
* ->registerCustomFixers([new BlockStringFixer()])
* ->setRules([
* BlockStringFixer::NAME => [
* BlockStringFixer::NAME => BlockStringFixer::config([
* 'J' => new CliPipeFormatter(
* // Either a version as a string, or the command to get the version (as an array).
* versionValueOrCommand: '1.0',
Expand All @@ -28,7 +28,7 @@
* // A normalizer for handling end-of-line characters.
* lineEndingNormalizer: null
* )
* ],
* ]),
* ]);
* ```
*
Expand Down Expand Up @@ -77,9 +77,16 @@ public function __construct(
}

parent::__construct(
is_string($versionValueOrCommand)
? $versionValueOrCommand
: $this->exec($versionValueOrCommand, null),
sprintf(
'%s: %s v%s',
static::class,
is_array($this->formatter['cmd'])
? implode(' ', $this->formatter['cmd'])
: $this->formatter['cmd'],
is_string($versionValueOrCommand)
? $versionValueOrCommand
: $this->exec($versionValueOrCommand, null)
),
$interpolationCodec,
$lineEndingNormalizer
);
Expand Down
Loading
Loading