Skip to content

Commit 043fbb5

Browse files
fix: level is now mandatory (#10695)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 0beea15 commit 043fbb5

6 files changed

Lines changed: 68 additions & 15 deletions

File tree

.changeset/olive-cameras-swim.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Fixed [#10674](https://github.com/biomejs/biome/issues/10674). Biome now throws an error when the field `level` is missing from a rule option.

crates/biome_configuration/src/analyzer/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ impl Merge for RuleAssistPlainConfiguration {
431431
#[serde(rename_all = "camelCase", deny_unknown_fields)]
432432
pub struct RuleAssistWithOptions<T: Default> {
433433
/// The severity of the emitted diagnostics by the rule
434+
#[deserializable(required)]
434435
pub level: RuleAssistPlainConfiguration,
435436
/// Rule's options
436437
pub options: T,
@@ -472,6 +473,7 @@ where
472473
#[serde(rename_all = "camelCase", deny_unknown_fields)]
473474
pub struct RuleWithOptions<T: Default + Merge> {
474475
/// The severity of the emitted diagnostics by the rule
476+
#[deserializable(required)]
475477
pub level: RulePlainConfiguration,
476478
/// Rule's options
477479
#[serde(default)]
@@ -518,6 +520,7 @@ where
518520
#[serde(rename_all = "camelCase", deny_unknown_fields)]
519521
pub struct RuleWithFixOptions<T: Default + Merge> {
520522
/// The severity of the emitted diagnostics by the rule
523+
#[deserializable(required)]
521524
pub level: RulePlainConfiguration,
522525
/// The kind of the code actions emitted by the rule
523526
#[serde(skip_serializing_if = "Option::is_none")]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"linter": {
3+
"rules": {
4+
"suspicious": {
5+
"noDebugger": {}
6+
}
7+
}
8+
}
9+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
source: crates/biome_configuration/tests/spec_tests.rs
3+
expression: rule_without_level.json
4+
---
5+
rule_without_level.json:5:23 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
6+
7+
× The key `level` is missing.
8+
9+
3"rules": {
10+
4"suspicious": {
11+
> 5"noDebugger": {}
12+
^^
13+
6 │ }
14+
7 │ }
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"linter": {
3+
"rules": {
4+
"style": {
5+
"useNamingConvention": {
6+
"level": "off",
7+
"options": {
8+
"strictCase": false
9+
}
10+
}
11+
}
12+
}
13+
}
14+
}

crates/biome_deserialize_macros/src/deserializable_derive.rs

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use crate::deserializable_derive::struct_field_attrs::StructFieldAttrs;
99
use biome_string_case::Case;
1010
use proc_macro_error2::*;
1111
use proc_macro2::{Ident, TokenStream};
12-
use quote::quote;
13-
use syn::{Data, GenericParam, Generics, Path, Type};
12+
use quote::{format_ident, quote};
13+
use syn::{Data, GenericParam, Generics, Path};
1414

1515
pub(crate) struct DeriveInput {
1616
pub ident: Ident,
@@ -75,10 +75,8 @@ impl DeriveInput {
7575
let fields = data
7676
.fields
7777
.into_iter()
78-
.filter_map(|field| {
79-
field.ident.map(|ident| (ident, field.attrs, field.ty))
80-
})
81-
.filter_map(|(ident, attrs, ty)| {
78+
.filter_map(|field| field.ident.map(|ident| (ident, field.attrs)))
79+
.filter_map(|(ident, attrs)| {
8280
let attrs = StructFieldAttrs::try_from(&attrs)
8381
.expect("Could not parse field attributes");
8482
if attrs.skip {
@@ -108,7 +106,6 @@ impl DeriveInput {
108106
ident,
109107
key,
110108
required: attrs.required,
111-
ty,
112109
validate: attrs.validate,
113110
})
114111
})
@@ -202,7 +199,6 @@ pub struct DeserializableFieldData {
202199
ident: Ident,
203200
key: String,
204201
required: bool,
205-
ty: Type,
206202
validate: Option<Path>,
207203
}
208204

@@ -351,11 +347,18 @@ fn generate_deserializable_struct(
351347
.fields
352348
.into_iter()
353349
.map(|field_data| {
350+
let is_required = field_data.required;
354351
let DeserializableFieldData {
355352
ident: field_ident,
356353
key,
357354
..
358355
} = field_data;
356+
let mark_seen = if is_required {
357+
let seen_ident = format_ident!("seen_{}", field_ident);
358+
quote! { #seen_ident = true; }
359+
} else {
360+
quote! {}
361+
};
359362
let deprecation_notice = field_data.deprecated.map(|deprecated| match deprecated {
360363
DeprecatedField::Message(message) => quote! {
361364
ctx.report(DeserializationDiagnostic::new_deprecated(
@@ -386,6 +389,7 @@ fn generate_deserializable_struct(
386389

387390
quote! {
388391
#key => {
392+
#mark_seen
389393
match Deserializable::deserialize(ctx, &value, &key_text)#validate {
390394
Some(value) => {
391395
#deprecation_notice
@@ -398,6 +402,13 @@ fn generate_deserializable_struct(
398402
})
399403
.collect();
400404

405+
let seen_declarations: Vec<_> = required_fields
406+
.iter()
407+
.map(|field_data| {
408+
let seen_ident = format_ident!("seen_{}", field_data.ident);
409+
quote! { let mut #seen_ident = false; }
410+
})
411+
.collect();
401412
let validator = if required_fields.is_empty() {
402413
quote! {}
403414
} else {
@@ -406,14 +417,10 @@ fn generate_deserializable_struct(
406417
.map(|field_data| &field_data.key)
407418
.collect();
408419
let required_fields = required_fields.iter().map(|field_data| {
409-
let DeserializableFieldData {
410-
ident: field_ident,
411-
key,
412-
ty,
413-
..
414-
} = field_data;
420+
let DeserializableFieldData { ident, key, .. } = field_data;
421+
let seen_ident = format_ident!("seen_{}", ident);
415422
quote! {
416-
if result.#field_ident == #ty::default() {
423+
if !#seen_ident {
417424
ctx.report(DeserializationDiagnostic::new_missing_key(
418425
#key,
419426
range,
@@ -496,6 +503,7 @@ fn generate_deserializable_struct(
496503
) -> Option<Self::Output> {
497504
use biome_deserialize::{Deserializable, DeserializationDiagnostic, Text};
498505
let mut result: Self::Output = Self::Output::default();
506+
#(#seen_declarations)*
499507
for (key, value) in members.flatten() {
500508
let Some(key_text) = Text::deserialize(ctx, &key, "") else {
501509
continue;

0 commit comments

Comments
 (0)