Skip to content

Commit

Permalink
Preserve indentation when writing JSON files (#11390)
Browse files Browse the repository at this point in the history
Refs #11341
  • Loading branch information
maximal committed Jul 19, 2023
1 parent 16d1b11 commit 1c9fbeb
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 8 deletions.
34 changes: 31 additions & 3 deletions src/Composer/Json/JsonFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,16 @@ class JsonFile

public const COMPOSER_SCHEMA_PATH = __DIR__ . '/../../../res/composer-schema.json';

public const INDENT_DEFAULT = ' ';

/** @var string */
private $path;
/** @var ?HttpDownloader */
private $httpDownloader;
/** @var ?IOInterface */
private $io;
/** @var string */
private $indent = self::INDENT_DEFAULT;

/**
* Initializes json file reader/parser.
Expand Down Expand Up @@ -117,6 +121,8 @@ public function read()
throw new \RuntimeException('Could not read '.$this->path);
}

$this->indent = self::detectIndenting($json);

return static::parseJson($json, $this->path);
}

Expand All @@ -131,7 +137,7 @@ public function read()
public function write(array $hash, int $options = JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
{
if ($this->path === 'php://memory') {
file_put_contents($this->path, static::encode($hash, $options));
file_put_contents($this->path, static::encode($hash, $options, $this->indent));

return;
}
Expand All @@ -153,7 +159,7 @@ public function write(array $hash, int $options = JSON_UNESCAPED_SLASHES | JSON_
$retries = 3;
while ($retries--) {
try {
$this->filePutContentsIfModified($this->path, static::encode($hash, $options). ($options & JSON_PRETTY_PRINT ? "\n" : ''));
$this->filePutContentsIfModified($this->path, static::encode($hash, $options, $this->indent). ($options & JSON_PRETTY_PRINT ? "\n" : ''));
break;
} catch (\Exception $e) {
if ($retries > 0) {
Expand Down Expand Up @@ -262,15 +268,28 @@ public static function validateJsonSchema(string $source, $data, int $schema, ?s
*
* @param mixed $data Data to encode into a formatted JSON string
* @param int $options json_encode options (defaults to JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
* @param string $indent Indentation string
* @return string Encoded json
*/
public static function encode($data, int $options = 448)
public static function encode($data, int $options = 448, string $indent = self::INDENT_DEFAULT): string
{
$json = json_encode($data, $options);

if (false === $json) {
self::throwEncodeError(json_last_error());
}

if (($options & JSON_PRETTY_PRINT) > 0 && $indent !== self::INDENT_DEFAULT ) {
// Pretty printing and not using default indentation
return Preg::replaceCallback(
'#^ {4,}#m',
static function ($match) use ($indent): string {
return str_repeat($indent, (int)(strlen($match[0] ?? '') / 4));
},
$json
);
}

return $json;
}

Expand All @@ -279,6 +298,7 @@ public static function encode($data, int $options = 448)
*
* @param int $code return code of json_last_error function
* @throws \RuntimeException
* @return never
*/
private static function throwEncodeError(int $code): void
{
Expand Down Expand Up @@ -356,4 +376,12 @@ protected static function validateSyntax(string $json, ?string $file = null): bo
$result->getDetails());
}
}

public static function detectIndenting(?string $json): string
{
if (Preg::isMatchStrictGroups('#^([ \t]+)"#m', $json ?? '', $match)) {
return $match[1];
}
return self::INDENT_DEFAULT;
}
}
6 changes: 1 addition & 5 deletions src/Composer/Json/JsonManipulator.php
Original file line number Diff line number Diff line change
Expand Up @@ -561,10 +561,6 @@ public function format($data, int $depth = 0): string

protected function detectIndenting(): void
{
if (Preg::isMatchStrictGroups('{^([ \t]+)"}m', $this->contents, $match)) {
$this->indent = $match[1];
} else {
$this->indent = ' ';
}
$this->indent = JsonFile::detectIndenting($this->contents);
}
}
3 changes: 3 additions & 0 deletions tests/Composer/Test/Json/Fixtures/tabs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"foo": "bar"
}
23 changes: 23 additions & 0 deletions tests/Composer/Test/Json/JsonFileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,29 @@ public function testDoubleEscapedUnicode(): void
$this->assertEquals($data, $doubleData);
}

public function testPreserveIndentationAfterRead(): void
{
copy(__DIR__.'/Fixtures/tabs.json', __DIR__.'/Fixtures/tabs2.json');
$jsonFile = new JsonFile(__DIR__.'/Fixtures/tabs2.json');
$data = $jsonFile->read();
$jsonFile->write(['foo' => 'baz']);

self::assertSame("{\n\t\"foo\": \"baz\"\n}\n", file_get_contents(__DIR__.'/Fixtures/tabs2.json'));

unlink(__DIR__.'/Fixtures/tabs2.json');
}

public function testOverwritesIndentationByDefault(): void
{
copy(__DIR__.'/Fixtures/tabs.json', __DIR__.'/Fixtures/tabs2.json');
$jsonFile = new JsonFile(__DIR__.'/Fixtures/tabs2.json');
$jsonFile->write(['foo' => 'baz']);

self::assertSame("{\n \"foo\": \"baz\"\n}\n", file_get_contents(__DIR__.'/Fixtures/tabs2.json'));

unlink(__DIR__.'/Fixtures/tabs2.json');
}

private function expectParseException(string $text, string $json): void
{
try {
Expand Down

0 comments on commit 1c9fbeb

Please sign in to comment.