@stll/oxlint-plugin 0.2.1 → 0.3.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.
@@ -1,119 +1,509 @@
1
- import { i as isIdentifier, o as unwrapExpression, r as getPropertyName, t as getCalleeName } from "./utils.mjs";
1
+ import { a as isAstNode, c as memberPropertyName, d as unwrapExpression, i as invokedCallee, l as resolveLiteralArraySpreads, o as isIdentifier, r as getPropertyName, s as isStringLiteral, u as staticStringValue } from "./utils.mjs";
2
2
  import { eslintCompatPlugin } from "@oxlint/plugins";
3
3
  //#region src/no-unsafe-inner-html.ts
4
+ const DOM_ELEMENT_PRODUCERS = [
5
+ {
6
+ name: "body",
7
+ kind: "property",
8
+ receiver: "document"
9
+ },
10
+ {
11
+ name: "head",
12
+ kind: "property",
13
+ receiver: "document"
14
+ },
15
+ {
16
+ name: "documentElement",
17
+ kind: "property",
18
+ receiver: "document"
19
+ },
20
+ {
21
+ name: "activeElement",
22
+ kind: "property",
23
+ receiver: "document"
24
+ },
25
+ {
26
+ name: "scrollingElement",
27
+ kind: "property",
28
+ receiver: "document"
29
+ },
30
+ {
31
+ name: "fullscreenElement",
32
+ kind: "property",
33
+ receiver: "document"
34
+ },
35
+ {
36
+ name: "createElement",
37
+ kind: "method",
38
+ receiver: "document"
39
+ },
40
+ {
41
+ name: "createElementNS",
42
+ kind: "method",
43
+ receiver: "document"
44
+ },
45
+ {
46
+ name: "getElementById",
47
+ kind: "method",
48
+ receiver: "document"
49
+ },
50
+ {
51
+ name: "querySelector",
52
+ kind: "method",
53
+ receiver: "either"
54
+ },
55
+ {
56
+ name: "closest",
57
+ kind: "method",
58
+ receiver: "element"
59
+ },
60
+ {
61
+ name: "elementFromPoint",
62
+ kind: "method",
63
+ receiver: "document"
64
+ },
65
+ {
66
+ name: "getElementsByClassName",
67
+ kind: "collection",
68
+ receiver: "either"
69
+ },
70
+ {
71
+ name: "getElementsByTagName",
72
+ kind: "collection",
73
+ receiver: "either"
74
+ },
75
+ {
76
+ name: "getElementsByTagNameNS",
77
+ kind: "collection",
78
+ receiver: "either"
79
+ },
80
+ {
81
+ name: "getElementsByName",
82
+ kind: "collection",
83
+ receiver: "document"
84
+ },
85
+ {
86
+ name: "querySelectorAll",
87
+ kind: "collection",
88
+ receiver: "either"
89
+ },
90
+ {
91
+ name: "parentElement",
92
+ kind: "property",
93
+ receiver: "element"
94
+ },
95
+ {
96
+ name: "firstElementChild",
97
+ kind: "property",
98
+ receiver: "element"
99
+ },
100
+ {
101
+ name: "lastElementChild",
102
+ kind: "property",
103
+ receiver: "element"
104
+ },
105
+ {
106
+ name: "nextElementSibling",
107
+ kind: "property",
108
+ receiver: "element"
109
+ },
110
+ {
111
+ name: "previousElementSibling",
112
+ kind: "property",
113
+ receiver: "element"
114
+ },
115
+ {
116
+ name: "offsetParent",
117
+ kind: "property",
118
+ receiver: "element"
119
+ }
120
+ ];
121
+ const WAIVER_RE = /^\s*safe-html:\s*\S/u;
122
+ const REACT_HTML_PROPS = [
123
+ {
124
+ name: "dangerouslySetInnerHTML",
125
+ kind: "payload",
126
+ htmlKey: "__html"
127
+ },
128
+ {
129
+ name: "srcDoc",
130
+ kind: "value"
131
+ },
132
+ {
133
+ name: "srcdoc",
134
+ kind: "value"
135
+ }
136
+ ];
137
+ const REACT_ELEMENT_FACTORIES = /* @__PURE__ */ new Set([
138
+ "createElement",
139
+ "React.createElement",
140
+ "jsx",
141
+ "jsxs",
142
+ "jsxDEV"
143
+ ]);
144
+ const HTML_PROPERTIES = /* @__PURE__ */ new Set([
145
+ "innerHTML",
146
+ "outerHTML",
147
+ "srcdoc"
148
+ ]);
149
+ const HTML_FIRST_ARGUMENT_METHODS = /* @__PURE__ */ new Set(["createContextualFragment", "setHTMLUnsafe"]);
150
+ const DOCUMENT_WRITE_METHODS = /* @__PURE__ */ new Set(["write", "writeln"]);
151
+ const DOCUMENT_MEMBERS = /* @__PURE__ */ new Set([
152
+ "contentDocument",
153
+ "document",
154
+ "ownerDocument"
155
+ ]);
4
156
  const HTML_SANITIZER_LEAF_RE = /^(?:escape(?:html|xml)|sanitize(?:html|dom)|purify(?:html|dom)|dompurify)$/iu;
5
- const DOM_PURIFY_RE = /^dompurify$/iu;
6
157
  const SANITIZER_RESULT_MEMBERS = /* @__PURE__ */ new Set([
7
158
  "value",
8
159
  "data",
9
160
  "html"
10
161
  ]);
11
- const ESCAPE_HATCH_RE = /^\s*safe-html:/u;
12
- const isComment = (value) => typeof value === "object" && value !== null && typeof value.value === "string" && typeof value.loc === "object" && value.loc !== null;
13
- const isJsxIdentifier = (node, name) => typeof node === "object" && node !== null && node.type === "JSXIdentifier" && node.name === name;
14
- const isHtmlSanitizerCalleeName = (dotted) => {
162
+ const SANITIZER_POLICY = {
163
+ allowlist: "allowlist",
164
+ provenance: "provenance"
165
+ };
166
+ const SANITIZER_POLICY_VALUES = Object.values(SANITIZER_POLICY);
167
+ const sanitizerPolicyFrom = (option) => {
168
+ if (option === void 0) return SANITIZER_POLICY.allowlist;
169
+ if (typeof option !== "object" || option === null || Array.isArray(option)) throw new Error("no-unsafe-inner-html options must be an object");
170
+ const policy = Reflect.get(option, "sanitizerPolicy");
171
+ switch (policy) {
172
+ case void 0:
173
+ case SANITIZER_POLICY.allowlist: return SANITIZER_POLICY.allowlist;
174
+ case SANITIZER_POLICY.provenance: return SANITIZER_POLICY.provenance;
175
+ default: throw new Error(`unknown sanitizer policy: ${String(policy)}`);
176
+ }
177
+ };
178
+ const staticCalleeName = (node) => {
179
+ const expression = unwrapExpression(node);
180
+ if (isIdentifier(expression)) return expression.name;
181
+ if (expression?.type !== "MemberExpression") return null;
182
+ const receiver = staticCalleeName(expression.object);
183
+ const property = memberPropertyName(expression);
184
+ return receiver === null || property === null ? null : `${receiver}.${property}`;
185
+ };
186
+ const isSanitizerCall = (node) => {
187
+ if (!isAstNode(node) || node.type !== "CallExpression") return false;
188
+ const dotted = staticCalleeName(node.callee);
189
+ if (dotted === null) return false;
15
190
  const parts = dotted.split(".");
16
191
  const leaf = parts.at(-1);
17
192
  if (leaf === void 0) return false;
18
193
  if (HTML_SANITIZER_LEAF_RE.test(leaf)) return true;
19
- const objectName = parts.at(-2);
20
- return leaf === "sanitize" && DOM_PURIFY_RE.test(objectName ?? "");
194
+ return leaf === "sanitize" && /^dompurify$/iu.test(parts.at(-2) ?? "");
21
195
  };
22
- const isSanitizerCall = (node) => {
23
- if (node.type !== "CallExpression") return false;
24
- const dotted = getCalleeName(node.callee);
25
- return dotted !== null && isHtmlSanitizerCalleeName(dotted);
196
+ const isSanitizerResult = (node) => {
197
+ const expression = unwrapExpression(node);
198
+ if (expression?.type !== "MemberExpression") return false;
199
+ const property = memberPropertyName(expression);
200
+ return property !== null && SANITIZER_RESULT_MEMBERS.has(property) && isSanitizerCall(expression.object);
201
+ };
202
+ const hasLocation = (node) => typeof node === "object" && node !== null && "loc" in node && typeof node.loc === "object" && node.loc !== null && "start" in node.loc && typeof node.loc.start === "object" && node.loc.start !== null && "line" in node.loc.start && typeof node.loc.start.line === "number";
203
+ const startLine = (node) => hasLocation(node) ? node.loc.start.line : NaN;
204
+ const isWaiverComment = (value) => typeof value === "object" && value !== null && "value" in value && typeof value.value === "string" && WAIVER_RE.test(value.value) && "loc" in value && typeof value.loc === "object" && value.loc !== null;
205
+ const jsxAttributeName = (node) => isAstNode(node) && node.type === "JSXIdentifier" && typeof node.name === "string" ? node.name : null;
206
+ const isProvenSafeValue = (node, sanitizerPolicy) => {
207
+ const expression = unwrapExpression(node);
208
+ if (expression === null) return false;
209
+ if (isStringLiteral(expression)) return true;
210
+ if (sanitizerPolicy === SANITIZER_POLICY.allowlist && (isSanitizerCall(expression) || isSanitizerResult(expression))) return true;
211
+ return expression.type === "TemplateLiteral" && Array.isArray(expression.expressions) && expression.expressions.every((entry) => isProvenSafeValue(entry, sanitizerPolicy));
26
212
  };
27
- const isProvenSafeValue = (node) => {
28
- if (!node || typeof node.type !== "string") return false;
29
- if (node.type === "Literal" && typeof node.value === "string") return true;
30
- if (node.type === "TemplateLiteral") return Array.isArray(node.expressions) && node.expressions.every(isProvenSafeValue);
31
- if (isSanitizerCall(node)) return true;
32
- if (node.type === "MemberExpression" && node.computed === false && isProvenSafeMemberLeaf(node)) return true;
33
- return false;
213
+ const objectPropertyKey = (property) => {
214
+ if (!isAstNode(property)) return null;
215
+ if (property.computed === true) return staticStringValue(property.key);
216
+ return getPropertyName(property.key);
34
217
  };
35
- const isProvenSafeMemberLeaf = (node) => {
36
- const propertyName = getPropertyName(node.property);
37
- if (propertyName === null || !SANITIZER_RESULT_MEMBERS.has(propertyName)) return false;
38
- return isSanitizerCall(node.object);
218
+ const isOpaqueProperty = (property) => property.type === "SpreadElement" || property.type === "Property" && property.computed === true && objectPropertyKey(property) === null;
219
+ const objectProperties = (node) => {
220
+ const expression = unwrapExpression(node);
221
+ if (expression?.type !== "ObjectExpression" || !Array.isArray(expression.properties)) return [];
222
+ return expression.properties.filter(isAstNode);
39
223
  };
40
- const isDangerouslySetInnerHtmlValue = (property) => {
41
- if (getPropertyName(property.key) !== "__html") return false;
42
- const objectExpression = property.parent;
43
- if (objectExpression?.type !== "ObjectExpression") return false;
44
- let current = objectExpression.parent;
45
- while (current?.type === "TSAsExpression" || current?.type === "TSSatisfiesExpression") current = current.parent;
46
- if (current?.type !== "JSXExpressionContainer") return false;
47
- const attribute = current.parent;
48
- return attribute?.type === "JSXAttribute" && isJsxIdentifier(attribute.name, "dangerouslySetInnerHTML");
224
+ const appliedArgumentsFor = (args) => {
225
+ const applied = unwrapExpression(args.at(1));
226
+ if (applied === null || applied.type === "Literal" && applied.value === null || isIdentifier(applied, "undefined")) return {
227
+ status: "known",
228
+ args: []
229
+ };
230
+ if (applied.type !== "ArrayExpression" || !Array.isArray(applied.elements)) return {
231
+ status: "opaque",
232
+ args: [],
233
+ spread: args.at(1),
234
+ tail: []
235
+ };
236
+ return resolveLiteralArraySpreads(applied.elements);
237
+ };
238
+ const invokedArguments = (call) => {
239
+ if (!isAstNode(call)) return {
240
+ status: "known",
241
+ args: []
242
+ };
243
+ const rawArguments = Array.isArray(call.arguments) ? call.arguments : [];
244
+ const resolved = resolveLiteralArraySpreads(rawArguments);
245
+ const callee = unwrapExpression(call.callee);
246
+ if (callee?.type !== "MemberExpression") return resolved;
247
+ const method = memberPropertyName(callee);
248
+ if (method === "call") return {
249
+ ...resolved,
250
+ args: resolved.args.slice(1)
251
+ };
252
+ if (method !== "apply") return resolved;
253
+ if (resolved.status === "opaque") {
254
+ if (resolved.args.length >= 2) return appliedArgumentsFor(resolved.args);
255
+ return {
256
+ ...resolved,
257
+ args: [],
258
+ tail: []
259
+ };
260
+ }
261
+ return appliedArgumentsFor(resolved.args);
262
+ };
263
+ const isDocumentReceiver = (node) => {
264
+ const receiver = unwrapExpression(node);
265
+ if (isIdentifier(receiver, "document")) return true;
266
+ if (receiver?.type !== "MemberExpression") return false;
267
+ const name = memberPropertyName(receiver);
268
+ return name !== null && DOCUMENT_MEMBERS.has(name);
49
269
  };
50
270
  var no_unsafe_inner_html_default = eslintCompatPlugin({
51
271
  meta: { name: "no-unsafe-inner-html" },
52
272
  rules: { "no-unsafe-inner-html": {
53
273
  meta: {
54
274
  type: "problem",
275
+ schema: [{
276
+ type: "object",
277
+ properties: { sanitizerPolicy: {
278
+ type: "string",
279
+ enum: SANITIZER_POLICY_VALUES
280
+ } },
281
+ additionalProperties: false
282
+ }],
55
283
  messages: {
56
- unsafeInnerHtml: "Raw HTML injected into the DOM must come from a provably sanitized/escaped value: a static string, an HTML/DOM sanitize/escape call (or its .value/.data/.html result). Wrap the value in a sanitizer, or add a `// safe-html: <reason naming the sanitizer/source>` comment on the line directly above if it is escaped at its source.",
284
+ 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.",
57
285
  unsafeInnerHtmlSpread: "Do not spread an object into dangerouslySetInnerHTML. Keep `__html` inline so this rule can prove the HTML value is sanitized or escaped."
58
286
  }
59
287
  },
60
288
  createOnce(context) {
61
- const escapeHatchLines = /* @__PURE__ */ new Set();
62
- const recordEscapeHatches = (node) => {
63
- const comments = node && Array.isArray(node.comments) ? node.comments.filter(isComment) : [];
64
- for (const comment of comments) if (ESCAPE_HATCH_RE.test(comment.value)) escapeHatchLines.add(comment.loc.end.line);
289
+ const isKnownDomTarget = ({ node, scope, seen = /* @__PURE__ */ new Set(), kind = "element" }) => {
290
+ const target = unwrapExpression(node);
291
+ if (isIdentifier(target)) {
292
+ let current = scope;
293
+ while (current !== null) {
294
+ const variable = current.set.get(target.name);
295
+ if (variable === void 0) {
296
+ current = current.upper;
297
+ continue;
298
+ }
299
+ if (variable.defs.length === 0) return kind === "element" && isDocumentReceiver(target);
300
+ if (seen.has(variable)) return false;
301
+ seen.add(variable);
302
+ const definition = variable.defs.at(0);
303
+ 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;
304
+ return isKnownDomTarget({
305
+ node: definition.node.init,
306
+ scope: context.sourceCode.getScope(definition.node),
307
+ seen,
308
+ kind
309
+ });
310
+ }
311
+ }
312
+ const hasProducerReceiver = (receiver, producer) => {
313
+ if (producer.receiver !== "element" && isDocumentReceiver(receiver)) return true;
314
+ return producer.receiver !== "document" && isKnownDomTarget({
315
+ node: receiver,
316
+ scope,
317
+ seen
318
+ });
319
+ };
320
+ const isElementCollection = (collection) => isKnownDomTarget({
321
+ node: collection,
322
+ scope,
323
+ seen,
324
+ kind: "collection"
325
+ });
326
+ if (kind === "element" && target?.type === "MemberExpression") {
327
+ const property = memberPropertyName(target);
328
+ const producer = DOM_ELEMENT_PRODUCERS.find((entry) => entry.kind === "property" && entry.name === property);
329
+ if (producer !== void 0) return hasProducerReceiver(target.object, producer);
330
+ const index = unwrapExpression(target.property);
331
+ if (target.computed === true && index?.type === "Literal" && typeof index.value === "number" && Number.isInteger(index.value) && index.value >= 0) return isElementCollection(target.object);
332
+ }
333
+ if (target?.type === "CallExpression") {
334
+ const callee = invokedCallee(target);
335
+ if (callee?.type !== "MemberExpression") return false;
336
+ const method = memberPropertyName(callee);
337
+ if (kind === "element" && method === "item" && isElementCollection(callee.object)) return true;
338
+ const producer = DOM_ELEMENT_PRODUCERS.find((entry) => entry.kind === (kind === "collection" ? "collection" : "method") && entry.name === method);
339
+ return producer !== void 0 && hasProducerReceiver(callee.object, producer);
340
+ }
341
+ return kind === "element" && isDocumentReceiver(target);
65
342
  };
66
- const hasEscapeHatchAbove = (node) => escapeHatchLines.has(node.loc.start.line - 1);
67
- const reportIfUnsafe = (node) => {
68
- if (isProvenSafeValue(node)) return;
69
- if (hasEscapeHatchAbove(node)) return;
70
- context.report({
343
+ let sanitizerPolicy = SANITIZER_POLICY.allowlist;
344
+ const waiverLines = /* @__PURE__ */ new Set();
345
+ const sinks = [];
346
+ const observeSink = (node, kind) => {
347
+ if (isAstNode(node)) sinks.push({
71
348
  node,
72
- messageId: "unsafeInnerHtml"
349
+ kind
73
350
  });
74
351
  };
75
- const reportPayloadSpreads = (objectNode) => {
76
- for (const property of objectNode.properties) {
77
- if (property?.type !== "SpreadElement") continue;
78
- context.report({
79
- node: property,
80
- messageId: "unsafeInnerHtmlSpread"
81
- });
352
+ const consumeWaiver = (node) => {
353
+ const line = startLine(node) - 1;
354
+ if (!waiverLines.has(line)) return false;
355
+ waiverLines.delete(line);
356
+ return true;
357
+ };
358
+ const reportIfUnsafe = (node) => {
359
+ observeSink(node, isProvenSafeValue(node, sanitizerPolicy) ? "safe" : "unsafe");
360
+ };
361
+ const checkDangerousPayload = (value, htmlKey) => {
362
+ const expression = unwrapExpression(value);
363
+ if (expression?.type !== "ObjectExpression") {
364
+ reportIfUnsafe(value);
365
+ return;
366
+ }
367
+ for (const property of objectProperties(expression)) {
368
+ if (isOpaqueProperty(property)) {
369
+ observeSink(property, "opaque-payload");
370
+ continue;
371
+ }
372
+ if (property.type === "Property" && objectPropertyKey(property) === htmlKey) reportIfUnsafe(property.value);
373
+ }
374
+ };
375
+ const checkReactHtmlProp = (prop, value) => {
376
+ switch (prop.kind) {
377
+ case "payload":
378
+ checkDangerousPayload(value, prop.htmlKey);
379
+ return;
380
+ case "value":
381
+ reportIfUnsafe(value);
382
+ return;
383
+ default: throw new Error(`Unknown React HTML prop: ${String(prop)}`);
384
+ }
385
+ };
386
+ const checkHtmlPropertySources = (sources, domTarget) => {
387
+ for (const source of sources) {
388
+ if (unwrapExpression(source)?.type !== "ObjectExpression") {
389
+ if (domTarget) reportIfUnsafe(source);
390
+ continue;
391
+ }
392
+ for (const property of objectProperties(source)) {
393
+ if (isOpaqueProperty(property)) {
394
+ if (domTarget) reportIfUnsafe(property);
395
+ continue;
396
+ }
397
+ const key = property.type === "Property" ? objectPropertyKey(property) : null;
398
+ if (key !== null && HTML_PROPERTIES.has(key)) reportIfUnsafe(property.value);
399
+ }
82
400
  }
83
401
  };
84
402
  return {
85
403
  before() {
86
- escapeHatchLines.clear();
404
+ sanitizerPolicy = sanitizerPolicyFrom(context.options.at(0));
405
+ waiverLines.clear();
406
+ sinks.length = 0;
407
+ },
408
+ "Program:exit"() {
409
+ sinks.sort((left, right) => left.node.range[0] - right.node.range[0]);
410
+ for (const { node, kind } of sinks) {
411
+ const waived = consumeWaiver(node);
412
+ if (kind === "safe" || kind === "unsafe" && waived) continue;
413
+ context.report({
414
+ node,
415
+ messageId: kind === "opaque-payload" ? "unsafeInnerHtmlSpread" : "unsafeInnerHtml"
416
+ });
417
+ }
418
+ sinks.length = 0;
87
419
  },
88
420
  Program(node) {
89
- recordEscapeHatches(node);
421
+ const comments = Array.isArray(node.comments) ? node.comments : [];
422
+ for (const comment of comments) if (isWaiverComment(comment)) waiverLines.add(comment.loc.end.line);
90
423
  },
91
424
  JSXAttribute(node) {
92
- if (!isJsxIdentifier(node.name, "dangerouslySetInnerHTML")) return;
93
- const value = node.value;
94
- if (value?.type !== "JSXExpressionContainer") return;
95
- if (!value.expression) return;
96
- const expression = unwrapExpression(value.expression);
97
- if (expression?.type === "ObjectExpression") {
98
- reportPayloadSpreads(expression);
99
- return;
100
- }
101
- reportIfUnsafe(value.expression);
425
+ const name = jsxAttributeName(node.name);
426
+ const prop = REACT_HTML_PROPS.find((entry) => entry.name === name);
427
+ if (prop === void 0) return;
428
+ const value = unwrapExpression(node.value);
429
+ checkReactHtmlProp(prop, value?.type === "JSXExpressionContainer" ? value.expression : value);
102
430
  },
103
431
  Property(node) {
104
- if (!isDangerouslySetInnerHtmlValue(node)) return;
105
- reportIfUnsafe(node.value);
432
+ const key = objectPropertyKey(node);
433
+ const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);
434
+ if (prop?.kind !== "payload") return;
435
+ const container = node.parent;
436
+ if (isAstNode(container) && container.type === "ObjectExpression") checkReactHtmlProp(prop, node.value);
106
437
  },
107
438
  AssignmentExpression(node) {
108
- const target = node.left;
109
- if (target.type !== "MemberExpression" || target.computed !== false || !isIdentifier(target.property, "innerHTML")) return;
110
- reportIfUnsafe(node.right);
439
+ const target = unwrapExpression(node.left);
440
+ if (target?.type !== "MemberExpression") return;
441
+ const name = memberPropertyName(target);
442
+ if (name !== null && HTML_PROPERTIES.has(name)) reportIfUnsafe(node.right);
443
+ },
444
+ CallExpression(node) {
445
+ const call = node;
446
+ const callee = isAstNode(call) ? invokedCallee(call) : null;
447
+ const invoked = invokedArguments(node);
448
+ const factory = staticCalleeName(callee);
449
+ if (factory !== null && REACT_ELEMENT_FACTORIES.has(factory)) {
450
+ for (const property of objectProperties(invoked.args.at(1))) {
451
+ if (property.type !== "Property") continue;
452
+ const key = objectPropertyKey(property);
453
+ const prop = REACT_HTML_PROPS.find((entry) => entry.name === key);
454
+ if (prop?.kind === "value") checkReactHtmlProp(prop, property.value);
455
+ }
456
+ return;
457
+ }
458
+ if (callee?.type !== "MemberExpression") return;
459
+ const method = memberPropertyName(callee);
460
+ if (method === null) return;
461
+ if (HTML_FIRST_ARGUMENT_METHODS.has(method)) {
462
+ if (invoked.status === "opaque") {
463
+ reportIfUnsafe(node);
464
+ return;
465
+ }
466
+ reportIfUnsafe(invoked.args.at(0));
467
+ return;
468
+ }
469
+ if (method === "insertAdjacentHTML") {
470
+ if (invoked.status === "opaque") {
471
+ reportIfUnsafe(node);
472
+ return;
473
+ }
474
+ reportIfUnsafe(invoked.args.at(1));
475
+ return;
476
+ }
477
+ if (method === "setAttribute") {
478
+ if (invoked.status === "opaque") {
479
+ reportIfUnsafe(node);
480
+ return;
481
+ }
482
+ if (staticStringValue(invoked.args.at(0))?.toLowerCase() === "srcdoc") reportIfUnsafe(invoked.args.at(1));
483
+ return;
484
+ }
485
+ if (DOCUMENT_WRITE_METHODS.has(method) && isDocumentReceiver(callee.object)) {
486
+ if (invoked.status === "opaque") {
487
+ reportIfUnsafe(node);
488
+ return;
489
+ }
490
+ observeSink(node, invoked.args.every((argument) => isProvenSafeValue(argument, sanitizerPolicy)) ? "safe" : "unsafe");
491
+ return;
492
+ }
493
+ if (isIdentifier(callee.object, "Object") && method === "assign") {
494
+ const domTarget = isKnownDomTarget({
495
+ node: invoked.args.at(0),
496
+ scope: context.sourceCode.getScope(node)
497
+ });
498
+ checkHtmlPropertySources([...invoked.args.slice(1), ...invoked.status === "opaque" ? invoked.tail : []], domTarget);
499
+ if (invoked.status === "opaque" && domTarget) reportIfUnsafe(invoked.spread);
500
+ }
111
501
  }
112
502
  };
113
503
  }
114
504
  } }
115
505
  });
116
506
  //#endregion
117
- export { no_unsafe_inner_html_default as default };
507
+ export { DOM_ELEMENT_PRODUCERS, REACT_HTML_PROPS, no_unsafe_inner_html_default as default };
118
508
 
119
509
  //# sourceMappingURL=no-unsafe-inner-html.mjs.map