Skip to content

Commit

Permalink
feat(lint/useExhaustiveDependencies): support Preact (#2105)
Browse files Browse the repository at this point in the history
  • Loading branch information
arendjr authored Apr 5, 2024
1 parent db5a1e9 commit 7b6439b
Show file tree
Hide file tree
Showing 13 changed files with 110 additions and 49 deletions.
8 changes: 3 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b

Contributed by @Conaclos

- Implement [#2043](https://github.com/biomejs/biome/issues/2043): The React rule [`useExhaustiveDependencies`](https://biomejs.dev/linter/rules/use-exhaustive-dependencies/) is now also compatible with Preact hooks imported from `preact/hooks` or `preact/compat`. Contributed by @arendjr

#### Enhancements

#### Bug fixes
Expand Down Expand Up @@ -272,7 +274,7 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b

- Support applying lint fixes when calling the `lintContent` method of the `Biome` class ([#1956](https://github.com/biomejs/biome/pull/1956)). Contributed by @mnahkies

### Linter
### Parser

#### Bug fixes

Expand Down Expand Up @@ -363,10 +365,6 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b

#### Bug fixes

- JavaScript lexer is now able to lex regular expression literals with escaped non-ascii chars ([#1941](https://github.com/biomejs/biome/issues/1941)).

Contributed by @Sec-ant

## 1.6.0 (2024-03-08)

### Analyzer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl Rule for NoRenderReturnValue {
"Do not depend on the value returned by the function "<Emphasis>"ReactDOM.render()"</Emphasis>"."
},
).note(markup! {
"The returned value is legacy and future versions of react might return that value asynchronously."
"The returned value is legacy and future versions of React might return that value asynchronously."
"
Check the "<Hyperlink href="https://facebook.github.io/react/docs/react-dom.html#render">"React documentation"</Hyperlink>" for more information."

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ declare_rule! {
///
/// This rule is a port of the rule [react-hooks/exhaustive-deps](https://legacy.reactjs.org/docs/hooks-rules.html#eslint-plugin), and it's meant to target projects that uses React.
///
/// If your project _doesn't_ use React, **you shouldn't use this rule**.
/// If your project _doesn't_ use React (or Preact), **you shouldn't use this rule**.
///
/// The rule will inspect the following **known** hooks:
///
Expand Down Expand Up @@ -213,6 +213,11 @@ declare_rule! {
/// const doAction = useCallback(() => dispatch(someAction()), []);
/// ```
///
/// ## Preact support
///
/// This rule recognizes rules imported from `preact/compat` and
/// `preact/hooks` and applies the same rules as for React hooks.
///
pub UseExhaustiveDependencies {
version: "1.0.0",
name: "useExhaustiveDependencies",
Expand Down
18 changes: 12 additions & 6 deletions crates/biome_js_analyze/src/react.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,10 @@ pub enum ReactLibrary {
}

impl ReactLibrary {
pub const fn import_name(self) -> &'static str {
pub const fn import_names(self) -> &'static [&'static str] {
match self {
ReactLibrary::React => "react",
ReactLibrary::ReactDOM => "react-dom",
ReactLibrary::React => &["react", "preact/compat", "preact/hooks"],
ReactLibrary::ReactDOM => &["react-dom"],
}
}

Expand Down Expand Up @@ -179,6 +179,9 @@ const VALID_REACT_API: [&str; 29] = [
/// The function has accepts a `api_name` to check against
///
/// [`React` API]: https://reactjs.org/docs/react-api.html
///
/// This also returns `true` for libraries that return React-compatible APIs,
/// such as Preact.
pub(crate) fn is_react_call_api(
expr: &AnyJsExpression,
model: &SemanticModel,
Expand Down Expand Up @@ -269,7 +272,7 @@ fn is_react_export(binding: &Binding, lib: ReactLibrary) -> bool {
.syntax()
.ancestors()
.find_map(|ancestor| JsImport::cast(ancestor)?.source_text().ok())
.is_some_and(|source| source.text() == lib.import_name())
.is_some_and(|source| lib.import_names().contains(&source.text()))
}

fn is_named_react_export(binding: &Binding, lib: ReactLibrary, name: &str) -> Option<bool> {
Expand All @@ -290,7 +293,10 @@ fn is_named_react_export(binding: &Binding, lib: ReactLibrary, name: &str) -> Op
}

let import = import_specifier.import_clause()?.parent::<JsImport>()?;
Some(import.source_text().ok()?.text() == lib.import_name())
import
.source_text()
.ok()
.map(|import_name| lib.import_names().contains(&import_name.text()))
}

/// Checks if `binding` is an import of the global name of `lib`.
Expand Down Expand Up @@ -324,5 +330,5 @@ pub(crate) fn is_global_react_import(binding: &JsIdentifierBinding, lib: ReactLi
.skip(1)
.find_map(JsImport::cast)
.and_then(|import| import.source_text().ok())
.is_some_and(|source| source.text() == lib.import_name())
.is_some_and(|source| lib.import_names().contains(&source.text()))
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ invalidGlobal.tsx:1:11 lint/correctness/noRenderReturnValue ━━━━━━
2 │ const foo = bar && ReactDOM.render(<div />, document.body);
3 │ const foo = bar ? ReactDOM.render(<div />, document.body) : null
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -47,7 +47,7 @@ invalidGlobal.tsx:2:20 lint/correctness/noRenderReturnValue ━━━━━━
3 │ const foo = bar ? ReactDOM.render(<div />, document.body) : null
4 │ const foo = () => ReactDOM.render(<div />, document.body);
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -65,7 +65,7 @@ invalidGlobal.tsx:3:19 lint/correctness/noRenderReturnValue ━━━━━━
4 │ const foo = () => ReactDOM.render(<div />, document.body);
5 │ const foo = {
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -83,7 +83,7 @@ invalidGlobal.tsx:4:19 lint/correctness/noRenderReturnValue ━━━━━━
5 │ const foo = {
6 │ react: ReactDOM.render(<div />, document.body)
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -101,7 +101,7 @@ invalidGlobal.tsx:6:12 lint/correctness/noRenderReturnValue ━━━━━━
7 │ };
8 │ let lorem;
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -119,7 +119,7 @@ invalidGlobal.tsx:9:9 lint/correctness/noRenderReturnValue ━━━━━━━
10 │ function render() {
11 │ return ReactDOM.render(<div />, document.body)
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -137,10 +137,8 @@ invalidGlobal.tsx:11:12 lint/correctness/noRenderReturnValue ━━━━━━
12 │ }
13 │
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
```


Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ invalidImport.tsx:3:11 lint/correctness/noRenderReturnValue ━━━━━━
4 │ const foo = bar && CustomReactDOM.render(<div />, document.body);
5 │ const foo = bar ? CustomReactDOM.render(<div />, document.body) : null
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -64,7 +64,7 @@ invalidImport.tsx:4:20 lint/correctness/noRenderReturnValue ━━━━━━
5 │ const foo = bar ? CustomReactDOM.render(<div />, document.body) : null
6 │ const foo = () => CustomReactDOM.render(<div />, document.body);
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -82,7 +82,7 @@ invalidImport.tsx:5:19 lint/correctness/noRenderReturnValue ━━━━━━
6 │ const foo = () => CustomReactDOM.render(<div />, document.body);
7 │ const foo = {
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -100,7 +100,7 @@ invalidImport.tsx:6:19 lint/correctness/noRenderReturnValue ━━━━━━
7 │ const foo = {
8 │ react: CustomReactDOM.render(<div />, document.body)
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -118,7 +118,7 @@ invalidImport.tsx:8:12 lint/correctness/noRenderReturnValue ━━━━━━
9 │ };
10 │ let lorem;
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -136,7 +136,7 @@ invalidImport.tsx:11:9 lint/correctness/noRenderReturnValue ━━━━━━
12 │ function render1() {
13 │ return CustomReactDOM.render(<div />, document.body)
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -154,7 +154,7 @@ invalidImport.tsx:13:12 lint/correctness/noRenderReturnValue ━━━━━━
14 │ }
15 │
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -172,7 +172,7 @@ invalidImport.tsx:16:11 lint/correctness/noRenderReturnValue ━━━━━━
17 │ const foo = bar && customRender(<div />, document.body);
18 │ const foo = bar ? customRender(<div />, document.body) : null
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -189,7 +189,7 @@ invalidImport.tsx:17:20 lint/correctness/noRenderReturnValue ━━━━━━
18 │ const foo = bar ? customRender(<div />, document.body) : null
19 │ const foo = () => customRender(<div />, document.body);
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -207,7 +207,7 @@ invalidImport.tsx:18:19 lint/correctness/noRenderReturnValue ━━━━━━
19 │ const foo = () => customRender(<div />, document.body);
20 │ const foo = {
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -225,7 +225,7 @@ invalidImport.tsx:19:19 lint/correctness/noRenderReturnValue ━━━━━━
20 │ const foo = {
21 │ react: customRender(<div />, document.body)
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -243,7 +243,7 @@ invalidImport.tsx:21:12 lint/correctness/noRenderReturnValue ━━━━━━
22 │ };
23 │ let lorem;
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -261,7 +261,7 @@ invalidImport.tsx:24:9 lint/correctness/noRenderReturnValue ━━━━━━
25 │ function render2() {
26 │ return customRender(<div />, document.body)
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
Expand All @@ -279,10 +279,8 @@ invalidImport.tsx:26:12 lint/correctness/noRenderReturnValue ━━━━━━
27 │ }
28 │
i The returned value is legacy and future versions of react might return that value asynchronously.
i The returned value is legacy and future versions of React might return that value asynchronously.
Check the React documentation for more information.
```


Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
useReducer,
useTransition,
} from "react";
import { useRef } from "preact/hooks"
import { useRef } from "unknown/hooks"

function MyComponent1() {
let a = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
useReducer,
useTransition,
} from "react";
import { useRef } from "preact/hooks"
import { useRef } from "unknown/hooks"

function MyComponent1() {
let a = 1;
Expand Down Expand Up @@ -844,5 +844,3 @@ missingDependenciesInvalid.js:152:2 lint/correctness/useExhaustiveDependencies
```


Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useCallback } from 'preact/compat';
import { useState } from 'preact/hooks';

function useCounter() {
const [value, setValue] = useState(0);
const increment = useCallback(() => {
setValue(value + 1);
}, []);
return { value, increment };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
expression: preactHooks.js
---
# Input
```jsx
import { useCallback } from 'preact/compat';
import { useState } from 'preact/hooks';

function useCounter() {
const [value, setValue] = useState(0);
const increment = useCallback(() => {
setValue(value + 1);
}, []);
return { value, increment };
}

```

# Diagnostics
```
preactHooks.js:6:23 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
! This hook does not specify all of its dependencies: value
4 │ function useCounter() {
5 │ const [value, setValue] = useState(0);
> 6 │ const increment = useCallback(() => {
│ ^^^^^^^^^^^
7 │ setValue(value + 1);
8 │ }, []);
i This dependency is not specified in the hook dependency list.
5 │ const [value, setValue] = useState(0);
6 │ const increment = useCallback(() => {
> 7 │ setValue(value + 1);
│ ^^^^^
8 │ }, []);
9 │ return { value, increment };
i Either include it or remove the dependency array
```
Loading

0 comments on commit 7b6439b

Please sign in to comment.