So long as you are just trying to solve obvious cases and aren't trying to create a bullet-proof system, regex may actually be very helpful here.
I'm going to use the words "cat" and "dog" so as not to cause too much offense (and risk getting banned):
Array of Regexes
const badWordRegexes = [
/\bc[a4]+t\b/ig,
/\bd[o0]+g\b/ig
];
const input = "bird snake cat fish c4t catfish birddog D00000000G";
// Use the reduce function to apply each regex in turn,
// passing along the censored input as you go
const censored = badWordRegexes.reduce(
// For each regex, replace all matches of the regex with "*"
(input, regex) =>
// As each regex is applied, the input cascades,
// rolling all edits together
input.replace(regex,
badWord => "*".repeat(badWord.length)
),
input
);
console.log(censored);
Depending on how long your word list is, this might not be the most efficient way to process bad words.
Single Regex with Alternation
You could use a single regex, though it might get very long:
const badWordsRegex = new RegExp('\\b(' +
'c[a4]+t|'+
'd[o0]+g' +
')\\b', 'ig');
// Alternatively:
// const badWordsRegex = /\b(c[a4]+t|d[o0]+g)\b/ig;
const input = "bird snake cat fish c4t catfish birddog D00000000G";
const censored = input.replace(badWordsRegex,
badWord => "*".repeat(badWord.length)
);
console.log(censored);
The code above is MUCH shorter and faster, but at the expense of being much harder to read and work with. You have to choose between a very hard to read regex, or a RegExp string (which runs into other complications).
List of Words Processed into Single Regex
A third alternative would be to automatically generate a regex from a list of words. This has the benefits of being both easy to maintain and relatively fast (so long as you generate the regex once and store it somewhere instead of with each function call). How exactly you want to do this will depend on your needs, but here's one way:
function escapeRegExp(string) {
return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
function convertWordToRegexString(word) {
// Start by escaping any special characters that might be in the string:
word = escapeRegExp(word);
// Replace all vowels with alternations of their l33t alternatives:
const l33t = { a: "4@", e: "3", i: "1|!", o: "0" };
return word.replace(/[aeio]/g, c => `[${c}${l33t[c]}]+`);
}
const badWords = ["cat", "dog"];
const badWordsRegexString = "\\b(" + badWords.map(convertWordToRegexString).join("|") + ")\\b";
const badWordsRegex = new RegExp(badWordsRegexString, 'ig');
// Alternatively:
// const badWordsRegex = /\b(c[a4]+t|d[o0]+g)\b/ig;
const input = "bird snake cat fish c4t catfish birddog D00000000G";
const censored = input.replace(badWordsRegex,
badWord => "*".repeat(badWord.length)
);
console.log(censored);
The downside is you get the least amount of flexibility with this approach. You may run into trouble if you want to filter words with special characters or entire phrases rather than just words, but you can always adapt this logic to suit your needs.
Ultimately this will be a balance between your moderator's time, performance requirements and developer time. But then again most software projects are.