@stll/oxlint-plugin 0.4.0 → 0.6.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/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 +18 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -1
- package/dist/index.d.mts +12 -1
- package/dist/index.mjs +15 -3
- package/dist/index.mjs.map +1 -1
- package/dist/no-unsafe-inner-html.cjs +181 -44
- package/dist/no-unsafe-inner-html.cjs.map +1 -1
- package/dist/no-unsafe-inner-html.d.cts +18 -1
- package/dist/no-unsafe-inner-html.d.mts +18 -1
- package/dist/no-unsafe-inner-html.mjs +180 -46
- package/dist/no-unsafe-inner-html.mjs.map +1 -1
- package/dist/no-wall-clock-bound.cjs +153 -0
- package/dist/no-wall-clock-bound.cjs.map +1 -0
- package/dist/no-wall-clock-bound.d.cts +4 -0
- package/dist/no-wall-clock-bound.d.mts +5 -0
- package/dist/no-wall-clock-bound.mjs +149 -0
- package/dist/no-wall-clock-bound.mjs.map +1 -0
- package/dist/require-loader-prefetch.mjs +1 -1
- package/dist/test-rule-utils.cjs.map +1 -1
- package/dist/test-rule-utils.mjs.map +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 +23 -1
package/dist/index.mjs
CHANGED
|
@@ -5,6 +5,15 @@ const noPartialRecordSatisfiesPluginSpecifier = "@stll/oxlint-plugin/no-partial-
|
|
|
5
5
|
const noUnsafeInnerHtmlPluginSpecifier = "@stll/oxlint-plugin/no-unsafe-inner-html";
|
|
6
6
|
const noRawRouteQueryClientPluginSpecifier = "@stll/oxlint-plugin/no-raw-route-query-client";
|
|
7
7
|
const requireLoaderPrefetchPluginSpecifier = "@stll/oxlint-plugin/require-loader-prefetch";
|
|
8
|
+
const bunTestHygienePluginSpecifier = "@stll/oxlint-plugin/bun-test-hygiene";
|
|
9
|
+
const bunTestHygienePluginSpecifiers = [bunTestHygienePluginSpecifier];
|
|
10
|
+
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
|
+
};
|
|
8
17
|
const portableSafetyPluginSpecifiers = [
|
|
9
18
|
noAnyCastsPluginSpecifier,
|
|
10
19
|
noDangerousTypeAssertionsPluginSpecifier,
|
|
@@ -27,21 +36,24 @@ const noRealTimerWaitsInTestsPluginSpecifier = "@stll/oxlint-plugin/no-real-time
|
|
|
27
36
|
const noUnseededRandomInTestsPluginSpecifier = "@stll/oxlint-plugin/no-unseeded-random-in-tests";
|
|
28
37
|
const noRealNetworkInTestsPluginSpecifier = "@stll/oxlint-plugin/no-real-network-in-tests";
|
|
29
38
|
const noCheckoutWritesInTestsPluginSpecifier = "@stll/oxlint-plugin/no-checkout-writes-in-tests";
|
|
39
|
+
const noWallClockBoundPluginSpecifier = "@stll/oxlint-plugin/no-wall-clock-bound";
|
|
30
40
|
const testReliabilityPluginSpecifiers = [
|
|
31
41
|
noWallClockInTestsPluginSpecifier,
|
|
32
42
|
noRealTimerWaitsInTestsPluginSpecifier,
|
|
33
43
|
noUnseededRandomInTestsPluginSpecifier,
|
|
34
44
|
noRealNetworkInTestsPluginSpecifier,
|
|
35
|
-
noCheckoutWritesInTestsPluginSpecifier
|
|
45
|
+
noCheckoutWritesInTestsPluginSpecifier,
|
|
46
|
+
noWallClockBoundPluginSpecifier
|
|
36
47
|
];
|
|
37
48
|
const testReliabilityRules = {
|
|
38
49
|
"no-wall-clock-in-tests/no-wall-clock-in-tests": "error",
|
|
39
50
|
"no-real-timer-waits-in-tests/no-real-timer-waits-in-tests": "error",
|
|
40
51
|
"no-unseeded-random-in-tests/no-unseeded-random-in-tests": "error",
|
|
41
52
|
"no-real-network-in-tests/no-real-network-in-tests": "error",
|
|
42
|
-
"no-checkout-writes-in-tests/no-checkout-writes-in-tests": "error"
|
|
53
|
+
"no-checkout-writes-in-tests/no-checkout-writes-in-tests": "error",
|
|
54
|
+
"no-wall-clock-bound/no-wall-clock-bound": "error"
|
|
43
55
|
};
|
|
44
56
|
//#endregion
|
|
45
|
-
export { noAnyCastsPluginSpecifier, noCheckoutWritesInTestsPluginSpecifier, noDangerousTypeAssertionsPluginSpecifier, noPartialRecordSatisfiesPluginSpecifier, noRawRouteQueryClientPluginSpecifier, noRealNetworkInTestsPluginSpecifier, noRealTimerWaitsInTestsPluginSpecifier, noUnsafeInnerHtmlPluginSpecifier, noUnseededRandomInTestsPluginSpecifier, noWallClockInTestsPluginSpecifier, portableSafetyPluginSpecifiers, portableSafetyRules, requireLoaderPrefetchPluginSpecifier, routeQueryPluginSpecifiers, routeQueryRules, testReliabilityPluginSpecifiers, testReliabilityRules };
|
|
57
|
+
export { bunTestHygienePluginSpecifier, bunTestHygienePluginSpecifiers, bunTestHygieneRules, noAnyCastsPluginSpecifier, noCheckoutWritesInTestsPluginSpecifier, noDangerousTypeAssertionsPluginSpecifier, noPartialRecordSatisfiesPluginSpecifier, noRawRouteQueryClientPluginSpecifier, noRealNetworkInTestsPluginSpecifier, noRealTimerWaitsInTestsPluginSpecifier, noUnsafeInnerHtmlPluginSpecifier, noUnseededRandomInTestsPluginSpecifier, noWallClockBoundPluginSpecifier, noWallClockInTestsPluginSpecifier, portableSafetyPluginSpecifiers, portableSafetyRules, requireLoaderPrefetchPluginSpecifier, routeQueryPluginSpecifiers, routeQueryRules, testReliabilityPluginSpecifiers, testReliabilityRules };
|
|
46
58
|
|
|
47
59
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","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\";\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] 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} 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;AAIF,MAAa,kCAAkC;CAC7C;CACA;CACA;CACA;CACA;AACF;AAEA,MAAa,uBAAuB;CAClC,iDAAiD;CACjD,6DAA6D;CAC7D,2DAA2D;CAC3D,qDAAqD;CACrD,2DAA2D;
|
|
1
|
+
{"version":3,"file":"index.mjs","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"}
|
|
@@ -91,6 +91,11 @@ const DOM_ELEMENT_PRODUCERS = [
|
|
|
91
91
|
kind: "collection",
|
|
92
92
|
receiver: "either"
|
|
93
93
|
},
|
|
94
|
+
{
|
|
95
|
+
name: "children",
|
|
96
|
+
kind: "collection-property",
|
|
97
|
+
receiver: "element"
|
|
98
|
+
},
|
|
94
99
|
{
|
|
95
100
|
name: "parentElement",
|
|
96
101
|
kind: "property",
|
|
@@ -152,11 +157,28 @@ const HTML_PROPERTIES = /* @__PURE__ */ new Set([
|
|
|
152
157
|
]);
|
|
153
158
|
const HTML_FIRST_ARGUMENT_METHODS = /* @__PURE__ */ new Set(["createContextualFragment", "setHTMLUnsafe"]);
|
|
154
159
|
const DOCUMENT_WRITE_METHODS = /* @__PURE__ */ new Set(["write", "writeln"]);
|
|
160
|
+
const HTML_ATTRIBUTE_SINKS = [{
|
|
161
|
+
name: "setAttribute",
|
|
162
|
+
namespaceIndex: null,
|
|
163
|
+
nameIndex: 0,
|
|
164
|
+
valueIndex: 1
|
|
165
|
+
}, {
|
|
166
|
+
name: "setAttributeNS",
|
|
167
|
+
namespaceIndex: 0,
|
|
168
|
+
nameIndex: 1,
|
|
169
|
+
valueIndex: 2
|
|
170
|
+
}];
|
|
155
171
|
const DOCUMENT_MEMBERS = /* @__PURE__ */ new Set([
|
|
156
172
|
"contentDocument",
|
|
157
173
|
"document",
|
|
158
174
|
"ownerDocument"
|
|
159
175
|
]);
|
|
176
|
+
const FILE_SYSTEM_MODULES = /* @__PURE__ */ new Set([
|
|
177
|
+
"fs",
|
|
178
|
+
"fs/promises",
|
|
179
|
+
"node:fs",
|
|
180
|
+
"node:fs/promises"
|
|
181
|
+
]);
|
|
160
182
|
const HTML_SANITIZER_LEAF_RE = /^(?:escape(?:html|xml)|sanitize(?:html|dom)|purify(?:html|dom)|dompurify)$/iu;
|
|
161
183
|
const SANITIZER_RESULT_MEMBERS = /* @__PURE__ */ new Set([
|
|
162
184
|
"value",
|
|
@@ -223,7 +245,15 @@ const isOpaqueProperty = (property) => property.type === "SpreadElement" || prop
|
|
|
223
245
|
const objectProperties = (node) => {
|
|
224
246
|
const expression = require_utils.unwrapExpression(node);
|
|
225
247
|
if (expression?.type !== "ObjectExpression" || !Array.isArray(expression.properties)) return [];
|
|
226
|
-
|
|
248
|
+
const properties = [];
|
|
249
|
+
for (const property of expression.properties.filter(require_utils.isAstNode)) {
|
|
250
|
+
if (property.type === "SpreadElement" && require_utils.unwrapExpression(property.argument)?.type === "ObjectExpression") {
|
|
251
|
+
properties.push(...objectProperties(property.argument));
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
properties.push(property);
|
|
255
|
+
}
|
|
256
|
+
return properties;
|
|
227
257
|
};
|
|
228
258
|
const appliedArgumentsFor = (args) => {
|
|
229
259
|
const applied = require_utils.unwrapExpression(args.at(1));
|
|
@@ -264,13 +294,6 @@ const invokedArguments = (call) => {
|
|
|
264
294
|
}
|
|
265
295
|
return appliedArgumentsFor(resolved.args);
|
|
266
296
|
};
|
|
267
|
-
const isDocumentReceiver = (node) => {
|
|
268
|
-
const receiver = require_utils.unwrapExpression(node);
|
|
269
|
-
if (require_utils.isIdentifier(receiver, "document")) return true;
|
|
270
|
-
if (receiver?.type !== "MemberExpression") return false;
|
|
271
|
-
const name = require_utils.memberPropertyName(receiver);
|
|
272
|
-
return name !== null && DOCUMENT_MEMBERS.has(name);
|
|
273
|
-
};
|
|
274
297
|
var no_unsafe_inner_html_default = (0, _oxlint_plugins.eslintCompatPlugin)({
|
|
275
298
|
meta: { name: "no-unsafe-inner-html" },
|
|
276
299
|
rules: { "no-unsafe-inner-html": {
|
|
@@ -286,35 +309,133 @@ var no_unsafe_inner_html_default = (0, _oxlint_plugins.eslintCompatPlugin)({
|
|
|
286
309
|
}],
|
|
287
310
|
messages: {
|
|
288
311
|
unsafeInnerHtml: "Dynamic HTML injected into the DOM must be static, proven by the configured sanitizer policy, or have an adjacent `// safe-html: <provenance>` comment naming the trusted source. Function names outside the allowlist are not proof because a local identity function can shadow them.",
|
|
289
|
-
unsafeInnerHtmlSpread: "Do not spread an object
|
|
312
|
+
unsafeInnerHtmlSpread: "Do not spread an object in dangerouslySetInnerHTML. Keep `__html` inline so this rule can prove the HTML value is sanitized or escaped.",
|
|
313
|
+
unsafeInnerHtmlComputedKey: "Do not use an unresolved computed key in dangerouslySetInnerHTML. Write an explicit `__html` property so this rule can prove the HTML value is sanitized or escaped."
|
|
290
314
|
}
|
|
291
315
|
},
|
|
292
316
|
createOnce(context) {
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
317
|
+
const resolveStableBinding = ({ node, scope, seen }) => {
|
|
318
|
+
let target = require_utils.unwrapExpression(node);
|
|
319
|
+
while (require_utils.isIdentifier(target)) {
|
|
296
320
|
let current = scope;
|
|
321
|
+
let variable;
|
|
297
322
|
while (current !== null) {
|
|
298
|
-
|
|
299
|
-
if (variable
|
|
300
|
-
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
if (variable.defs.length === 0) return kind === "element" && isDocumentReceiver(target);
|
|
304
|
-
if (seen.has(variable)) return false;
|
|
305
|
-
seen.add(variable);
|
|
306
|
-
const definition = variable.defs.at(0);
|
|
307
|
-
if (variable.defs.length !== 1 || definition?.type !== "Variable" || definition.node.type !== "VariableDeclarator" || definition.node.id.type !== "Identifier" || definition.node.init === null || variable.references.some((reference) => reference.isWrite() && !reference.init)) return false;
|
|
308
|
-
return isKnownDomTarget({
|
|
309
|
-
node: definition.node.init,
|
|
310
|
-
scope: context.sourceCode.getScope(definition.node),
|
|
311
|
-
seen,
|
|
312
|
-
kind
|
|
313
|
-
});
|
|
323
|
+
variable = current.set.get(target.name);
|
|
324
|
+
if (variable !== void 0) break;
|
|
325
|
+
current = current.upper;
|
|
314
326
|
}
|
|
327
|
+
if (variable === void 0 || variable.defs.length === 0) return {
|
|
328
|
+
kind: "expression",
|
|
329
|
+
node: target,
|
|
330
|
+
scope
|
|
331
|
+
};
|
|
332
|
+
if (seen.has(variable)) return null;
|
|
333
|
+
seen.add(variable);
|
|
334
|
+
const definition = variable.defs.at(0);
|
|
335
|
+
if (variable.defs.length !== 1 || definition?.type !== "Variable" || definition.node.type !== "VariableDeclarator" || definition.node.id.type !== "Identifier" || variable.references.some((reference) => reference.isWrite() && !reference.init)) return null;
|
|
336
|
+
const declarator = definition.node;
|
|
337
|
+
if (declarator.init !== null) {
|
|
338
|
+
target = require_utils.unwrapExpression(declarator.init);
|
|
339
|
+
scope = context.sourceCode.getScope(declarator);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const declaration = declarator.parent;
|
|
343
|
+
if (declaration.type !== "VariableDeclaration") return null;
|
|
344
|
+
const loop = declaration.parent;
|
|
345
|
+
if (loop.type !== "ForOfStatement" || loop.left !== declaration) return null;
|
|
346
|
+
return {
|
|
347
|
+
kind: "iteration",
|
|
348
|
+
node: require_utils.unwrapExpression(loop.right),
|
|
349
|
+
scope: context.sourceCode.getScope(loop.right)
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
kind: "expression",
|
|
354
|
+
node: target,
|
|
355
|
+
scope
|
|
356
|
+
};
|
|
357
|
+
};
|
|
358
|
+
const isKnownNonEmptyNamespace = (namespace, scope) => {
|
|
359
|
+
const source = resolveStableBinding({
|
|
360
|
+
node: namespace,
|
|
361
|
+
scope,
|
|
362
|
+
seen: /* @__PURE__ */ new Set()
|
|
363
|
+
});
|
|
364
|
+
if (source?.kind !== "expression") return false;
|
|
365
|
+
const text = require_utils.staticStringValue(source.node);
|
|
366
|
+
if (text !== null) return text !== "";
|
|
367
|
+
if (source.node?.type !== "Literal") return false;
|
|
368
|
+
const { value } = source.node;
|
|
369
|
+
return typeof value === "number" || typeof value === "boolean" || typeof value === "bigint";
|
|
370
|
+
};
|
|
371
|
+
const variableFor = (identifier, initialScope) => {
|
|
372
|
+
let current = initialScope;
|
|
373
|
+
while (current !== null) {
|
|
374
|
+
const variable = current.set.get(identifier.name);
|
|
375
|
+
if (variable !== void 0) return variable;
|
|
376
|
+
current = current.upper;
|
|
315
377
|
}
|
|
378
|
+
};
|
|
379
|
+
const isKnownNonDocumentReceiver = (node, scope) => {
|
|
380
|
+
const isUnshadowedGlobal = (identifier, name) => require_utils.isIdentifier(identifier, name) && variableFor(identifier, scope) === void 0;
|
|
381
|
+
const isFileSystemImport = (identifier) => {
|
|
382
|
+
if (!require_utils.isIdentifier(identifier)) return false;
|
|
383
|
+
const variable = variableFor(identifier, scope);
|
|
384
|
+
if (variable?.defs.length !== 1) return false;
|
|
385
|
+
const definition = variable.defs.at(0);
|
|
386
|
+
return definition?.type === "ImportBinding" && definition.parent?.type === "ImportDeclaration" && FILE_SYSTEM_MODULES.has(definition.parent.source.value);
|
|
387
|
+
};
|
|
388
|
+
const root = require_utils.unwrapExpression(node);
|
|
389
|
+
if (isUnshadowedGlobal(root, "Bun") || isFileSystemImport(root)) return true;
|
|
390
|
+
return root?.type === "MemberExpression" && (require_utils.memberPropertyName(root) === "stdout" || require_utils.memberPropertyName(root) === "stderr") && isUnshadowedGlobal(require_utils.unwrapExpression(root.object), "process");
|
|
391
|
+
};
|
|
392
|
+
const isDocumentReceiver = (node, scope) => {
|
|
393
|
+
if (isKnownNonDocumentReceiver(node, scope)) return false;
|
|
394
|
+
const source = resolveStableBinding({
|
|
395
|
+
node,
|
|
396
|
+
scope,
|
|
397
|
+
seen: /* @__PURE__ */ new Set()
|
|
398
|
+
});
|
|
399
|
+
if (source === null || source.kind !== "expression") return true;
|
|
400
|
+
const receiver = source.node;
|
|
401
|
+
if (require_utils.isIdentifier(receiver, "document")) return true;
|
|
402
|
+
if (receiver?.type !== "MemberExpression") return false;
|
|
403
|
+
const name = require_utils.memberPropertyName(receiver);
|
|
404
|
+
return name !== null && DOCUMENT_MEMBERS.has(name);
|
|
405
|
+
};
|
|
406
|
+
const isDocumentSinkReceiver = (node, scope) => {
|
|
407
|
+
if (isKnownNonDocumentReceiver(node, scope)) return false;
|
|
408
|
+
const receiver = require_utils.unwrapExpression(node);
|
|
409
|
+
if (!require_utils.isIdentifier(receiver)) return true;
|
|
410
|
+
let current = scope;
|
|
411
|
+
let variable;
|
|
412
|
+
while (current !== null) {
|
|
413
|
+
variable = current.set.get(receiver.name);
|
|
414
|
+
if (variable !== void 0) break;
|
|
415
|
+
current = current.upper;
|
|
416
|
+
}
|
|
417
|
+
if (variable === void 0 || variable.defs.length !== 1) return true;
|
|
418
|
+
const definition = variable.defs.at(0);
|
|
419
|
+
if (definition?.type !== "Variable" || definition.node.type !== "VariableDeclarator" || definition.node.id.type !== "Identifier" || definition.node.init === null || require_utils.unwrapExpression(definition.node.init)?.type !== "ObjectExpression" || definition.node.parent.type !== "VariableDeclaration" || definition.node.parent.kind !== "const" || variable.scope.variableScope !== scope.variableScope || variable.references.some((reference) => reference.isWrite() && !reference.init)) return true;
|
|
420
|
+
return false;
|
|
421
|
+
};
|
|
422
|
+
const isKnownDomTarget = ({ node, scope, seen = /* @__PURE__ */ new Set(), kind = "element" }) => {
|
|
423
|
+
const source = resolveStableBinding({
|
|
424
|
+
node,
|
|
425
|
+
scope,
|
|
426
|
+
seen
|
|
427
|
+
});
|
|
428
|
+
if (source === null) return true;
|
|
429
|
+
if (source.kind === "iteration") return kind === "element" && isKnownDomTarget({
|
|
430
|
+
node: source.node,
|
|
431
|
+
scope: source.scope,
|
|
432
|
+
seen,
|
|
433
|
+
kind: "collection"
|
|
434
|
+
});
|
|
435
|
+
const target = source.node;
|
|
436
|
+
scope = source.scope;
|
|
316
437
|
const hasProducerReceiver = (receiver, producer) => {
|
|
317
|
-
if (producer.receiver !== "element" && isDocumentReceiver(receiver)) return true;
|
|
438
|
+
if (producer.receiver !== "element" && isDocumentReceiver(receiver, scope)) return true;
|
|
318
439
|
return producer.receiver !== "document" && isKnownDomTarget({
|
|
319
440
|
node: receiver,
|
|
320
441
|
scope,
|
|
@@ -327,12 +448,12 @@ var no_unsafe_inner_html_default = (0, _oxlint_plugins.eslintCompatPlugin)({
|
|
|
327
448
|
seen,
|
|
328
449
|
kind: "collection"
|
|
329
450
|
});
|
|
330
|
-
if (
|
|
451
|
+
if (target?.type === "MemberExpression") {
|
|
331
452
|
const property = require_utils.memberPropertyName(target);
|
|
332
|
-
const producer = DOM_ELEMENT_PRODUCERS.find((entry) => entry.kind === "property" && entry.name === property);
|
|
453
|
+
const producer = DOM_ELEMENT_PRODUCERS.find((entry) => entry.kind === (kind === "collection" ? "collection-property" : "property") && entry.name === property);
|
|
333
454
|
if (producer !== void 0) return hasProducerReceiver(target.object, producer);
|
|
334
455
|
const index = require_utils.unwrapExpression(target.property);
|
|
335
|
-
if (target.computed === true && index?.type === "Literal" && typeof index.value === "number" && Number.isInteger(index.value) && index.value >= 0) return isElementCollection(target.object);
|
|
456
|
+
if (kind === "element" && target.computed === true && index?.type === "Literal" && typeof index.value === "number" && Number.isInteger(index.value) && index.value >= 0) return isElementCollection(target.object);
|
|
336
457
|
}
|
|
337
458
|
if (target?.type === "CallExpression") {
|
|
338
459
|
const callee = require_utils.invokedCallee(target);
|
|
@@ -342,7 +463,7 @@ var no_unsafe_inner_html_default = (0, _oxlint_plugins.eslintCompatPlugin)({
|
|
|
342
463
|
const producer = DOM_ELEMENT_PRODUCERS.find((entry) => entry.kind === (kind === "collection" ? "collection" : "method") && entry.name === method);
|
|
343
464
|
return producer !== void 0 && hasProducerReceiver(callee.object, producer);
|
|
344
465
|
}
|
|
345
|
-
return kind === "element" && isDocumentReceiver(target);
|
|
466
|
+
return kind === "element" && isDocumentReceiver(target, scope);
|
|
346
467
|
};
|
|
347
468
|
let sanitizerPolicy = SANITIZER_POLICY.allowlist;
|
|
348
469
|
const waiverLines = /* @__PURE__ */ new Set();
|
|
@@ -353,6 +474,11 @@ var no_unsafe_inner_html_default = (0, _oxlint_plugins.eslintCompatPlugin)({
|
|
|
353
474
|
kind
|
|
354
475
|
});
|
|
355
476
|
};
|
|
477
|
+
const messageIdFor = (kind) => {
|
|
478
|
+
if (kind === "opaque-payload-spread") return "unsafeInnerHtmlSpread";
|
|
479
|
+
if (kind === "opaque-payload-computed-key") return "unsafeInnerHtmlComputedKey";
|
|
480
|
+
return "unsafeInnerHtml";
|
|
481
|
+
};
|
|
356
482
|
const consumeWaiver = (node) => {
|
|
357
483
|
const line = startLine(node) - 1;
|
|
358
484
|
if (!waiverLines.has(line)) return false;
|
|
@@ -370,7 +496,7 @@ var no_unsafe_inner_html_default = (0, _oxlint_plugins.eslintCompatPlugin)({
|
|
|
370
496
|
}
|
|
371
497
|
for (const property of objectProperties(expression)) {
|
|
372
498
|
if (isOpaqueProperty(property)) {
|
|
373
|
-
observeSink(property, "opaque-payload");
|
|
499
|
+
observeSink(property, property.type === "SpreadElement" ? "opaque-payload-spread" : "opaque-payload-computed-key");
|
|
374
500
|
continue;
|
|
375
501
|
}
|
|
376
502
|
if (property.type === "Property" && objectPropertyKey(property) === htmlKey) reportIfUnsafe(property.value);
|
|
@@ -387,6 +513,14 @@ var no_unsafe_inner_html_default = (0, _oxlint_plugins.eslintCompatPlugin)({
|
|
|
387
513
|
default: throw new Error(`Unknown React HTML prop: ${String(prop)}`);
|
|
388
514
|
}
|
|
389
515
|
};
|
|
516
|
+
const checkReactProps = (value) => {
|
|
517
|
+
for (const property of objectProperties(value)) {
|
|
518
|
+
if (property.type !== "Property") continue;
|
|
519
|
+
const key = objectPropertyKey(property);
|
|
520
|
+
const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);
|
|
521
|
+
if (prop?.kind === "value") checkReactHtmlProp(prop, property.value);
|
|
522
|
+
}
|
|
523
|
+
};
|
|
390
524
|
const checkHtmlPropertySources = (sources, domTarget) => {
|
|
391
525
|
for (const source of sources) {
|
|
392
526
|
if (require_utils.unwrapExpression(source)?.type !== "ObjectExpression") {
|
|
@@ -416,7 +550,7 @@ var no_unsafe_inner_html_default = (0, _oxlint_plugins.eslintCompatPlugin)({
|
|
|
416
550
|
if (kind === "safe" || kind === "unsafe" && waived) continue;
|
|
417
551
|
context.report({
|
|
418
552
|
node,
|
|
419
|
-
messageId: kind
|
|
553
|
+
messageId: messageIdFor(kind)
|
|
420
554
|
});
|
|
421
555
|
}
|
|
422
556
|
sinks.length = 0;
|
|
@@ -432,6 +566,9 @@ var no_unsafe_inner_html_default = (0, _oxlint_plugins.eslintCompatPlugin)({
|
|
|
432
566
|
const value = require_utils.unwrapExpression(node.value);
|
|
433
567
|
checkReactHtmlProp(prop, value?.type === "JSXExpressionContainer" ? value.expression : value);
|
|
434
568
|
},
|
|
569
|
+
JSXSpreadAttribute(node) {
|
|
570
|
+
checkReactProps(node.argument);
|
|
571
|
+
},
|
|
435
572
|
Property(node) {
|
|
436
573
|
const key = objectPropertyKey(node);
|
|
437
574
|
const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);
|
|
@@ -451,12 +588,7 @@ var no_unsafe_inner_html_default = (0, _oxlint_plugins.eslintCompatPlugin)({
|
|
|
451
588
|
const invoked = invokedArguments(node);
|
|
452
589
|
const factory = staticCalleeName(callee);
|
|
453
590
|
if (factory !== null && REACT_ELEMENT_FACTORIES.has(factory)) {
|
|
454
|
-
|
|
455
|
-
if (property.type !== "Property") continue;
|
|
456
|
-
const key = objectPropertyKey(property);
|
|
457
|
-
const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);
|
|
458
|
-
if (prop?.kind === "value") checkReactHtmlProp(prop, property.value);
|
|
459
|
-
}
|
|
591
|
+
checkReactProps(invoked.args.at(1));
|
|
460
592
|
return;
|
|
461
593
|
}
|
|
462
594
|
if (callee?.type !== "MemberExpression") return;
|
|
@@ -478,15 +610,17 @@ var no_unsafe_inner_html_default = (0, _oxlint_plugins.eslintCompatPlugin)({
|
|
|
478
610
|
reportIfUnsafe(invoked.args.at(1));
|
|
479
611
|
return;
|
|
480
612
|
}
|
|
481
|
-
|
|
613
|
+
const attributeSink = HTML_ATTRIBUTE_SINKS.find((entry) => entry.name === method);
|
|
614
|
+
if (attributeSink !== void 0) {
|
|
615
|
+
if (attributeSink.namespaceIndex !== null && isKnownNonEmptyNamespace(invoked.args.at(attributeSink.namespaceIndex), context.sourceCode.getScope(node))) return;
|
|
482
616
|
if (invoked.status === "opaque") {
|
|
483
617
|
reportIfUnsafe(node);
|
|
484
618
|
return;
|
|
485
619
|
}
|
|
486
|
-
if (require_utils.staticStringValue(invoked.args.at(
|
|
620
|
+
if (require_utils.staticStringValue(invoked.args.at(attributeSink.nameIndex))?.toLowerCase() === "srcdoc") reportIfUnsafe(invoked.args.at(attributeSink.valueIndex));
|
|
487
621
|
return;
|
|
488
622
|
}
|
|
489
|
-
if (DOCUMENT_WRITE_METHODS.has(method) &&
|
|
623
|
+
if (DOCUMENT_WRITE_METHODS.has(method) && isDocumentSinkReceiver(callee.object, context.sourceCode.getScope(node))) {
|
|
490
624
|
if (invoked.status === "opaque") {
|
|
491
625
|
reportIfUnsafe(node);
|
|
492
626
|
return;
|
|
@@ -508,7 +642,10 @@ var no_unsafe_inner_html_default = (0, _oxlint_plugins.eslintCompatPlugin)({
|
|
|
508
642
|
} }
|
|
509
643
|
});
|
|
510
644
|
//#endregion
|
|
645
|
+
exports.DOCUMENT_WRITE_METHODS = DOCUMENT_WRITE_METHODS;
|
|
511
646
|
exports.DOM_ELEMENT_PRODUCERS = DOM_ELEMENT_PRODUCERS;
|
|
647
|
+
exports.HTML_ATTRIBUTE_SINKS = HTML_ATTRIBUTE_SINKS;
|
|
648
|
+
exports.REACT_ELEMENT_FACTORIES = REACT_ELEMENT_FACTORIES;
|
|
512
649
|
exports.REACT_HTML_PROPS = REACT_HTML_PROPS;
|
|
513
650
|
exports.default = no_unsafe_inner_html_default;
|
|
514
651
|
|