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
5 changes: 5 additions & 0 deletions apps/sim/lib/sim-search/live/text-match.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ describe('text match clauses', () => {
expect(holdsHalfTheTerms('databases releases checklist owner')('Database release')).toBe(true)
})

it('folds -ies plurals of both -y and -ie words, as the Porter stemmer does', () => {
expect(holdsHalfTheTerms('movies rotation schedule owner')('Movie rotation')).toBe(true)
expect(holdsHalfTheTerms('policies review schedule owner')('Policy review')).toBe(true)
})

it('matches a term with punctuation inside it as written', () => {
expect(holdsHalfTheTerms('ci/cd pipeline failure owner')('CI/CD pipeline')).toBe(true)
})
Expand Down
12 changes: 8 additions & 4 deletions apps/sim/lib/sim-search/live/text-match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,16 @@ function distinctTerms(query: string, keyOf: (term: string) => string): string[]
.slice(0, MAX_ANY_TERMS)
}

/** One key for a word and its regular plural (`token`/`tokens`, `process`/`processes`). */
/**
* One key for a word and its regular plural (`token`/`tokens`, `process`/`processes`). As in the
* Porter stemmer, a final `-y` and `-ie(s)` fold to `i`, so `policy`/`policies` and
* `movie`/`movies` each share a key.
*/
function singularKey(term: string): string {
const key = term.toLowerCase()
if (key.length > 4 && key.endsWith('ies')) return `${key.slice(0, -3)}y`
const stem = /[^su]s$/.test(key) ? key.slice(0, -1) : key
return /(?:s|x|z|ch|sh)e$/.test(stem) ? stem.slice(0, -1) : stem
const stripped = /[^su]s$/.test(key) ? key.slice(0, -1) : key
const stem = /(?:s|x|z|ch|sh|i)e$/.test(stripped) ? stripped.slice(0, -1) : stripped
return stem.replace(/y$/, 'i')
}

/** Escapes a value for a quoted JQL or CQL string, treating Lucene operators as literal text. */
Expand Down