@sarj/eslint-plugin 18.3.1 → 19.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/catalog.js +6590 -0
- package/dist/chunk-KBGBBVAC.js +35295 -0
- package/dist/index.js +3 -39104
- package/dist/rule-examples.js +117 -262
- package/package.json +22 -52
- package/dist/_docs-d31-QA2S.d.cts +0 -170
- package/dist/_docs-d31-QA2S.d.ts +0 -170
- package/dist/index.cjs +0 -39103
- package/dist/index.d.cts +0 -766
- package/dist/index.d.ts +0 -766
- package/dist/rule-examples.cjs +0 -331
- package/dist/rule-examples.d.cts +0 -15
- package/dist/rule-examples.d.ts +0 -15
package/dist/rule-examples.js
CHANGED
|
@@ -1,295 +1,150 @@
|
|
|
1
1
|
// src/verify-rule-examples.ts
|
|
2
|
+
import { AssertionError } from "assert";
|
|
2
3
|
import { mkdtemp, mkdir, rm, writeFile, readFile, symlink } from "fs/promises";
|
|
3
4
|
import { tmpdir } from "os";
|
|
4
|
-
import { dirname
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
noEmit: true,
|
|
14
|
-
noUnusedLocals: true,
|
|
15
|
-
noUnusedParameters: true,
|
|
5
|
+
import { dirname, join, resolve, sep } from "path";
|
|
6
|
+
import { parseSync } from "oxc-parser";
|
|
7
|
+
import { RuleTester } from "oxlint/plugins-dev";
|
|
8
|
+
var UNREACHABLE_OUTPUT = "\0sarj-rule-examples: actual fixed source\0";
|
|
9
|
+
var DEFAULT_TSCONFIG = JSON.stringify({ compilerOptions: {
|
|
10
|
+
target: "ESNext",
|
|
11
|
+
module: "ESNext",
|
|
12
|
+
moduleResolution: "Bundler",
|
|
13
|
+
strict: true,
|
|
16
14
|
allowJs: true,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
20
|
-
var MAX_LIBRARY_FILES = 256;
|
|
21
|
-
var MAX_LIBRARY_BYTES = 16 * 1024 * 1024;
|
|
22
|
-
function createExampleParser() {
|
|
23
|
-
const libraryRoot = ts.sys.realpath?.(dirname(ts.getDefaultLibFilePath({})));
|
|
24
|
-
const cache = /* @__PURE__ */ new Map();
|
|
25
|
-
const registries = /* @__PURE__ */ new Map();
|
|
26
|
-
const counts = { programs: 0, libraryAcquires: 0, libraryHits: 0, nativeFallbacks: 0 };
|
|
27
|
-
let bytes = 0;
|
|
28
|
-
return {
|
|
29
|
-
meta: parser.meta,
|
|
30
|
-
clear() {
|
|
31
|
-
cache.clear();
|
|
32
|
-
registries.clear();
|
|
33
|
-
bytes = 0;
|
|
34
|
-
},
|
|
35
|
-
statistics() {
|
|
36
|
-
return { ...counts, libraryFiles: cache.size, libraryBytes: bytes };
|
|
37
|
-
},
|
|
38
|
-
parseForESLint(code, options) {
|
|
39
|
-
const fallback = () => {
|
|
40
|
-
counts.nativeFallbacks += 1;
|
|
41
|
-
return parser.parseForESLint(code, options);
|
|
42
|
-
};
|
|
43
|
-
if (!Object.hasOwn(ts, "JSDocParsingMode")) return fallback();
|
|
44
|
-
if (typeof options?.project !== "string" || typeof options.filePath !== "string" || options.programs != null || options.projectService || options.extraFileExtensions?.length || options.debugLevel) {
|
|
45
|
-
return fallback();
|
|
46
|
-
}
|
|
47
|
-
const config = resolve(options.tsconfigRootDir ?? process.cwd(), options.project);
|
|
48
|
-
let unrecoverable = false;
|
|
49
|
-
const parsed = ts.getParsedCommandLineOfConfigFile(config, WATCH_DEFAULTS, {
|
|
50
|
-
...ts.sys,
|
|
51
|
-
onUnRecoverableConfigFileDiagnostic() {
|
|
52
|
-
unrecoverable = true;
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
if (unrecoverable || parsed === void 0 || parsed.errors.length > 0 || parsed.projectReferences?.length) {
|
|
56
|
-
return fallback();
|
|
57
|
-
}
|
|
58
|
-
const filename = resolve(options.filePath);
|
|
59
|
-
const host = ts.createCompilerHost(parsed.options, true);
|
|
60
|
-
const canonical = (path) => host.getCanonicalFileName(resolve(path));
|
|
61
|
-
if (!parsed.fileNames.some((path) => canonical(path) === canonical(filename))) return fallback();
|
|
62
|
-
const mode = options.jsDocParsingMode === "none" ? ts.JSDocParsingMode.ParseNone : options.jsDocParsingMode === "type-info" ? ts.JSDocParsingMode.ParseForTypeInfo : ts.JSDocParsingMode.ParseAll;
|
|
63
|
-
host.jsDocParsingMode = mode;
|
|
64
|
-
let registry = registries.get(mode);
|
|
65
|
-
if (registry === void 0) {
|
|
66
|
-
registry = ts.createDocumentRegistry(ts.sys.useCaseSensitiveFileNames, process.cwd(), mode);
|
|
67
|
-
registries.set(mode, registry);
|
|
68
|
-
}
|
|
69
|
-
const settingsKey = registry.getKeyForCompilationSettings(parsed.options);
|
|
70
|
-
const read = host.readFile.bind(host);
|
|
71
|
-
host.readFile = (path) => canonical(path) === canonical(filename) ? code : read(path);
|
|
72
|
-
const original = host.getSourceFile.bind(host);
|
|
73
|
-
host.getSourceFile = (path, language, onError, fresh) => {
|
|
74
|
-
if (fresh || libraryRoot === void 0 || host.realpath === void 0 || canonical(path) === canonical(filename) || dirname(resolve(path)) !== libraryRoot || !/^lib(?:\..+)?\.d\.ts$/u.test(basename(path)) || !host.fileExists(path)) {
|
|
75
|
-
return original(path, language, onError, fresh);
|
|
76
|
-
}
|
|
77
|
-
try {
|
|
78
|
-
if (host.realpath(path) !== resolve(path)) return original(path, language, onError, fresh);
|
|
79
|
-
} catch {
|
|
80
|
-
return original(path, language, onError, fresh);
|
|
81
|
-
}
|
|
82
|
-
const text = read(path);
|
|
83
|
-
if (text === void 0) return original(path, language, onError, fresh);
|
|
84
|
-
const version = typeof language === "number" ? language : language.languageVersion;
|
|
85
|
-
const format = typeof language === "number" ? void 0 : language.impliedNodeFormat;
|
|
86
|
-
const key = JSON.stringify([path, settingsKey, version, format, mode]);
|
|
87
|
-
const previous = cache.get(key);
|
|
88
|
-
if (previous?.text === text) {
|
|
89
|
-
counts.libraryHits += 1;
|
|
90
|
-
return previous.source;
|
|
91
|
-
}
|
|
92
|
-
const size = Buffer.byteLength(text);
|
|
93
|
-
if (previous !== void 0 || cache.size >= MAX_LIBRARY_FILES || bytes + size > MAX_LIBRARY_BYTES) {
|
|
94
|
-
return original(path, language, onError, fresh);
|
|
95
|
-
}
|
|
96
|
-
const source = registry.acquireDocument(
|
|
97
|
-
path,
|
|
98
|
-
parsed.options,
|
|
99
|
-
ts.ScriptSnapshot.fromString(text),
|
|
100
|
-
"unchanged-native-library",
|
|
101
|
-
ts.ScriptKind.TS,
|
|
102
|
-
language
|
|
103
|
-
);
|
|
104
|
-
cache.set(key, { text, source });
|
|
105
|
-
bytes += size;
|
|
106
|
-
counts.libraryAcquires += 1;
|
|
107
|
-
return source;
|
|
108
|
-
};
|
|
109
|
-
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
|
|
110
|
-
counts.programs += 1;
|
|
111
|
-
if (program.getSourceFile(filename)?.text !== code) {
|
|
112
|
-
throw new Error("The example compiler did not load the current lint source.");
|
|
113
|
-
}
|
|
114
|
-
return parser.parseForESLint(code, {
|
|
115
|
-
...options,
|
|
116
|
-
project: false,
|
|
117
|
-
projectService: false,
|
|
118
|
-
programs: [program],
|
|
119
|
-
disallowAutomaticSingleRunInference: true
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// src/verify-rule-examples.ts
|
|
15
|
+
jsx: "preserve",
|
|
16
|
+
noEmit: true
|
|
17
|
+
}, include: ["**/*"] });
|
|
126
18
|
async function verifyRuleExamples(rule, setup = {}) {
|
|
127
19
|
const spec = rule.documentation;
|
|
128
|
-
if (
|
|
20
|
+
if (!spec.publicExamples.some((item) => item.outcome === "match") || !spec.publicExamples.some((item) => item.outcome === "no-match")) {
|
|
129
21
|
throw new Error("rule requires documented public matching and non-matching examples");
|
|
130
22
|
}
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
validateFixtureSyntax(example.id, file, path);
|
|
139
|
-
await mkdir(dirname2(path), { recursive: true });
|
|
140
|
-
await writeFile(path, file.source);
|
|
141
|
-
}
|
|
142
|
-
for (const dependency of example.installedDependencies ?? []) {
|
|
143
|
-
if (setup.installedDependencyRoot === void 0) throw new Error(`${example.id}: explicit installed dependency root is required`);
|
|
144
|
-
const packageFile = fixturePath(resolve2(setup.installedDependencyRoot), `node_modules/${dependency.module}/package.json`);
|
|
145
|
-
const metadata = JSON.parse(await readFile(packageFile, "utf8"));
|
|
146
|
-
if (typeof metadata !== "object" || metadata === null || !("name" in metadata) || !("version" in metadata) || metadata.name !== dependency.module || metadata.version !== dependency.version) throw new Error(`${example.id}: installed dependency identity/version mismatch for ${dependency.module}`);
|
|
147
|
-
const destination = fixturePath(root, `node_modules/${dependency.module}`);
|
|
148
|
-
await mkdir(dirname2(destination), { recursive: true });
|
|
149
|
-
await symlink(dirname2(packageFile), destination, process.platform === "win32" ? "junction" : "dir");
|
|
150
|
-
}
|
|
151
|
-
const tsconfig = join(root, "tsconfig.json");
|
|
152
|
-
if (!example.files.some((file) => file.path === "tsconfig.json")) {
|
|
153
|
-
await writeFile(tsconfig, JSON.stringify({ compilerOptions: {
|
|
154
|
-
target: "ESNext",
|
|
155
|
-
module: "ESNext",
|
|
156
|
-
moduleResolution: "Bundler",
|
|
157
|
-
strict: true,
|
|
158
|
-
allowJs: true,
|
|
159
|
-
jsx: "preserve",
|
|
160
|
-
noEmit: true
|
|
161
|
-
}, include: ["**/*"] }));
|
|
162
|
-
}
|
|
163
|
-
const ruleId = `@sarj/${spec.ruleId}`;
|
|
164
|
-
const config = {
|
|
165
|
-
files: ["**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}"],
|
|
166
|
-
languageOptions: { parser: exampleParser, parserOptions: { project: tsconfig, tsconfigRootDir: root, disallowAutomaticSingleRunInference: true } },
|
|
167
|
-
plugins: { "@sarj": { rules: { [spec.ruleId]: rule } } },
|
|
168
|
-
rules: { [ruleId]: ["error", ...example.ruleOptions ?? rule.meta.defaultOptions ?? rule.defaultOptions ?? []] }
|
|
169
|
-
};
|
|
170
|
-
await verifyExample(new Linter({ cwd: root }), config, example, root, ruleId, setup.formatFixedSource);
|
|
171
|
-
} finally {
|
|
172
|
-
parser2.clearCaches();
|
|
173
|
-
await rm(root, { recursive: true, force: true });
|
|
174
|
-
}
|
|
23
|
+
for (const example of spec.examples) {
|
|
24
|
+
const root = await mkdtemp(join(tmpdir(), "sarj-rule-example-"));
|
|
25
|
+
try {
|
|
26
|
+
await writeProject(root, example, setup);
|
|
27
|
+
await verifyExample(rule, example, root, setup.formatFixedSource);
|
|
28
|
+
} finally {
|
|
29
|
+
await rm(root, { recursive: true, force: true });
|
|
175
30
|
}
|
|
176
|
-
} finally {
|
|
177
|
-
exampleParser.clear();
|
|
178
31
|
}
|
|
179
32
|
return spec.examples.length;
|
|
180
33
|
}
|
|
34
|
+
async function writeProject(root, example, setup) {
|
|
35
|
+
for (const file of example.files) {
|
|
36
|
+
const path = fixturePath(root, file.path);
|
|
37
|
+
validateFixtureSyntax(example.id, file, path);
|
|
38
|
+
await mkdir(dirname(path), { recursive: true });
|
|
39
|
+
await writeFile(path, file.source);
|
|
40
|
+
}
|
|
41
|
+
for (const dependency of example.installedDependencies ?? []) {
|
|
42
|
+
if (setup.installedDependencyRoot === void 0) throw new Error(`${example.id}: explicit installed dependency root is required`);
|
|
43
|
+
const packageFile = fixturePath(resolve(setup.installedDependencyRoot), `node_modules/${dependency.module}/package.json`);
|
|
44
|
+
const metadata = JSON.parse(await readFile(packageFile, "utf8"));
|
|
45
|
+
if (typeof metadata !== "object" || metadata === null || !("name" in metadata) || !("version" in metadata) || metadata.name !== dependency.module || metadata.version !== dependency.version) throw new Error(`${example.id}: installed dependency identity/version mismatch for ${dependency.module}`);
|
|
46
|
+
const destination = fixturePath(root, `node_modules/${dependency.module}`);
|
|
47
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
48
|
+
await symlink(dirname(packageFile), destination, process.platform === "win32" ? "junction" : "dir");
|
|
49
|
+
}
|
|
50
|
+
if (!example.files.some((file) => file.path === "tsconfig.json")) {
|
|
51
|
+
await writeFile(join(root, "tsconfig.json"), DEFAULT_TSCONFIG);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
181
54
|
function fixturePath(root, path) {
|
|
182
|
-
const resolved =
|
|
55
|
+
const resolved = resolve(root, path);
|
|
183
56
|
if (!resolved.startsWith(root + sep)) throw new Error(`unsafe example path: ${path}`);
|
|
184
57
|
return resolved;
|
|
185
58
|
}
|
|
186
59
|
function validateFixtureSyntax(exampleId, file, path) {
|
|
187
60
|
if (!/\.[cm]?[jt]sx?$/u.test(file.path)) return;
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
} catch (error) {
|
|
191
|
-
throw new Error(`${exampleId}: invalid fixture ${file.path}`, { cause: error });
|
|
192
|
-
}
|
|
61
|
+
const [error] = parseSync(path, file.source).errors;
|
|
62
|
+
if (error !== void 0) throw new Error(`${exampleId}: invalid fixture ${file.path}: ${error.message}`);
|
|
193
63
|
}
|
|
194
|
-
async function verifyExample(
|
|
64
|
+
async function verifyExample(rule, example, root, formatFixedSource) {
|
|
195
65
|
const focus = example.files.find((file) => file.path === example.focusPath);
|
|
196
66
|
if (focus === void 0) throw new Error(`${example.id}: focus file is missing`);
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
message.endLine,
|
|
212
|
-
message.endColumn
|
|
213
|
-
]));
|
|
214
|
-
if (new Set(locations).size !== locations.length) throw new Error(`${example.id}: duplicate diagnostic`);
|
|
215
|
-
if (messages.length !== example.expectedCount) {
|
|
216
|
-
throw new Error(`${example.id}: expected ${example.expectedCount} findings, received ${messages.length}`);
|
|
217
|
-
}
|
|
218
|
-
if (example.expectedDiagnostics !== void 0) {
|
|
219
|
-
verifyExpectedDiagnostics(example, messages, focus.source, lines);
|
|
67
|
+
const base = {
|
|
68
|
+
code: focus.source,
|
|
69
|
+
filename: fixturePath(root, focus.path),
|
|
70
|
+
// Authored options are JSON data: the catalog publishes them.
|
|
71
|
+
...example.ruleOptions !== void 0 && { options: [...example.ruleOptions] }
|
|
72
|
+
};
|
|
73
|
+
const name = rule.documentation.ruleId;
|
|
74
|
+
const tested = rule;
|
|
75
|
+
const tester = new RuleTester({ eslintCompat: true, cwd: root });
|
|
76
|
+
if (example.expectedCount === 0) {
|
|
77
|
+
run(example, () => {
|
|
78
|
+
tester.run(name, tested, { valid: [base], invalid: [] });
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
220
81
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (
|
|
230
|
-
throw new Error(`${example.id}: fix
|
|
231
|
-
}
|
|
232
|
-
if (formatFixedSource !== void 0 && await formatFixedSource({ path: input.path, source: fixed.output }, example) !== expected.source) {
|
|
233
|
-
throw new Error(`${example.id}: unexpected fixed source`);
|
|
82
|
+
const expected = example.expectedDiagnostics?.toSorted(byLocation);
|
|
83
|
+
const fixed = actualFixedSource(example, () => {
|
|
84
|
+
tester.run(name, tested, { valid: [], invalid: [{ ...base, errors: expected?.map(identity) ?? example.expectedCount, output: UNREACHABLE_OUTPUT, recursive: true }] });
|
|
85
|
+
});
|
|
86
|
+
if (expected !== void 0) {
|
|
87
|
+
actualFixedSource(example, () => {
|
|
88
|
+
tester.run(name, tested, { valid: [], invalid: [{ ...base, errors: expected.map(message), output: UNREACHABLE_OUTPUT, recursive: true }] });
|
|
89
|
+
});
|
|
90
|
+
if (expected.some((item) => item.automaticFix) !== (fixed !== focus.source)) {
|
|
91
|
+
throw new Error(`${example.id}: automatic fix delivery differs from the expected diagnostics`);
|
|
234
92
|
}
|
|
235
93
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
message.message,
|
|
246
|
-
[message.line, message.column, message.endLine, message.endColumn],
|
|
247
|
-
message.fix !== void 0,
|
|
248
|
-
(message.suggestions ?? []).map((suggestion) => [
|
|
249
|
-
suggestion.messageId,
|
|
250
|
-
applySuggestion(source, suggestion.fix, example.id)
|
|
251
|
-
])
|
|
252
|
-
])).sort();
|
|
253
|
-
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
|
254
|
-
throw new Error(`${example.id}: unexpected diagnostics
|
|
255
|
-
expected: ${JSON.stringify(expected)}
|
|
256
|
-
received: ${JSON.stringify(actual)}`);
|
|
94
|
+
const expectedFixed = example.fixedFiles?.find((file) => file.path === focus.path);
|
|
95
|
+
if (expectedFixed === void 0) return;
|
|
96
|
+
if (formatFixedSource === void 0 && fixed !== expectedFixed.source) throw new Error(`${example.id}: unexpected fixed source`);
|
|
97
|
+
await writeFile(fixturePath(root, focus.path), fixed);
|
|
98
|
+
run(example, () => {
|
|
99
|
+
tester.run(name, tested, { valid: [{ ...base, code: fixed }], invalid: [] });
|
|
100
|
+
});
|
|
101
|
+
if (formatFixedSource !== void 0 && await formatFixedSource({ path: focus.path, source: fixed }, example) !== expectedFixed.source) {
|
|
102
|
+
throw new Error(`${example.id}: unexpected fixed source`);
|
|
257
103
|
}
|
|
258
104
|
}
|
|
259
|
-
function
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
expected.message,
|
|
263
|
-
[expected.location.line, expected.location.column, expected.location.end?.line, expected.location.end?.column],
|
|
264
|
-
expected.automaticFix,
|
|
265
|
-
expected.suggestions.map((suggestion) => [suggestion.messageId, suggestion.output])
|
|
266
|
-
]);
|
|
105
|
+
function byLocation(left, right) {
|
|
106
|
+
const lines = left.location.line - right.location.line;
|
|
107
|
+
return lines === 0 ? left.location.column - right.location.column : lines;
|
|
267
108
|
}
|
|
268
|
-
function
|
|
269
|
-
|
|
270
|
-
const text = bom === "" ? source : source.slice(1);
|
|
271
|
-
const [start, end] = fix.range;
|
|
272
|
-
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start > end || start < (bom === "" ? 0 : -1) || end < 0 || end > text.length) {
|
|
273
|
-
throw new Error(`${exampleId}: invalid suggestion edit range`);
|
|
274
|
-
}
|
|
275
|
-
const prefix = start < 0 && end >= 0 || start === 0 && fix.text.startsWith("\uFEFF") ? "" : bom;
|
|
276
|
-
return prefix + text.slice(0, Math.max(0, start)) + fix.text + text.slice(end);
|
|
109
|
+
function location({ location: { line, column, end } }) {
|
|
110
|
+
return { line, column, endLine: end?.line, endColumn: end?.column };
|
|
277
111
|
}
|
|
278
|
-
function
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
112
|
+
function identity(expected) {
|
|
113
|
+
return {
|
|
114
|
+
...location(expected),
|
|
115
|
+
messageId: expected.messageId,
|
|
116
|
+
suggestions: expected.suggestions.map((suggestion) => ({ messageId: suggestion.messageId, output: suggestion.output }))
|
|
117
|
+
};
|
|
282
118
|
}
|
|
283
|
-
function
|
|
284
|
-
|
|
285
|
-
if (message.endLine === void 0 && message.endColumn === void 0) return true;
|
|
286
|
-
if (!physicalPosition(lines, message.endLine, message.endColumn)) return false;
|
|
287
|
-
return message.endLine !== void 0 && message.endColumn !== void 0 && (message.endLine > message.line || message.endLine === message.line && message.endColumn >= message.column);
|
|
119
|
+
function message(expected) {
|
|
120
|
+
return { ...location(expected), message: expected.message };
|
|
288
121
|
}
|
|
289
|
-
function
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
122
|
+
function run(example, body) {
|
|
123
|
+
const { describe, it } = RuleTester;
|
|
124
|
+
RuleTester.describe = (_text, method) => {
|
|
125
|
+
method();
|
|
126
|
+
};
|
|
127
|
+
RuleTester.it = (_text, method) => {
|
|
128
|
+
method();
|
|
129
|
+
};
|
|
130
|
+
try {
|
|
131
|
+
body();
|
|
132
|
+
} catch (error) {
|
|
133
|
+
throw new Error(`${example.id}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
134
|
+
} finally {
|
|
135
|
+
RuleTester.describe = describe;
|
|
136
|
+
RuleTester.it = it;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function actualFixedSource(example, body) {
|
|
140
|
+
try {
|
|
141
|
+
run(example, body);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
const { cause } = error;
|
|
144
|
+
if (cause instanceof AssertionError && cause.expected === UNREACHABLE_OUTPUT && typeof cause.actual === "string") return cause.actual;
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
throw new Error(`${example.id}: RuleTester accepted an unreachable fixed source`);
|
|
293
148
|
}
|
|
294
149
|
export {
|
|
295
150
|
verifyRuleExamples
|
package/package.json
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sarj/eslint-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "19.0.0",
|
|
4
4
|
"packageManager": "npm@12.2.1",
|
|
5
|
-
"description": "Custom
|
|
5
|
+
"description": "Custom Oxlint rules for hypermodern TypeScript / React / Next.js projects",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"main": "./dist/index.
|
|
8
|
-
"module": "./dist/index.js",
|
|
9
|
-
"types": "./dist/index.d.ts",
|
|
7
|
+
"main": "./dist/index.js",
|
|
10
8
|
"exports": {
|
|
11
9
|
".": {
|
|
12
|
-
"
|
|
13
|
-
"import": "./dist/index.js",
|
|
14
|
-
"require": "./dist/index.cjs"
|
|
10
|
+
"import": "./dist/index.js"
|
|
15
11
|
}
|
|
16
12
|
},
|
|
17
13
|
"files": [
|
|
@@ -28,24 +24,21 @@
|
|
|
28
24
|
"url": "git+https://github.com/sarj-ai/code-standards.git",
|
|
29
25
|
"directory": "packages/typescript"
|
|
30
26
|
},
|
|
31
|
-
"homepage": "https://code-standards.sarj.ai/rules/
|
|
27
|
+
"homepage": "https://code-standards.sarj.ai/rules/oxlint/",
|
|
32
28
|
"bugs": {
|
|
33
29
|
"url": "https://github.com/sarj-ai/code-standards/issues"
|
|
34
30
|
},
|
|
35
31
|
"scripts": {
|
|
36
32
|
"build": "tsup",
|
|
37
|
-
"
|
|
38
|
-
"lint": "node ../standards/src/sarj_standards/configs/eslint-selected-rules.mts \"{\\\"full\\\":true,\\\"config\\\":null,\\\"rules\\\":[],\\\"consoleOutput\\\":true,\\\"failOnWarnings\\\":true}\" -- . ../../.github/scripts ../standards/src ../standards/eslint.config.mts",
|
|
33
|
+
"lint": "(test -f dist/index.js || tsup) && export OXLINT_TSGOLINT_PATH=\"$PWD/node_modules/.bin/tsrs-lint\" && oxlint && cd ../standards && oxlint && cd ../../.github/scripts && oxlint",
|
|
39
34
|
"prepack": "npm run build && npm run verify-package",
|
|
40
35
|
"test": "vitest run",
|
|
41
|
-
"typecheck": "
|
|
42
|
-
"verify-package": "node -e \"const fs=require('node:fs'),p=require('./package.json'),walk=x=>typeof x==='string'?[x]:x&&typeof x==='object'?Object.values(x).flatMap(walk):[];for(const file of new Set([p.main,p.module,p.types,...walk(p.exports)].filter(Boolean).map(x=>x.startsWith('./')?x.slice(2):x))){if(!file.startsWith('dist/'))throw new Error('export must live under dist: '+file);const stat=fs.statSync(file);if(!stat.isFile()||stat.size===0)throw new Error('missing or empty export: '+file)}\""
|
|
43
|
-
"dogfood:built": "eslint --config eslint.dogfood.config.ts src tests scripts eslint-rule-setting.ts eslint.config.ts eslint.dogfood.config.ts module-constant-naming-options.ts tsup.config.ts vitest.config.ts --max-warnings 0 && node -e \"import('./dist/index.js').then(({default:p}) => console.log('dogfood: '+Object.keys(p.rules).length+' TypeScript rules, 0 diagnostics'))\""
|
|
36
|
+
"typecheck": "tsrs --noEmit && tsrs --project tsconfig.tools.json",
|
|
37
|
+
"verify-package": "node -e \"const fs=require('node:fs'),p=require('./package.json'),walk=x=>typeof x==='string'?[x]:x&&typeof x==='object'?Object.values(x).flatMap(walk):[];for(const file of new Set([p.main,p.module,p.types,...walk(p.exports)].filter(Boolean).map(x=>x.startsWith('./')?x.slice(2):x))){if(!file.startsWith('dist/'))throw new Error('export must live under dist: '+file);const stat=fs.statSync(file);if(!stat.isFile()||stat.size===0)throw new Error('missing or empty export: '+file)}\""
|
|
44
38
|
},
|
|
45
39
|
"keywords": [
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"eslint-plugin",
|
|
40
|
+
"oxlint",
|
|
41
|
+
"oxlint-plugin",
|
|
49
42
|
"sarj",
|
|
50
43
|
"react",
|
|
51
44
|
"nextjs",
|
|
@@ -54,57 +47,39 @@
|
|
|
54
47
|
"author": "sarj-ai",
|
|
55
48
|
"license": "MIT",
|
|
56
49
|
"dependencies": {
|
|
57
|
-
"
|
|
58
|
-
"@typescript-eslint/typescript-estree": "^8.72.1",
|
|
59
|
-
"@typescript-eslint/utils": "^8.72.1",
|
|
60
|
-
"jsonc-parser": "^3.3.1",
|
|
61
|
-
"ts-api-utils": "2.5.0"
|
|
50
|
+
"oxc-parser": "0.148.0"
|
|
62
51
|
},
|
|
63
52
|
"devDependencies": {
|
|
64
|
-
"@eslint-
|
|
65
|
-
"@
|
|
66
|
-
"@eslint
|
|
67
|
-
"@shadcn/lint": "0.2.5",
|
|
68
|
-
"@types/eslint-plugin-no-unsanitized": "4.1.0",
|
|
53
|
+
"@eslint-react/eslint-plugin": "5.28.8",
|
|
54
|
+
"@oxlint/plugins": "1.91.0",
|
|
55
|
+
"@tanstack/eslint-plugin-query": "5.106.1",
|
|
69
56
|
"@types/node": "26.6.5",
|
|
70
|
-
"@typescript-eslint/parser": "8.72.1",
|
|
71
|
-
"@typescript-eslint/rule-tester": "8.72.1",
|
|
72
|
-
"@typescript-eslint/utils": "8.72.1",
|
|
73
|
-
"@typescript/native": "npm:typescript@7.0.2",
|
|
74
|
-
"@vitest/eslint-plugin": "1.6.28",
|
|
75
57
|
"eslint": "10.14.0",
|
|
76
58
|
"eslint-node-test": "1.0.1",
|
|
77
|
-
"eslint-plugin-eslint-plugin": "7.6.2",
|
|
78
|
-
"eslint-plugin-better-tailwindcss": "^4.10.0",
|
|
79
|
-
"eslint-plugin-jest": "29.17.0",
|
|
80
59
|
"eslint-plugin-jsdoc": "65.4.4",
|
|
81
|
-
"eslint-plugin-n": "^18.5.0",
|
|
82
|
-
"eslint-plugin-perfectionist": "5.13.0",
|
|
83
60
|
"eslint-plugin-playwright": "2.12.2",
|
|
84
|
-
"eslint-plugin-promise": "^7.3.0",
|
|
85
61
|
"eslint-plugin-react": "^7.37.5",
|
|
86
|
-
"eslint-plugin-react-
|
|
62
|
+
"eslint-plugin-react-you-might-not-need-an-effect": "1.0.2",
|
|
87
63
|
"eslint-plugin-regexp": "3.3.3",
|
|
88
64
|
"eslint-plugin-security": "4.2.1",
|
|
89
|
-
"eslint-plugin-no-unsanitized": "4.1.5",
|
|
90
|
-
"eslint-plugin-simple-import-sort": "^14.0.0",
|
|
91
65
|
"eslint-plugin-testing-library": "7.16.2",
|
|
92
66
|
"eslint-plugin-unicorn": "^79.0.0",
|
|
93
67
|
"eslint-plugin-zod": "^5.2.2",
|
|
94
|
-
"
|
|
95
|
-
"
|
|
68
|
+
"jsonc-parser": "3.3.1",
|
|
69
|
+
"oxfmt": "0.76.0",
|
|
70
|
+
"oxlint": "1.91.0",
|
|
71
|
+
"oxlint-plugin-eslint": "1.91.0",
|
|
96
72
|
"prettier": "3.9.10",
|
|
97
|
-
"tsup": "9.0.0",
|
|
98
73
|
"syncpack": "15.3.3",
|
|
74
|
+
"tsrs": "0.23.0-ts7.1.0-beta",
|
|
75
|
+
"tsup": "9.0.0",
|
|
99
76
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
100
|
-
"typescript-eslint": "^8.72.1",
|
|
101
77
|
"uuid": "13.0.0",
|
|
102
78
|
"vitest": "5.0.5",
|
|
103
79
|
"zod": "4.6.7"
|
|
104
80
|
},
|
|
105
81
|
"peerDependencies": {
|
|
106
|
-
"
|
|
107
|
-
"typescript": ">=5.0.0"
|
|
82
|
+
"tsrs": ">=0.23.0-ts7.1.0-beta"
|
|
108
83
|
},
|
|
109
84
|
"engines": {
|
|
110
85
|
"node": "^22.22.2 || >=24.15.0"
|
|
@@ -113,11 +88,6 @@
|
|
|
113
88
|
"esbuild@0.28.2": true,
|
|
114
89
|
"fsevents@2.3.3": true
|
|
115
90
|
},
|
|
116
|
-
"peerDependenciesMeta": {
|
|
117
|
-
"typescript": {
|
|
118
|
-
"optional": true
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
91
|
"overrides": {
|
|
122
92
|
"brace-expansion@^5.0.0": "5.0.12",
|
|
123
93
|
"esbuild": "0.28.2",
|