From 650b8c3371ced215dacf18a651208d485185ba0f Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 10 Oct 2026 10:59:25 +0200 Subject: [PATCH 1/2] Do not report net-zero fixer runs in blink When one rule edits the stream and another reverts it (e.g. UnaryOperatorSpaces strips the space after "!" and NotOperatorWithSuccessorSpace adds it back), the rendered file equals the original. Those files were reported as fixable with an empty diff. Report a change only when the final content differs from the original, matching PHP-CS-Fixer. --- blink/internal/runner/net_zero_test.go | 40 ++++++++++++++++++++++++++ blink/internal/runner/runner.go | 5 ++++ 2 files changed, 45 insertions(+) create mode 100644 blink/internal/runner/net_zero_test.go diff --git a/blink/internal/runner/net_zero_test.go b/blink/internal/runner/net_zero_test.go new file mode 100644 index 0000000000..9e0239f1e9 --- /dev/null +++ b/blink/internal/runner/net_zero_test.go @@ -0,0 +1,40 @@ +package runner + +import ( + "context" + "os" + "path/filepath" + "testing" + + "blink/internal/config" + "blink/internal/fixer/rules" +) + +// Two rules that fight to a draw must not be reported as a change: UnaryOperatorSpaces +// (full) strips the space after "!" and NotOperatorWithSuccessorSpace adds it back, +// so the rendered file is identical to the original. PHP-CS-Fixer reports only real +// diffs, so blink must too (otherwise the file shows up with an empty diff). +func TestRunNetZeroNotReported(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "sample.php") + src := " Date: Sat, 10 Oct 2026 10:59:25 +0200 Subject: [PATCH 2/2] Skip keyword-named class references in blink ClassDefinition The lexer tags a class name like "Enum" (in "Enum::MODE_ADD") as the "enum" keyword. ClassDefinition treated it as a declaration and, with single_line, flattened everything up to the next brace, mangling unrelated code. A real declaration is followed by the type-name identifier, so skip a class-like keyword not followed by one. --- blink/internal/fixer/rules/class_definition.go | 6 ++++++ .../fixer/rules/class_definition_config_test.go | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/blink/internal/fixer/rules/class_definition.go b/blink/internal/fixer/rules/class_definition.go index 881e7b12ca..351f0db95d 100644 --- a/blink/internal/fixer/rules/class_definition.go +++ b/blink/internal/fixer/rules/class_definition.go @@ -77,6 +77,12 @@ func (f ClassDefinition) Fix(s *tokens.Stream) bool { } continue } + // a genuine declaration is followed by the type name; a class-like keyword + // followed by "::" or "(" (e.g. "Enum::MODE_ADD") is a name the lexer tagged + // as a keyword, not a declaration + if n := nextSignificantIndex(s, i); n < 0 || s.At(n).Kind != token.Ident { + continue + } if normalizeHeaderSpacing(s, i) { changed = true } diff --git a/blink/internal/fixer/rules/class_definition_config_test.go b/blink/internal/fixer/rules/class_definition_config_test.go index d325ac7225..4f9e95725c 100644 --- a/blink/internal/fixer/rules/class_definition_config_test.go +++ b/blink/internal/fixer/rules/class_definition_config_test.go @@ -19,6 +19,21 @@ func TestClassDefinitionSingleLine(t *testing.T) { } } +// A class-name reference the lexer tags as a class-like keyword (here "Enum" in +// "Enum::MODE_ADD") is not a declaration, so single_line must not flatten the code +// that follows it. +func TestClassDefinitionIgnoresKeywordNamedReference(t *testing.T) { + t.Parallel() + src := "