Skip to content

Commit 60ff7f0

Browse files
authored
Handle end-of-line normalisation (#16)
* Initial implementation * Add tests * Improve test coverage
1 parent 5f0b101 commit 60ff7f0

16 files changed

+464
-115
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -331,14 +331,14 @@ final class MyFormatter extends AbstractCodecFormatter
331331
}
332332
}
333333

334-
['formatters' => [ new MyFormatter('1.0', new PlainStringCodec()) ]]
334+
['formatters' => [ new MyFormatter('1.0') ]]
335335
```
336336

337337
Example with an anonymous class:
338338

339339
```php
340340
['formatters' => [
341-
new class ('1.0', new PlainStringCodec()) extends AbstractCodecFormatter
341+
new class ('1.0') extends AbstractCodecFormatter
342342
{
343343
protected function formatContent(string $original): string
344344
{
@@ -386,7 +386,7 @@ Example:
386386
versionValueOrCommand: '1.0', // Either a version as a string, or the command to get the version (as an array).
387387
formatCommand: ['cmd' => 'jfmt -'], // An array defining the external command to do the formatting.
388388
interpolationCodec: new PlainStringCodec(), // A codec for handling interpolations; depends on the content being formatted.
389-
stripLastNewLine: true, // Remove last line from cli output - you might need this, depending on the platform/shell.
389+
lineEndingNormalizer: null, // A normalizer for handling end-of-line characters.
390390
) ]]
391391
```
392392

@@ -410,7 +410,7 @@ Example:
410410
command: ['bin/tool', '--dry-run', '-'], // The command to run within the container, including any arguments.
411411
pullMode: 'always', // How/when the image should be pulled: 'never', 'always' or 'missing'.
412412
interpolationCodec: new PlainStringCodec(), // A codec for handling interpolations; depends on the content being formatted.
413-
stripLastNewLine: true, // Remove last line from docker output - typically needed.
413+
lineEndingNormalizer: null, // A normalizer for handling end-of-line characters.
414414
) ]]
415415
```
416416

@@ -425,6 +425,7 @@ Example:
425425
indentSize: 4, // The number of spaces defining one indentation level in your project.
426426
indentChar: "\t", // The actual character used for indentation (space or tab).
427427
interpolationCodec: new PlainStringCodec(), // A codec for handling interpolations; depends on the content being formatted.
428+
lineEndingNormalizer: null, // A normalizer for handling end-of-line characters.
428429
) ]]
429430
```
430431

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"require": {
2424
"php": "^7.4 || ^8.0",
2525
"symfony/process": "^5 || ^6 || ^7 || ^8",
26-
"friendsofphp/php-cs-fixer": "^3"
26+
"friendsofphp/php-cs-fixer": "^3",
27+
"symfony/deprecation-contracts": "^2 || ^3"
2728
},
2829
"require-dev": {
2930
"ext-json": "*",

src/Formatter/AbstractCodecFormatter.php

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
use uuf6429\PhpCsFixerBlockstring\BlockString\BlockString;
66
use uuf6429\PhpCsFixerBlockstring\InterpolationCodec\CodecInterface;
77
use uuf6429\PhpCsFixerBlockstring\InterpolationCodec\PlainStringCodec;
8+
use uuf6429\PhpCsFixerBlockstring\LineEndingNormalizer\DefaultNormalizer;
9+
use uuf6429\PhpCsFixerBlockstring\LineEndingNormalizer\NormalizerInterface;
810

911
/**
1012
* This formatter base class is aware of string interpolation - it passes content through a codec before and after
@@ -25,14 +27,14 @@
2527
* }
2628
* }
2729
*
28-
* ['formatters' => [ new MyFormatter('1.0', new PlainStringCodec()) ]]
30+
* ['formatters' => [ new MyFormatter('1.0') ]]
2931
* ```
3032
*
3133
* Example with an anonymous class:
3234
*
3335
* ```php
3436
* ['formatters' => [
35-
* new class ('1.0', new PlainStringCodec()) extends AbstractCodecFormatter
37+
* new class ('1.0') extends AbstractCodecFormatter
3638
* {
3739
* protected function formatContent(string $original): string
3840
* {
@@ -58,12 +60,21 @@ abstract class AbstractCodecFormatter extends AbstractFormatter
5860
*/
5961
protected CodecInterface $interpolationCodec;
6062

61-
public function __construct(string $version, ?CodecInterface $interpolationCodec)
62-
{
63+
/**
64+
* @readonly
65+
*/
66+
private NormalizerInterface $lineEndingNormalizer;
67+
68+
public function __construct(
69+
string $version,
70+
?CodecInterface $interpolationCodec=null,
71+
?NormalizerInterface $lineEndingNormalizer=null
72+
) {
6373
parent::__construct($version);
6474

6575
$this->objectIndex = self::$objectCounter++;
6676
$this->interpolationCodec = $interpolationCodec ?? new PlainStringCodec();
77+
$this->lineEndingNormalizer = $lineEndingNormalizer ?? new DefaultNormalizer(DefaultNormalizer::NO_CHANGE, DefaultNormalizer::NO_CHANGE);
6778
}
6879

6980
final public function formatBlock(BlockString $blockString): BlockString
@@ -72,8 +83,10 @@ final public function formatBlock(BlockString $blockString): BlockString
7283

7384
$cacheKey = $this->objectIndex . ':' . md5($codecResult->content);
7485
if (!isset(self::$cache[$cacheKey])) {
75-
self::$cache[$cacheKey] = $this->formatContent(
76-
$this->removeIndentation($codecResult->content, $blockString->indentation)
86+
$content = $this->removeIndentation($codecResult->content, $blockString->indentation);
87+
self::$cache[$cacheKey] = $this->lineEndingNormalizer->normalize(
88+
$this->formatContent($content),
89+
$content
7790
);
7891
}
7992
$newContent = $this->applyIndentation(self::$cache[$cacheKey], $blockString->indentation);
@@ -90,11 +103,11 @@ abstract protected function formatContent(string $original): string;
90103

91104
private function removeIndentation(string $lines, string $indentation): string
92105
{
93-
return substr(str_replace("\n{$indentation}", "\n", $lines), strlen($indentation));
106+
return substr(str_replace("\n{$indentation}", "\n", $lines), strlen($indentation)); // TODO feels wrong (eol)
94107
}
95108

96109
private function applyIndentation(string $lines, string $indentation): string
97110
{
98-
return $indentation . str_replace("\n", "\n{$indentation}", $lines);
111+
return $indentation . str_replace("\n", "\n{$indentation}", $lines); // TODO feels wrong (eol)
99112
}
100113
}

src/Formatter/CliPipeFormatter.php

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use Symfony\Component\Process\Process;
66
use uuf6429\PhpCsFixerBlockstring\InterpolationCodec\CodecInterface;
7+
use uuf6429\PhpCsFixerBlockstring\LineEndingNormalizer\DefaultNormalizer;
8+
use uuf6429\PhpCsFixerBlockstring\LineEndingNormalizer\NormalizerInterface;
79

810
/**
911
* It's no secret that the best formatting tools are not directly available in PHP. This formatter off-loads formatting
@@ -16,7 +18,7 @@
1618
* versionValueOrCommand: '1.0', // Either a version as a string, or the command to get the version (as an array).
1719
* formatCommand: ['cmd' => 'jfmt -'], // An array defining the external command to do the formatting.
1820
* interpolationCodec: new PlainStringCodec(), // A codec for handling interpolations; depends on the content being formatted.
19-
* stripLastNewLine: true, // Remove last line from cli output - you might need this, depending on the platform/shell.
21+
* lineEndingNormalizer: null, // A normalizer for handling end-of-line characters.
2022
* ) ]]
2123
* ```
2224
*
@@ -37,30 +39,39 @@ class CliPipeFormatter extends AbstractCodecFormatter
3739
*/
3840
private array $formatter;
3941

40-
/**
41-
* @readonly
42-
*/
43-
private bool $stripLastNewLine;
44-
4542
/**
4643
* @param TVersion|TCommand $versionValueOrCommand Either the version (as a string) or a command to retrieve the
4744
* version (as an array).
4845
* @param TCommand $formatCommand A command, as an array, to perform the formatting.
46+
* @param null|bool|NormalizerInterface $lineEndingNormalizer
4947
*/
5048
public function __construct(
5149
$versionValueOrCommand,
5250
array $formatCommand,
5351
?CodecInterface $interpolationCodec = null,
54-
bool $stripLastNewLine = false
52+
$lineEndingNormalizer = false
5553
) {
5654
$this->formatter = $formatCommand;
57-
$this->stripLastNewLine = $stripLastNewLine;
55+
56+
if (is_bool($lineEndingNormalizer)) {
57+
trigger_deprecation(
58+
'uuf6429/php-cs-fixer-blockstring',
59+
'1.0.4',
60+
'Passing a bool for argument $lineEndingNormalizer to %s is deprecated',
61+
__METHOD__
62+
);
63+
$lineEndingNormalizer = new DefaultNormalizer(
64+
DefaultNormalizer::LF,
65+
$lineEndingNormalizer ? DefaultNormalizer::STRIP : DefaultNormalizer::NO_CHANGE
66+
);
67+
}
5868

5969
parent::__construct(
6070
is_string($versionValueOrCommand)
6171
? $versionValueOrCommand
6272
: $this->exec($versionValueOrCommand, null),
63-
$interpolationCodec
73+
$interpolationCodec,
74+
$lineEndingNormalizer
6475
);
6576
}
6677

@@ -90,10 +101,6 @@ protected function exec(array $spec, ?string $input): string
90101

91102
protected function formatContent(string $original): string
92103
{
93-
$output = $this->exec($this->formatter, $original);
94-
95-
return ($this->stripLastNewLine && substr($output, -1) === "\n")
96-
? substr($output, 0, -1)
97-
: $output;
104+
return $this->exec($this->formatter, $original);
98105
}
99106
}

src/Formatter/DockerPipeFormatter.php

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use Symfony\Component\Process\Exception\ProcessFailedException;
88
use Symfony\Component\Process\Process;
99
use uuf6429\PhpCsFixerBlockstring\InterpolationCodec\CodecInterface;
10+
use uuf6429\PhpCsFixerBlockstring\LineEndingNormalizer\DefaultNormalizer;
11+
use uuf6429\PhpCsFixerBlockstring\LineEndingNormalizer\NormalizerInterface;
1012

1113
/**
1214
* The minimal setup, stable repeatability, and a rich ecosystem makes Docker images an ideal source of formatting
@@ -21,7 +23,7 @@
2123
* command: ['bin/tool', '--dry-run', '-'], // The command to run within the container, including any arguments.
2224
* pullMode: 'always', // How/when the image should be pulled: 'never', 'always' or 'missing'.
2325
* interpolationCodec: new PlainStringCodec(), // A codec for handling interpolations; depends on the content being formatted.
24-
* stripLastNewLine: true, // Remove last line from docker output - typically needed.
26+
* lineEndingNormalizer: null, // A normalizer for handling end-of-line characters.
2527
* ) ]]
2628
* ```
2729
*
@@ -58,34 +60,43 @@ class DockerPipeFormatter extends AbstractCodecFormatter
5860
*/
5961
private array $imageDetails;
6062

61-
/**
62-
* @readonly
63-
*/
64-
private bool $stripLastNewLine;
65-
6663
/**
6764
* @param list<string> $options
6865
* @param list<string> $command
6966
* @param 'never'|'missing'|'always' $pullMode
67+
* @param null|bool|NormalizerInterface $lineEndingNormalizer
7068
*/
7169
public function __construct(
7270
string $image,
7371
array $options = [],
7472
array $command = [],
7573
string $pullMode = 'never',
7674
?CodecInterface $interpolationCodec = null,
77-
bool $stripLastNewLine = true
75+
$lineEndingNormalizer = true
7876
) {
7977
$this->image = $image;
8078
$this->options = $options;
8179
$this->command = $command;
8280
$this->pullMode = $pullMode;
8381
$this->imageDetails = $this->resolveImageDetails();
84-
$this->stripLastNewLine = $stripLastNewLine;
82+
83+
if (is_bool($lineEndingNormalizer)) {
84+
trigger_deprecation(
85+
'uuf6429/php-cs-fixer-blockstring',
86+
'1.0.4',
87+
'Passing a bool for argument $lineEndingNormalizer to %s is deprecated',
88+
__METHOD__
89+
);
90+
$lineEndingNormalizer = new DefaultNormalizer(
91+
DefaultNormalizer::LF,
92+
$lineEndingNormalizer ? DefaultNormalizer::STRIP : DefaultNormalizer::NO_CHANGE
93+
);
94+
}
8595

8696
parent::__construct(
8797
"{$this->imageDetails['platform']};{$this->imageDetails['digest']}",
88-
$interpolationCodec
98+
$interpolationCodec,
99+
$lineEndingNormalizer
89100
);
90101
}
91102

@@ -172,10 +183,6 @@ protected function formatContent(string $original): string
172183
null
173184
);
174185

175-
$output = $process->mustRun()->getOutput();
176-
177-
return ($this->stripLastNewLine && substr($output, -1) === "\n")
178-
? substr($output, 0, -1)
179-
: $output;
186+
return $process->mustRun()->getOutput();
180187
}
181188
}

src/Formatter/SimpleLineFormatter.php

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace uuf6429\PhpCsFixerBlockstring\Formatter;
44

55
use uuf6429\PhpCsFixerBlockstring\InterpolationCodec\CodecInterface;
6+
use uuf6429\PhpCsFixerBlockstring\LineEndingNormalizer\DefaultNormalizer;
7+
use uuf6429\PhpCsFixerBlockstring\LineEndingNormalizer\NormalizerInterface;
68

79
/**
810
* A formatter that normalizes indentation and removes any trailing whitespace at the end of lines.
@@ -14,6 +16,7 @@
1416
* indentSize: 4, // The number of spaces defining one indentation level in your project.
1517
* indentChar: "\t", // The actual character used for indentation (space or tab).
1618
* interpolationCodec: new PlainStringCodec(), // A codec for handling interpolations; depends on the content being formatted.
19+
* lineEndingNormalizer: null, // A normalizer for handling end-of-line characters.
1720
* ) ]]
1821
* ```
1922
*/
@@ -34,16 +37,31 @@ class SimpleLineFormatter extends AbstractCodecFormatter
3437
/**
3538
* @param positive-int $indentSize
3639
* @param "\t"|' ' $indentChar
40+
* @param null|bool|NormalizerInterface $lineEndingNormalizer
3741
*/
3842
public function __construct(
3943
int $indentSize = 4,
4044
string $indentChar = "\t",
41-
?CodecInterface $interpolationCodec = null
45+
?CodecInterface $interpolationCodec = null,
46+
$lineEndingNormalizer = false
4247
) {
43-
parent::__construct('1', $interpolationCodec);
44-
4548
$this->indentSize = $indentSize;
4649
$this->indentChar = $indentChar;
50+
51+
if (is_bool($lineEndingNormalizer)) {
52+
trigger_deprecation(
53+
'uuf6429/php-cs-fixer-blockstring',
54+
'1.0.4',
55+
'Passing a bool for argument $lineEndingNormalizer to %s is deprecated',
56+
__METHOD__
57+
);
58+
$lineEndingNormalizer = new DefaultNormalizer(
59+
DefaultNormalizer::LF,
60+
$lineEndingNormalizer ? DefaultNormalizer::STRIP : DefaultNormalizer::NO_CHANGE
61+
);
62+
}
63+
64+
parent::__construct('1', $interpolationCodec, $lineEndingNormalizer);
4765
}
4866

4967
protected function formatContent(string $original): string

src/Formatter/WslPipeFormatter.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
<?php
1+
<?php declare(strict_types=1);
22

33
namespace uuf6429\PhpCsFixerBlockstring\Formatter;
44

55
use uuf6429\PhpCsFixerBlockstring\InterpolationCodec\CodecInterface;
6+
use uuf6429\PhpCsFixerBlockstring\LineEndingNormalizer\DefaultNormalizer;
7+
use uuf6429\PhpCsFixerBlockstring\LineEndingNormalizer\NormalizerInterface;
68

79
/**
810
* A formatter making use of Windows Subsystem for Linux (WSL). Of course you will need to be running on Windows and WSL
@@ -18,17 +20,31 @@ class WslPipeFormatter extends CliPipeFormatter
1820

1921
/**
2022
* @param 'standard'|'login'|'none' $shellType
23+
* @param null|bool|NormalizerInterface $lineEndingNormalizer
2124
*/
2225
public function __construct(
2326
$versionValueOrCommand,
2427
array $formatCommand,
2528
?CodecInterface $interpolationCodec = null,
2629
string $shellType = 'login',
27-
bool $stripLastNewLine = true
30+
$lineEndingNormalizer = true
2831
) {
2932
$this->shellType = $shellType;
3033

31-
parent::__construct($versionValueOrCommand, $formatCommand, $interpolationCodec, $stripLastNewLine);
34+
if (is_bool($lineEndingNormalizer)) {
35+
trigger_deprecation(
36+
'uuf6429/php-cs-fixer-blockstring',
37+
'1.0.4',
38+
'Passing a bool for argument $lineEndingNormalizer to %s is deprecated',
39+
__METHOD__
40+
);
41+
$lineEndingNormalizer = new DefaultNormalizer(
42+
DefaultNormalizer::LF,
43+
$lineEndingNormalizer ? DefaultNormalizer::STRIP : DefaultNormalizer::NO_CHANGE
44+
);
45+
}
46+
47+
parent::__construct($versionValueOrCommand, $formatCommand, $interpolationCodec, $lineEndingNormalizer);
3248
}
3349

3450
protected function exec(array $spec, ?string $input): string

0 commit comments

Comments
 (0)