oxlint-plugin-react-doctor 0.2.14-dev.75c1f99 → 0.2.14-dev.8921575

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
@@ -677,6 +677,24 @@ declare const REACT_DOCTOR_RULES: readonly [{
677
677
  readonly recommendation?: string;
678
678
  readonly create: (context: RuleContext) => RuleVisitors;
679
679
  };
680
+ }, {
681
+ readonly key: "react-doctor/expo-no-non-inlined-env";
682
+ readonly id: "expo-no-non-inlined-env";
683
+ readonly source: "react-doctor";
684
+ readonly originallyExternal: false;
685
+ readonly rule: {
686
+ readonly framework: "react-native";
687
+ readonly category: "Bugs";
688
+ readonly tags: readonly string[];
689
+ readonly id: string;
690
+ readonly title?: string;
691
+ readonly severity: RuleSeverity;
692
+ readonly requires?: ReadonlyArray<string>;
693
+ readonly disabledBy?: ReadonlyArray<string>;
694
+ readonly defaultEnabled?: boolean;
695
+ readonly recommendation?: string;
696
+ readonly create: (context: RuleContext) => RuleVisitors;
697
+ };
680
698
  }, {
681
699
  readonly key: "react-doctor/forbid-component-props";
682
700
  readonly id: "forbid-component-props";
@@ -4817,6 +4835,24 @@ declare const REACT_DOCTOR_RULES: readonly [{
4817
4835
  readonly recommendation?: string;
4818
4836
  readonly create: (context: RuleContext) => RuleVisitors;
4819
4837
  };
4838
+ }, {
4839
+ readonly key: "react-doctor/rn-detox-missing-await";
4840
+ readonly id: "rn-detox-missing-await";
4841
+ readonly source: "react-doctor";
4842
+ readonly originallyExternal: false;
4843
+ readonly rule: {
4844
+ readonly framework: "react-native";
4845
+ readonly category: "Bugs";
4846
+ readonly tags: readonly string[];
4847
+ readonly id: string;
4848
+ readonly title?: string;
4849
+ readonly severity: RuleSeverity;
4850
+ readonly requires?: ReadonlyArray<string>;
4851
+ readonly disabledBy?: ReadonlyArray<string>;
4852
+ readonly defaultEnabled?: boolean;
4853
+ readonly recommendation?: string;
4854
+ readonly create: (context: RuleContext) => RuleVisitors;
4855
+ };
4820
4856
  }, {
4821
4857
  readonly key: "react-doctor/rn-list-callback-per-row";
4822
4858
  readonly id: "rn-list-callback-per-row";
@@ -4889,6 +4925,24 @@ declare const REACT_DOCTOR_RULES: readonly [{
4889
4925
  readonly recommendation?: string;
4890
4926
  readonly create: (context: RuleContext) => RuleVisitors;
4891
4927
  };
4928
+ }, {
4929
+ readonly key: "react-doctor/rn-no-deep-imports";
4930
+ readonly id: "rn-no-deep-imports";
4931
+ readonly source: "react-doctor";
4932
+ readonly originallyExternal: false;
4933
+ readonly rule: {
4934
+ readonly framework: "react-native";
4935
+ readonly category: "Bugs";
4936
+ readonly tags: readonly string[];
4937
+ readonly id: string;
4938
+ readonly title?: string;
4939
+ readonly severity: RuleSeverity;
4940
+ readonly requires?: ReadonlyArray<string>;
4941
+ readonly disabledBy?: ReadonlyArray<string>;
4942
+ readonly defaultEnabled?: boolean;
4943
+ readonly recommendation?: string;
4944
+ readonly create: (context: RuleContext) => RuleVisitors;
4945
+ };
4892
4946
  }, {
4893
4947
  readonly key: "react-doctor/rn-no-deprecated-modules";
4894
4948
  readonly id: "rn-no-deprecated-modules";
@@ -4943,6 +4997,24 @@ declare const REACT_DOCTOR_RULES: readonly [{
4943
4997
  readonly recommendation?: string;
4944
4998
  readonly create: (context: RuleContext) => RuleVisitors;
4945
4999
  };
5000
+ }, {
5001
+ readonly key: "react-doctor/rn-no-image-children";
5002
+ readonly id: "rn-no-image-children";
5003
+ readonly source: "react-doctor";
5004
+ readonly originallyExternal: false;
5005
+ readonly rule: {
5006
+ readonly framework: "react-native";
5007
+ readonly category: "Bugs";
5008
+ readonly tags: readonly string[];
5009
+ readonly id: string;
5010
+ readonly title?: string;
5011
+ readonly severity: RuleSeverity;
5012
+ readonly requires?: ReadonlyArray<string>;
5013
+ readonly disabledBy?: ReadonlyArray<string>;
5014
+ readonly defaultEnabled?: boolean;
5015
+ readonly recommendation?: string;
5016
+ readonly create: (context: RuleContext) => RuleVisitors;
5017
+ };
4946
5018
  }, {
4947
5019
  readonly key: "react-doctor/rn-no-inline-flatlist-renderitem";
4948
5020
  readonly id: "rn-no-inline-flatlist-renderitem";
@@ -5033,6 +5105,24 @@ declare const REACT_DOCTOR_RULES: readonly [{
5033
5105
  readonly recommendation?: string;
5034
5106
  readonly create: (context: RuleContext) => RuleVisitors;
5035
5107
  };
5108
+ }, {
5109
+ readonly key: "react-doctor/rn-no-panresponder";
5110
+ readonly id: "rn-no-panresponder";
5111
+ readonly source: "react-doctor";
5112
+ readonly originallyExternal: false;
5113
+ readonly rule: {
5114
+ readonly framework: "react-native";
5115
+ readonly category: "Bugs";
5116
+ readonly tags: readonly string[];
5117
+ readonly id: string;
5118
+ readonly title?: string;
5119
+ readonly severity: RuleSeverity;
5120
+ readonly requires?: ReadonlyArray<string>;
5121
+ readonly disabledBy?: ReadonlyArray<string>;
5122
+ readonly defaultEnabled?: boolean;
5123
+ readonly recommendation?: string;
5124
+ readonly create: (context: RuleContext) => RuleVisitors;
5125
+ };
5036
5126
  }, {
5037
5127
  readonly key: "react-doctor/rn-no-raw-text";
5038
5128
  readonly id: "rn-no-raw-text";
@@ -5105,6 +5195,24 @@ declare const REACT_DOCTOR_RULES: readonly [{
5105
5195
  readonly recommendation?: string;
5106
5196
  readonly create: (context: RuleContext) => RuleVisitors;
5107
5197
  };
5198
+ }, {
5199
+ readonly key: "react-doctor/rn-no-set-native-props";
5200
+ readonly id: "rn-no-set-native-props";
5201
+ readonly source: "react-doctor";
5202
+ readonly originallyExternal: false;
5203
+ readonly rule: {
5204
+ readonly framework: "react-native";
5205
+ readonly category: "Bugs";
5206
+ readonly tags: readonly string[];
5207
+ readonly id: string;
5208
+ readonly title?: string;
5209
+ readonly severity: RuleSeverity;
5210
+ readonly requires?: ReadonlyArray<string>;
5211
+ readonly disabledBy?: ReadonlyArray<string>;
5212
+ readonly defaultEnabled?: boolean;
5213
+ readonly recommendation?: string;
5214
+ readonly create: (context: RuleContext) => RuleVisitors;
5215
+ };
5108
5216
  }, {
5109
5217
  readonly key: "react-doctor/rn-no-single-element-style-array";
5110
5218
  readonly id: "rn-no-single-element-style-array";
@@ -6539,6 +6647,24 @@ declare const RULES: readonly [{
6539
6647
  readonly recommendation?: string;
6540
6648
  readonly create: (context: RuleContext) => RuleVisitors;
6541
6649
  };
6650
+ }, {
6651
+ readonly key: "react-doctor/expo-no-non-inlined-env";
6652
+ readonly id: "expo-no-non-inlined-env";
6653
+ readonly source: "react-doctor";
6654
+ readonly originallyExternal: false;
6655
+ readonly rule: {
6656
+ readonly framework: "react-native";
6657
+ readonly category: "Bugs";
6658
+ readonly tags: readonly string[];
6659
+ readonly id: string;
6660
+ readonly title?: string;
6661
+ readonly severity: RuleSeverity;
6662
+ readonly requires?: ReadonlyArray<string>;
6663
+ readonly disabledBy?: ReadonlyArray<string>;
6664
+ readonly defaultEnabled?: boolean;
6665
+ readonly recommendation?: string;
6666
+ readonly create: (context: RuleContext) => RuleVisitors;
6667
+ };
6542
6668
  }, {
6543
6669
  readonly key: "react-doctor/forbid-component-props";
6544
6670
  readonly id: "forbid-component-props";
@@ -10679,6 +10805,24 @@ declare const RULES: readonly [{
10679
10805
  readonly recommendation?: string;
10680
10806
  readonly create: (context: RuleContext) => RuleVisitors;
10681
10807
  };
10808
+ }, {
10809
+ readonly key: "react-doctor/rn-detox-missing-await";
10810
+ readonly id: "rn-detox-missing-await";
10811
+ readonly source: "react-doctor";
10812
+ readonly originallyExternal: false;
10813
+ readonly rule: {
10814
+ readonly framework: "react-native";
10815
+ readonly category: "Bugs";
10816
+ readonly tags: readonly string[];
10817
+ readonly id: string;
10818
+ readonly title?: string;
10819
+ readonly severity: RuleSeverity;
10820
+ readonly requires?: ReadonlyArray<string>;
10821
+ readonly disabledBy?: ReadonlyArray<string>;
10822
+ readonly defaultEnabled?: boolean;
10823
+ readonly recommendation?: string;
10824
+ readonly create: (context: RuleContext) => RuleVisitors;
10825
+ };
10682
10826
  }, {
10683
10827
  readonly key: "react-doctor/rn-list-callback-per-row";
10684
10828
  readonly id: "rn-list-callback-per-row";
@@ -10751,6 +10895,24 @@ declare const RULES: readonly [{
10751
10895
  readonly recommendation?: string;
10752
10896
  readonly create: (context: RuleContext) => RuleVisitors;
10753
10897
  };
10898
+ }, {
10899
+ readonly key: "react-doctor/rn-no-deep-imports";
10900
+ readonly id: "rn-no-deep-imports";
10901
+ readonly source: "react-doctor";
10902
+ readonly originallyExternal: false;
10903
+ readonly rule: {
10904
+ readonly framework: "react-native";
10905
+ readonly category: "Bugs";
10906
+ readonly tags: readonly string[];
10907
+ readonly id: string;
10908
+ readonly title?: string;
10909
+ readonly severity: RuleSeverity;
10910
+ readonly requires?: ReadonlyArray<string>;
10911
+ readonly disabledBy?: ReadonlyArray<string>;
10912
+ readonly defaultEnabled?: boolean;
10913
+ readonly recommendation?: string;
10914
+ readonly create: (context: RuleContext) => RuleVisitors;
10915
+ };
10754
10916
  }, {
10755
10917
  readonly key: "react-doctor/rn-no-deprecated-modules";
10756
10918
  readonly id: "rn-no-deprecated-modules";
@@ -10805,6 +10967,24 @@ declare const RULES: readonly [{
10805
10967
  readonly recommendation?: string;
10806
10968
  readonly create: (context: RuleContext) => RuleVisitors;
10807
10969
  };
10970
+ }, {
10971
+ readonly key: "react-doctor/rn-no-image-children";
10972
+ readonly id: "rn-no-image-children";
10973
+ readonly source: "react-doctor";
10974
+ readonly originallyExternal: false;
10975
+ readonly rule: {
10976
+ readonly framework: "react-native";
10977
+ readonly category: "Bugs";
10978
+ readonly tags: readonly string[];
10979
+ readonly id: string;
10980
+ readonly title?: string;
10981
+ readonly severity: RuleSeverity;
10982
+ readonly requires?: ReadonlyArray<string>;
10983
+ readonly disabledBy?: ReadonlyArray<string>;
10984
+ readonly defaultEnabled?: boolean;
10985
+ readonly recommendation?: string;
10986
+ readonly create: (context: RuleContext) => RuleVisitors;
10987
+ };
10808
10988
  }, {
10809
10989
  readonly key: "react-doctor/rn-no-inline-flatlist-renderitem";
10810
10990
  readonly id: "rn-no-inline-flatlist-renderitem";
@@ -10895,6 +11075,24 @@ declare const RULES: readonly [{
10895
11075
  readonly recommendation?: string;
10896
11076
  readonly create: (context: RuleContext) => RuleVisitors;
10897
11077
  };
11078
+ }, {
11079
+ readonly key: "react-doctor/rn-no-panresponder";
11080
+ readonly id: "rn-no-panresponder";
11081
+ readonly source: "react-doctor";
11082
+ readonly originallyExternal: false;
11083
+ readonly rule: {
11084
+ readonly framework: "react-native";
11085
+ readonly category: "Bugs";
11086
+ readonly tags: readonly string[];
11087
+ readonly id: string;
11088
+ readonly title?: string;
11089
+ readonly severity: RuleSeverity;
11090
+ readonly requires?: ReadonlyArray<string>;
11091
+ readonly disabledBy?: ReadonlyArray<string>;
11092
+ readonly defaultEnabled?: boolean;
11093
+ readonly recommendation?: string;
11094
+ readonly create: (context: RuleContext) => RuleVisitors;
11095
+ };
10898
11096
  }, {
10899
11097
  readonly key: "react-doctor/rn-no-raw-text";
10900
11098
  readonly id: "rn-no-raw-text";
@@ -10967,6 +11165,24 @@ declare const RULES: readonly [{
10967
11165
  readonly recommendation?: string;
10968
11166
  readonly create: (context: RuleContext) => RuleVisitors;
10969
11167
  };
11168
+ }, {
11169
+ readonly key: "react-doctor/rn-no-set-native-props";
11170
+ readonly id: "rn-no-set-native-props";
11171
+ readonly source: "react-doctor";
11172
+ readonly originallyExternal: false;
11173
+ readonly rule: {
11174
+ readonly framework: "react-native";
11175
+ readonly category: "Bugs";
11176
+ readonly tags: readonly string[];
11177
+ readonly id: string;
11178
+ readonly title?: string;
11179
+ readonly severity: RuleSeverity;
11180
+ readonly requires?: ReadonlyArray<string>;
11181
+ readonly disabledBy?: ReadonlyArray<string>;
11182
+ readonly defaultEnabled?: boolean;
11183
+ readonly recommendation?: string;
11184
+ readonly create: (context: RuleContext) => RuleVisitors;
11185
+ };
10970
11186
  }, {
10971
11187
  readonly key: "react-doctor/rn-no-single-element-style-array";
10972
11188
  readonly id: "rn-no-single-element-style-array";
package/dist/index.js CHANGED
@@ -6177,6 +6177,42 @@ If the missing value is recreated every render, move it inside the hook or stabi
6177
6177
  }
6178
6178
  });
6179
6179
  //#endregion
6180
+ //#region src/plugin/rules/react-native/expo-no-non-inlined-env.ts
6181
+ const EMPTY_VISITORS$3 = {};
6182
+ const NODE_OR_BUILD_FILE = /(\.config\.[cm]?[jt]sx?$)|((^|\/)(scripts|tools|tooling|cli|bin)\/)|(\+(api|html)\.[cm]?[jt]sx?$)|(\.server\.[cm]?[jt]sx?$)|(\.(test|spec)\.[cm]?[jt]sx?$)|((^|\/)__tests__\/)|(\.e2e\.[cm]?[jt]sx?$)/;
6183
+ const isNonExpoPublicLiteralKey = (key) => isNodeOfType(key, "Literal") && typeof key.value === "string" && !key.value.startsWith("EXPO_PUBLIC_");
6184
+ const isProcessEnv = (node) => isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.object, "Identifier") && node.object.name === "process" && isNodeOfType(node.property, "Identifier") && node.property.name === "env";
6185
+ const expoNoNonInlinedEnv = defineRule({
6186
+ id: "expo-no-non-inlined-env",
6187
+ title: "Non-inlinable process.env access (Expo)",
6188
+ requires: ["expo"],
6189
+ severity: "warn",
6190
+ recommendation: "Read env vars with static dotted access (`process.env.EXPO_PUBLIC_NAME`). Computed access and destructuring aren't inlined by babel-preset-expo and resolve to `undefined` at runtime.",
6191
+ create: (context) => {
6192
+ const filename = normalizeFilename$1(context.filename ?? "");
6193
+ if (filename && NODE_OR_BUILD_FILE.test(filename)) return EMPTY_VISITORS$3;
6194
+ return {
6195
+ MemberExpression(node) {
6196
+ if (!node.computed) return;
6197
+ if (!isProcessEnv(node.object)) return;
6198
+ if (isNonExpoPublicLiteralKey(node.property)) return;
6199
+ context.report({
6200
+ node,
6201
+ message: "Computed `process.env[...]` access isn't inlined by babel-preset-expo and is `undefined` at runtime. Use static `process.env.EXPO_PUBLIC_NAME`."
6202
+ });
6203
+ },
6204
+ VariableDeclarator(node) {
6205
+ if (!isNodeOfType(node.id, "ObjectPattern")) return;
6206
+ if (!isProcessEnv(node.init)) return;
6207
+ context.report({
6208
+ node,
6209
+ message: "Destructuring `process.env` isn't inlined by babel-preset-expo, so the values are `undefined` at runtime. Read each var via `process.env.EXPO_PUBLIC_NAME`."
6210
+ });
6211
+ }
6212
+ };
6213
+ }
6214
+ });
6215
+ //#endregion
6180
6216
  //#region src/plugin/utils/compile-glob.ts
6181
6217
  /**
6182
6218
  * Compiles a simple glob pattern (only `*` as a wildcard) into an
@@ -26867,6 +26903,99 @@ const rnBottomSheetPreferNative = defineRule({
26867
26903
  } })
26868
26904
  });
26869
26905
  //#endregion
26906
+ //#region src/plugin/rules/react-native/rn-detox-missing-await.ts
26907
+ const EMPTY_VISITORS$2 = {};
26908
+ const DETOX_TEST_FILE = /(\.e2e\.[cm]?[jt]sx?$)|((^|\/)e2e\/)/;
26909
+ const DETOX_ELEMENT_ACTIONS = new Set([
26910
+ "tap",
26911
+ "multiTap",
26912
+ "longPress",
26913
+ "longPressAndDrag",
26914
+ "swipe",
26915
+ "scroll",
26916
+ "scrollTo",
26917
+ "scrollToIndex",
26918
+ "scrollToElement",
26919
+ "typeText",
26920
+ "replaceText",
26921
+ "clearText",
26922
+ "tapReturnKey",
26923
+ "tapBackspaceKey",
26924
+ "pinch",
26925
+ "setColumnToValue",
26926
+ "setDatePickerDate",
26927
+ "performAccessibilityAction",
26928
+ "adjustSliderToPosition"
26929
+ ]);
26930
+ const PROMISE_SETTLE_METHODS = new Set([
26931
+ "then",
26932
+ "catch",
26933
+ "finally"
26934
+ ]);
26935
+ const findChainRoot = (node) => {
26936
+ if (isNodeOfType(node, "CallExpression")) {
26937
+ if (isNodeOfType(node.callee, "Identifier")) return {
26938
+ calleeName: node.callee.name,
26939
+ rootCall: node
26940
+ };
26941
+ if (isNodeOfType(node.callee, "MemberExpression")) return findChainRoot(node.callee.object);
26942
+ return null;
26943
+ }
26944
+ if (isNodeOfType(node, "MemberExpression")) return findChainRoot(node.object);
26945
+ return null;
26946
+ };
26947
+ const isDetoxExpectSubject = (rootCall) => {
26948
+ const firstArgument = rootCall.arguments?.[0];
26949
+ if (!firstArgument || !isNodeOfType(firstArgument, "CallExpression")) return false;
26950
+ if (!isNodeOfType(firstArgument.callee, "Identifier")) return false;
26951
+ return firstArgument.callee.name === "element" || firstArgument.callee.name === "web";
26952
+ };
26953
+ const getTerminalMethodName = (callExpression) => {
26954
+ const callee = callExpression.callee;
26955
+ if (!isNodeOfType(callee, "MemberExpression")) return null;
26956
+ if (callee.computed || !isNodeOfType(callee.property, "Identifier")) return null;
26957
+ return callee.property.name;
26958
+ };
26959
+ const rnDetoxMissingAwait = defineRule({
26960
+ id: "rn-detox-missing-await",
26961
+ title: "Un-awaited Detox action",
26962
+ requires: ["react-native"],
26963
+ severity: "warn",
26964
+ recommendation: "Prepend `await` to Detox actions, `waitFor(...)` chains, and `expect(element(...))` assertions. They return promises tied to Detox's synchronization, so a missing await runs steps out of order.",
26965
+ create: (context) => {
26966
+ const filename = normalizeFilename$1(context.filename ?? "");
26967
+ if (!filename || !DETOX_TEST_FILE.test(filename)) return EMPTY_VISITORS$2;
26968
+ return { ExpressionStatement(node) {
26969
+ const expression = node.expression;
26970
+ if (!isNodeOfType(expression, "CallExpression")) return;
26971
+ const terminalMethod = getTerminalMethodName(expression);
26972
+ if (terminalMethod === null) return;
26973
+ if (PROMISE_SETTLE_METHODS.has(terminalMethod)) return;
26974
+ const root = findChainRoot(expression);
26975
+ if (!root) return;
26976
+ if (root.calleeName === "element") {
26977
+ if (!DETOX_ELEMENT_ACTIONS.has(terminalMethod)) return;
26978
+ context.report({
26979
+ node,
26980
+ message: `This Detox action (\`${terminalMethod}\`) isn't awaited, so it runs out of order and can race. Prepend \`await\`.`
26981
+ });
26982
+ return;
26983
+ }
26984
+ if (root.calleeName === "waitFor") {
26985
+ context.report({
26986
+ node,
26987
+ message: "This Detox `waitFor(...)` chain isn't awaited. Prepend `await`."
26988
+ });
26989
+ return;
26990
+ }
26991
+ if (root.calleeName === "expect" && isDetoxExpectSubject(root.rootCall)) context.report({
26992
+ node,
26993
+ message: "This Detox `expect(element(...))` assertion isn't awaited. Prepend `await`."
26994
+ });
26995
+ } };
26996
+ }
26997
+ });
26998
+ //#endregion
26870
26999
  //#region src/plugin/constants/react-native.ts
26871
27000
  const REACT_NATIVE_TEXT_COMPONENTS = new Set([
26872
27001
  "Text",
@@ -27139,6 +27268,96 @@ const rnListRecyclableWithoutTypes = defineRule({
27139
27268
  } })
27140
27269
  });
27141
27270
  //#endregion
27271
+ //#region src/plugin/rules/react-native/rn-no-deep-imports.ts
27272
+ const DEEP_IMPORT_PREFIX = "react-native/Libraries/";
27273
+ const NEW_APP_SCREEN_PATH = "react-native/Libraries/NewAppScreen";
27274
+ const PUBLIC_RN_ROOT_EXPORTS = new Set([
27275
+ "View",
27276
+ "Text",
27277
+ "Image",
27278
+ "ImageBackground",
27279
+ "ScrollView",
27280
+ "FlatList",
27281
+ "SectionList",
27282
+ "VirtualizedList",
27283
+ "TextInput",
27284
+ "Pressable",
27285
+ "TouchableOpacity",
27286
+ "TouchableHighlight",
27287
+ "TouchableWithoutFeedback",
27288
+ "TouchableNativeFeedback",
27289
+ "Button",
27290
+ "Switch",
27291
+ "Modal",
27292
+ "ActivityIndicator",
27293
+ "RefreshControl",
27294
+ "KeyboardAvoidingView",
27295
+ "StyleSheet",
27296
+ "Alert",
27297
+ "Animated",
27298
+ "Platform",
27299
+ "Dimensions",
27300
+ "AppRegistry",
27301
+ "AppState",
27302
+ "Linking",
27303
+ "Appearance",
27304
+ "Keyboard",
27305
+ "StatusBar",
27306
+ "PixelRatio",
27307
+ "PanResponder",
27308
+ "BackHandler",
27309
+ "InteractionManager"
27310
+ ]);
27311
+ const lastPathSegment = (source) => {
27312
+ const segments = source.split("/");
27313
+ return segments[segments.length - 1] ?? "";
27314
+ };
27315
+ const classifyDeepImport = (source) => {
27316
+ if (typeof source !== "string") return null;
27317
+ if (!source.startsWith(DEEP_IMPORT_PREFIX)) return null;
27318
+ if (source === NEW_APP_SCREEN_PATH || source.startsWith(`${NEW_APP_SCREEN_PATH}/`)) return { message: "`react-native/Libraries/NewAppScreen` was moved out of core in React Native 0.80; import from `@react-native/new-app-screen` instead." };
27319
+ const exportName = lastPathSegment(source);
27320
+ if (PUBLIC_RN_ROOT_EXPORTS.has(exportName)) return { message: `Deep import from "${source}" is a deprecated React Native internal subpath (RFC 0894) and breaks on upgrade. Import from "react-native" instead.` };
27321
+ return null;
27322
+ };
27323
+ const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
27324
+ if (!specifiers || specifiers.length === 0) return false;
27325
+ return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
27326
+ };
27327
+ const rnNoDeepImports = defineRule({
27328
+ id: "rn-no-deep-imports",
27329
+ title: "Deep import into react-native internals",
27330
+ requires: ["react-native"],
27331
+ severity: "warn",
27332
+ recommendation: "Import the symbol from `react-native` (the package root) instead of the deprecated `react-native/Libraries/...` subpath, which RFC 0894 removes on upgrade.",
27333
+ create: (context) => {
27334
+ const reportFinding = (node, source) => {
27335
+ const finding = classifyDeepImport(source);
27336
+ if (finding) context.report({
27337
+ node,
27338
+ message: finding.message
27339
+ });
27340
+ };
27341
+ return {
27342
+ ImportDeclaration(node) {
27343
+ if (node.importKind === "type") return;
27344
+ if (isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind")) return;
27345
+ reportFinding(node, node.source?.value);
27346
+ },
27347
+ ExportNamedDeclaration(node) {
27348
+ if (node.exportKind === "type") return;
27349
+ if (!node.source) return;
27350
+ if (isEverySpecifierInlineType(node.specifiers, "ExportSpecifier", "exportKind")) return;
27351
+ reportFinding(node, node.source.value);
27352
+ },
27353
+ ExportAllDeclaration(node) {
27354
+ if (node.exportKind === "type") return;
27355
+ reportFinding(node, node.source?.value);
27356
+ }
27357
+ };
27358
+ }
27359
+ });
27360
+ //#endregion
27142
27361
  //#region src/plugin/rules/react-native/rn-no-deprecated-modules.ts
27143
27362
  const rnNoDeprecatedModules = defineRule({
27144
27363
  id: "rn-no-deprecated-modules",
@@ -27278,6 +27497,39 @@ const rnNoFalsyAndRender = defineRule({
27278
27497
  }
27279
27498
  });
27280
27499
  //#endregion
27500
+ //#region src/plugin/rules/react-native/rn-no-image-children.ts
27501
+ const isMeaningfulImageChild = (child) => {
27502
+ if (isNodeOfType(child, "JSXElement") || isNodeOfType(child, "JSXFragment")) return true;
27503
+ if (isNodeOfType(child, "JSXText")) return (child.value ?? "").trim().length > 0;
27504
+ if (isNodeOfType(child, "JSXExpressionContainer")) {
27505
+ const expression = child.expression;
27506
+ if (isNodeOfType(expression, "JSXEmptyExpression")) return false;
27507
+ if (isNodeOfType(expression, "Literal") && (expression.value === null || expression.value === false)) return false;
27508
+ if (isNodeOfType(expression, "Identifier") && expression.name === "undefined") return false;
27509
+ return true;
27510
+ }
27511
+ return false;
27512
+ };
27513
+ const rnNoImageChildren = defineRule({
27514
+ id: "rn-no-image-children",
27515
+ title: "Children inside react-native <Image>",
27516
+ requires: ["react-native"],
27517
+ severity: "error",
27518
+ recommendation: "React Native's `<Image>` can't render children. Use `<ImageBackground>` (same `source`/`style` props) to layer content over an image.",
27519
+ create: (context) => ({ JSXElement(node) {
27520
+ const openingElement = node.openingElement;
27521
+ if (!openingElement) return;
27522
+ const localName = resolveJsxElementName(openingElement);
27523
+ if (!localName) return;
27524
+ if (getImportedNameFromModule(openingElement, localName, "react-native") !== "Image") return;
27525
+ if (!(node.children ?? []).some(isMeaningfulImageChild)) return;
27526
+ context.report({
27527
+ node: openingElement,
27528
+ message: "React Native's <Image> does not render children, so this content silently disappears. Use <ImageBackground> to layer content over an image."
27529
+ });
27530
+ } })
27531
+ });
27532
+ //#endregion
27281
27533
  //#region src/plugin/rules/react-native/rn-no-inline-flatlist-renderitem.ts
27282
27534
  const rnNoInlineFlatlistRenderitem = defineRule({
27283
27535
  id: "rn-no-inline-flatlist-renderitem",
@@ -27444,6 +27696,29 @@ const rnNoNonNativeNavigator = defineRule({
27444
27696
  } })
27445
27697
  });
27446
27698
  //#endregion
27699
+ //#region src/plugin/rules/react-native/rn-no-panresponder.ts
27700
+ const rnNoPanresponder = defineRule({
27701
+ id: "rn-no-panresponder",
27702
+ title: "PanResponder over react-native-gesture-handler",
27703
+ tags: ["test-noise"],
27704
+ requires: ["react-native"],
27705
+ severity: "warn",
27706
+ recommendation: "Use `react-native-gesture-handler` (`Gesture.Pan()`) instead of `PanResponder`. It runs gestures on the native UI thread, so they stay smooth even when the JS thread is busy.",
27707
+ create: (context) => ({ ImportDeclaration(node) {
27708
+ if (node.source?.value !== "react-native") return;
27709
+ if (node.importKind === "type") return;
27710
+ for (const specifier of node.specifiers ?? []) {
27711
+ if (!isNodeOfType(specifier, "ImportSpecifier")) continue;
27712
+ if (specifier.importKind === "type") continue;
27713
+ if (getImportedName$1(specifier) !== "PanResponder") continue;
27714
+ context.report({
27715
+ node: specifier,
27716
+ message: "PanResponder runs gesture handling on the JS thread, which stutters under load. Use react-native-gesture-handler (`Gesture.Pan()`) so gestures run on the native UI thread."
27717
+ });
27718
+ }
27719
+ } })
27720
+ });
27721
+ //#endregion
27447
27722
  //#region src/plugin/utils/is-inside-platform-os-web-branch.ts
27448
27723
  const unwrapAccessor = (node) => {
27449
27724
  let current = node;
@@ -27820,6 +28095,26 @@ const rnNoScrollviewMappedList = defineRule({
27820
28095
  } })
27821
28096
  });
27822
28097
  //#endregion
28098
+ //#region src/plugin/rules/react-native/rn-no-set-native-props.ts
28099
+ const isStaticMemberNamed = (node, name) => isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.property, "Identifier") && node.property.name === name;
28100
+ const rnNoSetNativeProps = defineRule({
28101
+ id: "rn-no-set-native-props",
28102
+ title: "Imperative setNativeProps (no-op under Fabric)",
28103
+ requires: ["react-native"],
28104
+ severity: "warn",
28105
+ recommendation: "Drive the prop through React state, an `Animated.Value` (with `useNativeDriver: true`), or a Reanimated shared value. `setNativeProps` is a silent no-op under the New Architecture.",
28106
+ create: (context) => ({ CallExpression(node) {
28107
+ const callee = node.callee;
28108
+ if (!isStaticMemberNamed(callee, "setNativeProps")) return;
28109
+ if (!isNodeOfType(callee, "MemberExpression")) return;
28110
+ if (!isStaticMemberNamed(callee.object, "current")) return;
28111
+ context.report({
28112
+ node,
28113
+ message: "`setNativeProps` is a silent no-op under the New Architecture (Fabric), so this imperative update won't change the view. Drive the prop via state, an Animated.Value, or a Reanimated shared value."
28114
+ });
28115
+ } })
28116
+ });
28117
+ //#endregion
27823
28118
  //#region src/plugin/rules/react-native/rn-no-single-element-style-array.ts
27824
28119
  const rnNoSingleElementStyleArray = defineRule({
27825
28120
  id: "rn-no-single-element-style-array",
@@ -34410,6 +34705,18 @@ const reactDoctorRules = [
34410
34705
  category: "Bugs"
34411
34706
  }
34412
34707
  },
34708
+ {
34709
+ key: "react-doctor/expo-no-non-inlined-env",
34710
+ id: "expo-no-non-inlined-env",
34711
+ source: "react-doctor",
34712
+ originallyExternal: false,
34713
+ rule: {
34714
+ ...expoNoNonInlinedEnv,
34715
+ framework: "react-native",
34716
+ category: "Bugs",
34717
+ tags: [...new Set(["react-native", ...expoNoNonInlinedEnv.tags ?? []])]
34718
+ }
34719
+ },
34413
34720
  {
34414
34721
  key: "react-doctor/forbid-component-props",
34415
34722
  id: "forbid-component-props",
@@ -36943,6 +37250,18 @@ const reactDoctorRules = [
36943
37250
  tags: [...new Set(["react-native", ...rnBottomSheetPreferNative.tags ?? []])]
36944
37251
  }
36945
37252
  },
37253
+ {
37254
+ key: "react-doctor/rn-detox-missing-await",
37255
+ id: "rn-detox-missing-await",
37256
+ source: "react-doctor",
37257
+ originallyExternal: false,
37258
+ rule: {
37259
+ ...rnDetoxMissingAwait,
37260
+ framework: "react-native",
37261
+ category: "Bugs",
37262
+ tags: [...new Set(["react-native", ...rnDetoxMissingAwait.tags ?? []])]
37263
+ }
37264
+ },
36946
37265
  {
36947
37266
  key: "react-doctor/rn-list-callback-per-row",
36948
37267
  id: "rn-list-callback-per-row",
@@ -36991,6 +37310,18 @@ const reactDoctorRules = [
36991
37310
  tags: [...new Set(["react-native", ...rnListRecyclableWithoutTypes.tags ?? []])]
36992
37311
  }
36993
37312
  },
37313
+ {
37314
+ key: "react-doctor/rn-no-deep-imports",
37315
+ id: "rn-no-deep-imports",
37316
+ source: "react-doctor",
37317
+ originallyExternal: false,
37318
+ rule: {
37319
+ ...rnNoDeepImports,
37320
+ framework: "react-native",
37321
+ category: "Bugs",
37322
+ tags: [...new Set(["react-native", ...rnNoDeepImports.tags ?? []])]
37323
+ }
37324
+ },
36994
37325
  {
36995
37326
  key: "react-doctor/rn-no-deprecated-modules",
36996
37327
  id: "rn-no-deprecated-modules",
@@ -37027,6 +37358,18 @@ const reactDoctorRules = [
37027
37358
  tags: [...new Set(["react-native", ...rnNoFalsyAndRender.tags ?? []])]
37028
37359
  }
37029
37360
  },
37361
+ {
37362
+ key: "react-doctor/rn-no-image-children",
37363
+ id: "rn-no-image-children",
37364
+ source: "react-doctor",
37365
+ originallyExternal: false,
37366
+ rule: {
37367
+ ...rnNoImageChildren,
37368
+ framework: "react-native",
37369
+ category: "Bugs",
37370
+ tags: [...new Set(["react-native", ...rnNoImageChildren.tags ?? []])]
37371
+ }
37372
+ },
37030
37373
  {
37031
37374
  key: "react-doctor/rn-no-inline-flatlist-renderitem",
37032
37375
  id: "rn-no-inline-flatlist-renderitem",
@@ -37087,6 +37430,18 @@ const reactDoctorRules = [
37087
37430
  tags: [...new Set(["react-native", ...rnNoNonNativeNavigator.tags ?? []])]
37088
37431
  }
37089
37432
  },
37433
+ {
37434
+ key: "react-doctor/rn-no-panresponder",
37435
+ id: "rn-no-panresponder",
37436
+ source: "react-doctor",
37437
+ originallyExternal: false,
37438
+ rule: {
37439
+ ...rnNoPanresponder,
37440
+ framework: "react-native",
37441
+ category: "Bugs",
37442
+ tags: [...new Set(["react-native", ...rnNoPanresponder.tags ?? []])]
37443
+ }
37444
+ },
37090
37445
  {
37091
37446
  key: "react-doctor/rn-no-raw-text",
37092
37447
  id: "rn-no-raw-text",
@@ -37135,6 +37490,18 @@ const reactDoctorRules = [
37135
37490
  tags: [...new Set(["react-native", ...rnNoScrollviewMappedList.tags ?? []])]
37136
37491
  }
37137
37492
  },
37493
+ {
37494
+ key: "react-doctor/rn-no-set-native-props",
37495
+ id: "rn-no-set-native-props",
37496
+ source: "react-doctor",
37497
+ originallyExternal: false,
37498
+ rule: {
37499
+ ...rnNoSetNativeProps,
37500
+ framework: "react-native",
37501
+ category: "Bugs",
37502
+ tags: [...new Set(["react-native", ...rnNoSetNativeProps.tags ?? []])]
37503
+ }
37504
+ },
37138
37505
  {
37139
37506
  key: "react-doctor/rn-no-single-element-style-array",
37140
37507
  id: "rn-no-single-element-style-array",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.2.14-dev.75c1f99",
3
+ "version": "0.2.14-dev.8921575",
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",