@dcoffey/espells
Version:
Pure JS/TS spellchecker, using Hunspell dictionaries. Based on Spylls.
113 lines • 4.33 kB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { CapType, CompoundPos } from "../constants.js";
import { includes, lowercase } from "../util.js";
import { decompose } from "./decompose.js";
import { LKFlags } from "./lk-flags.js";
/**
* Yields the allowed {@link AffixForm}s for this word, as in all ways the
* word can be split into stems and affixes, with all stems and affixes
* being mutually compatible.
*/
export function* affixForms(word, allowNoSuggest = true, withForbidden = false, flags = new LKFlags()) {
const aff = word.aff;
const dic = word.dic;
for (const form of decompose(word, flags)) {
let found = false;
const homonyms = dic.homonyms(form.stem);
if (homonyms.size) {
if (!withForbidden && hasForbidden(word, form, homonyms))
return;
found = yield* candidates(word, form, allowNoSuggest, homonyms);
}
if (word.pos === CompoundPos.BEGIN && aff.FORCEUCASE && word.type === CapType.INIT) {
const homonyms = dic.homonyms(lowercase(form.stem), true);
found = yield* candidates(word, form, allowNoSuggest, homonyms);
}
if (found || word.pos !== undefined || word.type !== CapType.ALL) {
continue;
}
if (aff.casing.guess(word.word) === CapType.NO) {
yield* candidates(word, form, allowNoSuggest, dic.homonyms(form.stem, true));
}
}
}
function hasForbidden(lkword, form, words) {
if (!lkword.aff.FORBIDDENWORD)
return false;
if (lkword.pos === undefined && !form.hasAffixes)
return false;
for (const word of words) {
if (word.has(lkword.aff.FORBIDDENWORD))
return true;
}
return false;
}
/**
* Takes an {@link AffixForm} and yields all of the allowed forms of the
* entire word form, taking into account {@link Aff} directives and other edge cases.
*/
function* candidates(word, form, allowNoSuggest = true, homonyms) {
let found = false;
for (const homonym of homonyms) {
const candidate = form.replace({ inDictionary: homonym });
if (validForm(candidate, word, allowNoSuggest)) {
found = true;
yield candidate;
}
}
return found;
}
/**
* Determines if this form is valid for the {@link LKWord} specified.
*
* @param word - The word to validate against.
* @param allowNoSuggest - If false, words which are in the dictionary, but
* are flagged with the `NOSUGGEST` flag (if provided), will not be
* considered correct. Defaults to true.
*/
export function validForm(form, word, allowNoSuggest = true) {
if (!form.inDictionary)
return false;
const aff = word.aff;
const rootFlags = form.inDictionary.flags ?? new Set();
const allFlags = form.flags;
if (!allowNoSuggest && includes(aff.NOSUGGEST, rootFlags))
return false;
if (word.type !== form.inDictionary.capType &&
includes(aff.KEEPCASE, rootFlags) &&
!aff.isSharps(form.inDictionary.stem)) {
return false;
}
if (aff.NEEDAFFIX) {
if (form.has(aff.NEEDAFFIX))
return false;
if (!form.hasAffixes && rootFlags.has(aff.NEEDAFFIX))
return false;
}
if (form.prefix && !allFlags.has(form.prefix.flag))
return false;
if (form.suffix && !allFlags.has(form.suffix.flag))
return false;
if (aff.CIRCUMFIX) {
const suffixHas = Boolean(form.suffix?.has(aff.CIRCUMFIX));
const prefixHas = Boolean(form.prefix?.has(aff.CIRCUMFIX));
if (suffixHas !== prefixHas)
return false;
}
if (word.pos === undefined) {
if (!includes(aff.ONLYINCOMPOUND, allFlags))
return true;
return false;
}
if (includes(aff.COMPOUNDFLAG, allFlags))
return true;
// prettier-ignore
switch (word.pos) {
case CompoundPos.BEGIN: return includes(aff.COMPOUNDBEGIN, allFlags);
case CompoundPos.MIDDLE: return includes(aff.COMPOUNDMIDDLE, allFlags);
case CompoundPos.END: return includes(aff.COMPOUNDEND, allFlags);
}
}
//# sourceMappingURL=affixes.js.map