Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
049c995
feat: initial parser implementation (33/634)
amitkumarashutosh May 23, 2026
30b187a
feat: implement paragraph and line break parsing (144/634)
amitkumarashutosh May 23, 2026
b9bb672
feat: introduce scanner-based parsing
amitkumarashutosh May 24, 2026
85f8053
feat: add escape sequences and inline code parsing (158/634)
amitkumarashutosh May 27, 2026
79fee3d
Merge branch 'RocketChat:develop' into feat/hand-written-parser
amitkumarashutosh May 28, 2026
33d22c2
feat: add bold, italic, and strikethrough inline parsing (262/634)
amitkumarashutosh May 28, 2026
ad257b1
feat: add heading, user and channel mention parsing (311/634)
amitkumarashutosh May 28, 2026
b96e5a5
feat: add code fence block parsing (319/634)
amitkumarashutosh May 28, 2026
5edfaf8
feat: add blockquote parsing with support for > prefix and inline mar…
amitkumarashutosh May 31, 2026
f3bc070
feat: add inline and block spoiler parsing with || delimiter support …
amitkumarashutosh May 31, 2026
5f26623
feat: add markdown and angle bracket link parsing (396/634)
amitkumarashutosh Jun 1, 2026
c788f6f
fix: handle Rocket.Cat style tokens as links (401/634)
amitkumarashutosh Jun 6, 2026
411372d
feat: add unordered and ordered list parsing (404/634)
amitkumarashutosh Jun 6, 2026
7425327
feat: add KaTeX block and inline parsing with dollar sign (413/634)
amitkumarashutosh Jun 6, 2026
5193ebc
feat: add auto-link URL detection (462/634)
amitkumarashutosh Jun 8, 2026
9eb1ce6
feat: add emoji shortcode parsing (492/634)
amitkumarashutosh Jun 13, 2026
65fd782
feat: add email autolinking support (508/634)
amitkumarashutosh Jun 15, 2026
84ffcd0
feat: add phone number autolinking (516/634)
amitkumarashutosh Jun 18, 2026
c67061d
feat: add timestamp parsing (529/634)
amitkumarashutosh Jun 18, 2026
721fe0f
feat: add emoticon parsing (554/634)
amitkumarashutosh Jun 22, 2026
bfb7b12
refactor: clean up message parser structure and typing (573/634)
amitkumarashutosh Jun 29, 2026
a21b870
feat: parse raw unicode emoji (600/634)
amitkumarashutosh Jul 5, 2026
225d0bf
fix: treat leading-asterisk and guard @mention after alphanumeric (60…
amitkumarashutosh Jul 12, 2026
bfe1220
feat: parse color:#rgb syntax (605/635)
amitkumarashutosh Jul 12, 2026
50f0ec2
Merge branch 'develop' into feat/hand-written-parser
amitkumarashutosh Jul 16, 2026
3a3fc48
refactor: improve timestamp parsing and clean up parser logic (646/688)
amitkumarashutosh Jul 21, 2026
50de391
feat: add image parsing support (649/688)
amitkumarashutosh Jul 21, 2026
a93bc2f
feat: add task list support (650/688)
amitkumarashutosh Jul 21, 2026
4686ddb
feat: add horizontal rule support (654/688)
amitkumarashutosh Jul 22, 2026
18cbe96
fix: handle escaped timestamps and reject relative link target (656/688)
amitkumarashutosh Jul 22, 2026
4990d98
feat: add table parsing support (664/688)
amitkumarashutosh Jul 23, 2026
6cde70c
Merge branch 'RocketChat:develop' into feat/hand-written-parser
amitkumarashutosh Jul 26, 2026
dadf6b9
fix: reject malformed URL structures in autolink (698/717)
amitkumarashutosh Jul 26, 2026
0efa86b
fix: correct link label parsing and simplify inline parser (704/717)
amitkumarashutosh Jul 27, 2026
76463ce
fix: add skipReferences guard for link labels and fix emoticon neighb…
amitkumarashutosh Jul 28, 2026
5f8f89b
fix: underscore-italic word boundaries and plain-wrapping (717/717)
amitkumarashutosh Jul 29, 2026
62d2249
refactor: address review feedback, fix esLint errors
amitkumarashutosh Jul 29, 2026
0e47bc2
chore: remove peggy loader and cleanup config
amitkumarashutosh Jul 30, 2026
a398af3
Merge branch 'RocketChat:develop' into feat/hand-written-parser
amitkumarashutosh Aug 6, 2026
040bc19
fix: parse keycap emoji sequences as single tokens (718/718)
amitkumarashutosh Aug 6, 2026
e4d5261
Merge branch 'RocketChat:develop' into feat/hand-written-parser
amitkumarashutosh Aug 9, 2026
908011a
fix: restore inline guards in finally and extract table cellAlignment…
amitkumarashutosh Aug 16, 2026
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
12 changes: 1 addition & 11 deletions packages/message-parser/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,8 @@
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

import server from '@rocket.chat/jest-presets/server';
import type { Config } from 'jest';

// Jest 30 loads this config via Node's native type stripping (Node 22.18+),
// which treats the file as ESM where __dirname is not defined as a global.
const __dirname = dirname(fileURLToPath(import.meta.url));

export default {
preset: server.preset,
transform: {
'\\.pegjs$': resolve(__dirname, './loaders/pegtransform.js'),
},
moduleFileExtensions: ['js', 'ts', 'pegjs'],
moduleFileExtensions: ['js', 'ts'],
testPathIgnorePatterns: ['/node_modules/', '\\.bench\\.ts$'],
} satisfies Config;
13 changes: 0 additions & 13 deletions packages/message-parser/loaders/pegjs-register.js

This file was deleted.

10 changes: 0 additions & 10 deletions packages/message-parser/loaders/pegtransform.js

This file was deleted.

5 changes: 1 addition & 4 deletions packages/message-parser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
".:build:bundle": "webpack-cli",
".:build:clean": "rimraf dist",
"build": "run-s .:build:clean .:build:bundle",
"bench": "ts-node --compiler-options '{\"module\":\"commonjs\"}' -r ./loaders/pegjs-register.js benchmarks/parser.bench.ts",
"bench": "ts-node --compiler-options '{\"module\":\"commonjs\"}' benchmarks/parser.bench.ts",
"bench:size": "node -e \"const fs=require('fs'),zlib=require('zlib'),p='dist/messageParser.js';try{const s=fs.statSync(p),c=fs.readFileSync(p),g=zlib.gzipSync(c);console.log('Bundle:',p);console.log('Raw:',s.size,'bytes ('+(s.size/1024).toFixed(1),'KB)');console.log('Gzip:',g.length,'bytes ('+(g.length/1024).toFixed(1),'KB)')}catch(e){console.error('Build first: yarn build');process.exit(1)}\"",
"lint": "eslint .",
"test": "jest",
Expand All @@ -47,17 +47,14 @@
},
"devDependencies": {
"@rocket.chat/jest-presets": "workspace:~",
"@rocket.chat/peggy-loader": "workspace:~",
"@rocket.chat/prettier-config": "~0.31.25",
"@types/jest": "~30.0.0",
"@types/node": "~22.19.21",
"eslint": "~9.39.5",
"fast-check": "^4.6.0",
"jest": "~30.2.0",
"npm-run-all": "^4.1.5",
"peggy": "4.1.1",
"prettier": "~3.3.3",
"prettier-plugin-pegjs": "~0.5.4",
"rimraf": "^6.0.1",
"tinybench": "^3.0.7",
"ts-loader": "~9.5.7",
Expand Down
100 changes: 100 additions & 0 deletions packages/message-parser/src/chars.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
export function isNewline(ch: string): boolean {
return ch === '\n' || ch === '\r';
}

export function isSpace(ch: string): boolean {
return ch === ' ' || ch === '\t';
}

export function isWhitespace(ch: string): boolean {
return isSpace(ch) || isNewline(ch);
}

export function isDigit(ch: string): boolean {
return ch >= '0' && ch <= '9';
}

export function isAlpha(ch: string): boolean {
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
}

export function isAlphaNum(ch: string): boolean {
return isAlpha(ch) || isDigit(ch);
}

export function isMarkupChar(ch: string): boolean {
return '*_~`#@:|\\[!<$+()'.includes(ch);
}

export function isPlainChar(ch: string): boolean {
return ch !== '' && !isNewline(ch) && !isMarkupChar(ch) && !isSpace(ch);
}

export function isUrlStart(ch: string): boolean {
return isAlpha(ch) || isDigit(ch);
}

export function isEmailStart(ch: string): boolean {
return isUrlStart(ch) || ch.charCodeAt(0) > 127;
}

export function isEmojiStart(ch: string): boolean {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
const code = ch.charCodeAt(0);
return (
code === 0xa9 ||
code === 0xae ||
code === 0x203c ||
code === 0x2049 ||
code === 0x2122 ||
code === 0x2139 ||
(code >= 0x2194 && code <= 0x21aa) || // Arrows
(code >= 0x231a && code <= 0x23fa) || // Misc Technical
code === 0x24c2 ||
(code >= 0x25aa && code <= 0x27bf) || // Geometric Shapes, Symbols, Dingbats
(code >= 0x2934 && code <= 0x2b55) || // Supplemental Arrows
code === 0x3030 ||
code === 0x303d ||
code === 0x3297 ||
code === 0x3299 ||
(code >= 0xd800 && code <= 0xdbff) || // High surrogates (U+10000+ emoji)
ch === '#' || // keycap bases
ch === '*' ||
(ch >= '0' && ch <= '9')
);
}

export function isHexDigit(ch: string): boolean {
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
}

// ─── Emoticon ──────────────────────────────────────────────────────────────
export const EMOTICONS: Record<string, string> = {
':)': 'slight_smile',
':-)': 'slight_smile',
':(': 'frowning',
':-(': 'frowning',
'D:': 'fearful',
':D': 'grinning',
':-D': 'grinning',
':P': 'stuck_out_tongue',
':-P': 'stuck_out_tongue',
':p': 'stuck_out_tongue',
':-p': 'stuck_out_tongue',
';)': 'wink',
';-)': 'wink',
':o': 'open_mouth',
':-o': 'open_mouth',
':O': 'open_mouth',
':-O': 'open_mouth',
':|': 'neutral_face',
':-|': 'neutral_face',
':/': 'confused',
':-/': 'confused',
':\\': 'confused',
':-\\': 'confused',
':*': 'kissing_heart',
'-_-': 'expressionless',
};

// Sorted longest-first so e.g. ":-D" wins over ":D"
export const EMOTICON_KEYS = Object.keys(EMOTICONS).sort((a, b) => b.length - a.length);
Loading