fuzzyjs
Version:
Simple fuzzy matching
228 lines (217 loc) • 6.64 kB
JavaScript
var __defProp = Object.defineProperty;
var __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
var __export = (target, all) => {
__markAsModule(target);
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
// src/index.ts
__export(exports, {
filter: () => filter,
match: () => match,
sort: () => sort,
surround: () => surround,
test: () => test
});
// src/utils/prepare.ts
function reshapeInput(query, source, opts) {
if (typeof query !== "string") {
throw new TypeError("Expecting query to be a string");
}
if (typeof source !== "string") {
throw new TypeError("Expecting source to be a string");
}
let reshapedQuery = query.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
let reshapedSource = source.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
if (!opts.caseSensitive) {
reshapedQuery = reshapedQuery.toLowerCase();
reshapedSource = reshapedSource.toLowerCase();
}
return [reshapedQuery, reshapedSource];
}
// src/test.ts
function test(query, source, opts = {}) {
const [reshapedQuery, reshapedSource] = reshapeInput(query, source, opts);
if (!reshapedSource.length) {
return !query.length;
}
if (!reshapedQuery.length) {
return true;
}
if (reshapedQuery.length > reshapedSource.length) {
return false;
}
let queryPos = 0;
let sourcePos = 0;
while (sourcePos < source.length) {
const actualSourceCharacter = reshapedSource[sourcePos];
const queryCharacterWaitingForMatch = reshapedQuery[queryPos];
if (actualSourceCharacter === queryCharacterWaitingForMatch) {
queryPos += 1;
}
sourcePos += 1;
}
return queryPos === reshapedQuery.length;
}
// src/utils/range.ts
function pushRange(ranges, sourcePos) {
const lastRange = ranges[ranges.length - 1];
if (lastRange && lastRange.stop === sourcePos) {
return [
...ranges.slice(0, -1),
{
start: lastRange.start,
stop: sourcePos + 1
}
];
} else {
return [...ranges, { start: sourcePos, stop: sourcePos + 1 }];
}
}
// src/score/defaultStrategy.ts
function pushScore(previousContext, context) {
if (!context) {
throw new TypeError("Expecting context to be defined");
}
if (!context.match) {
return context.currentScore - 1;
}
let increment = 0;
if (previousContext && previousContext.match) {
increment += 5;
}
if (context.leading) {
increment += 10;
}
return context.currentScore + increment;
}
// src/utils/toLatin.ts
function toLatin(str) {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
// src/utils/isLeading.ts
function isLeading(prevChar, char) {
const precededBySeparator = prevChar === "-" || prevChar === "_" || prevChar === " " || prevChar === "." || prevChar === "/" || prevChar === "\\";
const isCharLeading = char.toUpperCase() === char && /\w/.test(toLatin(char));
return precededBySeparator || isCharLeading;
}
// src/match.ts
function match(query, source, opts = { withScore: true }) {
const [reshapedQuery, reshapedSource] = reshapeInput(query, source, opts);
const withScore = !(opts?.withScore === false);
if (reshapedSource.length === 0 || reshapedQuery.length === 0) {
return {
match: query.length === 0,
ranges: query.length === 0 ? [{ start: 0, stop: reshapedSource.length }] : [],
score: withScore ? query.length === 0 ? 1 : 0 : void 0
};
}
if (reshapedQuery.length > reshapedSource.length) {
return { match: false, ranges: [], score: withScore ? 0 : void 0 };
}
let queryPos = 0;
let sourcePos = 0;
let score = 0;
let lastContext;
let ranges = [];
while (sourcePos < source.length) {
const actualSourceCharacter = reshapedSource[sourcePos];
const queryCharacterWaitingForMatch = reshapedQuery[queryPos];
const match2 = actualSourceCharacter === queryCharacterWaitingForMatch;
if (withScore) {
const previousCharacter = sourcePos > 0 ? source[sourcePos - 1] : "";
const newContext = {
currentScore: score,
character: source[sourcePos],
match: match2,
leading: isLeading(previousCharacter, source[sourcePos])
};
score = pushScore(lastContext, newContext);
lastContext = newContext;
}
if (match2) {
ranges = pushRange(ranges, sourcePos);
queryPos += 1;
}
sourcePos += 1;
}
if (queryPos === reshapedQuery.length) {
return {
match: true,
ranges,
score: withScore ? score : void 0
};
}
return {
match: false,
ranges: [],
score: withScore ? 0 : void 0
};
}
// src/array.ts
function filter(query, options) {
return function(item) {
const source = options.iterator(item);
return test(query, source, options);
};
}
function sort(query, options) {
const cacheMap = new Map();
return (leftItem, rightItem) => {
const leftSource = options.iterator(leftItem);
const rightSource = options.iterator(rightItem);
const cachedLeftMatch = cacheMap.get(leftSource);
const cachedRightMatch = cacheMap.get(rightSource);
const leftScore = cachedLeftMatch ? cachedLeftMatch : match(query, leftSource, {
withScore: true,
caseSensitive: options.caseSensitive
}).score;
const rightScore = cachedRightMatch ? cachedRightMatch : match(query, rightSource, {
withScore: true,
caseSensitive: options.caseSensitive
}).score;
if (!cacheMap.has(leftSource)) {
cacheMap.set(leftSource, leftScore);
}
if (!cacheMap.has(rightSource)) {
cacheMap.set(rightSource, rightScore);
}
if (rightScore === leftScore) {
return 0;
}
return rightScore > leftScore ? 1 : -1;
};
}
// src/surround.ts
function surround(source, options) {
if (typeof source !== "string") {
throw new TypeError("Expecting source to be a string");
}
if (source.length === 0) {
return "";
}
if (!options?.result?.ranges?.length) {
return source;
}
let result = source;
let accumulator = 0;
for (const range of options.result.ranges) {
result = insertAt(result, range.start + accumulator, options.prefix);
accumulator += (options.prefix ?? "").length;
result = insertAt(result, range.stop + accumulator, options.suffix);
accumulator += (options.suffix ?? "").length;
}
return result;
}
function insertAt(input, index, patch = "") {
return input.slice(0, index) + patch + input.slice(index);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
filter,
match,
sort,
surround,
test
});
//# sourceMappingURL=index.js.map