diff --git a/packages/inflection/__tests__/inflection.test.ts b/packages/inflection/__tests__/inflection.test.ts index c436bd8e..88a88df3 100644 --- a/packages/inflection/__tests__/inflection.test.ts +++ b/packages/inflection/__tests__/inflection.test.ts @@ -173,7 +173,21 @@ describe('inflection', () => { { name: 'information', result: 'information' }, { name: 'deer', result: 'deer' }, { name: 'series', result: 'series' }, - { name: 'species', result: 'species' } + { name: 'species', result: 'species' }, + // -f/-fe: only the f-stem nouns take -ves + { name: 'knife', result: 'knives' }, + { name: 'wolf', result: 'wolves' }, + { name: 'leaf', result: 'leaves' }, + { name: 'thief', result: 'thieves' }, + { name: 'shelf', result: 'shelves' }, + { name: 'cafe', result: 'cafes' }, + { name: 'safe', result: 'safes' }, + { name: 'roof', result: 'roofs' }, + { name: 'belief', result: 'beliefs' }, + { name: 'derive', result: 'derives' }, + { name: 'mouse', result: 'mice' }, + { name: 'spouse', result: 'spouses' }, + { name: 'apis', result: 'apis' } ] ); @@ -215,6 +229,40 @@ describe('inflection', () => { { name: 'media', result: 'medium' }, { name: 'data', result: 'datum' }, { name: 'strata', result: 'stratum' }, + // -ves: only the f-stem nouns rewrite to -f/-fe; the rest drop the "s". + // The old ([^fo])ves$ -> \1fe rule turned "derives" into "derife". + { name: 'derives', result: 'derive' }, + { name: 'metaschema_public.derives', result: 'metaschema_public.derive' }, + { name: 'archives', result: 'archive' }, + { name: 'curves', result: 'curve' }, + { name: 'olives', result: 'olive' }, + { name: 'solves', result: 'solve' }, + { name: 'valves', result: 'valve' }, + { name: 'motives', result: 'motive' }, + { name: 'natives', result: 'native' }, + { name: 'hives', result: 'hive' }, + { name: 'knives', result: 'knife' }, + { name: 'lives', result: 'life' }, + { name: 'wives', result: 'wife' }, + { name: 'wolves', result: 'wolf' }, + { name: 'shelves', result: 'shelf' }, + { name: 'halves', result: 'half' }, + { name: 'leaves', result: 'leaf' }, + { name: 'loaves', result: 'loaf' }, + { name: 'thieves', result: 'thief' }, + { name: 'hooves', result: 'hoof' }, + // -ice: only mice/lice are plurals + { name: 'mice', result: 'mouse' }, + { name: 'lice', result: 'louse' }, + { name: 'police', result: 'police' }, + { name: 'service', result: 'service' }, + { name: 'chalice', result: 'chalice' }, + // -is/-us words are already singular + { name: 'iris', result: 'iris' }, + { name: 'apis', result: 'api' }, + { name: 'analysis', result: 'analysis' }, + { name: 'analyses', result: 'analysis' }, + { name: 'status', result: 'status' }, // uncountable words { name: 'sheep', result: 'sheep' }, { name: 'equipment', result: 'equipment' }, diff --git a/packages/inflection/deploy/schemas/inflection/tables/inflection_rules/fixtures/1589249334312_fixture.sql b/packages/inflection/deploy/schemas/inflection/tables/inflection_rules/fixtures/1589249334312_fixture.sql index 93072f12..85283ff8 100644 --- a/packages/inflection/deploy/schemas/inflection/tables/inflection_rules/fixtures/1589249334312_fixture.sql +++ b/packages/inflection/deploy/schemas/inflection/tables/inflection_rules/fixtures/1589249334312_fixture.sql @@ -24,7 +24,7 @@ INSERT INTO inflection.inflection_rules ('plural', '(s)eries$', NULL), ('plural', '(m)ovies$', NULL), ('plural', '(x|ch|ss|sh)es$', NULL), - ('plural', '([m|l])ice$', NULL), + ('plural', '^(m|l|dorm|titm|woodl)ice$', NULL), ('plural', '(bus)es$', NULL), ('plural', '(o)es$', NULL), ('plural', '(shoe)s$', NULL), @@ -56,14 +56,32 @@ INSERT INTO inflection.inflection_rules ('plural', '(buffal|tomat|potat)o$', E'\\1oes'), ('plural', '([ti])um$', E'\\1a'), ('plural', 'sis$', E'ses'), - ('plural', '(?:([^f])fe|([lr])f)$', E'\\1\\2ves'), + -- only the f-stem nouns take -ves; everything else takes a plain "s" + -- (cafe -> cafes, safe -> safes, roof -> roofs) via the final '$' rule + ('plural', 'self$', E'selves'), + ('plural', 'shelf$', E'shelves'), + ('plural', '(^|[^[:alpha:]])elf$', E'\\1elves'), + ('plural', 'knife$', E'knives'), + ('plural', 'wife$', E'wives'), + ('plural', '(^|[^o])life$', E'\\1lives'), + ('plural', '(^|[^c])leaf$', E'\\1leaves'), + ('plural', 'calf$', E'calves'), + ('plural', 'dwarf$', E'dwarves'), + ('plural', 'half$', E'halves'), + ('plural', 'hoof$', E'hooves'), + ('plural', 'loaf$', E'loaves'), + ('plural', 'scarf$', E'scarves'), + ('plural', 'sheaf$', E'sheaves'), + ('plural', 'thief$', E'thieves'), + ('plural', 'wharf$', E'wharves'), + ('plural', 'wolf$', E'wolves'), ('plural', '^(focus)$', E'\\1es'), ('plural', '(hi|ti)ve$', E'\\1ves'), ('plural', '([^aeiouy]|qu)y$', E'\\1ies'), ('plural', '(matr)ix$', E'\\1ices'), ('plural', '(vert|ind)ex$', E'\\1ices'), ('plural', '(x|ch|ss|sh)$', E'\\1es'), - ('plural', '([m|l])ouse$', E'\\1ice'), + ('plural', '^(m|l|dorm|titm|woodl)ouse$', E'\\1ice'), ('plural', '^foot$', E'feet'), ('plural', '^tooth$', E'teeth'), ('plural', '^goose$', E'geese'), @@ -87,14 +105,27 @@ INSERT INTO inflection.inflection_rules ('singular', '(buffal|tomat|potat)o$', NULL), ('singular', '([ti])um$', NULL), ('singular', 'sis$', NULL), + -- no English plural ends in "us" (bus, status, census, radius, lotus, and + -- every -ous adjective), so those are singular already; the -u nouns whose + -- plural IS -us come first + ('singular', '(men|gur|em|haik|tof|tut|zeb|kudz)us$', E'\\1u'), + ('singular', 'us$', NULL), + -- -is words are singular too, but the guard has to be anchored: a blanket + -- is$ rule would also keep "apis" (a platform table) from reaching "api" + ('singular', '^(iris|chassis|clematis|trellis|tennis|debris|axis|oasis|pelvis|mantis|arthritis|bronchitis|hepatitis)$', NULL), ('singular', '(?:([^f])fe|([lr])f)$', NULL), + -- mice/lice first, then every other -ice word is already singular + -- (price, police, chalice, service): the ([m|l])ice$ rule's character + -- class also matched "|" and turned "police" into "polouse" + ('singular', '^(m|l|dorm|titm|woodl)ice$', E'\\1ouse'), + ('singular', 'ice$', NULL), ('singular', '^(focus)$', NULL), ('singular', '(hi|ti)ve$', NULL), ('singular', '([^aeiouy]|qu)y$', NULL), ('singular', '(x|ch|ss|sh)$', NULL), ('singular', '(matr)ix$', NULL), ('singular', '(vert|ind)ex$', NULL), - ('singular', '([m|l])ouse$', NULL), + ('singular', '^(m|l|dorm|titm|woodl)ouse$', NULL), ('singular', '^foot$', NULL), ('singular', '^tooth$', NULL), ('singular', '^goose$', NULL), @@ -117,18 +148,39 @@ INSERT INTO inflection.inflection_rules ('singular', '(curricul)a$', E'\\1um'), ('singular', '([ti])a$', E'\\1um'), ('singular', '(.+base)s$', E'\\1'), - ('singular', '((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$', E'\\1\\2sis'), - ('singular', '(hi|ti)ves$', E'\\1ve'), - ('singular', '(curve)s$', E'\\1'), - ('singular', '([lr])ves$', E'\\1f'), - ('singular', '([a])ves$', E'\\1ve'), - ('singular', '([^fo])ves$', E'\\1fe'), + -- one flat group: PostgreSQL numbers the nested groups of the node.js + -- rule differently, which turned "analyses" into "analyasis" + ('singular', '(analy|ba|diagno|parenthe|progno|synop|the)ses$', E'\\1sis'), + -- -ves: the f-stem nouns are an explicit list, everything else simply + -- drops the "s" (derives -> derive, curves -> curve, valves -> valve). + -- The inverse rule ([^fo])ves$ -> \\1fe is what produced "derife". + -- The f-stems are matched as whole words (or compound tails): a bare + -- "(li)ves$" would also fire on "olives" -> "olife" and "(el)ves$" on + -- "delves" -> "delf". + ('singular', 'selves$', E'self'), + ('singular', 'shelves$', E'shelf'), + ('singular', '(^|[^[:alpha:]])elves$', E'\\1elf'), + ('singular', 'knives$', E'knife'), + ('singular', 'wives$', E'wife'), + ('singular', '(^|[^o])lives$', E'\\1life'), + ('singular', '(^|[^c])leaves$', E'\\1leaf'), + ('singular', 'calves$', E'calf'), + ('singular', 'dwarves$', E'dwarf'), + ('singular', 'halves$', E'half'), + ('singular', 'hooves$', E'hoof'), + ('singular', 'loaves$', E'loaf'), + ('singular', 'scarves$', E'scarf'), + ('singular', 'sheaves$', E'sheaf'), + ('singular', 'thieves$', E'thief'), + ('singular', 'wharves$', E'wharf'), + ('singular', 'wolves$', E'wolf'), + ('singular', 'ves$', E've'), ('singular', '(m)ovies$', E'\\1ovie'), ('singular', '([^aeiouy]|qu)ies$', E'\\1y'), ('singular', '(s)eries$', E'\\1eries'), ('singular', '(x|ch|ss|sh)es$', E'\\1'), - ('singular', '([m|l])ice$', E'\\1ouse'), - ('singular', '(bus)es$', E'\\1'), + ('singular', '(bus|focus)es$', E'\\1'), + ('singular', 'lenses$', E'lens'), ('singular', '(o)es$', E'\\1'), ('singular', '(shoe)s$', E'\\1'), ('singular', '(cris|ax|test)es$', E'\\1is'), diff --git a/packages/inflection/sql/pgpm-inflection--0.33.0.sql b/packages/inflection/sql/pgpm-inflection--0.33.0.sql index 2be901a9..940bdc30 100644 --- a/packages/inflection/sql/pgpm-inflection--0.33.0.sql +++ b/packages/inflection/sql/pgpm-inflection--0.33.0.sql @@ -398,7 +398,7 @@ INSERT INTO inflection.inflection_rules ( ('plural', '(s)eries$', NULL), ('plural', '(m)ovies$', NULL), ('plural', '(x|ch|ss|sh)es$', NULL), - ('plural', '([m|l])ice$', NULL), + ('plural', '^(m|l|dorm|titm|woodl)ice$', NULL), ('plural', '(bus)es$', NULL), ('plural', '(o)es$', NULL), ('plural', '(shoe)s$', NULL), @@ -429,14 +429,30 @@ INSERT INTO inflection.inflection_rules ( ('plural', '(buffal|tomat|potat)o$', E'\\1oes'), ('plural', '([ti])um$', E'\\1a'), ('plural', 'sis$', 'ses'), - ('plural', '(?:([^f])fe|([lr])f)$', E'\\1\\2ves'), + ('plural', 'self$', 'selves'), + ('plural', 'shelf$', 'shelves'), + ('plural', '(^|[^[:alpha:]])elf$', E'\\1elves'), + ('plural', 'knife$', 'knives'), + ('plural', 'wife$', 'wives'), + ('plural', '(^|[^o])life$', E'\\1lives'), + ('plural', '(^|[^c])leaf$', E'\\1leaves'), + ('plural', 'calf$', 'calves'), + ('plural', 'dwarf$', 'dwarves'), + ('plural', 'half$', 'halves'), + ('plural', 'hoof$', 'hooves'), + ('plural', 'loaf$', 'loaves'), + ('plural', 'scarf$', 'scarves'), + ('plural', 'sheaf$', 'sheaves'), + ('plural', 'thief$', 'thieves'), + ('plural', 'wharf$', 'wharves'), + ('plural', 'wolf$', 'wolves'), ('plural', '^(focus)$', E'\\1es'), ('plural', '(hi|ti)ve$', E'\\1ves'), ('plural', '([^aeiouy]|qu)y$', E'\\1ies'), ('plural', '(matr)ix$', E'\\1ices'), ('plural', '(vert|ind)ex$', E'\\1ices'), ('plural', '(x|ch|ss|sh)$', E'\\1es'), - ('plural', '([m|l])ouse$', E'\\1ice'), + ('plural', '^(m|l|dorm|titm|woodl)ouse$', E'\\1ice'), ('plural', '^foot$', 'feet'), ('plural', '^tooth$', 'teeth'), ('plural', '^goose$', 'geese'), @@ -459,14 +475,19 @@ INSERT INTO inflection.inflection_rules ( ('singular', '(buffal|tomat|potat)o$', NULL), ('singular', '([ti])um$', NULL), ('singular', 'sis$', NULL), + ('singular', '(men|gur|em|haik|tof|tut|zeb|kudz)us$', E'\\1u'), + ('singular', 'us$', NULL), + ('singular', '^(iris|chassis|clematis|trellis|tennis|debris|axis|oasis|pelvis|mantis|arthritis|bronchitis|hepatitis)$', NULL), ('singular', '(?:([^f])fe|([lr])f)$', NULL), + ('singular', '^(m|l|dorm|titm|woodl)ice$', E'\\1ouse'), + ('singular', 'ice$', NULL), ('singular', '^(focus)$', NULL), ('singular', '(hi|ti)ve$', NULL), ('singular', '([^aeiouy]|qu)y$', NULL), ('singular', '(x|ch|ss|sh)$', NULL), ('singular', '(matr)ix$', NULL), ('singular', '(vert|ind)ex$', NULL), - ('singular', '([m|l])ouse$', NULL), + ('singular', '^(m|l|dorm|titm|woodl)ouse$', NULL), ('singular', '^foot$', NULL), ('singular', '^tooth$', NULL), ('singular', '^goose$', NULL), @@ -487,18 +508,31 @@ INSERT INTO inflection.inflection_rules ( ('singular', '(curricul)a$', E'\\1um'), ('singular', '([ti])a$', E'\\1um'), ('singular', '(.+base)s$', E'\\1'), - ('singular', '((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$', E'\\1\\2sis'), - ('singular', '(hi|ti)ves$', E'\\1ve'), - ('singular', '(curve)s$', E'\\1'), - ('singular', '([lr])ves$', E'\\1f'), - ('singular', '([a])ves$', E'\\1ve'), - ('singular', '([^fo])ves$', E'\\1fe'), + ('singular', '(analy|ba|diagno|parenthe|progno|synop|the)ses$', E'\\1sis'), + ('singular', 'selves$', 'self'), + ('singular', 'shelves$', 'shelf'), + ('singular', '(^|[^[:alpha:]])elves$', E'\\1elf'), + ('singular', 'knives$', 'knife'), + ('singular', 'wives$', 'wife'), + ('singular', '(^|[^o])lives$', E'\\1life'), + ('singular', '(^|[^c])leaves$', E'\\1leaf'), + ('singular', 'calves$', 'calf'), + ('singular', 'dwarves$', 'dwarf'), + ('singular', 'halves$', 'half'), + ('singular', 'hooves$', 'hoof'), + ('singular', 'loaves$', 'loaf'), + ('singular', 'scarves$', 'scarf'), + ('singular', 'sheaves$', 'sheaf'), + ('singular', 'thieves$', 'thief'), + ('singular', 'wharves$', 'wharf'), + ('singular', 'wolves$', 'wolf'), + ('singular', 'ves$', 've'), ('singular', '(m)ovies$', E'\\1ovie'), ('singular', '([^aeiouy]|qu)ies$', E'\\1y'), ('singular', '(s)eries$', E'\\1eries'), ('singular', '(x|ch|ss|sh)es$', E'\\1'), - ('singular', '([m|l])ice$', E'\\1ouse'), - ('singular', '(bus)es$', E'\\1'), + ('singular', '(bus|focus)es$', E'\\1'), + ('singular', 'lenses$', 'lens'), ('singular', '(o)es$', E'\\1'), ('singular', '(shoe)s$', E'\\1'), ('singular', '(cris|ax|test)es$', E'\\1is'),