From 989bd36eeb6dd6c4b391900765847cdcf87484d9 Mon Sep 17 00:00:00 2001 From: Justice Almanzar Date: Mon, 19 Dec 2022 17:59:54 -0500 Subject: refactor: identifier escapes + "self" group (#339) Co-authored-by: Ven --- src/components/PatchHelper.tsx | 23 ++++++-- src/plugins/shikiCodeblocks/index.ts | 6 +- src/plugins/shikiCodeblocks/shiki.css | 106 ++++++++++++++++++++++++++++++++++ src/plugins/shikiCodeblocks/style.css | 103 --------------------------------- src/utils/patches.ts | 55 ++++++++++++++++++ src/utils/types.ts | 4 +- src/webpack/patchWebpack.ts | 12 ++-- 7 files changed, 192 insertions(+), 117 deletions(-) create mode 100644 src/plugins/shikiCodeblocks/shiki.css delete mode 100644 src/plugins/shikiCodeblocks/style.css create mode 100644 src/utils/patches.ts (limited to 'src') diff --git a/src/components/PatchHelper.tsx b/src/components/PatchHelper.tsx index 22c2b4d..cb60980 100644 --- a/src/components/PatchHelper.tsx +++ b/src/components/PatchHelper.tsx @@ -18,6 +18,7 @@ import { debounce } from "@utils/debounce"; import { makeCodeblock } from "@utils/misc"; +import { canonicalizeMatch, canonicalizeReplace, ReplaceFn } from "@utils/patches"; import { search } from "@webpack"; import { Button, Clipboard, Forms, Margins, Parser, React, Switch, Text, TextInput } from "@webpack/common"; @@ -41,20 +42,29 @@ const findCandidates = debounce(function ({ find, setModule, setError }) { setModule([keys[0], candidates[keys[0]]]); }); -function ReplacementComponent({ module, match, replacement, setReplacementError }) { +interface ReplacementComponentProps { + module: [id: number, factory: Function]; + match: string | RegExp; + replacement: string | ReplaceFn; + setReplacementError(error: any): void; +} + +function ReplacementComponent({ module, match, replacement, setReplacementError }: ReplacementComponentProps) { const [id, fact] = module; const [compileResult, setCompileResult] = React.useState<[boolean, string]>(); const [patchedCode, matchResult, diff] = React.useMemo(() => { const src: string = fact.toString().replaceAll("\n", ""); + const canonicalMatch = canonicalizeMatch(match); try { - var patched = src.replace(match, replacement); + const canonicalReplace = canonicalizeReplace(replacement, "YourPlugin"); + var patched = src.replace(canonicalMatch, canonicalReplace as string); setReplacementError(void 0); } catch (e) { setReplacementError((e as Error).message); return ["", [], []]; } - const m = src.match(match); + const m = src.match(canonicalMatch); return [patched, m, makeDiff(src, patched, m)]; }, [id, match, replacement]); @@ -179,9 +189,10 @@ function ReplacementInput({ replacement, setReplacement, replacementError }) { {Object.entries({ "$$": "Insert a $", "$&": "Insert the entire match", - "$`​": "Insert the substring before the match", + "$`\u200b": "Insert the substring before the match", "$'": "Insert the substring after the match", - "$n": "Insert the nth capturing group ($1, $2...)" + "$n": "Insert the nth capturing group ($1, $2...)", + "$self": "Insert the plugin instance", }).map(([placeholder, desc]) => ( {Parser.parse("`" + placeholder + "`")}: {desc} @@ -206,7 +217,7 @@ function ReplacementInput({ replacement, setReplacement, replacementError }) { function PatchHelper() { const [find, setFind] = React.useState(""); const [match, setMatch] = React.useState(""); - const [replacement, setReplacement] = React.useState(""); + const [replacement, setReplacement] = React.useState(""); const [replacementError, setReplacementError] = React.useState(); diff --git a/src/plugins/shikiCodeblocks/index.ts b/src/plugins/shikiCodeblocks/index.ts index fd6b04b..58e0048 100644 --- a/src/plugins/shikiCodeblocks/index.ts +++ b/src/plugins/shikiCodeblocks/index.ts @@ -22,7 +22,7 @@ import { wordsFromPascal, wordsToTitle } from "@utils/text"; import definePlugin, { OptionType } from "@utils/types"; import previewExampleText from "~fileContent/previewExample.tsx"; -import cssText from "~fileContent/style.css"; +import cssText from "~fileContent/shiki.css"; import { Settings } from "../../Vencord"; import { shiki } from "./api/shiki"; @@ -44,8 +44,8 @@ export default definePlugin({ { find: "codeBlock:{react:function", replacement: { - match: /codeBlock:\{react:function\((.),(.),(.)\)\{/, - replace: "$&return Vencord.Plugins.plugins.ShikiCodeblocks.renderHighlighter($1,$2,$3);", + match: /codeBlock:\{react:function\((\i),(\i),(\i)\)\{/, + replace: "$&return $self.renderHighlighter($1,$2,$3);", }, }, ], diff --git a/src/plugins/shikiCodeblocks/shiki.css b/src/plugins/shikiCodeblocks/shiki.css new file mode 100644 index 0000000..b871d99 --- /dev/null +++ b/src/plugins/shikiCodeblocks/shiki.css @@ -0,0 +1,106 @@ +.shiki-container { + border: 4px; + /* fallback background */ + background-color: var(--background-secondary); +} + +.shiki-root { + border-radius: 4px; +} + +.shiki-root code { + display: block; + overflow-x: auto; + padding: 0.5em; + position: relative; + + font-size: 0.875rem; + line-height: 1.125rem; + text-indent: 0; + white-space: pre-wrap; + background: transparent; + border: none; +} + +.shiki-root [class^='devicon-'], +.shiki-root [class*=' devicon-'] { + margin-right: 8px; + user-select: none; +} + +.shiki-plain code { + padding-top: 8px; +} + +.shiki-btns { + font-size: 1em; + position: absolute; + right: 0; + bottom: 0; + opacity: 0; +} + +.shiki-root:hover .shiki-btns { + opacity: 1; +} + +.shiki-btn { + border-radius: 4px 4px 0 0; + padding: 4px 8px; +} + +.shiki-btn~.shiki-btn { + margin-left: 4px; +} + +.shiki-btn:last-child { + border-radius: 4px 0; +} + +.shiki-spinner-container { + align-items: center; + background-color: rgba(0, 0, 0, 0.6); + display: flex; + position: absolute; + justify-content: center; + top: 0; + right: 0; + bottom: 0; + left: 0; +} + +.shiki-preview { + margin-bottom: 2em; +} + +.shiki-lang { + padding: 0 5px; + margin-bottom: 6px; + font-weight: bold; + text-transform: capitalize; + display: flex; + align-items: center; +} + +.shiki-table { + border-collapse: collapse; + width: 100%; +} + +.shiki-table tr { + height: 19px; + width: 100%; +} + +.shiki-root td:first-child { + border-right: 1px solid transparent; + padding-left: 5px; + padding-right: 8px; + user-select: none; +} + +.shiki-root td:last-child { + padding-left: 8px; + word-break: break-word; + width: 100%; +} diff --git a/src/plugins/shikiCodeblocks/style.css b/src/plugins/shikiCodeblocks/style.css deleted file mode 100644 index b246db4..0000000 --- a/src/plugins/shikiCodeblocks/style.css +++ /dev/null @@ -1,103 +0,0 @@ -.shiki-root { - border-radius: 4px; - - /* fallback background */ - background-color: var(--background-secondary); -} - -.shiki-root code { - display: block; - overflow-x: auto; - padding: 0.5em; - position: relative; - - font-size: 0.875rem; - line-height: 1.125rem; - text-indent: 0; - white-space: pre-wrap; - background: transparent; - border: none; -} - -.shiki-root [class^='devicon-'], -.shiki-root [class*=' devicon-'] { - margin-right: 8px; - user-select: none; -} - -.shiki-plain code { - padding-top: 8px; -} - -.shiki-btns { - font-size: 1em; - position: absolute; - right: 0; - bottom: 0; - opacity: 0; -} - -.shiki-root:hover .shiki-btns { - opacity: 1; -} - -.shiki-btn { - border-radius: 4px 4px 0 0; - padding: 4px 8px; -} - -.shiki-btn~.shiki-btn { - margin-left: 4px; -} - -.shiki-btn:last-child { - border-radius: 4px 0; -} - -.shiki-spinner-container { - align-items: center; - background-color: rgba(0, 0, 0, 0.6); - display: flex; - position: absolute; - justify-content: center; - top: 0; - right: 0; - bottom: 0; - left: 0; -} - -.shiki-preview { - margin-bottom: 2em; -} - -.shiki-lang { - padding: 0 5px; - margin-bottom: 6px; - font-weight: bold; - text-transform: capitalize; - display: flex; - align-items: center; -} - -.shiki-table { - border-collapse: collapse; - width: 100%; -} - -.shiki-table tr { - height: 19px; - width: 100%; -} - -.shiki-root td:first-child { - border-right: 1px solid transparent; - padding-left: 5px; - padding-right: 8px; - user-select: none; -} - -.shiki-root td:last-child { - padding-left: 8px; - word-break: break-word; - width: 100%; -} diff --git a/src/utils/patches.ts b/src/utils/patches.ts new file mode 100644 index 0000000..8ecd68e --- /dev/null +++ b/src/utils/patches.ts @@ -0,0 +1,55 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import { PatchReplacement } from "./types"; + +export type ReplaceFn = (match: string, ...groups: string[]) => string; + +export function canonicalizeMatch(match: RegExp | string) { + if (typeof match === "string") return match; + const canonSource = match.source + .replaceAll("\\i", "[A-Za-z_$][\\w$]*"); + return new RegExp(canonSource, match.flags); +} + +export function canonicalizeReplace(replace: string | ReplaceFn, pluginName: string) { + if (typeof replace === "function") return replace; + return replace.replaceAll("$self", `Vencord.Plugins.plugins.${pluginName}`); +} + +export function canonicalizeDescriptor(descriptor: TypedPropertyDescriptor, canonicalize: (value: T) => T) { + if (descriptor.get) { + const original = descriptor.get; + descriptor.get = function () { + return canonicalize(original.call(this)); + }; + } else if (descriptor.value) { + descriptor.value = canonicalize(descriptor.value); + } + return descriptor; +} + +export function canonicalizeReplacement(replacement: Pick, plugin: string) { + const descriptors = Object.getOwnPropertyDescriptors(replacement); + descriptors.match = canonicalizeDescriptor(descriptors.match, canonicalizeMatch); + descriptors.replace = canonicalizeDescriptor( + descriptors.replace, + replace => canonicalizeReplace(replace, plugin), + ); + Object.defineProperties(replacement, descriptors); +} diff --git a/src/utils/types.ts b/src/utils/types.ts index fd8f02b..d3083fc 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -19,6 +19,8 @@ import { Command } from "@api/Commands"; import { Promisable } from "type-fest"; +import type { ReplaceFn } from "./patches"; + // exists to export default definePlugin({...}) export default function definePlugin

(p: P & Record) { return p; @@ -26,7 +28,7 @@ export default function definePlugin

(p: P & Record string); + replace: string | ReplaceFn; predicate?(): boolean; } diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index 8f11b63..7b318b2 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -18,6 +18,8 @@ import { WEBPACK_CHUNK } from "@utils/constants"; import Logger from "@utils/Logger"; +import { canonicalizeReplacement } from "@utils/patches"; +import { PatchReplacement } from "@utils/types"; import { _initWebpack } from "."; @@ -135,15 +137,17 @@ function patchPush() { if (code.includes(patch.find)) { patchedBy.add(patch.plugin); - // @ts-ignore we change all patch.replacement to array in plugins/index - for (const replacement of patch.replacement) { + // we change all patch.replacement to array in plugins/index + for (const replacement of patch.replacement as PatchReplacement[]) { if (replacement.predicate && !replacement.predicate()) continue; const lastMod = mod; const lastCode = code; + canonicalizeReplacement(replacement, patch.plugin); + try { - const newCode = code.replace(replacement.match, replacement.replace); - if (newCode === code && !replacement.noWarn) { + const newCode = code.replace(replacement.match, replacement.replace as string); + if (newCode === code && !patch.noWarn) { logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`); if (IS_DEV) { logger.debug("Function Source:\n", code); -- cgit