diff --git a/.eslintrc.json b/.eslintrc.json index 9bbe4f5..4cb86e0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -84,7 +84,9 @@ "no-extra-semi": "error", "consistent-return": ["warn", { "treatUndefinedAsUnspecified": true }], "dot-notation": "error", - "no-useless-escape": "error", + "no-useless-escape": ["error", { + "extra": "i" + }], "no-fallthrough": "error", "for-direction": "error", "no-async-promise-executor": "error", diff --git a/package.json b/package.json index f0c3103..fe83c6a 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,8 @@ "packageManager": "pnpm@7.13.4", "pnpm": { "patchedDependencies": { - "eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch" + "eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch", + "eslint@8.28.0": "patches/eslint@8.28.0.patch" } }, "webExt": { diff --git a/patches/eslint@8.28.0.patch b/patches/eslint@8.28.0.patch new file mode 100644 index 0000000..994481b --- /dev/null +++ b/patches/eslint@8.28.0.patch @@ -0,0 +1,45 @@ +diff --git a/lib/rules/no-useless-escape.js b/lib/rules/no-useless-escape.js +index 2046a148a17fd1d5f3a4bbc9f45f7700259d11fa..f4898c6b57355a4fd72c43a9f32bf1a36a6ccf4a 100644 +--- a/lib/rules/no-useless-escape.js ++++ b/lib/rules/no-useless-escape.js +@@ -97,12 +97,30 @@ module.exports = { + escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character." + }, + +- schema: [] ++ schema: [{ ++ type: "object", ++ properties: { ++ extra: { ++ type: "string", ++ default: "" ++ }, ++ extraCharClass: { ++ type: "string", ++ default: "" ++ }, ++ }, ++ additionalProperties: false ++ }] + }, + + create(context) { ++ const options = context.options[0] || {}; ++ const { extra, extraCharClass } = options || '' + const sourceCode = context.getSourceCode(); + ++ const NON_CHARCLASS_ESCAPES = union(REGEX_NON_CHARCLASS_ESCAPES, new Set(extra)) ++ const CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set(extraCharClass)) ++ + /** + * Reports a node + * @param {ASTNode} node The node to report +@@ -238,7 +256,7 @@ module.exports = { + .filter(charInfo => charInfo.escaped) + + // Filter out characters that are valid to escape, based on their position in the regular expression. +- .filter(charInfo => !(charInfo.inCharClass ? REGEX_GENERAL_ESCAPES : REGEX_NON_CHARCLASS_ESCAPES).has(charInfo.text)) ++ .filter(charInfo => !(charInfo.inCharClass ? CHARCLASS_ESCAPES : NON_CHARCLASS_ESCAPES).has(charInfo.text)) + + // Report all the remaining characters. + .forEach(charInfo => report(node, charInfo.index, charInfo.text)); \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6f76ff3..bca41ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,9 @@ patchedDependencies: eslint-plugin-path-alias@1.0.0: hash: m6sma4g6bh67km3q6igf6uxaja path: patches/eslint-plugin-path-alias@1.0.0.patch + eslint@8.28.0: + hash: 7wc6icvgtg3uswirb5tpsbjnbe + path: patches/eslint@8.28.0.patch specifiers: '@types/diff': ^5.0.2 @@ -50,7 +53,7 @@ devDependencies: diff: 5.1.0 discord-types: 1.3.26 esbuild: 0.15.16 - eslint: 8.28.0 + eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe eslint-import-resolver-alias: 1.1.2 eslint-plugin-header: 3.1.1_eslint@8.28.0 eslint-plugin-path-alias: 1.0.0_m6sma4g6bh67km3q6igf6uxaja_eslint@8.28.0 @@ -216,7 +219,7 @@ packages: '@typescript-eslint/type-utils': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a '@typescript-eslint/utils': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a debug: 4.3.4 - eslint: 8.28.0 + eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe ignore: 5.2.0 natural-compare-lite: 1.4.0 regexpp: 3.2.0 @@ -241,7 +244,7 @@ packages: '@typescript-eslint/types': 5.45.0 '@typescript-eslint/typescript-estree': 5.45.0_typescript@4.9.3 debug: 4.3.4 - eslint: 8.28.0 + eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe typescript: 4.9.3 transitivePeerDependencies: - supports-color @@ -268,7 +271,7 @@ packages: '@typescript-eslint/typescript-estree': 5.45.0_typescript@4.9.3 '@typescript-eslint/utils': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a debug: 4.3.4 - eslint: 8.28.0 + eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe tsutils: 3.21.0_typescript@4.9.3 typescript: 4.9.3 transitivePeerDependencies: @@ -312,7 +315,7 @@ packages: '@typescript-eslint/scope-manager': 5.45.0 '@typescript-eslint/types': 5.45.0 '@typescript-eslint/typescript-estree': 5.45.0_typescript@4.9.3 - eslint: 8.28.0 + eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe eslint-scope: 5.1.1 eslint-utils: 3.0.0_eslint@8.28.0 semver: 7.3.7 @@ -894,7 +897,7 @@ packages: peerDependencies: eslint: '>=7.7.0' dependencies: - eslint: 8.28.0 + eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe dev: true /eslint-plugin-path-alias/1.0.0_m6sma4g6bh67km3q6igf6uxaja_eslint@8.28.0: @@ -902,7 +905,7 @@ packages: peerDependencies: eslint: ^7 dependencies: - eslint: 8.28.0 + eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe nanomatch: 1.2.13 transitivePeerDependencies: - supports-color @@ -914,7 +917,7 @@ packages: peerDependencies: eslint: '>=5.0.0' dependencies: - eslint: 8.28.0 + eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe dev: true /eslint-plugin-unused-imports/2.0.0_5am2datodjm2qi4eijrjrnoz54: @@ -928,7 +931,7 @@ packages: optional: true dependencies: '@typescript-eslint/eslint-plugin': 5.45.0_czs5uoqkd3podpy6vgtsxfc7au - eslint: 8.28.0 + eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe eslint-rule-composer: 0.3.0 dev: true @@ -959,7 +962,7 @@ packages: peerDependencies: eslint: '>=5' dependencies: - eslint: 8.28.0 + eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe eslint-visitor-keys: 2.1.0 dev: true @@ -973,7 +976,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint/8.28.0: + /eslint/8.28.0_7wc6icvgtg3uswirb5tpsbjnbe: resolution: {integrity: sha512-S27Di+EVyMxcHiwDrFzk8dJYAaD+/5SoWKxL1ri/71CRHsnJnRDPNt2Kzj24+MT9FDupf4aqqyqPrvI8MvQ4VQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true @@ -1020,6 +1023,7 @@ packages: transitivePeerDependencies: - supports-color dev: true + patched: true /espree/9.4.0: resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==} 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/style.css b/src/plugins/shikiCodeblocks/shiki.css similarity index 97% rename from src/plugins/shikiCodeblocks/style.css rename to src/plugins/shikiCodeblocks/shiki.css index b246db4..b871d99 100644 --- a/src/plugins/shikiCodeblocks/style.css +++ b/src/plugins/shikiCodeblocks/shiki.css @@ -1,10 +1,13 @@ -.shiki-root { - border-radius: 4px; - +.shiki-container { + border: 4px; /* fallback background */ background-color: var(--background-secondary); } +.shiki-root { + border-radius: 4px; +} + .shiki-root code { display: block; overflow-x: auto; 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);