diff --git a/.github/workflows/blink.yaml b/.github/workflows/blink.yaml index c981d9e7e0..7522c219f7 100644 --- a/.github/workflows/blink.yaml +++ b/.github/workflows/blink.yaml @@ -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" diff --git a/.github/workflows/blink_parity.yaml b/.github/workflows/blink_parity.yaml index b4ed357521..6599ca8e15 100644 --- a/.github/workflows/blink_parity.yaml +++ b/.github/workflows/blink_parity.yaml @@ -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: diff --git a/.github/workflows/blink_rule_counts.yaml b/.github/workflows/blink_rule_counts.yaml index 2582ad8688..eb7e3ec6c2 100644 --- a/.github/workflows/blink_rule_counts.yaml +++ b/.github/workflows/blink_rule_counts.yaml @@ -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 diff --git a/.github/workflows/blink_tests.yaml b/.github/workflows/blink_tests.yaml index 511071ab50..acbd7579db 100644 --- a/.github/workflows/blink_tests.yaml +++ b/.github/workflows/blink_tests.yaml @@ -17,7 +17,7 @@ jobs: - uses: actions/setup-go@v5 with: - go-version: "1.26.9" + go-version: "1.26.10" - run: go build ./... @@ -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 . @@ -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: | @@ -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 @@ -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: @@ -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 diff --git a/.github/workflows/buid_release.yaml b/.github/workflows/buid_release.yaml index d224a23954..e2481b2384 100644 --- a/.github/workflows/buid_release.yaml +++ b/.github/workflows/buid_release.yaml @@ -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 diff --git a/blink/internal/fixer/rules/imports_order.go b/blink/internal/fixer/rules/imports_order.go index 2470116445..dc4243377d 100644 --- a/blink/internal/fixer/rules/imports_order.go +++ b/blink/internal/fixer/rules/imports_order.go @@ -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 " 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 @@ -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 @@ -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: diff --git a/blink/internal/fixer/rules/imports_order_config_test.go b/blink/internal/fixer/rules/imports_order_config_test.go index 406e14a217..75c6789366 100644 --- a/blink/internal/fixer/rules/imports_order_config_test.go +++ b/blink/internal/fixer/rules/imports_order_config_test.go @@ -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 " suffix is part of the alpha key: a plain import sorts + // before the aliased import of the same class, matching PHP-CS-Fixer + src := "