mirror of
https://github.com/nikic/PHP-Parser.git
synced 2025-01-16 14:48:32 +01:00
Add basic support for tab indentation
Add a new "indent" option for the pretty printer, which can be use to control the indentation width, or switch it to use tabs. Tab width is currenlty hardcoded to 4, but also shouldn't matter much. Possibly the formatting-preserving printer should auto-detect the indentation in the future.
This commit is contained in:
parent
26a0197186
commit
e50c67b7a9
@ -37,6 +37,7 @@ integer should be printed as decimal, hexadecimal, etc). Additionally, it suppor
|
||||
* `phpVersion` (defaults to 7.4) allows opting into formatting that is not supported by older PHP
|
||||
versions.
|
||||
* `newline` (defaults to `"\n"`) can be set to `"\r\n"` in order to produce Windows newlines.
|
||||
* `indent` (defaults to four spaces `" "`) can be set to any number of spaces or a single tab.
|
||||
* `shortArraySyntax` determines the used array syntax if the `kind` attribute is not set. This is
|
||||
a legacy option, and `phpVersion` should be used to control this behavior instead.
|
||||
|
||||
|
@ -20,9 +20,9 @@ class TokenStream {
|
||||
*
|
||||
* @param Token[] $tokens Tokens in PhpToken::tokenize() format
|
||||
*/
|
||||
public function __construct(array $tokens) {
|
||||
public function __construct(array $tokens, int $tabWidth) {
|
||||
$this->tokens = $tokens;
|
||||
$this->indentMap = $this->calcIndentMap();
|
||||
$this->indentMap = $this->calcIndentMap($tabWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -248,7 +248,7 @@ class TokenStream {
|
||||
*
|
||||
* @return int[] Token position to indentation map
|
||||
*/
|
||||
private function calcIndentMap(): array {
|
||||
private function calcIndentMap(int $tabWidth): array {
|
||||
$indentMap = [];
|
||||
$indent = 0;
|
||||
foreach ($this->tokens as $i => $token) {
|
||||
@ -258,11 +258,11 @@ class TokenStream {
|
||||
$content = $token->text;
|
||||
$newlinePos = \strrpos($content, "\n");
|
||||
if (false !== $newlinePos) {
|
||||
$indent = \strlen($content) - $newlinePos - 1;
|
||||
$indent = $this->getIndent(\substr($content, $newlinePos + 1), $tabWidth);
|
||||
} elseif ($i === 1 && $this->tokens[0]->id === \T_OPEN_TAG &&
|
||||
$this->tokens[0]->text[\strlen($this->tokens[0]->text) - 1] === "\n") {
|
||||
// Special case: Newline at the end of opening tag followed by whitespace.
|
||||
$indent = \strlen($content);
|
||||
$indent = $this->getIndent($content, $tabWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -272,4 +272,11 @@ class TokenStream {
|
||||
|
||||
return $indentMap;
|
||||
}
|
||||
|
||||
private function getIndent(string $ws, int $tabWidth): int {
|
||||
$spaces = \substr_count($ws, " ");
|
||||
$tabs = \substr_count($ws, "\t");
|
||||
assert(\strlen($ws) === $spaces + $tabs);
|
||||
return $spaces + $tabs * $tabWidth;
|
||||
}
|
||||
}
|
||||
|
@ -106,6 +106,15 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
|
||||
/** @var int Current indentation level. */
|
||||
protected int $indentLevel;
|
||||
/** @var string String for single level of indentation */
|
||||
private string $indent;
|
||||
/** @var int Width in spaces to indent by. */
|
||||
private int $indentWidth;
|
||||
/** @var bool Whether to use tab indentation. */
|
||||
private bool $useTabs;
|
||||
/** @var int Width in spaces of one tab. */
|
||||
private int $tabWidth = 4;
|
||||
|
||||
/** @var string Newline style. Does not include current indentation. */
|
||||
protected string $newline;
|
||||
/** @var string Newline including current indentation. */
|
||||
@ -170,12 +179,14 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
* PHP version while specifying an older target (but the result will
|
||||
* of course not be compatible with the older version in that case).
|
||||
* * string $newline: The newline style to use. Should be "\n" (default) or "\r\n".
|
||||
* * string $indent: The indentation to use. Should either be all spaces or a single
|
||||
* tab. Defaults to four spaces (" ").
|
||||
* * bool $shortArraySyntax: Whether to use [] instead of array() as the default array
|
||||
* syntax, if the node does not specify a format. Defaults to whether
|
||||
* the phpVersion support short array syntax.
|
||||
*
|
||||
* @param array{
|
||||
* phpVersion?: PhpVersion, newline?: string, shortArraySyntax?: bool
|
||||
* phpVersion?: PhpVersion, newline?: string, indent?: string, shortArraySyntax?: bool
|
||||
* } $options Dictionary of formatting options
|
||||
*/
|
||||
public function __construct(array $options = []) {
|
||||
@ -190,6 +201,17 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
$options['shortArraySyntax'] ?? $this->phpVersion->supportsShortArraySyntax();
|
||||
$this->docStringEndToken =
|
||||
$this->phpVersion->supportsFlexibleHeredoc() ? null : '_DOC_STRING_END_' . mt_rand();
|
||||
|
||||
$this->indent = $indent = $options['indent'] ?? ' ';
|
||||
if ($indent === "\t") {
|
||||
$this->useTabs = true;
|
||||
$this->indentWidth = $this->tabWidth;
|
||||
} elseif ($indent === \str_repeat(' ', \strlen($indent))) {
|
||||
$this->useTabs = false;
|
||||
$this->indentWidth = \strlen($indent);
|
||||
} else {
|
||||
throw new \LogicException('Option "indent" must either be all spaces or a single tab');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -208,24 +230,29 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
*/
|
||||
protected function setIndentLevel(int $level): void {
|
||||
$this->indentLevel = $level;
|
||||
$this->nl = $this->newline . \str_repeat(' ', $level);
|
||||
if ($this->useTabs) {
|
||||
$tabs = \intdiv($level, $this->tabWidth);
|
||||
$spaces = $level % $this->tabWidth;
|
||||
$this->nl = $this->newline . \str_repeat("\t", $tabs) . \str_repeat(' ', $spaces);
|
||||
} else {
|
||||
$this->nl = $this->newline . \str_repeat(' ', $level);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase indentation level.
|
||||
*/
|
||||
protected function indent(): void {
|
||||
$this->indentLevel += 4;
|
||||
$this->nl .= ' ';
|
||||
$this->indentLevel += $this->indentWidth;
|
||||
$this->nl .= $this->indent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrease indentation level.
|
||||
*/
|
||||
protected function outdent(): void {
|
||||
assert($this->indentLevel >= 4);
|
||||
$this->indentLevel -= 4;
|
||||
$this->nl = $this->newline . str_repeat(' ', $this->indentLevel);
|
||||
assert($this->indentLevel >= $this->indentWidth);
|
||||
$this->setIndentLevel($this->indentLevel - $this->indentWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -537,7 +564,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
|
||||
$this->initializeModifierChangeMap();
|
||||
|
||||
$this->resetState();
|
||||
$this->origTokens = new TokenStream($origTokens);
|
||||
$this->origTokens = new TokenStream($origTokens, $this->tabWidth);
|
||||
|
||||
$this->preprocessNodes($stmts);
|
||||
|
||||
|
@ -17,11 +17,13 @@ class PrettyPrinterTest extends CodeTestAbstract {
|
||||
private function createParserAndPrinter(array $options): array {
|
||||
$parserVersion = $options['parserVersion'] ?? $options['version'] ?? null;
|
||||
$printerVersion = $options['version'] ?? null;
|
||||
$indent = isset($options['indent']) ? json_decode($options['indent']) : null;
|
||||
$factory = new ParserFactory();
|
||||
$parser = $factory->createForVersion($parserVersion !== null
|
||||
? PhpVersion::fromString($parserVersion) : PhpVersion::getNewestSupported());
|
||||
$prettyPrinter = new Standard([
|
||||
'phpVersion' => $printerVersion !== null ? PhpVersion::fromString($printerVersion) : null
|
||||
'phpVersion' => $printerVersion !== null ? PhpVersion::fromString($printerVersion) : null,
|
||||
'indent' => $indent,
|
||||
]);
|
||||
return [$parser, $prettyPrinter];
|
||||
}
|
||||
@ -297,4 +299,10 @@ CODE
|
||||
$this->expectExceptionMessage('Option "newline" must be one of "\n" or "\r\n"');
|
||||
new PrettyPrinter\Standard(['newline' => 'foo']);
|
||||
}
|
||||
|
||||
public function testInvalidIndent(): void {
|
||||
$this->expectException(\LogicException::class);
|
||||
$this->expectExceptionMessage('Option "indent" must either be all spaces or a single tab');
|
||||
new PrettyPrinter\Standard(['indent' => "\t "]);
|
||||
}
|
||||
}
|
||||
|
37
test/code/formatPreservation/indent.test
Normal file
37
test/code/formatPreservation/indent.test
Normal file
@ -0,0 +1,37 @@
|
||||
Indentation
|
||||
-----
|
||||
<?php
|
||||
$x;
|
||||
-----
|
||||
$stmts[0] = new Stmt\If_(new Expr\Variable('a'), ['stmts' => $stmts]);
|
||||
-----
|
||||
!!indent=" "
|
||||
<?php
|
||||
if ($a) {
|
||||
$x;
|
||||
}
|
||||
-----
|
||||
<?php
|
||||
$x;
|
||||
-----
|
||||
$stmts[0] = new Stmt\If_(new Expr\Variable('a'), ['stmts' => $stmts]);
|
||||
-----
|
||||
!!indent="\t"
|
||||
<?php
|
||||
if ($a) {
|
||||
@@{"\t"}@@$x;
|
||||
}
|
||||
-----
|
||||
<?php
|
||||
if ($a) {
|
||||
@@{"\t"}@@$x;
|
||||
}
|
||||
-----
|
||||
$stmts[0]->stmts[] = new Stmt\Expression(new Expr\Variable('y'));
|
||||
-----
|
||||
!!indent="\t"
|
||||
<?php
|
||||
if ($a) {
|
||||
@@{"\t"}@@$x;
|
||||
@@{"\t"}@@$y;
|
||||
}
|
82
test/code/prettyPrinter/indent.test
Normal file
82
test/code/prettyPrinter/indent.test
Normal file
@ -0,0 +1,82 @@
|
||||
Indentation
|
||||
-----
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
/**
|
||||
* Comment
|
||||
*/
|
||||
public function foo() {
|
||||
if (1) {
|
||||
echo $bar;
|
||||
}
|
||||
}
|
||||
}
|
||||
-----
|
||||
!!indent=" "
|
||||
class Test
|
||||
{
|
||||
/**
|
||||
* Comment
|
||||
*/
|
||||
public function foo()
|
||||
{
|
||||
if (1) {
|
||||
echo $bar;
|
||||
}
|
||||
}
|
||||
}
|
||||
-----
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
/**
|
||||
* Comment
|
||||
*/
|
||||
public function foo() {
|
||||
if (1) {
|
||||
echo $bar;
|
||||
}
|
||||
}
|
||||
}
|
||||
-----
|
||||
!!indent=" "
|
||||
class Test
|
||||
{
|
||||
/**
|
||||
* Comment
|
||||
*/
|
||||
public function foo()
|
||||
{
|
||||
if (1) {
|
||||
echo $bar;
|
||||
}
|
||||
}
|
||||
}
|
||||
-----
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
/**
|
||||
* Comment
|
||||
*/
|
||||
public function foo() {
|
||||
if (1) {
|
||||
echo $bar;
|
||||
}
|
||||
}
|
||||
}
|
||||
-----
|
||||
!!indent="\t"
|
||||
class Test
|
||||
{
|
||||
@@{"\t"}@@/**
|
||||
@@{"\t"}@@ * Comment
|
||||
@@{"\t"}@@ */
|
||||
@@{"\t"}@@public function foo()
|
||||
@@{"\t"}@@{
|
||||
@@{"\t\t"}@@if (1) {
|
||||
@@{"\t\t\t"}@@echo $bar;
|
||||
@@{"\t\t"}@@}
|
||||
@@{"\t"}@@}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user