Skip to content

Commit

Permalink
feat: deserializer poc
Browse files Browse the repository at this point in the history
  • Loading branch information
Conaclos committed Oct 29, 2023
1 parent 3b22c30 commit 52ecb2d
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 6 deletions.
60 changes: 57 additions & 3 deletions crates/biome_deserialize/src/json.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use crate::{DeserializationDiagnostic, Deserialized, VisitNode};
use crate::{
DeserializableLanguage, DeserializationDiagnostic, Deserialized, NodeVisitor, VisitNode,
};
use biome_console::markup;
use biome_diagnostics::{DiagnosticExt, Error};
use biome_json_parser::{parse_json, JsonParserOptions};
use biome_json_syntax::{
AnyJsonValue, JsonArrayValue, JsonBooleanValue, JsonLanguage, JsonMemberName, JsonNumberValue,
JsonObjectValue, JsonRoot, JsonStringValue, JsonSyntaxNode,
JsonObjectValue, JsonRoot, JsonStringValue, JsonSyntaxNode, T,
};
use biome_rowan::{AstNode, AstSeparatedList, SyntaxNodeCast, TextRange, TokenText};
use biome_rowan::{AstNode, AstSeparatedList, SyntaxNode, SyntaxNodeCast, TextRange, TokenText};
use indexmap::IndexSet;
use std::num::ParseIntError;

Expand Down Expand Up @@ -624,3 +626,55 @@ where
deserialized: output,
}
}

impl DeserializableLanguage for JsonLanguage {
fn accept(
visitor: &mut impl NodeVisitor<JsonLanguage>,
value: SyntaxNode<JsonLanguage>,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) {
let range = value.text_trimmed_range();
let Some(value) = AnyJsonValue::cast(value) else {
return;
};
match value {
AnyJsonValue::JsonArrayValue(array) => {
let items = array
.elements()
.into_iter()
.filter_map(|x| x.ok())
.map(|x| x.into_syntax());
visitor.visit_array(items, range, diagnostics)
}
AnyJsonValue::JsonBogusValue(_) => visitor.fallback_on_error(range, diagnostics),
AnyJsonValue::JsonBooleanValue(value) => {
let Ok(value) = value.value_token() else {
return;
};
visitor.visit_bool(value.kind() == T![true], range, diagnostics)
}
AnyJsonValue::JsonNullValue(_) => visitor.visit_unit(range, diagnostics),
AnyJsonValue::JsonNumberValue(_) => todo!(),
AnyJsonValue::JsonObjectValue(object) => {
let members = object
.json_member_list()
.into_iter()
.filter_map(|x| x.ok())
.filter_map(|x| {
Some((x.name().ok()?.into_syntax(), x.value().ok()?.into_syntax()))
});
visitor.visit_map(members, range, diagnostics)
}
AnyJsonValue::JsonStringValue(value) => {
let Ok(value) = value.inner_string_text() else {
return;
};
visitor.visit_str(value.text(), range, diagnostics)
}
}
}

fn map_to_str(value: SyntaxNode<JsonLanguage>) -> Option<TokenText> {
JsonStringValue::cast(value).and_then(|value| value.inner_string_text().ok())
}
}
2 changes: 1 addition & 1 deletion crates/biome_deserialize/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use biome_diagnostics::{Error, Severity};
pub use diagnostics::{DeserializationAdvice, DeserializationDiagnostic};
use std::fmt::Debug;
pub use string_set::{deserialize_string_set, serialize_string_set, StringSet};
pub use visitor::VisitNode;
pub use visitor::*;

/// A small type to interrogate the result of a JSON deserialization
#[derive(Debug, Default)]
Expand Down
139 changes: 138 additions & 1 deletion crates/biome_deserialize/src/visitor.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::DeserializationDiagnostic;
use biome_rowan::{Language, SyntaxNode};
use biome_rowan::{Language, SyntaxNode, TextRange, TokenText};

/// Generic trait to implement when resolving the configuration from a generic language
pub trait VisitNode<L: Language>: Sized {
Expand Down Expand Up @@ -71,3 +71,140 @@ impl<L: Language> VisitNode<L> for () {
Some(())
}
}

pub trait NodeVisitor<L: DeserializableLanguage> {
fn visit_unit(&mut self, range: TextRange, diagnostics: &mut Vec<DeserializationDiagnostic>) {
self.fallback_on_error(range, diagnostics)
}

fn visit_bool(
&mut self,
_value: bool,
range: TextRange,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) {
self.fallback_on_error(range, diagnostics)
}

fn visit_str(
&mut self,
_value: &str,
range: TextRange,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) {
self.fallback_on_error(range, diagnostics)
}

fn visit_map(
&mut self,
_members: impl Iterator<Item = (SyntaxNode<L>, SyntaxNode<L>)>,
range: TextRange,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) {
self.fallback_on_error(range, diagnostics)
}

fn visit_array(
&mut self,
_items: impl Iterator<Item = SyntaxNode<L>>,
range: TextRange,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) {
self.fallback_on_error(range, diagnostics)
}

fn fallback_on_error(
&self,
range: TextRange,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) {
diagnostics.push(DeserializationDiagnostic::new("unsupported value").with_range(range))
}
}

pub trait Acceptable<L: Language> {
fn accept(&mut self, value: SyntaxNode<L>, diagnostics: &mut Vec<DeserializationDiagnostic>);
}

impl<L: DeserializableLanguage, T: NodeVisitor<L>> Acceptable<L> for T {
fn accept(&mut self, value: SyntaxNode<L>, diagnostics: &mut Vec<DeserializationDiagnostic>) {
L::accept(self, value, diagnostics)
}
}

pub trait DeserializableLanguage: Language {
fn accept(
visitor: &mut impl NodeVisitor<Self>,
value: SyntaxNode<Self>,
diagnostics: &mut Vec<DeserializationDiagnostic>,
);

fn map_to_str(value: SyntaxNode<Self>) -> Option<TokenText>;
}

impl<L: DeserializableLanguage> NodeVisitor<L> for bool {
fn visit_bool(
&mut self,
value: bool,
_range: TextRange,
_diagnostics: &mut Vec<DeserializationDiagnostic>,
) {
*self = value;
}

fn fallback_on_error(
&self,
range: TextRange,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) {
diagnostics.push(DeserializationDiagnostic::new_incorrect_type(
"boolean", range,
))
}
}

impl<L: DeserializableLanguage> NodeVisitor<L> for String {
fn visit_str(
&mut self,
value: &str,
_range: TextRange,
_diagnostics: &mut Vec<DeserializationDiagnostic>,
) {
*self = value.to_string();
}

fn fallback_on_error(
&self,
range: TextRange,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) {
diagnostics.push(DeserializationDiagnostic::new_incorrect_type(
"string", range,
))
}
}

impl<L: DeserializableLanguage, T: Default + NodeVisitor<L>> NodeVisitor<L> for Vec<T> {
fn visit_array(
&mut self,
values: impl Iterator<Item = SyntaxNode<L>>,
_range: TextRange,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) {
for value in values {
let mut element = T::default();
element.accept(value, diagnostics);
self.push(element);
}
}

fn fallback_on_error(
&self,
range: TextRange,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) {
diagnostics.push(DeserializationDiagnostic::new_incorrect_type(
"array", range,
))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use biome_analyze::{
use biome_console::markup;
use biome_deserialize::{
json::{has_only_known_keys, with_only_known_variants, VisitJsonNode},
DeserializationDiagnostic, VisitNode,
Acceptable, DeserializableLanguage, DeserializationDiagnostic, NodeVisitor, VisitNode,
};
use biome_diagnostics::Applicability;
use biome_js_semantic::CanBeImportedExported;
Expand Down Expand Up @@ -521,6 +521,56 @@ impl VisitNode<JsonLanguage> for NamingConventionOptions {
}
}

impl<L: DeserializableLanguage> NodeVisitor<L> for NamingConventionOptions {
fn visit_map(
&mut self,
members: impl Iterator<Item = (SyntaxNode<L>, SyntaxNode<L>)>,
_range: biome_rowan::TextRange,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) {
for (key, value) in members {
let Some(key) = L::map_to_str(key) else {
return;
};
match key.text() {
"strictCase" => {
self.strict_case.accept(value, diagnostics);
// or
// L::accept(&mut self.strict_case, value, diagnostics);
}
"enumMemberCase" => {
self.enum_member_case.accept(value, diagnostics);
// or
// L::accept(&mut self.enum_member_case, value, diagnostics);
}
_ => (),
}
}
}
}

impl<L: DeserializableLanguage> NodeVisitor<L> for EnumMemberCase {
fn visit_str(
&mut self,
value: &str,
range: biome_rowan::TextRange,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) {
match value {
"PascalCase" => *self = Self::Pascal,
"CONSTANT_CASE" => *self = Self::Constant,
"camelCase" => *self = Self::Camel,
_ => {
diagnostics.push(DeserializationDiagnostic::new_unknown_value(
value,
range,
&["PascalCase", "CONSTANT_CASE", "camelCase"],
));
}
}
}
}

/// Supported cases for TypeScript `enum` member names.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
Expand Down

0 comments on commit 52ecb2d

Please sign in to comment.