Repository: misskey-dev/mfm.js (ts)

Issue:
関数構文の関数名のホワイトリスト機能
オプションから与えた関数名リストに一致した場合のみ受理する


Pull request:
Supports white list for function syntax
<!-- ℹ お読みください
PRありがとうございます！ PRを作成する前に、コントリビューションガイドをご確認ください:
https://github.com/misskey-dev/misskey.js/blob/develop/CONTRIBUTING.md
-->
<!-- ℹ README
Thank you for your PR! Before creating a PR, please check the contribution guide:
https://github.com/misskey-dev/misskey.js/blob/develop/docs/CONTRIBUTING.en.md
-->

# What
<!-- このPRで何をしたのか？ どう変わるのか？ -->
<!-- What did you do with this PR? How will it change things? -->
- Supports white list for function syntax.
- `parse()` APIへの`opts`引数の追加
- opts.fnNameListの追加

fnNameListはデフォルト(null)ですべての関数名を受理する。
関数名の配列を与えることで、その配列の要素と一致する名前の関数のみを受理するようにカスタマイズできる。

Resolve #62 

# Why
<!-- なぜそうするのか？ どういう意図なのか？ 何が困っているのか？ -->
<!-- Why do you do it? What are your intentions? What is the problem? -->

# Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->

API`parse()`に変更あり(オプションの追加)。


Patch:
diff --git a/etc/mfm-js.api.md b/etc/mfm-js.api.md
index 3c5101a..d4e14c3 100644
--- a/etc/mfm-js.api.md
+++ b/etc/mfm-js.api.md
@@ -231,7 +231,9 @@ export const N_URL: (value: string, brackets?: boolean | undefined) => NodeType<
 export type NodeType<T extends MfmNode['type']> = T extends 'quote' ? MfmQuote : T extends 'search' ? MfmSearch : T extends 'blockCode' ? MfmCodeBlock : T extends 'mathBlock' ? MfmMathBlock : T extends 'center' ? MfmCenter : T extends 'unicodeEmoji' ? MfmUnicodeEmoji : T extends 'emojiCode' ? MfmEmojiCode : T extends 'bold' ? MfmBold : T extends 'small' ? MfmSmall : T extends 'italic' ? MfmItalic : T extends 'strike' ? MfmStrike : T extends 'inlineCode' ? MfmInlineCode : T extends 'mathInline' ? MfmMathInline : T extends 'mention' ? MfmMention : T extends 'hashtag' ? MfmHashtag : T extends 'url' ? MfmUrl : T extends 'link' ? MfmLink : T extends 'fn' ? MfmFn : T extends 'text' ? MfmText : never;
 
 // @public (undocumented)
-export function parse(input: string): MfmNode[];
+export function parse(input: string, opts?: Partial<{
+    fnNameList: string[];
+}>): MfmNode[];
 
 // Warning: (ae-forgotten-export) The symbol "MfmPlainNode" needs to be exported by the entry point index.d.ts
 //
diff --git a/src/api.ts b/src/api.ts
index 3a72c5c..4124061 100644
--- a/src/api.ts
+++ b/src/api.ts
@@ -8,8 +8,8 @@ const parser: peg.Parser = require('./internal/parser');
 /**
  * Generates a MfmNode tree from the MFM string.
 */
-export function parse(input: string): MfmNode[] {
-	const nodes = parser.parse(input, { startRule: 'fullParser' });
+export function parse(input: string, opts: Partial<{ fnNameList: string[]; }> = {}): MfmNode[] {
+	const nodes = parser.parse(input, { startRule: 'fullParser', fnNameList: opts.fnNameList });
 	return nodes;
 }
 
diff --git a/src/internal/parser.pegjs b/src/internal/parser.pegjs
index 9b6e3ac..5e3520c 100644
--- a/src/internal/parser.pegjs
+++ b/src/internal/parser.pegjs
@@ -56,6 +56,14 @@
 
 		return false;
 	}
+
+	function ensureFnName(name) {
+		if (!options.fnNameList) return true;
+		if (!Array.isArray(options.fnNameList)) {
+			error("options.fnNameList must be an array.");
+		}
+		return options.fnNameList.includes(name);
+	}
 }
 
 //
@@ -414,7 +422,7 @@ linkUrl
 // inline: fn
 
 fn
-	= "$[" name:$([a-z0-9_]i)+ args:fnArgs? _ content:(!("]") @inline)+ "]"
+	= "$[" name:$([a-z0-9_]i)+ &{ return ensureFnName(name); } args:fnArgs? _ content:(!("]") @inline)+ "]"
 {
 	args = args || {};
 	return FN(name, args, mergeText(content));


Test patch:
diff --git a/test/parser.ts b/test/parser.ts
index b36e505..f084f72 100644
--- a/test/parser.ts
+++ b/test/parser.ts
@@ -1050,6 +1050,24 @@ hoge`;
 			];
 			assert.deepStrictEqual(mfm.parse(input), output);
 		});
+
+		it('exists name in the fnNameList', () => {
+			const input = '$[spin.speed=1.1s text]';
+			const output = [
+				FN('spin', { speed: '1.1s' }, [
+					TEXT('text')
+				])
+			];
+			assert.deepStrictEqual(mfm.parse(input, { fnNameList: ['tada', 'spin'] }), output);
+		});
+
+		it('not exists name in the fnNameList', () => {
+			const input = '$[pope.speed=1.1s text]';
+			const output = [
+				TEXT('$[pope.speed=1.1s text]')
+			];
+			assert.deepStrictEqual(mfm.parse(input, { fnNameList: ['tada', 'spin'] }), output);
+		});
 	});
 
 	it('composite', () => {