oxlint-plugin-react-doctor 0.7.3-dev.82187a3 → 0.7.3-dev.f91ede7

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.d.ts CHANGED
@@ -4836,6 +4836,29 @@ declare const REACT_DOCTOR_RULES: readonly [{
4836
4836
  readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
4837
4837
  readonly create: (context: RuleContext) => RuleVisitors;
4838
4838
  };
4839
+ }, {
4840
+ readonly key: "react-doctor/no-match-media-in-state-initializer";
4841
+ readonly id: "no-match-media-in-state-initializer";
4842
+ readonly source: "react-doctor";
4843
+ readonly originallyExternal: false;
4844
+ readonly rule: {
4845
+ readonly framework: "global";
4846
+ readonly category: "Bugs";
4847
+ readonly requires: readonly Capability[];
4848
+ readonly id: string;
4849
+ readonly title?: string;
4850
+ readonly severity: RuleSeverity;
4851
+ readonly disabledWhen?: ReadonlyArray<Capability>;
4852
+ readonly tags?: ReadonlyArray<string>;
4853
+ readonly matchByOccurrence?: boolean;
4854
+ readonly defaultEnabled?: boolean;
4855
+ readonly lifecycle?: "retired";
4856
+ readonly scan?: FileScan;
4857
+ readonly committedFilesOnly?: boolean;
4858
+ readonly recommendation?: string;
4859
+ readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
4860
+ readonly create: (context: RuleContext) => RuleVisitors;
4861
+ };
4839
4862
  }, {
4840
4863
  readonly key: "react-doctor/no-mirror-prop-effect";
4841
4864
  readonly id: "no-mirror-prop-effect";
@@ -13964,6 +13987,29 @@ declare const RULES: readonly [{
13964
13987
  readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
13965
13988
  readonly create: (context: RuleContext) => RuleVisitors;
13966
13989
  };
13990
+ }, {
13991
+ readonly key: "react-doctor/no-match-media-in-state-initializer";
13992
+ readonly id: "no-match-media-in-state-initializer";
13993
+ readonly source: "react-doctor";
13994
+ readonly originallyExternal: false;
13995
+ readonly rule: {
13996
+ readonly framework: "global";
13997
+ readonly category: "Bugs";
13998
+ readonly requires: readonly Capability[];
13999
+ readonly id: string;
14000
+ readonly title?: string;
14001
+ readonly severity: RuleSeverity;
14002
+ readonly disabledWhen?: ReadonlyArray<Capability>;
14003
+ readonly tags?: ReadonlyArray<string>;
14004
+ readonly matchByOccurrence?: boolean;
14005
+ readonly defaultEnabled?: boolean;
14006
+ readonly lifecycle?: "retired";
14007
+ readonly scan?: FileScan;
14008
+ readonly committedFilesOnly?: boolean;
14009
+ readonly recommendation?: string;
14010
+ readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
14011
+ readonly create: (context: RuleContext) => RuleVisitors;
14012
+ };
13967
14013
  }, {
13968
14014
  readonly key: "react-doctor/no-mirror-prop-effect";
13969
14015
  readonly id: "no-mirror-prop-effect";
package/dist/index.js CHANGED
@@ -31160,6 +31160,64 @@ const noManyBooleanProps = defineRule({
31160
31160
  }
31161
31161
  });
31162
31162
  //#endregion
31163
+ //#region src/plugin/utils/is-global-match-media-call.ts
31164
+ const isGlobalMatchMediaCall = (node, scopes) => {
31165
+ if (!isNodeOfType(node, "CallExpression")) return false;
31166
+ const callee = stripParenExpression(node.callee);
31167
+ if (isNodeOfType(callee, "Identifier")) return callee.name === "matchMedia" && scopes.isGlobalReference(callee);
31168
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "matchMedia") return false;
31169
+ const receiver = stripParenExpression(callee.object);
31170
+ return isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && scopes.isGlobalReference(receiver);
31171
+ };
31172
+ //#endregion
31173
+ //#region src/plugin/rules/performance/no-match-media-in-state-initializer.ts
31174
+ const REACT_USE_STATE_OPTIONS = {
31175
+ allowGlobalReactNamespace: true,
31176
+ allowUnboundBareCalls: false
31177
+ };
31178
+ const isReactUseStateCall = (node, context) => isReactApiCall(node, "useState", context.scopes, REACT_USE_STATE_OPTIONS);
31179
+ const findDirectMatchMediaCall = (root, context) => {
31180
+ let matchMediaCall = null;
31181
+ walkAst(root, (visitedNode) => {
31182
+ if (matchMediaCall) return false;
31183
+ if (isFunctionLike$1(visitedNode)) return false;
31184
+ if (isGlobalMatchMediaCall(visitedNode, context.scopes)) {
31185
+ matchMediaCall = visitedNode;
31186
+ return false;
31187
+ }
31188
+ });
31189
+ return matchMediaCall;
31190
+ };
31191
+ const findMatchMediaCallDuringInitialization = (initializer, context) => {
31192
+ const rootInitializer = stripParenExpression(initializer);
31193
+ if (!isFunctionLike$1(rootInitializer)) return findDirectMatchMediaCall(rootInitializer, context);
31194
+ if (rootInitializer.async || rootInitializer.generator) return null;
31195
+ return findDirectMatchMediaCall(rootInitializer.body, context);
31196
+ };
31197
+ const noMatchMediaInStateInitializer = defineRule({
31198
+ id: "no-match-media-in-state-initializer",
31199
+ title: "matchMedia in state initializer",
31200
+ severity: "warn",
31201
+ category: "Correctness",
31202
+ disabledWhen: ["vite", "cra"],
31203
+ recommendation: "Prefer CSS media queries for layout, or subscribe with `useSyncExternalStore` and provide a stable server snapshot.",
31204
+ create: (context) => {
31205
+ if (isTestlikeFilename(context.filename)) return {};
31206
+ if (classifyReactNativeFileTarget(context) === "react-native") return {};
31207
+ return { CallExpression(node) {
31208
+ if (!isReactUseStateCall(node, context)) return;
31209
+ const initializer = node.arguments?.[0];
31210
+ if (!initializer || initializer.type === "SpreadElement") return;
31211
+ const matchMediaCall = findMatchMediaCallDuringInitialization(initializer, context);
31212
+ if (!matchMediaCall) return;
31213
+ context.report({
31214
+ node: matchMediaCall,
31215
+ message: "`matchMedia()` in a useState initializer can cause an SSR crash or seed different server and hydration state. Prefer CSS media queries for layout, or use `useSyncExternalStore` with a stable server snapshot."
31216
+ });
31217
+ } };
31218
+ }
31219
+ });
31220
+ //#endregion
31163
31221
  //#region src/plugin/rules/state-and-effects/no-mirror-prop-effect.ts
31164
31222
  const getPropRootName = (expression, propNames) => {
31165
31223
  const rootName = getRootIdentifierName(expression, { followCallChains: true });
@@ -55255,6 +55313,18 @@ const reactDoctorRules = [
55255
55313
  category: "Maintainability"
55256
55314
  }
55257
55315
  },
55316
+ {
55317
+ key: "react-doctor/no-match-media-in-state-initializer",
55318
+ id: "no-match-media-in-state-initializer",
55319
+ source: "react-doctor",
55320
+ originallyExternal: false,
55321
+ rule: {
55322
+ ...noMatchMediaInStateInitializer,
55323
+ framework: "global",
55324
+ category: "Bugs",
55325
+ requires: [...new Set(["react", ...noMatchMediaInStateInitializer.requires ?? []])]
55326
+ }
55327
+ },
55258
55328
  {
55259
55329
  key: "react-doctor/no-mirror-prop-effect",
55260
55330
  id: "no-mirror-prop-effect",
@@ -58123,6 +58193,7 @@ const CROSS_FILE_RULE_IDS = new Set([
58123
58193
  "no-full-lodash-import",
58124
58194
  "no-indeterminate-attribute",
58125
58195
  "no-locale-format-in-render",
58196
+ "no-match-media-in-state-initializer",
58126
58197
  "no-mutating-reducer-state",
58127
58198
  "prefer-dynamic-import",
58128
58199
  "rendering-hydration-mismatch-time",
@@ -58259,6 +58330,7 @@ const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
58259
58330
  ["no-full-lodash-import", collectNearestManifestDependencies],
58260
58331
  ["no-indeterminate-attribute", collectNearestManifestDependencies],
58261
58332
  ["no-locale-format-in-render", collectNearestManifestDependencies],
58333
+ ["no-match-media-in-state-initializer", collectNearestManifestDependencies],
58262
58334
  ["no-mutating-reducer-state", collectMutatingReducerDependencies],
58263
58335
  ["prefer-dynamic-import", collectNearestManifestDependencies],
58264
58336
  ["rendering-hydration-mismatch-time", collectNearestManifestDependencies],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.3-dev.82187a3",
3
+ "version": "0.7.3-dev.f91ede7",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",