Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/blink.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
-
uses: actions/setup-go@v5
with:
go-version: "1.26.9"
go-version: "1.26.10"

-
name: "Build the blink binary from the local blink/ module"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/blink_parity.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:

- uses: actions/setup-go@v5
with:
go-version: "1.26.9"
go-version: "1.26.10"

- uses: shivammathur/setup-php@v2
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/blink_rule_counts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:

- uses: actions/setup-go@v5
with:
go-version: "1.26.9"
go-version: "1.26.10"

# counts ECS/blink rules, writes them to the job summary
# (shown on every PR), and fails if README is stale
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/blink_tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:

- uses: actions/setup-go@v5
with:
go-version: "1.26.9"
go-version: "1.26.10"

- run: go build ./...

Expand All @@ -35,7 +35,7 @@ jobs:

- uses: actions/setup-go@v5
with:
go-version: "1.26.9"
go-version: "1.26.10"

- run: go build -o blink.exe .

Expand All @@ -59,7 +59,7 @@ jobs:

- uses: actions/setup-go@v5
with:
go-version: "1.26.9"
go-version: "1.26.10"

- name: Check gofmt -s
run: |
Expand Down Expand Up @@ -90,7 +90,7 @@ jobs:

- uses: actions/setup-go@v5
with:
go-version: "1.26.9"
go-version: "1.26.10"

- name: gopls modernize
# pinned: gopls v0.24.0 dropped this cmd path and needs Go >= 1.27
Expand All @@ -112,7 +112,7 @@ jobs:

- uses: actions/setup-go@v5
with:
go-version: "1.26.9"
go-version: "1.26.10"

- uses: actions/cache@v4
with:
Expand All @@ -133,7 +133,7 @@ jobs:

- uses: actions/setup-go@v5
with:
go-version: "1.26.9"
go-version: "1.26.10"

# verifies every fixer's SourceURL() points at a reachable PHP-CS-Fixer rule
- run: go run ./cmd/check-sources
2 changes: 1 addition & 1 deletion .github/workflows/buid_release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
-
uses: actions/setup-go@v5
with:
go-version: "1.26.9"
go-version: "1.26.10"

- run: |
for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64; do
Expand Down
33 changes: 19 additions & 14 deletions blink/internal/fixer/rules/imports_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,38 @@ import (
type importStmt struct {
start, semi int
kind string // "class", "function" or "const"
pathKey string // lowercased imported path, for alphabetical ordering
pathKey string // lowercased imported path + "as" alias, for alphabetical ordering
}

// importSortKey returns the lowercased imported path of a use statement (after an
// optional "function"/"const", up to ";" or " as "), used as the alphabetical
// sort key by ECS's default OrderedImports (sort_algorithm: alpha).
func importSortKey(s *tokens.Stream, useIdx, semi int) string {
// importSortKey returns the alphabetical sort key of a use statement (after an
// optional "function"/"const"): the imported path plus any "as <alias>" suffix,
// matching PHP-CS-Fixer's namespace key. PHP maps both "\" and whitespace to a
// space and compares; blink uses "\x00" as that shared separator so "\" and the
// space before "as" sort before any identifier character ("Rector\Php\X" before
// "Rector\Php71\Y", and "Foo\Bar as Baz" before "Foo\Bar\Qux"). lower controls
// case folding (off for sort_algorithm with case_sensitive: true).
func importSortKey(s *tokens.Stream, useIdx, semi int, lower bool) string {
j := skipWhitespace(s, useIdx+1)
if j < s.Len() && s.At(j).Kind == token.Keyword {
if lw := strings.ToLower(s.At(j).Value); lw == "function" || lw == "const" {
j = skipWhitespace(s, j+1)
}
}
const sep = "\x00"
var b strings.Builder
for k := j; k < semi; k++ {
t := s.At(k)
if t.Kind == token.Keyword && strings.ToLower(t.Value) == "as" {
break
}
if t.Kind == token.Whitespace {
b.WriteString(sep)
continue
}
b.WriteString(t.Value)
v := t.Value
if lower {
v = strings.ToLower(v)
}
b.WriteString(strings.ReplaceAll(v, `\`, sep))
}
// compare segment by segment: "\" must sort before any other character so
// "Rector\Php\X" precedes "Rector\Php71\Y" (ECS's alpha comparator)
return strings.ReplaceAll(strings.ToLower(b.String()), `\`, "\x00")
return strings.TrimRight(b.String(), sep)
}

// importKindRank orders use statements by kind for ECS's default imports_order
Expand Down Expand Up @@ -99,7 +104,7 @@ func collectImportRun(s *tokens.Stream, start int) []importStmt {
if group || semi < 0 {
break
}
stmts = append(stmts, importStmt{start: k, semi: semi, kind: useKind(s, k), pathKey: importSortKey(s, k, semi)})
stmts = append(stmts, importStmt{start: k, semi: semi, kind: useKind(s, k), pathKey: importSortKey(s, k, semi, true)})
n := skipWhitespace(s, semi+1)
if n < s.Len() && s.At(n).Kind == token.Keyword && strings.ToLower(s.At(n).Value) == "use" {
k = n
Expand Down Expand Up @@ -278,7 +283,7 @@ func (f OrderedImports) orderedImportsAlphaKey(s *tokens.Stream, st importStmt)
if !f.caseSensitive {
return st.pathKey
}
return strings.ReplaceAll(orderedImportsPath(s, st.start, st.semi), `\`, "\x00")
return importSortKey(s, st.start, st.semi, false)
}

// orderedImportsLen returns the length used by the (deprecated) length algorithm:
Expand Down
19 changes: 19 additions & 0 deletions blink/internal/fixer/rules/imports_order_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,22 @@ func TestOrderedImportsKeepsBlankBetweenGroups(t *testing.T) {
t.Fatalf("blank between groups must be preserved: changed=%v got=%q", changed, got)
}
}

func TestOrderedImportsAliasInSortKey(t *testing.T) {
t.Parallel()
// the "as <alias>" suffix is part of the alpha key: a plain import sorts
// before the aliased import of the same class, matching PHP-CS-Fixer
src := "<?php\nuse Mautic\\LeadBundle\\Entity\\Lead as TheLead;\nuse Mautic\\LeadBundle\\Entity\\Lead;\n"
want := "<?php\nuse Mautic\\LeadBundle\\Entity\\Lead;\nuse Mautic\\LeadBundle\\Entity\\Lead as TheLead;\n"
if got, changed := apply(t, OrderedImports{}, src); !changed || got != want {
t.Fatalf("alias key: changed=%v got=%q want=%q", changed, got, want)
}

// "\" and the space before "as" both sort before an identifier char, so an
// aliased class precedes a deeper path under the same prefix
src = "<?php\nuse Foo\\Bar\\Qux;\nuse Foo\\Bar as Baz;\n"
want = "<?php\nuse Foo\\Bar as Baz;\nuse Foo\\Bar\\Qux;\n"
if got, changed := apply(t, OrderedImports{}, src); !changed || got != want {
t.Fatalf("alias vs deeper path: changed=%v got=%q want=%q", changed, got, want)
}
}
Loading