feat(parse-mode): compile-time formatted string compatibility check
This commit is contained in:
parent
28baf50958
commit
d8111ea525
5 changed files with 44 additions and 26 deletions
|
@ -41,12 +41,12 @@ export interface IMessageEntityParser {
|
||||||
* Raw string that will not be escaped when passing
|
* Raw string that will not be escaped when passing
|
||||||
* to tagged template helpers (like `html` and `md`)
|
* to tagged template helpers (like `html` and `md`)
|
||||||
*/
|
*/
|
||||||
export class FormattedString {
|
export class FormattedString<T extends string = never> {
|
||||||
/**
|
/**
|
||||||
* @param value Value that the string holds
|
* @param value Value that the string holds
|
||||||
* @param mode Name of the parse mode used
|
* @param mode Name of the parse mode used
|
||||||
*/
|
*/
|
||||||
constructor (readonly value: string, readonly mode?: string) {}
|
constructor (readonly value: string, readonly mode?: T) {}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return this.value
|
return this.value
|
||||||
|
|
|
@ -20,8 +20,8 @@ const MENTION_REGEX =
|
||||||
*/
|
*/
|
||||||
export function html(
|
export function html(
|
||||||
strings: TemplateStringsArray,
|
strings: TemplateStringsArray,
|
||||||
...sub: (string | FormattedString)[]
|
...sub: (string | FormattedString<'html'>)[]
|
||||||
): FormattedString {
|
): FormattedString<'html'> {
|
||||||
let str = ''
|
let str = ''
|
||||||
sub.forEach((it, idx) => {
|
sub.forEach((it, idx) => {
|
||||||
if (typeof it === 'string') it = HtmlMessageEntityParser.escape(it)
|
if (typeof it === 'string') it = HtmlMessageEntityParser.escape(it)
|
||||||
|
|
|
@ -611,6 +611,8 @@ describe('HtmlMessageEntityParser', () => {
|
||||||
const unsafeString2 = new FormattedString('<&>', 'some-other-mode')
|
const unsafeString2 = new FormattedString('<&>', 'some-other-mode')
|
||||||
|
|
||||||
expect(() => html`${unsafeString}`.value).not.throw(Error)
|
expect(() => html`${unsafeString}`.value).not.throw(Error)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
expect(() => html`${unsafeString2}`.value).throw(Error)
|
expect(() => html`${unsafeString2}`.value).throw(Error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,8 @@ import { tl } from '@mtcute/tl'
|
||||||
import { FormattedString } from '@mtcute/client'
|
import { FormattedString } from '@mtcute/client'
|
||||||
import Long from 'long'
|
import Long from 'long'
|
||||||
|
|
||||||
const MENTION_REGEX = /^tg:\/\/user\?id=(\d+)(?:&hash=(-?[0-9a-fA-F]+)(?:&|$)|&|$)/
|
const MENTION_REGEX =
|
||||||
|
/^tg:\/\/user\?id=(\d+)(?:&hash=(-?[0-9a-fA-F]+)(?:&|$)|&|$)/
|
||||||
|
|
||||||
const TAG_BOLD = '**'
|
const TAG_BOLD = '**'
|
||||||
const TAG_ITALIC = '__'
|
const TAG_ITALIC = '__'
|
||||||
|
@ -22,12 +23,17 @@ const TO_BE_ESCAPED = /[*_\-~`[\\\]]/g
|
||||||
* const escaped = md`**${user.displayName}**`
|
* const escaped = md`**${user.displayName}**`
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export function md(strings: TemplateStringsArray, ...sub: (string | FormattedString)[]): FormattedString {
|
export function md(
|
||||||
|
strings: TemplateStringsArray,
|
||||||
|
...sub: (string | FormattedString<'markdown'>)[]
|
||||||
|
): FormattedString<'markdown'> {
|
||||||
let str = ''
|
let str = ''
|
||||||
sub.forEach((it, idx) => {
|
sub.forEach((it, idx) => {
|
||||||
if (typeof it === 'string') it = MarkdownMessageEntityParser.escape(it as string)
|
if (typeof it === 'string')
|
||||||
|
it = MarkdownMessageEntityParser.escape(it as string)
|
||||||
else {
|
else {
|
||||||
if (it.mode && it.mode !== 'markdown') throw new Error(`Incompatible parse mode: ${it.mode}`)
|
if (it.mode && it.mode !== 'markdown')
|
||||||
|
throw new Error(`Incompatible parse mode: ${it.mode}`)
|
||||||
it = it.value
|
it = it.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +132,9 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
|
||||||
if (text[pos + 1] !== '(') {
|
if (text[pos + 1] !== '(') {
|
||||||
// [link text]
|
// [link text]
|
||||||
// ignore this, and add opening [
|
// ignore this, and add opening [
|
||||||
result = `${result.substr(0, ent.offset)}[${result.substr(ent.offset)}]`
|
result = `${result.substr(0, ent.offset)}[${result.substr(
|
||||||
|
ent.offset
|
||||||
|
)}]`
|
||||||
pos += 1
|
pos += 1
|
||||||
insideLink = false
|
insideLink = false
|
||||||
continue
|
continue
|
||||||
|
@ -151,24 +159,34 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
|
||||||
const userId = parseInt(m[1])
|
const userId = parseInt(m[1])
|
||||||
const accessHash = m[2]
|
const accessHash = m[2]
|
||||||
if (accessHash) {
|
if (accessHash) {
|
||||||
;(ent as tl.Mutable<tl.RawInputMessageEntityMentionName>)._ =
|
;(
|
||||||
'inputMessageEntityMentionName'
|
ent as tl.Mutable<tl.RawInputMessageEntityMentionName>
|
||||||
;(ent as tl.Mutable<tl.RawInputMessageEntityMentionName>).userId = {
|
)._ = 'inputMessageEntityMentionName'
|
||||||
|
;(
|
||||||
|
ent as tl.Mutable<tl.RawInputMessageEntityMentionName>
|
||||||
|
).userId = {
|
||||||
_: 'inputUser',
|
_: 'inputUser',
|
||||||
userId,
|
userId,
|
||||||
accessHash: Long.fromString(accessHash, false,16),
|
accessHash: Long.fromString(
|
||||||
|
accessHash,
|
||||||
|
false,
|
||||||
|
16
|
||||||
|
),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
;(ent as tl.Mutable<tl.RawMessageEntityMentionName>)._ =
|
;(
|
||||||
'messageEntityMentionName'
|
ent as tl.Mutable<tl.RawMessageEntityMentionName>
|
||||||
;(ent as tl.Mutable<tl.RawMessageEntityMentionName>).userId = userId
|
)._ = 'messageEntityMentionName'
|
||||||
|
;(
|
||||||
|
ent as tl.Mutable<tl.RawMessageEntityMentionName>
|
||||||
|
).userId = userId
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (url.match(/^\/\//)) url = 'http:' + url
|
if (url.match(/^\/\//)) url = 'http:' + url
|
||||||
|
|
||||||
;(ent as tl.Mutable<tl.RawMessageEntityTextUrl>)._ =
|
;(ent as tl.Mutable<tl.RawMessageEntityTextUrl>)._ =
|
||||||
'messageEntityTextUrl'
|
'messageEntityTextUrl'
|
||||||
;(ent as tl.Mutable<tl.RawMessageEntityTextUrl>).url = url
|
;(ent as tl.Mutable<tl.RawMessageEntityTextUrl>).url =
|
||||||
|
url
|
||||||
}
|
}
|
||||||
entities.push(ent)
|
entities.push(ent)
|
||||||
}
|
}
|
||||||
|
@ -230,12 +248,8 @@ export class MarkdownMessageEntityParser implements IMessageEntityParser {
|
||||||
|
|
||||||
if (c === text[pos + 1]) {
|
if (c === text[pos + 1]) {
|
||||||
// maybe (?) start or end of an entity
|
// maybe (?) start or end of an entity
|
||||||
let type:
|
let type: 'Italic' | 'Bold' | 'Underline' | 'Strike' | null =
|
||||||
| 'Italic'
|
null
|
||||||
| 'Bold'
|
|
||||||
| 'Underline'
|
|
||||||
| 'Strike'
|
|
||||||
| null = null
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '_':
|
case '_':
|
||||||
type = 'Italic'
|
type = 'Italic'
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { expect } from 'chai'
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
import { MessageEntity, FormattedString } from '@mtcute/client'
|
import { MessageEntity, FormattedString } from '@mtcute/client'
|
||||||
import { MarkdownMessageEntityParser, md } from '../src'
|
import { MarkdownMessageEntityParser, md } from '../src'
|
||||||
import bigInt from 'big-integer'
|
import Long from 'long'
|
||||||
|
|
||||||
const createEntity = <T extends tl.TypeMessageEntity['_']>(
|
const createEntity = <T extends tl.TypeMessageEntity['_']>(
|
||||||
type: T,
|
type: T,
|
||||||
|
@ -356,7 +356,7 @@ describe('MarkdownMessageEntityParser', () => {
|
||||||
userId: {
|
userId: {
|
||||||
_: 'inputUser',
|
_: 'inputUser',
|
||||||
userId: 1234567,
|
userId: 1234567,
|
||||||
accessHash: bigInt('aabbccddaabbccdd', 16),
|
accessHash: Long.fromString('aabbccddaabbccdd', 16),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
@ -674,6 +674,8 @@ describe('MarkdownMessageEntityParser', () => {
|
||||||
const unsafeString2 = new FormattedString('<&>', 'some-other-mode')
|
const unsafeString2 = new FormattedString('<&>', 'some-other-mode')
|
||||||
|
|
||||||
expect(() => md`${unsafeString}`.value).not.throw(Error)
|
expect(() => md`${unsafeString}`.value).not.throw(Error)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
expect(() => md`${unsafeString2}`.value).throw(Error)
|
expect(() => md`${unsafeString2}`.value).throw(Error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue