@stll/oxlint-plugin 0.5.0 → 0.6.1
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/dist/bun-test-hygiene.cjs +401 -0
- package/dist/bun-test-hygiene.cjs.map +1 -0
- package/dist/bun-test-hygiene.d.cts +4 -0
- package/dist/bun-test-hygiene.d.mts +5 -0
- package/dist/bun-test-hygiene.mjs +397 -0
- package/dist/bun-test-hygiene.mjs.map +1 -0
- package/dist/index.cjs +12 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -19
- package/dist/index.d.mts +27 -19
- package/dist/index.mjs +10 -1
- package/dist/index.mjs.map +1 -1
- package/dist/no-unsafe-inner-html.cjs +210 -44
- package/dist/no-unsafe-inner-html.cjs.map +1 -1
- package/dist/no-unsafe-inner-html.d.cts +20 -3
- package/dist/no-unsafe-inner-html.d.mts +20 -3
- package/dist/no-unsafe-inner-html.mjs +209 -46
- package/dist/no-unsafe-inner-html.mjs.map +1 -1
- package/dist/no-wall-clock-bound.cjs.map +1 -1
- package/dist/no-wall-clock-bound.mjs.map +1 -1
- package/dist/require-loader-prefetch.mjs +1 -1
- package/dist/utils.cjs +76 -3
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.mjs +71 -4
- package/dist/utils.mjs.map +1 -1
- package/package.json +13 -4
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { a as isAstNode, c as memberPropertyName, f as unwrapExpression, l as resolveImportedExpressionForEnforcement, s as isStringLiteral } from "./utils.mjs";
|
|
2
|
+
import { eslintCompatPlugin } from "@oxlint/plugins";
|
|
3
|
+
//#region src/bun-test-hygiene.ts
|
|
4
|
+
const BUN_TEST_MODULE = "bun:test";
|
|
5
|
+
const EXPECT_EXPORT = "expect";
|
|
6
|
+
const FORCED_DATABASE_DROP = /(?:^|;)\s*DROP\s+DATABASE\b[^;]*(?:\bWITH\s*)?\(\s*FORCE\b/iu;
|
|
7
|
+
const SQL_IDENTIFIER_CHARACTER = /[A-Za-z0-9_$\u0080-\u{10ffff}]/u;
|
|
8
|
+
const SQL_DOLLAR_QUOTE = /\$(?:[A-Za-z_\u0080-\u{10ffff}][A-Za-z0-9_\u0080-\u{10ffff}]*)?\$/uy;
|
|
9
|
+
const maskSqlValuesAndComments = (text) => {
|
|
10
|
+
const masked = [];
|
|
11
|
+
let state = { type: "code" };
|
|
12
|
+
for (let index = 0; index < text.length; index++) {
|
|
13
|
+
const character = text.charAt(index);
|
|
14
|
+
const next = text.charAt(index + 1);
|
|
15
|
+
switch (state.type) {
|
|
16
|
+
case "line-comment":
|
|
17
|
+
if (character === "\n" || character === "\r") {
|
|
18
|
+
masked.push(character);
|
|
19
|
+
state = { type: "code" };
|
|
20
|
+
}
|
|
21
|
+
continue;
|
|
22
|
+
case "block-comment":
|
|
23
|
+
if (character === "/" && next === "*") {
|
|
24
|
+
state.depth++;
|
|
25
|
+
index++;
|
|
26
|
+
} else if (character === "*" && next === "/") {
|
|
27
|
+
state.depth--;
|
|
28
|
+
index++;
|
|
29
|
+
if (state.depth === 0) state = { type: "code" };
|
|
30
|
+
}
|
|
31
|
+
continue;
|
|
32
|
+
case "quoted-identifier":
|
|
33
|
+
if (character === "\"") {
|
|
34
|
+
if (next === "\"") index++;
|
|
35
|
+
else state = { type: "code" };
|
|
36
|
+
}
|
|
37
|
+
continue;
|
|
38
|
+
case "quoted-string":
|
|
39
|
+
if (state.syntax === "escaped" && character === "\\") index++;
|
|
40
|
+
else if (character === "'") {
|
|
41
|
+
if (next === "'") index++;
|
|
42
|
+
else state = { type: "code" };
|
|
43
|
+
}
|
|
44
|
+
continue;
|
|
45
|
+
case "dollar-quoted":
|
|
46
|
+
if (text.startsWith(state.delimiter, index)) {
|
|
47
|
+
index += state.delimiter.length - 1;
|
|
48
|
+
state = { type: "code" };
|
|
49
|
+
}
|
|
50
|
+
continue;
|
|
51
|
+
case "code":
|
|
52
|
+
if (character === "-" && next === "-") {
|
|
53
|
+
masked.push(" ");
|
|
54
|
+
state = { type: "line-comment" };
|
|
55
|
+
index++;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (character === "/" && next === "*") {
|
|
59
|
+
masked.push(" ");
|
|
60
|
+
state = {
|
|
61
|
+
type: "block-comment",
|
|
62
|
+
depth: 1
|
|
63
|
+
};
|
|
64
|
+
index++;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (character === "\"") {
|
|
68
|
+
masked.push(" __value__ ");
|
|
69
|
+
state = { type: "quoted-identifier" };
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (character === "'") {
|
|
73
|
+
masked.push(" __value__ ");
|
|
74
|
+
state = {
|
|
75
|
+
type: "quoted-string",
|
|
76
|
+
syntax: /[eE]/u.test(text.charAt(index - 1)) && !SQL_IDENTIFIER_CHARACTER.test(text.charAt(index - 2)) ? "escaped" : "standard"
|
|
77
|
+
};
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (character === "$" && !SQL_IDENTIFIER_CHARACTER.test(text.charAt(index - 1))) {
|
|
81
|
+
SQL_DOLLAR_QUOTE.lastIndex = index;
|
|
82
|
+
const delimiter = SQL_DOLLAR_QUOTE.exec(text)?.at(0);
|
|
83
|
+
if (delimiter !== void 0) {
|
|
84
|
+
masked.push(" __value__ ");
|
|
85
|
+
state = {
|
|
86
|
+
type: "dollar-quoted",
|
|
87
|
+
delimiter
|
|
88
|
+
};
|
|
89
|
+
index += delimiter.length - 1;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
masked.push(character);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return masked.join("");
|
|
98
|
+
};
|
|
99
|
+
const databaseDropText = (node) => {
|
|
100
|
+
if (isStringLiteral(node)) return node.value;
|
|
101
|
+
if (!isAstNode(node) || node.type !== "TemplateLiteral" || !Array.isArray(node.quasis)) return null;
|
|
102
|
+
return node.quasis.map((quasi) => {
|
|
103
|
+
const value = isAstNode(quasi) ? quasi.value : null;
|
|
104
|
+
return typeof value === "object" && value !== null && "cooked" in value && typeof value.cooked === "string" ? value.cooked : "";
|
|
105
|
+
}).join(" __identifier__ ");
|
|
106
|
+
};
|
|
107
|
+
const TEST_KIND = {
|
|
108
|
+
describe: "describe",
|
|
109
|
+
test: "test"
|
|
110
|
+
};
|
|
111
|
+
const TEST_FUNCTIONS = /* @__PURE__ */ new Map([
|
|
112
|
+
["describe", TEST_KIND.describe],
|
|
113
|
+
["xdescribe", TEST_KIND.describe],
|
|
114
|
+
["it", TEST_KIND.test],
|
|
115
|
+
["test", TEST_KIND.test],
|
|
116
|
+
["xit", TEST_KIND.test],
|
|
117
|
+
["xtest", TEST_KIND.test]
|
|
118
|
+
]);
|
|
119
|
+
const DISABLED_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
120
|
+
"xdescribe",
|
|
121
|
+
"xit",
|
|
122
|
+
"xtest"
|
|
123
|
+
]);
|
|
124
|
+
const PROMISE_MATCHER_PROPERTIES = /* @__PURE__ */ new Set(["rejects", "resolves"]);
|
|
125
|
+
const NEGATION_MODIFIER = "not";
|
|
126
|
+
const FOCUS_MODIFIER = "only";
|
|
127
|
+
const DISABLE_MODIFIERS = /* @__PURE__ */ new Set([
|
|
128
|
+
"skip",
|
|
129
|
+
"skipIf",
|
|
130
|
+
"todo",
|
|
131
|
+
"todoIf"
|
|
132
|
+
]);
|
|
133
|
+
const EACH_MODIFIER = "each";
|
|
134
|
+
const FUNCTION_TYPES = /* @__PURE__ */ new Set([
|
|
135
|
+
"ArrowFunctionExpression",
|
|
136
|
+
"FunctionDeclaration",
|
|
137
|
+
"FunctionExpression"
|
|
138
|
+
]);
|
|
139
|
+
const DIRECTIVE_COMMENT = /^\s*(?:oxlint-|eslint-|@ts-|expect-clean:|fixture-expect\b|prettier-ignore)/u;
|
|
140
|
+
const HAS_LETTER = /\p{L}/u;
|
|
141
|
+
const isOutermostCall = (call) => {
|
|
142
|
+
const parent = call.parent;
|
|
143
|
+
if (!isAstNode(parent)) return true;
|
|
144
|
+
if (parent.type === "CallExpression" && parent.callee === call) return false;
|
|
145
|
+
return !(parent.type === "MemberExpression" && parent.object === call);
|
|
146
|
+
};
|
|
147
|
+
const testFunctionReference = (context, expression) => {
|
|
148
|
+
const modifiers = [];
|
|
149
|
+
let current = unwrapExpression(expression);
|
|
150
|
+
while (current !== null) {
|
|
151
|
+
const binding = resolveImportedExpressionForEnforcement(context, current);
|
|
152
|
+
const kind = binding?.source === BUN_TEST_MODULE ? TEST_FUNCTIONS.get(binding.imported) : void 0;
|
|
153
|
+
if (binding !== null && kind !== void 0) return {
|
|
154
|
+
exportName: binding.imported,
|
|
155
|
+
kind,
|
|
156
|
+
modifiers
|
|
157
|
+
};
|
|
158
|
+
if (current.type === "MemberExpression") {
|
|
159
|
+
const modifier = memberPropertyName(current);
|
|
160
|
+
if (modifier === null) return null;
|
|
161
|
+
modifiers.unshift(modifier);
|
|
162
|
+
current = unwrapExpression(current.object);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (current.type !== "CallExpression") return null;
|
|
166
|
+
current = unwrapExpression(current.callee);
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
};
|
|
170
|
+
const testRegistration = (context, call) => {
|
|
171
|
+
if (!isAstNode(call) || call.type !== "CallExpression" || !isOutermostCall(call)) return null;
|
|
172
|
+
const reference = testFunctionReference(context, call.callee);
|
|
173
|
+
return reference === null ? null : {
|
|
174
|
+
call,
|
|
175
|
+
...reference
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
const describeRegistration = ({ exportName, modifiers }) => [exportName, ...modifiers].join(".");
|
|
179
|
+
const lineStartOf = (text, offset) => text.lastIndexOf("\n", offset - 1) + 1;
|
|
180
|
+
const lineEndOf = (text, offset) => {
|
|
181
|
+
const end = text.indexOf("\n", offset);
|
|
182
|
+
return end === -1 ? text.length : end;
|
|
183
|
+
};
|
|
184
|
+
const isReasonComment = (comment) => !DIRECTIVE_COMMENT.test(comment.value) && HAS_LETTER.test(comment.value);
|
|
185
|
+
const hasReasonComment = ({ comments, node, text }) => {
|
|
186
|
+
const lineStart = lineStartOf(text, node.range[0]);
|
|
187
|
+
const lineEnd = lineEndOf(text, node.range[0]);
|
|
188
|
+
if (comments.some((comment) => comment.range[0] >= lineStart && comment.range[0] < lineEnd && isReasonComment(comment))) return true;
|
|
189
|
+
let boundary = lineStart;
|
|
190
|
+
for (const comment of comments.toReversed()) {
|
|
191
|
+
if (comment.range[1] > boundary) continue;
|
|
192
|
+
const gap = text.slice(comment.range[1], boundary);
|
|
193
|
+
const commentLineStart = lineStartOf(text, comment.range[0]);
|
|
194
|
+
if (gap.trim() !== "" || gap.split("\n").length > 2 || text.slice(commentLineStart, comment.range[0]).trim() !== "") return false;
|
|
195
|
+
if (isReasonComment(comment)) return true;
|
|
196
|
+
boundary = commentLineStart;
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
};
|
|
200
|
+
const isSourceComment = (value) => typeof value === "object" && value !== null && "value" in value && typeof value.value === "string" && "range" in value && Array.isArray(value.range) && value.range.length === 2 && value.range.every((offset) => typeof offset === "number");
|
|
201
|
+
const staticTitle = (node) => {
|
|
202
|
+
const title = unwrapExpression(node);
|
|
203
|
+
if (isStringLiteral(title)) return title.value;
|
|
204
|
+
if (!isAstNode(title) || title.type !== "TemplateLiteral" || !Array.isArray(title.expressions) || title.expressions.length > 0 || !Array.isArray(title.quasis)) return null;
|
|
205
|
+
const quasi = title.quasis.at(0);
|
|
206
|
+
if (!isAstNode(quasi)) return null;
|
|
207
|
+
const value = quasi.value;
|
|
208
|
+
return typeof value === "object" && value !== null && "cooked" in value && typeof value.cooked === "string" ? value.cooked : null;
|
|
209
|
+
};
|
|
210
|
+
const isConditionalBranch = (node) => {
|
|
211
|
+
const parent = node.parent;
|
|
212
|
+
if (!isAstNode(parent)) return false;
|
|
213
|
+
if (parent.type === "IfStatement" || parent.type === "ConditionalExpression") return parent.consequent === node || parent.alternate === node;
|
|
214
|
+
return parent.type === "LogicalExpression" && parent.right === node;
|
|
215
|
+
};
|
|
216
|
+
const enclosingBlock = (node) => {
|
|
217
|
+
let current = node;
|
|
218
|
+
while (isAstNode(current)) {
|
|
219
|
+
if (isConditionalBranch(current)) return {
|
|
220
|
+
node: current,
|
|
221
|
+
conditional: true
|
|
222
|
+
};
|
|
223
|
+
const parent = current.parent;
|
|
224
|
+
if (isAstNode(parent) && (FUNCTION_TYPES.has(parent.type) || parent.type === "Program")) return {
|
|
225
|
+
node: parent,
|
|
226
|
+
conditional: false
|
|
227
|
+
};
|
|
228
|
+
current = parent;
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
node: null,
|
|
232
|
+
conditional: false
|
|
233
|
+
};
|
|
234
|
+
};
|
|
235
|
+
const mentionsBunTest = (text) => text.includes(BUN_TEST_MODULE);
|
|
236
|
+
var bun_test_hygiene_default = eslintCompatPlugin({
|
|
237
|
+
meta: { name: "bun-test-hygiene" },
|
|
238
|
+
rules: {
|
|
239
|
+
"no-forced-database-drop": {
|
|
240
|
+
meta: {
|
|
241
|
+
type: "problem",
|
|
242
|
+
messages: { forcedDrop: "Close fixture-owned database clients before a normal DROP DATABASE. FORCE requires backend-termination privileges and can terminate another fixture's sessions." }
|
|
243
|
+
},
|
|
244
|
+
createOnce(context) {
|
|
245
|
+
const report = (node) => {
|
|
246
|
+
const text = databaseDropText(node);
|
|
247
|
+
if (text !== null && FORCED_DATABASE_DROP.test(maskSqlValuesAndComments(text))) context.report({
|
|
248
|
+
node,
|
|
249
|
+
messageId: "forcedDrop"
|
|
250
|
+
});
|
|
251
|
+
};
|
|
252
|
+
return {
|
|
253
|
+
before() {
|
|
254
|
+
return /\bFORCE\b|\\[ux]/iu.test(context.sourceCode.text);
|
|
255
|
+
},
|
|
256
|
+
Literal: report,
|
|
257
|
+
TemplateLiteral: report
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
"no-focused-tests": {
|
|
262
|
+
meta: {
|
|
263
|
+
type: "problem",
|
|
264
|
+
messages: { focused: "`{{call}}` focuses the run on this test, so every other test in the file silently stops running. Remove `.only` before committing." }
|
|
265
|
+
},
|
|
266
|
+
createOnce(context) {
|
|
267
|
+
return {
|
|
268
|
+
before() {
|
|
269
|
+
return mentionsBunTest(context.sourceCode.text);
|
|
270
|
+
},
|
|
271
|
+
MemberExpression(node) {
|
|
272
|
+
if (!isAstNode(node) || memberPropertyName(node) !== FOCUS_MODIFIER) return;
|
|
273
|
+
context.report({
|
|
274
|
+
node,
|
|
275
|
+
messageId: "focused",
|
|
276
|
+
data: { call: ".only" }
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
"no-disabled-tests": {
|
|
283
|
+
meta: {
|
|
284
|
+
type: "problem",
|
|
285
|
+
messages: { disabled: "`{{call}}` disables this test without a stated reason. Add a comment on the same line or directly above saying why it is disabled and what re-enables it, or remove it." }
|
|
286
|
+
},
|
|
287
|
+
createOnce(context) {
|
|
288
|
+
let comments = null;
|
|
289
|
+
return {
|
|
290
|
+
before() {
|
|
291
|
+
comments = null;
|
|
292
|
+
return mentionsBunTest(context.sourceCode.text);
|
|
293
|
+
},
|
|
294
|
+
MemberExpression(node) {
|
|
295
|
+
if (!isAstNode(node) || !DISABLE_MODIFIERS.has(memberPropertyName(node) ?? "")) return;
|
|
296
|
+
comments ??= context.sourceCode.getAllComments().filter(isSourceComment);
|
|
297
|
+
if (hasReasonComment({
|
|
298
|
+
comments,
|
|
299
|
+
node,
|
|
300
|
+
text: context.sourceCode.text
|
|
301
|
+
})) return;
|
|
302
|
+
context.report({
|
|
303
|
+
node,
|
|
304
|
+
messageId: "disabled",
|
|
305
|
+
data: { call: `.${memberPropertyName(node)}` }
|
|
306
|
+
});
|
|
307
|
+
},
|
|
308
|
+
CallExpression(node) {
|
|
309
|
+
const registration = testRegistration(context, node);
|
|
310
|
+
if (registration === null || !DISABLED_FUNCTIONS.has(registration.exportName)) return;
|
|
311
|
+
comments ??= context.sourceCode.getAllComments().filter(isSourceComment);
|
|
312
|
+
if (hasReasonComment({
|
|
313
|
+
comments,
|
|
314
|
+
node: registration.call,
|
|
315
|
+
text: context.sourceCode.text
|
|
316
|
+
})) return;
|
|
317
|
+
context.report({
|
|
318
|
+
node,
|
|
319
|
+
messageId: "disabled",
|
|
320
|
+
data: { call: describeRegistration(registration) }
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
"no-promise-matchers": {
|
|
327
|
+
meta: {
|
|
328
|
+
type: "problem",
|
|
329
|
+
messages: { promiseMatcher: "`expect(...).{{property}}` can settle after the test ends when it is not awaited. `await` the promise instead (`expect(await promise)...`), or capture the rejection with `rejectionOf` from `@stll/property-testing/rejection` (`expect(await rejectionOf(promise))...`)." }
|
|
330
|
+
},
|
|
331
|
+
createOnce(context) {
|
|
332
|
+
return {
|
|
333
|
+
before() {
|
|
334
|
+
return mentionsBunTest(context.sourceCode.text);
|
|
335
|
+
},
|
|
336
|
+
MemberExpression(node) {
|
|
337
|
+
if (!isAstNode(node)) return;
|
|
338
|
+
const property = memberPropertyName(node);
|
|
339
|
+
if (property === null || !PROMISE_MATCHER_PROPERTIES.has(property)) return;
|
|
340
|
+
let subject = unwrapExpression(node.object);
|
|
341
|
+
while (isAstNode(subject) && subject.type === "MemberExpression" && memberPropertyName(subject) === NEGATION_MODIFIER) subject = unwrapExpression(subject.object);
|
|
342
|
+
if (!isAstNode(subject) || subject.type !== "CallExpression") return;
|
|
343
|
+
const binding = resolveImportedExpressionForEnforcement(context, subject.callee);
|
|
344
|
+
if (binding?.source !== BUN_TEST_MODULE || binding.imported !== EXPECT_EXPORT) return;
|
|
345
|
+
context.report({
|
|
346
|
+
node,
|
|
347
|
+
messageId: "promiseMatcher",
|
|
348
|
+
data: { property }
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
"no-identical-title": {
|
|
355
|
+
meta: {
|
|
356
|
+
type: "problem",
|
|
357
|
+
messages: { identicalTitle: "Another {{kind}} in this block is already titled '{{title}}'. Rename one to say what differs, so a failure names the case that failed." }
|
|
358
|
+
},
|
|
359
|
+
createOnce(context) {
|
|
360
|
+
const titlesByBlock = /* @__PURE__ */ new Map();
|
|
361
|
+
return {
|
|
362
|
+
before() {
|
|
363
|
+
titlesByBlock.clear();
|
|
364
|
+
return mentionsBunTest(context.sourceCode.text);
|
|
365
|
+
},
|
|
366
|
+
CallExpression(node) {
|
|
367
|
+
const registration = testRegistration(context, node);
|
|
368
|
+
if (registration === null || registration.modifiers.includes(EACH_MODIFIER)) return;
|
|
369
|
+
const { arguments: args } = registration.call;
|
|
370
|
+
const title = staticTitle(Array.isArray(args) ? args.at(0) : void 0);
|
|
371
|
+
if (title === null) return;
|
|
372
|
+
const block = enclosingBlock(registration.call).node;
|
|
373
|
+
const seen = titlesByBlock.get(block) ?? /* @__PURE__ */ new Set();
|
|
374
|
+
titlesByBlock.set(block, seen);
|
|
375
|
+
const key = `${registration.kind}\u0000${title}`;
|
|
376
|
+
if (!seen.has(key)) {
|
|
377
|
+
seen.add(key);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
context.report({
|
|
381
|
+
node,
|
|
382
|
+
messageId: "identicalTitle",
|
|
383
|
+
data: {
|
|
384
|
+
kind: registration.kind,
|
|
385
|
+
title
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
//#endregion
|
|
395
|
+
export { bun_test_hygiene_default as default };
|
|
396
|
+
|
|
397
|
+
//# sourceMappingURL=bun-test-hygiene.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bun-test-hygiene.mjs","names":[],"sources":["../src/bun-test-hygiene.ts"],"sourcesContent":["// Test-run hygiene for suites written against `bun:test`.\n//\n// Oxlint's jest and vitest rules recognise their own frameworks' imports and\n// globals, not `bun:test`, so a committed `test.only` or an unexplained\n// `test.skip` in a Bun suite passes lint. These rules resolve each test\n// function through its import (named, aliased, or a namespace member such as\n// `bt.test`) and only consider calls that reach a `bun:test` export. Focus\n// detection deliberately fails closed: in a Bun test file, every `.only`\n// property access is rejected regardless of the object it belongs to.\n//\n// no-focused-tests\n// Flagged wherever referenced: test.only, it.only, describe.only,\n// test[\"only\"], test.only.each(table), test.if(condition).only\n//\n// no-disabled-tests\n// Flagged without a reason comment on the same line or directly above:\n// every `.skip`/`.skipIf`/`.todo`/`.todoIf` property access in a\n// Bun test file, plus bun:test's xtest(...), xit(...), and\n// xdescribe(...) exports.\n//\n// no-forced-database-drop\n// Flagged: SQL string and template literals that use DROP DATABASE with\n// the FORCE option outside quoted values and comments.\n//\n// no-identical-title\n// Flagged: two tests (or two describe blocks) with the same static title in\n// the same block. Titles built from expressions and `.each` format\n// strings are not compared, and each branch of a conditional is a\n// block of its own, so the skipped and the real registration of\n// one suite may share a title.\n//\n// no-promise-matchers\n// Flagged: expect(promise).rejects.*, expect(promise).resolves.*, also\n// after `.not`, on the `bun:test` `expect` (named, aliased, or a\n// namespace member).\n// An `expect(...).rejects` that is not awaited can settle after the\n// test ends, so its failure lands on a later test or is lost.\n// Allowed: `expect(await promise)...` for a resolved value, and\n// `expect(await rejectionOf(promise))...` (from\n// `@stll/property-testing/rejection`) for a rejection. Another\n// framework's `expect` (Playwright) is not considered.\n\nimport { eslintCompatPlugin } from \"@oxlint/plugins\";\n\nimport {\n isAstNode,\n isStringLiteral,\n memberPropertyName,\n resolveImportedExpressionForEnforcement,\n unwrapExpression,\n} from \"./utils.ts\";\nimport type { AstNode, ScopeContext } from \"./utils.ts\";\n\nconst BUN_TEST_MODULE = \"bun:test\";\nconst EXPECT_EXPORT = \"expect\";\n\nconst FORCED_DATABASE_DROP =\n /(?:^|;)\\s*DROP\\s+DATABASE\\b[^;]*(?:\\bWITH\\s*)?\\(\\s*FORCE\\b/iu;\nconst SQL_IDENTIFIER_CHARACTER = /[A-Za-z0-9_$\\u0080-\\u{10ffff}]/u;\nconst SQL_DOLLAR_QUOTE =\n /\\$(?:[A-Za-z_\\u0080-\\u{10ffff}][A-Za-z0-9_\\u0080-\\u{10ffff}]*)?\\$/uy;\n\ntype SqlMaskState =\n | { type: \"code\" }\n | { type: \"line-comment\" }\n | { type: \"block-comment\"; depth: number }\n | { type: \"quoted-identifier\" }\n | { type: \"quoted-string\"; syntax: \"standard\" | \"escaped\" }\n | { type: \"dollar-quoted\"; delimiter: string };\n\n// A quote inside a comment belongs to that comment, and vice versa.\nconst maskSqlValuesAndComments = (text: string) => {\n const masked: string[] = [];\n let state: SqlMaskState = { type: \"code\" };\n for (let index = 0; index < text.length; index++) {\n const character = text.charAt(index);\n const next = text.charAt(index + 1);\n switch (state.type) {\n case \"line-comment\":\n if (character === \"\\n\" || character === \"\\r\") {\n masked.push(character);\n state = { type: \"code\" };\n }\n continue;\n case \"block-comment\":\n if (character === \"/\" && next === \"*\") {\n state.depth++;\n index++;\n } else if (character === \"*\" && next === \"/\") {\n state.depth--;\n index++;\n if (state.depth === 0) {\n state = { type: \"code\" };\n }\n }\n continue;\n case \"quoted-identifier\":\n if (character === '\"') {\n if (next === '\"') {\n index++;\n } else {\n state = { type: \"code\" };\n }\n }\n continue;\n case \"quoted-string\":\n if (state.syntax === \"escaped\" && character === \"\\\\\") {\n index++;\n } else if (character === \"'\") {\n if (next === \"'\") {\n index++;\n } else {\n state = { type: \"code\" };\n }\n }\n continue;\n case \"dollar-quoted\":\n if (text.startsWith(state.delimiter, index)) {\n index += state.delimiter.length - 1;\n state = { type: \"code\" };\n }\n continue;\n case \"code\":\n if (character === \"-\" && next === \"-\") {\n masked.push(\" \");\n state = { type: \"line-comment\" };\n index++;\n continue;\n }\n if (character === \"/\" && next === \"*\") {\n masked.push(\" \");\n state = { type: \"block-comment\", depth: 1 };\n index++;\n continue;\n }\n if (character === '\"') {\n masked.push(\" __value__ \");\n state = { type: \"quoted-identifier\" };\n continue;\n }\n if (character === \"'\") {\n masked.push(\" __value__ \");\n state = {\n type: \"quoted-string\",\n syntax:\n /[eE]/u.test(text.charAt(index - 1)) &&\n !SQL_IDENTIFIER_CHARACTER.test(text.charAt(index - 2))\n ? \"escaped\"\n : \"standard\",\n };\n continue;\n }\n if (\n character === \"$\" &&\n !SQL_IDENTIFIER_CHARACTER.test(text.charAt(index - 1))\n ) {\n SQL_DOLLAR_QUOTE.lastIndex = index;\n const delimiter = SQL_DOLLAR_QUOTE.exec(text)?.at(0);\n if (delimiter !== undefined) {\n masked.push(\" __value__ \");\n state = { type: \"dollar-quoted\", delimiter };\n index += delimiter.length - 1;\n continue;\n }\n }\n masked.push(character);\n continue;\n }\n }\n return masked.join(\"\");\n};\n\nconst databaseDropText = (node: unknown) => {\n if (isStringLiteral(node)) {\n return node.value;\n }\n if (\n !isAstNode(node) ||\n node.type !== \"TemplateLiteral\" ||\n !Array.isArray(node.quasis)\n ) {\n return null;\n }\n return node.quasis\n .map((quasi: unknown) => {\n const value = isAstNode(quasi) ? quasi.value : null;\n return typeof value === \"object\" &&\n value !== null &&\n \"cooked\" in value &&\n typeof value.cooked === \"string\"\n ? value.cooked\n : \"\";\n })\n .join(\" __identifier__ \");\n};\n\nconst TEST_KIND = { describe: \"describe\", test: \"test\" } as const;\ntype TestKind = (typeof TEST_KIND)[keyof typeof TEST_KIND];\n\n// `bun:test` exports that register a test or a block, by what they register.\nconst TEST_FUNCTIONS: ReadonlyMap<string, TestKind> = new Map([\n [\"describe\", TEST_KIND.describe],\n [\"xdescribe\", TEST_KIND.describe],\n [\"it\", TEST_KIND.test],\n [\"test\", TEST_KIND.test],\n [\"xit\", TEST_KIND.test],\n [\"xtest\", TEST_KIND.test],\n]);\n\n// Exports that are the disabled form of a test function themselves.\nconst DISABLED_FUNCTIONS = new Set([\"xdescribe\", \"xit\", \"xtest\"]);\n\nconst PROMISE_MATCHER_PROPERTIES = new Set([\"rejects\", \"resolves\"]);\nconst NEGATION_MODIFIER = \"not\";\n\nconst FOCUS_MODIFIER = \"only\";\nconst DISABLE_MODIFIERS = new Set([\"skip\", \"skipIf\", \"todo\", \"todoIf\"]);\nconst EACH_MODIFIER = \"each\";\n\nconst FUNCTION_TYPES = new Set([\n \"ArrowFunctionExpression\",\n \"FunctionDeclaration\",\n \"FunctionExpression\",\n]);\n\n// Comments that configure tooling rather than explain anything. The fixture\n// harness (scripts/check-oxlint-fixture-counts.ts) rewrites disable directives\n// into `fixture-expect` markers, which are directives too.\nconst DIRECTIVE_COMMENT =\n /^\\s*(?:oxlint-|eslint-|@ts-|expect-clean:|fixture-expect\\b|prettier-ignore)/u;\nconst HAS_LETTER = /\\p{L}/u;\n\ntype TestRegistration = {\n call: AstNode;\n exportName: string;\n kind: TestKind;\n // Member and call modifiers between the export and the registering call,\n // in source order: `test.only.each(table)(...)` has [\"only\", \"each\"].\n modifiers: string[];\n};\n\ntype TestFunctionReference = Omit<TestRegistration, \"call\">;\n\n// A call is the registering one when nothing further chains off it:\n// `test.if(condition)` inside `test.if(condition)(\"title\", fn)` only selects\n// a modifier, and reporting both would count the same test twice.\nconst isOutermostCall = (call: AstNode): boolean => {\n const parent = call.parent;\n if (!isAstNode(parent)) {\n return true;\n }\n if (parent.type === \"CallExpression\" && parent.callee === call) {\n return false;\n }\n return !(parent.type === \"MemberExpression\" && parent.object === call);\n};\n\n// The `bun:test` registration a call performs, following the callee chain\n// through member modifiers (`.only`, `[\"skip\"]`) and modifier calls\n// (`.if(condition)`, `.each(table)`) down to the imported test function.\nconst testFunctionReference = (\n context: ScopeContext,\n expression: unknown,\n): TestFunctionReference | null => {\n const modifiers: string[] = [];\n let current = unwrapExpression(expression);\n while (current !== null) {\n const binding = resolveImportedExpressionForEnforcement(context, current);\n const kind =\n binding?.source === BUN_TEST_MODULE\n ? TEST_FUNCTIONS.get(binding.imported)\n : undefined;\n if (binding !== null && kind !== undefined) {\n return { exportName: binding.imported, kind, modifiers };\n }\n if (current.type === \"MemberExpression\") {\n const modifier = memberPropertyName(current);\n if (modifier === null) {\n return null;\n }\n modifiers.unshift(modifier);\n current = unwrapExpression(current.object);\n continue;\n }\n if (current.type !== \"CallExpression\") {\n return null;\n }\n current = unwrapExpression(current.callee);\n }\n return null;\n};\n\nconst testRegistration = (\n context: ScopeContext,\n call: unknown,\n): TestRegistration | null => {\n if (\n !isAstNode(call) ||\n call.type !== \"CallExpression\" ||\n !isOutermostCall(call)\n ) {\n return null;\n }\n const reference = testFunctionReference(context, call.callee);\n return reference === null ? null : { call, ...reference };\n};\n\nconst describeRegistration = ({\n exportName,\n modifiers,\n}: TestFunctionReference): string => [exportName, ...modifiers].join(\".\");\n\ntype SourceComment = { range: [number, number]; value: string };\n\nconst lineStartOf = (text: string, offset: number): number =>\n text.lastIndexOf(\"\\n\", offset - 1) + 1;\n\nconst lineEndOf = (text: string, offset: number): number => {\n const end = text.indexOf(\"\\n\", offset);\n return end === -1 ? text.length : end;\n};\n\nconst isReasonComment = (comment: SourceComment): boolean =>\n !DIRECTIVE_COMMENT.test(comment.value) && HAS_LETTER.test(comment.value);\n\ntype ReasonLookup = {\n comments: readonly SourceComment[];\n node: AstNode;\n text: string;\n};\n\n// A reason is a non-directive comment on the call's first line, or in the\n// unbroken run of own-line comments directly above it (directives such as a\n// disable comment may sit in that run).\nconst hasReasonComment = ({ comments, node, text }: ReasonLookup): boolean => {\n const lineStart = lineStartOf(text, node.range[0]);\n const lineEnd = lineEndOf(text, node.range[0]);\n if (\n comments.some(\n (comment) =>\n comment.range[0] >= lineStart &&\n comment.range[0] < lineEnd &&\n isReasonComment(comment),\n )\n ) {\n return true;\n }\n let boundary = lineStart;\n for (const comment of comments.toReversed()) {\n if (comment.range[1] > boundary) {\n continue;\n }\n const gap = text.slice(comment.range[1], boundary);\n const commentLineStart = lineStartOf(text, comment.range[0]);\n if (\n gap.trim() !== \"\" ||\n gap.split(\"\\n\").length > 2 ||\n text.slice(commentLineStart, comment.range[0]).trim() !== \"\"\n ) {\n return false;\n }\n if (isReasonComment(comment)) {\n return true;\n }\n boundary = commentLineStart;\n }\n return false;\n};\n\nconst isSourceComment = (value: unknown): value is SourceComment =>\n typeof value === \"object\" &&\n value !== null &&\n \"value\" in value &&\n typeof value.value === \"string\" &&\n \"range\" in value &&\n Array.isArray(value.range) &&\n value.range.length === 2 &&\n value.range.every((offset) => typeof offset === \"number\");\n\n// A title the rule can compare: a string literal or a template without\n// substitutions.\nconst staticTitle = (node: unknown): string | null => {\n const title = unwrapExpression(node);\n if (isStringLiteral(title)) {\n return title.value;\n }\n if (\n !isAstNode(title) ||\n title.type !== \"TemplateLiteral\" ||\n !Array.isArray(title.expressions) ||\n title.expressions.length > 0 ||\n !Array.isArray(title.quasis)\n ) {\n return null;\n }\n const quasi: unknown = title.quasis.at(0);\n if (!isAstNode(quasi)) {\n return null;\n }\n // `TemplateElement.value` is a plain `{ raw, cooked }` record, not a node.\n const value: unknown = quasi.value;\n return typeof value === \"object\" &&\n value !== null &&\n \"cooked\" in value &&\n typeof value.cooked === \"string\"\n ? value.cooked\n : null;\n};\n\n// Whether `node` is one of the mutually exclusive arms of its parent.\nconst isConditionalBranch = (node: AstNode): boolean => {\n const parent = node.parent;\n if (!isAstNode(parent)) {\n return false;\n }\n if (\n parent.type === \"IfStatement\" ||\n parent.type === \"ConditionalExpression\"\n ) {\n return parent.consequent === node || parent.alternate === node;\n }\n return parent.type === \"LogicalExpression\" && parent.right === node;\n};\n\ntype EnclosingBlock = { node: unknown; conditional: boolean };\n\n// The block a registration belongs to: the nearest enclosing function (a\n// describe callback, or a helper), conditional branch, or the file itself.\nconst enclosingBlock = (node: AstNode): EnclosingBlock => {\n let current: unknown = node;\n while (isAstNode(current)) {\n if (isConditionalBranch(current)) {\n return { node: current, conditional: true };\n }\n const parent = current.parent;\n if (\n isAstNode(parent) &&\n (FUNCTION_TYPES.has(parent.type) || parent.type === \"Program\")\n ) {\n return { node: parent, conditional: false };\n }\n current = parent;\n }\n return { node: null, conditional: false };\n};\n\nconst mentionsBunTest = (text: string): boolean =>\n text.includes(BUN_TEST_MODULE);\n\nexport default eslintCompatPlugin({\n meta: { name: \"bun-test-hygiene\" },\n rules: {\n \"no-forced-database-drop\": {\n meta: {\n type: \"problem\",\n messages: {\n forcedDrop:\n \"Close fixture-owned database clients before a normal DROP DATABASE. FORCE requires backend-termination privileges and can terminate another fixture's sessions.\",\n },\n },\n createOnce(context) {\n const report = (\n node: NonNullable<Parameters<typeof context.report>[0][\"node\"]>,\n ) => {\n const text = databaseDropText(node);\n if (\n text !== null &&\n FORCED_DATABASE_DROP.test(maskSqlValuesAndComments(text))\n ) {\n context.report({ node, messageId: \"forcedDrop\" });\n }\n };\n return {\n before() {\n return /\\bFORCE\\b|\\\\[ux]/iu.test(context.sourceCode.text);\n },\n Literal: report,\n TemplateLiteral: report,\n };\n },\n },\n \"no-focused-tests\": {\n meta: {\n type: \"problem\",\n messages: {\n focused:\n \"`{{call}}` focuses the run on this test, so every other test in \" +\n \"the file silently stops running. Remove `.only` before committing.\",\n },\n },\n createOnce(context) {\n return {\n before() {\n return mentionsBunTest(context.sourceCode.text);\n },\n MemberExpression(node) {\n if (\n !isAstNode(node) ||\n memberPropertyName(node) !== FOCUS_MODIFIER\n ) {\n return;\n }\n context.report({\n node,\n messageId: \"focused\",\n data: { call: \".only\" },\n });\n },\n };\n },\n },\n \"no-disabled-tests\": {\n meta: {\n type: \"problem\",\n messages: {\n disabled:\n \"`{{call}}` disables this test without a stated reason. Add a \" +\n \"comment on the same line or directly above saying why it is \" +\n \"disabled and what re-enables it, or remove it.\",\n },\n },\n createOnce(context) {\n let comments: readonly SourceComment[] | null = null;\n\n return {\n before() {\n comments = null;\n return mentionsBunTest(context.sourceCode.text);\n },\n MemberExpression(node) {\n if (\n !isAstNode(node) ||\n !DISABLE_MODIFIERS.has(memberPropertyName(node) ?? \"\")\n ) {\n return;\n }\n comments ??= context.sourceCode\n .getAllComments()\n .filter(isSourceComment);\n if (\n hasReasonComment({\n comments,\n node,\n text: context.sourceCode.text,\n })\n ) {\n return;\n }\n context.report({\n node,\n messageId: \"disabled\",\n data: { call: `.${memberPropertyName(node)}` },\n });\n },\n CallExpression(node) {\n const registration = testRegistration(context, node);\n if (\n registration === null ||\n !DISABLED_FUNCTIONS.has(registration.exportName)\n ) {\n return;\n }\n comments ??= context.sourceCode\n .getAllComments()\n .filter(isSourceComment);\n if (\n hasReasonComment({\n comments,\n node: registration.call,\n text: context.sourceCode.text,\n })\n ) {\n return;\n }\n context.report({\n node,\n messageId: \"disabled\",\n data: { call: describeRegistration(registration) },\n });\n },\n };\n },\n },\n \"no-promise-matchers\": {\n meta: {\n type: \"problem\",\n messages: {\n promiseMatcher:\n \"`expect(...).{{property}}` can settle after the test ends when \" +\n \"it is not awaited. `await` the promise instead \" +\n \"(`expect(await promise)...`), or capture the rejection with \" +\n \"`rejectionOf` from `@stll/property-testing/rejection` \" +\n \"(`expect(await rejectionOf(promise))...`).\",\n },\n },\n createOnce(context) {\n return {\n before() {\n return mentionsBunTest(context.sourceCode.text);\n },\n MemberExpression(node) {\n if (!isAstNode(node)) {\n return;\n }\n const property = memberPropertyName(node);\n if (\n property === null ||\n !PROMISE_MATCHER_PROPERTIES.has(property)\n ) {\n return;\n }\n // `expect(p).not.rejects` puts the modifier between the call and\n // the matcher property.\n let subject = unwrapExpression(node.object);\n while (\n isAstNode(subject) &&\n subject.type === \"MemberExpression\" &&\n memberPropertyName(subject) === NEGATION_MODIFIER\n ) {\n subject = unwrapExpression(subject.object);\n }\n if (!isAstNode(subject) || subject.type !== \"CallExpression\") {\n return;\n }\n const binding = resolveImportedExpressionForEnforcement(\n context,\n subject.callee,\n );\n if (\n binding?.source !== BUN_TEST_MODULE ||\n binding.imported !== EXPECT_EXPORT\n ) {\n return;\n }\n context.report({\n node,\n messageId: \"promiseMatcher\",\n data: { property },\n });\n },\n };\n },\n },\n \"no-identical-title\": {\n meta: {\n type: \"problem\",\n messages: {\n identicalTitle:\n \"Another {{kind}} in this block is already titled '{{title}}'. \" +\n \"Rename one to say what differs, so a failure names the case \" +\n \"that failed.\",\n },\n },\n createOnce(context) {\n // Titles seen per enclosing block, keyed by kind and title.\n const titlesByBlock = new Map<unknown, Set<string>>();\n\n return {\n before() {\n titlesByBlock.clear();\n return mentionsBunTest(context.sourceCode.text);\n },\n CallExpression(node) {\n const registration = testRegistration(context, node);\n if (\n registration === null ||\n registration.modifiers.includes(EACH_MODIFIER)\n ) {\n return;\n }\n const { arguments: args } = registration.call;\n const title = staticTitle(\n Array.isArray(args) ? args.at(0) : undefined,\n );\n if (title === null) {\n return;\n }\n const block = enclosingBlock(registration.call).node;\n const seen = titlesByBlock.get(block) ?? new Set<string>();\n titlesByBlock.set(block, seen);\n const key = `${registration.kind}\\u0000${title}`;\n if (!seen.has(key)) {\n seen.add(key);\n return;\n }\n context.report({\n node,\n messageId: \"identicalTitle\",\n data: { kind: registration.kind, title },\n });\n },\n };\n },\n },\n },\n});\n"],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -6,6 +6,15 @@ const noPartialRecordSatisfiesPluginSpecifier = "@stll/oxlint-plugin/no-partial-
|
|
|
6
6
|
const noUnsafeInnerHtmlPluginSpecifier = "@stll/oxlint-plugin/no-unsafe-inner-html";
|
|
7
7
|
const noRawRouteQueryClientPluginSpecifier = "@stll/oxlint-plugin/no-raw-route-query-client";
|
|
8
8
|
const requireLoaderPrefetchPluginSpecifier = "@stll/oxlint-plugin/require-loader-prefetch";
|
|
9
|
+
const bunTestHygienePluginSpecifier = "@stll/oxlint-plugin/bun-test-hygiene";
|
|
10
|
+
const bunTestHygienePluginSpecifiers = [bunTestHygienePluginSpecifier];
|
|
11
|
+
const bunTestHygieneRules = {
|
|
12
|
+
"bun-test-hygiene/no-disabled-tests": "error",
|
|
13
|
+
"bun-test-hygiene/no-focused-tests": "error",
|
|
14
|
+
"bun-test-hygiene/no-forced-database-drop": "error",
|
|
15
|
+
"bun-test-hygiene/no-identical-title": "error",
|
|
16
|
+
"bun-test-hygiene/no-promise-matchers": "error"
|
|
17
|
+
};
|
|
9
18
|
const portableSafetyPluginSpecifiers = [
|
|
10
19
|
noAnyCastsPluginSpecifier,
|
|
11
20
|
noDangerousTypeAssertionsPluginSpecifier,
|
|
@@ -46,6 +55,9 @@ const testReliabilityRules = {
|
|
|
46
55
|
"no-wall-clock-bound/no-wall-clock-bound": "error"
|
|
47
56
|
};
|
|
48
57
|
//#endregion
|
|
58
|
+
exports.bunTestHygienePluginSpecifier = bunTestHygienePluginSpecifier;
|
|
59
|
+
exports.bunTestHygienePluginSpecifiers = bunTestHygienePluginSpecifiers;
|
|
60
|
+
exports.bunTestHygieneRules = bunTestHygieneRules;
|
|
49
61
|
exports.noAnyCastsPluginSpecifier = noAnyCastsPluginSpecifier;
|
|
50
62
|
exports.noCheckoutWritesInTestsPluginSpecifier = noCheckoutWritesInTestsPluginSpecifier;
|
|
51
63
|
exports.noDangerousTypeAssertionsPluginSpecifier = noDangerousTypeAssertionsPluginSpecifier;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { OxlintConfig } from \"oxlint\";\n\ntype JsPlugins = NonNullable<OxlintConfig[\"jsPlugins\"]>;\ntype Rules = NonNullable<OxlintConfig[\"rules\"]>;\n\nexport const noAnyCastsPluginSpecifier = \"@stll/oxlint-plugin/no-any-casts\";\nexport const noDangerousTypeAssertionsPluginSpecifier =\n \"@stll/oxlint-plugin/no-dangerous-type-assertions\";\nexport const noPartialRecordSatisfiesPluginSpecifier =\n \"@stll/oxlint-plugin/no-partial-record-satisfies\";\nexport const noUnsafeInnerHtmlPluginSpecifier =\n \"@stll/oxlint-plugin/no-unsafe-inner-html\";\nexport const noRawRouteQueryClientPluginSpecifier =\n \"@stll/oxlint-plugin/no-raw-route-query-client\";\nexport const requireLoaderPrefetchPluginSpecifier =\n \"@stll/oxlint-plugin/require-loader-prefetch\";\n\nexport const portableSafetyPluginSpecifiers = [\n noAnyCastsPluginSpecifier,\n noDangerousTypeAssertionsPluginSpecifier,\n noPartialRecordSatisfiesPluginSpecifier,\n noUnsafeInnerHtmlPluginSpecifier,\n] satisfies JsPlugins;\n\nexport const portableSafetyRules = {\n \"no-any-casts/no-any-casts\": \"error\",\n \"no-dangerous-type-assertions/no-dangerous-type-assertions\": \"error\",\n \"no-partial-record-satisfies/no-partial-record-satisfies\": \"error\",\n \"no-unsafe-inner-html/no-unsafe-inner-html\": \"error\",\n} satisfies Rules;\n\nexport const routeQueryPluginSpecifiers = [\n noRawRouteQueryClientPluginSpecifier,\n requireLoaderPrefetchPluginSpecifier,\n] satisfies JsPlugins;\n\nexport const routeQueryRules = {\n \"no-raw-route-query-client/no-raw-route-query-client\": \"error\",\n \"require-loader-prefetch/require-loader-prefetch\": \"error\",\n} satisfies Rules;\n\nexport const noWallClockInTestsPluginSpecifier =\n \"@stll/oxlint-plugin/no-wall-clock-in-tests\";\nexport const noRealTimerWaitsInTestsPluginSpecifier =\n \"@stll/oxlint-plugin/no-real-timer-waits-in-tests\";\nexport const noUnseededRandomInTestsPluginSpecifier =\n \"@stll/oxlint-plugin/no-unseeded-random-in-tests\";\nexport const noRealNetworkInTestsPluginSpecifier =\n \"@stll/oxlint-plugin/no-real-network-in-tests\";\nexport const noCheckoutWritesInTestsPluginSpecifier =\n \"@stll/oxlint-plugin/no-checkout-writes-in-tests\";\nexport const noWallClockBoundPluginSpecifier =\n \"@stll/oxlint-plugin/no-wall-clock-bound\";\n\n// Intentionally separate from every preset: consumers adopt these with a\n// shrink-only test baseline.\nexport const testReliabilityPluginSpecifiers = [\n noWallClockInTestsPluginSpecifier,\n noRealTimerWaitsInTestsPluginSpecifier,\n noUnseededRandomInTestsPluginSpecifier,\n noRealNetworkInTestsPluginSpecifier,\n noCheckoutWritesInTestsPluginSpecifier,\n noWallClockBoundPluginSpecifier,\n] satisfies JsPlugins;\n\nexport const testReliabilityRules = {\n \"no-wall-clock-in-tests/no-wall-clock-in-tests\": \"error\",\n \"no-real-timer-waits-in-tests/no-real-timer-waits-in-tests\": \"error\",\n \"no-unseeded-random-in-tests/no-unseeded-random-in-tests\": \"error\",\n \"no-real-network-in-tests/no-real-network-in-tests\": \"error\",\n \"no-checkout-writes-in-tests/no-checkout-writes-in-tests\": \"error\",\n \"no-wall-clock-bound/no-wall-clock-bound\": \"error\",\n} satisfies Rules;\n"],"mappings":";;AAKA,MAAa,4BAA4B;AACzC,MAAa,2CACX;AACF,MAAa,0CACX;AACF,MAAa,mCACX;AACF,MAAa,uCACX;AACF,MAAa,uCACX;AAEF,MAAa,iCAAiC;CAC5C;CACA;CACA;CACA;AACF;AAEA,MAAa,sBAAsB;CACjC,6BAA6B;CAC7B,6DAA6D;CAC7D,2DAA2D;CAC3D,6CAA6C;AAC/C;AAEA,MAAa,6BAA6B,CACxC,sCACA,oCACF;AAEA,MAAa,kBAAkB;CAC7B,uDAAuD;CACvD,mDAAmD;AACrD;AAEA,MAAa,oCACX;AACF,MAAa,yCACX;AACF,MAAa,yCACX;AACF,MAAa,sCACX;AACF,MAAa,yCACX;AACF,MAAa,kCACX;AAIF,MAAa,kCAAkC;CAC7C;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,MAAa,uBAAuB;CAClC,iDAAiD;CACjD,6DAA6D;CAC7D,2DAA2D;CAC3D,qDAAqD;CACrD,2DAA2D;CAC3D,2CAA2C;AAC7C"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { OxlintConfig } from \"oxlint\";\n\ntype JsPlugins = NonNullable<OxlintConfig[\"jsPlugins\"]>;\ntype Rules = NonNullable<OxlintConfig[\"rules\"]>;\n\nexport const noAnyCastsPluginSpecifier = \"@stll/oxlint-plugin/no-any-casts\";\nexport const noDangerousTypeAssertionsPluginSpecifier =\n \"@stll/oxlint-plugin/no-dangerous-type-assertions\";\nexport const noPartialRecordSatisfiesPluginSpecifier =\n \"@stll/oxlint-plugin/no-partial-record-satisfies\";\nexport const noUnsafeInnerHtmlPluginSpecifier =\n \"@stll/oxlint-plugin/no-unsafe-inner-html\";\nexport const noRawRouteQueryClientPluginSpecifier =\n \"@stll/oxlint-plugin/no-raw-route-query-client\";\nexport const requireLoaderPrefetchPluginSpecifier =\n \"@stll/oxlint-plugin/require-loader-prefetch\";\nexport const bunTestHygienePluginSpecifier =\n \"@stll/oxlint-plugin/bun-test-hygiene\";\n\nexport const bunTestHygienePluginSpecifiers = [\n bunTestHygienePluginSpecifier,\n] satisfies JsPlugins;\n\nexport const bunTestHygieneRules = {\n \"bun-test-hygiene/no-disabled-tests\": \"error\",\n \"bun-test-hygiene/no-focused-tests\": \"error\",\n \"bun-test-hygiene/no-forced-database-drop\": \"error\",\n \"bun-test-hygiene/no-identical-title\": \"error\",\n \"bun-test-hygiene/no-promise-matchers\": \"error\",\n} satisfies Rules;\n\nexport const portableSafetyPluginSpecifiers = [\n noAnyCastsPluginSpecifier,\n noDangerousTypeAssertionsPluginSpecifier,\n noPartialRecordSatisfiesPluginSpecifier,\n noUnsafeInnerHtmlPluginSpecifier,\n] satisfies JsPlugins;\n\nexport const portableSafetyRules = {\n \"no-any-casts/no-any-casts\": \"error\",\n \"no-dangerous-type-assertions/no-dangerous-type-assertions\": \"error\",\n \"no-partial-record-satisfies/no-partial-record-satisfies\": \"error\",\n \"no-unsafe-inner-html/no-unsafe-inner-html\": \"error\",\n} satisfies Rules;\n\nexport const routeQueryPluginSpecifiers = [\n noRawRouteQueryClientPluginSpecifier,\n requireLoaderPrefetchPluginSpecifier,\n] satisfies JsPlugins;\n\nexport const routeQueryRules = {\n \"no-raw-route-query-client/no-raw-route-query-client\": \"error\",\n \"require-loader-prefetch/require-loader-prefetch\": \"error\",\n} satisfies Rules;\n\nexport const noWallClockInTestsPluginSpecifier =\n \"@stll/oxlint-plugin/no-wall-clock-in-tests\";\nexport const noRealTimerWaitsInTestsPluginSpecifier =\n \"@stll/oxlint-plugin/no-real-timer-waits-in-tests\";\nexport const noUnseededRandomInTestsPluginSpecifier =\n \"@stll/oxlint-plugin/no-unseeded-random-in-tests\";\nexport const noRealNetworkInTestsPluginSpecifier =\n \"@stll/oxlint-plugin/no-real-network-in-tests\";\nexport const noCheckoutWritesInTestsPluginSpecifier =\n \"@stll/oxlint-plugin/no-checkout-writes-in-tests\";\nexport const noWallClockBoundPluginSpecifier =\n \"@stll/oxlint-plugin/no-wall-clock-bound\";\n\n// Intentionally separate from every preset: consumers adopt these with a\n// shrink-only test baseline.\nexport const testReliabilityPluginSpecifiers = [\n noWallClockInTestsPluginSpecifier,\n noRealTimerWaitsInTestsPluginSpecifier,\n noUnseededRandomInTestsPluginSpecifier,\n noRealNetworkInTestsPluginSpecifier,\n noCheckoutWritesInTestsPluginSpecifier,\n noWallClockBoundPluginSpecifier,\n] satisfies JsPlugins;\n\nexport const testReliabilityRules = {\n \"no-wall-clock-in-tests/no-wall-clock-in-tests\": \"error\",\n \"no-real-timer-waits-in-tests/no-real-timer-waits-in-tests\": \"error\",\n \"no-unseeded-random-in-tests/no-unseeded-random-in-tests\": \"error\",\n \"no-real-network-in-tests/no-real-network-in-tests\": \"error\",\n \"no-checkout-writes-in-tests/no-checkout-writes-in-tests\": \"error\",\n \"no-wall-clock-bound/no-wall-clock-bound\": \"error\",\n} satisfies Rules;\n"],"mappings":";;AAKA,MAAa,4BAA4B;AACzC,MAAa,2CACX;AACF,MAAa,0CACX;AACF,MAAa,mCACX;AACF,MAAa,uCACX;AACF,MAAa,uCACX;AACF,MAAa,gCACX;AAEF,MAAa,iCAAiC,CAC5C,6BACF;AAEA,MAAa,sBAAsB;CACjC,sCAAsC;CACtC,qCAAqC;CACrC,4CAA4C;CAC5C,uCAAuC;CACvC,wCAAwC;AAC1C;AAEA,MAAa,iCAAiC;CAC5C;CACA;CACA;CACA;AACF;AAEA,MAAa,sBAAsB;CACjC,6BAA6B;CAC7B,6DAA6D;CAC7D,2DAA2D;CAC3D,6CAA6C;AAC/C;AAEA,MAAa,6BAA6B,CACxC,sCACA,oCACF;AAEA,MAAa,kBAAkB;CAC7B,uDAAuD;CACvD,mDAAmD;AACrD;AAEA,MAAa,oCACX;AACF,MAAa,yCACX;AACF,MAAa,yCACX;AACF,MAAa,sCACX;AACF,MAAa,yCACX;AACF,MAAa,kCACX;AAIF,MAAa,kCAAkC;CAC7C;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,MAAa,uBAAuB;CAClC,iDAAiD;CACjD,6DAA6D;CAC7D,2DAA2D;CAC3D,qDAAqD;CACrD,2DAA2D;CAC3D,2CAA2C;AAC7C"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,30 +1,39 @@
|
|
|
1
1
|
//#region src/index.d.ts
|
|
2
|
-
declare const noAnyCastsPluginSpecifier = "@stll/oxlint-plugin/no-any-casts";
|
|
3
|
-
declare const noDangerousTypeAssertionsPluginSpecifier = "@stll/oxlint-plugin/no-dangerous-type-assertions";
|
|
4
|
-
declare const noPartialRecordSatisfiesPluginSpecifier = "@stll/oxlint-plugin/no-partial-record-satisfies";
|
|
5
|
-
declare const noUnsafeInnerHtmlPluginSpecifier = "@stll/oxlint-plugin/no-unsafe-inner-html";
|
|
6
|
-
declare const noRawRouteQueryClientPluginSpecifier = "@stll/oxlint-plugin/no-raw-route-query-client";
|
|
7
|
-
declare const requireLoaderPrefetchPluginSpecifier = "@stll/oxlint-plugin/require-loader-prefetch";
|
|
8
|
-
declare const
|
|
9
|
-
declare const
|
|
2
|
+
export declare const noAnyCastsPluginSpecifier = "@stll/oxlint-plugin/no-any-casts";
|
|
3
|
+
export declare const noDangerousTypeAssertionsPluginSpecifier = "@stll/oxlint-plugin/no-dangerous-type-assertions";
|
|
4
|
+
export declare const noPartialRecordSatisfiesPluginSpecifier = "@stll/oxlint-plugin/no-partial-record-satisfies";
|
|
5
|
+
export declare const noUnsafeInnerHtmlPluginSpecifier = "@stll/oxlint-plugin/no-unsafe-inner-html";
|
|
6
|
+
export declare const noRawRouteQueryClientPluginSpecifier = "@stll/oxlint-plugin/no-raw-route-query-client";
|
|
7
|
+
export declare const requireLoaderPrefetchPluginSpecifier = "@stll/oxlint-plugin/require-loader-prefetch";
|
|
8
|
+
export declare const bunTestHygienePluginSpecifier = "@stll/oxlint-plugin/bun-test-hygiene";
|
|
9
|
+
export declare const bunTestHygienePluginSpecifiers: string[];
|
|
10
|
+
export declare const bunTestHygieneRules: {
|
|
11
|
+
"bun-test-hygiene/no-disabled-tests": "error";
|
|
12
|
+
"bun-test-hygiene/no-focused-tests": "error";
|
|
13
|
+
"bun-test-hygiene/no-forced-database-drop": "error";
|
|
14
|
+
"bun-test-hygiene/no-identical-title": "error";
|
|
15
|
+
"bun-test-hygiene/no-promise-matchers": "error";
|
|
16
|
+
};
|
|
17
|
+
export declare const portableSafetyPluginSpecifiers: string[];
|
|
18
|
+
export declare const portableSafetyRules: {
|
|
10
19
|
"no-any-casts/no-any-casts": "error";
|
|
11
20
|
"no-dangerous-type-assertions/no-dangerous-type-assertions": "error";
|
|
12
21
|
"no-partial-record-satisfies/no-partial-record-satisfies": "error";
|
|
13
22
|
"no-unsafe-inner-html/no-unsafe-inner-html": "error";
|
|
14
23
|
};
|
|
15
|
-
declare const routeQueryPluginSpecifiers: string[];
|
|
16
|
-
declare const routeQueryRules: {
|
|
24
|
+
export declare const routeQueryPluginSpecifiers: string[];
|
|
25
|
+
export declare const routeQueryRules: {
|
|
17
26
|
"no-raw-route-query-client/no-raw-route-query-client": "error";
|
|
18
27
|
"require-loader-prefetch/require-loader-prefetch": "error";
|
|
19
28
|
};
|
|
20
|
-
declare const noWallClockInTestsPluginSpecifier = "@stll/oxlint-plugin/no-wall-clock-in-tests";
|
|
21
|
-
declare const noRealTimerWaitsInTestsPluginSpecifier = "@stll/oxlint-plugin/no-real-timer-waits-in-tests";
|
|
22
|
-
declare const noUnseededRandomInTestsPluginSpecifier = "@stll/oxlint-plugin/no-unseeded-random-in-tests";
|
|
23
|
-
declare const noRealNetworkInTestsPluginSpecifier = "@stll/oxlint-plugin/no-real-network-in-tests";
|
|
24
|
-
declare const noCheckoutWritesInTestsPluginSpecifier = "@stll/oxlint-plugin/no-checkout-writes-in-tests";
|
|
25
|
-
declare const noWallClockBoundPluginSpecifier = "@stll/oxlint-plugin/no-wall-clock-bound";
|
|
26
|
-
declare const testReliabilityPluginSpecifiers: string[];
|
|
27
|
-
declare const testReliabilityRules: {
|
|
29
|
+
export declare const noWallClockInTestsPluginSpecifier = "@stll/oxlint-plugin/no-wall-clock-in-tests";
|
|
30
|
+
export declare const noRealTimerWaitsInTestsPluginSpecifier = "@stll/oxlint-plugin/no-real-timer-waits-in-tests";
|
|
31
|
+
export declare const noUnseededRandomInTestsPluginSpecifier = "@stll/oxlint-plugin/no-unseeded-random-in-tests";
|
|
32
|
+
export declare const noRealNetworkInTestsPluginSpecifier = "@stll/oxlint-plugin/no-real-network-in-tests";
|
|
33
|
+
export declare const noCheckoutWritesInTestsPluginSpecifier = "@stll/oxlint-plugin/no-checkout-writes-in-tests";
|
|
34
|
+
export declare const noWallClockBoundPluginSpecifier = "@stll/oxlint-plugin/no-wall-clock-bound";
|
|
35
|
+
export declare const testReliabilityPluginSpecifiers: string[];
|
|
36
|
+
export declare const testReliabilityRules: {
|
|
28
37
|
"no-wall-clock-in-tests/no-wall-clock-in-tests": "error";
|
|
29
38
|
"no-real-timer-waits-in-tests/no-real-timer-waits-in-tests": "error";
|
|
30
39
|
"no-unseeded-random-in-tests/no-unseeded-random-in-tests": "error";
|
|
@@ -33,5 +42,4 @@ declare const testReliabilityRules: {
|
|
|
33
42
|
"no-wall-clock-bound/no-wall-clock-bound": "error";
|
|
34
43
|
};
|
|
35
44
|
//#endregion
|
|
36
|
-
export { noAnyCastsPluginSpecifier, noCheckoutWritesInTestsPluginSpecifier, noDangerousTypeAssertionsPluginSpecifier, noPartialRecordSatisfiesPluginSpecifier, noRawRouteQueryClientPluginSpecifier, noRealNetworkInTestsPluginSpecifier, noRealTimerWaitsInTestsPluginSpecifier, noUnsafeInnerHtmlPluginSpecifier, noUnseededRandomInTestsPluginSpecifier, noWallClockBoundPluginSpecifier, noWallClockInTestsPluginSpecifier, portableSafetyPluginSpecifiers, portableSafetyRules, requireLoaderPrefetchPluginSpecifier, routeQueryPluginSpecifiers, routeQueryRules, testReliabilityPluginSpecifiers, testReliabilityRules };
|
|
37
45
|
//# sourceMappingURL=index.d.cts.map
|