This commit is contained in:
王一之 2025-04-15 15:35:35 +08:00
parent c7763227d0
commit 2a0286e47d
12 changed files with 821 additions and 197 deletions

View File

@ -26,6 +26,7 @@
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
"dexie": "^4.0.10",
"eslint-linter-browserify": "^7.32.0",
"eventemitter3": "^5.0.1",
"i18next": "^23.16.4",
"monaco-editor": "^0.52.2",
@ -64,12 +65,13 @@
"eslint": "^9.24.0",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-userscripts": "^0.5.6",
"eslint-plugin-userscripts": "^0.2.12",
"fake-indexeddb": "^6.0.0",
"globals": "^16.0.0",
"jsdom": "^25.0.1",
"jszip": "^3.10.1",
"mock-xmlhttprequest": "^8.4.1",
"node-polyfill-webpack-plugin": "^3.0.0",
"postcss": "^8.4.49",
"postcss-loader": "^8.1.1",
"prettier": "^3.5.3",

View File

@ -14,6 +14,7 @@ const compatMap = {
GM_addElement: [
{ type: "tampermonkey", versionConstraint: ">=4.11.6113" },
{ type: "violentmonkey", versionConstraint: ">=2.13.0-beta.3" },
{ type: "scriptcat", versionConstraint: "*" },
],
"GM.addStyle": [
{ type: "tampermonkey", versionConstraint: ">=4.5" },
@ -24,15 +25,19 @@ const compatMap = {
{ type: "violentmonkey", versionConstraint: "*" },
{ type: "greasemonkey", versionConstraint: ">=0.6.1.4 <4" },
],
"GM.addValueChangeListener": [
{ type: "tampermonkey", versionConstraint: ">=4.5" },
],
"GM.addValueChangeListener": [{ type: "tampermonkey", versionConstraint: ">=4.5" }],
GM_addValueChangeListener: [
{ type: "tampermonkey", versionConstraint: ">=2.3.2607" },
{ type: "violentmonkey", versionConstraint: ">=2.12.0" },
],
"GM.cookie": [{ type: "tampermonkey", versionConstraint: ">=4.8" }],
GM_cookie: [{ type: "tampermonkey", versionConstraint: ">=4.8" }],
"GM.cookie": [
{ type: "tampermonkey", versionConstraint: ">=4.8" },
{ type: "scriptcat", versionConstraint: "*" },
],
GM_cookie: [
{ type: "tampermonkey", versionConstraint: ">=4.8" },
{ type: "scriptcat", versionConstraint: "*" },
],
"GM.deleteValue": [
{ type: "tampermonkey", versionConstraint: ">=4.5" },
{ type: "violentmonkey", versionConstraint: ">=2.12.0" },
@ -54,9 +59,7 @@ const compatMap = {
{ type: "violentmonkey", versionConstraint: "*" },
{ type: "greasemonkey", versionConstraint: ">=0.8.20080609.0 <4" },
],
"GM.getResourceURL": [
{ type: "violentmonkey", versionConstraint: ">=2.12.0 <2.13.0.10" },
],
"GM.getResourceURL": [{ type: "violentmonkey", versionConstraint: ">=2.12.0 <2.13.0.10" }],
GM_getResourceURL: [
{ type: "tampermonkey", versionConstraint: "*" },
{ type: "violentmonkey", versionConstraint: "*" },
@ -139,9 +142,7 @@ const compatMap = {
{ type: "violentmonkey", versionConstraint: "*" },
{ type: "greasemonkey", versionConstraint: ">=0.2.5 <4" },
],
"GM.removeValueChangeListener": [
{ type: "tampermonkey", versionConstraint: ">=4.5" },
],
"GM.removeValueChangeListener": [{ type: "tampermonkey", versionConstraint: ">=4.5" }],
GM_removeValueChangeListener: [
{ type: "tampermonkey", versionConstraint: ">=2.3.2607" },
{ type: "violentmonkey", versionConstraint: ">=2.12.0" },
@ -168,9 +169,7 @@ const compatMap = {
{ type: "violentmonkey", versionConstraint: "*" },
{ type: "greasemonkey", versionConstraint: ">=0.3-beta <4" },
],
"GM.unregisterMenuCommand": [
{ type: "tampermonkey", versionConstraint: ">=4.5" },
],
"GM.unregisterMenuCommand": [{ type: "tampermonkey", versionConstraint: ">=4.5" }],
GM_unregisterMenuCommand: [
{ type: "tampermonkey", versionConstraint: ">=3.6.3737" },
{ type: "violentmonkey", versionConstraint: ">=2.9.4" },

View File

@ -195,6 +195,7 @@ const compatMap = {
exportValue: [],
exportCookie: [],
scriptUrl: [],
storageName: [],
},
};

View File

@ -19,26 +19,26 @@ const userscriptsConfig = {
},
};
// const userscriptsRules = Object.fromEntries(
// Object.keys(userscriptsConfig.rules).map((name) => {
// const ruleName = name.split("/")[1];
// // eslint-disable-next-line import/no-dynamic-require, global-require
// const ruleMeta = require(`eslint-plugin-userscripts/lib/rules/${ruleName}.js`);
// return [
// name,
// {
// ...ruleMeta,
// meta: {
// ...ruleMeta.meta,
// docs: {
// ...ruleMeta.meta.docs,
// url: `https://yash-singh1.github.io/eslint-plugin-userscripts/#/rules/${ruleName}`,
// },
// },
// },
// ];
// })
// );
const userscriptsRules = Object.fromEntries(
Object.keys(userscriptsConfig.rules).map((name) => {
const ruleName = name.split("/")[1];
// eslint-disable-next-line import/no-dynamic-require, global-require
const ruleMeta = require(`eslint-plugin-userscripts/lib/rules/${ruleName}.js`);
return [
name,
{
...ruleMeta,
meta: {
...ruleMeta.meta,
docs: {
...ruleMeta.meta.docs,
url: `https://yash-singh1.github.io/eslint-plugin-userscripts/#/rules/${ruleName}`,
},
},
},
];
})
);
// 默认规则
const config = {
@ -126,6 +126,6 @@ const config = {
};
// 以文本形式导出默认规则
const defaultConfig = JSON.stringify(config);
const defaultConfig = JSON.stringify(config, null, 2);
export { defaultConfig, userscriptsConfig };
export { defaultConfig, userscriptsConfig, userscriptsRules };

664
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,8 @@ import { defineConfig } from "@rspack/cli";
import { rspack } from "@rspack/core";
import { version } from "./package.json";
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
const isDev = process.env.NODE_ENV === "development";
const isBeta = version.includes("-");
@ -36,6 +38,7 @@ export default defineConfig({
options: `${src}/pages/options/main.tsx`,
"editor.worker": "monaco-editor/esm/vs/editor/editor.worker.js",
"ts.worker": "monaco-editor/esm/vs/language/typescript/ts.worker.js",
"linter.worker": `${src}/linter.worker.ts`,
},
output: {
path: `${dist}/ext/src`,
@ -47,6 +50,9 @@ export default defineConfig({
alias: {
"@App": path.resolve(__dirname, "src/"),
"@Packages": path.resolve(__dirname, "packages/"),
// 改写eslint-plugin-userscripts以适配脚本猫打包时重定义模块路径
"../data/compat-grant": path.resolve(__dirname, "./packages/eslint/compat-grant"),
"../data/compat-headers": path.resolve(__dirname, "./packages/eslint/compat-headers"),
},
fallback: {
child_process: false,
@ -175,6 +181,7 @@ export default defineConfig({
minify: true,
chunks: ["sandbox"],
}),
new NodePolyfillPlugin(),
].filter(Boolean),
optimization: {
minimizer: [

92
src/linter.worker.ts Normal file
View File

@ -0,0 +1,92 @@
//@ts-ignore
import { Linter } from "eslint-linter-browserify";
import { userscriptsRules } from "../packages/eslint/linter-config";
// eslint语法检查,使用webworker
const linter = new Linter();
// 额外定义 userscripts 规则
linter.defineRules(userscriptsRules);
const rules = linter.getRules();
const severityMap = {
2: 8, // 2 for ESLint is error
1: 4, // 1 for ESLint is warning
};
function getTextBlock(text: string, startPosition: number, endPosition: number) {
if (startPosition > endPosition || startPosition < 0 || endPosition > text.length) {
throw new Error("Invalid positions provided");
}
let startLineNumber = 1;
let startColumn = 1;
let endLineNumber = 1;
let endColumn = 1;
for (let i = 0, currentLine = 1, currentColumn = 1; i < text.length; i += 1) {
if (i === startPosition) {
startLineNumber = currentLine;
startColumn = currentColumn;
}
if (i === endPosition) {
endLineNumber = currentLine;
endColumn = currentColumn;
break;
}
if (text[i] === "\n") {
currentLine += 1;
currentColumn = 0;
}
currentColumn += 1;
}
return {
startLineNumber,
startColumn,
endLineNumber,
endColumn,
};
}
self.addEventListener("message", (event) => {
const { code, id, config } = event.data;
const errs = linter.verify(code, config);
const markers = errs.map((err: any) => {
const rule = rules.get(err.ruleId);
let target = "";
if (rule) {
target = rule.meta.docs.url;
}
let fix: any;
if (err.fix) {
fix = {
range: getTextBlock(code, err.fix.range[0], err.fix.range[1]),
text: err.fix.text,
};
}
return {
code: {
value: err.ruleId || "",
target,
},
startLineNumber: err.line,
endLineNumber: err.endLine || err.line,
startColumn: err.column,
endColumn: err.endColumn || err.column,
message: err.message,
// 设置错误的等级此处ESLint与monaco的存在差异做一层映射
// @ts-ignore
severity: severityMap[err.severity],
source: "ESLint",
fix,
};
});
// 发回主进程
self.postMessage({ markers, id });
});

View File

@ -3,6 +3,7 @@ import { LinterWorker } from "@App/pkg/utils/monaco-editor";
import { useAppSelector } from "@App/pages/store/hooks";
import { editor, Range } from "monaco-editor";
import React, { useEffect, useImperativeHandle, useRef, useState } from "react";
import { globalCache, systemConfig } from "@App/pages/store/global";
type Props = {
className?: string;
@ -18,10 +19,26 @@ const CodeEditor: React.ForwardRefRenderFunction<{ editor: editor.IStandaloneCod
) => {
const settings = useAppSelector((state) => state.setting);
const [monacoEditor, setEditor] = useState<editor.IStandaloneCodeEditor>();
const [enableEslint, setEnableEslint] = useState(false);
const [eslintConfig, setEslintConfig] = useState("");
const div = useRef<HTMLDivElement>(null);
useImperativeHandle(ref, () => ({
editor: monacoEditor,
}));
useEffect(() => {
const loadConfigs = async () => {
const [eslintConfig, enableEslint] = await Promise.all([
systemConfig.getEslintConfig(),
systemConfig.getEnableEslint(),
]);
setEslintConfig(eslintConfig);
setEnableEslint(enableEslint);
};
loadConfigs();
}, []);
useEffect(() => {
if (diffCode === undefined || code === undefined || !div.current) {
return () => {};
@ -70,8 +87,7 @@ const CodeEditor: React.ForwardRefRenderFunction<{ editor: editor.IStandaloneCod
}, [div, code, diffCode, editable, id]);
useEffect(() => {
return () => {};
if (!settings.eslint.enable) {
if (!enableEslint) {
return () => {};
}
if (!monacoEditor) {
@ -89,7 +105,7 @@ const CodeEditor: React.ForwardRefRenderFunction<{ editor: editor.IStandaloneCod
LinterWorker.sendLinterMessage({
code: model.getValue(),
id,
config: JSON.parse(settings.eslint.config),
config: JSON.parse(eslintConfig),
});
}, 500);
};
@ -183,7 +199,7 @@ const CodeEditor: React.ForwardRefRenderFunction<{ editor: editor.IStandaloneCod
}
}
);
Cache.getInstance().set("eslint-fix", fix);
globalCache.set("eslint-fix", fix);
// 在行号旁显示ESLint错误/警告图标
const formatMarkers = message.markers.map(
@ -203,7 +219,7 @@ const CodeEditor: React.ForwardRefRenderFunction<{ editor: editor.IStandaloneCod
return () => {
LinterWorker.hook.removeListener("message", handler);
};
}, [id, monacoEditor, settings.eslint.config, settings.eslint.enable]);
}, [id, monacoEditor, enableEslint, eslintConfig]);
return (
<div

View File

@ -2,8 +2,9 @@ import { useEffect, useState } from "react";
import { Button, Card, Checkbox, Input, Message, Select, Space } from "@arco-design/web-react";
import Title from "@arco-design/web-react/es/Typography/title";
import { IconQuestionCircleFill } from "@arco-design/web-react/icon";
import { format } from "prettier";
import babel from "prettier/parser-babel";
import prettier from "prettier/standalone";
import * as babel from "prettier/parser-babel";
import prettierPluginEstree from "prettier/plugins/estree";
import GMApiSetting from "@App/pages/components/GMApiSetting";
import i18n from "@App/locales/locales";
import { useTranslation } from "react-i18next";
@ -270,10 +271,11 @@ function Setting() {
setEslintConfig(v);
}}
onBlur={(v) => {
format(eslintConfig, {
parser: "json",
plugins: [babel],
})
prettier
.format(eslintConfig, {
parser: "json",
plugins: [prettierPluginEstree, babel],
})
.then((res) => {
systemConfig.setEslintConfig(v.target.value);
})

View File

@ -5,3 +5,4 @@ import { MessageQueue } from "@Packages/message/message_queue";
export const message = new ExtensionMessage();
export const messageQueue = new MessageQueue();
export const systemConfig = new SystemConfig(messageQueue);
export const globalCache = new Map<string, any>();

View File

@ -1,6 +1,6 @@
import { Message } from "@arco-design/web-react";
import ChromeStorage from "./chrome_storage";
import { defaultConfig } from "../../../eslint/linter-config";
import { defaultConfig } from "../../../packages/eslint/linter-config";
import { FileSystemType } from "@Packages/filesystem/factory";
import { MessageQueue } from "@Packages/message/message_queue";

View File

@ -1,8 +1,10 @@
import { globalCache } from "@App/pages/store/global";
import dts from "@App/template/scriptcat.d.tpl";
import EventEmitter from "eventemitter3";
import { languages } from "monaco-editor";
// 注册eslint
// const linterWorker = new Worker("/src/linter.worker.js");
const linterWorker = new Worker("/src/linter.worker.js");
export default function registerEditor() {
window.MonacoEnvironment = {
@ -59,74 +61,74 @@ export default function registerEditor() {
},
});
// // 处理quick fix
// languages.registerCodeActionProvider("javascript", {
// provideCodeActions: (model /** ITextModel */, range /** Range */, context /** CodeActionContext */) => {
// // const actions: languages.CodeAction[] = [];
// // const eslintFix = <Map<string, any>>Cache.getInstance().get("eslint-fix");
// // for (let i = 0; i < context.markers.length; i += 1) {
// // // 判断有没有修复方案
// // const val = context.markers[i];
// // const code = typeof val.code === "string" ? val.code : val.code!.value;
// // const fix = eslintFix.get(
// // `${code}|${val.startLineNumber}|${val.endLineNumber}|${val.startColumn}|${val.endColumn}`
// // );
// // if (fix) {
// // const edit: languages.IWorkspaceTextEdit = {
// // resource: model.uri,
// // textEdit: {
// // range: fix.range,
// // text: fix.text,
// // },
// // versionId: undefined,
// // };
// // actions.push(<languages.CodeAction>{
// // title: `修复 ${code} 问题`,
// // diagnostics: [val],
// // kind: "quickfix",
// // edit: {
// // edits: [edit],
// // },
// // isPreferred: true,
// // });
// // }
// // }
// 处理quick fix
languages.registerCodeActionProvider("javascript", {
provideCodeActions: (model /** ITextModel */, range /** Range */, context /** CodeActionContext */) => {
const actions: languages.CodeAction[] = [];
const eslintFix = <Map<string, any>>globalCache.get("eslint-fix");
for (let i = 0; i < context.markers.length; i += 1) {
// 判断有没有修复方案
const val = context.markers[i];
const code = typeof val.code === "string" ? val.code : val.code!.value;
const fix = eslintFix.get(
`${code}|${val.startLineNumber}|${val.endLineNumber}|${val.startColumn}|${val.endColumn}`
);
if (fix) {
const edit: languages.IWorkspaceTextEdit = {
resource: model.uri,
textEdit: {
range: fix.range,
text: fix.text,
},
versionId: undefined,
};
actions.push(<languages.CodeAction>{
title: `修复 ${code} 问题`,
diagnostics: [val],
kind: "quickfix",
edit: {
edits: [edit],
},
isPreferred: true,
});
}
}
// // const actions = context.markers.map((error) => {
// // const edit: languages.IWorkspaceTextEdit = {
// // resource: model.uri,
// // textEdit: {
// // range,
// // text: "console.log(1)",
// // },
// // versionId: undefined,
// // };
// // return <languages.CodeAction>{
// // title: ``,
// // diagnostics: [error],
// // kind: "quickfix",
// // edit: {
// // edits: [edit],
// // },
// // isPreferred: true,
// // };
// // });
// return {
// actions,
// dispose: () => {},
// };
// },
// });
// const actions = context.markers.map((error) => {
// const edit: languages.IWorkspaceTextEdit = {
// resource: model.uri,
// textEdit: {
// range,
// text: "console.log(1)",
// },
// versionId: undefined,
// };
// return <languages.CodeAction>{
// title: ``,
// diagnostics: [error],
// kind: "quickfix",
// edit: {
// edits: [edit],
// },
// isPreferred: true,
// };
// });
return {
actions,
dispose: () => {},
};
},
});
}
// export class LinterWorker {
// static hook = new EventEmitter();
export class LinterWorker {
static hook = new EventEmitter();
// static sendLinterMessage(data: unknown) {
// linterWorker.postMessage(data);
// }
// }
static sendLinterMessage(data: unknown) {
linterWorker.postMessage(data);
}
}
// linterWorker.onmessage = (event) => {
// LinterWorker.hook.emit("message", event.data);
// };
linterWorker.onmessage = (event) => {
LinterWorker.hook.emit("message", event.data);
};