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
84 changes: 84 additions & 0 deletions __tests__/common/lowEffortComment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { isLowEffortComment } from '../../src/common/lowEffortComment';

describe('isLowEffortComment', () => {
describe('positive cases (low-effort)', () => {
it.each([
['nice'],
['Great!'],
['Thanks for sharing!'],
["Let's go!"],
['sooo cool'],
['👍👍👍'],
['Thanks 👍'],
['wow trop coool'],
['gracias'],
['Helpful post.'],
['this is good'],
['OK'],
['thx'],
['Damnnnnnn'],
['Greatt!!!'],
])('flags %p as low-effort', (content) => {
expect(isLowEffortComment(content)).toBe(true);
});
});

describe('negative cases (substantive)', () => {
it.each([
// Templated @user welcome … greetings (any continuation, not just "welcome to")
['@alice welcome to The Awesome Squad!'],
['@bob welcome aboard! Glad to have you'],
['@giovannicompitiliceo welcome as well!!!'],
['@carol welcome back!'],

// Image / GIF markdown embeds — anywhere in the comment, with or without surrounding text
['![GIF](https://media.tenor.com/PXOXwsJKbSYAAAAC/where-you.gif)'],
['![GIF](https://media.tenor.com/PXOXwsJKbSYAAAAC/where-you.gif) ??'],
['![GIF](<https://static.klipy.com/abc.gif>)'],
['check this out ![GIF](https://media.tenor.com/x.gif)'],

// Real questions
['is this secure?'],
['free version?'],
['is the github repo down?'],
['And what is that extension?'],

// Substantive short comments / opinions / answers
['Concerning'],
['Too verbose'],
['Embrace Enshittification!'],
['Certified NPM classic.'],
['Typescript supremacy'],
['Option D'],
['Answer: C (NoSQL Database)'],
['Awesome, a PR from Taylor!'],
])('does not flag %p', (content) => {
expect(isLowEffortComment(content)).toBe(false);
});
});

describe('edge cases', () => {
it('treats empty string as low-effort', () => {
expect(isLowEffortComment('')).toBe(true);
});

it('handles mixed-case the same as lowercase', () => {
expect(isLowEffortComment('GREAT!')).toBe(true);
expect(isLowEffortComment('NiCe')).toBe(true);
});

it('ignores leading/trailing whitespace', () => {
expect(isLowEffortComment(' nice ')).toBe(true);
expect(isLowEffortComment('\t\nthanks\n')).toBe(true);
});

it('does not flag a long substantive comment', () => {
const longComment =
'This article walks through the tradeoffs between event sourcing and ' +
'CRUD persistence really clearly, and I appreciated the concrete ' +
'benchmarks they ran on Postgres versus Kafka — definitely worth a ' +
'second read before our next architecture review.';
expect(isLowEffortComment(longComment)).toBe(false);
});
});
});
309 changes: 309 additions & 0 deletions src/common/lowEffortComment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
const LOW_VOCAB = new Set<string>([
'a',
'an',
'the',
'is',
'it',
'this',
'that',
'these',
'those',
'i',
'you',
'me',
'my',
'your',
'our',
'we',
'us',
'of',
'to',
'for',
'and',
'or',
'but',
'so',
'very',
'really',
'super',
'too',
'quite',
'pretty',
'much',
'all',
'one',
'ones',
'some',
'any',
'no',
'not',
'its',
'am',
'are',
'was',
'were',
'be',
'been',
'will',
'just',
'still',
'with',
'as',
'at',
'on',
'in',
'by',
'from',
'definitely',
'absolutely',
'totally',
'completely',
'darn',
'let',
'lot',
'another',
'being',
'thanks',
'thank',
'thx',
'ty',
'tysm',
'cheers',
'kudos',
'respect',
'props',
'salute',
'appreciate',
'appreciated',
'appreciation',
'gracias',
'por',
'la',
'informacion',
'información',
'merci',
'danke',
'obrigado',
'grazie',
'excelente',
'excellente',
'genial',
'bravo',
'arigato',
'spasibo',
'dziekuje',
'dziękuję',
'trop',
'nice',
'great',
'good',
'cool',
'awesome',
'amazing',
'wonderful',
'beautiful',
'brilliant',
'excellent',
'fantastic',
'impressive',
'inspiring',
'insightful',
'informative',
'helpful',
'useful',
'interesting',
'lovely',
'perfect',
'epic',
'solid',
'noice',
'dope',
'sweet',
'fire',
'lit',
'based',
'huge',
'massive',
'wise',
'well',
'deserved',
'hats',
'off',
'awsome',
'aweome',
'amazng',
'greatt',
'goood',
'niceee',
'cooll',
'colll',
'coll',
'coool',
'superb',
'inshight',
'impresive',
'interensting',
'damnn',
'wowww',
'soo',
'noo',
'boo',
'goo',
'woo',
'yoo',
'loo',
'loveet',
'love',
'loved',
'loves',
'liked',
'like',
'post',
'article',
'read',
'reading',
'work',
'find',
'share',
'sharing',
'info',
'tip',
'tips',
'update',
'news',
'idea',
'ideas',
'point',
'catch',
'tool',
'feature',
'stuff',
'job',
'observation',
'insight',
'insights',
'heads',
'up',
'image',
'content',
'blog',
'story',
'recap',
'digest',
'thinking',
'decision',
'wow',
'omg',
'damn',
'ouch',
'rip',
'lol',
'lolol',
'haha',
'hahaha',
'hehe',
'yay',
'yes',
'yeah',
'yep',
'yup',
'yepp',
'nope',
'ok',
'okay',
'hi',
'hello',
'hey',
'sad',
'crying',
'wtf',
'sus',
'meh',
'bruh',
'agree',
'agreed',
'same',
'true',
'truth',
'facts',
'fact',
'preach',
'real',
'accurate',
'correct',
'finally',
'first',
'congrats',
'congratulations',
'welcome',
'farewell',
'lets',
'go',
'lessgo',
'lessgoo',
'lfg',
'lesgo',
'lesgoo',
'said',
'put',
'done',
'banger',
'game',
'changer',
'changing',
'underrated',
'overrated',
'looks',
'sounds',
'seems',
'feels',
'feel',
'looking',
'sounding',
'building',
'build',
'built',
'bro',
'man',
'dude',
'guys',
'mate',
'sir',
'folks',
'team',
'w',
'l',
'ws',
'ls',
'big',
'small',
]);

const APOSTROPHE_FRAGMENTS = /\b(?:s|t|d|m|ll|ve|re)\b/g;
const TEMPLATED_WELCOME = /^\s*@[A-Za-z0-9_-]+\s+welcome\b/i;
const IMAGE_EMBED = /!\[[^\]]*\]\(<?[^)]+>?\)/;

export const isLowEffortComment = (content: string): boolean => {
// Carve-outs — intentional/templated content that should never be flagged:
// * "@user welcome …" greetings ("welcome to <Squad>", "welcome aboard",
// "welcome back", "welcome as well", …)
// * any comment containing a markdown image / GIF embed
if (TEMPLATED_WELCOME.test(content)) return false;
if (IMAGE_EMBED.test(content)) return false;

const normalized = content
.toLowerCase()
.replace(/<[^>]+>/g, ' ')
.replace(/https?:\/\/\S+/g, ' ')
.replace(/@[A-Za-z0-9_-]+/g, ' ')
.replace(/['`’]/g, ' ')
.replace(/[^a-z0-9 ]+/g, ' ')
.replace(/([a-z])\1{2,}/g, '$1$1')
.replace(APOSTROPHE_FRAGMENTS, ' ')
.replace(/\s+/g, ' ')
.trim();

if (normalized.length <= 4) return true;
const words = normalized.split(' ');
return words.length <= 6 && words.every((w) => LOW_VOCAB.has(w));
};
Loading
Loading