PERF: Avoid excessive object creations in watched words (#27354)

Inline the helper functions, avoid creating and then immediately destructuring arrays, use complete strings instead of string interpolation, Map instead of a pojo.
This commit is contained in:
Jarek Radosz
2024-06-10 14:44:31 +02:00
committed by GitHub
parent 71391cd40d
commit 42a529f9ae
6 changed files with 51 additions and 67 deletions

View File

@ -1,9 +1,5 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import {
createWatchedWordRegExp,
toWatchedWord,
} from "discourse-common/utils/watched-words";
export default class WatchedWordTest extends Component { export default class WatchedWordTest extends Component {
@tracked value; @tracked value;
@ -31,7 +27,10 @@ export default class WatchedWordTest extends Component {
if (this.isReplace || this.isLink) { if (this.isReplace || this.isLink) {
const matches = []; const matches = [];
this.args.model.watchedWord.words.forEach((word) => { this.args.model.watchedWord.words.forEach((word) => {
const regexp = createWatchedWordRegExp(word); const regexp = new RegExp(
word.regexp,
word.case_sensitive ? "gu" : "gui"
);
let match; let match;
while ((match = regexp.exec(this.value)) !== null) { while ((match = regexp.exec(this.value)) !== null) {
@ -42,38 +41,44 @@ export default class WatchedWordTest extends Component {
} }
}); });
return matches; return matches;
} else if (this.isTag) { }
const matches = {};
if (this.isTag) {
const matches = new Map();
this.args.model.watchedWord.words.forEach((word) => { this.args.model.watchedWord.words.forEach((word) => {
const regexp = createWatchedWordRegExp(word); const regexp = new RegExp(
word.regexp,
word.case_sensitive ? "gu" : "gui"
);
let match; let match;
while ((match = regexp.exec(this.value)) !== null) { while ((match = regexp.exec(this.value)) !== null) {
if (!matches[match[1]]) { if (!matches.has(match[1])) {
matches[match[1]] = new Set(); matches.set(match[1], new Set());
} }
let tags = matches[match[1]]; const tags = matches.get(match[1]);
word.replacement.split(",").forEach((tag) => { word.replacement.split(",").forEach((tag) => tags.add(tag));
tags.add(tag);
});
} }
}); });
return Object.entries(matches).map((entry) => ({ return Array.from(matches, ([match, tagsSet]) => ({
match: entry[0], match,
tags: Array.from(entry[1]), tags: Array.from(tagsSet),
})); }));
} else { }
let matches = [];
this.args.model.watchedWord.compiledRegularExpression.forEach( let matches = [];
(regexp) => { this.args.model.watchedWord.compiledRegularExpression.forEach((entry) => {
const wordRegexp = createWatchedWordRegExp(toWatchedWord(regexp)); const [regexp, options] = Object.entries(entry)[0];
matches.push(...(this.value.match(wordRegexp) || [])); const wordRegexp = new RegExp(
} regexp,
options.case_sensitive ? "gu" : "gui"
); );
return matches; matches.push(...(this.value.match(wordRegexp) || []));
} });
return matches;
} }
} }

View File

@ -1,9 +0,0 @@
export function createWatchedWordRegExp(word) {
const caseFlag = word.case_sensitive ? "" : "i";
return new RegExp(word.regexp, `${caseFlag}gu`);
}
export function toWatchedWord(regexp) {
const [[regexpString, options]] = Object.entries(regexp);
return { ...options, regexp: regexpString };
}

View File

@ -196,7 +196,7 @@ function applyEmoji(
enableShortcuts, enableShortcuts,
inlineEmoji, inlineEmoji,
customEmojiTranslation, customEmojiTranslation,
watchedWordsReplacer, watchedWordsReplace,
emojiDenyList emojiDenyList
) { ) {
let result = null; let result = null;
@ -206,19 +206,16 @@ function applyEmoji(
content = emojiUnicodeReplacer(content); content = emojiUnicodeReplacer(content);
} }
if (watchedWordsReplacer) { if (content && watchedWordsReplace) {
const watchedWordRegex = Object.keys(watchedWordsReplacer); Object.entries(watchedWordsReplace).forEach(([regexpString, options]) => {
if (content.match(regexpString)) {
watchedWordRegex.forEach((watchedWord) => { const regex = new RegExp(regexpString, "g");
if (content?.match(watchedWord)) {
const regex = new RegExp(watchedWord, "g");
const matches = content.match(regex); const matches = content.match(regex);
const replacement = watchedWordsReplacer[watchedWord].replacement;
matches.forEach(() => { matches.forEach(() => {
const matchingRegex = regex.exec(content); const matchingRegex = regex.exec(content);
if (matchingRegex) { if (matchingRegex) {
content = content.replace(matchingRegex[1], replacement); content = content.replace(matchingRegex[1], options.replacement);
} }
}); });
} }
@ -226,9 +223,9 @@ function applyEmoji(
} }
// prevent denied emoji and aliases from being rendered // prevent denied emoji and aliases from being rendered
if (emojiDenyList?.length > 0) { if (content && emojiDenyList?.length > 0) {
emojiDenyList.forEach((emoji) => { emojiDenyList.forEach((emoji) => {
if (content?.match(emoji)) { if (content.match(emoji)) {
const regex = new RegExp(`:${emoji}:`, "g"); const regex = new RegExp(`:${emoji}:`, "g");
content = content.replace(regex, ""); content = content.replace(regex, "");
} }

View File

@ -1,8 +1,3 @@
import {
createWatchedWordRegExp,
toWatchedWord,
} from "discourse-common/utils/watched-words";
const MAX_MATCHES = 100; const MAX_MATCHES = 100;
function isLinkOpen(str) { function isLinkOpen(str) {
@ -59,11 +54,12 @@ export function setup(helper) {
if (md.options.discourse.watchedWordsReplace) { if (md.options.discourse.watchedWordsReplace) {
Object.entries(md.options.discourse.watchedWordsReplace).forEach( Object.entries(md.options.discourse.watchedWordsReplace).forEach(
([regexpString, options]) => { ([regexpString, options]) => {
const word = toWatchedWord({ [regexpString]: options });
matchers.push({ matchers.push({
word: new RegExp(options.regexp, options.case_sensitive ? "" : "i"), word: new RegExp(options.regexp, options.case_sensitive ? "" : "i"),
pattern: createWatchedWordRegExp(word), pattern: new RegExp(
regexpString,
options.case_sensitive ? "gu" : "gui"
),
replacement: options.replacement, replacement: options.replacement,
link: false, link: false,
html: options.html, html: options.html,
@ -75,11 +71,12 @@ export function setup(helper) {
if (md.options.discourse.watchedWordsLink) { if (md.options.discourse.watchedWordsLink) {
Object.entries(md.options.discourse.watchedWordsLink).forEach( Object.entries(md.options.discourse.watchedWordsLink).forEach(
([regexpString, options]) => { ([regexpString, options]) => {
const word = toWatchedWord({ [regexpString]: options });
matchers.push({ matchers.push({
word: new RegExp(options.regexp, options.case_sensitive ? "" : "i"), word: new RegExp(options.regexp, options.case_sensitive ? "" : "i"),
pattern: createWatchedWordRegExp(word), pattern: new RegExp(
regexpString,
options.case_sensitive ? "gu" : "gui"
),
replacement: options.replacement, replacement: options.replacement,
link: true, link: true,
}); });

View File

@ -1,13 +1,8 @@
import { export function censorFn(regexpList, replacementLetter = "■") {
createWatchedWordRegExp,
toWatchedWord,
} from "discourse-common/utils/watched-words";
export function censorFn(regexpList, replacementLetter) {
if (regexpList?.length) { if (regexpList?.length) {
replacementLetter = replacementLetter || "■"; const censorRegexps = regexpList.map((entry) => {
let censorRegexps = regexpList.map((regexp) => { const [regexp, options] = Object.entries(entry)[0];
return createWatchedWordRegExp(toWatchedWord(regexp)); return new RegExp(regexp, options.case_sensitive ? "gu" : "gui");
}); });
return function (text) { return function (text) {

View File

@ -91,7 +91,6 @@ module PrettyText
discourse-common/addon/lib/deprecated discourse-common/addon/lib/deprecated
discourse-common/addon/lib/escape discourse-common/addon/lib/escape
discourse-common/addon/lib/avatar-utils discourse-common/addon/lib/avatar-utils
discourse-common/addon/utils/watched-words
discourse/app/lib/to-markdown discourse/app/lib/to-markdown
discourse/app/static/markdown-it/features discourse/app/static/markdown-it/features
].each do |f| ].each do |f|