Skip to content

Commit 79af7ca

Browse files
authored
feat: add Expo and React Native framework detection and enable RN-specific rules (#64)
React Native/Expo projects were detected as "unknown" framework, and the 8 existing rn-* lint rules were never enabled in the oxlint config. This change: - Adds "expo" and "react-native" as recognized Framework types - Detects expo/react-native packages in dependency detection - Conditionally enables all 8 rn-* rules when framework is Expo or React Native - Adds "React Native" category mapping and help text for all rn-* rules
1 parent 618350f commit 79af7ca

File tree

4 files changed

+43
-1
lines changed

4 files changed

+43
-1
lines changed

packages/react-doctor/src/oxlint-config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ const NEXTJS_RULES: Record<string, string> = {
2222
"react-doctor/nextjs-no-side-effect-in-get-handler": "error",
2323
};
2424

25+
const REACT_NATIVE_RULES: Record<string, string> = {
26+
"react-doctor/rn-no-raw-text": "error",
27+
"react-doctor/rn-no-deprecated-modules": "error",
28+
"react-doctor/rn-no-legacy-expo-packages": "warn",
29+
"react-doctor/rn-no-dimensions-get": "warn",
30+
"react-doctor/rn-no-inline-flatlist-renderitem": "warn",
31+
"react-doctor/rn-no-legacy-shadow-styles": "warn",
32+
"react-doctor/rn-prefer-reanimated": "warn",
33+
"react-doctor/rn-no-single-element-style-array": "warn",
34+
};
35+
2536
const REACT_COMPILER_RULES: Record<string, string> = {
2637
"react-hooks-js/set-state-in-render": "error",
2738
"react-hooks-js/immutability": "error",
@@ -147,5 +158,6 @@ export const createOxlintConfig = ({
147158

148159
"react-doctor/async-parallel": "warn",
149160
...(framework === "nextjs" ? NEXTJS_RULES : {}),
161+
...(framework === "expo" || framework === "react-native" ? REACT_NATIVE_RULES : {}),
150162
},
151163
});

packages/react-doctor/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export type Framework = "nextjs" | "vite" | "cra" | "remix" | "gatsby" | "unknown";
1+
export type Framework = "nextjs" | "vite" | "cra" | "remix" | "gatsby" | "expo" | "react-native" | "unknown";
22

33
export interface ProjectInfo {
44
rootDirectory: string;

packages/react-doctor/src/utils/discover-project.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ const FRAMEWORK_PACKAGES: Record<string, Framework> = {
5151
"react-scripts": "cra",
5252
"@remix-run/react": "remix",
5353
gatsby: "gatsby",
54+
expo: "expo",
55+
"react-native": "react-native",
5456
};
5557

5658
const FRAMEWORK_DISPLAY_NAMES: Record<Framework, string> = {
@@ -59,6 +61,8 @@ const FRAMEWORK_DISPLAY_NAMES: Record<Framework, string> = {
5961
cra: "Create React App",
6062
remix: "Remix",
6163
gatsby: "Gatsby",
64+
expo: "Expo",
65+
"react-native": "React Native",
6266
unknown: "React",
6367
};
6468

packages/react-doctor/src/utils/run-oxlint.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,15 @@ const RULE_CATEGORY_MAP: Record<string, string> = {
8787
"react-doctor/client-passive-event-listeners": "Performance",
8888

8989
"react-doctor/async-parallel": "Performance",
90+
91+
"react-doctor/rn-no-raw-text": "React Native",
92+
"react-doctor/rn-no-deprecated-modules": "React Native",
93+
"react-doctor/rn-no-legacy-expo-packages": "React Native",
94+
"react-doctor/rn-no-dimensions-get": "React Native",
95+
"react-doctor/rn-no-inline-flatlist-renderitem": "React Native",
96+
"react-doctor/rn-no-legacy-shadow-styles": "React Native",
97+
"react-doctor/rn-prefer-reanimated": "React Native",
98+
"react-doctor/rn-no-single-element-style-array": "React Native",
9099
};
91100

92101
const RULE_HELP_MAP: Record<string, string> = {
@@ -208,6 +217,23 @@ const RULE_HELP_MAP: Record<string, string> = {
208217

209218
"async-parallel":
210219
"Use `const [a, b] = await Promise.all([fetchA(), fetchB()])` to run independent operations concurrently",
220+
221+
"rn-no-raw-text":
222+
"Wrap text in a `<Text>` component: `<Text>{value}</Text>` — raw strings outside `<Text>` crash on React Native",
223+
"rn-no-deprecated-modules":
224+
"Import from the community package instead — deprecated modules were removed from the react-native core",
225+
"rn-no-legacy-expo-packages":
226+
"Migrate to the recommended replacement package — legacy Expo packages are no longer maintained",
227+
"rn-no-dimensions-get":
228+
"Use `const { width, height } = useWindowDimensions()` — it updates reactively on rotation and resize",
229+
"rn-no-inline-flatlist-renderitem":
230+
"Extract renderItem to a named function or wrap in useCallback to avoid re-creating on every render",
231+
"rn-no-legacy-shadow-styles":
232+
"Use `boxShadow` for cross-platform shadows on the new architecture instead of platform-specific shadow properties",
233+
"rn-prefer-reanimated":
234+
"Use `import Animated from 'react-native-reanimated'` — animations run on the UI thread instead of the JS thread",
235+
"rn-no-single-element-style-array":
236+
"Use `style={value}` instead of `style={[value]}` — single-element arrays add unnecessary allocation",
211237
};
212238

213239
const FILEPATH_WITH_LOCATION_PATTERN = /\S+\.\w+:\d+:\d+[\s\S]*$/;

0 commit comments

Comments
 (0)