diff --git a/docs/naming.md b/docs/naming.md index 5264b8f0..9115cbce 100644 --- a/docs/naming.md +++ b/docs/naming.md @@ -10,9 +10,9 @@ each rename. |---|---|---| | `client_address`, `client-address`, `client.address` | `ClientAddress` | Any character that is not a letter or a digit splits words, apart from the symbols below. | | `fooBar`, `HTTPServer` | `FooBar`, `HTTPServer` | A capital starts a word. An upper-case run ends before its last capital when a lower-case letter follows. | -| `v2api`, `404NotFound` | `V2API`, `N404NotFound` | Digits stay with the word before them. A letter after them starts a new word. | +| `v2api`, `404NotFound` | `V2API`, `N404NotFound` | Digits stay with the word before them. A letter after them starts a new word, unless the word starts with the digits: `1st` stays one word. | | `CLIENT_ADDRESS`, `SSN` | `ClientAddress`, `Ssn` | Every word is written with one capital, unless it is an initialism. | -| `userId`, `urls`, `userIDs`, `id2` | `UserID`, `URLs`, `UserIDs`, `ID2` | Initialisms stay upper case, also with a plural `s` or trailing digits. | +| `userId`, `urls`, `userIDs`, `id2`, `short_2url` | `UserID`, `URLs`, `UserIDs`, `ID2`, `Short2URL` | Initialisms stay upper case, also with a plural `s` or digits before or after them. | | `1st`, `200` | `N1st`, `N200` | A name that would start with a digit gets `N` in front. | | `+1`, `-1`, `-created_at`, `@type` | `Plus1`, `Minus1`, `MinusCreatedAt`, `AtType` | `+` and `@` become words anywhere, `-` only at the start. | | `1.5`, `v1.2` | `N1Dot5`, `V1Dot2` | A dot between two digits becomes `Dot`. | @@ -39,7 +39,8 @@ naming: ``` An initialism matches a whole word in any case: `Id`, `id` and `ID` all give `ID`. It does not -match inside a word, so `userid` stays `Userid`. +match inside a word, so `userid` stays `Userid`. Digits in front do not stop it: with `DS` listed, +`force_3ds_flow` gives `Force3DSFlow`. ## Names for types without a name diff --git a/internal/naming/namer.go b/internal/naming/namer.go index c7b9ffc2..2fe4c44b 100644 --- a/internal/naming/namer.go +++ b/internal/naming/namer.go @@ -241,22 +241,35 @@ func (n *Namer) camel(part string) string { return b.String() } -// caseWord keeps initialisms canonical, also with a plural "s" or trailing digits: IDs, HTTP2. +// caseWord keeps initialisms canonical, also after leading digits: IDs, HTTP2, 3DS. func (n *Namer) caseWord(w string) string { lw := strings.ToLower(w) - if c, ok := n.initialisms[lw]; ok { + if c, ok := n.initialism(lw); ok { return c } + rest := strings.TrimLeft(lw, "0123456789") + if c, ok := n.initialism(rest); ok { + return lw[:len(lw)-len(rest)] + c + } + return strings.ToUpper(lw[:1]) + lw[1:] +} + +// initialism writes lw canonical when it is one, also with a plural "s" or trailing digits. +func (n *Namer) initialism(lw string) (string, bool) { + if c, ok := n.initialisms[lw]; ok { + return c, true + } + letters := strings.TrimRight(lw, "0123456789") digits := lw[len(letters):] if c, ok := n.initialisms[letters]; ok { - return c + digits + return c + digits, true } if base, ok := strings.CutSuffix(letters, "s"); ok { if c, found := n.initialisms[base]; found { - return c + "s" + digits + return c + "s" + digits, true } } - return strings.ToUpper(lw[:1]) + lw[1:] + return "", false } diff --git a/internal/naming/namer_test.go b/internal/naming/namer_test.go index 7c6f0e2d..46e0acd1 100644 --- a/internal/naming/namer_test.go +++ b/internal/naming/namer_test.go @@ -75,6 +75,8 @@ func TestExported(t *testing.T) { {name: "Leading digits before capitals", parts: []string{"2FA"}, want: "N2Fa"}, {name: "Leading digits before camel case", parts: []string{"404NotFound"}, want: "N404NotFound"}, {name: "Leading digit after a separator", parts: []string{"_3d"}, want: "N3d"}, + {name: "Initialism after leading digits", parts: []string{"short_2url"}, want: "Short2URL"}, + {name: "Plural initialism after leading digits", parts: []string{"3ids"}, want: "N3IDs"}, {name: "Decimal point between digits", parts: []string{"1.5"}, want: "N1Dot5"}, {name: "Version number", parts: []string{"v1.2.3"}, want: "V1Dot2Dot3"}, {name: "Dot after a letter is a separator", parts: []string{"a.1"}, want: "A1"}, @@ -134,6 +136,8 @@ func TestExportedWithExtraInitialisms(t *testing.T) { {name: "Extra initialism", extra: []string{"PSP"}, in: "pspId", want: "PSPID"}, {name: "Extra initialism given in lower case is written upper case", extra: []string{"ssn"}, in: "SSN", want: "SSN"}, {name: "Extra initialism plural", extra: []string{"PSP"}, in: "PSPs", want: "PSPs"}, + {name: "Extra initialism after leading digits", extra: []string{"DS"}, in: "force_3ds_flow", want: "Force3DSFlow"}, + {name: "Extra initialism that starts with a digit", extra: []string{"3DS"}, in: "3ds", want: "N3DS"}, {name: "Empty extra is ignored", extra: []string{""}, in: "s", want: "S"}, {name: "Defaults stay with extras", extra: []string{"PSP"}, in: "url", want: "URL"}, }