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
20 changes: 18 additions & 2 deletions studio/src/lib/analysis.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,26 @@ export function tokenize(text) {
.filter(Boolean);
}

// Compiled emoji regexes are cached per emoji-list array so countEmoji does not
// recompile ~70 RegExps on every call (it runs once per tweet, several times).
// String.prototype.match with a /g regex is stateless (it ignores and resets
// lastIndex), so reusing the same compiled regexes across calls is safe.
const _emojiRegexCache = new WeakMap();

function emojiRegexes(list) {
let regexes = _emojiRegexCache.get(list);
if (!regexes) {
regexes = list.map(
(e) => new RegExp(e.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"), "g"),
);
_emojiRegexCache.set(list, regexes);
}
return regexes;
}

export function countEmoji(text, list) {
let count = 0;
for (const e of list) {
const re = new RegExp(e.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"), "g");
for (const re of emojiRegexes(list)) {
const m = text.match(re);
if (m) count += m.length;
}
Expand Down
7 changes: 7 additions & 0 deletions studio/src/lib/analysis.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ describe("countEmoji", () => {
it("returns 0 when no listed emoji are present", () => {
expect(countEmoji("plain text", POSITIVE_EMOJI)).toBe(0);
});

it("stays correct across repeated calls with the same list (cached regexes are stateless)", () => {
expect(countEmoji("🔥🔥", POSITIVE_EMOJI)).toBe(2);
expect(countEmoji("🔥", POSITIVE_EMOJI)).toBe(1);
expect(countEmoji("no emoji here", POSITIVE_EMOJI)).toBe(0);
expect(countEmoji("🔥🔥🔥", POSITIVE_EMOJI)).toBe(3);
});
});

describe("analyzeSentiment", () => {
Expand Down
Loading