Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add option to ignore diacritics #773

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 7 additions & 0 deletions docs/api/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ tags:

Indicates whether comparisons should be case sensitive.

### `ignoreDiacritics`

- Type: `boolean`
- Default: `false`

Indicates whether comparisons should ignore diacritics (accents).

### `includeScore`

- Type: `boolean`
Expand Down
2 changes: 2 additions & 0 deletions src/core/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const BasicOptions = {
// When `true`, the algorithm continues searching to the end of the input even if a perfect
// match is found before the end of the same input.
isCaseSensitive: false,
// When `true`, the algorithm will ignore diacritics (accents) in comparisons
ignoreDiacritics: false,
// When true, the matching function will continue to the end of a search pattern even if
includeScore: false,
// List of properties that will be searched. This also supports nested properties.
Expand Down
1 change: 1 addition & 0 deletions src/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default class Fuse {
constructor(docs, options = {}, index) {
this.options = { ...Config, ...options }

console.log(this.options);
piitaya marked this conversation as resolved.
Show resolved Hide resolved
if (
this.options.useExtendedSearch &&
!process.env.EXTENDED_SEARCH_ENABLED
Expand Down
3 changes: 3 additions & 0 deletions src/helpers/diacritics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const stripDiacritics = String.prototype.normalize
? ((str) => str.normalize('NFD').replace(/[\u0300-\u036F]/g, ''))
: ((str) => str);
2 changes: 2 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,8 @@ export type FuseOptionKey<T> = FuseOptionKeyObject<T> | string | string[]
export interface IFuseOptions<T> {
/** Indicates whether comparisons should be case sensitive. */
isCaseSensitive?: boolean
/** Indicates whether comparisons should ignore diacritics (accents). */
ignoreDiacritics?: boolean
/** Determines how close the match must be to the fuzzy location (specified by `location`). An exact letter match which is `distance` characters away from the fuzzy location would score as a complete mismatch. A `distance` of `0` requires the match be at the exact `location` specified. A distance of `1000` would require a perfect match to be within `800` characters of the `location` to be found using a `threshold` of `0.8`. */
distance?: number
/** When true, the matching function will continue to the end of a search pattern even if a perfect match has already been located in the string. */
Expand Down
14 changes: 9 additions & 5 deletions src/search/bitap/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import search from './search'
import createPatternAlphabet from './createPatternAlphabet'
import { MAX_BITS } from './constants'
import Config from '../../core/config'
import { stripDiacritics } from '../../helpers/diacritics'

export default class BitapSearch {
constructor(
Expand All @@ -14,6 +15,7 @@ export default class BitapSearch {
findAllMatches = Config.findAllMatches,
minMatchCharLength = Config.minMatchCharLength,
isCaseSensitive = Config.isCaseSensitive,
ignoreDiacritics = Config.ignoreDiacritics,
ignoreLocation = Config.ignoreLocation
} = {}
) {
Expand All @@ -25,10 +27,13 @@ export default class BitapSearch {
findAllMatches,
minMatchCharLength,
isCaseSensitive,
ignoreDiacritics,
ignoreLocation
}

this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase()
pattern = isCaseSensitive ? pattern : pattern.toLowerCase()
pattern = ignoreDiacritics ? stripDiacritics(pattern) : pattern;
this.pattern = pattern;

this.chunks = []

Expand Down Expand Up @@ -66,11 +71,10 @@ export default class BitapSearch {
}

searchIn(text) {
const { isCaseSensitive, includeMatches } = this.options
const { isCaseSensitive, ignoreDiacritics, includeMatches } = this.options

if (!isCaseSensitive) {
text = text.toLowerCase()
}
text = isCaseSensitive ? text : text.toLowerCase()
text = ignoreDiacritics ? stripDiacritics(text) : text

// Exact match
if (this.pattern === text) {
Expand Down
2 changes: 2 additions & 0 deletions src/search/extended/FuzzyMatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default class FuzzyMatch extends BaseMatch {
findAllMatches = Config.findAllMatches,
minMatchCharLength = Config.minMatchCharLength,
isCaseSensitive = Config.isCaseSensitive,
ignoreDiacritics = Config.ignoreDiacritics,
ignoreLocation = Config.ignoreLocation
} = {}
) {
Expand All @@ -25,6 +26,7 @@ export default class FuzzyMatch extends BaseMatch {
findAllMatches,
minMatchCharLength,
isCaseSensitive,
ignoreDiacritics,
ignoreLocation
})
}
Expand Down
10 changes: 8 additions & 2 deletions src/search/extended/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import parseQuery from './parseQuery'
import FuzzyMatch from './FuzzyMatch'
import IncludeMatch from './IncludeMatch'
import Config from '../../core/config'
import { stripDiacritics } from '../../helpers/diacritics'

// These extended matchers can return an array of matches, as opposed
// to a singl match
Expand Down Expand Up @@ -40,6 +41,7 @@ export default class ExtendedSearch {
pattern,
{
isCaseSensitive = Config.isCaseSensitive,
ignoreDiacritics = Config.ignoreDiacritics,
includeMatches = Config.includeMatches,
minMatchCharLength = Config.minMatchCharLength,
ignoreLocation = Config.ignoreLocation,
Expand All @@ -52,6 +54,7 @@ export default class ExtendedSearch {
this.query = null
this.options = {
isCaseSensitive,
ignoreDiacritics,
includeMatches,
minMatchCharLength,
findAllMatches,
Expand All @@ -61,7 +64,9 @@ export default class ExtendedSearch {
distance
}

this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase()
pattern = isCaseSensitive ? pattern : pattern.toLowerCase()
pattern = ignoreDiacritics ? stripDiacritics(pattern) : pattern
this.pattern = pattern
this.query = parseQuery(this.pattern, this.options)
}

Expand All @@ -79,9 +84,10 @@ export default class ExtendedSearch {
}
}

const { includeMatches, isCaseSensitive } = this.options
const { includeMatches, isCaseSensitive, ignoreDiacritics } = this.options

text = isCaseSensitive ? text : text.toLowerCase()
text = ignoreDiacritics ? stripDiacritics(text) : text

let numMatches = 0
let allIndices = []
Expand Down
43 changes: 43 additions & 0 deletions test/extended-search.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,46 @@ describe('ignoreLocation when useExtendedSearch is true', () => {
expect(result).toHaveLength(1)
})
})

describe('Searching using extended search ignoring diactrictics', () => {
const list = [
{
text: 'déjà'
},
{
text: 'cafe'
}
]

const options = {
useExtendedSearch: true,
ignoreDiacritics: true,
threshold: 0,
keys: ['text']
}
const fuse = new Fuse(list, options)

test('Search: query with diactrictics, list with diactrictics', () => {
let result = fuse.search('déjà')
expect(result).toHaveLength(1)
expect(result[0].refIndex).toBe(0)
})

test('Search: query without diactrictics, list with diactrictics', () => {
let result = fuse.search('deja')
expect(result).toHaveLength(1)
expect(result[0].refIndex).toBe(0)
})

test('Search: query with diactrictics, list without diactrictics', () => {
let result = fuse.search('café')
expect(result).toHaveLength(1)
expect(result[0].refIndex).toBe(1)
})

test('Search: query without diactrictics, list without diactrictics', () => {
let result = fuse.search('cafe')
expect(result).toHaveLength(1)
expect(result[0].refIndex).toBe(1)
})
})
42 changes: 42 additions & 0 deletions test/fuzzy-search.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1209,3 +1209,45 @@ describe('Breaking values', () => {
expect(result).toHaveLength(1)
})
})

describe('Searching ignoring diactrictics', () => {
const list = [
{
text: 'déjà'
},
{
text: 'cafe'
}
]

const options = {
ignoreDiacritics: true,
threshold: 0,
keys: ['text']
}
const fuse = new Fuse(list, options)

test('Search: query with diactrictics, list with diactrictics', () => {
let result = fuse.search('déjà')
expect(result).toHaveLength(1)
expect(result[0].refIndex).toBe(0)
})

test('Search: query without diactrictics, list with diactrictics', () => {
let result = fuse.search('deja')
expect(result).toHaveLength(1)
expect(result[0].refIndex).toBe(0)
})

test('Search: query with diactrictics, list without diactrictics', () => {
let result = fuse.search('café')
expect(result).toHaveLength(1)
expect(result[0].refIndex).toBe(1)
})

test('Search: query without diactrictics, list without diactrictics', () => {
let result = fuse.search('cafe')
expect(result).toHaveLength(1)
expect(result[0].refIndex).toBe(1)
})
})