oxlint-plugin-react-doctor 0.5.4-dev.f229c75 → 0.5.5-dev.5fc0e27

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/index.js CHANGED
@@ -323,9 +323,18 @@ const BROWSER_ARTIFACT_PATH_PATTERNS = [
323
323
  const AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN = /\b(?:exec|execSync|spawn|child_process|eval|new Function|vm\.run|readFile|writeFile|fs\.read|fs\.write|fetch|axios|http\.request|sandbox|runCode|executeCode)\b/;
324
324
  //#endregion
325
325
  //#region src/plugin/rules/security-scan/utils/is-browser-artifact-path.ts
326
- const isServerOnlyBuildArtifactPath = (relativePath) => /(?:^|\/)(?:\.next\/server|\.output\/server)\//.test(relativePath);
326
+ const SERVER_BUILD_ROOT_SEGMENTS = new Set([".next", ".output"]);
327
+ const isNonShippedBuildArtifactPath = (relativePath) => {
328
+ const segments = relativePath.split("/");
329
+ for (let index = 0; index < segments.length; index += 1) {
330
+ if (!SERVER_BUILD_ROOT_SEGMENTS.has(segments[index])) continue;
331
+ if (segments[index] === ".next" && segments[index + 1] === "dev") return true;
332
+ if (segments[index + 1] === "server" && index + 2 < segments.length) return true;
333
+ }
334
+ return false;
335
+ };
327
336
  const isBrowserArtifactPath = (relativePath, isGeneratedBundle) => {
328
- if (isServerOnlyBuildArtifactPath(relativePath)) return false;
337
+ if (isNonShippedBuildArtifactPath(relativePath)) return false;
329
338
  if (isGeneratedBundle) return true;
330
339
  if (relativePath.endsWith(".map")) return true;
331
340
  return BROWSER_ARTIFACT_PATH_PATTERNS.some((pattern) => pattern.test(relativePath));
@@ -1108,6 +1117,11 @@ const getImportedNameFromModule = (contextNode, localIdentifierName, moduleSourc
1108
1117
  if (info.source !== moduleSource) return null;
1109
1118
  return info.imported;
1110
1119
  };
1120
+ const getImportSourceForName = (contextNode, localIdentifierName) => {
1121
+ const lookup = getImportLookup(contextNode);
1122
+ if (!lookup) return null;
1123
+ return lookup.get(localIdentifierName)?.source ?? null;
1124
+ };
1111
1125
  //#endregion
1112
1126
  //#region src/plugin/utils/find-variable-initializer.ts
1113
1127
  const FUNCTION_LIKE_TYPES$1 = new Set([
@@ -12705,6 +12719,7 @@ const keyLifecycleRisk = defineRule({
12705
12719
  id: "key-lifecycle-risk",
12706
12720
  title: "Long-lived key material in repository",
12707
12721
  severity: "error",
12722
+ committedFilesOnly: true,
12708
12723
  recommendation: "Remove private keys from source, rotate exposed credentials, prefer short-lived deploy credentials, and document revocation/expiry for release keys.",
12709
12724
  scan: scanByPattern({
12710
12725
  shouldScan: (file) => !TEST_CONTEXT_PATTERN.test(file.relativePath) && !DOCUMENTATION_CONTEXT_PATTERN.test(file.relativePath),
@@ -22559,9 +22574,10 @@ const TANSTACK_ROUTE_CREATION_FUNCTIONS = new Set([
22559
22574
  "createRootRouteWithContext"
22560
22575
  ]);
22561
22576
  const TANSTACK_SERVER_FN_NAMES = new Set(["createServerFn"]);
22577
+ const TANSTACK_INPUT_VALIDATOR_METHOD_NAMES = new Set(["validator", "inputValidator"]);
22562
22578
  const TANSTACK_MIDDLEWARE_METHOD_ORDER = [
22563
22579
  "middleware",
22564
- "inputValidator",
22580
+ "validator",
22565
22581
  "client",
22566
22582
  "server",
22567
22583
  "handler"
@@ -27034,6 +27050,8 @@ const publicEnvSecretName = defineRule({
27034
27050
  });
27035
27051
  //#endregion
27036
27052
  //#region src/plugin/rules/tanstack-query/query-destructure-result.ts
27053
+ const TANSTACK_QUERY_PACKAGE_PATTERN = /^@tanstack\/[\w-]*query[\w-]*$/;
27054
+ const isTanstackQuerySource = (source) => TANSTACK_QUERY_PACKAGE_PATTERN.test(source) || source === "react-query";
27037
27055
  const queryDestructureResult = defineRule({
27038
27056
  id: "query-destructure-result",
27039
27057
  title: "Whole query result subscribes to every field",
@@ -27046,6 +27064,8 @@ const queryDestructureResult = defineRule({
27046
27064
  if (!node.init || !isNodeOfType(node.init, "CallExpression")) return;
27047
27065
  const calleeName = isNodeOfType(node.init.callee, "Identifier") ? node.init.callee.name : null;
27048
27066
  if (!calleeName || !TANSTACK_QUERY_HOOKS.has(calleeName)) return;
27067
+ const importSource = getImportSourceForName(node, calleeName);
27068
+ if (importSource !== null && !isTanstackQuerySource(importSource)) return;
27049
27069
  context.report({
27050
27070
  node: node.id,
27051
27071
  message: `Destructure ${calleeName}() results instead of assigning the whole query object, so TanStack Query only subscribes to the fields you use.`
@@ -27888,6 +27908,7 @@ const repositorySecretFile = defineRule({
27888
27908
  id: "repository-secret-file",
27889
27909
  title: "Secret file checked into repository",
27890
27910
  severity: "error",
27911
+ committedFilesOnly: true,
27891
27912
  recommendation: "Remove committed env files, service-account credentials, npm auth tokens, and webhook URLs; rotate exposed values and keep only redacted examples in source.",
27892
27913
  scan: (file) => {
27893
27914
  if (!isRepositorySecretFilePath(file.relativePath)) return [];
@@ -35200,7 +35221,7 @@ const walkServerFnChain = (outerNode) => {
35200
35221
  const result = {
35201
35222
  isServerFnChain: false,
35202
35223
  specifiedMethod: null,
35203
- hasInputValidator: false
35224
+ hasInputValidation: false
35204
35225
  };
35205
35226
  if (!isNodeOfType(outerNode, "CallExpression")) return result;
35206
35227
  if (!isNodeOfType(outerNode.callee, "MemberExpression")) return result;
@@ -35214,7 +35235,7 @@ const walkServerFnChain = (outerNode) => {
35214
35235
  for (const property of optionsArgument.properties ?? []) if (isNodeOfType(property, "Property") && isNodeOfType(property.key, "Identifier") && property.key.name === "method" && isNodeOfType(property.value, "Literal") && typeof property.value.value === "string") result.specifiedMethod = property.value.value;
35215
35236
  }
35216
35237
  }
35217
- if (calleeName === "inputValidator") result.hasInputValidator = true;
35238
+ if (calleeName && TANSTACK_INPUT_VALIDATOR_METHOD_NAMES.has(calleeName)) result.hasInputValidation = true;
35218
35239
  if (isNodeOfType(currentNode.callee, "MemberExpression")) currentNode = currentNode.callee.object;
35219
35240
  else break;
35220
35241
  }
@@ -35768,13 +35789,14 @@ const tanstackStartRoutePropertyOrder = defineRule({
35768
35789
  });
35769
35790
  //#endregion
35770
35791
  //#region src/plugin/rules/tanstack-start/tanstack-start-server-fn-method-order.ts
35792
+ const toMethodOrderToken = (methodName) => TANSTACK_INPUT_VALIDATOR_METHOD_NAMES.has(methodName) ? "validator" : methodName;
35771
35793
  const tanstackStartServerFnMethodOrder = defineRule({
35772
35794
  id: "tanstack-start-server-fn-method-order",
35773
35795
  title: "Server function method order breaks type inference",
35774
35796
  tags: ["test-noise"],
35775
35797
  requires: ["tanstack-start"],
35776
35798
  severity: "error",
35777
- recommendation: "Chain methods in order: .middleware() → .inputValidator() → .client() → .server() → .handler(). Types depend on this sequence.",
35799
+ recommendation: "Chain methods in order: .middleware() → .validator() → .client() → .server() → .handler(). Types depend on this sequence.",
35778
35800
  create: (context) => ({ CallExpression(node) {
35779
35801
  if (!isNodeOfType(node.callee, "MemberExpression")) return;
35780
35802
  const methodNames = [];
@@ -35789,10 +35811,10 @@ const tanstackStartServerFnMethodOrder = defineRule({
35789
35811
  } else return;
35790
35812
  const ownMethodName = isNodeOfType(node.callee.property, "Identifier") ? node.callee.property.name : null;
35791
35813
  if (methodNames[methodNames.length - 1] !== ownMethodName) return;
35792
- const orderSensitiveMethods = methodNames.filter((name) => TANSTACK_MIDDLEWARE_METHOD_ORDER.includes(name));
35814
+ const orderSensitiveMethods = methodNames.filter((name) => TANSTACK_MIDDLEWARE_METHOD_ORDER.includes(toMethodOrderToken(name)));
35793
35815
  let lastIndex = -1;
35794
35816
  for (const methodName of orderSensitiveMethods) {
35795
- const currentIndex = TANSTACK_MIDDLEWARE_METHOD_ORDER.indexOf(methodName);
35817
+ const currentIndex = TANSTACK_MIDDLEWARE_METHOD_ORDER.indexOf(toMethodOrderToken(methodName));
35796
35818
  if (currentIndex < lastIndex) {
35797
35819
  const expectedBefore = TANSTACK_MIDDLEWARE_METHOD_ORDER[lastIndex];
35798
35820
  context.report({
@@ -35813,7 +35835,7 @@ const tanstackStartServerFnValidateInput = defineRule({
35813
35835
  tags: ["test-noise"],
35814
35836
  requires: ["tanstack-start"],
35815
35837
  severity: "warn",
35816
- recommendation: "Add `.inputValidator(schema)` before `.handler()`. This data crosses the network and must be validated at runtime.",
35838
+ recommendation: "Add `.validator(schema)` before `.handler()`. This data crosses the network and must be validated at runtime.",
35817
35839
  create: (context) => ({ CallExpression(node) {
35818
35840
  if (!isNodeOfType(node.callee, "MemberExpression")) return;
35819
35841
  if (!isNodeOfType(node.callee.property, "Identifier")) return;
@@ -35827,9 +35849,9 @@ const tanstackStartServerFnValidateInput = defineRule({
35827
35849
  if (isNodeOfType(child, "MemberExpression") && isNodeOfType(child.property, "Identifier") && child.property.name === "data") accessesData = true;
35828
35850
  if (isNodeOfType(child, "ObjectPattern") && child.properties?.some((property) => isNodeOfType(property, "Property") && isNodeOfType(property.key, "Identifier") && property.key.name === "data")) accessesData = true;
35829
35851
  });
35830
- if (accessesData && !chainInfo.hasInputValidator) context.report({
35852
+ if (accessesData && !chainInfo.hasInputValidation) context.report({
35831
35853
  node,
35832
- message: "This server function reads network data with no inputValidator(), so anyone can send unvalidated input."
35854
+ message: "This server function reads network data with no validator(), so anyone can send unvalidated input."
35833
35855
  });
35834
35856
  } })
35835
35857
  });
@@ -36001,7 +36023,7 @@ const voidDomElementsNoChildren = defineRule({
36001
36023
  //#region src/plugin/rules/security-scan/webhook-signature-risk.ts
36002
36024
  const WEBHOOK_HANDLER_PATTERN = /(?:^|\/)[^/]*webhook[^/]*\/|(?:^|\/)[^/]*webhook[^/]*\.[cm]?[jt]s$|\bwebhook\b/i;
36003
36025
  const WEBHOOK_ENTRYPOINT_PATTERN = /\b(?:export\s+(?:async\s+)?function\s+POST|export\s+const\s+(?:POST|handler|webhook)|webhookHandler|webhookRoute)\b/i;
36004
- const WEBHOOK_SIGNATURE_VERIFICATION_PATTERN = /verifySignature|verify.*signature|verify\w*(?:Webhook|Auth)|constructEvent|createHmac|timingSafeEqual|svix|webhookSecret|stripe\.webhooks|["'][\w-]*signature["']/i;
36026
+ const WEBHOOK_SIGNATURE_VERIFICATION_PATTERN = new RegExp(`${/verifySignature|verify.*signature|verify\w*(?:Webhook|Auth)|constructEvent|createHmac|timingSafeEqual|svix|webhookSecret|stripe\.webhooks|["'][\w-]*signature["']/.source}|${/\b[A-Za-z]{0,40}(?:verif|valid|check|assert|authenticat|compare|guard)[A-Za-z]{0,40}(?:secret|signature|hmac|webhook|digest)[A-Za-z]{0,40}\s*\(/.source}`, "i");
36005
36027
  const OUTBOUND_WEBHOOK_URL_MENTION_PATTERN = /webhook[\s_-]?ur[il]\w*/gi;
36006
36028
  const OUTBOUND_WEBHOOK_CONFIG_PATTERN = /process\.env\.\w*WEBHOOK_URL|\b(?:send|post|dispatch|publish|notify)\w*Webhook/;
36007
36029
  const REQUEST_READ_PATTERN = /\b(?:req|request)\b/;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.5.4-dev.f229c75",
3
+ "version": "0.5.5-dev.5fc0e27",
4
4
  "description": "oxlint plugin for React Doctor: diagnose React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
5
5
  "keywords": [
6
6
  "accessibility",