diff --git a/.github/workflows/blink_parity_mautic.yaml b/.github/workflows/blink_parity_mautic.yaml index 6f00d34e8b..0620383f8f 100644 --- a/.github/workflows/blink_parity_mautic.yaml +++ b/.github/workflows/blink_parity_mautic.yaml @@ -95,7 +95,7 @@ jobs: - name: Compare the two trees run: | # ratchet gate: fail once the differing share crosses this; lower it as parity improves - MAX_DIFF_PERCENT=0.15 + MAX_DIFF_PERCENT=0.1 total=0 differ=0 differing_files="" diff --git a/blink/internal/fixer/rules/gen3_func.go b/blink/internal/fixer/rules/gen3_func.go index fe7d4c70ec..ef440dc7c0 100644 --- a/blink/internal/fixer/rules/gen3_func.go +++ b/blink/internal/fixer/rules/gen3_func.go @@ -46,6 +46,23 @@ func fnEnsureSingleSpaceBefore(s *tokens.Stream, i int) bool { // fnGlueAfter removes a single-line whitespace token immediately after i, gluing // the token at i to the following significant token. A newline is left intact. +// fnIsArrowFn reports whether the "fn" keyword at i opens an arrow function - its +// parameter "(" follows directly or after a by-reference "&". +func fnIsArrowFn(s *tokens.Stream, i int) bool { + n := nextSignificantIndex(s, i) + if n < 0 || s.At(n).Kind != token.Punct { + return false + } + if s.At(n).Value == "(" { + return true + } + if s.At(n).Value == "&" { + nn := nextSignificantIndex(s, n) + return nn >= 0 && s.At(nn).Kind == token.Punct && s.At(nn).Value == "(" + } + return false +} + func fnGlueAfter(s *tokens.Stream, i int) bool { if i+1 < s.Len() && s.At(i+1).Kind == token.Whitespace && !hasNewline(s.At(i+1).Value) { s.RemoveAt(i + 1) @@ -134,9 +151,9 @@ func (f FunctionDeclaration) Fix(s *tokens.Stream) bool { changed = true } case "fn": - // arrow function: spacing between "fn" and "(" (closure_fn_spacing) - if n := nextSignificantIndex(s, i); n >= 0 && - s.At(n).Kind == token.Punct && s.At(n).Value == "(" { + // arrow function: spacing after "fn" (closure_fn_spacing). The "(" + // follows directly, or after a by-reference "&" ("fn &() => ..."). + if fnIsArrowFn(s, i) { if f.closureFnSpacingNone { if fnGlueAfter(s, i) { changed = true diff --git a/blink/internal/fixer/rules/gen3_func_config_test.go b/blink/internal/fixer/rules/gen3_func_config_test.go index 375a83935d..da5d150930 100644 --- a/blink/internal/fixer/rules/gen3_func_config_test.go +++ b/blink/internal/fixer/rules/gen3_func_config_test.go @@ -24,6 +24,18 @@ func TestFunctionDeclarationClosureFnSpacing(t *testing.T) { if want := " 1;"; !changed || got != want { t.Fatalf("closure_fn_spacing=none: changed=%v got=%q want=%q", changed, got, want) } + + // a by-reference arrow function ("fn &() => ...") also glues the space after fn + got, changed = apply(t, cfg, " $x;") + if want := " $x;"; !changed || got != want { + t.Fatalf("by-ref fn: changed=%v got=%q want=%q", changed, got, want) + } + + // closure_fn_spacing=one keeps a single space for the by-reference form + one := FunctionDeclaration{}.WithConfig(map[string]any{"closure_fn_spacing": "one"}) + if _, changed := apply(t, one, " $x;"); changed { + t.Fatal("closure_fn_spacing=one must keep the single space in fn &()") + } } func TestFunctionDeclarationTrailingCommaSingleLine(t *testing.T) {