# core/matchers/search.matcher.ts

> 49 lines of code and 11 definitions.

Tree: Site tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/tree#file-core-matchers-search-matcher-ts
Source text: https://banes-lab.com/assets/sources/source.3abc980184f7914cc05e5b3a935c685097f916af2a56816557e2dfc08d1b1efa.generated.txt

## Definitions

- `matchesAll` (lexical_declaration, line 56, exported)
- `skipsOne` (lexical_declaration, line 18)
- `withinOneEdit` (lexical_declaration, line 34, exported)
- `markupWords` (lexical_declaration, line 10, exported)
- `fuzzyPrefix` (lexical_declaration, line 46)
- `termMatches` (lexical_declaration, line 50, exported)
- `swapsOne` (lexical_declaration, line 26)
- `queryTerms` (lexical_declaration, line 6, exported)
- `differences` (lexical_declaration, line 27)
- `at` (lexical_declaration, line 28)
- `PREFIX_SLACK` (lexical_declaration, line 44)

## Uses

- [core/converters/markup.converter.ts](https://banes-lab.com/source/tree/core/converters/markup.converter.ts.md)

## Used by

- [domain/converters/search.fragment.converter.ts](https://banes-lab.com/source/tree/domain/converters/search.fragment.converter.ts.md)

## Source

```typescript
import { FUZZY_MIN_TERM_LENGTH } from "#configuration/constants/search.constants";
import { SPACE } from "#configuration/constants/document.constants";
import { convertMarkup } from "#core/converters/markup.converter";
import { wordsOf } from "#core/normalizers/word.normalizer";

export const queryTerms = function queryTerms(query: string): readonly string[] {
    return [...new Set(wordsOf(query))];
};

export const markupWords = function markupWords(markup: string): readonly string[] {
    return wordsOf(
        convertMarkup(markup)
            .map((run) => run.text)
            .join(SPACE),
    );
};

const skipsOne = function skipsOne(longer: string, shorter: string): boolean {
    let at = 0;
    while (at < shorter.length && longer.charAt(at) === shorter.charAt(at)) {
        at += 1;
    }
    return longer.slice(at + 1) === shorter.slice(at);
};

const swapsOne = function swapsOne(left: string, right: string): boolean {
    let differences = 0;
    for (let at = 0; at < left.length; at += 1) {
        differences += left.charAt(at) === right.charAt(at) ? 0 : 1;
    }
    return differences <= 1;
};

export const withinOneEdit = function withinOneEdit(left: string, right: string): boolean {
    if (left.length === right.length) {
        return swapsOne(left, right);
    }
    if (Math.abs(left.length - right.length) > 1) {
        return false;
    }
    return left.length > right.length ? skipsOne(left, right) : skipsOne(right, left);
};

const PREFIX_SLACK = [-1, 0, 1];

const fuzzyPrefix = function fuzzyPrefix(term: string, word: string): boolean {
    return PREFIX_SLACK.some((slack) => withinOneEdit(term, word.slice(0, term.length + slack)));
};

export const termMatches = function termMatches(term: string, words: readonly string[]): boolean {
    return words.some(
        (word) => word.startsWith(term) || (term.length >= FUZZY_MIN_TERM_LENGTH && fuzzyPrefix(term, word)),
    );
};

export const matchesAll = function matchesAll(terms: readonly string[], words: readonly string[]): boolean {
    return terms.length > 0 && terms.every((term) => termMatches(term, words));
};
```
