Skip to content

Commit

Permalink
fix: pluralize type helper to account for uncountable nouns and speci…
Browse files Browse the repository at this point in the history
…al rules (#10011)
  • Loading branch information
thetutlage authored Nov 11, 2024
1 parent 1cab15b commit e228088
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 30 deletions.
44 changes: 44 additions & 0 deletions packages/core/types/src/common/__tests__/pluralize.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { expectTypeOf } from "expect-type"
import { Pluralize } from "../common"

describe("Pluralize", () => {
test("pluralize uncountable nouns", () => {
expectTypeOf<Pluralize<"media">>().toEqualTypeOf<"media">()
expectTypeOf<Pluralize<"Media">>().toEqualTypeOf<"Media">()
expectTypeOf<Pluralize<"you">>().toEqualTypeOf<"you">()
expectTypeOf<Pluralize<"sheep">>().toEqualTypeOf<"sheep">()
expectTypeOf<Pluralize<"series">>().toEqualTypeOf<"series">()
expectTypeOf<Pluralize<"species">>().toEqualTypeOf<"species">()
expectTypeOf<Pluralize<"deer">>().toEqualTypeOf<"deer">()
})

test("pluralize words ending with fe", () => {
expectTypeOf<Pluralize<"wife">>().toEqualTypeOf<"wives">()
expectTypeOf<Pluralize<"knife">>().toEqualTypeOf<"knives">()
})

test("pluralize words ending with o", () => {
expectTypeOf<Pluralize<"hero">>().toEqualTypeOf<"heroes">()
})

test("pluralize words ending with ch", () => {
expectTypeOf<Pluralize<"watch">>().toEqualTypeOf<"watches">()
})

test("pluralize words ending with z", () => {
expectTypeOf<Pluralize<"fiz">>().toEqualTypeOf<"fizes">()
})

test("pluralize words ending with y", () => {
expectTypeOf<Pluralize<"puppy">>().toEqualTypeOf<"puppies">()
})

test("pluralize words with special rules", () => {
expectTypeOf<Pluralize<"person">>().toEqualTypeOf<"people">()
expectTypeOf<Pluralize<"child">>().toEqualTypeOf<"children">()
expectTypeOf<Pluralize<"man">>().toEqualTypeOf<"men">()
expectTypeOf<Pluralize<"criterion">>().toEqualTypeOf<"criteria">()
expectTypeOf<Pluralize<"tooth">>().toEqualTypeOf<"teeth">()
expectTypeOf<Pluralize<"foot">>().toEqualTypeOf<"feet">()
})
})
174 changes: 144 additions & 30 deletions packages/core/types/src/common/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,39 +240,153 @@ export interface NumericalComparisonOperator {
lte?: number
}

/**
* The keywords that does not have a plural form
*/
type UncountableRules =
| "adulthood"
| "advice"
| "agenda"
| "aid"
| "aircraft"
| "alcohol"
| "ammo"
| "analytics"
| "anime"
| "athletics"
| "audio"
| "bison"
| "blood"
| "bream"
| "buffalo"
| "butter"
| "carp"
| "cash"
| "chassis"
| "chess"
| "clothing"
| "cod"
| "commerce"
| "cooperation"
| "corps"
| "debris"
| "diabetes"
| "digestion"
| "elk"
| "energy"
| "equipment"
| "excretion"
| "expertise"
| "firmware"
| "flounder"
| "fun"
| "gallows"
| "garbage"
| "graffiti"
| "hardware"
| "headquarters"
| "health"
| "herpes"
| "highjinks"
| "homework"
| "housework"
| "information"
| "jeans"
| "justice"
| "kudos"
| "labour"
| "literature"
| "machinery"
| "mackerel"
| "mail"
| "media"
| "mews"
| "moose"
| "music"
| "mud"
| "manga"
| "news"
| "only"
| "personnel"
| "pike"
| "plankton"
| "pliers"
| "police"
| "pollution"
| "premises"
| "rain"
| "research"
| "rice"
| "salmon"
| "scissors"
| "series"
| "sewage"
| "shambles"
| "shrimp"
| "software"
| "staff"
| "swine"
| "tennis"
| "traffic"
| "transportation"
| "trout"
| "tuna"
| "wealth"
| "welfare"
| "whiting"
| "wildebeest"
| "wildlife"
| "you"
| "deer"
| "sheep"

type PluralizationSpecialRules = {
person: "people"
child: "children"
man: "men"
criterion: "criteria"
tooth: "teeth"
foot: "feet"
}

/**
* @ignore
*/
export type Pluralize<Singular extends string> = Singular extends `${infer R}ss`
? `${Singular}es`
: Singular extends `${infer R}sis`
? `${R}ses`
: Singular extends `${infer R}is`
? `${R}ises`
: Singular extends `${infer R}s`
? `${Singular}`
: Singular extends `${infer R}ey`
? `${R}eys`
: Singular extends `${infer R}y`
? `${R}ies`
: Singular extends `${infer R}es`
? `${Singular}`
: Singular extends
| `${infer R}sh`
| `${infer R}ch`
| `${infer R}x`
| `${infer R}z`
| `${infer R}o`
? `${Singular}es`
: Singular extends `${infer R}fe`
? `${R}ves`
: Singular extends `${infer R}ex` | `${infer R}ix`
? `${R}ices`
: Singular extends `${infer R}eau`
? `${R}eaux`
: Singular extends `${infer R}ieu`
? `${R}ieux`
: `${Singular}s`
export type Pluralize<Singular extends string> =
Lowercase<Singular> extends keyof PluralizationSpecialRules
? PluralizationSpecialRules[Lowercase<Singular>]
: Lowercase<Singular> extends UncountableRules
? Singular
: Singular extends `${infer R}ss`
? `${Singular}es`
: Singular extends `${infer R}sis`
? `${R}ses`
: Singular extends `${infer R}is`
? `${R}ises`
: Singular extends `${infer R}s`
? `${Singular}`
: Singular extends `${infer R}ey`
? `${R}eys`
: Singular extends `${infer R}y`
? `${R}ies`
: Singular extends `${infer R}es`
? `${Singular}`
: Singular extends
| `${infer R}sh`
| `${infer R}ch`
| `${infer R}x`
| `${infer R}z`
| `${infer R}o`
? `${Singular}es`
: Singular extends `${infer R}fe`
? `${R}ves`
: Singular extends `${infer R}ex` | `${infer R}ix`
? `${R}ices`
: Singular extends `${infer R}eau`
? `${R}eaux`
: Singular extends `${infer R}ieu`
? `${R}ieux`
: `${Singular}s`

export type SnakeCase<S extends string> =
S extends `${infer T}${infer U}${infer V}`
Expand Down

0 comments on commit e228088

Please sign in to comment.