feat(rome_js_analyze): `noRenderReturnValue`
MODIFY
crates/rome_js_analyze/src/semantic_analyzers/nursery.rs
crates/rome_js_analyze/src/semantic_analyzers/nursery.rs
use rome_analyze::declare_group;
mod no_dangerously_set_inner_html;
+mod no_render_return_value;
mod no_unused_variables;
mod use_button_type;
mod use_camel_case;
-declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_dangerously_set_inner_html :: NoDangerouslySetInnerHtml , self :: no_unused_variables :: NoUnusedVariables , self :: use_button_type :: UseButtonType , self :: use_camel_case :: UseCamelCase ,] } }
+declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_dangerously_set_inner_html :: NoDangerouslySetInnerHtml , self :: no_render_return_value :: NoRenderReturnValue , self :: no_unused_variables :: NoUnusedVariables , self :: use_button_type :: UseButtonType , self :: use_camel_case :: UseCamelCase ,] } }

ADD
crates/rome_js_analyze/src/semantic_analyzers/nursery/no_render_return_value.rs
+use crate::semantic_services::Semantic;
+use rome_analyze::context::RuleContext;
+use rome_analyze::{declare_rule, Rule, RuleCategory, RuleDiagnostic};
+use rome_console::markup;
+use rome_js_semantic::SemanticModel;
+use rome_js_syntax::JsSyntaxKind::JS_IMPORT;
+use rome_js_syntax::{
+ JsAnyExpression, JsCallExpression, JsExpressionStatement, JsIdentifierBinding,
+ JsIdentifierExpression, JsStaticMemberExpression,
+};
+use rome_rowan::{declare_node_union, AstNode};
+
+declare_rule! {
+ /// Prevent the usage of the return value of `React.render`.
+ ///
+ /// > `ReactDOM.render()` currently returns a reference to the root `ReactComponent` instance. However, using this return value is legacy
+ /// and should be avoided because future versions of React may render components asynchronously in some cases.
+ /// If you need a reference to the root `ReactComponent` instance, the preferred solution is to attach a [callback ref](https://reactjs.org/docs/refs-and-the-dom.html#callback-refs)
+ /// to the root element.
+ ///
+ /// Source: [ReactDOM documentation](https://facebook.github.io/react/docs/react-dom.html#render)
+ ///
+ /// ## Examples
+ ///
+ /// ### Invalid
+ ///
+ /// ```jsx,expect_diagnostic
+ /// const foo = ReactDOM.render(<div />, document.body);
+ /// ```
+ ///
+ /// ### Valid
+ ///
+ /// ```jsx
+ /// ReactDOM.render(<div />, document.body);
+ /// ```
+ pub(crate) NoRenderReturnValue {
+ version: "0.10.0",
+ name: "noRenderReturnValue",
+ recommended: false,
+ }
+}
+
+impl Rule for NoRenderReturnValue {
+ const CATEGORY: RuleCategory = RuleCategory::Lint;
+
+ type Query = Semantic<JsCallExpression>;
+ type State = ();
+ type Signals = Option<Self::State>;
+
+ fn run(ctx: &RuleContext<Self>) -> Self::Signals {
+ let node = ctx.query();
+ let callee = node.callee().ok()?;
+ let model = ctx.model();
+ let is_react_render = match callee {
+ JsAnyExpression::JsStaticMemberExpression(static_member) => {
+ is_react_render(PossibleReactRender::from(static_member), model)
+ }
+ JsAnyExpression::JsIdentifierExpression(identifier_expression) => {
+ is_react_render(PossibleReactRender::from(identifier_expression), model)
+ }
+ _ => return None,
+ }?;
+ if is_react_render {
+ let parent = node.syntax().parent()?;
+
+ if !JsExpressionStatement::can_cast(parent.kind()) {
+ return Some(());
+ }
+ }
+ None
+ }
+
+ fn diagnostic(ctx: &RuleContext<Self>, _state: &Self::State) -> Option<RuleDiagnostic> {
+ let node = ctx.query();
+ Some(RuleDiagnostic::new(
+ node.syntax().text_trimmed_range(),
+ markup! {
+ "Do not depend on the value returned by the function "<Emphasis>"ReactDOM.render()"</Emphasis>"."
+ },
+ ).footer_note(markup! {
+"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."
+
+ })
+ )
+ }
+}
+
+declare_node_union! {
+ pub(crate) PossibleReactRender = JsStaticMemberExpression | JsIdentifierExpression
+}
+
+fn is_react_render(node: PossibleReactRender, model: &SemanticModel) -> Option<bool> {
+ let result = match node {
+ PossibleReactRender::JsStaticMemberExpression(node) => {
+ let object = node.object().ok()?;
+ let member = node.member().ok()?;
+ let member = member.as_js_name()?;
+ let identifier = object.as_js_identifier_expression()?.name().ok()?;
+
+ let maybe_from_react = identifier.syntax().text_trimmed() == "ReactDOM"
+ && member.syntax().text_trimmed() == "render";
+
+ if maybe_from_react {
+ let identifier_binding = model.declaration(&identifier);
+ if let Some(binding_identifier) = identifier_binding {
+ let binding_identifier =
+ JsIdentifierBinding::cast_ref(binding_identifier.syntax())?;
+ for ancestor in binding_identifier.syntax().ancestors() {
+ if ancestor.kind() == JS_IMPORT {
+ return Some(
+ binding_identifier.syntax().text_trimmed()
+ == identifier.syntax().text_trimmed(),
+ );
+ }
+ }
+ }
+ }
+ maybe_from_react
+ }
+ PossibleReactRender::JsIdentifierExpression(identifier) => {
+ let maybe_react_render = identifier.syntax().text_trimmed() == "render";
+ let name = identifier.name().ok()?;
+ if maybe_react_render {
+ let declaration = model.declaration(&name);
+ if let Some(declaration) = declaration {
+ let identifier_binding = JsIdentifierBinding::cast_ref(declaration.syntax())?;
+ for ancestor in identifier_binding.syntax().ancestors() {
+ if ancestor.kind() == JS_IMPORT {
+ return Some(
+ identifier_binding.syntax().text_trimmed()
+ == identifier.syntax().text_trimmed(),
+ );
+ }
+ }
+ }
+ }
+
+ maybe_react_render
+ }
+ };
+
+ Some(result)
+}

ADD
crates/rome_js_analyze/tests/specs/nursery/noRenderReturnValue.jsx
+// invalid
+const a = ReactDOM.render(<div />, document.body);
+const foo = bar && ReactDOM.render(<div />, document.body);
+const foo = bar ? ReactDOM.render(<div />, document.body) : null
+const foo = () => ReactDOM.render(<div />, document.body);
+const foo = {
+ react: ReactDOM.render(<div />, document.body)
+};
+let lorem;
+lorem = ReactDOM.render(<div />, document.body);
+function render () {
+ return ReactDOM.render(<div />, document.body)
+}
+
+// valid
+ReactDOM.render(<div />, document.body);
+function render () {
+ ReactDOM.render(<div />, document.body);
+}
+const render = () => {
+ ReactDOM.render(<div />, document.body);
+}
+

MODIFY
crates/rome_service/src/configuration/linter/rules.rs
crates/rome_service/src/configuration/linter/rules.rs
@@ -385,6 +385,7 @@ pub struct Nursery {
struct NurserySchema {
no_dangerously_set_inner_html: Option<RuleConfiguration>,
no_new_symbol: Option<RuleConfiguration>,
+ no_render_return_value: Option<RuleConfiguration>,
no_unreachable: Option<RuleConfiguration>,
no_unused_variables: Option<RuleConfiguration>,
use_button_type: Option<RuleConfiguration>,
@@ -393,9 +394,10 @@ struct NurserySchema {
}
impl Nursery {
const CATEGORY_NAME: &'static str = "nursery";
- pub(crate) const CATEGORY_RULES: [&'static str; 7] = [
+ pub(crate) const CATEGORY_RULES: [&'static str; 8] = [
"noDangerouslySetInnerHtml",
"noNewSymbol",
+ "noRenderReturnValue",
"noUnreachable",
"noUnusedVariables",
"useButtonType",

MODIFY
editors/vscode/configuration_schema.json
editors/vscode/configuration_schema.json
}
]
},
+ "noRenderReturnValue": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/RuleConfiguration"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
"noUnreachable": {
"anyOf": [
{

MODIFY
npm/backend-jsonrpc/src/workspace.ts
npm/backend-jsonrpc/src/workspace.ts
@@ -157,6 +157,7 @@ export interface Correctness {
export interface Nursery {
noDangerouslySetInnerHtml?: RuleConfiguration;
noNewSymbol?: RuleConfiguration;
+ noRenderReturnValue?: RuleConfiguration;
noUnreachable?: RuleConfiguration;
noUnusedVariables?: RuleConfiguration;
/**

MODIFY
website/src/docs/lint/rules/index.md
website/src/docs/lint/rules/index.md
@@ -267,6 +267,13 @@ Prevent the usage of dangerous JSX props
Disallow <code>new</code> operators with the <code>Symbol</code> object
</div>
<div class="rule">
+<h3 data-toc-exclude id="noRenderReturnValue">
+ <a href="/docs/lint/rules/noRenderReturnValue">noRenderReturnValue (since v0.10.0)</a>
+ <a class="header-anchor" href="#noRenderReturnValue"></a>
+</h3>
+Prevent the usage of the return value of <code>React.render</code>.
+</div>
+<div class="rule">
<h3 data-toc-exclude id="noUnreachable">
<a href="/docs/lint/rules/noUnreachable">noUnreachable (since v0.7.0)</a>
<a class="header-anchor" href="#noUnreachable"></a>

MODIFY
xtask/lintdoc/src/main.rs
xtask/lintdoc/src/main.rs
@@ -356,6 +356,14 @@ fn parse_documentation(
write!(content, "~")?;
}
+ Event::Start(Tag::BlockQuote) => {
+ write!(content, ">")?;
+ }
+
+ Event::End(Tag::BlockQuote) => {
+ writeln!(content)?;
+ }
+
_ => {
// TODO: Implement remaining events as required
bail!("unimplemented event {event:?}")

feat(rome_js_analyze): `noRenderReturnValue` (#3252)