Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .changeset/tricky-worms-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
"@biomejs/biome": patch
---

Fixed a bug where the combination `files.includes` and `extends` might result in an incorrect list of glob patterns.
As per requirements, the list of `files.includes` must have `**` at the beginning *if there are only negated patterns*.

The bug was caused by an incorrect merging of the `files.includes` and `extends` fields. When the `extends` field was merged into the `files.includes` field, which could result in `**` not being in at the first place of the list.

After this fix, if a configuration file coming from `extends` defines `**` in the first place, and the user configuration uses its own `files.includes`, the final list will contain `**` in the first place.

The following example, the final `files.includes` list will be `["**", "!**/dist", "!components"]`

**Example**

```json5
// shared.json, some shared configuration
{
"files": {
"includes": ["**", "!**/dist"]
}
}
```

```json5
// biome.json, the user configuration
{
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think an extends property is missing here.

"files": {
"includes": ["!components"]
}
}
```
Plus, Biome now removes duplicates
File renamed without changes.
11 changes: 7 additions & 4 deletions crates/biome_cli/src/commands/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ pub(crate) fn init(session: CliSession, emit_jsonc: bool) -> Result<(), CliDiagn
if fs.path_exists(&working_directory.join("dist")) {
dist_enabled = true;
config.files = Some(FilesConfiguration {
includes: Some(vec![
"**".parse::<biome_glob::NormalizedGlob>().unwrap(),
"!!**/dist".parse::<biome_glob::NormalizedGlob>().unwrap(),
]),
includes: Some(
vec![
"**".parse::<biome_glob::NormalizedGlob>().unwrap(),
"!!**/dist".parse::<biome_glob::NormalizedGlob>().unwrap(),
]
.into(),
),
ignore_unknown: None,
max_size: None,
experimental_scanner_ignores: None,
Expand Down
20 changes: 12 additions & 8 deletions crates/biome_cli/src/execute/migrate/eslint_to_biome.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::collections::BTreeSet;

use super::{eslint_any_rule_to_biome::migrate_eslint_any_rule, eslint_eslint, eslint_typescript};
use biome_configuration::analyzer::SeverityOrGroup;
use biome_configuration::glob_list::GlobList;
use biome_configuration::{self as biome_config};
use biome_console::markup;
use biome_deserialize::Merge;
Expand Down Expand Up @@ -625,11 +626,8 @@ fn migrate_eslint_rule(
}
}

fn to_biome_includes(
files: &[impl AsRef<str>],
ignores: &[impl AsRef<str>],
) -> Vec<biome_glob::NormalizedGlob> {
let mut includes: Vec<biome_glob::NormalizedGlob> = Vec::new();
fn to_biome_includes(files: &[impl AsRef<str>], ignores: &[impl AsRef<str>]) -> GlobList {
let mut includes = GlobList::new();
if !files.is_empty() {
includes.extend(files.iter().filter_map(|glob| glob.as_ref().parse().ok()));
}
Expand Down Expand Up @@ -687,7 +685,7 @@ mod tests {
let linter = biome_config.linter.unwrap();
assert_eq!(
linter.includes.unwrap(),
["*.js".parse().unwrap(), "!*.test.js".parse().unwrap()],
vec!["*.js".parse().unwrap(), "!*.test.js".parse().unwrap()].into(),
);
assert!(linter.rules.is_some());
}
Expand Down Expand Up @@ -736,11 +734,12 @@ mod tests {
let linter = biome_config.linter.unwrap();
assert_eq!(
linter.includes.unwrap(),
[
vec![
"**".parse().unwrap(),
"!*.test.js".parse().unwrap(),
"!*.spec.js".parse().unwrap()
]
.into()
);
assert_eq!(
linter
Expand All @@ -759,7 +758,12 @@ mod tests {
let override0 = overrides.0.into_iter().next().unwrap();
assert_eq!(
override0.includes.unwrap(),
OverrideGlobs::Globs(["*.ts".parse().unwrap()].into_iter().collect()),
OverrideGlobs::Globs(Box::new(
["*.ts".parse().unwrap()]
.into_iter()
.collect::<Vec<_>>()
.into()
)),
);
assert_eq!(
override0
Expand Down
8 changes: 5 additions & 3 deletions crates/biome_cli/src/execute/migrate/prettier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use biome_formatter::{
AttributePosition, BracketSpacing, Expand, IndentWidth, LineEnding, LineWidth, QuoteStyle,
};
use biome_fs::{FileSystem, OpenOptions};
use biome_glob::NormalizedGlob;
use biome_html_formatter::context::SelfCloseVoidElements;
use biome_js_formatter::context::{ArrowParentheses, QuoteProperties, Semicolons, TrailingCommas};
use biome_json_parser::JsonParserOptions;
Expand Down Expand Up @@ -316,12 +317,13 @@ impl TryFrom<Override> for biome_configuration::OverridePattern {
type Error = Error;
fn try_from(Override { files, options }: Override) -> anyhow::Result<Self> {
let mut result = Self {
includes: Some(biome_configuration::OverrideGlobs::Globs(
includes: Some(biome_configuration::OverrideGlobs::Globs(Box::new(
files
.into_iter()
.filter_map(|glob| glob.parse().ok())
.collect(),
)),
.collect::<Vec<NormalizedGlob>>()
.into(),
))),
..Default::default()
};
if options.print_width.is_some()
Expand Down
50 changes: 50 additions & 0 deletions crates/biome_cli/tests/cases/config_extends.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,3 +550,53 @@ fn extends_config_merge_overrides() {
result,
));
}

#[test]
fn extends_config_by_moving_double_star_at_the_beginning_from_extend_config() {
let fs = MemoryFileSystem::default();
let mut console = BufferConsole::default();

let shared = Utf8Path::new("shared.json");
fs.insert(
shared.into(),
r#"{
"files": {
"includes": ["**", "!**/dist"]
}
}"#,
);

let biome_json = Utf8Path::new("biome.json");
fs.insert(
biome_json.into(),
r#"{
"extends": ["shared.json"],
"files": {
"includes": ["!components"]
}
}"#,
);

let test_file = Utf8Path::new("components/test.js");
fs.insert(test_file.into(), "debugger; const a = 0;");

let test_file = Utf8Path::new("dist/test.js");
fs.insert(test_file.into(), "debugger; const a = 0;");

let test_file = Utf8Path::new("trigger.js");
fs.insert(test_file.into(), "debugger; const a = 0;");

let (fs, result) = run_cli(
fs,
&mut console,
Args::from(["lint", test_file.as_str()].as_slice()),
);

assert_cli_snapshot(SnapshotPayload::new(
module_path!(),
"extends_config_by_moving_double_star_at_the_beginning_from_extend_config",
fs,
console,
result,
));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
source: crates/biome_cli/tests/snap_test.rs
expression: redactor(content)
---
## `biome.json`

```json
{
"extends": ["shared.json"],
"files": {
"includes": ["!components"]
}
}
```

## `components/test.js`

```js
debugger; const a = 0;
```

## `dist/test.js`

```js
debugger; const a = 0;
```

## `shared.json`

```json
{
"files": {
"includes": ["**", "!**/dist"]
}
}
```

## `trigger.js`

```js
debugger; const a = 0;
```

# Termination Message

```block
lint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

× Some errors were emitted while running checks.



```

# Emitted Messages

```block
trigger.js:1:17 lint/correctness/noUnusedVariables FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! This variable a is unused.

> 1 │ debugger; const a = 0;
│ ^

i Unused variables are often the result of an incomplete refactoring, typos, or other sources of bugs.

i Unsafe fix: If this is intentional, prepend a with an underscore.

- debugger;·const·a·=·0;
+ debugger;·const·_a·=·0;


```

```block
trigger.js:1:1 lint/suspicious/noDebugger FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

× This is an unexpected use of the debugger statement.

> 1 │ debugger; const a = 0;
│ ^^^^^^^^^

i Unsafe fix: Remove debugger statement

1 │ debugger;·const·a·=·0;
│ ----------

```

```block
Checked 1 file in <TIME>. No fixes applied.
Found 1 error.
Found 1 warning.
```
3 changes: 2 additions & 1 deletion crates/biome_configuration/src/analyzer/assist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod actions;

pub use crate::analyzer::assist::actions::*;
use crate::bool::Bool;
use crate::glob_list::GlobList;
use biome_deserialize_macros::{Deserializable, Merge};
use bpaf::Bpaf;
use serde::{Deserialize, Serialize};
Expand All @@ -26,7 +27,7 @@ pub struct AssistConfiguration {
/// match these patterns.
#[bpaf(hide, pure(Default::default()))]
#[serde(skip_serializing_if = "Option::is_none")]
pub includes: Option<Vec<biome_glob::NormalizedGlob>>,
pub includes: Option<GlobList>,
}

impl AssistConfiguration {
Expand Down
3 changes: 2 additions & 1 deletion crates/biome_configuration/src/analyzer/linter/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod rules;

use crate::bool::Bool;
use crate::glob_list::GlobList;
use biome_analyze::RuleDomain;
use biome_deserialize_macros::{Deserializable, Merge};
use bpaf::Bpaf;
Expand Down Expand Up @@ -31,7 +32,7 @@ pub struct LinterConfiguration {
/// match these patterns.
#[bpaf(pure(Default::default()), hide)]
#[serde(skip_serializing_if = "Option::is_none")]
pub includes: Option<Vec<biome_glob::NormalizedGlob>>,
pub includes: Option<GlobList>,

/// An object where the keys are the names of the domains, and the values are `all`, `recommended`, or `none`.
#[bpaf(hide, pure(Default::default()))]
Expand Down
3 changes: 2 additions & 1 deletion crates/biome_configuration/src/formatter.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::bool::Bool;
use crate::glob_list::GlobList;
use biome_deserialize_macros::{Deserializable, Merge};
use biome_formatter::{
AttributePosition, BracketSameLine, BracketSpacing, Expand, IndentStyle, IndentWidth,
Expand Down Expand Up @@ -86,7 +87,7 @@ pub struct FormatterConfiguration {
/// match these patterns.
#[bpaf(pure(Default::default()), hide)]
#[serde(skip_serializing_if = "Option::is_none")]
pub includes: Option<Vec<biome_glob::NormalizedGlob>>,
pub includes: Option<GlobList>,
}

impl FormatterConfiguration {
Expand Down
Loading
Loading