Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/eslint/compat-headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const compatMap = {
storageName: [],
"early-start": [],
"require-css": [],
allFrames: [],
},
};

Expand Down
31 changes: 20 additions & 11 deletions packages/eslint/linter-config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* eslint-disable @typescript-eslint/no-require-imports */
const { configs } = require("eslint-plugin-userscripts");
import { configs } from "eslint-plugin-userscripts";

// 默认规则
const config = {
Expand All @@ -21,12 +20,12 @@ const config = {
rules: {
"constructor-super": ["error"],
"for-direction": ["error"],
"getter-return": ["error"],
"getter-return": ["warn"], // implicitly means return undefined
"no-async-promise-executor": ["error"],
"no-case-declarations": ["error"],
"no-class-assign": ["error"],
"no-compare-neg-zero": ["error"],
"no-cond-assign": ["error"],
"no-cond-assign": ["warn"], // this is common writing style in JavaScript
"no-const-assign": ["error"],
"no-constant-condition": ["error"],
"no-control-regex": ["error"],
Expand All @@ -37,15 +36,15 @@ const config = {
"no-dupe-else-if": ["error"],
"no-dupe-keys": ["error"],
"no-duplicate-case": ["error"],
"no-empty": ["error"],
"no-empty": ["error", { allowEmptyCatch: true }],
"no-empty-character-class": ["error"],
"no-empty-pattern": ["error"],
"no-ex-assign": ["error"],
"no-extra-boolean-cast": ["error"],
"no-extra-semi": ["error"],
"no-fallthrough": ["error"],
"no-func-assign": ["error"],
"no-global-assign": ["error"],
"no-global-assign": ["warn"], // we always modify global variable in UserScript
"no-import-assign": ["error"],
"no-inner-declarations": ["error"],
"no-invalid-regexp": ["error"],
Expand All @@ -58,10 +57,10 @@ const config = {
"no-obj-calls": ["error"],
"no-octal": ["error"],
"no-prototype-builtins": ["error"],
"no-redeclare": ["error"],
"no-redeclare": ["error", { builtinGlobals: false }],
"no-regex-spaces": ["error"],
"no-self-assign": ["error"],
"no-setter-return": ["error"],
"no-setter-return": ["warn"], // sometimes developers like to return true in setter
"no-shadow-restricted-names": ["error"],
"no-sparse-arrays": ["error"],
"no-this-before-super": ["error"],
Expand All @@ -75,13 +74,13 @@ const config = {
"no-unused-vars": ["warn"],
"no-useless-backreference": ["error"],
"no-useless-catch": ["error"],
"no-useless-escape": ["error"],
"no-useless-escape": ["error", { allowRegexCharacters: ["-", "&", "/"] }],
"no-with": ["error"],
"require-yield": ["error"],
"use-isnan": ["error"],
"valid-typeof": ["error"],
...configs.recommended.rules,
},
} as Record<string, any>,
env: {
es6: true,
browser: true,
Expand All @@ -90,8 +89,18 @@ const config = {
};

// 调整规则
config.rules["userscripts/align-attributes"] = ["warn", 2];
// ScriptCat 在 Monaco 侧用自定义检查处理 metadata 对齐:
// 只要求 value 起始列一致,不要求固定空格数。
config.rules["userscripts/align-attributes"] = ["off"];
config.rules["userscripts/require-download-url"] = ["warn"];

// ScriptCat 不适用 - 有必要存在的用法
// 不是所有 @include 都要改为 @match。改用自定义处理
config.rules["userscripts/better-use-match"] = ["off"];
// 不是 @name @name:en @name:zh-CN @name:zh-TW @name:ja 都要放在最前。这个连 warning 也很无谓
config.rules["userscripts/require-name"] = ["off"];
// ScriptCat 不用指定 ==UserScript== 放最前。在 ==UserScript== 前面可以写其他注释, 例如是 License。 不视为 invalid
config.rules["userscripts/no-invalid-metadata"] = ["off"];

// 以文本形式导出默认规则
export const defaultConfig = JSON.stringify(config, null, 2);
11 changes: 6 additions & 5 deletions src/linter.worker.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
/* eslint-disable @typescript-eslint/no-require-imports */
const { Linter } = require("eslint-linter-browserify");
const { rules } = require("eslint-plugin-userscripts");
import { Linter } from "eslint-linter-browserify";
import { rules } from "eslint-plugin-userscripts";

// eslint语法检查,使用webworker

const linter = new Linter({ configType: "eslintrc" });

// 额外定义 userscripts 规则
const formatRules = Object.fromEntries(Object.entries(rules).map(([key, metas]) => ["userscripts/" + key, metas]));
linter.defineRules(formatRules as any);
const formatRules: typeof rules = Object.fromEntries(
Object.entries(rules).map(([key, metas]) => ["userscripts/" + key, metas])
);
linter.defineRules(formatRules);

const getRules = linter.getRules();

Expand Down
11 changes: 8 additions & 3 deletions src/pages/components/CodeEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { useEffect, useImperativeHandle, useRef, useState } from "react";
import { systemConfig } from "@App/pages/store/global";
import { LinterWorkerController, registerEditor } from "@App/pkg/utils/monaco-editor";
import { fnPlaceHolder } from "@App/pages/store/AppContext";
import { clearModelEslintFixes, getModelEslintFixKey } from "@App/pkg/utils/monaco-editor/eslintFixCache";

fnPlaceHolder.setEditorTheme = (theme: string) => editor.setTheme(theme);

Expand Down Expand Up @@ -259,13 +260,13 @@ const CodeEditor = React.forwardRef<{ editor: editor.IStandaloneCodeEditor | und

editor.setModelMarkers(model, "ESLint", message.markers);

// 更新 eslint-fix 快取(每次替换整个 map,避免已修复问题的过期条目残留)
// 更新当前 model 的 eslint-fix 快取,避免多个脚本编辑器互相覆盖 quick-fix。
const eslintFixMap = (window.MonacoEnvironment as any)?.eslintFixMap;
if (eslintFixMap) {
eslintFixMap.clear();
clearModelEslintFixes(eslintFixMap, model);
message.markers.forEach((m: TMarker) => {
if (m.fix) {
const key = `${m.code.value}|${m.startLineNumber}|${m.endLineNumber}|${m.startColumn}|${m.endColumn}`;
const key = getModelEslintFixKey(model, m.code.value, m);
eslintFixMap.set(key, m.fix);
}
});
Expand All @@ -288,6 +289,10 @@ const CodeEditor = React.forwardRef<{ editor: editor.IStandaloneCodeEditor | und
timer = null;
}
changeListener.dispose();
const eslintFixMap = (window.MonacoEnvironment as any)?.eslintFixMap;
if (eslintFixMap) {
clearModelEslintFixes(eslintFixMap, model);
}
LinterWorkerController.hookRemoveListener("message", messageHandler);
};
}, [monacoEditor, enableEslint, eslintConfig, id]);
Expand Down
54 changes: 54 additions & 0 deletions src/pkg/utils/monaco-editor/eslintFixCache.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { describe, expect, it } from "vitest";
import type { editor } from "monaco-editor";
import { clearModelEslintFixes, getModelEslintFixKey, type EslintFix } from "./eslintFixCache";

const createMockModel = (uri: string): editor.ITextModel =>
({
uri: {
toString: () => uri,
},
}) as editor.ITextModel;

const marker = {
startLineNumber: 1,
endLineNumber: 5,
startColumn: 1,
endColumn: 19,
};

const fix: EslintFix = {
range: {
startLineNumber: 2,
endLineNumber: 2,
startColumn: 9,
endColumn: 10,
},
text: " ",
};

describe("eslint fix cache", () => {
it("uses the model uri in fix keys so identical markers from different editors do not collide", () => {
const modelA = createMockModel("inmemory://model/a");
const modelB = createMockModel("inmemory://model/b");

expect(getModelEslintFixKey(modelA, "userscripts/align-attributes", marker)).not.toBe(
getModelEslintFixKey(modelB, "userscripts/align-attributes", marker)
);
});

it("clears only fixes for the current model", () => {
const modelA = createMockModel("inmemory://model/a");
const modelB = createMockModel("inmemory://model/b");
const map = new Map<string, EslintFix>();
const keyA = getModelEslintFixKey(modelA, "userscripts/align-attributes", marker);
const keyB = getModelEslintFixKey(modelB, "userscripts/align-attributes", marker);

map.set(keyA, fix);
map.set(keyB, fix);

clearModelEslintFixes(map, modelA);

expect(map.has(keyA)).toBe(false);
expect(map.has(keyB)).toBe(true);
});
});
27 changes: 27 additions & 0 deletions src/pkg/utils/monaco-editor/eslintFixCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { editor, IRange } from "monaco-editor";

export type EslintFix = {
range: IRange;
text: string;
};

type EslintFixMarkerPosition = Pick<
editor.IMarkerData,
"startLineNumber" | "endLineNumber" | "startColumn" | "endColumn"
>;

export const getEslintFixKey = (modelUri: string, eslintRuleId: string, marker: EslintFixMarkerPosition) => {
return `${modelUri}|${eslintRuleId}|${marker.startLineNumber}|${marker.endLineNumber}|${marker.startColumn}|${marker.endColumn}`;
};

export const getModelEslintFixKey = (model: editor.ITextModel, eslintRuleId: string, marker: EslintFixMarkerPosition) =>
getEslintFixKey(model.uri.toString(), eslintRuleId, marker);

export const clearModelEslintFixes = (eslintFixMap: Map<string, EslintFix>, model: editor.ITextModel) => {
const prefix = `${model.uri.toString()}|`;
for (const key of eslintFixMap.keys()) {
if (key.startsWith(prefix)) {
eslintFixMap.delete(key);
}
}
};
Loading
Loading