Skip to content

Commit 91484d1

Browse files
author
Hiroki Okada
authored
feat(biome_js_analyze): implement noMultiStr rule (#8218)
1 parent e430555 commit 91484d1

14 files changed

Lines changed: 306 additions & 83 deletions

File tree

.changeset/happy-mirrors-grow.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Added the [`noMultiStr`](https://biomejs.dev/linter/rules/no-multi-str) rule, which disallows creating multiline strings by escaping newlines.
6+
7+
**Invalid:**
8+
9+
```js
10+
const foo =
11+
"Line 1\n\
12+
Line 2";
13+
```
14+
15+
**Valid:**
16+
17+
```js
18+
const foo = "Line 1\nLine 2";
19+
const bar = `Line 1
20+
Line 2`;
21+
```

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

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

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

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

crates/biome_diagnostics_categories/src/categories.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ define_categories! {
179179
"lint/nursery/noLeakedRender": "https://biomejs.dev/linter/rules/no-leaked-render",
180180
"lint/nursery/noMissingGenericFamilyKeyword": "https://biomejs.dev/linter/rules/no-missing-generic-family-keyword",
181181
"lint/nursery/noMisusedPromises": "https://biomejs.dev/linter/rules/no-misused-promises",
182+
"lint/nursery/noMultiStr": "https://biomejs.dev/linter/rules/no-multi-str",
182183
"lint/nursery/noNextAsyncClientComponent": "https://biomejs.dev/linter/rules/no-next-async-client-component",
183184
"lint/nursery/noParametersOnlyUsedInRecursion": "https://biomejs.dev/linter/rules/no-parameters-only-used-in-recursion",
184185
"lint/nursery/noReactForwardRef": "https://biomejs.dev/linter/rules/no-react-forward-ref",

crates/biome_js_analyze/src/lint/nursery.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub mod no_increment_decrement;
1414
pub mod no_jsx_literals;
1515
pub mod no_leaked_render;
1616
pub mod no_misused_promises;
17+
pub mod no_multi_str;
1718
pub mod no_next_async_client_component;
1819
pub mod no_parameters_only_used_in_recursion;
1920
pub mod no_react_forward_ref;
@@ -42,4 +43,4 @@ pub mod use_sorted_classes;
4243
pub mod use_spread;
4344
pub mod use_vue_define_macros_order;
4445
pub mod use_vue_multi_word_component_names;
45-
declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_continue :: NoContinue , self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_empty_source :: NoEmptySource , self :: no_equals_to_null :: NoEqualsToNull , self :: no_floating_promises :: NoFloatingPromises , self :: no_for_in :: NoForIn , self :: no_import_cycles :: NoImportCycles , self :: no_increment_decrement :: NoIncrementDecrement , self :: no_jsx_literals :: NoJsxLiterals , self :: no_leaked_render :: NoLeakedRender , self :: no_misused_promises :: NoMisusedPromises , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_shadow :: NoShadow , self :: no_sync_scripts :: NoSyncScripts , self :: no_ternary :: NoTernary , self :: no_unknown_attribute :: NoUnknownAttribute , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_array_sort_compare :: UseArraySortCompare , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_find :: UseFind , self :: use_max_params :: UseMaxParams , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_sorted_classes :: UseSortedClasses , self :: use_spread :: UseSpread , self :: use_vue_define_macros_order :: UseVueDefineMacrosOrder , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } }
46+
declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_continue :: NoContinue , self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_empty_source :: NoEmptySource , self :: no_equals_to_null :: NoEqualsToNull , self :: no_floating_promises :: NoFloatingPromises , self :: no_for_in :: NoForIn , self :: no_import_cycles :: NoImportCycles , self :: no_increment_decrement :: NoIncrementDecrement , self :: no_jsx_literals :: NoJsxLiterals , self :: no_leaked_render :: NoLeakedRender , self :: no_misused_promises :: NoMisusedPromises , self :: no_multi_str :: NoMultiStr , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_shadow :: NoShadow , self :: no_sync_scripts :: NoSyncScripts , self :: no_ternary :: NoTernary , self :: no_unknown_attribute :: NoUnknownAttribute , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: use_array_sort_compare :: UseArraySortCompare , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_find :: UseFind , self :: use_max_params :: UseMaxParams , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_sorted_classes :: UseSortedClasses , self :: use_spread :: UseSpread , self :: use_vue_define_macros_order :: UseVueDefineMacrosOrder , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } }
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use biome_analyze::{
2+
Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule,
3+
};
4+
use biome_console::markup;
5+
use biome_diagnostics::Severity;
6+
use biome_js_syntax::JsStringLiteralExpression;
7+
use biome_rowan::AstNode;
8+
use biome_rule_options::no_multi_str::NoMultiStrOptions;
9+
10+
declare_lint_rule! {
11+
/// Disallow creating multiline strings by escaping newlines.
12+
///
13+
/// Escaping newlines to create multiline strings is discouraged because it
14+
/// can lead to subtle errors caused by unexpected whitespace after the
15+
/// backslash.
16+
///
17+
/// ## Examples
18+
///
19+
/// ### Invalid
20+
///
21+
/// ```js,expect_diagnostic
22+
/// const foo =
23+
/// "Line 1\n\
24+
/// Line 2";
25+
/// ```
26+
///
27+
/// ### Valid
28+
///
29+
/// ```js
30+
/// const foo = "Line 1\nLine 2";
31+
/// ```
32+
///
33+
/// ```js
34+
/// const bar = `Line 1
35+
/// Line 2`;
36+
/// ```
37+
pub NoMultiStr {
38+
version: "next",
39+
name: "noMultiStr",
40+
language: "js",
41+
recommended: false,
42+
sources: &[RuleSource::Eslint("no-multi-str").same()],
43+
severity: Severity::Error,
44+
}
45+
}
46+
47+
impl Rule for NoMultiStr {
48+
type Query = Ast<JsStringLiteralExpression>;
49+
type State = ();
50+
type Signals = Option<Self::State>;
51+
type Options = NoMultiStrOptions;
52+
53+
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
54+
let node = ctx.query();
55+
let text = node.value_token().ok()?.token_text_trimmed();
56+
57+
if text.contains("\\\n") || text.contains("\\\r\n") {
58+
return Some(());
59+
}
60+
61+
None
62+
}
63+
64+
fn diagnostic(ctx: &RuleContext<Self>, _state: &Self::State) -> Option<RuleDiagnostic> {
65+
let node = ctx.query();
66+
Some(RuleDiagnostic::new(
67+
rule_category!(),
68+
node.range(),
69+
markup! {
70+
"Escaping newlines to create multiline strings is disallowed."
71+
},
72+
))
73+
}
74+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/* should generate diagnostics */
2+
const foo =
3+
"Line 1\n\
4+
Line 2";
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
source: crates/biome_js_analyze/tests/spec_tests.rs
3+
expression: invalid.js
4+
---
5+
# Input
6+
```js
7+
/* should generate diagnostics */
8+
const foo =
9+
"Line 1\n\
10+
Line 2";
11+
12+
```
13+
14+
# Diagnostics
15+
```
16+
invalid.js:3:2 lint/nursery/noMultiStr ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
17+
18+
× Escaping newlines to create multiline strings is disallowed.
19+
20+
1 │ /* should generate diagnostics */
21+
2 │ const foo =
22+
> 3 │ "Line 1\n\
23+
│ ^^^^^^^^^^
24+
> 4 │ Line 2";
25+
│ ^^^^^^^
26+
5 │
27+
28+
29+
```
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/* should not generate diagnostics */
2+
const foo = "Line 1\nLine 2";
3+
const bar = `Line 1
4+
Line 2`;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
source: crates/biome_js_analyze/tests/spec_tests.rs
3+
expression: valid.js
4+
---
5+
# Input
6+
```js
7+
/* should not generate diagnostics */
8+
const foo = "Line 1\nLine 2";
9+
const bar = `Line 1
10+
Line 2`;
11+
12+
```

0 commit comments

Comments
 (0)