Skip to content

Commit

Permalink
refactor: replace ignoreReact with jsxRuntime setting (#2397)
Browse files Browse the repository at this point in the history
  • Loading branch information
arendjr authored Apr 10, 2024
1 parent dcb695d commit 39139a5
Show file tree
Hide file tree
Showing 19 changed files with 228 additions and 121 deletions.
7 changes: 3 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,12 +196,11 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b

#### New features

- Add a new option `ignoreReact` to [noUnusedImports](https://biomejs.dev/linter/rules/no-unused-imports).
- Add a new option `jsxRuntime` to the `javascript` configuration. When set to `reactClassic`, the [noUnusedImports](https://biomejs.dev/linter/rules/no-unused-imports) rule uses this information to make an exception for the React global that is required by the React Classic JSX transform.

When `ignoreReact` is enabled, Biome ignores imports of `React` from the `react` package.
The option is disabled by default.
This is only necessary for React users who haven't upgraded to the [new JSX transform](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html).

Contributed by @Conaclos
Contributed by @Conaclos and @arendjr

- 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

Expand Down
11 changes: 10 additions & 1 deletion crates/biome_analyze/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::options::PreferredQuote;
use crate::options::{JsxRuntime, PreferredQuote};
use crate::{registry::RuleRoot, FromServices, Queryable, Rule, RuleKey, ServiceBag};
use biome_diagnostics::{Error, Result};
use std::ops::Deref;
Expand All @@ -19,12 +19,14 @@ where
file_path: &'a Path,
options: &'a R::Options,
preferred_quote: &'a PreferredQuote,
jsx_runtime: JsxRuntime,
}

impl<'a, R> RuleContext<'a, R>
where
R: Rule + Sized + 'static,
{
#[allow(clippy::too_many_arguments)]
pub fn new(
query_result: &'a RuleQueryResult<R>,
root: &'a RuleRoot<R>,
Expand All @@ -33,6 +35,7 @@ where
file_path: &'a Path,
options: &'a R::Options,
preferred_quote: &'a PreferredQuote,
jsx_runtime: JsxRuntime,
) -> Result<Self, Error> {
let rule_key = RuleKey::rule::<R>();
Ok(Self {
Expand All @@ -44,6 +47,7 @@ where
file_path,
options,
preferred_quote,
jsx_runtime,
})
}

Expand Down Expand Up @@ -97,6 +101,11 @@ where
self.options
}

/// Checks whether the JSX runtime matches the given value.
pub fn has_jsx_runtime(&self, jsx_runtime: JsxRuntime) -> bool {
self.jsx_runtime == jsx_runtime
}

/// Checks whether the provided text belongs to globals
pub fn is_global(&self, text: &str) -> bool {
self.globals.contains(&text)
Expand Down
15 changes: 15 additions & 0 deletions crates/biome_analyze/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ pub struct AnalyzerConfiguration {

/// Allows to choose a different quote when applying fixes inside the lint rules
pub preferred_quote: PreferredQuote,

/// Indicates the type of runtime or transformation used for interpreting JSX.
pub jsx_runtime: JsxRuntime,
}

/// A set of information useful to the analyzer infrastructure
Expand All @@ -67,6 +70,7 @@ pub struct AnalyzerOptions {
/// The file that is being analyzed
pub file_path: PathBuf,
}

impl AnalyzerOptions {
pub fn globals(&self) -> Vec<&str> {
self.configuration
Expand All @@ -76,6 +80,10 @@ impl AnalyzerOptions {
.collect()
}

pub fn jsx_runtime(&self) -> JsxRuntime {
self.configuration.jsx_runtime
}

pub fn rule_options<R: 'static>(&self) -> Option<R::Options>
where
R: Rule,
Expand Down Expand Up @@ -110,3 +118,10 @@ impl PreferredQuote {
matches!(self, Self::Single)
}
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum JsxRuntime {
#[default]
Transparent,
ReactClassic,
}
2 changes: 2 additions & 0 deletions crates/biome_analyze/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ impl<L: Language + Default> RegistryRule<L> {
let query_result = <R::Query as Queryable>::unwrap_match(params.services, query_result);
let globals = params.options.globals();
let preferred_quote = params.options.preferred_quote();
let jsx_runtime = params.options.jsx_runtime();
let options = params.options.rule_options::<R>().unwrap_or_default();
let ctx = match RuleContext::new(
&query_result,
Expand All @@ -421,6 +422,7 @@ impl<L: Language + Default> RegistryRule<L> {
&params.options.file_path,
&options,
preferred_quote,
jsx_runtime,
) {
Ok(ctx) => ctx,
Err(error) => return Err(error),
Expand Down
7 changes: 5 additions & 2 deletions crates/biome_analyze/src/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ where
&self.options.file_path,
&options,
preferred_quote,
self.options.jsx_runtime(),
)
.ok()?;

Expand All @@ -374,7 +375,8 @@ where
&globals,
&self.options.file_path,
&options,
&self.options.configuration.preferred_quote,
self.options.preferred_quote(),
self.options.jsx_runtime(),
)
.ok();
if let Some(ctx) = ctx {
Expand Down Expand Up @@ -419,7 +421,8 @@ where
&globals,
&self.options.file_path,
&options,
&self.options.configuration.preferred_quote,
self.options.preferred_quote(),
self.options.jsx_runtime(),
)
.ok();
if let Some(ctx) = ctx {
Expand Down
40 changes: 40 additions & 0 deletions crates/biome_configuration/src/javascript/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod formatter;

use std::str::FromStr;

use biome_deserialize::StringSet;
use biome_deserialize_macros::{Deserializable, Merge, Partial};
use bpaf::Bpaf;
Expand Down Expand Up @@ -28,6 +30,10 @@ pub struct JavascriptConfiguration {
#[partial(bpaf(hide))]
pub globals: StringSet,

/// Indicates the type of runtime or transformation used for interpreting JSX.
#[partial(bpaf(hide))]
pub jsx_runtime: JsxRuntime,

#[partial(type, bpaf(external(partial_javascript_organize_imports), optional))]
pub organize_imports: JavascriptOrganizeImports,
}
Expand All @@ -50,3 +56,37 @@ pub struct JavascriptParser {
#[partial(bpaf(hide))]
pub unsafe_parameter_decorators_enabled: bool,
}

/// Indicates the type of runtime or transformation used for interpreting JSX.
#[derive(
Bpaf, Clone, Copy, Debug, Default, Deserialize, Deserializable, Eq, Merge, PartialEq, Serialize,
)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum JsxRuntime {
/// Indicates a modern or native JSX environment, that doesn't require
/// special handling by Biome.
#[default]
Transparent,

/// Indicates a classic React environment that requires the `React` import.
///
/// Corresponds to the `react` value for the `jsx` option in TypeScript's
/// `tsconfig.json`.
///
/// This option should only be necessary if you cannot upgrade to a React
/// version that supports the new JSX runtime. For more information about
/// the old vs. new JSX runtime, please see:
/// https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html
ReactClassic,
}

impl FromStr for JsxRuntime {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"transparent" => Ok(Self::Transparent),
"react-classic" | "reactClassic" => Ok(Self::ReactClassic),
_ => Err("Unexpected value".to_string()),
}
}
}
41 changes: 9 additions & 32 deletions crates/biome_js_analyze/src/lint/correctness/no_unused_imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ use crate::{
JsRuleAction,
};
use biome_analyze::{
context::RuleContext, declare_rule, ActionCategory, FixKind, Rule, RuleDiagnostic,
context::RuleContext, declare_rule, options::JsxRuntime, ActionCategory, FixKind, Rule,
RuleDiagnostic,
};
use biome_console::markup;
use biome_deserialize_macros::Deserializable;
use biome_diagnostics::Applicability;
use biome_js_factory::make;
use biome_js_semantic::ReferencesExtensions;
Expand All @@ -16,10 +16,6 @@ use biome_js_syntax::{
JsIdentifierBinding, JsImport, JsLanguage, JsNamedImportSpecifierList, JsSyntaxNode, T,
};
use biome_rowan::{AstNode, AstSeparatedList, BatchMutation, BatchMutationExt};
use serde::{Deserialize, Serialize};

#[cfg(feature = "schemars")]
use schemars::JsonSchema;

declare_rule! {
/// Disallow unused imports.
Expand All @@ -34,22 +30,9 @@ declare_rule! {
///
/// ## Options
///
/// The rule provides a single option `ignoreReact`.
/// When this option is set to `true`, imports named `React` from the package `react` are ignored.
/// `ignoreReact` is disabled by default.
///
/// ```json
/// {
/// "//": "...",
/// "options": {
/// "ignoreReact": true
/// }
/// }
/// ```
///
/// This option should only be necessary if you cannot upgrade to a React version that supports the new JSX runtime.
/// In the new JSX runtime, you no longer need to import `React`.
/// You can find more details in [this comment](https://github.com/biomejs/biome/issues/571#issuecomment-1774026734).
/// This rule respects the [`jsxRuntime`](https://biomejs.dev/reference/configuration/#javascriptjsxruntime)
/// setting and will make an exception for React globals if it is set to
/// `"reactClassic"`.
///
/// ## Examples
///
Expand Down Expand Up @@ -105,15 +88,17 @@ impl Rule for NoUnusedImports {
type Query = Semantic<JsIdentifierBinding>;
type State = ();
type Signals = Option<Self::State>;
type Options = UnusedImportsOptions;
type Options = ();

fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let binding = ctx.query();
let declaration = binding.declaration()?;
if !is_import(&declaration) {
return None;
}
if ctx.options().ignore_react && is_global_react_import(binding, ReactLibrary::React) {
if ctx.has_jsx_runtime(JsxRuntime::ReactClassic)
&& is_global_react_import(binding, ReactLibrary::React)
{
return None;
}
let model = ctx.model();
Expand Down Expand Up @@ -182,14 +167,6 @@ impl Rule for NoUnusedImports {
}
}

#[derive(Clone, Debug, Default, Deserializable, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct UnusedImportsOptions {
/// Ignore `React` imports from the `react` package when set to `true`.
ignore_react: bool,
}

fn remove_import_specifier(
mutation: &mut BatchMutation<JsLanguage>,
specifier: &JsSyntaxNode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
"linter": {
"rules": {
"correctness": {
"noUnusedImports": {
"level": "error",
"options": {
"ignoreReact": true
}
}
"noUnusedImports": "error"
}
}
},
"javascript": {
"jsxRuntime": "reactClassic"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
"linter": {
"rules": {
"correctness": {
"noUnusedImports": {
"level": "error",
"options": {
"ignoreReact": true
}
}
"noUnusedImports": "error"
}
}
},
"javascript": {
"jsxRuntime": "reactClassic"
}
}
}
2 changes: 2 additions & 0 deletions crates/biome_service/src/file_handlers/css.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ impl ServiceLanguage for CssLanguage {
type OrganizeImportsSettings = ();
type FormatOptions = CssFormatOptions;
type ParserSettings = CssParserSettings;
type EnvironmentSettings = ();

fn lookup_settings(language: &LanguageListSettings) -> &LanguageSettings<Self> {
&language.css
}
Expand Down
Loading

0 comments on commit 39139a5

Please sign in to comment.