Skip to content

Commit bd1e74f

Browse files
feat(lint): add react native deep import rule (#10023)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 68fb8d4 commit bd1e74f

15 files changed

Lines changed: 535 additions & 0 deletions

File tree

.changeset/fair-goats-scream.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Added a new nursery rule [`noReactNativeDeepImports`](https://biomejs.dev/linter/rules/no-react-native-deep-imports/) that disallows deep imports from the `react-native` package. Internal paths like `react-native/Libraries/...` are not part of the public API and may change between versions.
6+
7+
For example, the following code triggers the rule:
8+
9+
```js
10+
import View from "react-native/Libraries/Components/View/View";
11+
```

crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_configuration/src/analyzer/linter/rules.rs

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_configuration/src/generated/domain_selector.rs

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_configuration/src/generated/linter_options_check.rs

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_diagnostics_categories/src/categories.rs

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use biome_analyze::{
2+
Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule,
3+
};
4+
use biome_console::markup;
5+
use biome_diagnostics::Severity;
6+
use biome_js_syntax::AnyJsImportLike;
7+
use biome_rowan::TextRange;
8+
use biome_rule_options::no_react_native_deep_imports::NoReactNativeDeepImportsOptions;
9+
10+
declare_lint_rule! {
11+
/// Disallow deep imports from the `react-native` package.
12+
///
13+
/// Deep imports reach into React Native's internal file structure,
14+
/// which is not part of the public API. Internal paths can change
15+
/// between versions without warning, breaking code that depends on them.
16+
///
17+
/// ## Examples
18+
///
19+
/// ### Invalid
20+
///
21+
/// ```js,expect_diagnostic
22+
/// import View from "react-native/Libraries/Components/View/View";
23+
/// ```
24+
///
25+
/// ```js,expect_diagnostic
26+
/// const Platform = require("react-native/Libraries/Utilities/Platform");
27+
/// ```
28+
///
29+
/// ```js,expect_diagnostic
30+
/// const View = require("react-native/Libraries/Components/View/View");
31+
/// ```
32+
///
33+
/// ```js,expect_diagnostic
34+
/// import("react-native/Libraries/Utilities/Platform");
35+
/// ```
36+
///
37+
/// ### Valid
38+
///
39+
/// ```js
40+
/// import { View } from "react-native";
41+
/// ```
42+
///
43+
/// ```js
44+
/// const { Platform } = require("react-native");
45+
/// ```
46+
///
47+
pub NoReactNativeDeepImports {
48+
version: "next",
49+
name: "noReactNativeDeepImports",
50+
language: "js",
51+
sources: &[RuleSource::EslintReactNative("no-deep-imports").same()],
52+
domains: &[RuleDomain::ReactNative],
53+
recommended: true,
54+
severity: Severity::Error,
55+
}
56+
}
57+
58+
impl Rule for NoReactNativeDeepImports {
59+
type Query = Ast<AnyJsImportLike>;
60+
type State = TextRange;
61+
type Signals = Option<Self::State>;
62+
type Options = NoReactNativeDeepImportsOptions;
63+
64+
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
65+
let node = ctx.query();
66+
let module_name_token = node.module_name_token()?;
67+
let import_path = node.inner_string_text()?;
68+
let import_path = import_path.text();
69+
70+
if let Some(rest) = import_path.strip_prefix("react-native/")
71+
&& !rest.is_empty()
72+
{
73+
return Some(module_name_token.text_trimmed_range());
74+
}
75+
76+
None
77+
}
78+
79+
fn diagnostic(_ctx: &RuleContext<Self>, range: &Self::State) -> Option<RuleDiagnostic> {
80+
Some(
81+
RuleDiagnostic::new(
82+
rule_category!(),
83+
range,
84+
markup! {
85+
"Deep imports from "<Emphasis>"react-native"</Emphasis>" are not allowed."
86+
},
87+
)
88+
.note(markup! {
89+
"React Native's internal file structure is not part of the public API and may change between versions without warning."
90+
})
91+
.note(markup! {
92+
"Import from the top-level "<Emphasis>"react-native"</Emphasis>" entry point instead."
93+
}),
94+
)
95+
}
96+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/* should generate diagnostics */
2+
3+
// ESM default import from internal path
4+
import View from "react-native/Libraries/Components/View/View";
5+
6+
// ESM named import from internal path
7+
import { something } from "react-native/Libraries/Utilities/Platform";
8+
9+
// ESM type import from internal path
10+
import type { RootTag } from "react-native/Libraries/Types/RootTagTypes";
11+
12+
// CJS require from internal path
13+
const Platform = require("react-native/Libraries/Utilities/Platform");
14+
15+
// CJS destructured require from internal path
16+
const { View: V } = require("react-native/Libraries/Components/View/View");
17+
18+
// Dynamic import from internal path
19+
import("react-native/Libraries/Utilities/Platform");
20+
21+
// Deep import from src directory
22+
import Foo from "react-native/src/private/specs/modules/NativeAppearance";
23+
24+
// Side-effect import of internal path
25+
import "react-native/Libraries/Core/InitializeCore";
26+
27+
// Namespace import from internal path
28+
import * as Internals from "react-native/Libraries/Renderer/shims/ReactNativeViewConfigRegistry";
29+
30+
// Single-level deep import
31+
import something from "react-native/index";

0 commit comments

Comments
 (0)