From 980422f654fb510c3ac44e138520ceb323af365d Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 22 May 2024 15:21:07 +0900 Subject: [PATCH 01/15] lint: fix lint warning that is easy to fix --- .eslintrc.js | 11 +++++++++-- src/cli/parse.ts | 2 +- src/cli/parseSimple.ts | 2 +- src/internal/core/index.ts | 2 +- src/internal/parser.ts | 36 ++++++++++++++++++------------------ src/internal/util.ts | 6 +++--- 6 files changed, 33 insertions(+), 26 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 92d542fe..4f9858be 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -43,14 +43,21 @@ module.exports = { 'prefer-arrow-callback': ['error'], 'no-throw-literal': ['error'], 'no-param-reassign': ['warn'], - 'no-constant-condition': ['warn'], + 'no-constant-condition': ['warn', { + checkLoops: false, + }], 'no-empty-pattern': ['warn'], - '@typescript-eslint/no-unnecessary-condition': ['warn'], + '@typescript-eslint/no-unnecessary-condition': ['warn', { + allowConstantLoopConditions: true, + }], '@typescript-eslint/no-inferrable-types': ['warn'], '@typescript-eslint/no-non-null-assertion': ['warn'], '@typescript-eslint/explicit-function-return-type': ['warn'], '@typescript-eslint/no-misused-promises': ['error', { 'checksVoidReturn': false, }], + '@typescript-eslint/no-unused-vars': ['error', { + "argsIgnorePattern": "^_", + }] }, }; diff --git a/src/cli/parse.ts b/src/cli/parse.ts index 120df082..b37e7ae8 100644 --- a/src/cli/parse.ts +++ b/src/cli/parse.ts @@ -2,7 +2,7 @@ import { performance } from 'perf_hooks'; import inputLine, { InputCanceledError } from './misc/inputLine'; import { parse } from '..'; -async function entryPoint() { +async function entryPoint(): Promise { console.log('intaractive parser'); while (true) { diff --git a/src/cli/parseSimple.ts b/src/cli/parseSimple.ts index 41674b4f..e01b299d 100644 --- a/src/cli/parseSimple.ts +++ b/src/cli/parseSimple.ts @@ -2,7 +2,7 @@ import { performance } from 'perf_hooks'; import inputLine, { InputCanceledError } from './misc/inputLine'; import { parseSimple } from '..'; -async function entryPoint() { +async function entryPoint(): Promise { console.log('intaractive simple parser'); while (true) { diff --git a/src/internal/core/index.ts b/src/internal/core/index.ts index 2241d507..b960190f 100644 --- a/src/internal/core/index.ts +++ b/src/internal/core/index.ts @@ -31,7 +31,7 @@ export class Parser { public handler: ParserHandler; constructor(handler: ParserHandler, name?: string) { - this.handler = (input, index, state) => { + this.handler = (input, index, state) : Failure | Success => { if (state.trace && this.name != null) { const pos = `${index}`; console.log(`${pos.padEnd(6, ' ')}enter ${this.name}`); diff --git a/src/internal/parser.ts b/src/internal/parser.ts index 20e0072e..f5466ac8 100644 --- a/src/internal/parser.ts +++ b/src/internal/parser.ts @@ -222,7 +222,7 @@ export const language = P.createLanguage({ }); }, - codeBlock: r => { + codeBlock: () => { const mark = P.str('```'); return P.seq([ newLine.option(), @@ -242,7 +242,7 @@ export const language = P.createLanguage({ }); }, - mathBlock: r => { + mathBlock: () => { const open = P.str('\\['); const close = P.str('\\]'); return P.seq([ @@ -316,7 +316,7 @@ export const language = P.createLanguage({ }); }, - boldUnder: r => { + boldUnder: () => { const mark = P.str('__'); return P.seq([ mark, @@ -351,7 +351,7 @@ export const language = P.createLanguage({ }); }, - italicAsta: r => { + italicAsta: () => { const mark = P.str('*'); const parser = P.seq([ mark, @@ -372,7 +372,7 @@ export const language = P.createLanguage({ }); }, - italicUnder: r => { + italicUnder: () => { const mark = P.str('_'); const parser = P.seq([ mark, @@ -418,12 +418,12 @@ export const language = P.createLanguage({ }); }, - unicodeEmoji: r => { + unicodeEmoji: () => { const emoji = RegExp(twemojiRegex.source); return P.regexp(emoji).map(content => M.UNI_EMOJI(content)); }, - plainTag: r => { + plainTag: () => { const open = P.str(''); const close = P.str(''); return P.seq([ @@ -485,7 +485,7 @@ export const language = P.createLanguage({ }); }, - inlineCode: r => { + inlineCode: () => { const mark = P.str('`'); return P.seq([ mark, @@ -497,7 +497,7 @@ export const language = P.createLanguage({ ]).map(result => M.INLINE_CODE(result[1].join(''))); }, - mathInline: r => { + mathInline: () => { const open = P.str('\\('); const close = P.str('\\)'); return P.seq([ @@ -510,7 +510,7 @@ export const language = P.createLanguage({ ]).map(result => M.MATH_INLINE(result[1].join(''))); }, - mention: r => { + mention: () => { const parser = P.seq([ notLinkLabel, P.str('@'), @@ -576,13 +576,13 @@ export const language = P.createLanguage({ }); }, - hashtag: r => { + hashtag: () => { const mark = P.str('#'); const hashTagChar = P.seq([ P.notMatch(P.alt([P.regexp(/[ \u3000\t.,!?'"#:/[\]【】()「」()<>]/), space, newLine])), P.char, ], 1); - const innerItem: P.Parser = P.lazy(() => P.alt([ + const innerItem: P.Parser = P.lazy(() => P.alt([ P.seq([ P.str('('), nest(innerItem, hashTagChar).many(0), P.str(')'), ]), @@ -622,7 +622,7 @@ export const language = P.createLanguage({ }); }, - emojiCode: r => { + emojiCode: () => { const side = P.notMatch(P.regexp(/[a-z0-9]/i)); const mark = P.str(':'); return P.seq([ @@ -661,9 +661,9 @@ export const language = P.createLanguage({ }); }, - url: r => { + url: () => { const urlChar = P.regexp(/[.,a-z0-9_/:%#@$&?!~=+-]/i); - const innerItem: P.Parser = P.lazy(() => P.alt([ + const innerItem: P.Parser = P.lazy(() => P.alt([ P.seq([ P.str('('), nest(innerItem, urlChar).many(0), P.str(')'), ]), @@ -700,7 +700,7 @@ export const language = P.createLanguage({ }); }, - urlAlt: r => { + urlAlt: () => { const open = P.str('<'); const close = P.str('>'); const parser = P.seq([ @@ -720,7 +720,7 @@ export const language = P.createLanguage({ }); }, - search: r => { + search: () => { const button = P.alt([ P.regexp(/\[(検索|search)\]/i), P.regexp(/(検索|search)/i), @@ -745,5 +745,5 @@ export const language = P.createLanguage({ }); }, - text: r => P.char, + text: () => P.char, }); diff --git a/src/internal/util.ts b/src/internal/util.ts index 3f0cffcd..e772b5b2 100644 --- a/src/internal/util.ts +++ b/src/internal/util.ts @@ -7,7 +7,7 @@ export function mergeText(nodes: ((T extends MfmInline ? MfmI /** * Generate a text node from the stored chars, And push it. */ - function generateText() { + function generateText(): void { if (storedChars.length > 0) { dest.push(TEXT(storedChars.join(''))); storedChars.length = 0; @@ -136,7 +136,7 @@ export function stringifyTree(nodes: MfmNode[]): string { // block -> inline : Yes // block -> block : Yes - let pushLf: boolean = true; + let pushLf = true; if (isMfmBlock(node)) { if (state === stringifyState.none) { pushLf = false; @@ -159,7 +159,7 @@ export function stringifyTree(nodes: MfmNode[]): string { return dest.map(n => stringifyNode(n)).join(''); } -export function inspectOne(node: MfmNode, action: (node: MfmNode) => void) { +export function inspectOne(node: MfmNode, action: (node: MfmNode) => void): void { action(node); if (node.children != null) { for (const child of node.children) { From 4f227743c577ec69fdc80eb77876aa06581817bb Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 22 May 2024 15:36:56 +0900 Subject: [PATCH 02/15] lint: typesafe signature of seqOrText --- src/internal/parser.ts | 49 ++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/internal/parser.ts b/src/internal/parser.ts index f5466ac8..d6c8f29f 100644 --- a/src/internal/parser.ts +++ b/src/internal/parser.ts @@ -16,9 +16,15 @@ const space = P.regexp(/[\u0020\u3000\t]/); const alphaAndNum = P.regexp(/[a-z0-9]/i); const newLine = P.alt([P.crlf, P.cr, P.lf]); -function seqOrText(parsers: P.Parser[]): P.Parser { - return new P.Parser((input, index, state) => { - const accum: any[] = []; +type SeqParseResult = + T extends [P.Parser, ...infer R] ? [A, ...SeqParseResult] + : T extends [] ? [] + : unknown[]; + +function seqOrText[]>(...parsers: Parsers): P.Parser | string> { + return new P.Parser | string>((input, index, state) => { + // TODO: typesafe implementation + const accum: unknown[] = []; let latestIndex = index; for (let i = 0 ; i < parsers.length; i++) { const result = parsers[i].handler(input, latestIndex, state); @@ -32,7 +38,7 @@ function seqOrText(parsers: P.Parser[]): P.Parser { accum.push(result.value); latestIndex = result.index; } - return P.success(latestIndex, accum); + return P.success(latestIndex, accum as SeqParseResult); }); } @@ -281,11 +287,11 @@ export const language = P.createLanguage({ big: r => { const mark = P.str('***'); - return seqOrText([ + return seqOrText( mark, P.seq([P.notMatch(mark), nest(r.inline)], 1).many(1), mark, - ]).map(result => { + ).map(result => { if (typeof result === 'string') return result; return M.FN('tada', {}, mergeText(result[1])); }); @@ -293,11 +299,11 @@ export const language = P.createLanguage({ boldAsta: r => { const mark = P.str('**'); - return seqOrText([ + return seqOrText( mark, P.seq([P.notMatch(mark), nest(r.inline)], 1).many(1), mark, - ]).map(result => { + ).map(result => { if (typeof result === 'string') return result; return M.BOLD(mergeText(result[1] as (M.MfmInline | string)[])); }); @@ -306,11 +312,11 @@ export const language = P.createLanguage({ boldTag: r => { const open = P.str(''); const close = P.str(''); - return seqOrText([ + return seqOrText( open, P.seq([P.notMatch(close), nest(r.inline)], 1).many(1), close, - ]).map(result => { + ).map(result => { if (typeof result === 'string') return result; return M.BOLD(mergeText(result[1] as (M.MfmInline | string)[])); }); @@ -328,11 +334,11 @@ export const language = P.createLanguage({ smallTag: r => { const open = P.str(''); const close = P.str(''); - return seqOrText([ + return seqOrText( open, P.seq([P.notMatch(close), nest(r.inline)], 1).many(1), close, - ]).map(result => { + ).map(result => { if (typeof result === 'string') return result; return M.SMALL(mergeText(result[1] as (M.MfmInline | string)[])); }); @@ -341,11 +347,11 @@ export const language = P.createLanguage({ italicTag: r => { const open = P.str(''); const close = P.str(''); - return seqOrText([ + return seqOrText( open, P.seq([P.notMatch(close), nest(r.inline)], 1).many(1), close, - ]).map(result => { + ).map(result => { if (typeof result === 'string') return result; return M.ITALIC(mergeText(result[1] as (M.MfmInline | string)[])); }); @@ -396,11 +402,11 @@ export const language = P.createLanguage({ strikeTag: r => { const open = P.str(''); const close = P.str(''); - return seqOrText([ + return seqOrText( open, P.seq([P.notMatch(close), nest(r.inline)], 1).many(1), close, - ]).map(result => { + ).map(result => { if (typeof result === 'string') return result; return M.STRIKE(mergeText(result[1] as (M.MfmInline | string)[])); }); @@ -408,11 +414,11 @@ export const language = P.createLanguage({ strikeWave: r => { const mark = P.str('~~'); - return seqOrText([ + return seqOrText( mark, P.seq([P.notMatch(P.alt([mark, newLine])), nest(r.inline)], 1).many(1), mark, - ]).map(result => { + ).map(result => { if (typeof result === 'string') return result; return M.STRIKE(mergeText(result[1] as (M.MfmInline | string)[])); }); @@ -469,17 +475,18 @@ export const language = P.createLanguage({ return result; }); const fnClose = P.str(']'); - return seqOrText([ + return seqOrText( P.str('$['), fnName, args.option(), P.str(' '), P.seq([P.notMatch(fnClose), nest(r.inline)], 1).many(1), fnClose, - ]).map(result => { + ).map(result => { if (typeof result === 'string') return result; const name = result[1]; - const args = result[2] || {}; + // @ts-expect-error the Args should be Partial, but it is not. + const args: Args = result[2] || {}; const content = result[4]; return M.FN(name, args, mergeText(content)); }); From 76be17f207bb7dfd0fc30ee844d462c06e9f6744 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 22 May 2024 16:24:40 +0900 Subject: [PATCH 03/15] lint: typesafe createLanguage and language --- src/internal/core/index.ts | 13 ++++++++----- src/internal/parser.ts | 38 +++++++++++++++++++++++++++++++++++++- src/internal/util.ts | 4 +++- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/internal/core/index.ts b/src/internal/core/index.ts index b960190f..3a36ae9c 100644 --- a/src/internal/core/index.ts +++ b/src/internal/core/index.ts @@ -232,12 +232,15 @@ export function lazy(fn: () => Parser): Parser { //type SyntaxReturn = T extends (rules: Record>) => infer R ? R : never; //export function createLanguage2>>(syntaxes: T): { [K in keyof T]: SyntaxReturn } { +type ParserTable = { [K in keyof T]: Parser }; + // TODO: 関数の型宣言をいい感じにしたい -export function createLanguage(syntaxes: { [K in keyof T]: (r: Record>) => T[K] }): T { - const rules: Record> = {}; - for (const key of Object.keys(syntaxes)) { +export function createLanguage(syntaxes: { [K in keyof T]: (r: ParserTable) => Parser }): ParserTable { + // @ts-expect-error initializing object so type error here + const rules: ParserTable = {}; + for (const key of Object.keys(syntaxes) as (keyof T & string)[]) { rules[key] = lazy(() => { - const parser = (syntaxes as any)[key](rules); + const parser = syntaxes[key](rules); if (parser == null) { throw new Error('syntax must return a parser.'); } @@ -245,5 +248,5 @@ export function createLanguage(syntaxes: { [K in keyof T]: (r: Record(parser: P.Parser, fallback?: P.Parser): P.Parser, + codeBlock: NodeType<'blockCode'>, + mathBlock: NodeType<'mathBlock'>, + centerTag: NodeType<'center'>, + big: NodeType<'fn'> | string, + boldAsta: NodeType<'bold'> | string, + boldTag: NodeType<'bold'> | string, + boldUnder: NodeType<'bold'>, + smallTag: NodeType<'small'> | string, + italicTag: NodeType<'italic'> | string, + italicAsta: NodeType<'italic'>, + italicUnder: NodeType<'italic'>, + strikeTag: NodeType<'strike'> | string, + strikeWave: NodeType<'strike'> | string, + unicodeEmoji: NodeType<'unicodeEmoji'>, + plainTag: NodeType<'plain'>, + fn: NodeType<'fn'> | string, + inlineCode: NodeType<'inlineCode'>, + mathInline: NodeType<'mathInline'>, + mention: NodeType<'mention'> | string, + hashtag: NodeType<'hashtag'> | string, + emojiCode: NodeType<'emojiCode'>, + link: NodeType<'link'>, + url: NodeType<'url'> | string, + urlAlt: NodeType<'url'> | string, + search: NodeType<'search'>, + text: string, +} + +export const language = P.createLanguage({ fullParser: r => { return r.full.many(0); }, diff --git a/src/internal/util.ts b/src/internal/util.ts index e772b5b2..4c3935b3 100644 --- a/src/internal/util.ts +++ b/src/internal/util.ts @@ -1,6 +1,8 @@ import { isMfmBlock, MfmInline, MfmNode, MfmText, TEXT } from '../node'; -export function mergeText(nodes: ((T extends MfmInline ? MfmInline : MfmNode) | string)[]): (T | MfmText)[] { +type ArrayRecursive = T | Array>; + +export function mergeText(nodes: ArrayRecursive<((T extends MfmInline ? MfmInline : MfmNode) | string)>[]): (T | MfmText)[] { const dest: (T | MfmText)[] = []; const storedChars: string[] = []; From 835bfd4e39ba2646ab75e74617692616d616895d Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 22 May 2024 17:48:46 +0900 Subject: [PATCH 04/15] lint: typesafe seq --- src/internal/core/index.ts | 28 ++++-- src/internal/parser.ts | 188 ++++++++++++++++++------------------- 2 files changed, 113 insertions(+), 103 deletions(-) diff --git a/src/internal/core/index.ts b/src/internal/core/index.ts index 3a36ae9c..da1ae8db 100644 --- a/src/internal/core/index.ts +++ b/src/internal/core/index.ts @@ -91,17 +91,21 @@ export class Parser { }); } - sep(separator: Parser, min: number): Parser { + sep(separator: Parser, min: number): Parser { if (min < 1) { throw new Error('"min" must be a value greater than or equal to 1.'); } - return seq([ + return seq( this, - seq([ + seq( separator, this, - ], 1).many(min - 1), - ]).map(result => [result[0], ...result[1]]); + ).select(1).many(min - 1), + ).map(result => [result[0], ...result[1]]); + } + + select(key: K): Parser { + return this.map(v => v[key]); } option(): Parser { @@ -136,7 +140,17 @@ export function regexp(pattern: T): Parser { }); } -export function seq(parsers: Parser[], select?: number): Parser { +type ParsedType> = T extends Parser ? U : never; + +export type SeqParseResult = + T extends [] ? [] + : T extends [infer F, ...infer R] + ? ( + F extends Parser ? [ParsedType, ...SeqParseResult] : [unknown, ...SeqParseResult] + ) + : unknown[]; + +export function seq[]>(...parsers: Parsers): Parser> { return new Parser((input, index, state) => { let result; let latestIndex = index; @@ -149,7 +163,7 @@ export function seq(parsers: Parser[], select?: number): Parser { latestIndex = result.index; accum.push(result.value); } - return success(latestIndex, (select != null ? accum[select] : accum)); + return success(latestIndex, accum as SeqParseResult); }); } diff --git a/src/internal/parser.ts b/src/internal/parser.ts index 2b2e87dc..ef6a04a0 100644 --- a/src/internal/parser.ts +++ b/src/internal/parser.ts @@ -2,6 +2,7 @@ import * as M from '..'; import * as P from './core'; import { mergeText } from './util'; import { MfmInline, MfmNode, NodeType } from '..'; +import { SeqParseResult } from './core'; // NOTE: // tsdのテストでファイルを追加しているにも関わらず「@twemoji/parser/dist/lib/regex」の型定義ファイルがないとエラーが出るため、 @@ -17,11 +18,6 @@ const space = P.regexp(/[\u0020\u3000\t]/); const alphaAndNum = P.regexp(/[a-z0-9]/i); const newLine = P.alt([P.crlf, P.cr, P.lf]); -type SeqParseResult = - T extends [P.Parser, ...infer R] ? [A, ...SeqParseResult] - : T extends [] ? [] - : unknown[]; - function seqOrText[]>(...parsers: Parsers): P.Parser | string> { return new P.Parser | string>((input, index, state) => { // TODO: typesafe implementation @@ -58,7 +54,7 @@ const nestable = new P.Parser((_input, index, state) => { function nest(parser: P.Parser, fallback?: P.Parser): P.Parser { // nesting limited? -> No: specified parser, Yes: fallback parser (default = P.char) const inner = P.alt([ - P.seq([nestable, parser], 1), + P.seq(nestable, parser).select(1), (fallback != null) ? fallback : P.char, ]); return new P.Parser((input, index, state) => { @@ -228,19 +224,19 @@ export const language = P.createLanguage({ }, quote: r => { - const lines: P.Parser = P.seq([ + const lines: P.Parser = P.seq( P.str('>'), space.option(), - P.seq([P.notMatch(newLine), P.char], 1).many(0).text(), - ], 2).sep(newLine, 1); - const parser = P.seq([ + P.seq(P.notMatch(newLine), P.char).select(1).many(0).text(), + ).select(2).sep(newLine, 1); + const parser = P.seq( newLine.option(), newLine.option(), P.lineBegin, lines, newLine.option(), newLine.option(), - ], 3); + ).select(3); return new P.Parser((input, index, state) => { let result; // parse quote @@ -266,20 +262,20 @@ export const language = P.createLanguage({ codeBlock: () => { const mark = P.str('```'); - return P.seq([ + return P.seq( newLine.option(), P.lineBegin, mark, - P.seq([P.notMatch(newLine), P.char], 1).many(0), + P.seq(P.notMatch(newLine), P.char).select(1).many(0), newLine, - P.seq([P.notMatch(P.seq([newLine, mark, P.lineEnd])), P.char], 1).many(1), + P.seq(P.notMatch(P.seq(newLine, mark, P.lineEnd)), P.char).select(1).many(1), newLine, mark, P.lineEnd, newLine.option(), - ]).map(result => { - const lang = (result[3] as string[]).join('').trim(); - const code = (result[5] as string[]).join(''); + ).map(result => { + const lang = result[3].join('').trim(); + const code = result[5].join(''); return M.CODE_BLOCK(code, (lang.length > 0 ? lang : null)); }); }, @@ -287,18 +283,18 @@ export const language = P.createLanguage({ mathBlock: () => { const open = P.str('\\['); const close = P.str('\\]'); - return P.seq([ + return P.seq( newLine.option(), P.lineBegin, open, newLine.option(), - P.seq([P.notMatch(P.seq([newLine.option(), close])), P.char], 1).many(1), + P.seq(P.notMatch(P.seq(newLine.option(), close)), P.char).select(1).many(1), newLine.option(), close, P.lineEnd, newLine.option(), - ]).map(result => { - const formula = (result[4] as string[]).join(''); + ).map(result => { + const formula = result[4].join(''); return M.MATH_BLOCK(formula); }); }, @@ -306,17 +302,17 @@ export const language = P.createLanguage({ centerTag: r => { const open = P.str('
'); const close = P.str('
'); - return P.seq([ + return P.seq( newLine.option(), P.lineBegin, open, newLine.option(), - P.seq([P.notMatch(P.seq([newLine.option(), close])), nest(r.inline)], 1).many(1), + P.seq(P.notMatch(P.seq(newLine.option(), close)), nest(r.inline)).select(1).many(1), newLine.option(), close, P.lineEnd, newLine.option(), - ]).map(result => { + ).map(result => { return M.CENTER(mergeText(result[4])); }); }, @@ -325,7 +321,7 @@ export const language = P.createLanguage({ const mark = P.str('***'); return seqOrText( mark, - P.seq([P.notMatch(mark), nest(r.inline)], 1).many(1), + P.seq(P.notMatch(mark), nest(r.inline)).select(1).many(1), mark, ).map(result => { if (typeof result === 'string') return result; @@ -337,7 +333,7 @@ export const language = P.createLanguage({ const mark = P.str('**'); return seqOrText( mark, - P.seq([P.notMatch(mark), nest(r.inline)], 1).many(1), + P.seq(P.notMatch(mark), nest(r.inline)).select(1).many(1), mark, ).map(result => { if (typeof result === 'string') return result; @@ -350,7 +346,7 @@ export const language = P.createLanguage({ const close = P.str(''); return seqOrText( open, - P.seq([P.notMatch(close), nest(r.inline)], 1).many(1), + P.seq(P.notMatch(close), nest(r.inline)).select(1).many(1), close, ).map(result => { if (typeof result === 'string') return result; @@ -360,11 +356,11 @@ export const language = P.createLanguage({ boldUnder: () => { const mark = P.str('__'); - return P.seq([ + return P.seq( mark, P.alt([alphaAndNum, space]).many(1), mark, - ]).map(result => M.BOLD(mergeText(result[1] as string[]))); + ).map(result => M.BOLD(mergeText(result[1] as string[]))); }, smallTag: r => { @@ -372,11 +368,11 @@ export const language = P.createLanguage({ const close = P.str(''); return seqOrText( open, - P.seq([P.notMatch(close), nest(r.inline)], 1).many(1), + P.seq(P.notMatch(close), nest(r.inline)).select(1).many(1), close, ).map(result => { if (typeof result === 'string') return result; - return M.SMALL(mergeText(result[1] as (M.MfmInline | string)[])); + return M.SMALL(mergeText(result[1])); }); }, @@ -385,21 +381,21 @@ export const language = P.createLanguage({ const close = P.str(''); return seqOrText( open, - P.seq([P.notMatch(close), nest(r.inline)], 1).many(1), + P.seq(P.notMatch(close), nest(r.inline)).select(1).many(1), close, ).map(result => { if (typeof result === 'string') return result; - return M.ITALIC(mergeText(result[1] as (M.MfmInline | string)[])); + return M.ITALIC(mergeText(result[1])); }); }, italicAsta: () => { const mark = P.str('*'); - const parser = P.seq([ + const parser = P.seq( mark, P.alt([alphaAndNum, space]).many(1), mark, - ]); + ); return new P.Parser((input, index, state) => { const result = parser.handler(input, index, state); if (!result.success) { @@ -416,11 +412,11 @@ export const language = P.createLanguage({ italicUnder: () => { const mark = P.str('_'); - const parser = P.seq([ + const parser = P.seq( mark, P.alt([alphaAndNum, space]).many(1), mark, - ]); + ); return new P.Parser((input, index, state) => { const result = parser.handler(input, index, state); if (!result.success) { @@ -440,11 +436,11 @@ export const language = P.createLanguage({ const close = P.str(''); return seqOrText( open, - P.seq([P.notMatch(close), nest(r.inline)], 1).many(1), + P.seq(P.notMatch(close), nest(r.inline)).select(1).many(1), close, ).map(result => { if (typeof result === 'string') return result; - return M.STRIKE(mergeText(result[1] as (M.MfmInline | string)[])); + return M.STRIKE(mergeText(result[1])); }); }, @@ -452,7 +448,7 @@ export const language = P.createLanguage({ const mark = P.str('~~'); return seqOrText( mark, - P.seq([P.notMatch(P.alt([mark, newLine])), nest(r.inline)], 1).many(1), + P.seq(P.notMatch(P.alt([mark, newLine])), nest(r.inline)).select(1).many(1), mark, ).map(result => { if (typeof result === 'string') return result; @@ -468,16 +464,16 @@ export const language = P.createLanguage({ plainTag: () => { const open = P.str(''); const close = P.str(''); - return P.seq([ + return P.seq( open, newLine.option(), - P.seq([ - P.notMatch(P.seq([newLine.option(), close])), + P.seq( + P.notMatch(P.seq(newLine.option(), close)), P.char, - ], 1).many(1).text(), + ).select(1).many(1).text(), newLine.option(), close, - ], 2).map(result => M.PLAIN(result)); + ).select(2).map(result => M.PLAIN(result)); }, fn: r => { @@ -488,22 +484,22 @@ export const language = P.createLanguage({ } return P.success(result.index, result.value); }); - const arg: P.Parser = P.seq([ + const arg: P.Parser = P.seq( P.regexp(/[a-z0-9_]+/i), - P.seq([ + P.seq( P.str('='), P.regexp(/[a-z0-9_.-]+/i), - ], 1).option(), - ]).map(result => { + ).select(1).option() as P.Parser, + ).map(result => { return { k: result[0], v: (result[1] != null) ? result[1] : true, }; }); - const args = P.seq([ + const args = P.seq( P.str('.'), arg.sep(P.str(','), 1), - ], 1).map(pairs => { + ).select(1).map(pairs => { const result: Args = { }; for (const pair of pairs) { result[pair.k] = pair.v; @@ -516,7 +512,7 @@ export const language = P.createLanguage({ fnName, args.option(), P.str(' '), - P.seq([P.notMatch(fnClose), nest(r.inline)], 1).many(1), + P.seq(P.notMatch(fnClose), nest(r.inline)).select(1).many(1), fnClose, ).map(result => { if (typeof result === 'string') return result; @@ -530,39 +526,39 @@ export const language = P.createLanguage({ inlineCode: () => { const mark = P.str('`'); - return P.seq([ + return P.seq( mark, - P.seq([ + P.seq( P.notMatch(P.alt([mark, P.str('´'), newLine])), P.char, - ], 1).many(1), + ).select(1).many(1), mark, - ]).map(result => M.INLINE_CODE(result[1].join(''))); + ).map(result => M.INLINE_CODE(result[1].join(''))); }, mathInline: () => { const open = P.str('\\('); const close = P.str('\\)'); - return P.seq([ + return P.seq( open, - P.seq([ + P.seq( P.notMatch(P.alt([close, newLine])), P.char, - ], 1).many(1), + ).select(1).many(1), close, - ]).map(result => M.MATH_INLINE(result[1].join(''))); + ).map(result => M.MATH_INLINE(result[1].join(''))); }, mention: () => { - const parser = P.seq([ + const parser = P.seq( notLinkLabel, P.str('@'), P.regexp(/[a-z0-9_-]+/i), - P.seq([ + P.seq( P.str('@'), P.regexp(/[a-z0-9_.-]+/i), - ], 1).option(), - ]); + ).select(1).option() as P.Parser, + ); return new P.Parser((input, index, state) => { let result; result = parser.handler(input, index, state); @@ -621,30 +617,30 @@ export const language = P.createLanguage({ hashtag: () => { const mark = P.str('#'); - const hashTagChar = P.seq([ + const hashTagChar = P.seq( P.notMatch(P.alt([P.regexp(/[ \u3000\t.,!?'"#:/[\]【】()「」()<>]/), space, newLine])), P.char, - ], 1); + ).select(1); const innerItem: P.Parser = P.lazy(() => P.alt([ - P.seq([ + P.seq( P.str('('), nest(innerItem, hashTagChar).many(0), P.str(')'), - ]), - P.seq([ + ), + P.seq( P.str('['), nest(innerItem, hashTagChar).many(0), P.str(']'), - ]), - P.seq([ + ), + P.seq( P.str('「'), nest(innerItem, hashTagChar).many(0), P.str('」'), - ]), - P.seq([ + ), + P.seq( P.str('('), nest(innerItem, hashTagChar).many(0), P.str(')'), - ]), + ), hashTagChar, ])); - const parser = P.seq([ + const parser = P.seq( notLinkLabel, mark, innerItem.many(1).text(), - ], 2); + ).select(2); return new P.Parser((input, index, state) => { const result = parser.handler(input, index, state); if (!result.success) { @@ -668,13 +664,13 @@ export const language = P.createLanguage({ emojiCode: () => { const side = P.notMatch(P.regexp(/[a-z0-9]/i)); const mark = P.str(':'); - return P.seq([ + return P.seq( P.alt([P.lineBegin, side]), mark, P.regexp(/[a-z0-9_+-]+/i), mark, P.alt([P.lineEnd, side]), - ], 2).map(name => M.EMOJI_CODE(name as string)); + ).select(2).map(name => M.EMOJI_CODE(name)); }, link: r => { @@ -685,18 +681,18 @@ export const language = P.createLanguage({ return result; }); const closeLabel = P.str(']'); - return P.seq([ + return P.seq( notLinkLabel, P.alt([P.str('?['), P.str('[')]), - P.seq([ + P.seq( P.notMatch(P.alt([closeLabel, newLine])), nest(labelInline), - ], 1).many(1), + ).select(1).many(1), closeLabel, P.str('('), P.alt([r.urlAlt, r.url]), P.str(')'), - ]).map(result => { + ).map(result => { const silent = (result[1] === '?['); const label = result[2]; const url: M.MfmUrl = result[5]; @@ -707,19 +703,19 @@ export const language = P.createLanguage({ url: () => { const urlChar = P.regexp(/[.,a-z0-9_/:%#@$&?!~=+-]/i); const innerItem: P.Parser = P.lazy(() => P.alt([ - P.seq([ + P.seq( P.str('('), nest(innerItem, urlChar).many(0), P.str(')'), - ]), - P.seq([ + ), + P.seq( P.str('['), nest(innerItem, urlChar).many(0), P.str(']'), - ]), + ), urlChar, ])); - const parser = P.seq([ + const parser = P.seq( notLinkLabel, P.regexp(/https?:\/\//), innerItem.many(1).text(), - ]); + ); return new P.Parser((input, index, state) => { let result; result = parser.handler(input, index, state); @@ -746,13 +742,13 @@ export const language = P.createLanguage({ urlAlt: () => { const open = P.str('<'); const close = P.str('>'); - const parser = P.seq([ + const parser = P.seq( notLinkLabel, open, P.regexp(/https?:\/\//), - P.seq([P.notMatch(P.alt([close, space])), P.char], 1).many(1), + P.seq(P.notMatch(P.alt([close, space])), P.char).select(1).many(1), close, - ]).text(); + ).text(); return new P.Parser((input, index, state) => { const result = parser.handler(input, index, state); if (!result.success) { @@ -768,21 +764,21 @@ export const language = P.createLanguage({ P.regexp(/\[(検索|search)\]/i), P.regexp(/(検索|search)/i), ]); - return P.seq([ + return P.seq( newLine.option(), P.lineBegin, - P.seq([ + P.seq( P.notMatch(P.alt([ newLine, - P.seq([space, button, P.lineEnd]), + P.seq(space, button, P.lineEnd), ])), P.char, - ], 1).many(1), + ).select(1).many(1), space, button, P.lineEnd, newLine.option(), - ]).map(result => { + ).map(result => { const query = result[2].join(''); return M.SEARCH(query, `${query}${result[3]}${result[4]}`); }); From e1ef46e957708a8222f5d19814d865fcabd43cfa Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 22 May 2024 17:57:08 +0900 Subject: [PATCH 05/15] lint: typesafe Parser.option --- src/internal/core/index.ts | 2 +- src/internal/parser.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/internal/core/index.ts b/src/internal/core/index.ts index da1ae8db..c268f69b 100644 --- a/src/internal/core/index.ts +++ b/src/internal/core/index.ts @@ -108,7 +108,7 @@ export class Parser { return this.map(v => v[key]); } - option(): Parser { + option(): Parser { return alt([ this, succeeded(null), diff --git a/src/internal/parser.ts b/src/internal/parser.ts index ef6a04a0..4e27111e 100644 --- a/src/internal/parser.ts +++ b/src/internal/parser.ts @@ -489,7 +489,7 @@ export const language = P.createLanguage({ P.seq( P.str('='), P.regexp(/[a-z0-9_.-]+/i), - ).select(1).option() as P.Parser, + ).select(1).option(), ).map(result => { return { k: result[0], @@ -517,7 +517,6 @@ export const language = P.createLanguage({ ).map(result => { if (typeof result === 'string') return result; const name = result[1]; - // @ts-expect-error the Args should be Partial, but it is not. const args: Args = result[2] || {}; const content = result[4]; return M.FN(name, args, mergeText(content)); @@ -557,7 +556,7 @@ export const language = P.createLanguage({ P.seq( P.str('@'), P.regexp(/[a-z0-9_.-]+/i), - ).select(1).option() as P.Parser, + ).select(1).option(), ); return new P.Parser((input, index, state) => { let result; From 3ce98be453acb5d7891ea5db795fe90eec612cb2 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 22 May 2024 17:59:24 +0900 Subject: [PATCH 06/15] fix: node can be string --- src/internal/parser.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/internal/parser.ts b/src/internal/parser.ts index 4e27111e..6c45bf70 100644 --- a/src/internal/parser.ts +++ b/src/internal/parser.ts @@ -66,11 +66,11 @@ function nest(parser: P.Parser, fallback?: P.Parser): P.Parser, codeBlock: NodeType<'blockCode'>, mathBlock: NodeType<'mathBlock'>, From 92bf21e58c09a734329bbcda301f765a28d43a6d Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 22 May 2024 18:40:54 +0900 Subject: [PATCH 07/15] lint: typesafe alt --- src/internal/core/index.ts | 10 +++++----- src/internal/parser.ts | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/internal/core/index.ts b/src/internal/core/index.ts index c268f69b..5472f197 100644 --- a/src/internal/core/index.ts +++ b/src/internal/core/index.ts @@ -167,13 +167,13 @@ export function seq[]>(...parsers: Parsers): Par }); } -export function alt(parsers: Parser[]): Parser { - return new Parser((input, index, state) => { - let result; +export function alt[]>(parsers: Parsers): Parser> { + return new Parser>((input, index, state): Result> => { for (let i = 0; i < parsers.length; i++) { - result = parsers[i].handler(input, index, state); + const parser: Parsers[number] = parsers[i]; + const result = parser.handler(input, index, state); if (result.success) { - return result; + return result as Result>; } } return failure(); diff --git a/src/internal/parser.ts b/src/internal/parser.ts index 6c45bf70..9678e35a 100644 --- a/src/internal/parser.ts +++ b/src/internal/parser.ts @@ -95,7 +95,7 @@ interface TypeTable { emojiCode: NodeType<'emojiCode'>, link: NodeType<'link'>, url: NodeType<'url'> | string, - urlAlt: NodeType<'url'> | string, + urlAlt: NodeType<'url'>, search: NodeType<'search'>, text: string, } @@ -337,7 +337,7 @@ export const language = P.createLanguage({ mark, ).map(result => { if (typeof result === 'string') return result; - return M.BOLD(mergeText(result[1] as (M.MfmInline | string)[])); + return M.BOLD(mergeText(result[1])); }); }, @@ -350,7 +350,7 @@ export const language = P.createLanguage({ close, ).map(result => { if (typeof result === 'string') return result; - return M.BOLD(mergeText(result[1] as (M.MfmInline | string)[])); + return M.BOLD(mergeText(result[1])); }); }, @@ -360,7 +360,7 @@ export const language = P.createLanguage({ mark, P.alt([alphaAndNum, space]).many(1), mark, - ).map(result => M.BOLD(mergeText(result[1] as string[]))); + ).map(result => M.BOLD(mergeText(result[1]))); }, smallTag: r => { @@ -406,7 +406,7 @@ export const language = P.createLanguage({ if (/[a-z0-9]$/i.test(beforeStr)) { return P.failure(); } - return P.success(result.index, M.ITALIC(mergeText(result.value[1] as string[]))); + return P.success(result.index, M.ITALIC(mergeText(result.value[1]))); }); }, @@ -427,7 +427,7 @@ export const language = P.createLanguage({ if (/[a-z0-9]$/i.test(beforeStr)) { return P.failure(); } - return P.success(result.index, M.ITALIC(mergeText(result.value[1] as string[]))); + return P.success(result.index, M.ITALIC(mergeText(result.value[1]))); }); }, @@ -452,7 +452,7 @@ export const language = P.createLanguage({ mark, ).map(result => { if (typeof result === 'string') return result; - return M.STRIKE(mergeText(result[1] as (M.MfmInline | string)[])); + return M.STRIKE(mergeText(result[1])); }); }, From b83b175d590bcd5ca73bbbe9a407ffd0e402e72a Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 22 May 2024 18:50:57 +0900 Subject: [PATCH 08/15] fix: invalid url in link element will cause error --- src/internal/parser.ts | 20 ++++++++++++++------ test/parser.ts | 8 ++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/internal/parser.ts b/src/internal/parser.ts index 9678e35a..78939671 100644 --- a/src/internal/parser.ts +++ b/src/internal/parser.ts @@ -680,7 +680,7 @@ export const language = P.createLanguage({ return result; }); const closeLabel = P.str(']'); - return P.seq( + const parser = P.seq( notLinkLabel, P.alt([P.str('?['), P.str('[')]), P.seq( @@ -691,11 +691,19 @@ export const language = P.createLanguage({ P.str('('), P.alt([r.urlAlt, r.url]), P.str(')'), - ).map(result => { - const silent = (result[1] === '?['); - const label = result[2]; - const url: M.MfmUrl = result[5]; - return M.LINK(silent, url.props.url, mergeText(label)); + ); + return new P.Parser((input, index, state) => { + const result = parser.handler(input, index, state); + if (!result.success) { + return P.failure(); + } + + const [, prefix, label,,, url] = result.value; + + const silent = (prefix === '?['); + if (typeof url === 'string') return P.failure(); + + return P.success(result.index, M.LINK(silent, url.props.url, mergeText(label))); }); }, diff --git a/test/parser.ts b/test/parser.ts index 50ae7007..852699c0 100644 --- a/test/parser.ts +++ b/test/parser.ts @@ -1192,6 +1192,14 @@ hoge`; ]; assert.deepStrictEqual(mfm.parse(input), output); }); + + test('bad url in url part', () => { + const input = "[test](http://..)"; + const output = [ + TEXT("[test](http://..)") + ]; + assert.deepStrictEqual(mfm.parse(input), output); + }) }); describe('fn', () => { From 8540507624c5bf28ce401f687cf25fcc8bf28847 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 22 May 2024 18:57:40 +0900 Subject: [PATCH 09/15] chore: get rid of any --- src/internal/core/index.ts | 2 +- src/internal/index.ts | 7 ++++--- src/internal/parser.ts | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/internal/core/index.ts b/src/internal/core/index.ts index 5472f197..7cf26e76 100644 --- a/src/internal/core/index.ts +++ b/src/internal/core/index.ts @@ -186,7 +186,7 @@ function succeeded(value: T): Parser { }); } -export function notMatch(parser: Parser): Parser { +export function notMatch(parser: Parser): Parser { return new Parser((input, index, state) => { const result = parser.handler(input, index, state); return !result.success diff --git a/src/internal/index.ts b/src/internal/index.ts index 45138c39..c0e3574b 100644 --- a/src/internal/index.ts +++ b/src/internal/index.ts @@ -1,7 +1,6 @@ import * as M from '..'; import { language } from './parser'; import { mergeText } from './util'; -import * as P from './core'; export type FullParserOpts = { nestLimit?: number; @@ -13,11 +12,13 @@ export function fullParser(input: string, opts: FullParserOpts): M.MfmNode[] { depth: 0, linkLabel: false, trace: false, - }) as P.Success; + }); + if (!result.success) throw new Error('Unexpected parse error'); return mergeText(result.value); } export function simpleParser(input: string): M.MfmSimpleNode[] { - const result = language.simpleParser.handler(input, 0, { }) as P.Success; + const result = language.simpleParser.handler(input, 0, { }); + if (!result.success) throw new Error('Unexpected parse error'); return mergeText(result.value); } diff --git a/src/internal/parser.ts b/src/internal/parser.ts index 78939671..5fe2c959 100644 --- a/src/internal/parser.ts +++ b/src/internal/parser.ts @@ -1,7 +1,7 @@ import * as M from '..'; import * as P from './core'; import { mergeText } from './util'; -import { MfmInline, MfmNode, NodeType } from '..'; +import { MfmInline, MfmNode, MfmSimpleNode, NodeType } from '..'; import { SeqParseResult } from './core'; // NOTE: @@ -67,9 +67,9 @@ function nest(parser: P.Parser, fallback?: P.Parser): P.Parser, codeBlock: NodeType<'blockCode'>, From befa1d429ac8bda6deef90a68ecf437e849fd6af Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 22 May 2024 18:59:23 +0900 Subject: [PATCH 10/15] fix: unnecessary import --- src/internal/parser.ts | 63 +++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/src/internal/parser.ts b/src/internal/parser.ts index 5fe2c959..d64b122e 100644 --- a/src/internal/parser.ts +++ b/src/internal/parser.ts @@ -1,7 +1,6 @@ import * as M from '..'; import * as P from './core'; import { mergeText } from './util'; -import { MfmInline, MfmNode, MfmSimpleNode, NodeType } from '..'; import { SeqParseResult } from './core'; // NOTE: @@ -66,37 +65,37 @@ function nest(parser: P.Parser, fallback?: P.Parser): P.Parser, - codeBlock: NodeType<'blockCode'>, - mathBlock: NodeType<'mathBlock'>, - centerTag: NodeType<'center'>, - big: NodeType<'fn'> | string, - boldAsta: NodeType<'bold'> | string, - boldTag: NodeType<'bold'> | string, - boldUnder: NodeType<'bold'>, - smallTag: NodeType<'small'> | string, - italicTag: NodeType<'italic'> | string, - italicAsta: NodeType<'italic'>, - italicUnder: NodeType<'italic'>, - strikeTag: NodeType<'strike'> | string, - strikeWave: NodeType<'strike'> | string, - unicodeEmoji: NodeType<'unicodeEmoji'>, - plainTag: NodeType<'plain'>, - fn: NodeType<'fn'> | string, - inlineCode: NodeType<'inlineCode'>, - mathInline: NodeType<'mathInline'>, - mention: NodeType<'mention'> | string, - hashtag: NodeType<'hashtag'> | string, - emojiCode: NodeType<'emojiCode'>, - link: NodeType<'link'>, - url: NodeType<'url'> | string, - urlAlt: NodeType<'url'>, - search: NodeType<'search'>, + fullParser: (M.MfmNode | string)[], + simpleParser: (M.MfmSimpleNode | string)[], + full: M.MfmNode | string, + simple: M.MfmSimpleNode | string, + inline: M.MfmInline | string, + quote: M.NodeType<'quote'>, + codeBlock: M.NodeType<'blockCode'>, + mathBlock: M.NodeType<'mathBlock'>, + centerTag: M.NodeType<'center'>, + big: M.NodeType<'fn'> | string, + boldAsta: M.NodeType<'bold'> | string, + boldTag: M.NodeType<'bold'> | string, + boldUnder: M.NodeType<'bold'>, + smallTag: M.NodeType<'small'> | string, + italicTag: M.NodeType<'italic'> | string, + italicAsta: M.NodeType<'italic'>, + italicUnder: M.NodeType<'italic'>, + strikeTag: M.NodeType<'strike'> | string, + strikeWave: M.NodeType<'strike'> | string, + unicodeEmoji: M.NodeType<'unicodeEmoji'>, + plainTag: M.NodeType<'plain'>, + fn: M.NodeType<'fn'> | string, + inlineCode: M.NodeType<'inlineCode'>, + mathInline: M.NodeType<'mathInline'>, + mention: M.NodeType<'mention'> | string, + hashtag: M.NodeType<'hashtag'>, + emojiCode: M.NodeType<'emojiCode'>, + link: M.NodeType<'link'>, + url: M.NodeType<'url'> | string, + urlAlt: M.NodeType<'url'>, + search: M.NodeType<'search'>, text: string, } From 8c7462f4a745800499a63ecf0632df3647b3e22c Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 22 May 2024 21:24:24 +0900 Subject: [PATCH 11/15] lint: kill any but still with loose type checking --- src/internal/core/index.ts | 68 ++++++++++++++++++++++---------------- src/internal/index.ts | 2 +- src/internal/parser.ts | 48 ++++++++++++++++----------- 3 files changed, 68 insertions(+), 50 deletions(-) diff --git a/src/internal/core/index.ts b/src/internal/core/index.ts index 7cf26e76..e52b985d 100644 --- a/src/internal/core/index.ts +++ b/src/internal/core/index.ts @@ -12,7 +12,11 @@ export type Failure = { success: false }; export type Result = Success | Failure; -export type ParserHandler = (input: string, index: number, state: any) => Result +export interface StateBase { + trace?: boolean, +} + +export type ParserHandler = (input: string, index: number, state: S) => Result export function success(index: number, value: T): Success { return { @@ -26,11 +30,11 @@ export function failure(): Failure { return { success: false }; } -export class Parser { +export class Parser { public name?: string; - public handler: ParserHandler; + public handler: ParserHandler; - constructor(handler: ParserHandler, name?: string) { + constructor(handler: ParserHandler, name?: string) { this.handler = (input, index, state) : Failure | Success => { if (state.trace && this.name != null) { const pos = `${index}`; @@ -50,7 +54,7 @@ export class Parser { this.name = name; } - map(fn: (value: T) => U): Parser { + map(fn: (value: T) => U): Parser { return new Parser((input, index, state) => { const result = this.handler(input, index, state); if (!result.success) { @@ -60,7 +64,7 @@ export class Parser { }); } - text(): Parser { + text(): Parser { return new Parser((input, index, state) => { const result = this.handler(input, index, state); if (!result.success) { @@ -71,7 +75,7 @@ export class Parser { }); } - many(min: number): Parser { + many(min: number): Parser { return new Parser((input, index, state) => { let result; let latestIndex = index; @@ -91,32 +95,32 @@ export class Parser { }); } - sep(separator: Parser, min: number): Parser { + sep(separator: Parser, min: number): Parser { if (min < 1) { throw new Error('"min" must be a value greater than or equal to 1.'); } return seq( - this, + this as Parser, seq( separator, - this, + this as Parser, ).select(1).many(min - 1), ).map(result => [result[0], ...result[1]]); } - select(key: K): Parser { + select(key: K): Parser { return this.map(v => v[key]); } - option(): Parser { + option(): Parser { return alt([ - this, + this as Parser, succeeded(null), ]); } } -export function str(value: T): Parser { +export function str(value: T): Parser { return new Parser((input, index, _state) => { if ((input.length - index) < value.length) { return failure(); @@ -128,7 +132,7 @@ export function str(value: T): Parser { }); } -export function regexp(pattern: T): Parser { +export function regexp(pattern: T): Parser { const re = RegExp(`^(?:${pattern.source})`, pattern.flags); return new Parser((input, index, _state) => { const text = input.slice(index); @@ -140,23 +144,29 @@ export function regexp(pattern: T): Parser { }); } -type ParsedType> = T extends Parser ? U : never; +type ParsedType> = T extends Parser ? U : never; export type SeqParseResult = T extends [] ? [] : T extends [infer F, ...infer R] ? ( - F extends Parser ? [ParsedType, ...SeqParseResult] : [unknown, ...SeqParseResult] + F extends Parser ? [ParsedType, ...SeqParseResult] : [unknown, ...SeqParseResult] ) : unknown[]; -export function seq[]>(...parsers: Parsers): Parser> { +export type CommonState = + T extends [] ? StateBase + : T extends [Parser, ...infer R] ? S & CommonState + : T extends Parser[] ? S + : never; + +export function seq[]>(...parsers: Parsers): Parser, CommonState> { return new Parser((input, index, state) => { let result; let latestIndex = index; const accum = []; for (let i = 0; i < parsers.length; i++) { - result = parsers[i].handler(input, latestIndex, state); + result = parsers[i].handler(input, latestIndex, state as never); if (!result.success) { return result; } @@ -167,11 +177,11 @@ export function seq[]>(...parsers: Parsers): Par }); } -export function alt[]>(parsers: Parsers): Parser> { - return new Parser>((input, index, state): Result> => { +export function alt[]>(parsers: Parsers): Parser, CommonState> { + return new Parser, CommonState>((input, index, state): Result> => { for (let i = 0; i < parsers.length; i++) { const parser: Parsers[number] = parsers[i]; - const result = parser.handler(input, index, state); + const result = parser.handler(input, index, state as never); if (result.success) { return result as Result>; } @@ -180,13 +190,13 @@ export function alt[]>(parsers: Parsers): Parser }); } -function succeeded(value: T): Parser { +function succeeded(value: T): Parser { return new Parser((_input, index, _state) => { return success(index, value); }); } -export function notMatch(parser: Parser): Parser { +export function notMatch(parser: Parser): Parser { return new Parser((input, index, state) => { const result = parser.handler(input, index, state); return !result.success @@ -234,8 +244,8 @@ export const lineEnd = new Parser((input, index, state) => { return failure(); }); -export function lazy(fn: () => Parser): Parser { - const parser: Parser = new Parser((input, index, state) => { +export function lazy(fn: () => Parser): Parser { + const parser: Parser = new Parser((input, index, state) => { parser.handler = fn().handler; return parser.handler(input, index, state); }); @@ -246,10 +256,10 @@ export function lazy(fn: () => Parser): Parser { //type SyntaxReturn = T extends (rules: Record>) => infer R ? R : never; //export function createLanguage2>>(syntaxes: T): { [K in keyof T]: SyntaxReturn } { -type ParserTable = { [K in keyof T]: Parser }; +type ParserTable = { [K in keyof T]: Parser }; // TODO: 関数の型宣言をいい感じにしたい -export function createLanguage(syntaxes: { [K in keyof T]: (r: ParserTable) => Parser }): ParserTable { +export function createLanguage(syntaxes: { [K in keyof T]: (r: ParserTable) => Parser }): ParserTable { // @ts-expect-error initializing object so type error here const rules: ParserTable = {}; for (const key of Object.keys(syntaxes) as (keyof T & string)[]) { @@ -260,7 +270,7 @@ export function createLanguage(syntaxes: { [K in keyof T]: (r: ParserTable } parser.name = key; return parser; - }); + }) as Parser; } return rules; } diff --git a/src/internal/index.ts b/src/internal/index.ts index c0e3574b..b6a67e6d 100644 --- a/src/internal/index.ts +++ b/src/internal/index.ts @@ -12,7 +12,7 @@ export function fullParser(input: string, opts: FullParserOpts): M.MfmNode[] { depth: 0, linkLabel: false, trace: false, - }); + } as never); if (!result.success) throw new Error('Unexpected parse error'); return mergeText(result.value); } diff --git a/src/internal/parser.ts b/src/internal/parser.ts index d64b122e..bccd9aef 100644 --- a/src/internal/parser.ts +++ b/src/internal/parser.ts @@ -1,7 +1,6 @@ import * as M from '..'; import * as P from './core'; import { mergeText } from './util'; -import { SeqParseResult } from './core'; // NOTE: // tsdのテストでファイルを追加しているにも関わらず「@twemoji/parser/dist/lib/regex」の型定義ファイルがないとエラーが出るため、 @@ -17,13 +16,13 @@ const space = P.regexp(/[\u0020\u3000\t]/); const alphaAndNum = P.regexp(/[a-z0-9]/i); const newLine = P.alt([P.crlf, P.cr, P.lf]); -function seqOrText[]>(...parsers: Parsers): P.Parser | string> { - return new P.Parser | string>((input, index, state) => { +function seqOrText[]>(...parsers: Parsers): P.Parser | string, P.CommonState> { + return new P.Parser | string, P.CommonState>((input, index, state) => { // TODO: typesafe implementation const accum: unknown[] = []; let latestIndex = index; for (let i = 0 ; i < parsers.length; i++) { - const result = parsers[i].handler(input, latestIndex, state); + const result = parsers[i].handler(input, latestIndex, state as never); if (!result.success) { if (latestIndex === index) { return P.failure(); @@ -34,29 +33,38 @@ function seqOrText[]>(...parsers: Parsers): P. accum.push(result.value); latestIndex = result.index; } - return P.success(latestIndex, accum as SeqParseResult); + return P.success(latestIndex, accum as P.SeqParseResult); }); } -const notLinkLabel = new P.Parser((_input, index, state) => { +interface LinkState extends P.StateBase { + linkLabel?: boolean; +} + +const notLinkLabel = new P.Parser((_input, index, state: LinkState) => { return (!state.linkLabel) ? P.success(index, null) : P.failure(); }); -const nestable = new P.Parser((_input, index, state) => { +interface RecursiveState extends P.StateBase { + depth: number; + nestLimit: number; +} + +const nestable = new P.Parser((_input, index, state) => { return (state.depth < state.nestLimit) ? P.success(index, null) : P.failure(); }); -function nest(parser: P.Parser, fallback?: P.Parser): P.Parser { +function nest(parser: P.Parser, fallback?: P.Parser): P.Parser { // nesting limited? -> No: specified parser, Yes: fallback parser (default = P.char) - const inner = P.alt([ + const inner: P.Parser = P.alt([ P.seq(nestable, parser).select(1), (fallback != null) ? fallback : P.char, ]); - return new P.Parser((input, index, state) => { + return new P.Parser((input, index, state) => { state.depth++; const result = inner.handler(input, index, state); state.depth--; @@ -223,7 +231,7 @@ export const language = P.createLanguage({ }, quote: r => { - const lines: P.Parser = P.seq( + const lines = P.seq( P.str('>'), space.option(), P.seq(P.notMatch(newLine), P.char).select(1).many(0).text(), @@ -316,7 +324,7 @@ export const language = P.createLanguage({ }); }, - big: r => { + big: (r) => { const mark = P.str('***'); return seqOrText( mark, @@ -483,7 +491,7 @@ export const language = P.createLanguage({ } return P.success(result.index, result.value); }); - const arg: P.Parser = P.seq( + const arg = P.seq( P.regexp(/[a-z0-9_]+/i), P.seq( P.str('='), @@ -493,7 +501,7 @@ export const language = P.createLanguage({ return { k: result[0], v: (result[1] != null) ? result[1] : true, - }; + } as ArgPair; }); const args = P.seq( P.str('.'), @@ -557,7 +565,7 @@ export const language = P.createLanguage({ P.regexp(/[a-z0-9_.-]+/i), ).select(1).option(), ); - return new P.Parser((input, index, state) => { + return new P.Parser((input, index, state) => { let result; result = parser.handler(input, index, state); if (!result.success) { @@ -619,7 +627,7 @@ export const language = P.createLanguage({ P.notMatch(P.alt([P.regexp(/[ \u3000\t.,!?'"#:/[\]【】()「」()<>]/), space, newLine])), P.char, ).select(1); - const innerItem: P.Parser = P.lazy(() => P.alt([ + const innerItem: P.Parser = P.lazy(() => P.alt([ P.seq( P.str('('), nest(innerItem, hashTagChar).many(0), P.str(')'), ), @@ -672,7 +680,7 @@ export const language = P.createLanguage({ }, link: r => { - const labelInline = new P.Parser((input, index, state) => { + const labelInline = new P.Parser((input, index, state: LinkState) => { state.linkLabel = true; const result = r.inline.handler(input, index, state); state.linkLabel = false; @@ -691,7 +699,7 @@ export const language = P.createLanguage({ P.alt([r.urlAlt, r.url]), P.str(')'), ); - return new P.Parser((input, index, state) => { + return new P.Parser((input, index, state) => { const result = parser.handler(input, index, state); if (!result.success) { return P.failure(); @@ -708,7 +716,7 @@ export const language = P.createLanguage({ url: () => { const urlChar = P.regexp(/[.,a-z0-9_/:%#@$&?!~=+-]/i); - const innerItem: P.Parser = P.lazy(() => P.alt([ + const innerItem: P.Parser = P.lazy(() => P.alt([ P.seq( P.str('('), nest(innerItem, urlChar).many(0), P.str(')'), ), @@ -722,7 +730,7 @@ export const language = P.createLanguage({ P.regexp(/https?:\/\//), innerItem.many(1).text(), ); - return new P.Parser((input, index, state) => { + return new P.Parser((input, index, state) => { let result; result = parser.handler(input, index, state); if (!result.success) { From 79b55f0a380f5f1beecac6c92cd17ef1cb479a5c Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 23 May 2024 10:33:59 +0900 Subject: [PATCH 12/15] Revert "lint: kill any but still with loose type checking" This reverts commit 8c7462f4a745800499a63ecf0632df3647b3e22c. --- src/internal/core/index.ts | 68 ++++++++++++++++---------------------- src/internal/index.ts | 2 +- src/internal/parser.ts | 48 +++++++++++---------------- 3 files changed, 50 insertions(+), 68 deletions(-) diff --git a/src/internal/core/index.ts b/src/internal/core/index.ts index e52b985d..7cf26e76 100644 --- a/src/internal/core/index.ts +++ b/src/internal/core/index.ts @@ -12,11 +12,7 @@ export type Failure = { success: false }; export type Result = Success | Failure; -export interface StateBase { - trace?: boolean, -} - -export type ParserHandler = (input: string, index: number, state: S) => Result +export type ParserHandler = (input: string, index: number, state: any) => Result export function success(index: number, value: T): Success { return { @@ -30,11 +26,11 @@ export function failure(): Failure { return { success: false }; } -export class Parser { +export class Parser { public name?: string; - public handler: ParserHandler; + public handler: ParserHandler; - constructor(handler: ParserHandler, name?: string) { + constructor(handler: ParserHandler, name?: string) { this.handler = (input, index, state) : Failure | Success => { if (state.trace && this.name != null) { const pos = `${index}`; @@ -54,7 +50,7 @@ export class Parser { this.name = name; } - map(fn: (value: T) => U): Parser { + map(fn: (value: T) => U): Parser { return new Parser((input, index, state) => { const result = this.handler(input, index, state); if (!result.success) { @@ -64,7 +60,7 @@ export class Parser { }); } - text(): Parser { + text(): Parser { return new Parser((input, index, state) => { const result = this.handler(input, index, state); if (!result.success) { @@ -75,7 +71,7 @@ export class Parser { }); } - many(min: number): Parser { + many(min: number): Parser { return new Parser((input, index, state) => { let result; let latestIndex = index; @@ -95,32 +91,32 @@ export class Parser { }); } - sep(separator: Parser, min: number): Parser { + sep(separator: Parser, min: number): Parser { if (min < 1) { throw new Error('"min" must be a value greater than or equal to 1.'); } return seq( - this as Parser, + this, seq( separator, - this as Parser, + this, ).select(1).many(min - 1), ).map(result => [result[0], ...result[1]]); } - select(key: K): Parser { + select(key: K): Parser { return this.map(v => v[key]); } - option(): Parser { + option(): Parser { return alt([ - this as Parser, + this, succeeded(null), ]); } } -export function str(value: T): Parser { +export function str(value: T): Parser { return new Parser((input, index, _state) => { if ((input.length - index) < value.length) { return failure(); @@ -132,7 +128,7 @@ export function str(value: T): Parser { }); } -export function regexp(pattern: T): Parser { +export function regexp(pattern: T): Parser { const re = RegExp(`^(?:${pattern.source})`, pattern.flags); return new Parser((input, index, _state) => { const text = input.slice(index); @@ -144,29 +140,23 @@ export function regexp(pattern: T): Parser { }); } -type ParsedType> = T extends Parser ? U : never; +type ParsedType> = T extends Parser ? U : never; export type SeqParseResult = T extends [] ? [] : T extends [infer F, ...infer R] ? ( - F extends Parser ? [ParsedType, ...SeqParseResult] : [unknown, ...SeqParseResult] + F extends Parser ? [ParsedType, ...SeqParseResult] : [unknown, ...SeqParseResult] ) : unknown[]; -export type CommonState = - T extends [] ? StateBase - : T extends [Parser, ...infer R] ? S & CommonState - : T extends Parser[] ? S - : never; - -export function seq[]>(...parsers: Parsers): Parser, CommonState> { +export function seq[]>(...parsers: Parsers): Parser> { return new Parser((input, index, state) => { let result; let latestIndex = index; const accum = []; for (let i = 0; i < parsers.length; i++) { - result = parsers[i].handler(input, latestIndex, state as never); + result = parsers[i].handler(input, latestIndex, state); if (!result.success) { return result; } @@ -177,11 +167,11 @@ export function seq[]>(...parsers: Parser }); } -export function alt[]>(parsers: Parsers): Parser, CommonState> { - return new Parser, CommonState>((input, index, state): Result> => { +export function alt[]>(parsers: Parsers): Parser> { + return new Parser>((input, index, state): Result> => { for (let i = 0; i < parsers.length; i++) { const parser: Parsers[number] = parsers[i]; - const result = parser.handler(input, index, state as never); + const result = parser.handler(input, index, state); if (result.success) { return result as Result>; } @@ -190,13 +180,13 @@ export function alt[]>(parsers: Parsers): }); } -function succeeded(value: T): Parser { +function succeeded(value: T): Parser { return new Parser((_input, index, _state) => { return success(index, value); }); } -export function notMatch(parser: Parser): Parser { +export function notMatch(parser: Parser): Parser { return new Parser((input, index, state) => { const result = parser.handler(input, index, state); return !result.success @@ -244,8 +234,8 @@ export const lineEnd = new Parser((input, index, state) => { return failure(); }); -export function lazy(fn: () => Parser): Parser { - const parser: Parser = new Parser((input, index, state) => { +export function lazy(fn: () => Parser): Parser { + const parser: Parser = new Parser((input, index, state) => { parser.handler = fn().handler; return parser.handler(input, index, state); }); @@ -256,10 +246,10 @@ export function lazy(fn: () => Parser): Parser = T extends (rules: Record>) => infer R ? R : never; //export function createLanguage2>>(syntaxes: T): { [K in keyof T]: SyntaxReturn } { -type ParserTable = { [K in keyof T]: Parser }; +type ParserTable = { [K in keyof T]: Parser }; // TODO: 関数の型宣言をいい感じにしたい -export function createLanguage(syntaxes: { [K in keyof T]: (r: ParserTable) => Parser }): ParserTable { +export function createLanguage(syntaxes: { [K in keyof T]: (r: ParserTable) => Parser }): ParserTable { // @ts-expect-error initializing object so type error here const rules: ParserTable = {}; for (const key of Object.keys(syntaxes) as (keyof T & string)[]) { @@ -270,7 +260,7 @@ export function createLanguage(syntaxes: { [K in keyof T]: (r: ParserTable } parser.name = key; return parser; - }) as Parser; + }); } return rules; } diff --git a/src/internal/index.ts b/src/internal/index.ts index b6a67e6d..c0e3574b 100644 --- a/src/internal/index.ts +++ b/src/internal/index.ts @@ -12,7 +12,7 @@ export function fullParser(input: string, opts: FullParserOpts): M.MfmNode[] { depth: 0, linkLabel: false, trace: false, - } as never); + }); if (!result.success) throw new Error('Unexpected parse error'); return mergeText(result.value); } diff --git a/src/internal/parser.ts b/src/internal/parser.ts index bccd9aef..d64b122e 100644 --- a/src/internal/parser.ts +++ b/src/internal/parser.ts @@ -1,6 +1,7 @@ import * as M from '..'; import * as P from './core'; import { mergeText } from './util'; +import { SeqParseResult } from './core'; // NOTE: // tsdのテストでファイルを追加しているにも関わらず「@twemoji/parser/dist/lib/regex」の型定義ファイルがないとエラーが出るため、 @@ -16,13 +17,13 @@ const space = P.regexp(/[\u0020\u3000\t]/); const alphaAndNum = P.regexp(/[a-z0-9]/i); const newLine = P.alt([P.crlf, P.cr, P.lf]); -function seqOrText[]>(...parsers: Parsers): P.Parser | string, P.CommonState> { - return new P.Parser | string, P.CommonState>((input, index, state) => { +function seqOrText[]>(...parsers: Parsers): P.Parser | string> { + return new P.Parser | string>((input, index, state) => { // TODO: typesafe implementation const accum: unknown[] = []; let latestIndex = index; for (let i = 0 ; i < parsers.length; i++) { - const result = parsers[i].handler(input, latestIndex, state as never); + const result = parsers[i].handler(input, latestIndex, state); if (!result.success) { if (latestIndex === index) { return P.failure(); @@ -33,38 +34,29 @@ function seqOrText[]>(...parsers: Parse accum.push(result.value); latestIndex = result.index; } - return P.success(latestIndex, accum as P.SeqParseResult); + return P.success(latestIndex, accum as SeqParseResult); }); } -interface LinkState extends P.StateBase { - linkLabel?: boolean; -} - -const notLinkLabel = new P.Parser((_input, index, state: LinkState) => { +const notLinkLabel = new P.Parser((_input, index, state) => { return (!state.linkLabel) ? P.success(index, null) : P.failure(); }); -interface RecursiveState extends P.StateBase { - depth: number; - nestLimit: number; -} - -const nestable = new P.Parser((_input, index, state) => { +const nestable = new P.Parser((_input, index, state) => { return (state.depth < state.nestLimit) ? P.success(index, null) : P.failure(); }); -function nest(parser: P.Parser, fallback?: P.Parser): P.Parser { +function nest(parser: P.Parser, fallback?: P.Parser): P.Parser { // nesting limited? -> No: specified parser, Yes: fallback parser (default = P.char) - const inner: P.Parser = P.alt([ + const inner = P.alt([ P.seq(nestable, parser).select(1), (fallback != null) ? fallback : P.char, ]); - return new P.Parser((input, index, state) => { + return new P.Parser((input, index, state) => { state.depth++; const result = inner.handler(input, index, state); state.depth--; @@ -231,7 +223,7 @@ export const language = P.createLanguage({ }, quote: r => { - const lines = P.seq( + const lines: P.Parser = P.seq( P.str('>'), space.option(), P.seq(P.notMatch(newLine), P.char).select(1).many(0).text(), @@ -324,7 +316,7 @@ export const language = P.createLanguage({ }); }, - big: (r) => { + big: r => { const mark = P.str('***'); return seqOrText( mark, @@ -491,7 +483,7 @@ export const language = P.createLanguage({ } return P.success(result.index, result.value); }); - const arg = P.seq( + const arg: P.Parser = P.seq( P.regexp(/[a-z0-9_]+/i), P.seq( P.str('='), @@ -501,7 +493,7 @@ export const language = P.createLanguage({ return { k: result[0], v: (result[1] != null) ? result[1] : true, - } as ArgPair; + }; }); const args = P.seq( P.str('.'), @@ -565,7 +557,7 @@ export const language = P.createLanguage({ P.regexp(/[a-z0-9_.-]+/i), ).select(1).option(), ); - return new P.Parser((input, index, state) => { + return new P.Parser((input, index, state) => { let result; result = parser.handler(input, index, state); if (!result.success) { @@ -627,7 +619,7 @@ export const language = P.createLanguage({ P.notMatch(P.alt([P.regexp(/[ \u3000\t.,!?'"#:/[\]【】()「」()<>]/), space, newLine])), P.char, ).select(1); - const innerItem: P.Parser = P.lazy(() => P.alt([ + const innerItem: P.Parser = P.lazy(() => P.alt([ P.seq( P.str('('), nest(innerItem, hashTagChar).many(0), P.str(')'), ), @@ -680,7 +672,7 @@ export const language = P.createLanguage({ }, link: r => { - const labelInline = new P.Parser((input, index, state: LinkState) => { + const labelInline = new P.Parser((input, index, state) => { state.linkLabel = true; const result = r.inline.handler(input, index, state); state.linkLabel = false; @@ -699,7 +691,7 @@ export const language = P.createLanguage({ P.alt([r.urlAlt, r.url]), P.str(')'), ); - return new P.Parser((input, index, state) => { + return new P.Parser((input, index, state) => { const result = parser.handler(input, index, state); if (!result.success) { return P.failure(); @@ -716,7 +708,7 @@ export const language = P.createLanguage({ url: () => { const urlChar = P.regexp(/[.,a-z0-9_/:%#@$&?!~=+-]/i); - const innerItem: P.Parser = P.lazy(() => P.alt([ + const innerItem: P.Parser = P.lazy(() => P.alt([ P.seq( P.str('('), nest(innerItem, urlChar).many(0), P.str(')'), ), @@ -730,7 +722,7 @@ export const language = P.createLanguage({ P.regexp(/https?:\/\//), innerItem.many(1).text(), ); - return new P.Parser((input, index, state) => { + return new P.Parser((input, index, state) => { let result; result = parser.handler(input, index, state); if (!result.success) { From 30cf9de6e96121a60aca6435e79fff3fef47194a Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 23 May 2024 10:36:03 +0900 Subject: [PATCH 13/15] lint: kill any again --- src/internal/core/index.ts | 9 ++++++++- src/internal/index.ts | 5 ++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/internal/core/index.ts b/src/internal/core/index.ts index 7cf26e76..b9c21a1f 100644 --- a/src/internal/core/index.ts +++ b/src/internal/core/index.ts @@ -12,7 +12,14 @@ export type Failure = { success: false }; export type Result = Success | Failure; -export type ParserHandler = (input: string, index: number, state: any) => Result +interface State { + trace?: boolean, + linkLabel?: boolean, + nestLimit: number, + depth: number, +} + +export type ParserHandler = (input: string, index: number, state: State) => Result export function success(index: number, value: T): Success { return { diff --git a/src/internal/index.ts b/src/internal/index.ts index c0e3574b..a62fa136 100644 --- a/src/internal/index.ts +++ b/src/internal/index.ts @@ -18,7 +18,10 @@ export function fullParser(input: string, opts: FullParserOpts): M.MfmNode[] { } export function simpleParser(input: string): M.MfmSimpleNode[] { - const result = language.simpleParser.handler(input, 0, { }); + const result = language.simpleParser.handler(input, 0, { + depth: 0, + nestLimit: 1 / 0, // reliable infinite + }); if (!result.success) throw new Error('Unexpected parse error'); return mergeText(result.value); } From 4bf9f1796d881173f4f91ba2401fb911d8bc06c6 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 23 May 2024 11:01:26 +0900 Subject: [PATCH 14/15] test: write type test --- test-d/index.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test-d/index.ts b/test-d/index.ts index 388aca34..423b5a30 100644 --- a/test-d/index.ts +++ b/test-d/index.ts @@ -5,6 +5,7 @@ import { expectType } from 'tsd'; import { NodeType, MfmUrl } from '../src'; +import * as P from '../src/internal/core'; describe('#NodeType', () => { test('returns node that has sprcified type', () => { @@ -12,3 +13,18 @@ describe('#NodeType', () => { expectType(x); }); }); + +describe('parser internals', () => { + test('seq', () => { + const first = null as unknown as P.Parser<'first'>; + const second = null as unknown as P.Parser<'second'>; + const third = null as unknown as P.Parser<'third' | 'third-second'>; + expectType>(P.seq(first, second, third)); + }); + test('alt', () => { + const first = null as unknown as P.Parser<'first'>; + const second = null as unknown as P.Parser<'second'>; + const third = null as unknown as P.Parser<'third' | 'third-second'>; + expectType>(P.alt([first, second, third])); + }); +}); From 1f7fbed44682551ec63a90aed5f9dac9984dd5fc Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 23 May 2024 11:12:31 +0900 Subject: [PATCH 15/15] ci: upgrade node version for lint --- .github/workflows/api.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/api.yml b/.github/workflows/api.yml index dcbc7c02..73640746 100644 --- a/.github/workflows/api.yml +++ b/.github/workflows/api.yml @@ -14,7 +14,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v1 with: - node-version: 16.5.0 + node-version: 16.10.0 - name: Cache dependencies uses: actions/cache@v2 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cdf6d202..dea9d5ad 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,7 +14,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v1 with: - node-version: 16.5.0 + node-version: 16.10.0 - name: Cache dependencies uses: actions/cache@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e072e198..598042b5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: - node-version: [16.5.0] + node-version: [16.10.0] steps: - name: Checkout