From ba438dc6b50868362a91f1e4aa6e21d6a8260469 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 26 Oct 2026 13:17:45 +0100 Subject: [PATCH 1/2] Add RemoveSlashFromNativeConstantFixer for PHP and blink Strips the redundant leading backslash from the true, false and null constants (\true -> true). Added to the common namespaces set and ported to the blink Go runner. --- blink/README.md | 2 +- blink/internal/fixer/fixer.go | 1 + blink/internal/fixer/rules/priorities.go | 1 + blink/internal/fixer/rules/registry.go | 1 + .../remove_slash_from_native_constant.go | 59 ++++++++++++++ .../remove_slash_from_native_constant_test.go | 33 ++++++++ config/set/common/namespaces.php | 2 + .../RemoveSlashFromNativeConstantFixer.php | 77 +++++++++++++++++++ .../Fixture/fix_bool_and_null.php.inc | 29 +++++++ .../Fixture/keep_casing.php.inc | 17 ++++ .../Fixture/skip_namespace_segment.php.inc | 10 +++ ...RemoveSlashFromNativeConstantFixerTest.php | 28 +++++++ .../config/configured_rule.php | 10 +++ 13 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 blink/internal/fixer/rules/remove_slash_from_native_constant.go create mode 100644 blink/internal/fixer/rules/remove_slash_from_native_constant_test.go create mode 100644 packages/coding-standard/src/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer.php create mode 100644 packages/coding-standard/tests/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer/Fixture/fix_bool_and_null.php.inc create mode 100644 packages/coding-standard/tests/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer/Fixture/keep_casing.php.inc create mode 100644 packages/coding-standard/tests/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer/Fixture/skip_namespace_segment.php.inc create mode 100644 packages/coding-standard/tests/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer/RemoveSlashFromNativeConstantFixerTest.php create mode 100644 packages/coding-standard/tests/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer/config/configured_rule.php diff --git a/blink/README.md b/blink/README.md index c9d8d8c349..80e7af2c28 100644 --- a/blink/README.md +++ b/blink/README.md @@ -13,7 +13,7 @@ CI keeps it current. | tool | rules | of ECS | |---|---:|---:| | [ECS](https://github.com/symplify/easy-coding-standard) (baseline) | 207 | 100% | -| blink | 211 | 101% | +| blink | 212 | 102% | ## Build diff --git a/blink/internal/fixer/fixer.go b/blink/internal/fixer/fixer.go index 4e843e666a..1ed15174da 100644 --- a/blink/internal/fixer/fixer.go +++ b/blink/internal/fixer/fixer.go @@ -41,6 +41,7 @@ var ecsSrcOnlyFixers = map[string]bool{ `Symplify\CodingStandard\Fixer\Annotation\RemovePropertyVariableNameDescriptionFixer`: true, `Symplify\CodingStandard\Fixer\Annotation\RemoveMethodNameDuplicateDescriptionFixer`: true, `Symplify\CodingStandard\Fixer\Annotation\RemoveEventSubscriberDescriptionFixer`: true, + `Symplify\CodingStandard\Fixer\ConstantNotation\RemoveSlashFromNativeConstantFixer`: true, } // Fixer inspects a token stream and mutates it in place. It is the core contract diff --git a/blink/internal/fixer/rules/priorities.go b/blink/internal/fixer/rules/priorities.go index bc2f23efc4..531ab11233 100644 --- a/blink/internal/fixer/rules/priorities.go +++ b/blink/internal/fixer/rules/priorities.go @@ -14,6 +14,7 @@ var fixerPriority = map[string]int{ "FixParamNameTypoFixer": -37, "RemoveParamNameReferenceFixer": -37, "RemoveParamDescriptionDuplicateNameFixer": 7, + "RemoveSlashFromNativeConstantFixer": 0, "SwitchedTypeAndNameFixer": -37, "RemoveDeadParamFixer": -37, "RemoveDeadVarThisFixer": -37, diff --git a/blink/internal/fixer/rules/registry.go b/blink/internal/fixer/rules/registry.go index 11b2207dd4..ecdeb4f46d 100644 --- a/blink/internal/fixer/rules/registry.go +++ b/blink/internal/fixer/rules/registry.go @@ -228,6 +228,7 @@ func StructuralFixers() []fixer.Fixer { SingleBlankLineBeforeNamespace{}, BlankLineAfterNamespace{}, NoUnusedImports{}, + RemoveSlashFromNativeConstant{}, FullyQualifiedStrictTypes{}, SingleImportPerStatement{}, OrderedImports{}, diff --git a/blink/internal/fixer/rules/remove_slash_from_native_constant.go b/blink/internal/fixer/rules/remove_slash_from_native_constant.go new file mode 100644 index 0000000000..f802367db7 --- /dev/null +++ b/blink/internal/fixer/rules/remove_slash_from_native_constant.go @@ -0,0 +1,59 @@ +package rules + +import ( + "blink/internal/token" + "blink/internal/tokens" +) + +// Symplify: https://github.com/ecsphp/ecs-src/blob/main/packages/coding-standard/src/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer.php +// +// RemoveSlashFromNativeConstant strips the redundant leading backslash from the +// true, false and null constants (\true -> true). +type RemoveSlashFromNativeConstant struct{} + +func (RemoveSlashFromNativeConstant) Name() string { + return `Symplify\CodingStandard\Fixer\ConstantNotation\RemoveSlashFromNativeConstantFixer` +} + +func (RemoveSlashFromNativeConstant) SourceURL() string { + return "https://github.com/ecsphp/ecs-src/blob/main/packages/coding-standard/src/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer.php" +} + +func (RemoveSlashFromNativeConstant) Fix(s *tokens.Stream) bool { + changed := false + for i := 0; i < s.Len(); i++ { + if s.At(i).Kind != token.Punct || s.At(i).Value != `\` { + continue + } + + next := nextSignificantIndex(s, i) + if next < 0 || s.At(next).Kind != token.Ident { + continue + } + + lower := lowerASCII(s.At(next).Value) + if lower != "true" && lower != "false" && lower != "null" { + continue + } + + // the keyword must stand alone, not be a namespace segment like + // "Foo\true" or "\true\Bar" + if prev := prevSignificantIndex(s, i); prev >= 0 { + pt := s.At(prev) + if pt.Kind == token.Ident || (pt.Kind == token.Punct && pt.Value == `\`) { + continue + } + } + if after := nextSignificantIndex(s, next); after >= 0 { + at := s.At(after) + if at.Kind == token.Punct && at.Value == `\` { + continue + } + } + + s.RemoveAt(i) + changed = true + i-- + } + return changed +} diff --git a/blink/internal/fixer/rules/remove_slash_from_native_constant_test.go b/blink/internal/fixer/rules/remove_slash_from_native_constant_test.go new file mode 100644 index 0000000000..6e47de5d57 --- /dev/null +++ b/blink/internal/fixer/rules/remove_slash_from_native_constant_test.go @@ -0,0 +1,33 @@ +package rules + +import "testing" + +func TestRemoveSlashFromNativeConstant(t *testing.T) { + t.Parallel() + f := RemoveSlashFromNativeConstant{} + + // leading backslash on true/false/null is stripped + got, changed := apply(t, f, " $tokens + */ + public function isCandidate(Tokens $tokens): bool + { + return $tokens->isTokenKindFound(T_NS_SEPARATOR); + } + + /** + * @param Tokens $tokens + */ + public function fix(SplFileInfo $fileInfo, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (! $token->isGivenKind(T_NS_SEPARATOR)) { + continue; + } + + $nextIndex = $tokens->getNextMeaningfulToken($index); + if ($nextIndex === null) { + continue; + } + + if (! $tokens[$nextIndex]->isGivenKind(T_STRING)) { + continue; + } + + if (! in_array(strtolower($tokens[$nextIndex]->getContent()), self::KEYWORDS, true)) { + continue; + } + + // the keyword must be a standalone name, not a namespace segment like "Foo\true" or "\true\Bar" + $prevIndex = $tokens->getPrevMeaningfulToken($index); + if ($prevIndex !== null && $tokens[$prevIndex]->isGivenKind([T_STRING, T_NS_SEPARATOR])) { + continue; + } + + $afterKeywordIndex = $tokens->getNextMeaningfulToken($nextIndex); + if ($afterKeywordIndex !== null && $tokens[$afterKeywordIndex]->isGivenKind(T_NS_SEPARATOR)) { + continue; + } + + $tokens->clearAt($index); + } + } +} diff --git a/packages/coding-standard/tests/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer/Fixture/fix_bool_and_null.php.inc b/packages/coding-standard/tests/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer/Fixture/fix_bool_and_null.php.inc new file mode 100644 index 0000000000..a73778414b --- /dev/null +++ b/packages/coding-standard/tests/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer/Fixture/fix_bool_and_null.php.inc @@ -0,0 +1,29 @@ + +----- + diff --git a/packages/coding-standard/tests/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer/Fixture/keep_casing.php.inc b/packages/coding-standard/tests/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer/Fixture/keep_casing.php.inc new file mode 100644 index 0000000000..8b891f1874 --- /dev/null +++ b/packages/coding-standard/tests/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer/Fixture/keep_casing.php.inc @@ -0,0 +1,17 @@ + +----- + diff --git a/packages/coding-standard/tests/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer/Fixture/skip_namespace_segment.php.inc b/packages/coding-standard/tests/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer/Fixture/skip_namespace_segment.php.inc new file mode 100644 index 0000000000..043dcc82e8 --- /dev/null +++ b/packages/coding-standard/tests/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer/Fixture/skip_namespace_segment.php.inc @@ -0,0 +1,10 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFiles(__DIR__ . '/Fixture'); + } + + public function provideConfig(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/packages/coding-standard/tests/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer/config/configured_rule.php b/packages/coding-standard/tests/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer/config/configured_rule.php new file mode 100644 index 0000000000..f18f957be6 --- /dev/null +++ b/packages/coding-standard/tests/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(RemoveSlashFromNativeConstantFixer::class); +}; From b662038cc33611d5fb5ebb20aeff5579fb46fb54 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 26 Oct 2026 13:24:32 +0100 Subject: [PATCH 2/2] Keep PHP fixer only; blink port follows in a separate PR --- blink/README.md | 2 +- blink/internal/fixer/fixer.go | 1 - blink/internal/fixer/rules/priorities.go | 1 - blink/internal/fixer/rules/registry.go | 1 - .../remove_slash_from_native_constant.go | 59 ------------------- .../remove_slash_from_native_constant_test.go | 33 ----------- 6 files changed, 1 insertion(+), 96 deletions(-) delete mode 100644 blink/internal/fixer/rules/remove_slash_from_native_constant.go delete mode 100644 blink/internal/fixer/rules/remove_slash_from_native_constant_test.go diff --git a/blink/README.md b/blink/README.md index 80e7af2c28..c9d8d8c349 100644 --- a/blink/README.md +++ b/blink/README.md @@ -13,7 +13,7 @@ CI keeps it current. | tool | rules | of ECS | |---|---:|---:| | [ECS](https://github.com/symplify/easy-coding-standard) (baseline) | 207 | 100% | -| blink | 212 | 102% | +| blink | 211 | 101% | ## Build diff --git a/blink/internal/fixer/fixer.go b/blink/internal/fixer/fixer.go index 1ed15174da..4e843e666a 100644 --- a/blink/internal/fixer/fixer.go +++ b/blink/internal/fixer/fixer.go @@ -41,7 +41,6 @@ var ecsSrcOnlyFixers = map[string]bool{ `Symplify\CodingStandard\Fixer\Annotation\RemovePropertyVariableNameDescriptionFixer`: true, `Symplify\CodingStandard\Fixer\Annotation\RemoveMethodNameDuplicateDescriptionFixer`: true, `Symplify\CodingStandard\Fixer\Annotation\RemoveEventSubscriberDescriptionFixer`: true, - `Symplify\CodingStandard\Fixer\ConstantNotation\RemoveSlashFromNativeConstantFixer`: true, } // Fixer inspects a token stream and mutates it in place. It is the core contract diff --git a/blink/internal/fixer/rules/priorities.go b/blink/internal/fixer/rules/priorities.go index 531ab11233..bc2f23efc4 100644 --- a/blink/internal/fixer/rules/priorities.go +++ b/blink/internal/fixer/rules/priorities.go @@ -14,7 +14,6 @@ var fixerPriority = map[string]int{ "FixParamNameTypoFixer": -37, "RemoveParamNameReferenceFixer": -37, "RemoveParamDescriptionDuplicateNameFixer": 7, - "RemoveSlashFromNativeConstantFixer": 0, "SwitchedTypeAndNameFixer": -37, "RemoveDeadParamFixer": -37, "RemoveDeadVarThisFixer": -37, diff --git a/blink/internal/fixer/rules/registry.go b/blink/internal/fixer/rules/registry.go index ecdeb4f46d..11b2207dd4 100644 --- a/blink/internal/fixer/rules/registry.go +++ b/blink/internal/fixer/rules/registry.go @@ -228,7 +228,6 @@ func StructuralFixers() []fixer.Fixer { SingleBlankLineBeforeNamespace{}, BlankLineAfterNamespace{}, NoUnusedImports{}, - RemoveSlashFromNativeConstant{}, FullyQualifiedStrictTypes{}, SingleImportPerStatement{}, OrderedImports{}, diff --git a/blink/internal/fixer/rules/remove_slash_from_native_constant.go b/blink/internal/fixer/rules/remove_slash_from_native_constant.go deleted file mode 100644 index f802367db7..0000000000 --- a/blink/internal/fixer/rules/remove_slash_from_native_constant.go +++ /dev/null @@ -1,59 +0,0 @@ -package rules - -import ( - "blink/internal/token" - "blink/internal/tokens" -) - -// Symplify: https://github.com/ecsphp/ecs-src/blob/main/packages/coding-standard/src/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer.php -// -// RemoveSlashFromNativeConstant strips the redundant leading backslash from the -// true, false and null constants (\true -> true). -type RemoveSlashFromNativeConstant struct{} - -func (RemoveSlashFromNativeConstant) Name() string { - return `Symplify\CodingStandard\Fixer\ConstantNotation\RemoveSlashFromNativeConstantFixer` -} - -func (RemoveSlashFromNativeConstant) SourceURL() string { - return "https://github.com/ecsphp/ecs-src/blob/main/packages/coding-standard/src/Fixer/ConstantNotation/RemoveSlashFromNativeConstantFixer.php" -} - -func (RemoveSlashFromNativeConstant) Fix(s *tokens.Stream) bool { - changed := false - for i := 0; i < s.Len(); i++ { - if s.At(i).Kind != token.Punct || s.At(i).Value != `\` { - continue - } - - next := nextSignificantIndex(s, i) - if next < 0 || s.At(next).Kind != token.Ident { - continue - } - - lower := lowerASCII(s.At(next).Value) - if lower != "true" && lower != "false" && lower != "null" { - continue - } - - // the keyword must stand alone, not be a namespace segment like - // "Foo\true" or "\true\Bar" - if prev := prevSignificantIndex(s, i); prev >= 0 { - pt := s.At(prev) - if pt.Kind == token.Ident || (pt.Kind == token.Punct && pt.Value == `\`) { - continue - } - } - if after := nextSignificantIndex(s, next); after >= 0 { - at := s.At(after) - if at.Kind == token.Punct && at.Value == `\` { - continue - } - } - - s.RemoveAt(i) - changed = true - i-- - } - return changed -} diff --git a/blink/internal/fixer/rules/remove_slash_from_native_constant_test.go b/blink/internal/fixer/rules/remove_slash_from_native_constant_test.go deleted file mode 100644 index 6e47de5d57..0000000000 --- a/blink/internal/fixer/rules/remove_slash_from_native_constant_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package rules - -import "testing" - -func TestRemoveSlashFromNativeConstant(t *testing.T) { - t.Parallel() - f := RemoveSlashFromNativeConstant{} - - // leading backslash on true/false/null is stripped - got, changed := apply(t, f, "