oxlint-plugin-react-doctor 0.7.5-dev.bc49aaa → 0.7.5-dev.dbd4067

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.
Files changed (2) hide show
  1. package/dist/index.js +97 -57
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -23108,61 +23108,73 @@ const PURE_GLOBAL_CALLEE_NAMES = new Set([
23108
23108
  "Array",
23109
23109
  "BigInt",
23110
23110
  "Boolean",
23111
+ "encodeURIComponent",
23111
23112
  "Number",
23112
23113
  "Object",
23113
23114
  "String",
23114
23115
  "parseFloat",
23115
- "parseInt"
23116
+ "parseInt",
23117
+ "structuredClone"
23118
+ ]);
23119
+ const PURE_GLOBAL_CONSTRUCTOR_NAMES = new Set(["Date", "Set"]);
23120
+ const PURE_HELPER_NAMESPACE_MEMBER_NAMES = new Map([
23121
+ ["Array", new Set(["from"])],
23122
+ ["JSON", new Set([
23123
+ "isRawJSON",
23124
+ "parse",
23125
+ "rawJSON",
23126
+ "stringify"
23127
+ ])],
23128
+ ["Math", new Set([
23129
+ "abs",
23130
+ "acos",
23131
+ "acosh",
23132
+ "asin",
23133
+ "asinh",
23134
+ "atan",
23135
+ "atan2",
23136
+ "atanh",
23137
+ "cbrt",
23138
+ "ceil",
23139
+ "clz32",
23140
+ "cos",
23141
+ "cosh",
23142
+ "exp",
23143
+ "floor",
23144
+ "fround",
23145
+ "hypot",
23146
+ "imul",
23147
+ "log",
23148
+ "log10",
23149
+ "log1p",
23150
+ "log2",
23151
+ "max",
23152
+ "min",
23153
+ "pow",
23154
+ "round",
23155
+ "sign",
23156
+ "sin",
23157
+ "sinh",
23158
+ "sqrt",
23159
+ "tan",
23160
+ "tanh",
23161
+ "trunc"
23162
+ ])],
23163
+ ["Object", new Set(["assign"])]
23116
23164
  ]);
23117
- const PURE_GLOBAL_NAMESPACE_NAMES = new Set(["JSON", "Math"]);
23118
- const PURE_HELPER_NAMESPACE_MEMBER_NAMES = new Map([["JSON", new Set([
23119
- "isRawJSON",
23120
- "parse",
23121
- "rawJSON",
23122
- "stringify"
23123
- ])], ["Math", new Set([
23124
- "abs",
23125
- "acos",
23126
- "acosh",
23127
- "asin",
23128
- "asinh",
23129
- "atan",
23130
- "atan2",
23131
- "atanh",
23132
- "cbrt",
23133
- "ceil",
23134
- "clz32",
23135
- "cos",
23136
- "cosh",
23137
- "exp",
23138
- "floor",
23139
- "fround",
23140
- "hypot",
23141
- "imul",
23142
- "log",
23143
- "log10",
23144
- "log1p",
23145
- "log2",
23146
- "max",
23147
- "min",
23148
- "pow",
23149
- "round",
23150
- "sign",
23151
- "sin",
23152
- "sinh",
23153
- "sqrt",
23154
- "tan",
23155
- "tanh",
23156
- "trunc"
23157
- ])]]);
23158
23165
  const PURE_MEMBER_TRANSFORM_NAMES = new Set([
23159
23166
  "concat",
23160
23167
  "filter",
23168
+ "flatMap",
23161
23169
  "join",
23162
23170
  "map",
23171
+ "reduce",
23172
+ "replace",
23173
+ "slice",
23163
23174
  "split",
23164
23175
  "toLowerCase",
23165
23176
  "toString",
23177
+ "toSorted",
23166
23178
  "toUpperCase",
23167
23179
  "trim"
23168
23180
  ]);
@@ -23544,6 +23556,11 @@ const analyzeHelperExpression = (expression, environment, usedParameterIndices)
23544
23556
  const callbackSummary = analyzeHelperStatements(node.body.body ?? [], callbackEnvironment, usedParameterIndices, analyzeHelperExpression);
23545
23557
  return callbackSummary.isValid && !callbackSummary.canContinue;
23546
23558
  }
23559
+ if (isNodeOfType(node, "NewExpression")) {
23560
+ const callee = stripParenExpression(node.callee);
23561
+ if (!isNodeOfType(callee, "Identifier") || !PURE_GLOBAL_CONSTRUCTOR_NAMES.has(callee.name) || environment.parameterIndices.has(callee.name) || environment.recursiveNames.has(callee.name) || environment.shadowedGlobalNames.has(callee.name)) return false;
23562
+ return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
23563
+ }
23547
23564
  if (isNodeOfType(node, "CallExpression")) {
23548
23565
  const callee = stripParenExpression(node.callee);
23549
23566
  const calleeRoot = getMemberRoot(callee);
@@ -23746,7 +23763,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23746
23763
  }
23747
23764
  const callee = stripParenExpression(node.callee);
23748
23765
  const calleeRoot = getMemberRoot(callee);
23749
- const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && getIdentifierBindingIdentity(analysis, callee) === null || isNodeOfType(calleeRoot, "Identifier") && PURE_GLOBAL_NAMESPACE_NAMES.has(calleeRoot.name) && getIdentifierBindingIdentity(analysis, calleeRoot) === null;
23766
+ const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && getIdentifierBindingIdentity(analysis, callee) === null || isNodeOfType(callee, "MemberExpression") && isNodeOfType(calleeRoot, "Identifier") && getIdentifierBindingIdentity(analysis, calleeRoot) === null && PURE_HELPER_NAMESPACE_MEMBER_NAMES.get(calleeRoot.name)?.has(getStaticMemberName$1(callee) ?? "") === true;
23750
23767
  const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName$1(callee) ?? "");
23751
23768
  if (isPureGlobalCall || isPureMemberTransform) {
23752
23769
  if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression")) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
@@ -23799,6 +23816,15 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23799
23816
  }
23800
23817
  return evidence;
23801
23818
  }
23819
+ if (isNodeOfType(node, "NewExpression")) {
23820
+ const callee = stripParenExpression(node.callee);
23821
+ if (!isNodeOfType(callee, "Identifier") || !PURE_GLOBAL_CONSTRUCTOR_NAMES.has(callee.name) || getIdentifierBindingIdentity(analysis, callee) !== null) {
23822
+ evidence.hasUnknownSource = true;
23823
+ return evidence;
23824
+ }
23825
+ for (const argument of node.arguments ?? []) mergeEvidence(evidence, collectValueEvidence(analysis, argument, frame, remainingCallFrames, new Set(visitedBindings)));
23826
+ return evidence;
23827
+ }
23802
23828
  if (isFunctionLike$1(node) || isNodeOfType(node, "AwaitExpression")) {
23803
23829
  evidence.hasUnknownSource = true;
23804
23830
  return evidence;
@@ -28348,20 +28374,31 @@ const noDocumentStartViewTransition = defineRule({
28348
28374
  } })
28349
28375
  });
28350
28376
  //#endregion
28377
+ //#region src/plugin/utils/get-static-property-name.ts
28378
+ const getStaticPropertyName = (memberExpression) => {
28379
+ const property = memberExpression.property;
28380
+ if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
28381
+ if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
28382
+ if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
28383
+ return null;
28384
+ };
28385
+ //#endregion
28351
28386
  //#region src/plugin/rules/js-performance/no-document-write.ts
28352
28387
  const MESSAGE$24 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
28353
28388
  const WRITE_METHODS = new Set(["write", "writeln"]);
28389
+ const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
28354
28390
  const noDocumentWrite = defineRule({
28355
28391
  id: "no-document-write",
28356
28392
  title: "document.write/writeln",
28357
28393
  severity: "warn",
28358
28394
  recommendation: "Don't use `document.write()`/`document.writeln()`. Append DOM nodes or set `innerHTML`/`textContent` on a specific element instead.",
28359
28395
  create: (context) => ({ CallExpression(node) {
28360
- const callee = node.callee;
28361
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
28396
+ const callee = stripParenExpression(node.callee);
28397
+ if (!isNodeOfType(callee, "MemberExpression")) return;
28362
28398
  const receiver = stripParenExpression(callee.object);
28363
- if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "document") return;
28364
- if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
28399
+ if (!isNodeOfType(receiver, "Identifier") || !isGlobalDocumentReference(receiver, context)) return;
28400
+ const methodName = getStaticPropertyName(callee);
28401
+ if (methodName === null || !WRITE_METHODS.has(methodName)) return;
28365
28402
  context.report({
28366
28403
  node,
28367
28404
  message: MESSAGE$24
@@ -29168,6 +29205,14 @@ const noEffectWithFreshDeps = defineRule({
29168
29205
  //#endregion
29169
29206
  //#region src/plugin/rules/security/no-eval.ts
29170
29207
  const SANDBOX_SURFACE_PATH_PATTERN = /(?:^|[/-])sandbox(?:$|[/-])|(?:^|\/)[\w.]+-sandbox(?:\/|\.[cm]?[jt]sx?$)/i;
29208
+ const getExecutableGlobalName = (node, context) => {
29209
+ const expression = stripParenExpression(node);
29210
+ if (isNodeOfType(expression, "Identifier")) return context.scopes.isGlobalReference(expression) ? expression.name : null;
29211
+ if (!isNodeOfType(expression, "MemberExpression")) return null;
29212
+ const receiver = stripParenExpression(expression.object);
29213
+ if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "globalThis" || !context.scopes.isGlobalReference(receiver)) return null;
29214
+ return getStaticPropertyName(expression);
29215
+ };
29171
29216
  const noEval = defineRule({
29172
29217
  id: "no-eval",
29173
29218
  title: "eval() runs untrusted code strings",
@@ -29177,20 +29222,21 @@ const noEval = defineRule({
29177
29222
  if (SANDBOX_SURFACE_PATH_PATTERN.test(normalizeFilename(context.filename ?? ""))) return {};
29178
29223
  return {
29179
29224
  CallExpression(node) {
29180
- if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "eval") {
29225
+ const executableGlobalName = getExecutableGlobalName(node.callee, context);
29226
+ if (executableGlobalName === "eval") {
29181
29227
  context.report({
29182
29228
  node,
29183
29229
  message: "eval() is a code-injection vulnerability: it runs any string as code."
29184
29230
  });
29185
29231
  return;
29186
29232
  }
29187
- if (isNodeOfType(node.callee, "Identifier") && (node.callee.name === "setTimeout" || node.callee.name === "setInterval") && isNodeOfType(node.arguments?.[0], "Literal") && typeof node.arguments[0].value === "string") context.report({
29233
+ if ((executableGlobalName === "setTimeout" || executableGlobalName === "setInterval") && isNodeOfType(node.arguments?.[0], "Literal") && typeof node.arguments[0].value === "string") context.report({
29188
29234
  node,
29189
- message: `Passing a string to ${node.callee.name}() is a code-injection vulnerability, since it runs that string as code.`
29235
+ message: `Passing a string to ${executableGlobalName}() is a code-injection vulnerability, since it runs that string as code.`
29190
29236
  });
29191
29237
  },
29192
29238
  NewExpression(node) {
29193
- if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "Function") {
29239
+ if (getExecutableGlobalName(node.callee, context) === "Function") {
29194
29240
  const onlyArgument = node.arguments.length === 1 ? node.arguments[0] : void 0;
29195
29241
  if (isNodeOfType(onlyArgument, "Literal") && typeof onlyArgument.value === "string" && onlyArgument.value.trim() === "return this") return;
29196
29242
  context.report({
@@ -54751,12 +54797,6 @@ const webhookSignatureRisk = defineRule({
54751
54797
  //#region src/plugin/rules/zod/utils/zod-ast.ts
54752
54798
  const ZOD_MODULE = "zod";
54753
54799
  const ZOD_MODULE_SOURCES = [ZOD_MODULE];
54754
- const getStaticPropertyName = (member) => {
54755
- const property = member.property;
54756
- if (!member.computed && isNodeOfType(property, "Identifier")) return property.name;
54757
- if (member.computed && isNodeOfType(property, "Literal") && typeof property.value === "string") return property.value;
54758
- return null;
54759
- };
54760
54800
  const importInfoCache = /* @__PURE__ */ new WeakMap();
54761
54801
  const getImportInfoForIdentifier = (identifier) => {
54762
54802
  if (importInfoCache.has(identifier)) return importInfoCache.get(identifier) ?? null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.5-dev.bc49aaa",
3
+ "version": "0.7.5-dev.dbd4067",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",