Skip to content
Open
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
13 changes: 13 additions & 0 deletions ruleguard.rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,19 @@ func joinpath(m dsl.Matcher) {
Report(`did you mean path.Join() or filepath.Join() ?`)
}

func rewriteSplitN(m dsl.Matcher) {
m.Match(
`$x := strings.SplitN($y, $delim, 2)[0]`,
`$x := strings.SplitN($y, $delim, -1)[0]`,
`$x := strings.Split($y, $delim)[0]`,
).
Suggest(`$x, _, _ := strings.Cut($y, $delim)`).
Report(`Use strings.Cut instead of strings.SplitN for cleaner string splitting`)
m.Match(`$x := strings.SplitN($y, $delim, 2)[1]`).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point was also to add SplitN -1 and Split for [1]

As you can see there are some out there.

Copy link
Contributor Author

@dnwe dnwe Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I wrote this rule to catch "someone wrote valid and reasonable code with a clear intent (split into exactly two values), but it could be done in a cleaner more readable way that achieves the exact same result, here is an autofix to tidy that up for you" approach – a bit like the new gopls modernize package.

For $x := strings.Split($y, $delim)[1] and $x := strings.SplitN($y, $delim, -1)[1] the autofix is only valid if there are exactly two elements because the [1] equivalent in strings.Cut is "after", i.e., all elements after the delimiter, not just the second element alone

So whilst these two lines are equivalent:

port := strings.Split("host:port", ":")[1]
_, port, _ := strings.Cut("host:port", ":")

These two are not

want := strings.Split("a,b,c", ",")[1]
_, got, _ := strings.Cut("a,b,c", ",")
string differs (-got, +want):
  string(
- 	"b,c",
+ 	"b",
  )

(playground link)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right.

Suggest(`_, $x, _ := strings.Cut($y, $delim)`).
Report(`Use strings.Cut instead of strings.SplitN for cleaner string splitting`)
}

func readfull(m dsl.Matcher) {
m.Match(`$n, $err := io.ReadFull($_, $slice)
if $err != nil || $n != len($slice) {
Expand Down
21 changes: 21 additions & 0 deletions splitn.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
rules:
- id: rewrite-splitn-to-cut-first
patterns:
- pattern-either:
- pattern: $X := strings.SplitN($Y, $DELIM, 2)[0]
- pattern: $X := strings.SplitN($Y, $DELIM, -1)[0]
- pattern: $X := strings.Split($Y, $DELIM)[0]
message: Replace strings.SplitN with strings.Cut for cleaner string splitting
languages: [go]
severity: ERROR
fix: |
$X, _, _ := strings.Cut($Y, $DELIM)

- id: rewrite-splitn-to-cut-second
pattern: |
$X := strings.SplitN($Y, $DELIM, 2)[1]
message: Replace strings.SplitN with strings.Cut for cleaner string splitting
languages: [go]
severity: ERROR
fix: |
_, $X, _ := strings.Cut($Y, $DELIM)