From 268b7aeb3f03c57c6a34e0461d1eb4d54cf56392 Mon Sep 17 00:00:00 2001
From: Lhcfl <Lhcfl@outlook.com>
Date: Wed, 10 Apr 2024 17:16:25 +0800
Subject: [PATCH] refactor: Fix type errors of mfm.ts

---
 packages/client/src/components/mfm.ts | 229 +++++++++++++++-----------
 1 file changed, 137 insertions(+), 92 deletions(-)

diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts
index 0800e8270b..f2b100a207 100644
--- a/packages/client/src/components/mfm.ts
+++ b/packages/client/src/components/mfm.ts
@@ -1,6 +1,6 @@
 import { defineComponent, h } from "vue";
 import * as mfm from "mfm-js";
-import type { VNode, PropType } from "vue";
+import type { PropType, VNodeArrayChildren } from "vue";
 import MkUrl from "@/components/global/MkUrl.vue";
 import MkLink from "@/components/MkLink.vue";
 import MkMention from "@/components/MkMention.vue";
@@ -30,11 +30,12 @@ export default defineComponent({
 			default: false,
 		},
 		author: {
-			type: Object,
+			type: Object as PropType<entities.User>,
 			default: null,
 		},
+		// TODO: This variable is not used in the code and may be removed
 		i: {
-			type: Object,
+			type: Object as PropType<entities.User>,
 			default: null,
 		},
 		customEmojis: {
@@ -58,14 +59,16 @@ export default defineComponent({
 
 		const ast = (isPlain ? mfm.parseSimple : mfm.parse)(this.text);
 
-		const validTime = (t: string | null | undefined) => {
+		const validTime = (t: string | null | undefined | boolean) => {
 			if (t == null) return null;
+			if (typeof t !== "string") return null;
 			return t.match(/^[0-9.]+s$/) ? t : null;
 		};
 
-		const validNumber = (n: string | null | undefined) => {
+		const validNumber = (n: string | null | undefined | boolean) => {
 			if (n == null) return null;
-			const parsed = parseFloat(n);
+			if (typeof n !== "string") return null;
+			const parsed = Number.parseFloat(n);
 			return !Number.isNaN(parsed) && Number.isFinite(parsed) && parsed > 0;
 		};
 		// const validEase = (e: string | null | undefined) => {
@@ -77,13 +80,13 @@ export default defineComponent({
 
 		const genEl = (ast: mfm.MfmNode[]) =>
 			concat(
-				ast.map((token, index): VNode[] => {
+				ast.map((token, index): VNodeArrayChildren => {
 					switch (token.type) {
 						case "text": {
 							const text = token.props.text.replace(/(\r\n|\n|\r)/g, "\n");
 
 							if (!this.plain) {
-								const res = [];
+								const res: VNodeArrayChildren = [];
 								for (const t of text.split("\n")) {
 									res.push(h("br"));
 									res.push(t);
@@ -104,18 +107,20 @@ export default defineComponent({
 						}
 
 						case "italic": {
-							return h(
-								"i",
-								{
-									style: "font-style: oblique;",
-								},
-								genEl(token.children),
-							);
+							return [
+								h(
+									"i",
+									{
+										style: "font-style: oblique;",
+									},
+									genEl(token.children),
+								),
+							];
 						}
 
 						case "fn": {
 							// TODO: CSSを文字列で組み立てていくと token.props.args.~~~ 経由でCSSインジェクションできるのでよしなにやる
-							let style: string;
+							let style: string | null = null;
 							switch (token.props.name) {
 								case "tada": {
 									const speed = validTime(token.props.args.speed) || "1s";
@@ -188,7 +193,7 @@ export default defineComponent({
 									if (reducedMotion()) {
 										return genEl(token.children);
 									}
-									return h(MkSparkle, {}, genEl(token.children));
+									return [h(MkSparkle, {}, genEl(token.children))];
 								}
 								case "fade": {
 									const direction = token.props.args.out
@@ -211,31 +216,37 @@ export default defineComponent({
 									break;
 								}
 								case "x2": {
-									return h(
-										"span",
-										{
-											class: "mfm-x2",
-										},
-										genEl(token.children),
-									);
+									return [
+										h(
+											"span",
+											{
+												class: "mfm-x2",
+											},
+											genEl(token.children),
+										),
+									];
 								}
 								case "x3": {
-									return h(
-										"span",
-										{
-											class: "mfm-x3",
-										},
-										genEl(token.children),
-									);
+									return [
+										h(
+											"span",
+											{
+												class: "mfm-x3",
+											},
+											genEl(token.children),
+										),
+									];
 								}
 								case "x4": {
-									return h(
-										"span",
-										{
-											class: "mfm-x4",
-										},
-										genEl(token.children),
-									);
+									return [
+										h(
+											"span",
+											{
+												class: "mfm-x4",
+											},
+											genEl(token.children),
+										),
+									];
 								}
 								case "font": {
 									const family = token.props.args.serif
@@ -255,13 +266,15 @@ export default defineComponent({
 									break;
 								}
 								case "blur": {
-									return h(
-										"span",
-										{
-											class: "_blur_text",
-										},
-										genEl(token.children),
-									);
+									return [
+										h(
+											"span",
+											{
+												class: "_blur_text",
+											},
+											genEl(token.children),
+										),
+									];
 								}
 								case "rotate": {
 									const rotate = token.props.args.x
@@ -269,77 +282,105 @@ export default defineComponent({
 										: token.props.args.y
 											? "perspective(128px) rotateY"
 											: "rotate";
-									const degrees = parseFloat(token.props.args.deg ?? "90");
+									const degrees = Number.parseFloat(
+										token.props.args.deg.toString() ?? "90",
+									);
 									style = `transform: ${rotate}(${degrees}deg); transform-origin: center center;`;
 									break;
 								}
 								case "position": {
-									const x = parseFloat(token.props.args.x ?? "0");
-									const y = parseFloat(token.props.args.y ?? "0");
+									const x = Number.parseFloat(
+										token.props.args.x.toString() ?? "0",
+									);
+									const y = Number.parseFloat(
+										token.props.args.y.toString() ?? "0",
+									);
 									style = `transform: translateX(${x}em) translateY(${y}em);`;
 									break;
 								}
 								case "crop": {
-									const top = parseFloat(token.props.args.top ?? "0");
-									const right = parseFloat(token.props.args.right ?? "0");
-									const bottom = parseFloat(token.props.args.bottom ?? "0");
-									const left = parseFloat(token.props.args.left ?? "0");
+									const top = Number.parseFloat(
+										token.props.args.top.toString() ?? "0",
+									);
+									const right = Number.parseFloat(
+										token.props.args.right.toString() ?? "0",
+									);
+									const bottom = Number.parseFloat(
+										token.props.args.bottom.toString() ?? "0",
+									);
+									const left = Number.parseFloat(
+										token.props.args.left.toString() ?? "0",
+									);
 									style = `clip-path: inset(${top}% ${right}% ${bottom}% ${left}%);`;
 									break;
 								}
 								case "scale": {
-									const x = Math.min(parseFloat(token.props.args.x ?? "1"), 5);
-									const y = Math.min(parseFloat(token.props.args.y ?? "1"), 5);
+									const x = Math.min(
+										Number.parseFloat(token.props.args.x.toString() ?? "1"),
+										5,
+									);
+									const y = Math.min(
+										Number.parseFloat(token.props.args.y.toString() ?? "1"),
+										5,
+									);
 									style = `transform: scale(${x}, ${y});`;
 									break;
 								}
 								case "fg": {
 									let color = token.props.args.color;
-									if (!/^[0-9a-f]{3,6}$/i.test(color)) color = "f00";
+									if (!/^[0-9a-f]{3,6}$/i.test(color.toString())) color = "f00";
 									style = `color: #${color};`;
 									break;
 								}
 								case "bg": {
 									let color = token.props.args.color;
-									if (!/^[0-9a-f]{3,6}$/i.test(color)) color = "f00";
+									if (!/^[0-9a-f]{3,6}$/i.test(color.toString())) color = "f00";
 									style = `background-color: #${color};`;
 									break;
 								}
 								case "small": {
-									return h(
-										"small",
-										{
-											style: "opacity: 0.7;",
-										},
-										genEl(token.children),
-									);
+									return [
+										h(
+											"small",
+											{
+												style: "opacity: 0.7;",
+											},
+											genEl(token.children),
+										),
+									];
 								}
 								case "center": {
-									return h(
-										"div",
-										{
-											style: "text-align: center;",
-										},
-										genEl(token.children),
-									);
+									return [
+										h(
+											"div",
+											{
+												style: "text-align: center;",
+											},
+											genEl(token.children),
+										),
+									];
 								}
 							}
 							if (style == null) {
-								return h("span", {}, [
-									"$[",
-									token.props.name,
-									" ",
-									...genEl(token.children),
-									"]",
-								]);
+								return [
+									h("span", {}, [
+										"$[",
+										token.props.name,
+										" ",
+										...genEl(token.children),
+										"]",
+									]),
+								];
 							} else {
-								return h(
-									"span",
-									{
-										style: `display: inline-block;${style}`,
-									},
-									genEl(token.children),
-								);
+								return [
+									h(
+										"span",
+										{
+											style: `display: inline-block;${style}`,
+										},
+										genEl(token.children),
+									),
+								];
 							}
 						}
 
@@ -425,7 +466,7 @@ export default defineComponent({
 								h(MkCode, {
 									key: Math.random(),
 									code: token.props.code,
-									lang: token.props.lang,
+									lang: token.props.lang ?? undefined,
 								}),
 							];
 						}
@@ -506,13 +547,15 @@ export default defineComponent({
 								const ast2 = (isPlain ? mfm.parseSimple : mfm.parse)(
 									token.props.content.slice(0, -6) + sentinel,
 								);
+								function isMfmText(n: mfm.MfmNode): n is mfm.MfmText {
+									return n.type === "text";
+								}
+								const txtNode = ast2[ast2.length - 1];
 								if (
-									ast2[ast2.length - 1].type === "text" &&
-									ast2[ast2.length - 1].props.text.endsWith(sentinel)
+									isMfmText(txtNode) &&
+									txtNode.props.text.endsWith(sentinel)
 								) {
-									ast2[ast2.length - 1].props.text = ast2[
-										ast2.length - 1
-									].props.text.slice(0, -1);
+									txtNode.props.text = txtNode.props.text.slice(0, -1);
 								} else {
 									// I don't think this scope is reachable
 									console.warn(
@@ -554,8 +597,10 @@ export default defineComponent({
 						}
 
 						default: {
-							console.error("unrecognized ast type:", token.type);
-
+							console.error(
+								"unrecognized ast type:",
+								(token as { type: never }).type,
+							);
 							return [];
 						}
 					}