Skip to content
Merged
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
51 changes: 51 additions & 0 deletions packages/inflekt/__tests__/ves-and-exceptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,57 @@ describe('singularize: suffix classes the inflection library gets wrong', () =>
});
});

describe('singularize: -is and -us words', () => {
it('depluralizes coined identifiers whose stem ends in a vowel', () => {
// The regression that started this: an "apis" table became "apises",
// because every -is/-us word was assumed to be already singular.
expect(singularize('apis')).toBe('api');
expect(singularize('uris')).toBe('uri');
expect(singularize('guis')).toBe('gui');
expect(singularize('cpus')).toBe('cpu');
expect(singularize('gpus')).toBe('gpu');
expect(singularizeLast('ApiSchemas')).toBe('ApiSchema');
expect(pluralize(singularize('apis'))).toBe('apis');
expect(pluralize(singularize('cpus'))).toBe('cpus');
});

it('leaves the dictionary singulars alone', () => {
// "iris" and "apis" have the same shape (a vowel plus "s"), so these are
// dictionary facts, not rules — see SINGULAR_EXCEPTIONS.
for (const word of [
'aegis',
'analysis',
'anxious',
'apparatus',
'bus',
'cactus',
'census',
'chassis',
'corpus',
'genius',
'iris',
'nucleus',
'radius',
'status',
'tennis',
'various',
'virus',
]) {
expect(singularize(word)).toBe(word);
}
});

it('still depluralizes the dictionary plurals of vowel stems', () => {
expect(singularize('menus')).toBe('menu');
expect(singularize('emus')).toBe('emu');
expect(singularize('skis')).toBe('ski');
expect(singularize('taxis')).toBe('taxi');
expect(singularize('alibis')).toBe('alibi');
expect(singularize('statuses')).toBe('status');
expect(singularize('irises')).toBe('iris');
});
});

describe('SINGULAR_EXCEPTIONS table', () => {
it('is applied for every entry', () => {
for (const [plural, singular] of Object.entries(SINGULAR_EXCEPTIONS)) {
Expand Down
10 changes: 10 additions & 0 deletions packages/inflekt/scripts/generate-exceptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,16 @@ for (const singular of [...exceptions.values()]) {
}
}

// Round-trip closure for the singulars that end in "s" (aegis, iris, corpus):
// their plural takes "-es", and the generic rules read that back one letter
// short (aegises -> aegise), because "-ses" is only "-sis" for the Greek nouns.
for (const singular of [...new Set(exceptions.values())]) {
if (!singular.endsWith('s')) continue;
const plural = pluralize(singular);
if (plural === singular || exceptions.has(plural)) continue;
if (singularizeByRules(plural) !== singular) exceptions.set(plural, singular);
}

const entries = [...exceptions.entries()].sort(([a], [b]) => a.localeCompare(b));
const body = entries.map(([plural, singular]) => ` ${plural}: '${singular}',`).join('\n');

Expand Down
Loading
Loading