diff --git a/Cargo.lock b/Cargo.lock index e27789ea5bc2..48155a23afb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -400,6 +400,7 @@ dependencies = [ "countme", "insta", "lazy_static", + "log", "natord", "roaring", "rustc-hash", diff --git a/crates/biome_cli/src/commands/ci.rs b/crates/biome_cli/src/commands/ci.rs index 1d98101b1418..1ae748de9bcb 100644 --- a/crates/biome_cli/src/commands/ci.rs +++ b/crates/biome_cli/src/commands/ci.rs @@ -3,7 +3,7 @@ use crate::configuration::LoadedConfiguration; use crate::vcs::store_path_to_ignore_from_vcs; use crate::{ configuration::load_configuration, execute_mode, setup_cli_subscriber, CliDiagnostic, - CliSession, Execution, TraversalMode, + CliSession, Execution, }; use biome_service::configuration::organize_imports::OrganizeImports; use biome_service::configuration::{FormatterConfiguration, LinterConfiguration}; @@ -94,7 +94,7 @@ pub(crate) fn ci(mut session: CliSession, payload: CiCommandPayload) -> Result<( .update_settings(UpdateSettingsParams { configuration })?; execute_mode( - Execution::new(TraversalMode::CI), + Execution::new_ci(), session, &payload.cli_options, payload.paths, diff --git a/crates/biome_cli/src/execute/mod.rs b/crates/biome_cli/src/execute/mod.rs index 38bf3d1a93f3..40bcc38f874d 100644 --- a/crates/biome_cli/src/execute/mod.rs +++ b/crates/biome_cli/src/execute/mod.rs @@ -35,6 +35,11 @@ impl Execution { } } +#[derive(Debug)] +pub(crate) enum ExecutionEnvironment { + GitHub, +} + #[derive(Debug)] pub(crate) enum TraversalMode { /// This mode is enabled when running the command `biome check` @@ -62,7 +67,10 @@ pub(crate) enum TraversalMode { stdin: Option<(PathBuf, String)>, }, /// This mode is enabled when running the command `biome ci` - CI, + CI { + /// Whether the CI is running in a specific environment, e.g. GitHub, GitLab, etc. + environment: Option, + }, /// This mode is enabled when running the command `biome format` Format { /// It ignores parse errors @@ -113,6 +121,26 @@ impl Execution { } } + pub(crate) fn new_ci() -> Self { + // Ref: https://docs.github.com/actions/learn-github-actions/variables#default-environment-variables + let is_github = std::env::var("GITHUB_ACTIONS") + .ok() + .map(|value| value == "true") + .unwrap_or(false); + + Self { + report_mode: ReportMode::default(), + traversal_mode: TraversalMode::CI { + environment: if is_github { + Some(ExecutionEnvironment::GitHub) + } else { + None + }, + }, + max_diagnostics: MAXIMUM_DISPLAYABLE_DIAGNOSTICS, + } + } + /// Creates an instance of [Execution] by passing [traversal mode](TraversalMode) and [report mode](ReportMode) pub(crate) fn with_report(traversal_mode: TraversalMode, report_mode: ReportMode) -> Self { Self { @@ -140,9 +168,9 @@ impl Execution { match &self.traversal_mode { TraversalMode::Check { fix_file_mode, .. } | TraversalMode::Lint { fix_file_mode, .. } => fix_file_mode.as_ref(), - TraversalMode::Format { .. } | TraversalMode::CI | TraversalMode::Migrate { .. } => { - None - } + TraversalMode::Format { .. } + | TraversalMode::CI { .. } + | TraversalMode::Migrate { .. } => None, } } @@ -150,7 +178,7 @@ impl Execution { match self.traversal_mode { TraversalMode::Check { .. } => category!("check"), TraversalMode::Lint { .. } => category!("lint"), - TraversalMode::CI => category!("ci"), + TraversalMode::CI { .. } => category!("ci"), TraversalMode::Format { .. } => category!("format"), TraversalMode::Migrate { .. } => category!("migrate"), } @@ -205,7 +233,7 @@ impl Execution { match self.traversal_mode { TraversalMode::Check { fix_file_mode, .. } | TraversalMode::Lint { fix_file_mode, .. } => fix_file_mode.is_some(), - TraversalMode::CI => false, + TraversalMode::CI { .. } => false, TraversalMode::Format { write, .. } => write, TraversalMode::Migrate { write: dry_run, .. } => dry_run, } diff --git a/crates/biome_cli/src/execute/process_file.rs b/crates/biome_cli/src/execute/process_file.rs index bee018f6a4ab..c17531d4e722 100644 --- a/crates/biome_cli/src/execute/process_file.rs +++ b/crates/biome_cli/src/execute/process_file.rs @@ -224,7 +224,9 @@ pub(crate) fn process_file(ctx: &TraversalOptions, path: &Path) -> FileResult { TraversalMode::Check { .. } => { check_file(shared_context, path, &file_features, category!("check")) } - TraversalMode::CI => check_file(shared_context, path, &file_features, category!("ci")), + TraversalMode::CI { .. } => { + check_file(shared_context, path, &file_features, category!("ci")) + } TraversalMode::Migrate { .. } => { unreachable!("The migration should not be called for this file") } diff --git a/crates/biome_cli/src/execute/traverse.rs b/crates/biome_cli/src/execute/traverse.rs index 4fd683ec5320..dc2559bcd314 100644 --- a/crates/biome_cli/src/execute/traverse.rs +++ b/crates/biome_cli/src/execute/traverse.rs @@ -1,4 +1,5 @@ use super::process_file::{process_file, DiffKind, FileStatus, Message}; +use super::ExecutionEnvironment; use crate::cli_options::CliOptions; use crate::execute::diagnostics::{ CIFormatDiffDiagnostic, CIOrganizeImportsDiffDiagnostic, ContentDiffAdvice, @@ -9,6 +10,7 @@ use crate::{ Report, ReportDiagnostic, ReportDiff, ReportErrorKind, ReportKind, TraversalMode, }; use biome_console::{fmt, markup, Console, ConsoleExt}; +use biome_diagnostics::PrintGitHubDiagnostic; use biome_diagnostics::{ adapters::StdError, category, DiagnosticExt, Error, PrintDescription, PrintDiagnostic, Resource, Severity, @@ -570,6 +572,12 @@ fn process_messages(options: ProcessMessagesOptions) { } } } + let running_on_github = matches!( + mode.traversal_mode(), + TraversalMode::CI { + environment: Some(ExecutionEnvironment::GitHub), + } + ); for diagnostic in diagnostics_to_print { if diagnostic.severity() >= *diagnostic_level { @@ -577,6 +585,10 @@ fn process_messages(options: ProcessMessagesOptions) { {if verbose { PrintDiagnostic::verbose(&diagnostic) } else { PrintDiagnostic::simple(&diagnostic) }} }); } + + if running_on_github { + console.log(markup! {{PrintGitHubDiagnostic::simple(&diagnostic)}}); + } } if mode.is_check() && total_skipped_suggested_fixes > 0 { diff --git a/crates/biome_cli/tests/snap_test.rs b/crates/biome_cli/tests/snap_test.rs index f8dc3add30b3..5303cd55b3e1 100644 --- a/crates/biome_cli/tests/snap_test.rs +++ b/crates/biome_cli/tests/snap_test.rs @@ -49,10 +49,9 @@ impl CliSnapshot { let mut content = String::new(); if let Some(configuration) = &self.configuration { - let parsed = parse_json( - &redact_snapshot(configuration), - JsonParserOptions::default(), - ); + let redacted = redact_snapshot(configuration).unwrap_or(String::new().into()); + + let parsed = parse_json(&redacted, JsonParserOptions::default()); let formatted = format_node( JsonFormatOptions::default() .with_indent_style(IndentStyle::Space) @@ -75,10 +74,14 @@ impl CliSnapshot { if !name.starts_with("biome.json") { let extension = name.split('.').last().unwrap(); - let _ = write!(content, "## `{}`\n\n", redact_snapshot(name)); + let redacted_name = redact_snapshot(name).unwrap_or(String::new().into()); + let redacted_content = + redact_snapshot(file_content).unwrap_or(String::new().into()); + + let _ = write!(content, "## `{}`\n\n", redacted_name); let _ = write!(content, "```{extension}"); content.push('\n'); - content.push_str(&redact_snapshot(file_content)); + content.push_str(&redacted_content); content.push('\n'); content.push_str("```"); content.push_str("\n\n") @@ -97,22 +100,29 @@ impl CliSnapshot { if let Some(termination) = &self.termination { let message = print_diagnostic_to_string(termination); - content.push_str("# Termination Message\n\n"); - content.push_str("```block"); - content.push('\n'); - content.push_str(&redact_snapshot(&message)); - content.push('\n'); - content.push_str("```"); - content.push_str("\n\n"); + + if let Some(redacted) = &redact_snapshot(&message) { + content.push_str("# Termination Message\n\n"); + content.push_str("```block"); + content.push('\n'); + content.push_str(redacted); + content.push('\n'); + content.push_str("```"); + content.push_str("\n\n"); + } } if !self.messages.is_empty() { content.push_str("# Emitted Messages\n\n"); for message in &self.messages { + let Some(redacted) = &redact_snapshot(message) else { + continue; + }; + content.push_str("```block"); content.push('\n'); - content.push_str(&redact_snapshot(message)); + content.push_str(redacted); content.push('\n'); content.push_str("```"); content.push_str("\n\n") @@ -123,7 +133,7 @@ impl CliSnapshot { } } -fn redact_snapshot(input: &str) -> Cow<'_, str> { +fn redact_snapshot(input: &str) -> Option> { let mut output = Cow::Borrowed(input); // There are some logs that print the timing, and we can't snapshot that message @@ -148,6 +158,33 @@ fn redact_snapshot(input: &str) -> Cow<'_, str> { output = replace_temp_dir(output); + // Ref: https://docs.github.com/actions/learn-github-actions/variables#default-environment-variables + let is_github = std::env::var("GITHUB_ACTIONS") + .ok() + .map(|value| value == "true") + .unwrap_or(false); + + if is_github { + // GitHub actions sets the env var GITHUB_ACTIONS=true in CI + // Based on that, biome also has a feature that detects if it's running on GH Actions and + // emits additional information in the output (workflow commands, see PR + discussion at #681) + // To avoid polluting snapshots with that, we filter out those lines + + let lines: Vec<_> = output + .split('\n') + .filter(|line| { + !line.starts_with("::notice ") + && !line.starts_with("::warning ") + && !line.starts_with("::error ") + }) + .collect(); + + output = Cow::Owned(lines.join("\n")); + if output.is_empty() { + return None; + } + } + // Normalize Windows-specific path separators to "/" if cfg!(windows) { let mut rest = &*output; @@ -185,7 +222,7 @@ fn redact_snapshot(input: &str) -> Cow<'_, str> { } } - output + Some(output) } /// Replace the path to the temporary directory with "" diff --git a/crates/biome_diagnostics/src/display.rs b/crates/biome_diagnostics/src/display.rs index 83ef3af2aece..12951c97f66f 100644 --- a/crates/biome_diagnostics/src/display.rs +++ b/crates/biome_diagnostics/src/display.rs @@ -6,7 +6,7 @@ use unicode_width::UnicodeWidthStr; mod backtrace; mod diff; -mod frame; +pub(super) mod frame; mod message; use crate::display::frame::SourceFile; diff --git a/crates/biome_diagnostics/src/display/frame.rs b/crates/biome_diagnostics/src/display/frame.rs index a65d11244697..e129b9bf8d6b 100644 --- a/crates/biome_diagnostics/src/display/frame.rs +++ b/crates/biome_diagnostics/src/display/frame.rs @@ -407,17 +407,17 @@ fn show_invisible_char(char: char) -> Option<&'static str> { /// A user-facing location in a source file. #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub(super) struct SourceLocation { +pub struct SourceLocation { /// The user-facing line number. - pub(super) line_number: OneIndexed, + pub line_number: OneIndexed, /// The user-facing column number. - pub(super) column_number: OneIndexed, + pub column_number: OneIndexed, } /// Representation of a single source file holding additional information for /// efficiently rendering code frames #[derive(Clone)] -pub(super) struct SourceFile<'diagnostic> { +pub struct SourceFile<'diagnostic> { /// The source code of the file. source: &'diagnostic str, /// The starting byte indices in the source code. @@ -426,7 +426,7 @@ pub(super) struct SourceFile<'diagnostic> { impl<'diagnostic> SourceFile<'diagnostic> { /// Create a new [SourceFile] from a slice of text - pub(super) fn new(source_code: BorrowedSourceCode<'diagnostic>) -> Self { + pub fn new(source_code: BorrowedSourceCode<'diagnostic>) -> Self { // Either re-use the existing line index provided by the diagnostic or create one Self { source: source_code.text, @@ -484,7 +484,7 @@ impl<'diagnostic> SourceFile<'diagnostic> { } /// Get a source location from a byte index into the text of this file - pub(super) fn location(&self, byte_index: TextSize) -> io::Result { + pub fn location(&self, byte_index: TextSize) -> io::Result { let line_index = self.line_index(byte_index); Ok(SourceLocation { diff --git a/crates/biome_diagnostics/src/display_github.rs b/crates/biome_diagnostics/src/display_github.rs new file mode 100644 index 000000000000..279d30e9b167 --- /dev/null +++ b/crates/biome_diagnostics/src/display_github.rs @@ -0,0 +1,129 @@ +use std::io; + +use biome_console::{fmt, markup, MarkupBuf}; + +use crate::display::frame::SourceFile; +use crate::{diagnostic::internal::AsDiagnostic, Diagnostic, Resource, Severity}; + +/// Helper struct for printing a diagnostic as markup into any formatter +/// implementing [biome_console::fmt::Write]. +pub struct PrintGitHubDiagnostic<'fmt, D: ?Sized> { + diag: &'fmt D, +} + +impl<'fmt, D: AsDiagnostic + ?Sized> PrintGitHubDiagnostic<'fmt, D> { + pub fn simple(diag: &'fmt D) -> Self { + Self { diag } + } +} + +impl<'fmt, D: AsDiagnostic + ?Sized> fmt::Display for PrintGitHubDiagnostic<'fmt, D> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> io::Result<()> { + let diagnostic = self.diag.as_diagnostic(); + let location = diagnostic.location(); + + // Docs: + // https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions + + let Some(span) = location.span else { + return Ok(()); + }; + let Some(source_code) = location.source_code else { + return Ok(()); + }; + + let file_name_unescaped = match &location.resource { + Some(Resource::File(file)) => file, + _ => return Ok(()), + }; + + let source = SourceFile::new(source_code); + let start = source.location(span.start())?; + let end = source.location(span.end())?; + + let command = match diagnostic.severity() { + Severity::Error | Severity::Fatal => "error", + Severity::Warning => "warning", + Severity::Hint | Severity::Information => "notice", + }; + + let title = { + let mut message = MarkupBuf::default(); + let mut fmt = fmt::Formatter::new(&mut message); + fmt.write_markup(markup!({ PrintDiagnosticMessage(diagnostic) }))?; + markup_to_string(&message) + }; + + fmt.write_str( + format! { + "::{} file={},line={},endLine={},col={},endColumn={}::{}", + command, // constant, doesn't need escaping + escape_property(file_name_unescaped), + start.line_number, // integer, doesn't need escaping + end.line_number, // integer, doesn't need escaping + start.column_number, // integer, doesn't need escaping + end.column_number, // integer, doesn't need escaping + title.map(escape_data).unwrap_or(String::new()), + } + .as_str(), + )?; + + Ok(()) + } +} + +struct PrintDiagnosticMessage<'fmt, D: ?Sized>(&'fmt D); + +impl<'fmt, D: Diagnostic + ?Sized> fmt::Display for PrintDiagnosticMessage<'fmt, D> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> io::Result<()> { + let Self(diagnostic) = *self; + diagnostic.message(fmt)?; + Ok(()) + } +} + +fn escape_data>(value: S) -> String { + let value = value.as_ref(); + + // Refs: + // - https://github.com/actions/runner/blob/a4c57f27477077e57545af79851551ff7f5632bd/src/Runner.Common/ActionCommand.cs#L18-L22 + // - https://github.com/actions/toolkit/blob/fe3e7ce9a7f995d29d1fcfd226a32bca407f9dc8/packages/core/src/command.ts#L80-L94 + let mut result = String::with_capacity(value.len()); + for c in value.chars() { + match c { + '\r' => result.push_str("%0D"), + '\n' => result.push_str("%0A"), + '%' => result.push_str("%25"), + _ => result.push(c), + } + } + result +} + +fn escape_property>(value: S) -> String { + let value = value.as_ref(); + + // Refs: + // - https://github.com/actions/runner/blob/a4c57f27477077e57545af79851551ff7f5632bd/src/Runner.Common/ActionCommand.cs#L25-L32 + // - https://github.com/actions/toolkit/blob/fe3e7ce9a7f995d29d1fcfd226a32bca407f9dc8/packages/core/src/command.ts#L80-L94 + let mut result = String::with_capacity(value.len()); + for c in value.chars() { + match c { + '\r' => result.push_str("%0D"), + '\n' => result.push_str("%0A"), + ':' => result.push_str("%3A"), + ',' => result.push_str("%2C"), + '%' => result.push_str("%25"), + _ => result.push(c), + } + } + result +} + +fn markup_to_string(markup: &MarkupBuf) -> Option { + let mut buffer = Vec::new(); + let mut write = fmt::Termcolor(termcolor::NoColor::new(&mut buffer)); + let mut fmt = fmt::Formatter::new(&mut write); + fmt.write_markup(markup! { {markup} }).ok()?; + String::from_utf8(buffer).ok() +} diff --git a/crates/biome_diagnostics/src/lib.rs b/crates/biome_diagnostics/src/lib.rs index 4f56bcec28f8..050e8b34dd06 100644 --- a/crates/biome_diagnostics/src/lib.rs +++ b/crates/biome_diagnostics/src/lib.rs @@ -7,6 +7,7 @@ pub mod advice; pub mod context; pub mod diagnostic; pub mod display; +pub mod display_github; pub mod error; pub mod location; pub mod panic; @@ -33,6 +34,7 @@ pub use crate::diagnostic::{Diagnostic, DiagnosticTags, Severity}; pub use crate::display::{ set_bottom_frame, Backtrace, MessageAndDescription, PrintDescription, PrintDiagnostic, }; +pub use crate::display_github::PrintGitHubDiagnostic; pub use crate::error::{Error, Result}; pub use crate::location::{LineIndex, LineIndexBuf, Location, Resource, SourceCode}; use biome_console::fmt::{Formatter, Termcolor}; diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index 3462ca8e2bf0..82e12dcda2f7 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -104,6 +104,7 @@ define_categories! { "lint/nursery/noDuplicateJsonKeys": "https://biomejs.dev/linter/rules/no-duplicate-json-keys", "lint/nursery/noEmptyBlockStatements": "https://biomejs.dev/linter/rules/no-empty-block-statements", "lint/nursery/noImplicitAnyLet": "https://biomejs.dev/lint/rules/no-implicit-any-let", + "lint/nursery/noMisleadingCharacterClass": "https://biomejs.dev/linter/rules/no-misleading-character-class", "lint/nursery/noUnusedImports": "https://biomejs.dev/linter/rules/no-unused-imports", "lint/nursery/noUnusedPrivateClassMembers": "https://biomejs.dev/linter/rules/no-unused-private-class-members", "lint/nursery/noUselessLoneBlockStatements": "https://biomejs.dev/linter/rules/no-useless-lone-block-statements", @@ -114,6 +115,7 @@ define_categories! { "lint/nursery/useImportRestrictions": "https://biomejs.dev/linter/rules/use-import-restrictions", "lint/nursery/useRegexLiterals": "https://biomejs.dev/linter/rules/use-regex-literals", "lint/nursery/useValidAriaRole": "https://biomejs.dev/lint/rules/use-valid-aria-role", + "lint/nursery/useShorthandFunctionType": "https://biomejs.dev/lint/rules/use-shorthand-function-type", "lint/performance/noAccumulatingSpread": "https://biomejs.dev/linter/rules/no-accumulating-spread", "lint/performance/noDelete": "https://biomejs.dev/linter/rules/no-delete", "lint/security/noDangerouslySetInnerHtml": "https://biomejs.dev/linter/rules/no-dangerously-set-inner-html", diff --git a/crates/biome_js_analyze/Cargo.toml b/crates/biome_js_analyze/Cargo.toml index d2eeeb4fd447..873994629913 100644 --- a/crates/biome_js_analyze/Cargo.toml +++ b/crates/biome_js_analyze/Cargo.toml @@ -26,6 +26,7 @@ biome_json_syntax = { workspace = true } biome_rowan = { workspace = true } bpaf.workspace = true lazy_static = { workspace = true } +log = "0.4.20" natord = "1.0.9" roaring = "0.10.1" rustc-hash = { workspace = true } diff --git a/crates/biome_js_analyze/src/analyzers/nursery.rs b/crates/biome_js_analyze/src/analyzers/nursery.rs index f9b06e33a0f1..62595b0b840e 100644 --- a/crates/biome_js_analyze/src/analyzers/nursery.rs +++ b/crates/biome_js_analyze/src/analyzers/nursery.rs @@ -11,6 +11,7 @@ pub(crate) mod use_await; pub(crate) mod use_grouped_type_import; pub(crate) mod use_import_restrictions; pub(crate) mod use_regex_literals; +pub(crate) mod use_shorthand_function_type; declare_group! { pub (crate) Nursery { @@ -25,6 +26,7 @@ declare_group! { self :: use_grouped_type_import :: UseGroupedTypeImport , self :: use_import_restrictions :: UseImportRestrictions , self :: use_regex_literals :: UseRegexLiterals , + self :: use_shorthand_function_type :: UseShorthandFunctionType , ] } } diff --git a/crates/biome_js_analyze/src/analyzers/nursery/use_shorthand_function_type.rs b/crates/biome_js_analyze/src/analyzers/nursery/use_shorthand_function_type.rs new file mode 100644 index 000000000000..5da49c3d3641 --- /dev/null +++ b/crates/biome_js_analyze/src/analyzers/nursery/use_shorthand_function_type.rs @@ -0,0 +1,193 @@ +use crate::JsRuleAction; +use biome_analyze::{ + context::RuleContext, declare_rule, ActionCategory, Ast, FixKind, Rule, RuleDiagnostic, +}; +use biome_console::markup; +use biome_diagnostics::Applicability; +use biome_js_factory::make; +use biome_js_factory::make::ts_type_alias_declaration; +use biome_js_syntax::AnyTsType::TsThisType; +use biome_js_syntax::{ + AnyJsDeclarationClause, AnyTsReturnType, AnyTsType, TsCallSignatureTypeMember, TsFunctionType, + TsInterfaceDeclaration, TsObjectType, TsTypeMemberList, T, +}; +use biome_rowan::{AstNode, AstNodeList, BatchMutationExt, TriviaPieceKind}; + +declare_rule! { + /// Enforce using function types instead of object type with call signatures. + /// + /// TypeScript allows for two common ways to declare a type for a function: + /// + /// - Function type: `() => string` + /// - Object type with a signature: `{ (): string }` + /// + /// The function type form is generally preferred when possible for being more succinct. + /// + /// This rule suggests using a function type instead of an interface or object type literal with a single call signature. + /// + /// Source: https://typescript-eslint.io/rules/prefer-function-type/ + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```ts,expect_diagnostic + /// interface Example { + /// (): string; + /// } + /// ``` + /// + /// ```ts,expect_diagnostic + /// function foo(example: { (): number }): number { + /// return example(); + /// } + /// ``` + /// + /// ## Valid + /// + /// ```ts + /// type Example = () => string; + /// ``` + /// + /// ```ts + /// function foo(example: () => number): number { + /// return bar(); + /// } + /// ``` + /// + /// ```ts + /// // returns the function itself, not the `this` argument. + /// type ReturnsSelf2 = (arg: string) => ReturnsSelf; + /// ``` + /// + /// ```ts + /// interface Foo { + /// bar: string; + /// } + /// interface Bar extends Foo { + /// (): void; + /// } + /// ``` + /// + /// ```ts + /// // multiple call signatures (overloads) is allowed: + /// interface Overloaded { + /// (data: string): number; + /// (id: number): string; + /// } + /// // this is equivalent to Overloaded interface. + /// type Intersection = ((data: string) => number) & ((id: number) => string); + ///``` + /// + pub(crate) UseShorthandFunctionType { + version: "next", + name: "useShorthandFunctionType", + recommended: false, + fix_kind: FixKind::Safe, + } +} + +impl Rule for UseShorthandFunctionType { + type Query = Ast; + type State = (); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let query = ctx.query(); + + if let Some(ts_type_member_list) = query.parent::() { + // If there is more than one member, it's not a single call signature. + if ts_type_member_list.len() > 1 { + return None; + } + // If the parent is an interface with an extends clause, it's not a single call signature. + if let Some(interface_decl) = ts_type_member_list.parent::() { + if interface_decl.extends_clause().is_some() { + return None; + } + + if let AnyTsReturnType::AnyTsType(TsThisType(_)) = + query.return_type_annotation()?.ty().ok()? + { + return None; + } + } + return Some(()); + } + + None + } + + fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + Some(RuleDiagnostic::new(rule_category!(), ctx.query().range(), markup! { + "Use a function type instead of a call signature." + }).note(markup! { "Types containing only a call signature can be shortened to a function type." })) + } + + fn action(ctx: &RuleContext, _: &Self::State) -> Option { + let node = ctx.query(); + let mut mutation = ctx.root().begin(); + + let ts_type_member_list = node.parent::()?; + + if let Some(interface_decl) = ts_type_member_list.parent::() { + let type_alias_declaration = ts_type_alias_declaration( + make::token(T![type]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), + interface_decl.id().ok()?, + make::token(T![=]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), + AnyTsType::from(convert_ts_call_signature_type_member_to_function_type( + node, + )?), + ) + .build(); + + mutation.replace_node( + AnyJsDeclarationClause::from(interface_decl), + AnyJsDeclarationClause::from(type_alias_declaration), + ); + + return Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::Always, + message: markup! { "Alias a function type instead of using an interface with a call signature." }.to_owned(), + mutation, + }); + } + + if let Some(ts_object_type) = ts_type_member_list.parent::() { + let new_function_type = convert_ts_call_signature_type_member_to_function_type(node)?; + + mutation.replace_node( + AnyTsType::from(ts_object_type), + AnyTsType::from(new_function_type), + ); + + return Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::Always, + message: markup! { "Use a function type instead of an object type with a call signature." }.to_owned(), + mutation, + }); + } + + None + } +} + +fn convert_ts_call_signature_type_member_to_function_type( + node: &TsCallSignatureTypeMember, +) -> Option { + let new_node = make::ts_function_type( + make::js_parameters( + make::token(T!['(']), + node.parameters().ok()?.items(), + make::token(T![')']).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), + ), + make::token(T![=>]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), + node.return_type_annotation()?.ty().ok()?, + ) + .build(); + + Some(new_node.with_type_parameters(node.type_parameters())) +} diff --git a/crates/biome_js_analyze/src/react/hooks.rs b/crates/biome_js_analyze/src/react/hooks.rs index bba7e3e329e0..a3c505424245 100644 --- a/crates/biome_js_analyze/src/react/hooks.rs +++ b/crates/biome_js_analyze/src/react/hooks.rs @@ -209,6 +209,7 @@ impl StableReactHookConfiguration { /// ``` pub fn is_binding_react_stable( binding: &AnyJsIdentifierBinding, + model: &SemanticModel, stable_config: &FxHashSet, ) -> bool { fn array_binding_declarator_index( @@ -232,26 +233,22 @@ pub fn is_binding_react_stable( array_binding_declarator_index(binding) .or_else(|| assignment_declarator(binding)) .and_then(|(declarator, index)| { - let hook_name = declarator + let callee = declarator .initializer()? .expression() .ok()? .as_js_call_expression()? .callee() - .ok()? - .as_js_identifier_expression()? - .name() - .ok()? - .value_token() - .ok()? - .token_text_trimmed(); + .ok()?; - let stable = StableReactHookConfiguration { - hook_name: hook_name.to_string(), - index, - }; - - Some(stable_config.contains(&stable)) + Some(stable_config.iter().any(|config| { + is_react_call_api( + callee.clone(), + model, + ReactLibrary::React, + &config.hook_name, + ) && index == config.index + })) }) .unwrap_or(false) } @@ -260,12 +257,46 @@ pub fn is_binding_react_stable( mod test { use super::*; use biome_js_parser::JsParserOptions; + use biome_js_semantic::{semantic_model, SemanticModelOptions}; use biome_js_syntax::JsFileSource; #[test] pub fn ok_react_stable_captures() { let r = biome_js_parser::parse( - "const ref = useRef();", + r#" + import { useRef } from "react"; + const ref = useRef(); + "#, + JsFileSource::js_module(), + JsParserOptions::default(), + ); + let node = r + .syntax() + .descendants() + .filter(|x| x.text_trimmed() == "ref") + .last() + .unwrap(); + let set_name = AnyJsIdentifierBinding::cast(node).unwrap(); + + let config = FxHashSet::from_iter([ + StableReactHookConfiguration::new("useRef", None), + StableReactHookConfiguration::new("useState", Some(1)), + ]); + + assert!(is_binding_react_stable( + &set_name, + &semantic_model(&r.ok().unwrap(), SemanticModelOptions::default()), + &config + )); + } + + #[test] + pub fn ok_react_stable_captures_with_default_import() { + let r = biome_js_parser::parse( + r#" + import * as React from "react"; + const ref = React.useRef(); + "#, JsFileSource::js_module(), JsParserOptions::default(), ); @@ -282,6 +313,10 @@ mod test { StableReactHookConfiguration::new("useState", Some(1)), ]); - assert!(is_binding_react_stable(&set_name, &config)); + assert!(is_binding_react_stable( + &set_name, + &semantic_model(&r.ok().unwrap(), SemanticModelOptions::default()), + &config + )); } } diff --git a/crates/biome_js_analyze/src/semantic_analyzers/correctness/use_exhaustive_dependencies.rs b/crates/biome_js_analyze/src/semantic_analyzers/correctness/use_exhaustive_dependencies.rs index 515a3017d7e5..5121618d7cfe 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/correctness/use_exhaustive_dependencies.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/correctness/use_exhaustive_dependencies.rs @@ -120,7 +120,7 @@ declare_rule! { /// ``` /// /// ```js - /// import { useEffect } from "react"; + /// import { useEffect, useState } from "react"; /// /// function component() { /// const [name, setName] = useState(); @@ -472,7 +472,8 @@ fn capture_needs_to_be_in_the_dependency_list( } // ... they are assign to stable returns of another React function - let not_stable = !is_binding_react_stable(&binding.tree(), &options.stable_config); + let not_stable = + !is_binding_react_stable(&binding.tree(), model, &options.stable_config); not_stable.then_some(capture) } diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs index 037aba6b8976..6d44f0a37463 100644 --- a/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery.rs @@ -2,6 +2,7 @@ use biome_analyze::declare_group; +pub(crate) mod no_misleading_character_class; pub(crate) mod no_unused_imports; pub(crate) mod use_for_of; @@ -9,6 +10,7 @@ declare_group! { pub (crate) Nursery { name : "nursery" , rules : [ + self :: no_misleading_character_class :: NoMisleadingCharacterClass , self :: no_unused_imports :: NoUnusedImports , self :: use_for_of :: UseForOf , ] diff --git a/crates/biome_js_analyze/src/semantic_analyzers/nursery/no_misleading_character_class.rs b/crates/biome_js_analyze/src/semantic_analyzers/nursery/no_misleading_character_class.rs new file mode 100644 index 000000000000..d0e8cffba819 --- /dev/null +++ b/crates/biome_js_analyze/src/semantic_analyzers/nursery/no_misleading_character_class.rs @@ -0,0 +1,646 @@ +use crate::{semantic_services::Semantic, JsRuleAction}; +use biome_analyze::{ + context::RuleContext, declare_rule, ActionCategory, FixKind, Rule, RuleDiagnostic, +}; +use biome_console::markup; +use biome_diagnostics::Applicability; +use biome_js_factory::make; +use biome_js_syntax::{ + AnyJsCallArgument, AnyJsExpression, AnyJsLiteralExpression, AnyJsTemplateElement, + JsCallArguments, JsCallExpression, JsNewExpression, JsRegexLiteralExpression, + JsStringLiteralExpression, JsSyntaxKind, JsSyntaxToken, T, +}; +use biome_rowan::{ + declare_node_union, AstNode, AstNodeList, AstSeparatedList, BatchMutationExt, TextRange, +}; +declare_rule! { + /// Disallow characters made with multiple code points in character class syntax. + /// + /// Unicode includes the characters which are made with multiple code points. e.g. Á, 🇯🇵, 👨‍👩‍👦. + /// A RegExp character class `/[abc]/` cannot handle characters with multiple code points. + /// For example, the character `❇️` consists of two code points: `❇` (U+2747) and `VARIATION SELECTOR-16` (U+FE0F). + /// If this character is in a RegExp character class, it will match to either `❇` or `VARIATION SELECTOR-16` rather than `❇️`. + /// This rule reports the regular expressions which include multiple code point characters in character class syntax. + /// + /// Source: https://eslint.org/docs/latest/rules/no-misleading-character-class + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// /^[Á]$/u; + /// ``` + /// + /// ```js,expect_diagnostic + /// /^[❇️]$/u; + /// ``` + /// + /// ```js,expect_diagnostic + /// /^[👶🏻]$/u; + /// ``` + /// + /// ```js,expect_diagnostic + /// /^[🇯🇵]$/u; + /// ``` + /// + /// ```js,expect_diagnostic + /// /^[👨‍👩‍👦]$/u; + /// ``` + /// + /// ```js,expect_diagnostic + /// /^[👍]$/; // surrogate pair without u flag + /// ``` + /// + /// ## Valid + /// + /// ```js + /// /^[abc]$/; + /// /^[👍]$/u; + /// /^[\q{👶🏻}]$/v; + /// ``` + /// + + pub(crate) NoMisleadingCharacterClass { + version: "next", + name: "noMisleadingCharacterClass", + recommended: false, + fix_kind: FixKind::Safe, + } +} + +declare_node_union! { + pub(crate) AnyRegexExpression = JsNewExpression | JsCallExpression | JsRegexLiteralExpression +} + +pub enum Message { + SurrogatePairWithoutUFlag, + EmojiModifier, + RegionalIndicatorSymbol, + CombiningClassOrVs16, + JoinedCharSequence, +} + +impl Message { + fn as_str(&self) -> &str { + match self { + Self::CombiningClassOrVs16 => "Unexpected combined character in the character class.", + Self::SurrogatePairWithoutUFlag => { + "Unexpected surrogate pair in character class. Use the 'u' flag." + } + Self::EmojiModifier => "Unexpected modified Emoji in the character class. ", + Self::RegionalIndicatorSymbol => { + "Regional indicator symbol characters should not be used in the character class." + } + Self::JoinedCharSequence => "Unexpected joined character sequence in character class.", + } + } +} + +pub struct RuleState { + range: TextRange, + message: Message, +} + +impl Rule for NoMisleadingCharacterClass { + type Query = Semantic; + type State = RuleState; + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let regex = ctx.query(); + + match regex { + AnyRegexExpression::JsRegexLiteralExpression(expr) => { + let Ok((pattern, flags)) = expr.decompose() else { + return None; + }; + + if flags.text().contains('v') { + return None; + } + let regex_pattern = replace_escaped_unicode(pattern.text()); + let has_u_flag = flags.text().contains('u'); + let range = expr.syntax().text_range(); + return diagnostic_regex_pattern(®ex_pattern, has_u_flag, range); + } + + AnyRegexExpression::JsNewExpression(expr) => { + let is_reg_exp = match expr.callee().ok()? { + AnyJsExpression::JsIdentifierExpression(callee) => { + callee.name().ok()?.has_name("RegExp") + } + AnyJsExpression::JsStaticMemberExpression(callee) => { + let is_global_this = match callee.object().ok()? { + AnyJsExpression::JsIdentifierExpression(e) => { + e.name().ok()?.has_name("globalThis") + } + _ => false, + }; + + is_global_this + && callee.member().ok()?.value_token().ok()?.text() == "RegExp" + } + _ => false, + }; + + if is_reg_exp { + let mut args = expr.arguments()?.args().iter(); + let raw_regex_pattern = args + .next() + .and_then(|arg| arg.ok()) + .and_then(|arg| JsStringLiteralExpression::cast_ref(arg.syntax())) + .and_then(|js_string_literal| js_string_literal.inner_string_text().ok())? + .to_string(); + + let regex_pattern = replace_escaped_unicode(raw_regex_pattern.as_str()); + let regexp_flags = args + .next() + .and_then(|arg| arg.ok()) + .and_then(|arg| JsStringLiteralExpression::cast_ref(arg.syntax())) + .map(|js_string_literal| js_string_literal.text()) + .unwrap_or_default(); + + if regexp_flags.contains('v') { + return None; + } + let has_u_flag = regexp_flags.contains('u'); + let range = expr.syntax().text_range(); + return diagnostic_regex_pattern(®ex_pattern, has_u_flag, range); + } + } + AnyRegexExpression::JsCallExpression(expr) => { + let callee = match expr.callee().ok()? { + AnyJsExpression::JsIdentifierExpression(callee) => callee, + _ => return None, + }; + if callee.name().ok()?.has_name("RegExp") { + let mut args = expr.arguments().ok()?.args().iter(); + let raw_regex_pattern = args + .next() + .and_then(|arg| arg.ok()) + .and_then(|arg| JsStringLiteralExpression::cast_ref(arg.syntax())) + .and_then(|js_string_literal| js_string_literal.inner_string_text().ok())? + .to_string(); + + let regex_pattern = replace_escaped_unicode(raw_regex_pattern.as_str()); + + let regexp_flags = args + .next() + .and_then(|arg| arg.ok()) + .and_then(|arg| JsStringLiteralExpression::cast_ref(arg.syntax())) + .map(|js_string_literal| js_string_literal.text()) + .unwrap_or_default(); + + if regexp_flags.contains('v') { + return None; + } + + let has_u_flag = regexp_flags.contains('u'); + let range = expr.syntax().text_range(); + return diagnostic_regex_pattern(®ex_pattern, has_u_flag, range); + } + } + } + None + } + + fn diagnostic(_ctx: &RuleContext, state: &Self::State) -> Option { + Some(RuleDiagnostic::new( + rule_category!(), + state.range, + state.message.as_str(), + )) + } + + fn action(ctx: &RuleContext, state: &Self::State) -> Option { + let node = ctx.query(); + let is_fixable = matches!(state.message, Message::SurrogatePairWithoutUFlag); + if is_fixable { + match node { + AnyRegexExpression::JsRegexLiteralExpression(expr) => { + let prev_token = expr.value_token().ok()?; + let text = prev_token.text(); + let next_token = JsSyntaxToken::new_detached( + JsSyntaxKind::JS_REGEX_LITERAL, + &format!("{}u", text), + [], + [], + ); + + let mut mutation = ctx.root().begin(); + mutation.replace_token(prev_token, next_token); + + Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::Always, + message: markup! { "Add unicode ""u"" flag to regex" } + .to_owned(), + mutation, + }) + } + + AnyRegexExpression::JsNewExpression(expr) => { + let prev_node = expr.arguments()?; + let mut prev_args = prev_node.args().iter(); + + let regex_pattern = prev_args.next().and_then(|a| a.ok())?; + let flag = prev_args.next().and_then(|a| a.ok()); + + match make_suggestion(regex_pattern, flag) { + Some(suggest) => { + let mut mutation = ctx.root().begin(); + mutation.replace_node(prev_node, suggest); + Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::Always, + message: markup! { "Add unicode ""u"" flag to regex" } + .to_owned(), + mutation, + }) + } + None => None, + } + } + + AnyRegexExpression::JsCallExpression(expr) => { + let prev_node = expr.arguments().ok()?; + let mut prev_args = expr.arguments().ok()?.args().iter(); + + let regex_pattern = prev_args.next().and_then(|a| a.ok())?; + let flag = prev_args.next().and_then(|a| a.ok()); + + match make_suggestion(regex_pattern, flag) { + Some(suggest) => { + let mut mutation = ctx.root().begin(); + mutation.replace_node(prev_node, suggest); + Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::Always, + message: markup! { "Add unicode ""u"" flag to regex" } + .to_owned(), + mutation, + }) + } + None => None, + } + } + } + } else { + None + } + } +} + +fn diagnostic_regex_pattern( + regex_pattern: &str, + has_u_flag: bool, + range: TextRange, +) -> Option { + let mut is_in_character_class = false; + let mut escape_next = false; + let char_iter = regex_pattern.chars().peekable(); + for (i, ch) in char_iter.enumerate() { + if escape_next { + escape_next = false; + continue; + } + match ch { + '\\' => escape_next = true, + '[' => is_in_character_class = true, + ']' => is_in_character_class = false, + _ if is_in_character_class && i < regex_pattern.len() => { + if !has_u_flag && has_surrogate_pair(®ex_pattern[i..]) { + return Some(RuleState { + range, + message: Message::SurrogatePairWithoutUFlag, + }); + } + + if has_combining_class_or_vs16(®ex_pattern[i..]) { + return Some(RuleState { + range, + message: Message::CombiningClassOrVs16, + }); + } + + if has_regional_indicator_symbol(®ex_pattern[i..]) { + return Some(RuleState { + range, + message: Message::RegionalIndicatorSymbol, + }); + } + + if has_emoji_modifier(®ex_pattern[i..]) { + return Some(RuleState { + range, + message: Message::EmojiModifier, + }); + } + + if zwj(®ex_pattern[i..]) { + return Some(RuleState { + range, + message: Message::JoinedCharSequence, + }); + } + } + _ => {} + } + } + None +} + +fn make_suggestion( + literal: AnyJsCallArgument, + flag: Option, +) -> Option { + let suggestion = match flag { + None => Some(AnyJsCallArgument::AnyJsExpression( + AnyJsExpression::AnyJsLiteralExpression( + AnyJsLiteralExpression::JsStringLiteralExpression( + make::js_string_literal_expression(make::js_string_literal("u")), + ), + ), + )), + Some(f) => match f { + AnyJsCallArgument::AnyJsExpression(expr) => match expr { + AnyJsExpression::AnyJsLiteralExpression(e) => { + let text = e.text(); + if text.starts_with('\'') { + Some(AnyJsCallArgument::AnyJsExpression( + AnyJsExpression::AnyJsLiteralExpression( + AnyJsLiteralExpression::JsStringLiteralExpression( + make::js_string_literal_expression(make::js_string_literal( + &format!("'{}u'", text), + )), + ), + ), + )) + } else { + Some(AnyJsCallArgument::AnyJsExpression( + AnyJsExpression::AnyJsLiteralExpression( + AnyJsLiteralExpression::JsStringLiteralExpression( + make::js_string_literal_expression(make::js_string_literal( + &format!("{}u", text.replace('"', "")), + )), + ), + ), + )) + } + } + AnyJsExpression::JsTemplateExpression(expr) => { + let mut elements = expr + .elements() + .iter() + .collect::>(); + + let uflag = AnyJsTemplateElement::from(make::js_template_chunk_element( + make::js_template_chunk("u"), + )); + elements.push(uflag); + Some(AnyJsCallArgument::AnyJsExpression( + AnyJsExpression::JsTemplateExpression( + make::js_template_expression( + make::token(T!['`']), + make::js_template_element_list(elements), + make::token(T!['`']), + ) + .build(), + ), + )) + } + AnyJsExpression::JsIdentifierExpression(_) => None, + _ => None, + }, + AnyJsCallArgument::JsSpread(_) => None, + }, + }; + + suggestion.map(|s| { + make::js_call_arguments( + make::token(T!['(']), + make::js_call_argument_list([literal, s], [make::token(T![,])]), + make::token(T![')']), + ) + }) +} + +fn is_emoji_modifier(code: u32) -> bool { + (0x1F3FB..=0x1F3FF).contains(&code) +} + +fn has_emoji_modifier(chars: &str) -> bool { + let char_vec: Vec = chars.chars().collect(); + + char_vec.iter().enumerate().any(|(i, &c)| { + i != 0 && is_emoji_modifier(c as u32) && !is_emoji_modifier(char_vec[i - 1] as u32) + }) +} + +fn is_regional_indicator_symbol(code: u32) -> bool { + (0x1F1E6..=0x1F1FF).contains(&code) +} + +fn has_regional_indicator_symbol(chars: &str) -> bool { + let char_vec: Vec = chars.chars().collect(); + + char_vec.iter().enumerate().any(|(i, &c)| { + i != 0 + && is_regional_indicator_symbol(c as u32) + && is_regional_indicator_symbol(char_vec[i - 1] as u32) + }) +} + +fn is_combining_character(ch: char) -> bool { + match ch { + '\u{0300}'..='\u{036F}' | // Combining Diacritical Marks + '\u{1AB0}'..='\u{1AFF}' | // Combining Diacritical Marks Extended + '\u{1DC0}'..='\u{1DFF}' | // Combining Diacritical Marks Supplement + '\u{20D0}'..='\u{20FF}' | // Combining Diacritical Marks for Symbols + '\u{FE20}'..='\u{FE2F}' // Combining Half Marks + => true, + _ => false + } +} + +fn is_variation_selector_16(ch: char) -> bool { + ('\u{FE00}'..='\u{FE0F}').contains(&ch) +} + +fn has_combining_class_or_vs16(chars: &str) -> bool { + chars.chars().enumerate().any(|(i, c)| { + i != 0 + && (is_combining_character(c) || is_variation_selector_16(c)) + && !(is_combining_character(chars.chars().nth(i - 1).unwrap()) + || is_variation_selector_16(chars.chars().nth(i - 1).unwrap())) + }) +} + +fn zwj(chars: &str) -> bool { + let char_vec: Vec = chars.chars().collect(); + let last_index = char_vec.len() - 1; + char_vec.iter().enumerate().any(|(i, &c)| { + i != 0 + && i != last_index + && c as u32 == 0x200D + && char_vec[i - 1] as u32 != 0x200D + && char_vec[i + 1] as u32 != 0x200D + }) +} + +fn has_surrogate_pair(s: &str) -> bool { + s.chars().any(|c| c as u32 > 0xFFFF) +} + +/// Convert unicode escape sequence string to unicode character +/// - unicode escape sequences: \u{XXXX} +/// - unicode escape sequences without parenthesis: \uXXXX +/// - surrogate pair: \uXXXX\uXXXX +/// If the unicode escape sequence is not valid, it will be treated as a simple string. +/// +/// ```example +/// \uD83D\uDC4D -> 👍 +/// \u0041\u0301 -> Á +/// \uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66 -> 👨‍👩‍👦 +/// \u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466} -> 👨‍👩‍👦 +/// \u899\uD83D\uDC4D -> \u899👍 +/// ```` +fn replace_escaped_unicode(input: &str) -> String { + let mut result = String::new(); + let mut chars_iter = input.chars().peekable(); + + while let Some(ch) = chars_iter.next() { + if ch == '\\' { + match handle_escape_sequence(&mut chars_iter) { + Some(unicode_char) => result.push_str(&unicode_char), + None => result.push(ch), + } + } else { + result.push(ch); + } + } + result +} + +fn handle_escape_sequence(chars_iter: &mut std::iter::Peekable) -> Option { + if chars_iter.peek() != Some(&'u') { + return None; + } + chars_iter.next(); + + if chars_iter.peek() == Some(&'{') { + handle_braced_escape_sequence(chars_iter) + } else { + handle_simple_or_surrogate_escape_sequence(chars_iter) + } +} + +fn handle_braced_escape_sequence( + chars_iter: &mut std::iter::Peekable, +) -> Option { + chars_iter.next(); + let mut codepoint_str = String::new(); + while let Some(&next_char) = chars_iter.peek() { + if next_char == '}' { + chars_iter.next(); + break; + } else { + codepoint_str.push(next_char); + chars_iter.next(); + } + } + u32::from_str_radix(&codepoint_str, 16) + .ok() + .and_then(char::from_u32) + .map(|c| c.to_string()) +} + +fn handle_simple_or_surrogate_escape_sequence( + chars_iter: &mut std::iter::Peekable, +) -> Option { + let mut invalid_pair = String::new(); + let mut high_surrogate_str = String::new(); + + for _ in 0..4 { + if let Some(&next_char) = chars_iter.peek() { + if next_char.is_ascii_hexdigit() { + high_surrogate_str.push(next_char); + chars_iter.next(); + } else { + // If the character is not a valid Unicode char, return as simple string. + return Some(format!("\\u{}", high_surrogate_str)); + } + } else { + // If not enough characters, return as if it were a simple string. + return Some(format!("\\u{}", high_surrogate_str)); + } + } + + if let Ok(high_surrogate) = u32::from_str_radix(&high_surrogate_str, 16) { + // Check if it is in the high surrogate range(0xD800-0xDBFF) in UTF-16. + if (0xD800..=0xDBFF).contains(&high_surrogate) { + // If we have a high surrogate, expect a low surrogate next + if chars_iter.next() == Some('\\') && chars_iter.next() == Some('u') { + let mut low_surrogate_str = String::new(); + for _ in 0..4 { + if let Some(next_char) = chars_iter.peek() { + if !next_char.is_ascii_hexdigit() { + // Return as a simple string + // - high surrogate on its own doesn't make sense + // - low surrogate is not a valid unicode codepoint + // e.g \uD83D\u333 + invalid_pair.push_str(&format!("\\u{}", high_surrogate_str)); + invalid_pair.push_str(&format!("\\u{}", low_surrogate_str)); + return Some(invalid_pair); + } + low_surrogate_str.push(*next_char); + chars_iter.next(); + } + } + if let Ok(low_surrogate) = u32::from_str_radix(&low_surrogate_str, 16) { + // Check if it is in the low surrogate range(0xDC00-0xDFFF) in UTF-16. + if (0xDC00..=0xDFFF).contains(&low_surrogate) { + // Calculate the codepoint from the surrogate pair + let codepoint = + ((high_surrogate - 0xD800) << 10) + (low_surrogate - 0xDC00) + 0x10000; + return char::from_u32(codepoint).map(|c| c.to_string()); + }; + } + } + } else { + match char::from_u32(high_surrogate) { + Some(c) => return Some(c.to_string()), + None => invalid_pair.push_str(&format!("\\u{}", high_surrogate_str)), + } + } + } + Some(invalid_pair) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_replace_escaped_unicode() { + assert_eq!(replace_escaped_unicode(r#"/[\uD83D\uDC4D]/"#), "/[👍]/"); + assert_eq!(replace_escaped_unicode(r#"/[\u0041\u0301]/"#), "/[Á]/"); + assert_eq!( + replace_escaped_unicode("/[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u"), + "/[👨‍👩‍👦]/u" + ); + assert_eq!( + replace_escaped_unicode(r#"/[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u"#), + "/[👨‍👩‍👦]/u" + ); + assert_eq!( + replace_escaped_unicode(r#"/[\u899\uD83D\uDC4D]/"#), + r#"/[\u899👍]/"# + ); + assert_eq!( + replace_escaped_unicode(r#"/[\u899\uD83D\u899\uDC4D]/"#), + r#"/[\u899\uD83D\u899\uDC4D]/"# + ); + } +} diff --git a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/missingDependenciesInvalid.js b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/missingDependenciesInvalid.js index 4d48fb914dfc..cabbf29b47ac 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/missingDependenciesInvalid.js +++ b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/missingDependenciesInvalid.js @@ -1,5 +1,16 @@ import React from "react"; -import { useEffect, useCallback, useMemo, useLayoutEffect, useInsertionEffect, useImperativeHandle } from "react"; +import { + useEffect, + useCallback, + useMemo, + useLayoutEffect, + useInsertionEffect, + useImperativeHandle, + useState, + useReducer, + useTransition, +} from "react"; +import { useRef } from "preact/hooks" function MyComponent1() { let a = 1; @@ -123,3 +134,22 @@ function MyComponent13() { console.log(a); }, []); } + +// imports from other libraries +function MyComponent14() { + const ref = useRef(); + useEffect(() => { + console.log(ref.current); + }, []); +} + +// local overrides +function MyComponent15() { + const useRef = () => { + return { current: 1 } + } + const ref = useRef(); + useEffect(() => { + console.log(ref.current); + }, []); +} diff --git a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/missingDependenciesInvalid.js.snap b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/missingDependenciesInvalid.js.snap index 01aa7d17f2d9..972516db330c 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/missingDependenciesInvalid.js.snap +++ b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/missingDependenciesInvalid.js.snap @@ -5,7 +5,18 @@ expression: missingDependenciesInvalid.js # Input ```js import React from "react"; -import { useEffect, useCallback, useMemo, useLayoutEffect, useInsertionEffect, useImperativeHandle } from "react"; +import { + useEffect, + useCallback, + useMemo, + useLayoutEffect, + useInsertionEffect, + useImperativeHandle, + useState, + useReducer, + useTransition, +} from "react"; +import { useRef } from "preact/hooks" function MyComponent1() { let a = 1; @@ -130,29 +141,48 @@ function MyComponent13() { }, []); } +// imports from other libraries +function MyComponent14() { + const ref = useRef(); + useEffect(() => { + console.log(ref.current); + }, []); +} + +// local overrides +function MyComponent15() { + const useRef = () => { + return { current: 1 } + } + const ref = useRef(); + useEffect(() => { + console.log(ref.current); + }, []); +} + ``` # Diagnostics ``` -missingDependenciesInvalid.js:7:5 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:18:5 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 5 │ let a = 1; - 6 │ const b = a + 1; - > 7 │ useEffect(() => { - │ ^^^^^^^^^ - 8 │ console.log(a, b); - 9 │ }, []); + 16 │ let a = 1; + 17 │ const b = a + 1; + > 18 │ useEffect(() => { + │ ^^^^^^^^^ + 19 │ console.log(a, b); + 20 │ }, []); i This dependency is not specified in the hook dependency list. - 6 │ const b = a + 1; - 7 │ useEffect(() => { - > 8 │ console.log(a, b); + 17 │ const b = a + 1; + 18 │ useEffect(() => { + > 19 │ console.log(a, b); │ ^ - 9 │ }, []); - 10 │ } + 20 │ }, []); + 21 │ } i Either include it or remove the dependency array @@ -160,25 +190,25 @@ missingDependenciesInvalid.js:7:5 lint/correctness/useExhaustiveDependencies ━ ``` ``` -missingDependenciesInvalid.js:7:5 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:18:5 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 5 │ let a = 1; - 6 │ const b = a + 1; - > 7 │ useEffect(() => { - │ ^^^^^^^^^ - 8 │ console.log(a, b); - 9 │ }, []); + 16 │ let a = 1; + 17 │ const b = a + 1; + > 18 │ useEffect(() => { + │ ^^^^^^^^^ + 19 │ console.log(a, b); + 20 │ }, []); i This dependency is not specified in the hook dependency list. - 6 │ const b = a + 1; - 7 │ useEffect(() => { - > 8 │ console.log(a, b); + 17 │ const b = a + 1; + 18 │ useEffect(() => { + > 19 │ console.log(a, b); │ ^ - 9 │ }, []); - 10 │ } + 20 │ }, []); + 21 │ } i Either include it or remove the dependency array @@ -186,25 +216,25 @@ missingDependenciesInvalid.js:7:5 lint/correctness/useExhaustiveDependencies ━ ``` ``` -missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:32:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 19 │ const deferredValue = useDeferredValue(value); - 20 │ const [isPending, startTransition] = useTransition(); - > 21 │ useEffect(() => { + 30 │ const deferredValue = useDeferredValue(value); + 31 │ const [isPending, startTransition] = useTransition(); + > 32 │ useEffect(() => { │ ^^^^^^^^^ - 22 │ console.log(name); - 23 │ setName(1); + 33 │ console.log(name); + 34 │ setName(1); i This dependency is not specified in the hook dependency list. - 28 │ console.log(memoizedCallback); - 29 │ console.log(memoizedValue); - > 30 │ console.log(deferredValue); + 39 │ console.log(memoizedCallback); + 40 │ console.log(memoizedValue); + > 41 │ console.log(deferredValue); │ ^^^^^^^^^^^^^ - 31 │ - 32 │ console.log(isPending); + 42 │ + 43 │ console.log(isPending); i Either include it or remove the dependency array @@ -212,25 +242,25 @@ missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:32:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 19 │ const deferredValue = useDeferredValue(value); - 20 │ const [isPending, startTransition] = useTransition(); - > 21 │ useEffect(() => { + 30 │ const deferredValue = useDeferredValue(value); + 31 │ const [isPending, startTransition] = useTransition(); + > 32 │ useEffect(() => { │ ^^^^^^^^^ - 22 │ console.log(name); - 23 │ setName(1); + 33 │ console.log(name); + 34 │ setName(1); i This dependency is not specified in the hook dependency list. - 26 │ dispatch(1); - 27 │ - > 28 │ console.log(memoizedCallback); + 37 │ dispatch(1); + 38 │ + > 39 │ console.log(memoizedCallback); │ ^^^^^^^^^^^^^^^^ - 29 │ console.log(memoizedValue); - 30 │ console.log(deferredValue); + 40 │ console.log(memoizedValue); + 41 │ console.log(deferredValue); i Either include it or remove the dependency array @@ -238,25 +268,25 @@ missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:32:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 19 │ const deferredValue = useDeferredValue(value); - 20 │ const [isPending, startTransition] = useTransition(); - > 21 │ useEffect(() => { + 30 │ const deferredValue = useDeferredValue(value); + 31 │ const [isPending, startTransition] = useTransition(); + > 32 │ useEffect(() => { │ ^^^^^^^^^ - 22 │ console.log(name); - 23 │ setName(1); + 33 │ console.log(name); + 34 │ setName(1); i This dependency is not specified in the hook dependency list. - 23 │ setName(1); - 24 │ - > 25 │ console.log(state); + 34 │ setName(1); + 35 │ + > 36 │ console.log(state); │ ^^^^^ - 26 │ dispatch(1); - 27 │ + 37 │ dispatch(1); + 38 │ i Either include it or remove the dependency array @@ -264,25 +294,25 @@ missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:32:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 19 │ const deferredValue = useDeferredValue(value); - 20 │ const [isPending, startTransition] = useTransition(); - > 21 │ useEffect(() => { + 30 │ const deferredValue = useDeferredValue(value); + 31 │ const [isPending, startTransition] = useTransition(); + > 32 │ useEffect(() => { │ ^^^^^^^^^ - 22 │ console.log(name); - 23 │ setName(1); + 33 │ console.log(name); + 34 │ setName(1); i This dependency is not specified in the hook dependency list. - 20 │ const [isPending, startTransition] = useTransition(); - 21 │ useEffect(() => { - > 22 │ console.log(name); + 31 │ const [isPending, startTransition] = useTransition(); + 32 │ useEffect(() => { + > 33 │ console.log(name); │ ^^^^ - 23 │ setName(1); - 24 │ + 34 │ setName(1); + 35 │ i Either include it or remove the dependency array @@ -290,25 +320,25 @@ missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:32:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 19 │ const deferredValue = useDeferredValue(value); - 20 │ const [isPending, startTransition] = useTransition(); - > 21 │ useEffect(() => { + 30 │ const deferredValue = useDeferredValue(value); + 31 │ const [isPending, startTransition] = useTransition(); + > 32 │ useEffect(() => { │ ^^^^^^^^^ - 22 │ console.log(name); - 23 │ setName(1); + 33 │ console.log(name); + 34 │ setName(1); i This dependency is not specified in the hook dependency list. - 30 │ console.log(deferredValue); - 31 │ - > 32 │ console.log(isPending); + 41 │ console.log(deferredValue); + 42 │ + > 43 │ console.log(isPending); │ ^^^^^^^^^ - 33 │ startTransition(); - 34 │ }, []); + 44 │ startTransition(); + 45 │ }, []); i Either include it or remove the dependency array @@ -316,24 +346,24 @@ missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:32:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 19 │ const deferredValue = useDeferredValue(value); - 20 │ const [isPending, startTransition] = useTransition(); - > 21 │ useEffect(() => { + 30 │ const deferredValue = useDeferredValue(value); + 31 │ const [isPending, startTransition] = useTransition(); + > 32 │ useEffect(() => { │ ^^^^^^^^^ - 22 │ console.log(name); - 23 │ setName(1); + 33 │ console.log(name); + 34 │ setName(1); i This dependency is not specified in the hook dependency list. - 28 │ console.log(memoizedCallback); - > 29 │ console.log(memoizedValue); + 39 │ console.log(memoizedCallback); + > 40 │ console.log(memoizedValue); │ ^^^^^^^^^^^^^ - 30 │ console.log(deferredValue); - 31 │ + 41 │ console.log(deferredValue); + 42 │ i Either include it or remove the dependency array @@ -341,25 +371,25 @@ missingDependenciesInvalid.js:21:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:41:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:52:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 39 │ function MyComponent3() { - 40 │ let a = 1; - > 41 │ useEffect(() => console.log(a), []); + 50 │ function MyComponent3() { + 51 │ let a = 1; + > 52 │ useEffect(() => console.log(a), []); │ ^^^^^^^^^ - 42 │ useCallback(() => console.log(a), []); - 43 │ useMemo(() => console.log(a), []); + 53 │ useCallback(() => console.log(a), []); + 54 │ useMemo(() => console.log(a), []); i This dependency is not specified in the hook dependency list. - 39 │ function MyComponent3() { - 40 │ let a = 1; - > 41 │ useEffect(() => console.log(a), []); + 50 │ function MyComponent3() { + 51 │ let a = 1; + > 52 │ useEffect(() => console.log(a), []); │ ^ - 42 │ useCallback(() => console.log(a), []); - 43 │ useMemo(() => console.log(a), []); + 53 │ useCallback(() => console.log(a), []); + 54 │ useMemo(() => console.log(a), []); i Either include it or remove the dependency array @@ -367,25 +397,25 @@ missingDependenciesInvalid.js:41:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:42:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:53:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 40 │ let a = 1; - 41 │ useEffect(() => console.log(a), []); - > 42 │ useCallback(() => console.log(a), []); + 51 │ let a = 1; + 52 │ useEffect(() => console.log(a), []); + > 53 │ useCallback(() => console.log(a), []); │ ^^^^^^^^^^^ - 43 │ useMemo(() => console.log(a), []); - 44 │ useImperativeHandle(ref, () => console.log(a), []); + 54 │ useMemo(() => console.log(a), []); + 55 │ useImperativeHandle(ref, () => console.log(a), []); i This dependency is not specified in the hook dependency list. - 40 │ let a = 1; - 41 │ useEffect(() => console.log(a), []); - > 42 │ useCallback(() => console.log(a), []); + 51 │ let a = 1; + 52 │ useEffect(() => console.log(a), []); + > 53 │ useCallback(() => console.log(a), []); │ ^ - 43 │ useMemo(() => console.log(a), []); - 44 │ useImperativeHandle(ref, () => console.log(a), []); + 54 │ useMemo(() => console.log(a), []); + 55 │ useImperativeHandle(ref, () => console.log(a), []); i Either include it or remove the dependency array @@ -393,25 +423,25 @@ missingDependenciesInvalid.js:42:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:43:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:54:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 41 │ useEffect(() => console.log(a), []); - 42 │ useCallback(() => console.log(a), []); - > 43 │ useMemo(() => console.log(a), []); + 52 │ useEffect(() => console.log(a), []); + 53 │ useCallback(() => console.log(a), []); + > 54 │ useMemo(() => console.log(a), []); │ ^^^^^^^ - 44 │ useImperativeHandle(ref, () => console.log(a), []); - 45 │ useLayoutEffect(() => console.log(a), []); + 55 │ useImperativeHandle(ref, () => console.log(a), []); + 56 │ useLayoutEffect(() => console.log(a), []); i This dependency is not specified in the hook dependency list. - 41 │ useEffect(() => console.log(a), []); - 42 │ useCallback(() => console.log(a), []); - > 43 │ useMemo(() => console.log(a), []); + 52 │ useEffect(() => console.log(a), []); + 53 │ useCallback(() => console.log(a), []); + > 54 │ useMemo(() => console.log(a), []); │ ^ - 44 │ useImperativeHandle(ref, () => console.log(a), []); - 45 │ useLayoutEffect(() => console.log(a), []); + 55 │ useImperativeHandle(ref, () => console.log(a), []); + 56 │ useLayoutEffect(() => console.log(a), []); i Either include it or remove the dependency array @@ -419,25 +449,25 @@ missingDependenciesInvalid.js:43:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:44:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:55:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 42 │ useCallback(() => console.log(a), []); - 43 │ useMemo(() => console.log(a), []); - > 44 │ useImperativeHandle(ref, () => console.log(a), []); + 53 │ useCallback(() => console.log(a), []); + 54 │ useMemo(() => console.log(a), []); + > 55 │ useImperativeHandle(ref, () => console.log(a), []); │ ^^^^^^^^^^^^^^^^^^^ - 45 │ useLayoutEffect(() => console.log(a), []); - 46 │ useInsertionEffect(() => console.log(a), []); + 56 │ useLayoutEffect(() => console.log(a), []); + 57 │ useInsertionEffect(() => console.log(a), []); i This dependency is not specified in the hook dependency list. - 42 │ useCallback(() => console.log(a), []); - 43 │ useMemo(() => console.log(a), []); - > 44 │ useImperativeHandle(ref, () => console.log(a), []); + 53 │ useCallback(() => console.log(a), []); + 54 │ useMemo(() => console.log(a), []); + > 55 │ useImperativeHandle(ref, () => console.log(a), []); │ ^ - 45 │ useLayoutEffect(() => console.log(a), []); - 46 │ useInsertionEffect(() => console.log(a), []); + 56 │ useLayoutEffect(() => console.log(a), []); + 57 │ useInsertionEffect(() => console.log(a), []); i Either include it or remove the dependency array @@ -445,25 +475,25 @@ missingDependenciesInvalid.js:44:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:45:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:56:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 43 │ useMemo(() => console.log(a), []); - 44 │ useImperativeHandle(ref, () => console.log(a), []); - > 45 │ useLayoutEffect(() => console.log(a), []); + 54 │ useMemo(() => console.log(a), []); + 55 │ useImperativeHandle(ref, () => console.log(a), []); + > 56 │ useLayoutEffect(() => console.log(a), []); │ ^^^^^^^^^^^^^^^ - 46 │ useInsertionEffect(() => console.log(a), []); - 47 │ } + 57 │ useInsertionEffect(() => console.log(a), []); + 58 │ } i This dependency is not specified in the hook dependency list. - 43 │ useMemo(() => console.log(a), []); - 44 │ useImperativeHandle(ref, () => console.log(a), []); - > 45 │ useLayoutEffect(() => console.log(a), []); + 54 │ useMemo(() => console.log(a), []); + 55 │ useImperativeHandle(ref, () => console.log(a), []); + > 56 │ useLayoutEffect(() => console.log(a), []); │ ^ - 46 │ useInsertionEffect(() => console.log(a), []); - 47 │ } + 57 │ useInsertionEffect(() => console.log(a), []); + 58 │ } i Either include it or remove the dependency array @@ -471,25 +501,25 @@ missingDependenciesInvalid.js:45:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:46:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:57:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 44 │ useImperativeHandle(ref, () => console.log(a), []); - 45 │ useLayoutEffect(() => console.log(a), []); - > 46 │ useInsertionEffect(() => console.log(a), []); + 55 │ useImperativeHandle(ref, () => console.log(a), []); + 56 │ useLayoutEffect(() => console.log(a), []); + > 57 │ useInsertionEffect(() => console.log(a), []); │ ^^^^^^^^^^^^^^^^^^ - 47 │ } - 48 │ + 58 │ } + 59 │ i This dependency is not specified in the hook dependency list. - 44 │ useImperativeHandle(ref, () => console.log(a), []); - 45 │ useLayoutEffect(() => console.log(a), []); - > 46 │ useInsertionEffect(() => console.log(a), []); + 55 │ useImperativeHandle(ref, () => console.log(a), []); + 56 │ useLayoutEffect(() => console.log(a), []); + > 57 │ useInsertionEffect(() => console.log(a), []); │ ^ - 47 │ } - 48 │ + 58 │ } + 59 │ i Either include it or remove the dependency array @@ -497,25 +527,25 @@ missingDependenciesInvalid.js:46:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:53:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:64:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 51 │ function MyComponent4() { - 52 │ let a = 1; - > 53 │ useEffect(() => { + 62 │ function MyComponent4() { + 63 │ let a = 1; + > 64 │ useEffect(() => { │ ^^^^^^^^^ - 54 │ return () => console.log(a) - 55 │ }, []); + 65 │ return () => console.log(a) + 66 │ }, []); i This dependency is not specified in the hook dependency list. - 52 │ let a = 1; - 53 │ useEffect(() => { - > 54 │ return () => console.log(a) + 63 │ let a = 1; + 64 │ useEffect(() => { + > 65 │ return () => console.log(a) │ ^ - 55 │ }, []); - 56 │ } + 66 │ }, []); + 67 │ } i Either include it or remove the dependency array @@ -523,34 +553,34 @@ missingDependenciesInvalid.js:53:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:62:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:73:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 60 │ function MyComponent5() { - 61 │ let a = 1; - > 62 │ useEffect(() => { + 71 │ function MyComponent5() { + 72 │ let a = 1; + > 73 │ useEffect(() => { │ ^^^^^^^^^ - 63 │ console.log(a); - 64 │ return () => console.log(a); + 74 │ console.log(a); + 75 │ return () => console.log(a); i This dependency is not specified in the hook dependency list. - 61 │ let a = 1; - 62 │ useEffect(() => { - > 63 │ console.log(a); + 72 │ let a = 1; + 73 │ useEffect(() => { + > 74 │ console.log(a); │ ^ - 64 │ return () => console.log(a); - 65 │ }, []); + 75 │ return () => console.log(a); + 76 │ }, []); i This dependency is not specified in the hook dependency list. - 62 │ useEffect(() => { - 63 │ console.log(a); - > 64 │ return () => console.log(a); + 73 │ useEffect(() => { + 74 │ console.log(a); + > 75 │ return () => console.log(a); │ ^ - 65 │ }, []); - 66 │ } + 76 │ }, []); + 77 │ } i Either include them or remove the dependency array @@ -558,25 +588,25 @@ missingDependenciesInvalid.js:62:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:72:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:83:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 70 │ function MyComponent6() { - 71 │ let someObj = getObj(); - > 72 │ useEffect(() => { + 81 │ function MyComponent6() { + 82 │ let someObj = getObj(); + > 83 │ useEffect(() => { │ ^^^^^^^^^ - 73 │ console.log(someObj.name) - 74 │ }, []); + 84 │ console.log(someObj.name) + 85 │ }, []); i This dependency is not specified in the hook dependency list. - 71 │ let someObj = getObj(); - 72 │ useEffect(() => { - > 73 │ console.log(someObj.name) + 82 │ let someObj = getObj(); + 83 │ useEffect(() => { + > 84 │ console.log(someObj.name) │ ^^^^^^^^^^^^ - 74 │ }, []); - 75 │ } + 85 │ }, []); + 86 │ } i Either include it or remove the dependency array @@ -584,24 +614,24 @@ missingDependenciesInvalid.js:72:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:78:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:89:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 77 │ const MyComponent7 = React.memo(function ({ a }) { - > 78 │ useEffect(() => { + 88 │ const MyComponent7 = React.memo(function ({ a }) { + > 89 │ useEffect(() => { │ ^^^^^^^^^ - 79 │ console.log(a); - 80 │ }, []); + 90 │ console.log(a); + 91 │ }, []); i This dependency is not specified in the hook dependency list. - 77 │ const MyComponent7 = React.memo(function ({ a }) { - 78 │ useEffect(() => { - > 79 │ console.log(a); + 88 │ const MyComponent7 = React.memo(function ({ a }) { + 89 │ useEffect(() => { + > 90 │ console.log(a); │ ^ - 80 │ }, []); - 81 │ }); + 91 │ }, []); + 92 │ }); i Either include it or remove the dependency array @@ -609,24 +639,24 @@ missingDependenciesInvalid.js:78:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:84:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:95:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 83 │ const MyComponent8 = React.memo(({ a }) => { - > 84 │ useEffect(() => { + 94 │ const MyComponent8 = React.memo(({ a }) => { + > 95 │ useEffect(() => { │ ^^^^^^^^^ - 85 │ console.log(a); - 86 │ }, []); + 96 │ console.log(a); + 97 │ }, []); i This dependency is not specified in the hook dependency list. - 83 │ const MyComponent8 = React.memo(({ a }) => { - 84 │ useEffect(() => { - > 85 │ console.log(a); + 94 │ const MyComponent8 = React.memo(({ a }) => { + 95 │ useEffect(() => { + > 96 │ console.log(a); │ ^ - 86 │ }, []); - 87 │ }); + 97 │ }, []); + 98 │ }); i Either include it or remove the dependency array @@ -634,25 +664,25 @@ missingDependenciesInvalid.js:84:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:92:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:103:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 90 │ export function MyComponent9() { - 91 │ let a = 1; - > 92 │ useEffect(() => { - │ ^^^^^^^^^ - 93 │ console.log(a); - 94 │ }, []); + 101 │ export function MyComponent9() { + 102 │ let a = 1; + > 103 │ useEffect(() => { + │ ^^^^^^^^^ + 104 │ console.log(a); + 105 │ }, []); i This dependency is not specified in the hook dependency list. - 91 │ let a = 1; - 92 │ useEffect(() => { - > 93 │ console.log(a); - │ ^ - 94 │ }, []); - 95 │ } + 102 │ let a = 1; + 103 │ useEffect(() => { + > 104 │ console.log(a); + │ ^ + 105 │ }, []); + 106 │ } i Either include it or remove the dependency array @@ -660,25 +690,25 @@ missingDependenciesInvalid.js:92:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:99:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:110:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 97 │ export default function MyComponent10() { - 98 │ let a = 1; - > 99 │ useEffect(() => { + 108 │ export default function MyComponent10() { + 109 │ let a = 1; + > 110 │ useEffect(() => { │ ^^^^^^^^^ - 100 │ console.log(a); - 101 │ }, []); + 111 │ console.log(a); + 112 │ }, []); i This dependency is not specified in the hook dependency list. - 98 │ let a = 1; - 99 │ useEffect(() => { - > 100 │ console.log(a); + 109 │ let a = 1; + 110 │ useEffect(() => { + > 111 │ console.log(a); │ ^ - 101 │ }, []); - 102 │ } + 112 │ }, []); + 113 │ } i Either include it or remove the dependency array @@ -686,25 +716,25 @@ missingDependenciesInvalid.js:99:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:107:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:118:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 105 │ function MyComponent11() { - 106 │ let a = 1; - > 107 │ useEffect(function inner() { + 116 │ function MyComponent11() { + 117 │ let a = 1; + > 118 │ useEffect(function inner() { │ ^^^^^^^^^ - 108 │ console.log(a); - 109 │ }, []); + 119 │ console.log(a); + 120 │ }, []); i This dependency is not specified in the hook dependency list. - 106 │ let a = 1; - 107 │ useEffect(function inner() { - > 108 │ console.log(a); + 117 │ let a = 1; + 118 │ useEffect(function inner() { + > 119 │ console.log(a); │ ^ - 109 │ }, []); - 110 │ } + 120 │ }, []); + 121 │ } i Either include it or remove the dependency array @@ -712,25 +742,25 @@ missingDependenciesInvalid.js:107:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:114:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:125:3 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 112 │ function MyComponent12() { - 113 │ let a = 1; - > 114 │ useEffect(async function inner() { + 123 │ function MyComponent12() { + 124 │ let a = 1; + > 125 │ useEffect(async function inner() { │ ^^^^^^^^^ - 115 │ console.log(a); - 116 │ }, []); + 126 │ console.log(a); + 127 │ }, []); i This dependency is not specified in the hook dependency list. - 113 │ let a = 1; - 114 │ useEffect(async function inner() { - > 115 │ console.log(a); + 124 │ let a = 1; + 125 │ useEffect(async function inner() { + > 126 │ console.log(a); │ ^ - 116 │ }, []); - 117 │ } + 127 │ }, []); + 128 │ } i Either include it or remove the dependency array @@ -738,25 +768,77 @@ missingDependenciesInvalid.js:114:3 lint/correctness/useExhaustiveDependencies ``` ``` -missingDependenciesInvalid.js:122:9 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ +missingDependenciesInvalid.js:133:9 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ ! This hook does not specify all of its dependencies. - 120 │ function MyComponent13() { - 121 │ let a = 1; - > 122 │ React.useEffect(() => { + 131 │ function MyComponent13() { + 132 │ let a = 1; + > 133 │ React.useEffect(() => { │ ^^^^^^^^^ - 123 │ console.log(a); - 124 │ }, []); + 134 │ console.log(a); + 135 │ }, []); i This dependency is not specified in the hook dependency list. - 121 │ let a = 1; - 122 │ React.useEffect(() => { - > 123 │ console.log(a); + 132 │ let a = 1; + 133 │ React.useEffect(() => { + > 134 │ console.log(a); │ ^ - 124 │ }, []); - 125 │ } + 135 │ }, []); + 136 │ } + + i Either include it or remove the dependency array + + +``` + +``` +missingDependenciesInvalid.js:141:2 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ + + ! This hook does not specify all of its dependencies. + + 139 │ function MyComponent14() { + 140 │ const ref = useRef(); + > 141 │ useEffect(() => { + │ ^^^^^^^^^ + 142 │ console.log(ref.current); + 143 │ }, []); + + i This dependency is not specified in the hook dependency list. + + 140 │ const ref = useRef(); + 141 │ useEffect(() => { + > 142 │ console.log(ref.current); + │ ^^^^^^^^^^^ + 143 │ }, []); + 144 │ } + + i Either include it or remove the dependency array + + +``` + +``` +missingDependenciesInvalid.js:152:2 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━ + + ! This hook does not specify all of its dependencies. + + 150 │ } + 151 │ const ref = useRef(); + > 152 │ useEffect(() => { + │ ^^^^^^^^^ + 153 │ console.log(ref.current); + 154 │ }, []); + + i This dependency is not specified in the hook dependency list. + + 151 │ const ref = useRef(); + 152 │ useEffect(() => { + > 153 │ console.log(ref.current); + │ ^^^^^^^^^^^ + 154 │ }, []); + 155 │ } i Either include it or remove the dependency array diff --git a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js index 5699230c7baa..26d9d740b749 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js +++ b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js @@ -1,7 +1,19 @@ /* should not generate diagnostics */ import React from "react"; -import { useEffect, useSyncExternalStore, useMemo } from "react"; +import { + useEffect, + useSyncExternalStore, + useRef, + useState, + useContext, + useReducer, + useCallback, + useMemo, + useTransition, + useId, +} from "react"; +import { useRef as uR } from "react" import doSomething from 'a'; // No captures @@ -194,3 +206,20 @@ function MyComponent19() { console.log(a); }); } + +// Namespaced imports +// https://github.com/biomejs/biome/issues/578 +function MyComponent20() { + const ref = React.useRef() + React.useEffect(() => { + console.log(ref.current) + }, []) +} + +// Aliased imports +function MyComponent21() { + const ref = uR() + useEffect(() => { + console.log(ref.current) + }, []) +} diff --git a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js.snap b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js.snap index 7dfb89268f8e..fa0957fbb235 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/correctness/useExhaustiveDependencies/valid.js.snap @@ -7,7 +7,19 @@ expression: valid.js /* should not generate diagnostics */ import React from "react"; -import { useEffect, useSyncExternalStore, useMemo } from "react"; +import { + useEffect, + useSyncExternalStore, + useRef, + useState, + useContext, + useReducer, + useCallback, + useMemo, + useTransition, + useId, +} from "react"; +import { useRef as uR } from "react" import doSomething from 'a'; // No captures @@ -201,6 +213,23 @@ function MyComponent19() { }); } +// Namespaced imports +// https://github.com/biomejs/biome/issues/578 +function MyComponent20() { + const ref = React.useRef() + React.useEffect(() => { + console.log(ref.current) + }, []) +} + +// Aliased imports +function MyComponent21() { + const ref = uR() + useEffect(() => { + console.log(ref.current) + }, []) +} + ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/invalid.js new file mode 100644 index 000000000000..9cce41c6e7fd --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/invalid.js @@ -0,0 +1,63 @@ +var r = /[👍]/; +var r = /[\uD83D\uDC4D]/; +var r = /[👍]\\a/; +var r = /(?<=[👍])/; +var r = /[Á]/; +var r = /[Á]/u; +var r = /[\u0041\u0301]/; +var r = /[\u0041\u0301]/u; +var r = /[\u{41}\u{301}]/u; +var r = /[❇️]/; +var r = /[❇️]/u; +var r = /[\u2747\uFE0F]/; +var r = /[\u2747\uFE0F]/u; +var r = /[\u{2747}\u{FE0F}]/u; +var r = /[👶🏻]/; +var r = /[👶🏻]/u; +var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; +var r = /[\u{1F476}\u{1F3FB}]/u; +var r = /[🇯🇵]/; +var r = /[🇯🇵]/i; +var r = /[🇯🇵]/u; +var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; +var r = /[\u{1F1EF}\u{1F1F5}]/u; +var r = /[👨‍👩‍👦]/; +var r = /[👨‍👩‍👦]/u; +var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; +var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; +var r = new RegExp("[👍]", ""); +var r = new RegExp('[👍]', ``); +var r = new RegExp("[👍]", flags); +var r = new RegExp("[\uD83D\uDC4D]", ""); +var r = new RegExp("/(?<=[👍])", ""); +var r = new RegExp("[Á]", ""); +var r = new RegExp("[Á]", "u"); +var r = new RegExp("[\u0041\u0301]", ""); +var r = new RegExp("[\u0041\u0301]", "u"); +var r = new RegExp("[\u{41}\u{301}]", "u"); +var r = new RegExp("[❇️]", ""); +var r = new RegExp("[❇️]", "u"); +var r = new RegExp("[\u2747\uFE0F]", ""); +var r = new RegExp("[\u2747\uFE0F]", "u"); +var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); +var r = new RegExp("[👶🏻]", ""); +var r = new RegExp("[👶🏻]", "u"); +var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); +var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); +var r = new RegExp("[🇯🇵]", ""); +var r = new RegExp("[🇯🇵]", "i"); +var r = new RegExp('[🇯🇵]', `i`); +var r = new RegExp('[🇯🇵]', `${foo}`); +var r = new RegExp("[🇯🇵]"); +var r = new RegExp("[🇯🇵]",); +var r = new RegExp("[🇯🇵]", "u"); +var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); +var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); +var r = new RegExp("[👨‍👩‍👦]", ""); +var r = new RegExp("[👨‍👩‍👦]", "u"); +var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); +var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); +var r = new globalThis.RegExp("[❇️]", ""); +var r = new globalThis.RegExp("[👶🏻]", "u"); +var r = new globalThis.RegExp("[🇯🇵]", ""); +var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/invalid.js.snap new file mode 100644 index 000000000000..a6f24a38253e --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/invalid.js.snap @@ -0,0 +1,1155 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.js +--- +# Input +```js +var r = /[👍]/; +var r = /[\uD83D\uDC4D]/; +var r = /[👍]\\a/; +var r = /(?<=[👍])/; +var r = /[Á]/; +var r = /[Á]/u; +var r = /[\u0041\u0301]/; +var r = /[\u0041\u0301]/u; +var r = /[\u{41}\u{301}]/u; +var r = /[❇️]/; +var r = /[❇️]/u; +var r = /[\u2747\uFE0F]/; +var r = /[\u2747\uFE0F]/u; +var r = /[\u{2747}\u{FE0F}]/u; +var r = /[👶🏻]/; +var r = /[👶🏻]/u; +var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; +var r = /[\u{1F476}\u{1F3FB}]/u; +var r = /[🇯🇵]/; +var r = /[🇯🇵]/i; +var r = /[🇯🇵]/u; +var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; +var r = /[\u{1F1EF}\u{1F1F5}]/u; +var r = /[👨‍👩‍👦]/; +var r = /[👨‍👩‍👦]/u; +var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; +var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; +var r = new RegExp("[👍]", ""); +var r = new RegExp('[👍]', ``); +var r = new RegExp("[👍]", flags); +var r = new RegExp("[\uD83D\uDC4D]", ""); +var r = new RegExp("/(?<=[👍])", ""); +var r = new RegExp("[Á]", ""); +var r = new RegExp("[Á]", "u"); +var r = new RegExp("[\u0041\u0301]", ""); +var r = new RegExp("[\u0041\u0301]", "u"); +var r = new RegExp("[\u{41}\u{301}]", "u"); +var r = new RegExp("[❇️]", ""); +var r = new RegExp("[❇️]", "u"); +var r = new RegExp("[\u2747\uFE0F]", ""); +var r = new RegExp("[\u2747\uFE0F]", "u"); +var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); +var r = new RegExp("[👶🏻]", ""); +var r = new RegExp("[👶🏻]", "u"); +var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); +var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); +var r = new RegExp("[🇯🇵]", ""); +var r = new RegExp("[🇯🇵]", "i"); +var r = new RegExp('[🇯🇵]', `i`); +var r = new RegExp('[🇯🇵]', `${foo}`); +var r = new RegExp("[🇯🇵]"); +var r = new RegExp("[🇯🇵]",); +var r = new RegExp("[🇯🇵]", "u"); +var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); +var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); +var r = new RegExp("[👨‍👩‍👦]", ""); +var r = new RegExp("[👨‍👩‍👦]", "u"); +var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); +var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); +var r = new globalThis.RegExp("[❇️]", ""); +var r = new globalThis.RegExp("[👶🏻]", "u"); +var r = new globalThis.RegExp("[🇯🇵]", ""); +var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); +``` + +# Diagnostics +``` +invalid.js:1:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + > 1 │ var r = /[👍]/; + │ ^^^^^^ + 2 │ var r = /[\uD83D\uDC4D]/; + 3 │ var r = /[👍]\\a/; + + i Safe fix: Add unicode u flag to regex + + 1 │ var·r·=·/[👍]/u; + │ + + +``` + +``` +invalid.js:2:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 1 │ var r = /[👍]/; + > 2 │ var r = /[\uD83D\uDC4D]/; + │ ^^^^^^^^^^^^^^^^ + 3 │ var r = /[👍]\\a/; + 4 │ var r = /(?<=[👍])/; + + i Safe fix: Add unicode u flag to regex + + 2 │ var·r·=·/[\uD83D\uDC4D]/u; + │ + + +``` + +``` +invalid.js:3:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 1 │ var r = /[👍]/; + 2 │ var r = /[\uD83D\uDC4D]/; + > 3 │ var r = /[👍]\\a/; + │ ^^^^^^^^^ + 4 │ var r = /(?<=[👍])/; + 5 │ var r = /[A�]/; + + i Safe fix: Add unicode u flag to regex + + 3 │ var·r·=·/[👍]\\a/u; + │ + + +``` + +``` +invalid.js:4:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 2 │ var r = /[\uD83D\uDC4D]/; + 3 │ var r = /[👍]\\a/; + > 4 │ var r = /(?<=[👍])/; + │ ^^^^^^^^^^^ + 5 │ var r = /[A�]/; + 6 │ var r = /[A�]/u; + + i Safe fix: Add unicode u flag to regex + + 4 │ var·r·=·/(?<=[👍])/u; + │ + + +``` + +``` +invalid.js:5:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 3 │ var r = /[👍]\\a/; + 4 │ var r = /(?<=[👍])/; + > 5 │ var r = /[A�]/; + │ ^^^^^ + 6 │ var r = /[A�]/u; + 7 │ var r = /[\u0041\u0301]/; + + +``` + +``` +invalid.js:6:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 4 │ var r = /(?<=[👍])/; + 5 │ var r = /[A�]/; + > 6 │ var r = /[A�]/u; + │ ^^^^^^ + 7 │ var r = /[\u0041\u0301]/; + 8 │ var r = /[\u0041\u0301]/u; + + +``` + +``` +invalid.js:7:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 5 │ var r = /[A�]/; + 6 │ var r = /[A�]/u; + > 7 │ var r = /[\u0041\u0301]/; + │ ^^^^^^^^^^^^^^^^ + 8 │ var r = /[\u0041\u0301]/u; + 9 │ var r = /[\u{41}\u{301}]/u; + + +``` + +``` +invalid.js:8:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 6 │ var r = /[A�]/u; + 7 │ var r = /[\u0041\u0301]/; + > 8 │ var r = /[\u0041\u0301]/u; + │ ^^^^^^^^^^^^^^^^^ + 9 │ var r = /[\u{41}\u{301}]/u; + 10 │ var r = /[❇�]/; + + +``` + +``` +invalid.js:9:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 7 │ var r = /[\u0041\u0301]/; + 8 │ var r = /[\u0041\u0301]/u; + > 9 │ var r = /[\u{41}\u{301}]/u; + │ ^^^^^^^^^^^^^^^^^^ + 10 │ var r = /[❇�]/; + 11 │ var r = /[❇�]/u; + + +``` + +``` +invalid.js:10:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 8 │ var r = /[\u0041\u0301]/u; + 9 │ var r = /[\u{41}\u{301}]/u; + > 10 │ var r = /[❇�]/; + │ ^^^^^ + 11 │ var r = /[❇�]/u; + 12 │ var r = /[\u2747\uFE0F]/; + + +``` + +``` +invalid.js:11:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 9 │ var r = /[\u{41}\u{301}]/u; + 10 │ var r = /[❇�]/; + > 11 │ var r = /[❇�]/u; + │ ^^^^^^ + 12 │ var r = /[\u2747\uFE0F]/; + 13 │ var r = /[\u2747\uFE0F]/u; + + +``` + +``` +invalid.js:12:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 10 │ var r = /[❇�]/; + 11 │ var r = /[❇�]/u; + > 12 │ var r = /[\u2747\uFE0F]/; + │ ^^^^^^^^^^^^^^^^ + 13 │ var r = /[\u2747\uFE0F]/u; + 14 │ var r = /[\u{2747}\u{FE0F}]/u; + + +``` + +``` +invalid.js:13:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 11 │ var r = /[❇�]/u; + 12 │ var r = /[\u2747\uFE0F]/; + > 13 │ var r = /[\u2747\uFE0F]/u; + │ ^^^^^^^^^^^^^^^^^ + 14 │ var r = /[\u{2747}\u{FE0F}]/u; + 15 │ var r = /[👶🏻]/; + + +``` + +``` +invalid.js:14:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 12 │ var r = /[\u2747\uFE0F]/; + 13 │ var r = /[\u2747\uFE0F]/u; + > 14 │ var r = /[\u{2747}\u{FE0F}]/u; + │ ^^^^^^^^^^^^^^^^^^^^^ + 15 │ var r = /[👶🏻]/; + 16 │ var r = /[👶🏻]/u; + + +``` + +``` +invalid.js:15:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 13 │ var r = /[\u2747\uFE0F]/u; + 14 │ var r = /[\u{2747}\u{FE0F}]/u; + > 15 │ var r = /[👶🏻]/; + │ ^^^^^^^^ + 16 │ var r = /[👶🏻]/u; + 17 │ var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; + + i Safe fix: Add unicode u flag to regex + + 15 │ var·r·=·/[👶🏻]/u; + │ + + +``` + +``` +invalid.js:16:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected modified Emoji in the character class. + + 14 │ var r = /[\u{2747}\u{FE0F}]/u; + 15 │ var r = /[👶🏻]/; + > 16 │ var r = /[👶🏻]/u; + │ ^^^^^^^^^ + 17 │ var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; + 18 │ var r = /[\u{1F476}\u{1F3FB}]/u; + + +``` + +``` +invalid.js:17:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected modified Emoji in the character class. + + 15 │ var r = /[👶🏻]/; + 16 │ var r = /[👶🏻]/u; + > 17 │ var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 18 │ var r = /[\u{1F476}\u{1F3FB}]/u; + 19 │ var r = /[🇯🇵]/; + + +``` + +``` +invalid.js:18:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected modified Emoji in the character class. + + 16 │ var r = /[👶🏻]/u; + 17 │ var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; + > 18 │ var r = /[\u{1F476}\u{1F3FB}]/u; + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 19 │ var r = /[🇯🇵]/; + 20 │ var r = /[🇯🇵]/i; + + +``` + +``` +invalid.js:19:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 17 │ var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; + 18 │ var r = /[\u{1F476}\u{1F3FB}]/u; + > 19 │ var r = /[🇯🇵]/; + │ ^^^^^^ + 20 │ var r = /[🇯🇵]/i; + 21 │ var r = /[🇯🇵]/u; + + i Safe fix: Add unicode u flag to regex + + 19 │ var·r·=·/[🇯🇵]/u; + │ + + +``` + +``` +invalid.js:20:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 18 │ var r = /[\u{1F476}\u{1F3FB}]/u; + 19 │ var r = /[🇯🇵]/; + > 20 │ var r = /[🇯🇵]/i; + │ ^^^^^^^ + 21 │ var r = /[🇯🇵]/u; + 22 │ var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; + + i Safe fix: Add unicode u flag to regex + + 18 18 │ var r = /[\u{1F476}\u{1F3FB}]/u; + 19 19 │ var r = /[🇯🇵]/; + 20 │ - var·r·=·/[🇯🇵]/i; + 20 │ + var·r·=·/[🇯🇵]/iu; + 21 21 │ var r = /[🇯🇵]/u; + 22 22 │ var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; + + +``` + +``` +invalid.js:21:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Regional indicator symbol characters should not be used in the character class. + + 19 │ var r = /[🇯🇵]/; + 20 │ var r = /[🇯🇵]/i; + > 21 │ var r = /[🇯🇵]/u; + │ ^^^^^^^ + 22 │ var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; + 23 │ var r = /[\u{1F1EF}\u{1F1F5}]/u; + + +``` + +``` +invalid.js:22:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Regional indicator symbol characters should not be used in the character class. + + 20 │ var r = /[🇯🇵]/i; + 21 │ var r = /[🇯🇵]/u; + > 22 │ var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 23 │ var r = /[\u{1F1EF}\u{1F1F5}]/u; + 24 │ var r = /[👨�👩�👦]/; + + +``` + +``` +invalid.js:23:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Regional indicator symbol characters should not be used in the character class. + + 21 │ var r = /[🇯🇵]/u; + 22 │ var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; + > 23 │ var r = /[\u{1F1EF}\u{1F1F5}]/u; + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 24 │ var r = /[👨�👩�👦]/; + 25 │ var r = /[👨�👩�👦]/u; + + +``` + +``` +invalid.js:24:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 22 │ var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; + 23 │ var r = /[\u{1F1EF}\u{1F1F5}]/u; + > 24 │ var r = /[👨�👩�👦]/; + │ ^^^^^^^^^^ + 25 │ var r = /[👨�👩�👦]/u; + 26 │ var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; + + i Safe fix: Add unicode u flag to regex + + 24 │ var·r·=·/[👨�👩�👦]/u; + │ + + +``` + +``` +invalid.js:25:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected joined character sequence in character class. + + 23 │ var r = /[\u{1F1EF}\u{1F1F5}]/u; + 24 │ var r = /[👨�👩�👦]/; + > 25 │ var r = /[👨�👩�👦]/u; + │ ^^^^^^^^^^^ + 26 │ var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; + 27 │ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; + + +``` + +``` +invalid.js:26:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected joined character sequence in character class. + + 24 │ var r = /[👨�👩�👦]/; + 25 │ var r = /[👨�👩�👦]/u; + > 26 │ var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 27 │ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; + 28 │ var r = new RegExp("[👍]", ""); + + +``` + +``` +invalid.js:27:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected joined character sequence in character class. + + 25 │ var r = /[👨�👩�👦]/u; + 26 │ var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; + > 27 │ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 28 │ var r = new RegExp("[👍]", ""); + 29 │ var r = new RegExp('[👍]', ``); + + +``` + +``` +invalid.js:28:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 26 │ var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; + 27 │ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; + > 28 │ var r = new RegExp("[👍]", ""); + │ ^^^^^^^^^^^^^^^^^^^^^^ + 29 │ var r = new RegExp('[👍]', ``); + 30 │ var r = new RegExp("[👍]", flags); + + i Safe fix: Add unicode u flag to regex + + 26 26 │ var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; + 27 27 │ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; + 28 │ - var·r·=·new·RegExp("[👍]",·""); + 28 │ + var·r·=·new·RegExp("[👍]","u"); + 29 29 │ var r = new RegExp('[👍]', ``); + 30 30 │ var r = new RegExp("[👍]", flags); + + +``` + +``` +invalid.js:29:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 27 │ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; + 28 │ var r = new RegExp("[👍]", ""); + > 29 │ var r = new RegExp('[👍]', ``); + │ ^^^^^^^^^^^^^^^^^^^^^^ + 30 │ var r = new RegExp("[👍]", flags); + 31 │ var r = new RegExp("[\uD83D\uDC4D]", ""); + + i Safe fix: Add unicode u flag to regex + + 27 27 │ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; + 28 28 │ var r = new RegExp("[👍]", ""); + 29 │ - var·r·=·new·RegExp('[👍]',·``); + 29 │ + var·r·=·new·RegExp('[👍]',`u`); + 30 30 │ var r = new RegExp("[👍]", flags); + 31 31 │ var r = new RegExp("[\uD83D\uDC4D]", ""); + + +``` + +``` +invalid.js:30:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 28 │ var r = new RegExp("[👍]", ""); + 29 │ var r = new RegExp('[👍]', ``); + > 30 │ var r = new RegExp("[👍]", flags); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ + 31 │ var r = new RegExp("[\uD83D\uDC4D]", ""); + 32 │ var r = new RegExp("/(?<=[👍])", ""); + + +``` + +``` +invalid.js:31:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 29 │ var r = new RegExp('[👍]', ``); + 30 │ var r = new RegExp("[👍]", flags); + > 31 │ var r = new RegExp("[\uD83D\uDC4D]", ""); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 32 │ var r = new RegExp("/(?<=[👍])", ""); + 33 │ var r = new RegExp("[A�]", ""); + + i Safe fix: Add unicode u flag to regex + + 29 29 │ var r = new RegExp('[👍]', ``); + 30 30 │ var r = new RegExp("[👍]", flags); + 31 │ - var·r·=·new·RegExp("[\uD83D\uDC4D]",·""); + 31 │ + var·r·=·new·RegExp("[\uD83D\uDC4D]","u"); + 32 32 │ var r = new RegExp("/(?<=[👍])", ""); + 33 33 │ var r = new RegExp("[A�]", ""); + + +``` + +``` +invalid.js:32:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 30 │ var r = new RegExp("[👍]", flags); + 31 │ var r = new RegExp("[\uD83D\uDC4D]", ""); + > 32 │ var r = new RegExp("/(?<=[👍])", ""); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 33 │ var r = new RegExp("[A�]", ""); + 34 │ var r = new RegExp("[A�]", "u"); + + i Safe fix: Add unicode u flag to regex + + 30 30 │ var r = new RegExp("[👍]", flags); + 31 31 │ var r = new RegExp("[\uD83D\uDC4D]", ""); + 32 │ - var·r·=·new·RegExp("/(?<=[👍])",·""); + 32 │ + var·r·=·new·RegExp("/(?<=[👍])","u"); + 33 33 │ var r = new RegExp("[A�]", ""); + 34 34 │ var r = new RegExp("[A�]", "u"); + + +``` + +``` +invalid.js:33:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 31 │ var r = new RegExp("[\uD83D\uDC4D]", ""); + 32 │ var r = new RegExp("/(?<=[👍])", ""); + > 33 │ var r = new RegExp("[A�]", ""); + │ ^^^^^^^^^^^^^^^^^^^^^ + 34 │ var r = new RegExp("[A�]", "u"); + 35 │ var r = new RegExp("[\u0041\u0301]", ""); + + +``` + +``` +invalid.js:34:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 32 │ var r = new RegExp("/(?<=[👍])", ""); + 33 │ var r = new RegExp("[A�]", ""); + > 34 │ var r = new RegExp("[A�]", "u"); + │ ^^^^^^^^^^^^^^^^^^^^^^ + 35 │ var r = new RegExp("[\u0041\u0301]", ""); + 36 │ var r = new RegExp("[\u0041\u0301]", "u"); + + +``` + +``` +invalid.js:35:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 33 │ var r = new RegExp("[A�]", ""); + 34 │ var r = new RegExp("[A�]", "u"); + > 35 │ var r = new RegExp("[\u0041\u0301]", ""); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 36 │ var r = new RegExp("[\u0041\u0301]", "u"); + 37 │ var r = new RegExp("[\u{41}\u{301}]", "u"); + + +``` + +``` +invalid.js:36:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 34 │ var r = new RegExp("[A�]", "u"); + 35 │ var r = new RegExp("[\u0041\u0301]", ""); + > 36 │ var r = new RegExp("[\u0041\u0301]", "u"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 37 │ var r = new RegExp("[\u{41}\u{301}]", "u"); + 38 │ var r = new RegExp("[❇�]", ""); + + +``` + +``` +invalid.js:37:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 35 │ var r = new RegExp("[\u0041\u0301]", ""); + 36 │ var r = new RegExp("[\u0041\u0301]", "u"); + > 37 │ var r = new RegExp("[\u{41}\u{301}]", "u"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 38 │ var r = new RegExp("[❇�]", ""); + 39 │ var r = new RegExp("[❇�]", "u"); + + +``` + +``` +invalid.js:38:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 36 │ var r = new RegExp("[\u0041\u0301]", "u"); + 37 │ var r = new RegExp("[\u{41}\u{301}]", "u"); + > 38 │ var r = new RegExp("[❇�]", ""); + │ ^^^^^^^^^^^^^^^^^^^^^ + 39 │ var r = new RegExp("[❇�]", "u"); + 40 │ var r = new RegExp("[\u2747\uFE0F]", ""); + + +``` + +``` +invalid.js:39:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 37 │ var r = new RegExp("[\u{41}\u{301}]", "u"); + 38 │ var r = new RegExp("[❇�]", ""); + > 39 │ var r = new RegExp("[❇�]", "u"); + │ ^^^^^^^^^^^^^^^^^^^^^^ + 40 │ var r = new RegExp("[\u2747\uFE0F]", ""); + 41 │ var r = new RegExp("[\u2747\uFE0F]", "u"); + + +``` + +``` +invalid.js:40:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 38 │ var r = new RegExp("[❇�]", ""); + 39 │ var r = new RegExp("[❇�]", "u"); + > 40 │ var r = new RegExp("[\u2747\uFE0F]", ""); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 41 │ var r = new RegExp("[\u2747\uFE0F]", "u"); + 42 │ var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); + + +``` + +``` +invalid.js:41:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 39 │ var r = new RegExp("[❇�]", "u"); + 40 │ var r = new RegExp("[\u2747\uFE0F]", ""); + > 41 │ var r = new RegExp("[\u2747\uFE0F]", "u"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 42 │ var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); + 43 │ var r = new RegExp("[👶🏻]", ""); + + +``` + +``` +invalid.js:42:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 40 │ var r = new RegExp("[\u2747\uFE0F]", ""); + 41 │ var r = new RegExp("[\u2747\uFE0F]", "u"); + > 42 │ var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 43 │ var r = new RegExp("[👶🏻]", ""); + 44 │ var r = new RegExp("[👶🏻]", "u"); + + +``` + +``` +invalid.js:43:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 41 │ var r = new RegExp("[\u2747\uFE0F]", "u"); + 42 │ var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); + > 43 │ var r = new RegExp("[👶🏻]", ""); + │ ^^^^^^^^^^^^^^^^^^^^^^^^ + 44 │ var r = new RegExp("[👶🏻]", "u"); + 45 │ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); + + i Safe fix: Add unicode u flag to regex + + 41 41 │ var r = new RegExp("[\u2747\uFE0F]", "u"); + 42 42 │ var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); + 43 │ - var·r·=·new·RegExp("[👶🏻]",·""); + 43 │ + var·r·=·new·RegExp("[👶🏻]","u"); + 44 44 │ var r = new RegExp("[👶🏻]", "u"); + 45 45 │ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); + + +``` + +``` +invalid.js:44:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected modified Emoji in the character class. + + 42 │ var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); + 43 │ var r = new RegExp("[👶🏻]", ""); + > 44 │ var r = new RegExp("[👶🏻]", "u"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ + 45 │ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); + 46 │ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); + + +``` + +``` +invalid.js:45:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected modified Emoji in the character class. + + 43 │ var r = new RegExp("[👶🏻]", ""); + 44 │ var r = new RegExp("[👶🏻]", "u"); + > 45 │ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 46 │ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); + 47 │ var r = new RegExp("[🇯🇵]", ""); + + +``` + +``` +invalid.js:46:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected modified Emoji in the character class. + + 44 │ var r = new RegExp("[👶🏻]", "u"); + 45 │ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); + > 46 │ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 47 │ var r = new RegExp("[🇯🇵]", ""); + 48 │ var r = new RegExp("[🇯🇵]", "i"); + + +``` + +``` +invalid.js:47:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 45 │ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); + 46 │ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); + > 47 │ var r = new RegExp("[🇯🇵]", ""); + │ ^^^^^^^^^^^^^^^^^^^^^^ + 48 │ var r = new RegExp("[🇯🇵]", "i"); + 49 │ var r = new RegExp('[🇯🇵]', `i`); + + i Safe fix: Add unicode u flag to regex + + 45 45 │ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); + 46 46 │ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); + 47 │ - var·r·=·new·RegExp("[🇯🇵]",·""); + 47 │ + var·r·=·new·RegExp("[🇯🇵]","u"); + 48 48 │ var r = new RegExp("[🇯🇵]", "i"); + 49 49 │ var r = new RegExp('[🇯🇵]', `i`); + + +``` + +``` +invalid.js:48:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 46 │ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); + 47 │ var r = new RegExp("[🇯🇵]", ""); + > 48 │ var r = new RegExp("[🇯🇵]", "i"); + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 49 │ var r = new RegExp('[🇯🇵]', `i`); + 50 │ var r = new RegExp('[🇯🇵]', `${foo}`); + + i Safe fix: Add unicode u flag to regex + + 46 46 │ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); + 47 47 │ var r = new RegExp("[🇯🇵]", ""); + 48 │ - var·r·=·new·RegExp("[🇯🇵]",·"i"); + 48 │ + var·r·=·new·RegExp("[🇯🇵]","iu"); + 49 49 │ var r = new RegExp('[🇯🇵]', `i`); + 50 50 │ var r = new RegExp('[🇯🇵]', `${foo}`); + + +``` + +``` +invalid.js:49:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 47 │ var r = new RegExp("[🇯🇵]", ""); + 48 │ var r = new RegExp("[🇯🇵]", "i"); + > 49 │ var r = new RegExp('[🇯🇵]', `i`); + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 50 │ var r = new RegExp('[🇯🇵]', `${foo}`); + 51 │ var r = new RegExp("[🇯🇵]"); + + i Safe fix: Add unicode u flag to regex + + 47 47 │ var r = new RegExp("[🇯🇵]", ""); + 48 48 │ var r = new RegExp("[🇯🇵]", "i"); + 49 │ - var·r·=·new·RegExp('[🇯🇵]',·`i`); + 49 │ + var·r·=·new·RegExp('[🇯🇵]',`iu`); + 50 50 │ var r = new RegExp('[🇯🇵]', `${foo}`); + 51 51 │ var r = new RegExp("[🇯🇵]"); + + +``` + +``` +invalid.js:50:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 48 │ var r = new RegExp("[🇯🇵]", "i"); + 49 │ var r = new RegExp('[🇯🇵]', `i`); + > 50 │ var r = new RegExp('[🇯🇵]', `${foo}`); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 51 │ var r = new RegExp("[🇯🇵]"); + 52 │ var r = new RegExp("[🇯🇵]",); + + i Safe fix: Add unicode u flag to regex + + 48 48 │ var r = new RegExp("[🇯🇵]", "i"); + 49 49 │ var r = new RegExp('[🇯🇵]', `i`); + 50 │ - var·r·=·new·RegExp('[🇯🇵]',·`${foo}`); + 50 │ + var·r·=·new·RegExp('[🇯🇵]',`${foo}u`); + 51 51 │ var r = new RegExp("[🇯🇵]"); + 52 52 │ var r = new RegExp("[🇯🇵]",); + + +``` + +``` +invalid.js:51:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 49 │ var r = new RegExp('[🇯🇵]', `i`); + 50 │ var r = new RegExp('[🇯🇵]', `${foo}`); + > 51 │ var r = new RegExp("[🇯🇵]"); + │ ^^^^^^^^^^^^^^^^^^ + 52 │ var r = new RegExp("[🇯🇵]",); + 53 │ var r = new RegExp("[🇯🇵]", "u"); + + i Safe fix: Add unicode u flag to regex + + 51 │ var·r·=·new·RegExp("[🇯🇵]","u"); + │ ++++ + +``` + +``` +invalid.js:52:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 50 │ var r = new RegExp('[🇯🇵]', `${foo}`); + 51 │ var r = new RegExp("[🇯🇵]"); + > 52 │ var r = new RegExp("[🇯🇵]",); + │ ^^^^^^^^^^^^^^^^^^^ + 53 │ var r = new RegExp("[🇯🇵]", "u"); + 54 │ var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); + + i Safe fix: Add unicode u flag to regex + + 52 │ var·r·=·new·RegExp("[🇯🇵]","u"); + │ +++ + +``` + +``` +invalid.js:53:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Regional indicator symbol characters should not be used in the character class. + + 51 │ var r = new RegExp("[🇯🇵]"); + 52 │ var r = new RegExp("[🇯🇵]",); + > 53 │ var r = new RegExp("[🇯🇵]", "u"); + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 54 │ var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); + 55 │ var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); + + +``` + +``` +invalid.js:54:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Regional indicator symbol characters should not be used in the character class. + + 52 │ var r = new RegExp("[🇯🇵]",); + 53 │ var r = new RegExp("[🇯🇵]", "u"); + > 54 │ var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 55 │ var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); + 56 │ var r = new RegExp("[👨�👩�👦]", ""); + + +``` + +``` +invalid.js:55:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Regional indicator symbol characters should not be used in the character class. + + 53 │ var r = new RegExp("[🇯🇵]", "u"); + 54 │ var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); + > 55 │ var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 56 │ var r = new RegExp("[👨�👩�👦]", ""); + 57 │ var r = new RegExp("[👨�👩�👦]", "u"); + + +``` + +``` +invalid.js:56:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 54 │ var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); + 55 │ var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); + > 56 │ var r = new RegExp("[👨�👩�👦]", ""); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + 57 │ var r = new RegExp("[👨�👩�👦]", "u"); + 58 │ var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); + + i Safe fix: Add unicode u flag to regex + + 54 54 │ var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); + 55 55 │ var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); + 56 │ - var·r·=·new·RegExp("[👨�👩�👦]",·""); + 56 │ + var·r·=·new·RegExp("[👨�👩�👦]","u"); + 57 57 │ var r = new RegExp("[👨�👩�👦]", "u"); + 58 58 │ var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); + + +``` + +``` +invalid.js:57:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected joined character sequence in character class. + + 55 │ var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); + 56 │ var r = new RegExp("[👨�👩�👦]", ""); + > 57 │ var r = new RegExp("[👨�👩�👦]", "u"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 58 │ var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); + 59 │ var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); + + +``` + +``` +invalid.js:58:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected joined character sequence in character class. + + 56 │ var r = new RegExp("[👨�👩�👦]", ""); + 57 │ var r = new RegExp("[👨�👩�👦]", "u"); + > 58 │ var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 59 │ var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); + 60 │ var r = new globalThis.RegExp("[❇�]", ""); + + +``` + +``` +invalid.js:59:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected joined character sequence in character class. + + 57 │ var r = new RegExp("[👨�👩�👦]", "u"); + 58 │ var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); + > 59 │ var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 60 │ var r = new globalThis.RegExp("[❇�]", ""); + 61 │ var r = new globalThis.RegExp("[👶🏻]", "u"); + + +``` + +``` +invalid.js:60:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected combined character in the character class. + + 58 │ var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); + 59 │ var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); + > 60 │ var r = new globalThis.RegExp("[❇�]", ""); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 61 │ var r = new globalThis.RegExp("[👶🏻]", "u"); + 62 │ var r = new globalThis.RegExp("[🇯🇵]", ""); + + +``` + +``` +invalid.js:61:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected modified Emoji in the character class. + + 59 │ var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); + 60 │ var r = new globalThis.RegExp("[❇�]", ""); + > 61 │ var r = new globalThis.RegExp("[👶🏻]", "u"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 62 │ var r = new globalThis.RegExp("[🇯🇵]", ""); + 63 │ var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); + + +``` + +``` +invalid.js:62:9 lint/nursery/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected surrogate pair in character class. Use the 'u' flag. + + 60 │ var r = new globalThis.RegExp("[❇�]", ""); + 61 │ var r = new globalThis.RegExp("[👶🏻]", "u"); + > 62 │ var r = new globalThis.RegExp("[🇯🇵]", ""); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 63 │ var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); + + i Safe fix: Add unicode u flag to regex + + 60 60 │ var r = new globalThis.RegExp("[❇�]", ""); + 61 61 │ var r = new globalThis.RegExp("[👶🏻]", "u"); + 62 │ - var·r·=·new·globalThis.RegExp("[🇯🇵]",·""); + 62 │ + var·r·=·new·globalThis.RegExp("[🇯🇵]","u"); + 63 63 │ var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); + + +``` + +``` +invalid.js:63:9 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected joined character sequence in character class. + + 61 │ var r = new globalThis.RegExp("[👶🏻]", "u"); + 62 │ var r = new globalThis.RegExp("[🇯🇵]", ""); + > 63 │ var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +``` + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/valid.js new file mode 100644 index 000000000000..4854e27c2b07 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/valid.js @@ -0,0 +1,49 @@ +var r = /[👍]/u; +var r = /[\\uD83D\\uDC4D]/u; +var r = /[\\u{1F44D}]/u; +var r = /❇️/; +var r = /Á/; +var r = /[❇]/; +var r = /👶🏻/; +var r = /[👶]/u; +var r = /🇯🇵/; +var r = /[JP]/; +var r = /👨‍👩‍👦/; + +// Ignore solo lead/tail surrogate. +var r = /[\\uD83D]/; +var r = /[\\uDC4D]/; +var r = /[\\uD83D]/u; +var r = /[\\uDC4D]/u; + +// Ignore solo combining char. +var r = /[\\u0301]/; +var r = /[\\uFE0F]/; +var r = /[\\u0301]/u; +var r = /[\\uFE0F]/u; + +// Ignore solo emoji modifier. +var r = /[\\u{1F3FB}]/u; +var r = /[\u{1F3FB}]/u; + +// Ignore solo regional indicator symbol. +var r = /[🇯]/u; +var r = /[🇵]/u; + +// Ignore solo ZWJ. +var r = /[\\u200D]/; +var r = /[\\u200D]/u; + +// don't report and don't crash on invalid regex +// FIXME: need to ecma regex parser to handle this case +// var r = new RegExp('[Á] [ '); +// var r = RegExp('{ [Á]', 'u'); +// var r = new globalThis.RegExp('[Á] [ '); +// var r = globalThis.RegExp('{ [Á]', 'u'); + +// v flag +var r = /[👍]/v; +var r = /^[\q{👶🏻}]$/v; +var r = /[🇯\q{abc}🇵]/v; +var r = /[🇯[A]🇵]/v; +var r = /[🇯[A--B]🇵]/v; \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/valid.js.snap new file mode 100644 index 000000000000..575f07754a4b --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noMisleadingCharacterClass/valid.js.snap @@ -0,0 +1,58 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.js +--- +# Input +```js +var r = /[👍]/u; +var r = /[\\uD83D\\uDC4D]/u; +var r = /[\\u{1F44D}]/u; +var r = /❇️/; +var r = /Á/; +var r = /[❇]/; +var r = /👶🏻/; +var r = /[👶]/u; +var r = /🇯🇵/; +var r = /[JP]/; +var r = /👨‍👩‍👦/; + +// Ignore solo lead/tail surrogate. +var r = /[\\uD83D]/; +var r = /[\\uDC4D]/; +var r = /[\\uD83D]/u; +var r = /[\\uDC4D]/u; + +// Ignore solo combining char. +var r = /[\\u0301]/; +var r = /[\\uFE0F]/; +var r = /[\\u0301]/u; +var r = /[\\uFE0F]/u; + +// Ignore solo emoji modifier. +var r = /[\\u{1F3FB}]/u; +var r = /[\u{1F3FB}]/u; + +// Ignore solo regional indicator symbol. +var r = /[🇯]/u; +var r = /[🇵]/u; + +// Ignore solo ZWJ. +var r = /[\\u200D]/; +var r = /[\\u200D]/u; + +// don't report and don't crash on invalid regex +// FIXME: need to ecma regex parser to handle this case +// var r = new RegExp('[Á] [ '); +// var r = RegExp('{ [Á]', 'u'); +// var r = new globalThis.RegExp('[Á] [ '); +// var r = globalThis.RegExp('{ [Á]', 'u'); + +// v flag +var r = /[👍]/v; +var r = /^[\q{👶🏻}]$/v; +var r = /[🇯\q{abc}🇵]/v; +var r = /[🇯[A]🇵]/v; +var r = /[🇯[A--B]🇵]/v; +``` + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/useShorthandFunctionType/invalid.ts b/crates/biome_js_analyze/tests/specs/nursery/useShorthandFunctionType/invalid.ts new file mode 100644 index 000000000000..de0e7918af17 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useShorthandFunctionType/invalid.ts @@ -0,0 +1,7 @@ +interface Example { + (): string; +} + +function foo(example: { (): number }): number { + return example(); +} \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/useShorthandFunctionType/invalid.ts.snap b/crates/biome_js_analyze/tests/specs/nursery/useShorthandFunctionType/invalid.ts.snap new file mode 100644 index 000000000000..4bbe2e03100e --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useShorthandFunctionType/invalid.ts.snap @@ -0,0 +1,68 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.ts +--- +# Input +```js +interface Example { + (): string; +} + +function foo(example: { (): number }): number { + return example(); +} +``` + +# Diagnostics +``` +invalid.ts:2:2 lint/nursery/useShorthandFunctionType FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Use a function type instead of a call signature. + + 1 │ interface Example { + > 2 │ (): string; + │ ^^^^^^^^^^^ + 3 │ } + 4 │ + + i Types containing only a call signature can be shortened to a function type. + + i Safe fix: Alias a function type instead of using an interface with a call signature. + + 1 │ - interface·Example·{ + 2 │ - ·():·string; + 3 │ - } + 1 │ + type·Example·=·()·=>·string + 4 2 │ + 5 3 │ function foo(example: { (): number }): number { + + +``` + +``` +invalid.ts:5:25 lint/nursery/useShorthandFunctionType FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Use a function type instead of a call signature. + + 3 │ } + 4 │ + > 5 │ function foo(example: { (): number }): number { + │ ^^^^^^^^^^ + 6 │ return example(); + 7 │ } + + i Types containing only a call signature can be shortened to a function type. + + i Safe fix: Use a function type instead of an object type with a call signature. + + 3 3 │ } + 4 4 │ + 5 │ - function·foo(example:·{·():·number·}):·number·{ + 5 │ + function·foo(example:·()·=>·number):·number·{ + 6 6 │ return example(); + 7 7 │ } + + +``` + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/useShorthandFunctionType/valid.ts b/crates/biome_js_analyze/tests/specs/nursery/useShorthandFunctionType/valid.ts new file mode 100644 index 000000000000..bae591fde495 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useShorthandFunctionType/valid.ts @@ -0,0 +1,30 @@ +type Example = () => string; + +function foo(example: () => number): number { + return bar(); +} + +// returns the function itself, not the `this` argument. +type ReturnsSelf = (arg: string) => ReturnsSelf; + +interface Foo { + bar: string; +} + +interface Bar extends Foo { + (): void; +} + +// multiple call signatures (overloads) is allowed: +interface Overloaded { + (data: string): number; + (id: number): string; +} + +// this is equivelent to Overloaded interface. +type Intersection = ((data: string) => number) & ((id: number) => string); + +interface ReturnsSelf { + // returns the function itself, not the `this` argument. + (arg: string): this; +} \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/useShorthandFunctionType/valid.ts.snap b/crates/biome_js_analyze/tests/specs/nursery/useShorthandFunctionType/valid.ts.snap new file mode 100644 index 000000000000..56eb2704baf5 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useShorthandFunctionType/valid.ts.snap @@ -0,0 +1,39 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.ts +--- +# Input +```js +type Example = () => string; + +function foo(example: () => number): number { + return bar(); +} + +// returns the function itself, not the `this` argument. +type ReturnsSelf = (arg: string) => ReturnsSelf; + +interface Foo { + bar: string; +} + +interface Bar extends Foo { + (): void; +} + +// multiple call signatures (overloads) is allowed: +interface Overloaded { + (data: string): number; + (id: number): string; +} + +// this is equivelent to Overloaded interface. +type Intersection = ((data: string) => number) & ((id: number) => string); + +interface ReturnsSelf { + // returns the function itself, not the `this` argument. + (arg: string): this; +} +``` + + diff --git a/crates/biome_js_formatter/src/js/auxiliary/case_clause.rs b/crates/biome_js_formatter/src/js/auxiliary/case_clause.rs index 32271f3723cd..8e9996f74e79 100644 --- a/crates/biome_js_formatter/src/js/auxiliary/case_clause.rs +++ b/crates/biome_js_formatter/src/js/auxiliary/case_clause.rs @@ -27,17 +27,55 @@ impl FormatNodeRule for FormatJsCaseClause { ] )?; - let is_first_child_block_stmt = matches!( + // Whether the first statement in the clause is a BlockStatement, and + // there are no other non-empty statements. Empties may show up + // depending on whether the input code includes certain newlines. + let is_single_block_statement = matches!( consequent.iter().next(), Some(AnyJsStatement::JsBlockStatement(_)) - ); + ) && consequent + .iter() + .filter(|statement| !matches!(statement, AnyJsStatement::JsEmptyStatement(_))) + .count() + == 1; + // When the case block is empty, the case becomes a fallthrough, so it + // is collapsed directly on top of the next case (just a single + // hardline). + // When the block is a single statement _and_ it's a block statement, + // then the opening brace of the block can hug the same line as the + // case. But, if there's more than one statement, then the block + // _cannot_ hug. This distinction helps clarify that the case continues + // past the end of the block statement, despite the braces making it + // seem like it might end. + // Lastly, the default case is just to break and indent the body. + // + // switch (key) { + // case fallthrough: // trailing comment + // case normalBody: + // someWork(); + // break; + // + // case blockBody: { + // const a = 1; + // break; + // } + // + // case separateBlockBody: + // { + // breakIsNotInsideTheBlock(); + // } + // break; + // + // default: + // break; + // } if consequent.is_empty() { - // Skip inserting an indent block is the consequent is empty to print - // the trailing comments for the case clause inline if there is no - // block to push them into - write!(f, [hard_line_break()]) - } else if is_first_child_block_stmt { + // Print nothing to ensure that trailing comments on the same line + // are printed on the same line. The parent list formatter takes + // care of inserting a hard line break between cases. + Ok(()) + } else if is_single_block_statement { write![f, [space(), consequent.format()]] } else { // no line break needed after because it is added by the indent in the switch statement diff --git a/crates/biome_js_formatter/src/js/expressions/arrow_function_expression.rs b/crates/biome_js_formatter/src/js/expressions/arrow_function_expression.rs index b27f38552cd1..a77a7ee5d967 100644 --- a/crates/biome_js_formatter/src/js/expressions/arrow_function_expression.rs +++ b/crates/biome_js_formatter/src/js/expressions/arrow_function_expression.rs @@ -519,6 +519,7 @@ impl Format for ArrowChain { AnyJsExpression::JsObjectExpression(_) | AnyJsExpression::JsArrayExpression(_) | AnyJsExpression::JsSequenceExpression(_) + | AnyJsExpression::JsxTagExpression(_) ) ); @@ -667,13 +668,25 @@ impl Format for ArrowChain { }); let format_tail_body = format_with(|f| { + // if it's inside a JSXExpression (e.g. an attribute) we should align the expression's closing } with the line with the opening {. + let should_add_soft_line = matches!( + head_parent.kind(), + Some( + JsSyntaxKind::JSX_EXPRESSION_CHILD + | JsSyntaxKind::JSX_EXPRESSION_ATTRIBUTE_VALUE + ) + ); + if body_on_separate_line { write!( f, - [indent(&format_args![ - soft_line_break_or_space(), - format_tail_body_inner - ])] + [ + indent(&format_args![ + soft_line_break_or_space(), + format_tail_body_inner + ]), + should_add_soft_line.then_some(soft_line_break()) + ] ) } else { write!(f, [space(), format_tail_body_inner]) diff --git a/crates/biome_js_formatter/tests/quick_test.rs b/crates/biome_js_formatter/tests/quick_test.rs index 38483afefdef..00544a75f4d9 100644 --- a/crates/biome_js_formatter/tests/quick_test.rs +++ b/crates/biome_js_formatter/tests/quick_test.rs @@ -14,10 +14,8 @@ mod language { // use this test check if your snippet prints as you wish, without using a snapshot fn quick_test() { let src = r#" - aLongFunctionName(({ a, eventHeff }: ReallyLongNameForATypeThingHereOptionNameApplicationCommandOptionKeepsGoingOnn) => { - const a = 1; - } - ); + ((C) => (props) => ); + (({C}) => (props) => ); "#; let source_type = JsFileSource::tsx(); let tree = parse( diff --git a/crates/biome_js_formatter/tests/specs/js/module/statement/switch.js b/crates/biome_js_formatter/tests/specs/js/module/statement/switch.js index ec9f94dbcdbd..cc78b4c492ea 100644 --- a/crates/biome_js_formatter/tests/specs/js/module/statement/switch.js +++ b/crates/biome_js_formatter/tests/specs/js/module/statement/switch.js @@ -2,9 +2,11 @@ switch (key) { case // comment value: + case value: // fallthrough same-line case value: // fallthrough + case fallthrough: case value: break; @@ -17,3 +19,17 @@ switch (key) { switch ("test") { case "test": {} } + +switch (key) { + case blockBody: { + const a = 1; + break; + } + + // The block is not the only statement in the case body, + // so it doesn't hug the same line as the case here. + case separateBlockBody: { + const a = 1; + } + break; +} \ No newline at end of file diff --git a/crates/biome_js_formatter/tests/specs/js/module/statement/switch.js.snap b/crates/biome_js_formatter/tests/specs/js/module/statement/switch.js.snap index 968700e155a2..6341ed0c71e4 100644 --- a/crates/biome_js_formatter/tests/specs/js/module/statement/switch.js.snap +++ b/crates/biome_js_formatter/tests/specs/js/module/statement/switch.js.snap @@ -10,9 +10,11 @@ switch (key) { case // comment value: + case value: // fallthrough same-line case value: // fallthrough + case fallthrough: case value: break; @@ -26,6 +28,19 @@ switch ("test") { case "test": {} } +switch (key) { + case blockBody: { + const a = 1; + break; + } + + // The block is not the only statement in the case body, + // so it doesn't hug the same line as the case here. + case separateBlockBody: { + const a = 1; + } + break; +} ``` @@ -55,9 +70,11 @@ switch (key) { case // comment value: + case value: // fallthrough same-line case value: // fallthrough + case fallthrough: case value: break; @@ -69,6 +86,21 @@ switch ("test") { case "test": { } } + +switch (key) { + case blockBody: { + const a = 1; + break; + } + + // The block is not the only statement in the case body, + // so it doesn't hug the same line as the case here. + case separateBlockBody: + { + const a = 1; + } + break; +} ``` diff --git a/crates/biome_js_formatter/tests/specs/jsx/arrow_function.jsx b/crates/biome_js_formatter/tests/specs/jsx/arrow_function.jsx index 190d8b5667bf..09de9595f229 100644 --- a/crates/biome_js_formatter/tests/specs/jsx/arrow_function.jsx +++ b/crates/biome_js_formatter/tests/specs/jsx/arrow_function.jsx @@ -27,3 +27,14 @@ function ArrowBodyIsJsxWithComment({ action }) {
  • ); } + + + +function ArrowCurryWithPlainParameters() { + return (C) => (props) => ; +} + +function ArrowCurryWithDestructuringParameters() { + return ({ C }) => + (props) => ; +} diff --git a/crates/biome_js_formatter/tests/specs/jsx/arrow_function.jsx.snap b/crates/biome_js_formatter/tests/specs/jsx/arrow_function.jsx.snap index 5e858e14f1fa..5c097f27e1d2 100644 --- a/crates/biome_js_formatter/tests/specs/jsx/arrow_function.jsx.snap +++ b/crates/biome_js_formatter/tests/specs/jsx/arrow_function.jsx.snap @@ -36,6 +36,17 @@ function ArrowBodyIsJsxWithComment({ action }) { ); } + + +function ArrowCurryWithPlainParameters() { + return (C) => (props) => ; +} + +function ArrowCurryWithDestructuringParameters() { + return ({ C }) => + (props) => ; +} + ``` @@ -92,6 +103,15 @@ function ArrowBodyIsJsxWithComment({ action }) {
  • ); } + +function ArrowCurryWithPlainParameters() { + return (C) => (props) => ; +} + +function ArrowCurryWithDestructuringParameters() { + return ({ C }) => + (props) => ; +} ``` diff --git a/crates/biome_js_formatter/tests/specs/prettier/jsx/jsx/arrow.js.snap b/crates/biome_js_formatter/tests/specs/prettier/jsx/jsx/arrow.js.snap deleted file mode 100644 index 33dea9d27d67..000000000000 --- a/crates/biome_js_formatter/tests/specs/prettier/jsx/jsx/arrow.js.snap +++ /dev/null @@ -1,183 +0,0 @@ ---- -source: crates/biome_formatter_test/src/snapshot_builder.rs -info: jsx/jsx/arrow.js ---- - -# Input - -```js -() => ; -() => () => ; -() => () => () => ; - -() =>
    Some text here
    ; -() => () =>
    Some text here
    ; -() => () => () =>
    Some text here
    ; - -() =>
    Long long long long long, very very long text. And more text. Another text.
    ; -() => () =>
    Long long long long long, very very long text. And more text. Another text.
    ; -() => () => () =>
    Long long long long long, very very long text. And more text. Another text.
    ; - - - {We => 'The purple monkey danced with a tambourine made of cheese.' + 'The robot chef cooked a cake that tasted like rainbows.' + 'The talking pineapple sang a lullaby to the sleepy giraffe.'} -; - - {We => love => 'The purple monkey danced with a tambourine made of cheese.' + 'The robot chef cooked a cake that tasted like rainbows.' + 'The talking pineapple sang a lullaby to the sleepy giraffe.'} -; - - {We => love => currying => 'The purple monkey danced with a tambourine made of cheese.' + 'The robot chef cooked a cake that tasted like rainbows.' + 'The talking pineapple sang a lullaby to the sleepy giraffe.'} -; - -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Biome -@@ -3,16 +3,18 @@ - - - ); --() => () => ( -- -- -- --); --() => () => () => ( -- -- -- --); -+() => () => -+ ( -+ -+ -+ -+ ); -+() => () => () => -+ ( -+ -+ -+ -+ ); - - () =>
    Some text here
    ; - () => () =>
    Some text here
    ; -@@ -23,16 +25,20 @@ - Long long long long long, very very long text. And more text. Another text. - - ); --() => () => ( --
    -- Long long long long long, very very long text. And more text. Another text. --
    --); --() => () => () => ( --
    -- Long long long long long, very very long text. And more text. Another text. --
    --); -+() => () => -+ ( -+
    -+ Long long long long long, very very long text. And more text. Another -+ text. -+
    -+ ); -+() => () => () => -+ ( -+
    -+ Long long long long long, very very long text. And more text. Another -+ text. -+
    -+ ); - - - {(We) => -@@ -45,13 +51,11 @@ - {(We) => (love) => - "The purple monkey danced with a tambourine made of cheese." + - "The robot chef cooked a cake that tasted like rainbows." + -- "The talking pineapple sang a lullaby to the sleepy giraffe." -- } -+ "The talking pineapple sang a lullaby to the sleepy giraffe."} - ; - - {(We) => (love) => (currying) => - "The purple monkey danced with a tambourine made of cheese." + - "The robot chef cooked a cake that tasted like rainbows." + -- "The talking pineapple sang a lullaby to the sleepy giraffe." -- } -+ "The talking pineapple sang a lullaby to the sleepy giraffe."} - ; -``` - -# Output - -```js -() => ( - - - -); -() => () => - ( - - - - ); -() => () => () => - ( - - - - ); - -() =>
    Some text here
    ; -() => () =>
    Some text here
    ; -() => () => () =>
    Some text here
    ; - -() => ( -
    - Long long long long long, very very long text. And more text. Another text. -
    -); -() => () => - ( -
    - Long long long long long, very very long text. And more text. Another - text. -
    - ); -() => () => () => - ( -
    - Long long long long long, very very long text. And more text. Another - text. -
    - ); - - - {(We) => - "The purple monkey danced with a tambourine made of cheese." + - "The robot chef cooked a cake that tasted like rainbows." + - "The talking pineapple sang a lullaby to the sleepy giraffe." - } -; - - {(We) => (love) => - "The purple monkey danced with a tambourine made of cheese." + - "The robot chef cooked a cake that tasted like rainbows." + - "The talking pineapple sang a lullaby to the sleepy giraffe."} -; - - {(We) => (love) => (currying) => - "The purple monkey danced with a tambourine made of cheese." + - "The robot chef cooked a cake that tasted like rainbows." + - "The talking pineapple sang a lullaby to the sleepy giraffe."} -; -``` - - diff --git a/crates/biome_service/src/configuration/linter/rules.rs b/crates/biome_service/src/configuration/linter/rules.rs index 347990076c9f..25aa98327e6b 100644 --- a/crates/biome_service/src/configuration/linter/rules.rs +++ b/crates/biome_service/src/configuration/linter/rules.rs @@ -2786,6 +2786,15 @@ pub struct Nursery { #[bpaf(long("no-implicit-any-let"), argument("on|off|warn"), optional, hide)] #[serde(skip_serializing_if = "Option::is_none")] pub no_implicit_any_let: Option, + #[doc = "Disallow characters made with multiple code points in character class syntax."] + #[bpaf( + long("no-misleading-character-class"), + argument("on|off|warn"), + optional, + hide + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub no_misleading_character_class: Option, #[doc = "Disallow unused imports."] #[bpaf(long("no-unused-imports"), argument("on|off|warn"), optional, hide)] #[serde(skip_serializing_if = "Option::is_none")] @@ -2838,6 +2847,15 @@ pub struct Nursery { #[bpaf(long("use-regex-literals"), argument("on|off|warn"), optional, hide)] #[serde(skip_serializing_if = "Option::is_none")] pub use_regex_literals: Option, + #[doc = "Enforce using function types instead of object type with call signatures."] + #[bpaf( + long("use-shorthand-function-type"), + argument("on|off|warn"), + optional, + hide + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub use_shorthand_function_type: Option, #[doc = "Elements with ARIA roles must use a valid, non-abstract ARIA role."] #[bpaf(long("use-valid-aria-role"), argument("on|off|warn"), optional, hide)] #[serde(skip_serializing_if = "Option::is_none")] @@ -2860,6 +2878,9 @@ impl MergeWith for Nursery { if let Some(no_implicit_any_let) = other.no_implicit_any_let { self.no_implicit_any_let = Some(no_implicit_any_let); } + if let Some(no_misleading_character_class) = other.no_misleading_character_class { + self.no_misleading_character_class = Some(no_misleading_character_class); + } if let Some(no_unused_imports) = other.no_unused_imports { self.no_unused_imports = Some(no_unused_imports); } @@ -2884,6 +2905,9 @@ impl MergeWith for Nursery { if let Some(use_regex_literals) = other.use_regex_literals { self.use_regex_literals = Some(use_regex_literals); } + if let Some(use_shorthand_function_type) = other.use_shorthand_function_type { + self.use_shorthand_function_type = Some(use_shorthand_function_type); + } if let Some(use_valid_aria_role) = other.use_valid_aria_role { self.use_valid_aria_role = Some(use_valid_aria_role); } @@ -2899,12 +2923,13 @@ impl MergeWith for Nursery { } impl Nursery { const GROUP_NAME: &'static str = "nursery"; - pub(crate) const GROUP_RULES: [&'static str; 14] = [ + pub(crate) const GROUP_RULES: [&'static str; 16] = [ "noAriaHiddenOnFocusable", "noDefaultExport", "noDuplicateJsonKeys", "noEmptyBlockStatements", "noImplicitAnyLet", + "noMisleadingCharacterClass", "noUnusedImports", "noUnusedPrivateClassMembers", "noUselessLoneBlockStatements", @@ -2913,6 +2938,7 @@ impl Nursery { "useGroupedTypeImport", "useImportRestrictions", "useRegexLiterals", + "useShorthandFunctionType", "useValidAriaRole", ]; const RECOMMENDED_RULES: [&'static str; 6] = [ @@ -2927,11 +2953,11 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15]), ]; - const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 14] = [ + const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 16] = [ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), @@ -2946,6 +2972,8 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended(&self) -> bool { @@ -2987,51 +3015,61 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } } - if let Some(rule) = self.no_unused_imports.as_ref() { + if let Some(rule) = self.no_misleading_character_class.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_unused_private_class_members.as_ref() { + if let Some(rule) = self.no_unused_imports.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_useless_lone_block_statements.as_ref() { + if let Some(rule) = self.no_unused_private_class_members.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.use_await.as_ref() { + if let Some(rule) = self.no_useless_lone_block_statements.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.use_for_of.as_ref() { + if let Some(rule) = self.use_await.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.use_grouped_type_import.as_ref() { + if let Some(rule) = self.use_for_of.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_grouped_type_import.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.use_regex_literals.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.use_valid_aria_role.as_ref() { + if let Some(rule) = self.use_regex_literals.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } + if let Some(rule) = self.use_shorthand_function_type.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); + } + } + if let Some(rule) = self.use_valid_aria_role.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> IndexSet { @@ -3061,51 +3099,61 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } } - if let Some(rule) = self.no_unused_imports.as_ref() { + if let Some(rule) = self.no_misleading_character_class.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_unused_private_class_members.as_ref() { + if let Some(rule) = self.no_unused_imports.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_useless_lone_block_statements.as_ref() { + if let Some(rule) = self.no_unused_private_class_members.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.use_await.as_ref() { + if let Some(rule) = self.no_useless_lone_block_statements.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.use_for_of.as_ref() { + if let Some(rule) = self.use_await.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.use_grouped_type_import.as_ref() { + if let Some(rule) = self.use_for_of.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_grouped_type_import.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.use_regex_literals.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.use_valid_aria_role.as_ref() { + if let Some(rule) = self.use_regex_literals.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } + if let Some(rule) = self.use_shorthand_function_type.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); + } + } + if let Some(rule) = self.use_valid_aria_role.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -3119,7 +3167,7 @@ impl Nursery { pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 6] { Self::RECOMMENDED_RULES_AS_FILTERS } - pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 14] { + pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 16] { Self::ALL_RULES_AS_FILTERS } #[doc = r" Select preset rules"] @@ -3147,6 +3195,7 @@ impl Nursery { "noDuplicateJsonKeys" => self.no_duplicate_json_keys.as_ref(), "noEmptyBlockStatements" => self.no_empty_block_statements.as_ref(), "noImplicitAnyLet" => self.no_implicit_any_let.as_ref(), + "noMisleadingCharacterClass" => self.no_misleading_character_class.as_ref(), "noUnusedImports" => self.no_unused_imports.as_ref(), "noUnusedPrivateClassMembers" => self.no_unused_private_class_members.as_ref(), "noUselessLoneBlockStatements" => self.no_useless_lone_block_statements.as_ref(), @@ -3155,6 +3204,7 @@ impl Nursery { "useGroupedTypeImport" => self.use_grouped_type_import.as_ref(), "useImportRestrictions" => self.use_import_restrictions.as_ref(), "useRegexLiterals" => self.use_regex_literals.as_ref(), + "useShorthandFunctionType" => self.use_shorthand_function_type.as_ref(), "useValidAriaRole" => self.use_valid_aria_role.as_ref(), _ => None, } diff --git a/crates/biome_service/src/configuration/parse/json/rules.rs b/crates/biome_service/src/configuration/parse/json/rules.rs index a3840d0fb923..d2d671f50504 100644 --- a/crates/biome_service/src/configuration/parse/json/rules.rs +++ b/crates/biome_service/src/configuration/parse/json/rules.rs @@ -937,6 +937,13 @@ impl Deserializable for Nursery { diagnostics, ); } + "noMisleadingCharacterClass" => { + result.no_misleading_character_class = Deserializable::deserialize( + &value, + "noMisleadingCharacterClass", + diagnostics, + ); + } "noUnusedImports" => { result.no_unused_imports = Deserializable::deserialize(&value, "noUnusedImports", diagnostics); @@ -984,6 +991,13 @@ impl Deserializable for Nursery { diagnostics, ); } + "useShorthandFunctionType" => { + result.use_shorthand_function_type = Deserializable::deserialize( + &value, + "useShorthandFunctionType", + diagnostics, + ); + } "useValidAriaRole" => { result.use_valid_aria_role = Deserializable::deserialize( &value, @@ -1003,6 +1017,7 @@ impl Deserializable for Nursery { "noDuplicateJsonKeys", "noEmptyBlockStatements", "noImplicitAnyLet", + "noMisleadingCharacterClass", "noUnusedImports", "noUnusedPrivateClassMembers", "noUselessLoneBlockStatements", @@ -1011,6 +1026,7 @@ impl Deserializable for Nursery { "useGroupedTypeImport", "useImportRestrictions", "useRegexLiterals", + "useShorthandFunctionType", "useValidAriaRole", ], )); diff --git a/crates/biome_service/tests/invalid/hooks_incorrect_options.json.snap b/crates/biome_service/tests/invalid/hooks_incorrect_options.json.snap index b2ae35370b28..56b4b0b9b442 100644 --- a/crates/biome_service/tests/invalid/hooks_incorrect_options.json.snap +++ b/crates/biome_service/tests/invalid/hooks_incorrect_options.json.snap @@ -22,6 +22,7 @@ hooks_incorrect_options.json:6:5 deserialize ━━━━━━━━━━━ - noDuplicateJsonKeys - noEmptyBlockStatements - noImplicitAnyLet + - noMisleadingCharacterClass - noUnusedImports - noUnusedPrivateClassMembers - noUselessLoneBlockStatements @@ -30,6 +31,7 @@ hooks_incorrect_options.json:6:5 deserialize ━━━━━━━━━━━ - useGroupedTypeImport - useImportRestrictions - useRegexLiterals + - useShorthandFunctionType - useValidAriaRole diff --git a/crates/biome_service/tests/invalid/hooks_missing_name.json.snap b/crates/biome_service/tests/invalid/hooks_missing_name.json.snap index 479320a69bf3..09d7c3761915 100644 --- a/crates/biome_service/tests/invalid/hooks_missing_name.json.snap +++ b/crates/biome_service/tests/invalid/hooks_missing_name.json.snap @@ -22,6 +22,7 @@ hooks_missing_name.json:6:5 deserialize ━━━━━━━━━━━━━ - noDuplicateJsonKeys - noEmptyBlockStatements - noImplicitAnyLet + - noMisleadingCharacterClass - noUnusedImports - noUnusedPrivateClassMembers - noUselessLoneBlockStatements @@ -30,6 +31,7 @@ hooks_missing_name.json:6:5 deserialize ━━━━━━━━━━━━━ - useGroupedTypeImport - useImportRestrictions - useRegexLiterals + - useShorthandFunctionType - useValidAriaRole diff --git a/justfile b/justfile index 043944b9a231..dadb745e4470 100644 --- a/justfile +++ b/justfile @@ -5,6 +5,7 @@ alias f := format alias t := test alias r := ready alias l := lint +alias qt := test-quick # Installs the tools needed to develop @@ -101,6 +102,10 @@ test-transformation name: just _touch crates/biome_js_transform/tests/spec_tests.rs cargo test -p biome_js_transform -- {{snakecase(name)}} +# Run the quick_test for the given package. +test-quick package: + cargo test -p {{package}} --test quick_test -- quick_test --nocapture + # Alias for `cargo lint`, it runs clippy on the whole codebase lint: diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 650c77a60b4c..458b05558508 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -797,6 +797,10 @@ export interface Nursery { * Disallow use of implicit any type on variable declarations. */ noImplicitAnyLet?: RuleConfiguration; + /** + * Disallow characters made with multiple code points in character class syntax. + */ + noMisleadingCharacterClass?: RuleConfiguration; /** * Disallow unused imports. */ @@ -833,6 +837,10 @@ export interface Nursery { * Enforce the use of the regular expression literals instead of the RegExp constructor if possible. */ useRegexLiterals?: RuleConfiguration; + /** + * Enforce using function types instead of object type with call signatures. + */ + useShorthandFunctionType?: RuleConfiguration; /** * Elements with ARIA roles must use a valid, non-abstract ARIA role. */ @@ -1506,6 +1514,7 @@ export type Category = | "lint/nursery/noDuplicateJsonKeys" | "lint/nursery/noEmptyBlockStatements" | "lint/nursery/noImplicitAnyLet" + | "lint/nursery/noMisleadingCharacterClass" | "lint/nursery/noUnusedImports" | "lint/nursery/noUnusedPrivateClassMembers" | "lint/nursery/noUselessLoneBlockStatements" @@ -1516,6 +1525,7 @@ export type Category = | "lint/nursery/useImportRestrictions" | "lint/nursery/useRegexLiterals" | "lint/nursery/useValidAriaRole" + | "lint/nursery/useShorthandFunctionType" | "lint/performance/noAccumulatingSpread" | "lint/performance/noDelete" | "lint/security/noDangerouslySetInnerHtml" diff --git a/packages/@biomejs/biome/README.md b/packages/@biomejs/biome/README.md index 83079b7fd75f..2f2773afb734 100644 --- a/packages/@biomejs/biome/README.md +++ b/packages/@biomejs/biome/README.md @@ -24,6 +24,12 @@ [open-vsx-url]: https://open-vsx.org/extension/biomejs/biome + + +
    + +English | [简体中文](https://github.com/biomejs/biome/blob/main/packages/%40biomejs/biome/README.zh-CN.md) +
    **Biome** is a performant toolchain for web projects, it aims to provide developer tools to maintain the health of said projects. diff --git a/packages/@biomejs/biome/README.zh-CN.md b/packages/@biomejs/biome/README.zh-CN.md new file mode 100644 index 000000000000..d576b7d32592 --- /dev/null +++ b/packages/@biomejs/biome/README.zh-CN.md @@ -0,0 +1,120 @@ +

    + Biome - Toolchain of the web +

    + +
    + +[![Discord chat][discord-badge]][discord-url] +[![CI on main][ci-badge]][ci-url] +[![npm version][npm-badge]][npm-url] +[![VSCode version][vscode-badge]][vscode-url] +[![Open VSX version][open-vsx-badge]][open-vsx-url] + +[discord-badge]: https://badgen.net/discord/online-members/BypW39g6Yc?icon=discord&label=discord&color=green +[discord-url]: https://discord.gg/BypW39g6Yc +[ci-badge]: https://github.com/biomejs/biome/actions/workflows/main.yml/badge.svg +[ci-url]: https://github.com/biomejs/biome/actions/workflows/main.yml +[npm-badge]: https://badgen.net/npm/v/@biomejs/biome?icon=npm&color=green&label=%40biomejs%2Fbiome +[npm-url]: https://www.npmjs.com/package/@biomejs/biome/v/latest +[vscode-badge]: https://badgen.net/vs-marketplace/v/biomejs.biome?label=vscode&icon=visualstudio&color=green +[vscode-url]: https://marketplace.visualstudio.com/items?itemName=biomejs.biome +[open-vsx-badge]: https://badgen.net/open-vsx/version/biomejs/biome?label=open-vsx&color=green +[open-vsx-url]: https://open-vsx.org/extension/biomejs/biome + + +
    + +
    + +[English](./README.md) | 简体中文 + +
    + +**Biome** 是一个用于网络项目的高性能工具链,旨在为开发者提供维护这些项目的工具。 + +**Biome 是一个[快速的格式化器](./benchmark#formatting)**,适用于 _JavaScript_、_TypeScript_、_JSX_ 和 _JSON_,与 _Prettier_ 的兼容性达到了 **[96%](https://console.algora.io/challenges/prettier)**。 + +**Biome 是一个[高性能的 linter](https://github.com/biomejs/biome/tree/main/benchmark#linting)**,适用于 _JavaScript_、_TypeScript_ 和 _JSX_,包含了来自 ESLint、TypeSCript ESLint 和 [其他来源](https://github.com/biomejs/biome/discussions/3)的 **[超过 170 条规则](https://biomejs.dev/zh-cn/linter/rules/)**。 +它**输出详细且有上下文的诊断信息**,帮助你改进代码,成为一个更好的程序员! + +**Biome** 从一开始就设计为[在编辑器中交互式使用](https://biomejs.dev/zh-cn/guides/integrate-in-editor/)。 +你在编写代码时,它可以格式化和 lint 不规范的代码。 + +### 安装 + +```shell +npm install --save-dev --save-exact @biomejs/biome +``` + +### 使用 + +```shell +# 格式化文件 +npx @biomejs/biome format --write ./src + +# lint 文件 +npx @biomejs/biome lint ./src + +# 运行格式化,lint 等,并应用安全的建议 +npx @biomejs/biome check --apply ./src + +# 在 CI 环境中检查所有文件是否符合格式,lint 等 +npx @biomejs/biome ci ./src +``` + +如果你想在不安装的情况下试用 Biome,可以使用[在线 playground](https://biomejs.dev/playground/),它被编译为 WebAssembly。 + +## 文档 + +查看我们的[主页][biomejs]以了解更多关于 Biome 的信息, +或者直接前往[入门指南][getting-started]开始使用 Biome。 + +## 更多关于 Biome + +**Biome** 有合理的默认设置,不需要配置。 + +**Biome** 旨在支持[所有主要的现代网络开发语言][language-support]。 + +**Biome** [不需要 Node.js](https://biomejs.dev/zh-cn/guides/manual-installation/)就可以运行。 + +**Biome** 有一流的 LSP 支持,具有精密的解析器,可以完全保真地表示源文本,并具有顶级的错误恢复能力。 + +**Biome** 统一了以前分散的功能。基于共享的基础,我们可以提供一个处理代码、显示错误、并行工作、缓存和配置的一致体验。 + +阅读更多关于我们的[项目理念][biome-philosophy]。 + +**Biome** 采用 [MIT 许可](https://github.com/biomejs/biome/tree/main/LICENSE-MIT) 或 [Apache 2.0 许可](https://github.com/biomejs/biome/tree/main/LICENSE-APACHE),并在 [贡献者公约行为准则](https://github.com/biomejs/biome/tree/main/CODE_OF_CONDUCT.md) 下进行管理。 + +## 赞助商 + +### 金牌赞助商 + + + + + + + +
    + +
    + +### 铜牌赞助商 + + + + + + + +
    + +
    + +[bench]: https://github.com/biomejs/biome/blob/main/benchmark/README.md +[biomejs]: https://biomejs.dev/zh-cn/ +[biome-philosophy]: https://biomejs.dev/zh-cn/internals/philosophy/ +[language-support]: https://biomejs.dev/zh-cn/internals/language-support/ +[getting-started]: https://biomejs.dev/zh-cn/guides/getting-started/ diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 11133c005b1e..b410a537e0fe 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1147,6 +1147,13 @@ { "type": "null" } ] }, + "noMisleadingCharacterClass": { + "description": "Disallow characters made with multiple code points in character class syntax.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noUnusedImports": { "description": "Disallow unused imports.", "anyOf": [ @@ -1207,6 +1214,13 @@ { "type": "null" } ] }, + "useShorthandFunctionType": { + "description": "Enforce using function types instead of object type with call signatures.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "useValidAriaRole": { "description": "Elements with ARIA roles must use a valid, non-abstract ARIA role.", "anyOf": [ diff --git a/website/astro.config.ts b/website/astro.config.ts index 98a2a5f437f9..7cc6d7587ccc 100644 --- a/website/astro.config.ts +++ b/website/astro.config.ts @@ -126,6 +126,10 @@ export default defineConfig({ label: "日本語", lang: "ja", }, + "zh-cn": { + label: "简体中文", + lang: "zh-CN", + }, }, sidebar: [ { label: "Home", link: "/" }, diff --git a/website/src/components/generated/NumberOfRules.astro b/website/src/components/generated/NumberOfRules.astro index 59aabab17f0b..c537c48e13f3 100644 --- a/website/src/components/generated/NumberOfRules.astro +++ b/website/src/components/generated/NumberOfRules.astro @@ -1,2 +1,2 @@ -

    Biome's linter has a total of 179 rules

    \ No newline at end of file +

    Biome's linter has a total of 181 rules

    \ No newline at end of file diff --git a/website/src/content/blog/annoucing-biome.mdx b/website/src/content/blog/annoucing-biome.mdx index 34fe27582f7e..d0351fabe2e9 100644 --- a/website/src/content/blog/annoucing-biome.mdx +++ b/website/src/content/blog/annoucing-biome.mdx @@ -169,3 +169,7 @@ We will eventually sunset the `rome.json` configuration file for `biome.json`, b - [Strager](https://github.com/strager): his inputs and constructive criticisms to the project are what helped Biome to arrive to this point; - [Boshen](https://github.com/Boshen): one of the greatest admirers of the project since the Rust rewrite; he joined the Biome community to learn from us and contribute as much as possible. He now leads a similar project to Biome, [oxc](https://github.com/web-infra-dev/oxc). Check it out. - [Micha](https://github.com/MichaReiser): ex-employee of Rome Tools Inc., he is now a full-time developer of the project [Ruff](https://github.com/astral-sh/ruff), he gave a lot of good pieces of advice, and he was a good listener when I was struggling to make the right decisions. + +## Translations + +- [中文翻译: 宣布 Biome](https://juejin.cn/post/7308539123538608165) diff --git a/website/src/content/blog/biome-v1.mdx b/website/src/content/blog/biome-v1.mdx index d200c538efe6..0f5f1ca5de52 100644 --- a/website/src/content/blog/biome-v1.mdx +++ b/website/src/content/blog/biome-v1.mdx @@ -323,6 +323,10 @@ Big thank you to the following contributors: - [ddanielsantos](https://github.com/ddanielsantos), for their first contribution to the project; - [nikeee](https://github.com/nikeee), for their first contribution to the project; +## Translations + +- [中文翻译: Biome v1版本](https://juejin.cn/post/7308539123538624549) + [//]: # "KEEP IT, NEEDED FOR THE FUTURE" [//]: # [//]: # diff --git a/website/src/content/blog/biome-wins-prettier-challenge.md b/website/src/content/blog/biome-wins-prettier-challenge.md index 9d6bdabba002..bf95e3892254 100644 --- a/website/src/content/blog/biome-wins-prettier-challenge.md +++ b/website/src/content/blog/biome-wins-prettier-challenge.md @@ -403,3 +403,7 @@ In the next months we will focus on: - Publishing a Roadmap. **Keep an eye on it**, it will involve a lot of **interesting** work. - Rebranding the website with a new logo. - Translate the website in Japanese. + +## Translations + +- [中文翻译: Biome赢得了Prettier挑战](https://juejin.cn/post/7308643782375768118) diff --git a/website/src/content/docs/linter/rules/index.mdx b/website/src/content/docs/linter/rules/index.mdx index 1a4359c5b100..a4d62b88a864 100644 --- a/website/src/content/docs/linter/rules/index.mdx +++ b/website/src/content/docs/linter/rules/index.mdx @@ -232,6 +232,7 @@ Rules that belong to this group are not subject to semantic versionany type on variable declarations. | | +| [noMisleadingCharacterClass](/linter/rules/no-misleading-character-class) | Disallow characters made with multiple code points in character class syntax. | 🔧 | | [noUnusedImports](/linter/rules/no-unused-imports) | Disallow unused imports. | 🔧 | | [noUnusedPrivateClassMembers](/linter/rules/no-unused-private-class-members) | Disallow unused private class members | ⚠️ | | [noUselessLoneBlockStatements](/linter/rules/no-useless-lone-block-statements) | Disallow unnecessary nested block statements. | ⚠️ | @@ -240,4 +241,5 @@ Rules that belong to this group are not subject to semantic versionimport type when an import only has specifiers with type qualifier. | ⚠️ | | [useImportRestrictions](/linter/rules/use-import-restrictions) | Disallows package private imports. | | | [useRegexLiterals](/linter/rules/use-regex-literals) | Enforce the use of the regular expression literals instead of the RegExp constructor if possible. | ⚠️ | +| [useShorthandFunctionType](/linter/rules/use-shorthand-function-type) | Enforce using function types instead of object type with call signatures. | 🔧 | | [useValidAriaRole](/linter/rules/use-valid-aria-role) | Elements with ARIA roles must use a valid, non-abstract ARIA role. | ⚠️ | diff --git a/website/src/content/docs/linter/rules/no-misleading-character-class.md b/website/src/content/docs/linter/rules/no-misleading-character-class.md new file mode 100644 index 000000000000..9d932840d116 --- /dev/null +++ b/website/src/content/docs/linter/rules/no-misleading-character-class.md @@ -0,0 +1,124 @@ +--- +title: noMisleadingCharacterClass (since vnext) +--- + +**Diagnostic Category: `lint/nursery/noMisleadingCharacterClass`** + +:::caution +This rule is part of the [nursery](/linter/rules/#nursery) group. +::: + +Disallow characters made with multiple code points in character class syntax. + +Unicode includes the characters which are made with multiple code points. e.g. Á, 🇯🇵, 👨‍👩‍👦. +A RegExp character class `/[abc]/` cannot handle characters with multiple code points. +For example, the character `❇️` consists of two code points: `❇` (U+2747) and `VARIATION SELECTOR-16` (U+FE0F). +If this character is in a RegExp character class, it will match to either `❇` or `VARIATION SELECTOR-16` rather than `❇️`. +This rule reports the regular expressions which include multiple code point characters in character class syntax. + +Source: https://eslint.org/docs/latest/rules/no-misleading-character-class + +## Examples + +### Invalid + +```jsx +/^[Á]$/u; +``` + +

    nursery/noMisleadingCharacterClass.js:1:1 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━
    +
    +   Unexpected combined character in the character class.
    +  
    +  > 1 │ /^[Á]$/u;
    +   ^^^^^^^^
    +    2 │ 
    +  
    +
    + +```jsx +/^[❇️]$/u; +``` + +
    nursery/noMisleadingCharacterClass.js:1:1 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━
    +
    +   Unexpected combined character in the character class.
    +  
    +  > 1 │ /^[❇️]$/u;
    +   ^^^^^^^^
    +    2 │ 
    +  
    +
    + +```jsx +/^[👶🏻]$/u; +``` + +
    nursery/noMisleadingCharacterClass.js:1:1 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━
    +
    +   Unexpected modified Emoji in the character class. 
    +  
    +  > 1 │ /^[👶🏻]$/u;
    +   ^^^^^^^^^^^
    +    2 │ 
    +  
    +
    + +```jsx +/^[🇯🇵]$/u; +``` + +
    nursery/noMisleadingCharacterClass.js:1:1 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━
    +
    +   Regional indicator symbol characters should not be used in the character class.
    +  
    +  > 1 │ /^[🇯🇵]$/u;
    +   ^^^^^^^^^
    +    2 │ 
    +  
    +
    + +```jsx +/^[👨‍👩‍👦]$/u; +``` + +
    nursery/noMisleadingCharacterClass.js:1:1 lint/nursery/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━
    +
    +   Unexpected joined character sequence in character class.
    +  
    +  > 1 │ /^[👨‍👩‍👦]$/u;
    +   ^^^^^^^^^^^^^
    +    2 │ 
    +  
    +
    + +```jsx +/^[👍]$/; // surrogate pair without u flag +``` + +
    nursery/noMisleadingCharacterClass.js:1:1 lint/nursery/noMisleadingCharacterClass  FIXABLE  ━━━━━━━━━━
    +
    +   Unexpected surrogate pair in character class. Use the 'u' flag.
    +  
    +  > 1 │ /^[👍]$/; // surrogate pair without u flag
    +   ^^^^^^^^
    +    2 │ 
    +  
    +   Safe fix: Add unicode u flag to regex
    +  
    +    1 │ /^[👍]$/u;·//·surrogate·pair·without·u·flag
    +          +                                  
    +
    + +## Valid + +```jsx +/^[abc]$/; +/^[👍]$/u; +/^[\q{👶🏻}]$/v; +``` + +## Related links + +- [Disable a rule](/linter/#disable-a-lint-rule) +- [Rule options](/linter/#rule-options) diff --git a/website/src/content/docs/linter/rules/use-exhaustive-dependencies.md b/website/src/content/docs/linter/rules/use-exhaustive-dependencies.md index 945e900c2cdc..ae33b4a886ba 100644 --- a/website/src/content/docs/linter/rules/use-exhaustive-dependencies.md +++ b/website/src/content/docs/linter/rules/use-exhaustive-dependencies.md @@ -197,7 +197,7 @@ function component() { ``` ```jsx -import { useEffect } from "react"; +import { useEffect, useState } from "react"; function component() { const [name, setName] = useState(); diff --git a/website/src/content/docs/linter/rules/use-shorthand-function-type.md b/website/src/content/docs/linter/rules/use-shorthand-function-type.md new file mode 100644 index 000000000000..8098c61b2e54 --- /dev/null +++ b/website/src/content/docs/linter/rules/use-shorthand-function-type.md @@ -0,0 +1,121 @@ +--- +title: useShorthandFunctionType (since vnext) +--- + +**Diagnostic Category: `lint/nursery/useShorthandFunctionType`** + +:::caution +This rule is part of the [nursery](/linter/rules/#nursery) group. +::: + +Enforce using function types instead of object type with call signatures. + +TypeScript allows for two common ways to declare a type for a function: + +- Function type: `() => string` +- Object type with a signature: `{ (): string }` + +The function type form is generally preferred when possible for being more succinct. + +This rule suggests using a function type instead of an interface or object type literal with a single call signature. + +Source: https://typescript-eslint.io/rules/prefer-function-type/ + +## Examples + +### Invalid + +```ts +interface Example { + (): string; +} +``` + +
    nursery/useShorthandFunctionType.js:2:3 lint/nursery/useShorthandFunctionType  FIXABLE  ━━━━━━━━━━━━
    +
    +   Use a function type instead of a call signature.
    +  
    +    1 │ interface Example {
    +  > 2 │   (): string;
    +     ^^^^^^^^^^^
    +    3 │ }
    +    4 │ 
    +  
    +   Types containing only a call signature can be shortened to a function type.
    +  
    +   Safe fix: Alias a function type instead of using an interface with a call signature.
    +  
    +    1  - interface·Example·{
    +    2  - ··():·string;
    +    3  - }
    +      1+ type·Example·=·()·=>·string
    +    4 2  
    +  
    +
    + +```ts +function foo(example: { (): number }): number { + return example(); +} +``` + +
    nursery/useShorthandFunctionType.js:1:25 lint/nursery/useShorthandFunctionType  FIXABLE  ━━━━━━━━━━━
    +
    +   Use a function type instead of a call signature.
    +  
    +  > 1 │ function foo(example: { (): number }): number {
    +                           ^^^^^^^^^^
    +    2 │   return example();
    +    3 │ }
    +  
    +   Types containing only a call signature can be shortened to a function type.
    +  
    +   Safe fix: Use a function type instead of an object type with a call signature.
    +  
    +    1  - function·foo(example:·{·():·number·}):·number·{
    +      1+ function·foo(example:·()·=>·number):·number·{
    +    2 2    return example();
    +    3 3  }
    +  
    +
    + +## Valid + +```ts +type Example = () => string; +``` + +```ts +function foo(example: () => number): number { + return bar(); +} +``` + +```ts +// returns the function itself, not the `this` argument. +type ReturnsSelf2 = (arg: string) => ReturnsSelf; +``` + +```ts +interface Foo { + bar: string; +} +interface Bar extends Foo { + (): void; +} +``` + +```ts +// multiple call signatures (overloads) is allowed: +interface Overloaded { + (data: string): number; + (id: number): string; +} +// this is equivalent to Overloaded interface. +type Intersection = ((data: string) => number) & ((id: number) => string); +``` + +## Related links + +- [Disable a rule](/linter/#disable-a-lint-rule) +- [Rule options](/linter/#rule-options) diff --git a/website/src/content/docs/zh-cn/analyzer/index.mdx b/website/src/content/docs/zh-cn/analyzer/index.mdx new file mode 100644 index 000000000000..2783e2cc8607 --- /dev/null +++ b/website/src/content/docs/zh-cn/analyzer/index.mdx @@ -0,0 +1,133 @@ +--- +title: 分析器 +description: Biome分析器提供的功能 +--- + +Biome的分析器提供了一系列用户可以利用的功能。 + +## 导入排序 + +Biome允许使用[自然排序](https://en.wikipedia.org/wiki/Natural_sort_order)对导入语句进行排序。 + +此功能默认启用,但可以通过配置选择禁用: + +```json title="biome.json" +{ + "organizeImports": { + "enabled": false + } +} +``` + +### 导入的排序方式 + +导入语句按照“距离”排序。与用户“距离”较远的模块放在顶部,与用户“距离”较近的模块放在底部: + +1. 通过`bun:`协议导入的模块。这适用于编写Bun运行的代码; +2. 显式使用`node:`协议导入的内置Node.js模块和常见的Node内置模块,如`assert`; +3. 通过`npm:`协议导入的模块。这适用于编写Deno运行的代码; +4. 通过URL导入的模块; +5. 从库导入的模块; +6. 通过绝对导入导入的模块; +7. 通过以`#`为前缀的名称导入的模块。这适用于使用[Node的子路径导入](https://nodejs.org/api/packages.html#subpath-imports)时; +8. 通过相对导入导入的模块; +9. 无法通过前面的标准确定的模块; + +例如,给定以下代码: + +```ts title="example.ts" +import uncle from "../uncle"; +import sibling from "./sibling"; +import express from "npm:express"; +import imageUrl from "url:./image.png"; +import assert from "node:assert"; +import aunt from "../aunt"; +import { VERSION } from "https://deno.land/std/version.ts"; +import { mock, test } from "node:test"; +import { expect } from "bun:test"; +import { internal } from "#internal"; +import { secret } from "/absolute/path"; +import React from "react"; +``` + +它们将按照以下方式排序: + +```ts title="example.ts" +import { expect } from "bun:test"; +import assert from "node:assert"; +import { mock, test } from "node:test"; +import express from "npm:express"; +import { VERSION } from "https://deno.land/std/version.ts"; +import React from "react"; +import { secret } from "/absolute/path"; +import { internal } from "#internal"; +import aunt from "../aunt"; +import uncle from "../uncle"; +import sibling from "./sibling"; +import imageUrl from "url:./image.png"; +``` + +您可以通过两种方式应用排序:通过[CLI](#通过cli进行导入排序)或[VSCode扩展](#通过vscode扩展进行导入排序)。 + +### 分组导入 + +在特定顺序下导入语句是很常见的,特别是在前端项目中工作时,您导入CSS文件: + +```js title="example.js" +import "../styles/reset.css"; +import "../styles/layout.css"; +import { Grid } from "../components/Grid.jsx"; +``` + +另一个常见的情况是导入polyfill或shim文件,这需要在文件顶部保留: + +```js title="example.js" +import "../polyfills/array/flatMap"; +import { functionThatUsesFlatMap } from "./utils.js"; +``` + +在这些情况下,Biome将对所有这些导入语句进行排序,可能会导致顺序**破坏**应用程序。 + +为了避免这种情况,创建一个导入语句的“组”。通过添加一个**新行**来分隔这些组。 + +这样做,Biome将仅对属于同一组的导入语句进行排序: + +```js title="example.js" +// 组1,仅对这两个文件进行排序 +import "../styles/reset.css"; +import "../styles/layout.css"; + +// 组2,仅对这个文件进行排序 +import { Grid } from "../components/Grid.jsx"; +``` + +```js title="example.js" +// 组1,polyfill/shim +import "../polyfills/array/flatMap"; + +// 组2,需要polyfill/shim的文件 +import { functionThatUsesFlatMap } from "./utils.js"; +``` + +### 通过CLI进行导入排序 + +使用`check`命令,带有`--apply`选项。 + +```shell +biome check --apply-unsafe ./path/to/src +``` + +### 通过VSCode扩展进行导入排序 + +Biome VS Code扩展通过“Organize Imports”代码操作支持导入排序。 +默认情况下,可以使用+Alt+O键盘快捷键运行此操作,也可以通过*命令面板*(Ctrl/++P)选择*Organize Imports*来访问它。 + +如果希望自动在保存时运行该操作而不是手动调用它,请将以下内容添加到编辑器配置中: + +```json title="biome.json" +{ + "editor.codeActionsOnSave": { + "source.organizeImports.biome": true + } +} +``` diff --git a/website/src/content/docs/zh-cn/formatter/index.mdx b/website/src/content/docs/zh-cn/formatter/index.mdx new file mode 100644 index 000000000000..45f4a538580c --- /dev/null +++ b/website/src/content/docs/zh-cn/formatter/index.mdx @@ -0,0 +1,280 @@ +--- +title: 格式化程序 +description: 如何使用Biome格式化程序。 +--- + +import PackageManagerBiomeCommand from "@src/components/PackageManagerBiomeCommand.astro"; + +Biome是一款具有停止所有关于样式的持续辩论的目标的主观格式化程序。它遵循类似于[Prettier的哲学](https://prettier.io/docs/en/option-philosophy.html),只支持少量选项,以避免关于样式的辩论转变为关于Biome选项的辩论。它故意[抵制添加新选项的冲动](https://github.com/prettier/prettier/issues/40),以防止团队中的[琐事讨论](https://en.wikipedia.org/wiki/Law_of_triviality),以便他们可以专注于真正重要的事情。 + +## 选项 + +Biome支持的与语言无关的选项有: + +- 缩进样式(默认为`tab`):使用空格或制表符进行缩进 +- 制表符宽度(默认为`2`):每个缩进级别的空格数 +- 行宽(默认为`80`):Biome在此列宽处换行代码 + +还有其他针对特定语言的格式化选项。有关详细信息,请参阅[配置](/reference/configuration)选项。 + +## 使用CLI格式化程序 + +默认情况下,格式化程序**检查**代码并在格式化发生更改时发出诊断: + + + +如果要**应用**新的格式化,请使用`--write`选项: + + + +使用`--help`标志来了解可用选项: + + + +或者查看[CLI参考部分](/reference/cli#biomeformat)。 + +## 配置 + +您可以使用`biome.json`来[配置Biome](/reference/configuration/#formatter)。应用以下默认值: + +```json title="biome.json" +{ + "formatter": { + "enabled": true, + "formatWithErrors": false, + "indentStyle": "tab", + "indentWidth": 2, + "lineWidth": 80, + "ignore": [] + } +} +``` + +## 忽略代码 + +有时,格式化后的代码并不理想。 + +对于这些情况,您可以使用格式化抑制注释: + +```js title="example.js" +// biome-ignore format: +``` + +示例: + +```js title="example.js" +const expr = + // biome-ignore format: the array should not be formatted + [ + (2 * n) / (r - l), + 0, + (r + l) / (r - l), + 0, + 0, + (2 * n) / (t - b), + (t + b) / (t - b), + 0, + 0, + 0, + -(f + n) / (f - n), + -(2 * f * n) / (f - n), + 0, + 0, + -1, + 0, + ]; +``` + +## 与Prettier的差异 + +与Prettier存在一些差异。 + +### 类属性上的虚假或重复修饰符 + +输入 + +```ts title="example.ts" +// 多个可访问性修饰符 +class Foo { + private a = 1; +} + +// 带有函数体的声明函数 +declare function foo() {}; + +// 无效使用abstract +class Bar { + abstract foo; +} + +// 重复的Readonly +class Read { + readonly x: number; +} +``` + +差异 + +```ts title="example.ts" del={3, 8, 13, 19} ins={4, 9, 14, 20} +// 多个可访问性修饰符 +class Foo { + private a = 1; + private a = 1; +} + +// 带有函数体的声明函数 +declare function foo() {}; +declare function foo() {}; + +// 无效使用abstract +class Bar { + abstract foo; + abstract foo; +} + +// 重复的Readonly +class Read { + readonly x: number; + readonly x: number; +} +``` + +#### 原因 + +Prettier基于Babel的JS/TS解析非常宽松,并且允许忽略多个错误。Biome的解析器故意比Prettier更严格,正确断言这些测试中的语句是无效的。函数的重复修饰符在语义上无效,函数声明不允许有主体,非抽象类不能有抽象属性等。 + +在Prettier中,这些错误不被视为解析错误,并且AST仍然以适当的节点构建出来。在格式化时,Prettier只是将这些节点视为正常节点并相应地进行格式化。 + +在Biome中,解析错误会导致一个Bogus节点,它可以包含任意数量的有效节点、无效节点和/或原始令牌。在格式化时,Biome将Bogus节点视为纯文本,将其原样打印到生成的代码中,而不进行任何格式化,因为尝试进行格式化可能是不正确的并引起语义更改。 + +对于类属性,Prettier的当前解析策略还使用布尔字段表示修饰符,这意味着每种修饰符只能出现一次(可访问性修饰符存储为单个字符串)。在打印时,Prettier只查看布尔列表,并决定要重新打印哪些修饰符。Biome相反保留修饰符列表,这意味着重复的修饰符仍然存在并可进行分析(因此解析错误消息中有关重复修饰符和排序的信息)。在打印出Bogus节点时,此列表仍然保持不变,打印未格式化的文本会导致这些修饰符继续存在。 + +Biome可以解决这个问题的方法。一种可能性是在格式化时尝试解释Bogus节点并构建有效节点。如果可以构建有效节点,则像正常节点一样进行格式化,否则按原样打印Bogus文本,就像当前所做的那样。然而,这很混乱,并引入了一种形式的解析逻辑到格式化程序中,这实际上没有意义。 + +另一个选择是在解析器中引入一种形式的“语法上有效的Bogus节点”,它接受这些纯语义错误(重复修饰符、非抽象类中的抽象属性)。它将继续像正常一样构建节点(实际上与Prettier的行为相匹配),但将它们存储在一个新的Bogus节点中,其中包括诊断信息。在格式化时,这些特殊的Bogus节点将尝试格式化内部节点,然后在出现错误时回退(现有的`format_or_verbatim`实用程序已经执行此操作)。这样可以保持解析和格式化逻辑彼此分离,但会使解析器更复杂,允许考虑无效状态为半有效。 + +### Prettier不会取消引用一些在JavaScript中有效的对象属性。 + +```js title="example.js" +const obj = { + a: true, + b: true, + "𐊧": true, +}; +``` + +差异 + +```js title="exmaple.js" del={4} ins={5} +const obj = { + a: true, + b: true, + "𐊧": true, + 𐊧: true, +}; +``` + +#### 原因 + +Prettier和Biome都会取消引用在JavaScript中有效的对象和类属性。Prettier [仅取消引用有效的ES5标识符](https://github.com/prettier/prettier/blob/a5d502513e5de4819a41fd90b9be7247146effc7/src/language-js/utils/index.js#L646)。 + +在ES2015已经广泛应用的生态系统中,这似乎是一种遗留的限制。因此,我们决定在这里偏离,并取消引用ES2015+中的所有有效的JavaScript标识符。 + +一个可能的解决方法是引入一个配置来设置项目使用的ECMAScript版本。然后,我们可以根据该版本调整取消引用行为。将ECMAScript版本设置为“ES5”可以与Prettier的行为相匹配。 + +### Prettier对计算键中的赋值具有不一致的行为。 + +输入 + +```js title="example.js" +a = { + [(this.resource = resource)]: 1, +}; +``` + +差异 + +```js title="example.js" del={2} ins={3} +a = { + [(this.resource = resource)]: 1, + [(this.resource = resource)]: 1, +}; +``` + +#### 原因 + +Prettier和Biome在某些情况下,会在括号中包含一些赋值表达式,特别是在条件语句中。这样可以识别可能是比较的表达式。 + +Prettier的行为是不一致的,因为它在对象属性的计算键中添加括号,但在类属性的计算键中不添加括号,如下面的示例所示: + +输入 + +```js title="example.js" +a = { + [(x = 0)]: 1, +}; + +class C { + [x = 0] = 1; +} +``` + +输出 + +```js title="example.js" +a = { + [(x = 0)]: 1, +}; + +class C { + [x = 0] = 1; +} +``` + +[Playground链接](https://biomejs.dev/playground?enabledLinting=false&code=YQAgAD0AIAB7AAoAIAAgAFsAeAAgAD0AIAAwAF0AOgAgADEALAAKAH0ACgAKAGMAbABhAHMAcwAgAEMAIAB7AAoAIAAgACAAIABbAHgAIAA9ACAAMABdACAAPQAgADEACgB9AAoA) + +为了保持一致,我们决定与Prettier分歧,并省略括号。或者,我们可以在对象或类的计算键中包含任何赋值。 + +### Prettier接受接口的类型参数中的错误修饰符。 + +输入 + +```ts title="example.js" +interface L {} +``` + +差异 + +```ts title="example.js" del={1} ins={2} +interface L {} +interface L {} +``` + +#### 原因 + +如前所述,Prettier基于Babel的JS/TS解析非常宽松,允许忽略多个错误。Biome的解析器故意比Prettier更严格,正确断言接口的类型参数不允许使用`const`修饰符。 + +在Prettier中,这个错误不被视为解析错误,AST仍然以适当的节点构建。在格式化时,Prettier会将这些节点视为正常节点并进行相应的格式化。 + +在Biome中,解析错误会导致Bogus节点,它可以包含任意数量的有效节点、无效节点和/或原始令牌。在格式化时,Biome将Bogus节点视为纯文本,将其原样打印到生成的代码中,而不进行任何格式化,因为尝试格式化可能是不正确的并引起语义更改。 + +### Prettier在箭头函数的类型参数中添加尾随逗号,即使不需要。 + +输入 + +```tsx title="example.tsx" +() => {}; +``` + +差异 + +```tsx title="example.tsx" del={1} ins={2} +() => {}; +() => {}; +``` + +#### 原因 + +在某些特定情况下,箭头函数的类型参数列表需要一个尾随逗号,以区分它与JSX元素。当提供默认类型时,不需要尾随逗号。在这里,我们与Prettier分歧,因为我们认为它更好地尊重了Prettier的原始意图,即仅在必要时添加尾随逗号。 + +或者,我们可以在对象或类的计算键中包含任何赋值。 diff --git a/website/src/content/docs/zh-cn/guides/big-projects.mdx b/website/src/content/docs/zh-cn/guides/big-projects.mdx new file mode 100644 index 000000000000..fd14795f109f --- /dev/null +++ b/website/src/content/docs/zh-cn/guides/big-projects.mdx @@ -0,0 +1,90 @@ +--- +title: 在大型项目中使用Biome +description: 一份关于如何在大型项目中设置Biome的小指南 +--- + +Biome可以提供一些工具,帮助您在大型项目中正确使用它,例如包含多个项目的单体库或工作区。 + +## 使用多个配置文件 + +当您使用Biome的功能-无论是使用CLI还是LSP时-工具会使用当前工作目录查找最近的配置文件。 + +如果Biome在那里找不到配置文件,它会**向上遍历**文件系统的目录,直到找到一个配置文件。 + +您可以利用此功能基于项目/文件夹应用不同的设置。 + +假设我们有一个包含后端应用和新前端应用的项目。 + +``` +app +├── backend +│ ├── biome.json +│ └── package.json +└── frontend + ├── biome.json + ├── legacy-app + │ └── package.json + └── new-app + └── package.json +``` + +这意味着当您从文件`app/backend/package.json`运行脚本时,Biome将使用配置文件`app/backend/biome.json`。 + +当您从`app/frontend/legacy-app/package.json`或`app/frontend/new-app/package.json`运行脚本时,Biome将使用配置文件`app/frontend/biome.json`。 + +## 共享配置 + +可以使用[`extends`](/reference/configuration#extends)配置选项将选项分解到不同的文件中。 + +假设我们有以下要求: + +- `legacy-app`必须使用空格进行格式化; +- `backend`和`new-app`必须使用制表符进行格式化; +- 所有应用程序都必须使用行宽为120; +- `backend`应用程序需要一些额外的代码检查; + +我们首先在`app/biome.json`中创建一个新的配置文件,并将共享选项放在那里: + +```json title="app/biome.json" +{ + "formatter": { + "enabled": true, + "lineWidth": 120 + } +} +``` + +现在让我们**移动**`app/frontend/biome.json`到`app/frontend/legacy-app/`,因为我们需要在那里使用不同的格式化方式。 + +```json title="app/frontend/legacy-app/biome.json" +{ + "formatter": { + "indentStyle": "space" + } +} +``` + +然后,我们告诉Biome从主要的`app/biome.json`文件继承所有选项,使用`extends`属性: + +```json title="app/frontend/legacy-app/biome.json" ins={2} +{ + "extends": ["../../biome.json"], + "formatter": { + "indentStyle": "space" + } +} +``` + +让我们跳到`app/backend/biome.json`,在那里我们需要启用代码检查: + +```json title="app/backend/biome.json" +{ + "extends": ["../biome.json"], + "linter": { + "enabled": "true", + "rules": { + "recommended": true + } + } +} +``` diff --git a/website/src/content/docs/zh-cn/guides/getting-started.mdx b/website/src/content/docs/zh-cn/guides/getting-started.mdx new file mode 100644 index 000000000000..687161a6d0d9 --- /dev/null +++ b/website/src/content/docs/zh-cn/guides/getting-started.mdx @@ -0,0 +1,99 @@ +--- +title: 入门指南 +description: 学习如何使用Biome设置新项目。 +--- + +import PackageManagerBiomeCommand from "@src/components/PackageManagerBiomeCommand.astro"; +import PackageManagerCommand from "@src/components/PackageManagerCommand.astro"; + +## 系统要求 + +- Windows(包括WSL)、macOS或Linux +- x86_64或ARM64 +- Node.js v14.18或更新版本(如果使用独立可执行文件则不适用) + +## 安装 + +下载Biome的最快方法是使用`npm`或您偏好的包管理器。如果您想在不安装Node.js的情况下使用Biome,CLI也可作为[独立可执行文件](/guides/manual-installation)使用。 + +要安装Biome,请在包含`package.json`文件的目录中运行以下命令。 + + + +> **注意**:也可以全局安装Biome,而不是本地安装。但不推荐这样做。 + +强烈建议在安装Biome时不要使用范围运算符。有关更多信息,请查看[版本页面](/internals/versioning/)。 + +## 配置 + +我们建议为每个项目创建一个`biome.json`配置文件。它可以避免每次运行命令时重复输入CLI选项,并确保Biome在编辑器中应用相同的配置。如果您对Biome的默认设置满意,则无需创建配置。 + +要创建配置,请在项目的根文件夹中运行`init`命令: + + + +运行`init`命令后,您的目录中将出现一个新的`biome.json`文件: + +```json title="biome.json" +{ + "$schema": "https://biomejs.dev/schemas/1.4.0/schema.json", + "organizeImports": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + } +} +``` + +`linter.enabled: true`启用了代码检查器,`rules.recommended: true`启用了[推荐规则](/linter/rules/)。 + +格式化已启用,因为配置中没有明确[禁用](/reference/configuration/#formatterenabled)`formatter.enabled: false`。 + +## 使用方法 + +Biome CLI提供了许多命令和选项,因此您可以只使用您需要的部分。 + +您可以使用[`format`](/reference/cli#biome-format)命令和`--write`选项格式化文件和目录: + + + +您可以使用[`lint`](/reference/cli#biome-lint)命令和`--apply`选项来对文件和目录进行代码检查并应用[安全修复](/linter#safe-fixes): + + + +您可以通过使用[`check`](/reference/cli#biome-check)命令来同时应用**两者**: + + + +`check`命令旨在一次运行多个工具。目前,它会: + +- 格式化文件 +- 检查文件 +- 组织导入 + +## 安装编辑器插件 + +我们建议安装编辑器插件以充分发挥Biome的功能。请查看[编辑器页面](/guides/integrate-in-editor)以了解支持Biome的编辑器。 + +## CI设置 + +如果使用Node.js,在CI中运行Biome的推荐方式是使用[您偏好的包管理器](/guides/getting-started#installation)。这样可以确保您的CI流水线使用与您在编辑器内或在本地运行CLI命令时相同的Biome版本。 + +## 下一步 + +成功!您现在已经准备好使用Biome了。🥳 + +- 了解如何使用和配置[代码格式化](/formatter) +- 了解如何使用和配置[代码检查器](/linter) +- 熟悉[CLI选项](/reference/cli) +- 熟悉[配置选项](/reference/configuration) +- 加入我们的[Discord社区](https://discord.gg/BypW39g6Yc) diff --git a/website/src/content/docs/zh-cn/guides/how-biome-works.mdx b/website/src/content/docs/zh-cn/guides/how-biome-works.mdx new file mode 100644 index 000000000000..0eb5d5fe12eb --- /dev/null +++ b/website/src/content/docs/zh-cn/guides/how-biome-works.mdx @@ -0,0 +1,90 @@ +--- +title: Biome工作原理 +description: 学习如何使用Biome设置新项目。 +--- + +import DefaultConfiguration from "@src/components/generated/DefaultConfiguration.mdx"; + +## 配置 + +配置文件被认为是**可选的**,Biome有很好的默认值。使用配置文件来更改这些默认值。 + +Biome的配置文件名为`biome.json`,应放置在项目的根目录中。根目录通常是包含项目的`package.json`的目录。 + +此配置文件启用了格式化工具,并设置了首选的缩进样式和宽度。禁用了代码检查器: + +```json title="biome.json" +{ + "formatter": { + "enabled": true, + "indentStyle": "tab", + "lineWidth": 120 + }, + "linter": { + "enabled": false + } +} +``` + +### 默认配置 + +运行`biome init`时,生成的默认配置如下: + + + +### 配置文件解析 + +Biome使用自动发现来查找最近的`biome.json`文件。它从当前工作目录开始查找`biome.json`,然后向上级目录查找,直到: + +- 找到`biome.json`文件; +- 如果**找不到`biome.json`文件**,则应用Biome的默认值; + +以下是一个示例: + +``` +└── app + ├── backend + │ ├── package.json + │ └── biome.json + └── frontend + ├── legacy + │ └── package.json + ├── new + │ └── package.json + └── biome.json + +``` + +- 在`app/backend/package.json`中运行的biome命令将使用配置文件`app/backend/biome.json`; +- 在`app/frontend/legacy/package.json`和`app/frontend/new/package.json`中运行的biome命令将使用配置文件`app/frontend/biome.json`; + +## 已知文件 + +以下文件目前被Biome忽略。这意味着Biome不会为这些文件发出任何诊断信息。 + +- `package.json` +- `package-lock.json` +- `npm-shrinkwrap.json` +- `yarn.lock` +- `composer.json` +- `composer.lock` +- `typescript.json` +- `tsconfig.json` +- `jsconfig.json` +- `deno.json` +- `deno.jsonc` + +以下文件被解析为**`JSON`文件**,选项`json.parser.allowComments`和`json.parser.allowTrailingCommas`设置为`true`。这是因为像VSCode这样的编辑器工具将其视为这种格式。 + +- `tslint.json` +- `babel.config.json` +- `.babelrc.json` +- `.ember-cli` +- `typedoc.json` +- `.eslintrc` +- `.eslintrc.json` +- `.jsfmtrc` +- `.jshintrc` +- `.swcrc` +- `.hintrc` +- `.babelrc` diff --git a/website/src/content/docs/zh-cn/guides/integrate-in-editor.mdx b/website/src/content/docs/zh-cn/guides/integrate-in-editor.mdx new file mode 100644 index 000000000000..929601b28653 --- /dev/null +++ b/website/src/content/docs/zh-cn/guides/integrate-in-editor.mdx @@ -0,0 +1,137 @@ +--- +title: 在编辑器中集成Biome +description: 学习如何将Biome与编辑器和集成开发环境(IDE)结合使用 +--- + +## 官方插件 + +这些插件由Biome团队维护,并属于[Biome组织](https://github.com/biomejs)的一部分。 + +### VS Code + +Biome编辑器集成功能允许您: + +- 在保存文件时或发出格式化命令时格式化文件。 +- 对文件进行代码检查并应用代码修复。 + +从[Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=biomejs.biome)安装我们官方的[Biome VS Code扩展](https://marketplace.visualstudio.com/items?itemName=biomejs.biome)。 + +要将Biome设置为默认格式化程序,请打开[支持的文件](/internals/language-support/)并执行以下操作: + +- 打开*命令面板*(视图或Ctrl/++P) +- 选择*使用...格式化文档* +- 选择*配置默认格式化程序* +- 选择*Biome*。 + +### IntelliJ + +要安装Biome IntelliJ插件,请前往[官方插件页面](https://plugins.jetbrains.com/plugin/22761-biome)或按照以下步骤操作: + +**从JetBrains IDE:** + +1. 打开IntelliJ IDEA。 +2. 转到**设置/首选项**。 +3. 从左侧菜单中选择**插件**。 +4. 单击**Marketplace**选项卡。 +5. 搜索"Biome"并单击**安装**。 +6. 重新启动IDE以激活插件。 + +**从磁盘:** + +1. 从发布选项卡下载插件.zip文件。 +2. 按下`⌘Cmd,`打开IDE设置,然后选择插件。 +3. 在插件页面上,单击设置按钮,然后单击从磁盘安装插件...。 + +## 第三方插件 + +这些是由其他社区维护的插件,您可以在您的编辑器中安装: + +- [`neovim`](https://neovim.io/): 您需要安装[`nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig/),并按照[说明](https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#biome)进行设置; +- [`helix`](https://helix-editor.com/):请按照[此手册](https://github.com/biomejs/biome/blob/main/editors/helix/manual.md)的说明操作; +- [`coc-biome`](https://github.com/fannheyward/coc-biome): [`coc.nvim`](https://github.com/neoclide/coc.nvim)的Biome扩展 + +:::note +是否有适用于未在此处列出的编辑器的插件?请提交PR,我们将很乐意将其添加到列表中。 +::: + +## 编写自己的插件 + +Biome对[LSP](https://microsoft.github.io/language-server-protocol/)具有完全支持。如果您的编辑器实现了LSP,则Biome的集成应该是无缝的。 + +### 使用LSP代理 + +Biome有一个名为`lsp-proxy`的命令。当执行此命令时,Biome将生成两个进程: + +- 一个[守护进程](/internals/architecture#daemon),用于执行请求的操作; +- 一个服务器,作为客户端(编辑器)和服务器(守护进程)之间请求的代理; + +如果您的编辑器能够与服务器交互并发送[JSON-RPC](https://www.jsonrpc.org/)请求,您只需要配置编辑器运行该命令即可。 + +您可以查看[`neo-vim biome插件`](https://github.com/neovim/nvim-lspconfig/blob/master/lua/lspconfig/server_configurations/biome.lua)的实现方式。 + +### 使用`stdin` + +如果您的编辑器不支持LSP,则可以直接使用`biome`二进制文件,并使用[标准输入流]()调用它。 + +以下命令可以通过标准输入流调用: + +- [`format`](/reference/cli/#biome-format) +- [`lint`](/reference/cli/#biome-lint) +- [`check`](/reference/cli/#biome-check) + +Biome将将新的输出(如果没有更改,则为原始输出)返回到[标准输出流](),并将诊断信息返回到[标准错误流]()。 + +使用`stdin`时,您必须传递`--stdin-file-path`选项。文件`path`**不需要存在**于文件系统中,它可以是任何名称。**重要的是**提供正确的文件扩展名,以便Biome知道如何处理您的文件。 + +编辑器的责任是找到二进制文件的路径,然后在需要时调用它。二进制文件是基于我们支持的体系结构和操作系统进行npm发布的: + +- `@biomejs/cli-darwin-arm64` +- `@biomejs/cli-darwin-x64` +- `@biomejs/cli-linux-arm64` +- `@biomejs/cli-linux-x64` +- `@biomejs/cli-win32-arm64` +- `@biomejs/cli-win32-x64` + +二进制文件的名称是`biome`或`biome.exe`,可以在库的根目录中找到,例如:`@biomejs/cli-darwin-arm64/biome`、`@biomejs/cli-win32-x64/biome.exe`。 + +### 使用带有二进制文件的守护进程 + +通过CLI使用二进制文件非常高效,尽管您将无法为用户提供[日志](#daemon-logs)。CLI允许您启动一个守护进程,然后通过守护进程本身使用CLI命令。 + +为此,您首先需要使用[`start`](/reference/cli#biome-start)命令启动守护进程: + +```shell +biome start +``` + +然后,每个命令都需要添加`--use-server`选项,例如: + +```shell +echo "console.log('')" | biome format --use-server --stdin-file-path=dummy.js +``` + +:::note +如果决定使用守护进程,则您还需要负责使用[`stop`](/reference/cli#biome-stop)命令重新启动/终止进程,以避免产生僵尸进程。 +::: + +:::caution +通过守护进程的操作比CLI本身要慢得多,因此建议仅对单个文件运行操作。 +::: + +### 守护进程日志 + +Biome守护进程将日志保存在文件系统中。日志存储在名为`biome-logs`的文件夹中。您可以在操作系统的临时目录中找到这个文件夹。 + +在Windows上,使用powershell: + +```shell +$env:TEMP +``` + +在Linux/macOS上,使用终端: + +```shell +echo $TMPDIR +``` + +日志文件每小时进行一次轮换。 diff --git a/website/src/content/docs/zh-cn/guides/manual-installation.mdx b/website/src/content/docs/zh-cn/guides/manual-installation.mdx new file mode 100644 index 000000000000..1c259c5ea9da --- /dev/null +++ b/website/src/content/docs/zh-cn/guides/manual-installation.mdx @@ -0,0 +1,61 @@ +--- +title: 手动安装 +description: 手动安装Biome +--- + +## 在没有Node.js的情况下使用Biome + +如果您还没有使用Node或npm(或任何其他软件包管理器),那么使用Biome的独立CLI二进制文件可能是一个很好的选择。 +换句话说,Biome不应该是您拥有`package.json`的唯一原因。 + +> 注意:如果您已经使用npm或其他软件包管理器,则使用软件包管理器是[安装](/guides/getting-started#installation) Biome的[首选方式](/guides/getting-started#installation)。 +> 您已经熟悉工具链,并且安装和更新更加简单。 + +### Homebrew + +Biome可以作为[Homebrew formula](https://formulae.brew.sh/formula/biome)提供给macOS和Linux用户。 + +```shell +brew install biome +``` + +## 系统要求 + +- Windows(包括WSL),macOS或Linux +- x86_64或ARM64 + +## 支持的平台 + +你必须为你的平台选择正确的二进制文件才能使用Biome。下表将帮助您完成此操作。 + +| CPU架构 | Windows | macOS | Linux | Linux(musl) | +| ------- | ------------- | ----------------------------- | ------------- | ------------------ | +| `arm64` | `win32-arm64` | `darwin-arm64` (M1或更新版本) | `linux-arm64` | `linux-arm64-musl` | +| `x64` | `win32-x64` | `darwin-x64` | `linux-x64` | `linux-x64-musl` | + +> 注意:在Windows Subsystem for Linux (WSL)中使用Linux变体 + +## 安装Biome + +要安装Biome,请从GitHub的[最新CLI版本](https://github.com/biomejs/biome/releases)中获取适用于您平台的可执行文件,并给它执行权限。 + +```shell +# macOS arm (M1或更新版本) +curl -L https://github.com/biomejs/biome/releases/download/cli%2Fv/biome-darwin-arm64 -o biome +chmod +x biome + +# Linux (x86_64) +curl -L https://github.com/biomejs/biome/releases/download/cli%2Fv/biome-linux-x64 -o biome +chmod +x biome + +# Windows (x86_64, Powershell) +Invoke-WebRequest -Uri "https://github.com/biomejs/biome/releases/download/cli%2Fv/biome-win32-x64.exe" -OutFile "biome.exe" +``` + +> 注意:请确保将``替换为您要安装的Biome版本。 + +现在,您可以通过运行`./biome`来使用Biome。 + +## 下一步 + +在我们的[入门指南](/guides/getting-started#next-steps)中阅读更多关于如何使用Biome的内容。 diff --git a/website/src/content/docs/zh-cn/index.mdx b/website/src/content/docs/zh-cn/index.mdx new file mode 100644 index 000000000000..ff51ae719c20 --- /dev/null +++ b/website/src/content/docs/zh-cn/index.mdx @@ -0,0 +1,58 @@ +--- +title: Biome +template: splash +description: 瞬间完成 Format、Lint 等! +hero: + title: Web 项目的唯一工具链 + tagline: 瞬间完成 Format、Lint 等! + image: + alt: Biome,Web 工具链 + file: ../../../assets/biome-logo-slogan.svg + actions: + - text: 开始使用 + link: /zh-cn/guides/getting-started/ + icon: right-arrow + variant: primary + - text: GitHub + link: https://github.com/biomejs/biome + icon: external + variant: secondary +banner: + content: | + Biome 赢得了 Prettier挑战 的奖金! +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + + + + 使用 Rust 构建,灵感来自 rust-analyzer 的革命性架构。 + + + 无需任何配置直接使用。若需要,也有大量配置选项可用。 + + + 为处理任何规模的代码库而设计。专注于产品的开发而不是工具。 + + + 每项功能的内部处理都尽可能地进行整合与重复使用。这就使一个工具的改进也可以让其他所有工具都更好用。 + + + 避免晦涩的错误消息,当出现问题时,我们会准确告诉您问题的位置以及如何修复它。 + + + 开箱即用,支持前端各种语言特性。尤其对 TypeScript 与 JSX 提供完美支持。 + + + +## 赞助商 + + diff --git a/website/src/content/docs/zh-cn/internals/architecture.mdx b/website/src/content/docs/zh-cn/internals/architecture.mdx new file mode 100644 index 000000000000..811e7c5be176 --- /dev/null +++ b/website/src/content/docs/zh-cn/internals/architecture.mdx @@ -0,0 +1,107 @@ +--- +title: 架构 +description: Biome在幕后是如何工作的。 +--- + +这篇文档涵盖了Biome的一些内部工作原理,以及它们在项目中的使用方式。 + +## 解析器和CST + +解析器的架构使用了一个内部的[rowan]分支,它是一个实现[绿树和红树模式]的库。 + +CST(具体语法树)是一种与AST(抽象语法树)非常相似的数据结构,它跟踪程序的所有信息,包括空白和注释。 + +**空白和注释**是程序运行所需的所有重要信息: + +- 空格 +- 制表符 +- 注释 + +空白和注释被附加到一个节点上。一个节点可以有前导空白和尾随空白。如果你从左到右阅读代码,前导空白出现在关键字之前,尾随空白出现在关键字之后。 + +前导空白和尾随空白被分类如下: + +- 直到令牌/关键字之前的所有空白(包括换行符)将是**前导空白**; +- 直到下一个换行符之前的所有内容(不包括换行符)将是**尾随空白**; + +给定以下JavaScript代码片段,`// comment 1`是分号`';'`的尾随空白,`// comment 2`是`const`关键字的前导空白。以下是Biome表示的CST的简化版本: + +```js +const a = "foo"; // comment 1 +// comment 2 +const b = "bar"; +``` + +``` +0: JS_MODULE@0..55 + ... + 1: SEMICOLON@15..27 ";" [] [Whitespace(" "), Comments("// comment 1")] + 1: JS_VARIABLE_STATEMENT@27..55 + ... + 1: CONST_KW@27..45 "const" [Newline("\n"), Comments("// comment 2"), Newline("\n")] [Whitespace(" ")] + 3: EOF@55..55 "" [] [] +``` + +由于设计原因,CST本身无法直接访问,开发人员可以使用Red树读取其信息,使用一系列从语言语法自动生成的API。 + +#### 弹性和可恢复的解析器 + +为了构建CST,解析器需要具备弹性和可恢复性: + +- 弹性:解析器能够在遇到属于语言的语法错误后恢复解析; +- 可恢复性:解析器能够**理解**发生错误的位置,并能够通过创建**正确的**信息来恢复解析; + +解析器在恢复阶段无法正确理解语法的情况下,需要使用`Bogus`节点来标记某些语法为错误的。注意`JsBogusStatement`: + +```js +function} +``` + +``` +JsModule { + interpreter_token: missing (optional), + directives: JsDirectiveList [], + items: JsModuleItemList [ + TsDeclareFunctionDeclaration { + async_token: missing (optional), + function_token: FUNCTION_KW@0..8 "function" [] [], + id: missing (required), + type_parameters: missing (optional), + parameters: missing (required), + return_type_annotation: missing (optional), + semicolon_token: missing (optional), + }, + JsBogusStatement { + items: [ + R_CURLY@8..9 "}" [] [], + ], + }, + ], + eof_token: EOF@9..9 "" [] [], +} +``` + +以下是解析阶段的错误信息: + +``` +main.tsx:1:9 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ✖ expected a name for the function in a function declaration, but found none + + > 1 │ function} + │ ^ +``` + +## 格式化程序(WIP) + +## 静态检查器(WIP) + +## 守护进程(WIP) + +Biome使用了一个服务器-客户端架构来运行其任务。 + +[守护进程]是一个长时间运行的服务器,Biome在后台生成并用于处理来自编辑器和CLI的请求。 + +[rowan]: https://github.com/rust-analyzer/rowan +[绿树和红树模式]: https://learn.microsoft.com/en-us/archive/blogs/ericlippert/persistence-facades-and-roslyns-red-green-trees +[守护进程]: https://en.wikipedia.org/wiki/Daemon_(computing) diff --git a/website/src/content/docs/zh-cn/internals/changelog.mdx b/website/src/content/docs/zh-cn/internals/changelog.mdx new file mode 100644 index 000000000000..4c3a186abc6c --- /dev/null +++ b/website/src/content/docs/zh-cn/internals/changelog.mdx @@ -0,0 +1,1488 @@ +--- +title: Changelog +description: The changelog of Biome +tableOfContents: + maxHeadingLevel: 2 +--- + +# Biome changelog + +This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +Due to the nature of Biome as a toolchain, +it can be unclear what changes are considered major, minor, or patch. +Read our [guidelines to categorize a change](https://biomejs.dev/internals/versioning). + +New entries must be placed in a section entitled `Unreleased`. +Read our [guidelines for writing a good changelog entry](https://github.com/biomejs/biome/blob/main/CONTRIBUTING.md#changelog). + +## 1.4.1 (2023-11-30) + +### Editors + +- Fix [#933](https://github.com/biomejs/biome/issues/933). Some files are properly ignored in the LSP too. E.g. `package.json`, `tsconfig.json`, etc. + +### Formatter + +#### Bug fixes + +- Fix some accidental line breaks when printing array expressions within arrow functions and other long lines [#917](https://github.com/biomejs/biome/pull/917). Contributed by @faultyserver + +- Match Prettier's breaking strategy for `ArrowChain` layouts [#934](https://github.com/biomejs/biome/pull/934). Contributed by @faultyserver + +- Fix double-printing of leading comments in arrow chain expressions [#951](https://github.com/biomejs/biome/pull/951). Contributed by @faultyserver + +### Linter + +#### Bug fixes + +- Fix [#910](https://github.com/biomejs/biome/issues/910), where the rule `noSvgWithoutTitle` should skip elements that have `aria-hidden` attributes. Contributed by @vasucp1207 + +#### New features + +#### Enhancement + +- Implements [#924](https://github.com/biomejs/biome/issues/924) and [#920](https://github.com/biomejs/biome/issues/920). [noUselessElse](https://biomejs.dev/linter/rules/no-useless-else) now ignores `else` clauses that follow at least one `if` statement that doesn't break early. Contributed by @Conaclos + + For example, the following code is no longer reported by the rule: + + ```js + function f(x) { + if (x < 0) { + // this `if` doesn't break early. + } else if (x > 0) { + return x; + } else { + // This `else` block was previously reported as useless. + } + } + ``` + +#### Bug fixes + +- Fix [#918](https://github.com/biomejs/biome/issues/918), [useSimpleNumberKeys](https://biomejs.dev/linter/rules/use-simple-number-keys) no longer repports false positive on comments. Contributed by @kalleep + +- Fix [#953](https://github.com/biomejs/biome/issues/953), [noRedeclare](https://biomejs.dev/linter/rules/no-redeclare) no longer reports type parameters with the same name in different mapped types as redeclarations. Contributed by @Conaclos + +- Fix [#608](https://github.com/biomejs/biome/issues/608), [useExhaustiveDependencies](https://biomejs.dev/linter/rules/use-exhaustive-dependencies) no longer repports missing dependencies for React hooks without dependency array. Contributed by @kalleep + +### Parser + +## 1.4.0 (2023-11-27) + +### CLI + +- Remove the CLI options from the `lsp-proxy`, as they were never meant to be passed to that command. Contributed by @ematipico + +- Add option `--config-path` to `lsp-proxy` and `start` commands. It's now possible to tell the Daemon server to load `biome.json` from a custom path. Contributed by @ematipico + +- Add new `--diagnostic-level` option to let users control the level of diagnostics printed by the CLI. Possible values are: `"info"`, `"warn"`, `"hint"`. Contributed by @simonxabris +- Add option `--line-feed` to the `format` command. Contributed by @SuperchupuDev +- Add option `--bracket-same-line` to the `format` command. Contributed by @faultyserve +- Add option `--bracket-spacing` to the `format` command. Contributed by @faultyserve + +#### Bug fixes + +- Fix the command `format`, now it returns a non-zero exit code when if there pending diffs. Contributed by @ematipico + +### Configuration + +- Add option `formatter.lineFeed`. Contributed by @SuperchupuDev +- Add option `javascript.formatter.bracketSameLine`. Contributed by @faultyserve +- Add option `javascript.formatter.bracketSpacing`. Contributed by @faultyserve + +### Formatter + +#### New features + +- Add a new option [`--line-ending`](https://biomejs.dev/reference/configuration/#formatterlineending). This option allows changing the type of line endings. Contributed by @SuperchupuDev +- Added a new option called `--bracket-spacing` to the formatter. This option allows you to control whether spaces are inserted around the brackets of object literals. [#627](https://github.com/biomejs/biome/issues/627). Contributed by @faultyserver +- Added a new option called `--bracket-same-line` to the formatter. This option allows you to control whether spaces are inserted around the brackets of object literals. [#627](https://github.com/biomejs/biome/issues/627). Contributed by @faultyserver + +#### Bug fixes + +- Fix [#832](https://github.com/biomejs/biome/issues/832), the formatter no longer keeps an unnecessary trailing comma in type parameter lists. Contributed by @Conaclos + +- Fix [#301](https://github.com/biomejs/biome/issues/301), the formatter should not break before the `in` keyword. Contributed by @ematipico + +### Linter + +#### Promoted rules + +- [a11y/noInteractiveElementToNoninteractiveRole](https://biomejs.dev/linter/rules/no-interactive-element-to-noninteractive-role) +- [complexity/noThisInStatic](https://biomejs.dev/linter/rules/no-this-in-static) +- [complexity/useArrowFunction](https://biomejs.dev/linter/rules/use-arrow-function) +- [correctness/noEmptyCharacterClassInRegex](https://biomejs.dev/linter/rules/no-empty-character-class-in-regex) +- [correctness/noInvalidNewBuiltin](https://biomejs.dev/linter/rules/no-invalid-new-builtin) +- [style/noUselessElse](https://biomejs.dev/linter/rules/no-useless-else) +- [style/useAsConstAssertion](https://biomejs.dev/linter/rules/use-as-const-assertion) +- [style/useShorthandAssign](https://biomejs.dev/linter/rules/use-shorthand-assign) +- [suspicious/noApproximativeNumericConstant](https://biomejs.dev/linter/rules/no-approximative-numeric-constant) +- [suspicious/noMisleadingInstantiator](https://biomejs.dev/linter/rules/no-misleading-instantiator) +- [suspicious/noMisrefactoredShorthandAssign](https://biomejs.dev/linter/rules/no-misrefactored-shorthand-assign) + +The following rules are now recommended: + +- [a11y/noAccessKey](https://biomejs.dev/linter/rules/no-access-key) +- [a11y/useHeadingContent](https://biomejs.dev/linter/rules/use-heading-content) +- [complexity/useSimpleNumberKeys](https://biomejs.dev/linter/use-simple-number-keys) + +The following rules are now deprecated: + +- [correctness/noNewSymbol](https://biomejs.dev/linter/rules/no-new-symbol) + The rule is replaced by [correctness/noInvalidNewBuiltin](https://biomejs.dev/linter/rules/no-invalid-new-builtin) + +#### New features + +- Add [noDefaultExport](https://biomejs.dev/linter/rules/no-default-export) which disallows `export default`. Contributed by @Conaclos + +- Add [noAriaHiddenOnFocusable](https://biomejs.dev/linter/rules/no-aria-hidden-on-focusable) which reports hidden and focusable elements. Contributed by @vasucp1207 + +- Add [noImplicitAnyLet](https://biomejs.dev/linter/rules/no-implicit-any-let) that reports variables declared with `let` and without initialization and type annotation. Contributed by @TaKO8Ki and @b4s36t4 + +- Add [useAwait](https://biomejs.dev/linter/rules/use-await) that reports `async` functions that don't use an `await` expression. + +- Add [useValidAriaRole](https://biomejs.dev/linter/rules/use-valid-aria-role). Contributed by @vasucp1207 + +- Add [useRegexLiterals](https://biomejs.dev/linter/use-regex-literals) that suggests turning call to the regex constructor into regex literals. COntributed by @Yuiki + +#### Enhancements + +- Add an unsafe code fix for [a11y/useAriaActivedescendantWithTabindex](https://biomejs.dev/linter/rules/use-aria-activedescendant-with-tabindex) + +#### Bug fixes + +- Fix [#639](https://github.com/biomejs/biome/issues/639) by ignoring unused TypeScript's mapped key. Contributed by @Conaclos + +- Fix [#565](https://github.com/biomejs/biome/issues/565) by handling several `infer` with the same name in extends clauses of TypeScript's conditional types. Contributed by @Conaclos + +- Fix [#653](https://github.com/biomejs/biome/issues/653). [noUnusedImports](https://biomejs.dev/linter/rules/no-unused-imports) now correctly removes the entire line where the unused `import` is. Contributed by @Conaclos + +- Fix [#607](https://github.com/biomejs/biome/issues/609) `useExhaustiveDependencies`, ignore optional chaining, Contributed by @msdlisper + +- Fix [#676](https://github.com/biomejs/biome/issues/676), by using the correct node for the `"noreferrer"` when applying the code action. Contributed by @ematipico + +- Fix [#455](https://github.com/biomejs/biome/issues/455). The CLI can now print complex emojis to the console correctly. + +- Fix [#727](https://github.com/biomejs/biome/issues/727). [noInferrableTypes](https://biomejs.dev/linter/rules/no-inferrable-types) now correctly keeps type annotations when the initialization expression is `null`. Contributed by @Conaclos + +- Fix [#784](https://github.com/biomejs/biome/issues/784), [noSvgWithoutTitle](https://biomejs.dev/linter/rules/no-svg-without-title) fixes false-positives to `aria-label` and reports svg's role attribute is implicit. Contributed by @unvalley + +- Fix [#834](https://github.com/biomejs/biome/issues/834) that made [noUselessLoneBlockStatements](https://biomejs.dev/linter/rules/no-useless-lone-block-statements) reports block statements of switch clauses. Contributed by @vasucp1207 + +- Fix [#783](https://github.com/biomejs/biome/issues/834) that made [noUselessLoneBlockStatements](https://biomejs.dev/linter/rules/no-useless-lone-block-statements) reports block statements of `try-catch` structures. Contributed by @hougesen + +- Fix [#69](https://github.com/biomejs/biome/issues/69) that made [correctness/noUnnecessaryContinue](https://biomejs.dev/linter/rules/no-unnecessary-continue) incorrectly reports a `continue` used to break a switch clause. Contributed by @TaKO8Ki + +- Fix [#664](https://github.com/biomejs/biome/issues/664) by improving the diagnostic of [style/useNamingConvention](https://biomejs.dev/linter/use-naming-convention) when double capital are detected in strict camel case mode. Contributed by @vasucp1207 + +- Fix [#643](https://github.com/biomejs/biome/issues/643) that erroneously parsed the option of [complexity/useExhaustiveDependencies](https://biomejs.dev/linter/use-naming-convention). Contributed by @arendjr + +### Parser + +#### Bug fixes + +- Fix [#846](https://github.com/biomejs/biome/issues/846) that erroneously parsed `() => {}` as a JSX tag instead of an arrow function when both TypeScript and JSX are enabled. + +### VSCode + +## 1.3.3 (2023-10-31) + +### Analyzer + +#### Bug fixes + +- Fix [#604](https://github.com/biomejs/biome/issues/604) which made [noConfusingVoidType](https://biomejs.dev/linter/rules/no-confusing-void-type) report false positives when the `void` type is used in a generic type parameter. Contributed by @unvalley + +### CLI + +#### Bug fixes + +- Fix how `overrides` behave. Now `ignore` and `include` apply or not the override pattern, so they override each other. + Now the options inside `overrides` override the top-level options. +- Bootstrap the logger only when needed. Contributed by @ematipico +- Fix how `overrides` are run. The properties `ignore` and `include` have different semantics and only apply/not apply an override. Contributed by @ematipico + +### Editors + +#### Bug fixes + +- Fix [#592](https://github.com/biomejs/biome/issues/592), by changing binary resolution in the IntelliJ plugin. Contributed by @Joshuabaker2 + +### Formatter + +#### Bug fixes + +- Apply the correct layout when the right hand of an assignment expression is a await expression or a yield expression. Contributed by @ematipico +- Fix [#303](https://github.com/biomejs/biome/issues/303), where nested arrow functions didn't break. Contributed by @victor-teles + +### Linter + +#### New features + +- Add [noUnusedPrivateClassMembers](https://biomejs.dev/linter/rules/no-unused-private-class-members) rule. + The rule disallow unused private class members. + Contributed by @victor-teles + +#### Bug fixes + +- Fix [#175](https://github.com/biomejs/biome/issues/175) which made [noRedeclare](https://biomejs.dev/linter/rules/no-redeclare) report index signatures using the name of a variable in the parent scope. + +- Fix [#557](https://github.com/biomejs/biome/issues/557) which made [noUnusedImports](https://biomejs.dev/linter/rules/no-unused-imports) report imported types used in `typeof` expression. Contributed by @Conaclos + +- Fix [#576](https://github.com/biomejs/biome/issues/576) by removing some erroneous logic in [noSelfAssign](https://biomejs.dev/linter/rules/no-self-assign/). Contributed by @ematipico + +- Fix [#861](https://github.com/biomejs/biome/issues/861) that made [noUnusedVariables](https://biomejs.dev/linter/rules/no-unused-variables) always reports the parameter of an non-parenthesize arrow function as unused. + +- Fix [#595](https://github.com/biomejs/biome/issues/595) by updating unsafe-apply logic to avoid unexpected errors in [noUselessFragments](https://biomejs.dev/linter/rules/no-useless-fragments/). Contributed by @nissy-dev + +- Fix [#591](https://github.com/biomejs/biome/issues/591) which made [noRedeclare](https://biomejs.dev/linter/rules/no-redeclare) report type parameters with identical names but in different method signatures. Contributed by @Conaclos +- Support more a11y roles and fix some methods for a11y lint rules Contributed @nissy-dev +- Fix [#609](https://github.com/biomejs/biome/issues/609) `useExhaustiveDependencies`, by removing `useContext`, `useId` and `useSyncExternalStore` from the known hooks. Contributed by @msdlisper +- Fix `useExhaustiveDependencies`, by removing `useContext`, `useId` and `useSyncExternalStore` from the known hooks. Contributed by @msdlisper + +### Parser + +#### Enhancements + +- Support RegExp v flag. Contributed by @nissy-dev +- Improve error messages. Contributed by @ematipico + +## 1.3.1 (2023-10-20) + +### CLI + +#### Bug fixes + +- Fix `rage` command, now it doesn't print info about running servers. Contributed by @ematipico + +### Editors + +#### Bug fixes + +- Fix [#552](https://github.com/biomejs/biome/issues/552), where the formatter isn't correctly triggered in Windows systems. Contributed by @victor-teles + +### Linter + +#### New features + +- Add [noThisInStatic](https://biomejs.dev/linter/rules/no-this-in-static) rule. Contributed by @ditorodev and @Conaclos + +#### Bug fixes + +- Fix [#548](https://github.com/biomejs/biome/issues/548) which made [noSelfAssign](https://biomejs.dev/linter/rules/no-self-assign) panic. + +- Fix [#555](https://github.com/biomejs/biome/issues/555), by correctly map `globals` into the workspace. + +## 1.3.0 (2023-10-19) + +### Analyzer + +#### Enhancements + +- Import sorting is safe to apply now, and it will be applied when running `check --apply` instead of `check --apply-unsafe`. + +- Import sorting now handles Bun imports `bun:`, absolute path imports `/`, and [Node's subpath imports `#`](https://nodejs.org/api/packages.html#subpath-imports). See [our documentation](https://biomejs.dev/analyzer/) for more details. Contributed by @Conaclos + +### CLI + +#### Bug fixes + +- Fix [#319](https://github.com/biomejs/biome/issues/319). The command `biome lint` now shows the correct options. Contributed by @ematipico +- Fix [#312](https://github.com/biomejs/biome/issues/312). Running `biome --version` now exits with status code `0` instead of `1`. Contributed by @nhedger +- Fix a bug where the `extends` functionality doesn't carry over `organizeImports.ignore`. Contributed by @ematipico +- The CLI now returns the original content when using `stdin` and the original content doesn't change. Contributed by @ematipico + +#### New features + +- Add support for `BIOME_BINARY` environment variable to override the location of the binary. Contributed by @ematipico +- Add option `--indent-width`, and deprecated the option `--indent-size`. Contributed by @ematipico +- Add option `--javascript-formatter-indent-width`, and deprecated the option `--javascript-formatter-indent-size`. Contributed by @ematipico +- Add option `--json-formatter-indent-width`, and deprecated the option `--json-formatter-indent-size`. Contributed by @ematipico +- Add option `--daemon-logs` to `biome rage`. The option is required to view Biome daemon server logs. Contributed by @unvalley +- Add support for logging. By default, Biome doesn't log anything other than diagnostics. Logging can be enabled with the new option `--log-level`: + + ```shell + biome format --log-level=info ./src + ``` + + There are four different levels of logging, from the most verbose to the least verbose: `debug`, `info`, `warn` and `error`. Here's how an `INFO` log will look like: + + ``` + 2023-10-05T08:27:01.954727Z INFO Analyze file ./website/src/playground/components/Resizable.tsx + at crates/biome_service/src/file_handlers/javascript.rs:298 on biome::worker_5 + in Pulling diagnostics with categories: RuleCategories(SYNTAX) + in Processes formatting with path: "./website/src/playground/components/Resizable.tsx" + in Process check with path: "./website/src/playground/components/Resizable.tsx" + ``` + + You can customize how the log will look like with a new option `--log-kind`. The supported kinds are: `pretty`, `compact` and `json`. + + `pretty` is the default logging. Here's how a `compact` log will look like: + + ``` + 2023-10-05T08:29:04.864247Z INFO biome::worker_2 Process check:Processes linting:Pulling diagnostics: crates/biome_service/src/file_handlers/javascript.rs: Analyze file ./website/src/playground/components/Resizable.tsx path="./website/src/playground/components/Resizable.tsx" path="./website/src/playground/components/Resizable.tsx" categories=RuleCategories(LINT) + 2023-10-05T08:29:04.864290Z INFO biome::worker_7 Process check:Processes formatting: crates/biome_service/src/file_handlers/javascript.rs: Format file ./website/src/playground/components/Tabs.tsx path="./website/src/playground/components/Tabs.tsx" path="./website/src/playground/components/Tabs.tsx" + 2023-10-05T08:29:04.879332Z INFO biome::worker_2 Process check:Processes formatting:Pulling diagnostics: crates/biome_service/src/file_handlers/javascript.rs: Analyze file ./website/src/playground/components/Resizable.tsx path="./website/src/playground/components/Resizable.tsx" path="./website/src/playground/components/Resizable.tsx" categories=RuleCategories(SYNTAX) + 2023-10-05T08:29:04.879383Z INFO biome::worker_2 Process check:Processes formatting: crates/biome_service/src/file_handlers/javascript.rs: Format file ./website/src/playground/components/Resizable.tsx path="./website/src/playground/components/Resizable.tsx" path="./website/src/playground/components/Resizable.tsx" + ``` + +#### Enhancements + +- Deprecated the environment variable `ROME_BINARY`. Use `BIOME_BINARY` instead. Contributed by @ematipico +- Biome doesn't check anymore the presence of the `.git` folder when VCS support is enabled. Contributed by @ematipico +- `biome rage` doesn't print the logs of the daemon, use `biome rage --daemon-logs` to print them. Contributed by @unvalley + +### Configuration + +#### New features + +- Add option `formatter.indentWidth`, and deprecated the option `formatter.indentSize`. Contributed by @ematipico +- Add option `javascript.formatter.indentWidth`, and deprecated the option `javascript.formatter.indentSize`. Contributed by @ematipico +- Add option `json.formatter.indentWidth`, and deprecated the option `json.formatter.indentSize`. Contributed by @ematipico +- Add option `include` to multiple sections of the configuration + - `files.include`; + - `formatter.include`; + - `linter.include`; + - `organizeImports.include`; + When `include` and `ignore` are both specified, `ignore` takes **precedence** over `include` +- Add option `overrides`, where users can modify the behaviour of the tools for certain files or paths. + + For example, it's possible to modify the formatter `lineWidth`, and even `quoteStyle` for certain files that are included in glob path `generated/**`: + + ```json + { + "formatter": { + "lineWidth": 100 + }, + "overrides": [ + { + "include": ["generated/**"], + "formatter": { + "lineWidth": 160 + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + } + } + ] + } + ``` + + Or, you can disable certain rules for certain path, and disable the linter for other paths: + + ```json + { + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "overrides": [ + { + "include": ["lib/**"], + "linter": { + "rules": { + "suspicious": { + "noDebugger": "off" + } + } + } + }, + { + "include": ["shims/**"], + "linter": { + "enabled": false + } + } + ] + } + ``` + +### Bug fixes + +- Fix [#343](https://github.com/biomejs/biome/issues/343), `extends` was incorrectly applied to the `biome.json` file. Contributed by @ematipico + +### Editors + +#### Bug fixes + +- Fix [#404](https://github.com/biomejs/biome/issues/404). Biome intellij plugin now works on Windows. Contributed by @victor-teles + +- Fix [#402](https://github.com/biomejs/biome/issues/402). Biome `format` on intellij plugin now recognize biome.json. Contributed by @victor-teles + +### Formatter + +#### Enhancements + +- Use `OnceCell` for the Memoized memory because that's what the `RefCell