Skip to content

Commit

Permalink
Review HangingIndentation
Browse files Browse the repository at this point in the history
- Rewrite `HangingIndentation` to improve maintainability, performance
  and output quality, especially when formatting complex expressions
  that break over multiple lines
- In `TokenUtil` methods `getOperatorExpression()` and
  `getOperatorEndExpression()`, allow precedence to be given explicitly
- Add `TokenUtil::getTernary2AfterTernary1()` and use it for containment
  of ternary expressions in `getOperatorEndExpression()`
- Add list delimiter to token data for list parents
- Remove unused token properties and data
  • Loading branch information
lkrms committed Jan 1, 2025
1 parent 73da344 commit 0cdb972
Show file tree
Hide file tree
Showing 23 changed files with 1,334 additions and 718 deletions.
12 changes: 9 additions & 3 deletions docs/Indentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,15 @@ After finding a token to indent, `HangingIndentation` creates a context for it,
and if indentation for that context has already been applied, the token is not
indented further.

A token's context is comprised of its parent token (or `null` if it's a
top-level token), and an optional anchor token shared by any siblings that
should receive the same level of indentation.
A token's context is an `array{?Token, ?Token, ?Token, 3?:Token|int, 4?:int}`
comprised of:

- its parent (or `null` if it's a top-level token)
- the most recent assignment operator in the same statement (or `null` if it
isn't part of an expression being assigned to a variable)
- its ternary context (or `null` if it isn't part of a ternary expression)
- an optional token and/or precedence value shared by any siblings that should
receive the same level of indentation

The aim is to differentiate between lines where a new expression starts, and
lines where an expression continues:
Expand Down
12 changes: 10 additions & 2 deletions docs/Rules.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/App/PrettyPHPCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -1078,7 +1078,7 @@ protected function run(...$params)
$dir = dirname($inputFile);
$formatter = $this->FormatterByDir[$dir] ??=
$this->DefaultFormatter ??=
$this->getFormatter();
$this->getFormatter();

$inputStream = File::open($file, 'rb');

Expand Down
40 changes: 20 additions & 20 deletions src/Catalog/TokenData.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,90 +15,90 @@ interface TokenData
*/
public const COMMENT_CONTENT = 0;

/**
* The token ID of the delimiter associated with a LIST_PARENT token
*/
public const LIST_DELIMITER = 1;

/**
* A collection of items associated with a LIST_PARENT token
*/
public const LIST_ITEMS = 1;
public const LIST_ITEMS = 2;

/**
* The number of items associated with a LIST_PARENT token
*/
public const LIST_ITEM_COUNT = 2;
public const LIST_ITEM_COUNT = 3;

/**
* The LIST_PARENT of the first token in a LIST_ITEM
*/
public const LIST_PARENT = 3;
public const LIST_PARENT = 4;

/**
* The T_COLON or T_QUESTION associated with a T_QUESTION or T_COLON flagged
* as a TERNARY_OPERATOR
*/
public const OTHER_TERNARY_OPERATOR = 4;
public const OTHER_TERNARY_OPERATOR = 5;

/**
* The last token of the string opened by the token
*/
public const STRING_CLOSED_BY = 5;
public const STRING_CLOSED_BY = 6;

/**
* The first T_OBJECT_OPERATOR or T_NULLSAFE_OBJECT_OPERATOR in a chain
* thereof
*/
public const CHAIN_OPENED_BY = 6;
public const CHAIN_OPENED_BY = 7;

/**
* A collection of tokens that form a NAMED_DECLARATION
*/
public const NAMED_DECLARATION_PARTS = 7;
public const NAMED_DECLARATION_PARTS = 8;

/**
* The type of a NAMED_DECLARATION
*/
public const NAMED_DECLARATION_TYPE = 8;
public const NAMED_DECLARATION_TYPE = 9;

/**
* A collection of property hooks for a NAMED_DECLARATION with type PROPERTY
* or PROMOTED_PARAM
*/
public const PROPERTY_HOOKS = 9;
public const PROPERTY_HOOKS = 10;

/**
* A list of closures that align other tokens with the token when its output
* column changes
*/
public const ALIGNMENT_CALLBACKS = 10;
public const ALIGNMENT_CALLBACKS = 11;

/**
* The control structure a T_OPEN_UNENCLOSED token is associated with
*/
public const UNENCLOSED_PARENT = 11;
public const UNENCLOSED_PARENT = 12;

/**
* Whether or not the control structure a T_OPEN_UNENCLOSED token is
* associated with continues after its T_CLOSE_UNENCLOSED counterpart
*/
public const UNENCLOSED_CONTINUES = 12;
public const UNENCLOSED_CONTINUES = 13;

/**
* The non-virtual token a virtual token is bound to
*/
public const BOUND_TO = 13;
public const BOUND_TO = 14;

/**
* The last non-virtual token before a virtual token, or null if there is no
* such token
*/
public const PREV_REAL = 14;
public const PREV_REAL = 15;

/**
* The next non-virtual token after a virtual token, or null if there is no
* such token
*/
public const NEXT_REAL = 15;

/**
* The type applied to an open bracket by the HangingIndentation rule
*/
public const HANGING_INDENT_PARENT_TYPE = 16;
public const NEXT_REAL = 16;
}
126 changes: 63 additions & 63 deletions src/Contract/HasOperatorPrecedence.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,69 +17,69 @@ interface HasOperatorPrecedence
* @var array<int,array<array{int,int,bool,bool}>>
*/
public const OPERATOR_PRECEDENCE = [
\T_CLONE => [[0, 0, false, false]],
\T_NEW => [[0, 0, false, false]],
\T_POW => [[0, 1, false, true]],
\T_PLUS => [[self::UNARY, 2, false, false], [self::BINARY, 6, true, false]],
\T_MINUS => [[self::UNARY, 2, false, false], [self::BINARY, 6, true, false]],
\T_INC => [[0, 2, false, false]],
\T_DEC => [[0, 2, false, false]],
\T_NOT => [[0, 2, false, false]],
\T_INT_CAST => [[0, 2, false, false]],
\T_DOUBLE_CAST => [[0, 2, false, false]],
\T_STRING_CAST => [[0, 2, false, false]],
\T_ARRAY_CAST => [[0, 2, false, false]],
\T_OBJECT_CAST => [[0, 2, false, false]],
\T_BOOL_CAST => [[0, 2, false, false]],
\T_UNSET_CAST => [[0, 2, false, false]],
\T_AT => [[0, 2, false, false]],
\T_INSTANCEOF => [[0, 3, true, false]],
\T_LOGICAL_NOT => [[0, 4, false, false]],
\T_MUL => [[0, 5, true, false]],
\T_DIV => [[0, 5, true, false]],
\T_MOD => [[0, 5, true, false]],
\T_SL => [[0, 7, true, false]],
\T_SR => [[0, 7, true, false]],
\T_CONCAT => [[0, 8, true, false]],
\T_SMALLER => [[0, 9, false, false]],
\T_IS_SMALLER_OR_EQUAL => [[0, 9, false, false]],
\T_GREATER => [[0, 9, false, false]],
\T_IS_GREATER_OR_EQUAL => [[0, 9, false, false]],
\T_IS_EQUAL => [[0, 10, false, false]],
\T_IS_NOT_EQUAL => [[0, 10, false, false]],
\T_IS_IDENTICAL => [[0, 10, false, false]],
\T_IS_NOT_IDENTICAL => [[0, 10, false, false]],
\T_SPACESHIP => [[0, 10, false, false]],
\T_AND => [[0, 11, true, false]],
\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG => [[0, 11, true, false]],
\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG => [[0, 11, true, false]],
\T_XOR => [[0, 12, true, false]],
\T_OR => [[0, 13, true, false]],
\T_BOOLEAN_AND => [[0, 14, true, false]],
\T_BOOLEAN_OR => [[0, 15, true, false]],
\T_COALESCE => [[0, 16, false, true]],
\T_QUESTION => [[self::TERNARY, 17, false, false]],
\T_COLON => [[self::TERNARY, 17, false, false]],
\T_EQUAL => [[0, 18, false, true]],
\T_PLUS_EQUAL => [[0, 18, false, true]],
\T_MINUS_EQUAL => [[0, 18, false, true]],
\T_MUL_EQUAL => [[0, 18, false, true]],
\T_POW_EQUAL => [[0, 18, false, true]],
\T_DIV_EQUAL => [[0, 18, false, true]],
\T_CONCAT_EQUAL => [[0, 18, false, true]],
\T_MOD_EQUAL => [[0, 18, false, true]],
\T_AND_EQUAL => [[0, 18, false, true]],
\T_OR_EQUAL => [[0, 18, false, true]],
\T_XOR_EQUAL => [[0, 18, false, true]],
\T_SL_EQUAL => [[0, 18, false, true]],
\T_SR_EQUAL => [[0, 18, false, true]],
\T_COALESCE_EQUAL => [[0, 18, false, true]],
\T_YIELD_FROM => [[0, 19, false, false]],
\T_YIELD => [[0, 20, false, false]],
\T_PRINT => [[0, 21, false, false]],
\T_LOGICAL_AND => [[0, 22, true, false]],
\T_LOGICAL_XOR => [[0, 23, true, false]],
\T_LOGICAL_OR => [[0, 24, true, false]],
\T_CLONE => [[0, 1, false, false]],
\T_NEW => [[0, 1, false, false]],
\T_POW => [[0, 2, false, true]],
\T_PLUS => [[self::UNARY, 3, false, false], [self::BINARY, 7, true, false]],
\T_MINUS => [[self::UNARY, 3, false, false], [self::BINARY, 7, true, false]],
\T_INC => [[0, 3, false, false]],
\T_DEC => [[0, 3, false, false]],
\T_NOT => [[0, 3, false, false]],
\T_INT_CAST => [[0, 3, false, false]],
\T_DOUBLE_CAST => [[0, 3, false, false]],
\T_STRING_CAST => [[0, 3, false, false]],
\T_ARRAY_CAST => [[0, 3, false, false]],
\T_OBJECT_CAST => [[0, 3, false, false]],
\T_BOOL_CAST => [[0, 3, false, false]],
\T_UNSET_CAST => [[0, 3, false, false]],
\T_AT => [[0, 3, false, false]],
\T_INSTANCEOF => [[0, 4, true, false]],
\T_LOGICAL_NOT => [[0, 5, false, false]],
\T_MUL => [[0, 6, true, false]],
\T_DIV => [[0, 6, true, false]],
\T_MOD => [[0, 6, true, false]],
\T_SL => [[0, 8, true, false]],
\T_SR => [[0, 8, true, false]],
\T_CONCAT => [[0, 9, true, false]],
\T_SMALLER => [[0, 10, false, false]],
\T_IS_SMALLER_OR_EQUAL => [[0, 10, false, false]],
\T_GREATER => [[0, 10, false, false]],
\T_IS_GREATER_OR_EQUAL => [[0, 10, false, false]],
\T_IS_EQUAL => [[0, 11, false, false]],
\T_IS_NOT_EQUAL => [[0, 11, false, false]],
\T_IS_IDENTICAL => [[0, 11, false, false]],
\T_IS_NOT_IDENTICAL => [[0, 11, false, false]],
\T_SPACESHIP => [[0, 11, false, false]],
\T_AND => [[0, 12, true, false]],
\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG => [[0, 12, true, false]],
\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG => [[0, 12, true, false]],
\T_XOR => [[0, 13, true, false]],
\T_OR => [[0, 14, true, false]],
\T_BOOLEAN_AND => [[0, 15, true, false]],
\T_BOOLEAN_OR => [[0, 16, true, false]],
\T_COALESCE => [[0, 17, false, true]],
\T_QUESTION => [[self::TERNARY, 18, false, false]],
\T_COLON => [[self::TERNARY, 18, false, false]],
\T_EQUAL => [[0, 19, false, true]],
\T_PLUS_EQUAL => [[0, 19, false, true]],
\T_MINUS_EQUAL => [[0, 19, false, true]],
\T_MUL_EQUAL => [[0, 19, false, true]],
\T_POW_EQUAL => [[0, 19, false, true]],
\T_DIV_EQUAL => [[0, 19, false, true]],
\T_CONCAT_EQUAL => [[0, 19, false, true]],
\T_MOD_EQUAL => [[0, 19, false, true]],
\T_AND_EQUAL => [[0, 19, false, true]],
\T_OR_EQUAL => [[0, 19, false, true]],
\T_XOR_EQUAL => [[0, 19, false, true]],
\T_SL_EQUAL => [[0, 19, false, true]],
\T_SR_EQUAL => [[0, 19, false, true]],
\T_COALESCE_EQUAL => [[0, 19, false, true]],
\T_YIELD_FROM => [[0, 20, false, false]],
\T_YIELD => [[0, 21, false, false]],
\T_PRINT => [[0, 22, false, false]],
\T_LOGICAL_AND => [[0, 23, true, false]],
\T_LOGICAL_XOR => [[0, 24, true, false]],
\T_LOGICAL_OR => [[0, 25, true, false]],
\T_DOUBLE_ARROW => [[0, 99, false, false]],
\T_BREAK => [[0, 99, false, false]],
\T_CASE => [[0, 99, false, false]],
Expand Down
29 changes: 16 additions & 13 deletions src/Formatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,11 @@ public function format(
continue;
}

$delimiter = $parent->id === \T_OPEN_PARENTHESIS
&& $parent->PrevCode
&& $parent->PrevCode->id === \T_FOR
? \T_SEMICOLON
: \T_COMMA;
$items = null;
$minCount = 1;

Expand All @@ -975,7 +980,9 @@ public function format(
$items = $first->withNextSiblings($last)
->filter(
fn(Token $t, ?Token $next, ?Token $prev) =>
!$prev || ($t->PrevCode && $t->PrevCode->id === \T_COMMA)
!$prev
|| !$t->PrevCode
|| $t->PrevCode->id === $delimiter
);
$minCount = 2;
break;
Expand Down Expand Up @@ -1003,24 +1010,20 @@ public function format(
continue 2;
}

if ($items === null) {
$delimiter = $parent->PrevCode && $parent->PrevCode->id === \T_FOR
? \T_SEMICOLON
: \T_COMMA;
$items = $parent->children()
->filter(
fn(Token $t, ?Token $next, ?Token $prev) =>
$t->id !== $delimiter && (
!$prev || ($t->PrevCode && $t->PrevCode->id === $delimiter)
)
);
}
$items ??= $parent->children()
->filter(
fn(Token $t, ?Token $next, ?Token $prev) =>
$t->id !== $delimiter && (
!$prev || ($t->PrevCode && $t->PrevCode->id === $delimiter)
)
);

$count = $items->count();
if ($count < $minCount) {
continue;
}
$parent->Flags |= TokenFlag::LIST_PARENT;
$parent->Data[TokenData::LIST_DELIMITER] = $delimiter;
$parent->Data[TokenData::LIST_ITEMS] = $items;
$parent->Data[TokenData::LIST_ITEM_COUNT] = $count;
foreach ($items as $token) {
Expand Down
8 changes: 5 additions & 3 deletions src/Rule/AlignChains.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,21 +135,22 @@ public function processTokens(array $tokens): void
$t->AlignedWith = $alignWith;
}

/** @var Token */
$first = $chain->first();
$until = TokenUtil::getOperatorEndExpression($token);
$idx = $this->Idx;

$this->Formatter->registerCallback(
static::class,
$token,
static function () use (
$callback = static function () use (
$chain,
$alignWith,
$offset,
$first,
$until,
$idx
) {
/** @var Token */
$first = $chain->first();
$delta = $first->getColumnDelta($alignWith, false) + $offset;
if ($first->id === \T_NULLSAFE_OBJECT_OPERATOR) {
$delta++;
Expand Down Expand Up @@ -186,6 +187,7 @@ static function () use (
$chain->forEach($callback);
},
);
$alignWith->Data[TokenData::ALIGNMENT_CALLBACKS][] = $callback;
}
}
}
Loading

0 comments on commit 0cdb972

Please sign in to comment.