diff --git a/crates/cli/src/analyser/core.rs b/crates/cli/src/analyser/core.rs new file mode 100644 index 0000000..542864f --- /dev/null +++ b/crates/cli/src/analyser/core.rs @@ -0,0 +1,80 @@ +use crate::analyser::index_identifier::IdentifierIndex; +use crate::diagnostics::diagnose::Diagnose; +use crate::diagnostics::kinds::DiagnosticKind; +use crate::diagnostics::reporter::Reporter; +use crate::parser::Meta; +use tucana::shared::{DefinitionDataType, FlowType, RuntimeFunctionDefinition}; + +#[derive(Clone)] +pub struct AnalysableDataType { + pub original_definition: Meta, + pub definition_data_type: DefinitionDataType, + pub id: i16, +} + +#[derive(Clone)] +pub struct AnalysableFlowType { + pub original_definition: Meta, + pub flow_type: FlowType, + pub id: i16, +} + +#[derive(Clone)] +pub struct AnalysableFunction { + pub original_definition: Meta, + pub function: RuntimeFunctionDefinition, + pub id: i16, +} + +pub struct Analyser { + pub reporter: Reporter, + pub(crate) index: IdentifierIndex, + pub data_types: Vec, + pub flow_types: Vec, + pub functions: Vec, +} + +impl Analyser { + pub fn new(path: &str) -> Self { + super::loader::load_from_path(path) + } + + pub fn report(&mut self, will_exit: bool) { + // Run analysis passes + for dt in self.data_types.clone() { + self.analyse_data_type(&dt); + } + for ft in self.flow_types.clone() { + self.analyse_flow_type(&ft); + } + for f in self.functions.clone() { + self.analyse_runtime_function(&f); + } + self.reporter.print(will_exit); + } + + pub fn data_type_identifier_exists(&self, identifier: &str, except_id: Option) -> bool { + self.index.has_data_type(identifier, except_id) + } + + pub fn generic_key_in_target(&self, key: &str, target: &str) -> bool { + let norm_target = target.to_ascii_lowercase(); + self.data_types.iter().any(|dt| { + dt.definition_data_type + .identifier + .eq_ignore_ascii_case(&norm_target) + && dt + .definition_data_type + .generic_keys + .contains(&key.to_string()) + }) + } + + pub fn null_field(&mut self, name: String, adt: &AnalysableDataType) { + self.reporter.add(Diagnose::new( + adt.definition_data_type.identifier.clone(), + adt.original_definition.clone(), + DiagnosticKind::NullField { field_name: name }, + )); + } +} diff --git a/crates/cli/src/analyser/data_type.rs b/crates/cli/src/analyser/data_type.rs new file mode 100644 index 0000000..0ae8f71 --- /dev/null +++ b/crates/cli/src/analyser/data_type.rs @@ -0,0 +1,180 @@ +use crate::analyser::core::{AnalysableDataType, Analyser}; +use crate::diagnostics::diagnose::Diagnose; +use crate::diagnostics::kinds::DiagnosticKind; +use tucana::shared::DataTypeIdentifier; +use tucana::shared::data_type_identifier::Type; +use tucana::shared::definition_data_type_rule::Config; + +impl Analyser { + pub fn analyse_data_type(&mut self, adt: &AnalysableDataType) { + let dt = &adt.definition_data_type; + if self.index.has_data_type(&dt.identifier, Some(adt.id)) { + self.reporter.add(Diagnose::new( + dt.identifier.clone(), + adt.original_definition.clone(), + DiagnosticKind::DuplicateDataTypeIdentifier { + identifier: dt.identifier.clone(), + }, + )); + } + + if dt.variant == 0 { + self.reporter.add(Diagnose::new( + dt.identifier.clone(), + adt.original_definition.clone(), + DiagnosticKind::ForbiddenVariant, + )); + } + + let mut detected: Vec = vec![]; + for optional_rule in &dt.rules { + if let Some(config) = &optional_rule.config { + match config { + Config::ContainsKey(rule) => { + if let Some(dti) = &rule.data_type_identifier { + self.walk_data_type_identifier(adt, dti, &mut detected); + } else { + self.null_field("definition_data_type_contains_key_rule".into(), adt); + } + } + Config::ContainsType(rule) => { + if let Some(dti) = &rule.data_type_identifier { + self.walk_data_type_identifier(adt, dti, &mut detected); + } else { + self.null_field("definition_data_type_contains_type_rule".into(), adt); + } + } + Config::ItemOfCollection(rule) => { + if rule.items.is_empty() { + self.null_field( + "definition_data_type_item_of_collection_rule".into(), + adt, + ); + } + } + Config::NumberRange(_) | Config::Regex(_) => {} + Config::InputTypes(rule) => { + if rule.input_types.is_empty() { + self.null_field("definition_data_type_input_types_rule".into(), adt); + } + for input in &rule.input_types { + if let Some(dti) = &input.data_type_identifier { + self.walk_data_type_identifier(adt, dti, &mut detected); + } else { + self.reporter.add(Diagnose::new( + dt.identifier.clone(), + adt.original_definition.clone(), + DiagnosticKind::UndefinedDataTypeIdentifier { + identifier: dt.identifier.clone(), + }, + )); + } + } + } + Config::ReturnType(rule) => { + if let Some(dti) = &rule.data_type_identifier { + self.walk_data_type_identifier(adt, dti, &mut detected); + } else { + self.null_field("definition_data_type_return_type_rule".into(), adt); + } + } + Config::ParentType(rule) => { + if let Some(dti) = &rule.parent_type { + self.walk_data_type_identifier(adt, dti, &mut detected); + } else { + self.null_field("definition_data_type_parent_type_rule".into(), adt); + } + } + } + } else { + self.null_field("rule".into(), adt); + } + } + + for key in dt.generic_keys.iter().filter(|k| !detected.contains(k)) { + self.reporter.add(Diagnose::new( + dt.identifier.clone(), + adt.original_definition.clone(), + DiagnosticKind::UnusedGenericKey { key: key.clone() }, + )); + } + for key in detected + .into_iter() + .filter(|k| !dt.generic_keys.contains(k)) + { + self.reporter.add(Diagnose::new( + dt.identifier.clone(), + adt.original_definition.clone(), + DiagnosticKind::UndefinedGenericKey { key }, + )); + } + + if dt.name.is_empty() { + self.reporter.add(Diagnose::new( + dt.identifier.clone(), + adt.original_definition.clone(), + DiagnosticKind::UndefinedTranslation { + translation_field: "name".into(), + }, + )); + } + } + + fn walk_data_type_identifier( + &mut self, + adt: &AnalysableDataType, + dti: &DataTypeIdentifier, + acc: &mut Vec, + ) { + if let Some(t) = &dti.r#type { + match t { + Type::DataTypeIdentifier(identifier) => { + if !self.data_type_identifier_exists(identifier, Some(adt.id)) { + self.reporter.add(Diagnose::new( + adt.definition_data_type.identifier.clone(), + adt.original_definition.clone(), + DiagnosticKind::UndefinedDataTypeIdentifier { + identifier: identifier.clone(), + }, + )); + } + } + Type::GenericType(generic) => { + if !self + .data_type_identifier_exists(&generic.data_type_identifier, Some(adt.id)) + { + self.reporter.add(Diagnose::new( + adt.definition_data_type.identifier.clone(), + adt.original_definition.clone(), + DiagnosticKind::UndefinedDataTypeIdentifier { + identifier: generic.data_type_identifier.clone(), + }, + )); + } + if generic.generic_mappers.is_empty() { + self.reporter.add(Diagnose::new( + adt.definition_data_type.identifier.clone(), + adt.original_definition.clone(), + DiagnosticKind::EmptyGenericMapper, + )); + } + for mapper in &generic.generic_mappers { + if adt + .definition_data_type + .generic_keys + .contains(&mapper.target) + { + acc.push(mapper.target.clone()); + } + for source in &mapper.source { + self.walk_data_type_identifier(adt, source, acc); + } + } + } + Type::GenericKey(key) => acc.push(key.clone()), + } + } else { + self.null_field("data_type".into(), adt); + } + } +} diff --git a/crates/cli/src/analyser/flow_type.rs b/crates/cli/src/analyser/flow_type.rs new file mode 100644 index 0000000..1ecb51a --- /dev/null +++ b/crates/cli/src/analyser/flow_type.rs @@ -0,0 +1,100 @@ +use crate::analyser::core::{AnalysableFlowType, Analyser}; +use crate::diagnostics::diagnose::Diagnose; +use crate::diagnostics::kinds::DiagnosticKind; + +impl Analyser { + pub fn analyse_flow_type(&mut self, aft: &AnalysableFlowType) { + let flow = &aft.flow_type; + let name = flow.identifier.clone(); + let original = aft.original_definition.clone(); + + if flow.name.is_empty() { + self.reporter.add(Diagnose::new( + name.clone(), + original.clone(), + DiagnosticKind::UndefinedTranslation { + translation_field: "name".into(), + }, + )); + } + if flow.description.is_empty() { + self.reporter.add(Diagnose::new( + name.clone(), + original.clone(), + DiagnosticKind::UndefinedTranslation { + translation_field: "description".into(), + }, + )); + } + if flow.documentation.is_empty() { + self.reporter.add(Diagnose::new( + name.clone(), + original.clone(), + DiagnosticKind::UndefinedTranslation { + translation_field: "documentation".into(), + }, + )); + } + + if let Some(identifier) = &flow.input_type_identifier { + if !self.data_type_identifier_exists(identifier, None) { + self.reporter.add(Diagnose::new( + name.clone(), + original.clone(), + DiagnosticKind::UndefinedDataTypeIdentifier { + identifier: identifier.clone(), + }, + )); + } + } + if let Some(identifier) = &flow.return_type_identifier { + if !self.data_type_identifier_exists(identifier, None) { + self.reporter.add(Diagnose::new( + name.clone(), + original.clone(), + DiagnosticKind::UndefinedDataTypeIdentifier { + identifier: identifier.clone(), + }, + )); + } + } + + for setting in &flow.settings { + if setting.name.is_empty() { + self.reporter.add(Diagnose::new( + setting.identifier.clone(), + original.clone(), + DiagnosticKind::UndefinedTranslation { + translation_field: "flow_setting.name".into(), + }, + )); + } + if setting.description.is_empty() { + self.reporter.add(Diagnose::new( + setting.identifier.clone(), + original.clone(), + DiagnosticKind::UndefinedTranslation { + translation_field: "flow_setting.description".into(), + }, + )); + } + if !self.data_type_identifier_exists(&setting.data_type_identifier, None) { + self.reporter.add(Diagnose::new( + name.clone(), + original.clone(), + DiagnosticKind::UndefinedDataTypeIdentifier { + identifier: setting.data_type_identifier.clone(), + }, + )); + } + } + + if self.index.has_flow_type(&name, Some(aft.id)) { + self.reporter.add(Diagnose::new( + name.clone(), + original.clone(), + DiagnosticKind::DuplicateFlowTypeIdentifier { identifier: name }, + )); + } + } +} diff --git a/crates/cli/src/analyser/function.rs b/crates/cli/src/analyser/function.rs new file mode 100644 index 0000000..bd026e3 --- /dev/null +++ b/crates/cli/src/analyser/function.rs @@ -0,0 +1,188 @@ +use crate::analyser::core::{AnalysableFunction, Analyser}; +use crate::diagnostics::diagnose::Diagnose; +use crate::diagnostics::kinds::DiagnosticKind; +use tucana::shared::DataTypeIdentifier; +use tucana::shared::data_type_identifier::Type; + +impl Analyser { + pub fn analyse_runtime_function(&mut self, af: &AnalysableFunction) { + let name = af.function.runtime_name.clone(); + let function = &af.function; + let original = af.original_definition.clone(); + + if function.name.is_empty() { + self.reporter.add(Diagnose::new( + name.clone(), + original.clone(), + DiagnosticKind::UndefinedTranslation { + translation_field: "name".into(), + }, + )); + } + if function.description.is_empty() { + self.reporter.add(Diagnose::new( + name.clone(), + original.clone(), + DiagnosticKind::UndefinedTranslation { + translation_field: "description".into(), + }, + )); + } + if function.documentation.is_empty() { + self.reporter.add(Diagnose::new( + name.clone(), + original.clone(), + DiagnosticKind::UndefinedTranslation { + translation_field: "documentation".into(), + }, + )); + } + + let mut detected_generic_keys: Vec = vec![]; + if let Some(identifier) = &function.return_type_identifier { + detected_generic_keys.append(&mut self.walk_function_dti( + &name, + &original, + identifier.clone(), + )); + } + + let mut param_names: Vec = vec![]; + for parameter in &function.runtime_parameter_definitions { + if parameter.name.is_empty() { + self.reporter.add(Diagnose::new( + name.clone(), + original.clone(), + DiagnosticKind::UndefinedTranslation { + translation_field: "name".into(), + }, + )); + } + if parameter.description.is_empty() { + self.reporter.add(Diagnose::new( + name.clone(), + original.clone(), + DiagnosticKind::UndefinedTranslation { + translation_field: "description".into(), + }, + )); + } + if parameter.documentation.is_empty() { + self.reporter.add(Diagnose::new( + name.clone(), + original.clone(), + DiagnosticKind::UndefinedTranslation { + translation_field: "documentation".into(), + }, + )); + } + + if let Some(identifier) = ¶meter.data_type_identifier { + detected_generic_keys.append(&mut self.walk_function_dti( + &name, + &original, + identifier.clone(), + )); + } else { + self.reporter.add(Diagnose::new( + name.clone(), + original.clone(), + DiagnosticKind::NullField { + field_name: "data_type".into(), + }, + )); + } + + if param_names.contains(¶meter.runtime_name) { + self.reporter.add(Diagnose::new( + name.clone(), + original.clone(), + DiagnosticKind::DuplicateRuntimeParameterIdentifier { + identifier: parameter.runtime_name.clone(), + }, + )); + } + param_names.push(parameter.runtime_name.clone()); + } + + for key in function + .generic_keys + .iter() + .filter(|k| !detected_generic_keys.contains(k)) + .cloned() + { + self.reporter.add(Diagnose::new( + name.clone(), + original.clone(), + DiagnosticKind::UnusedGenericKey { key }, + )); + } + for key in detected_generic_keys + .into_iter() + .filter(|k| !function.generic_keys.contains(k)) + { + self.reporter.add(Diagnose::new( + name.clone(), + original.clone(), + DiagnosticKind::UndefinedGenericKey { key }, + )); + } + } + + fn walk_function_dti( + &mut self, + name: &str, + original: &crate::parser::Meta, + identifier: DataTypeIdentifier, + ) -> Vec { + let mut result: Vec = vec![]; + if let Some(t) = identifier.r#type { + match t { + Type::DataTypeIdentifier(dt) => { + if !self.data_type_identifier_exists(&dt, None) { + self.reporter.add(Diagnose::new( + name.to_string(), + original.clone(), + DiagnosticKind::UndefinedDataTypeIdentifier { identifier: dt }, + )); + } + } + Type::GenericType(gt) => { + if !self.data_type_identifier_exists(>.data_type_identifier, None) { + self.reporter.add(Diagnose::new( + name.to_string(), + original.clone(), + DiagnosticKind::UndefinedDataTypeIdentifier { + identifier: gt.data_type_identifier.clone(), + }, + )); + } + if gt.generic_mappers.is_empty() { + self.reporter.add(Diagnose::new( + name.to_string(), + original.clone(), + DiagnosticKind::EmptyGenericMapper, + )); + } + for mapper in >.generic_mappers { + for source in mapper.source.clone() { + result.append(&mut self.walk_function_dti(name, original, source)); + } + if !self.generic_key_in_target(&mapper.target, >.data_type_identifier) { + self.reporter.add(Diagnose::new( + name.to_string(), + original.clone(), + DiagnosticKind::GenericKeyNotInMappingTarget { + key: mapper.target.clone(), + target: gt.data_type_identifier.clone(), + }, + )); + } + } + } + Type::GenericKey(key) => result.push(key.clone()), + } + } + result + } +} diff --git a/crates/cli/src/analyser/index_identifier.rs b/crates/cli/src/analyser/index_identifier.rs new file mode 100644 index 0000000..588c889 --- /dev/null +++ b/crates/cli/src/analyser/index_identifier.rs @@ -0,0 +1,45 @@ +use std::collections::HashMap; + +#[derive(Default, Debug, Clone)] +pub struct IdentifierIndex { + data_types: HashMap, + flow_types: HashMap, + functions: HashMap, +} + +fn normalize(s: &str) -> String { + s.trim().to_ascii_lowercase() +} + +impl IdentifierIndex { + pub fn insert_data_type(&mut self, name: &str, id: i16) -> Option { + self.data_types.insert(normalize(name), id) + } + pub fn insert_flow_type(&mut self, name: &str, id: i16) -> Option { + self.flow_types.insert(normalize(name), id) + } + pub fn insert_function(&mut self, name: &str, id: i16) -> Option { + self.functions.insert(normalize(name), id) + } + + pub fn has_data_type(&self, name: &str, except: Option) -> bool { + self.data_types + .get(&normalize(name)) + .map(|found| except.map(|e| *found != e).unwrap_or(true)) + .unwrap_or(false) + } + + pub fn has_flow_type(&self, name: &str, except: Option) -> bool { + self.flow_types + .get(&normalize(name)) + .map(|found| except.map(|e| *found != e).unwrap_or(true)) + .unwrap_or(false) + } + + pub fn has_function(&self, name: &str, except: Option) -> bool { + self.functions + .get(&normalize(name)) + .map(|found| except.map(|e| *found != e).unwrap_or(true)) + .unwrap_or(false) + } +} diff --git a/crates/cli/src/analyser/loader.rs b/crates/cli/src/analyser/loader.rs new file mode 100644 index 0000000..c7cafb6 --- /dev/null +++ b/crates/cli/src/analyser/loader.rs @@ -0,0 +1,142 @@ +use super::core::{AnalysableDataType, AnalysableFlowType, AnalysableFunction, Analyser}; +use crate::analyser::index_identifier::IdentifierIndex; +use crate::diagnostics::diagnose::Diagnose; +use crate::diagnostics::kinds::DiagnosticKind; +use crate::diagnostics::reporter::Reporter; +use crate::parser::{MetaType, Parser, Reader}; +use tucana::shared::{DefinitionDataType, FlowType, RuntimeFunctionDefinition}; + +pub fn load_from_path(path: &str) -> Analyser { + let mut reporter = Reporter::default(); + let reader = Reader::from_path(path).expect("No definitions behind this path"); + + let mut current_index: i16 = 0; + let mut collected_data_types: Vec = vec![]; + let mut collected_flow_types: Vec = vec![]; + let mut collected_functions: Vec = vec![]; + let mut index = IdentifierIndex::default(); + + for definition in reader.meta { + match definition.r#type { + MetaType::FlowType => { + current_index += 1; + match serde_json::from_str::(definition.definition_string.as_str()) { + Ok(flow_type) => { + if let Some(_prev) = + index.insert_flow_type(&flow_type.identifier, current_index) + { + reporter.add(Diagnose::new( + flow_type.identifier.clone(), + definition.clone(), + DiagnosticKind::DuplicateFlowTypeIdentifier { + identifier: flow_type.identifier.clone(), + }, + )); + } + collected_flow_types.push(AnalysableFlowType { + original_definition: definition.clone(), + flow_type, + id: current_index, + }); + } + Err(err) => { + let name = Parser::extract_identifier( + definition.definition_string.as_str(), + MetaType::FlowType, + ); + reporter.add(Diagnose::new( + name, + definition.clone(), + DiagnosticKind::DeserializationError { + description: err.to_string(), + }, + )); + } + } + } + MetaType::DataType => { + current_index += 1; + match serde_json::from_str::( + definition.definition_string.as_str(), + ) { + Ok(data_type) => { + if let Some(_prev) = + index.insert_data_type(&data_type.identifier, current_index) + { + reporter.add(Diagnose::new( + data_type.identifier.clone(), + definition.clone(), + DiagnosticKind::DuplicateDataTypeIdentifier { + identifier: data_type.identifier.clone(), + }, + )); + } + collected_data_types.push(AnalysableDataType { + original_definition: definition.clone(), + definition_data_type: data_type, + id: current_index, + }); + } + Err(err) => { + let name = Parser::extract_identifier( + definition.definition_string.as_str(), + MetaType::DataType, + ); + reporter.add(Diagnose::new( + name, + definition.clone(), + DiagnosticKind::DeserializationError { + description: err.to_string(), + }, + )); + } + } + } + MetaType::RuntimeFunction => { + current_index += 1; + match serde_json::from_str::( + definition.definition_string.as_str(), + ) { + Ok(function) => { + if let Some(_prev) = + index.insert_function(&function.runtime_name, current_index) + { + reporter.add(Diagnose::new( + function.runtime_name.clone(), + definition.clone(), + DiagnosticKind::DuplicateRuntimeFunctionIdentifier { + identifier: function.runtime_name.clone(), + }, + )); + } + collected_functions.push(AnalysableFunction { + original_definition: definition.clone(), + function, + id: current_index, + }); + } + Err(err) => { + let name = Parser::extract_identifier( + definition.definition_string.as_str(), + MetaType::RuntimeFunction, + ); + reporter.add(Diagnose::new( + name, + definition.clone(), + DiagnosticKind::DeserializationError { + description: err.to_string(), + }, + )); + } + } + } + } + } + Analyser { + reporter, + index, + data_types: collected_data_types, + flow_types: collected_flow_types, + functions: collected_functions, + } +} diff --git a/crates/cli/src/analyser/mod.rs b/crates/cli/src/analyser/mod.rs index 1bf63d9..ff23c76 100644 --- a/crates/cli/src/analyser/mod.rs +++ b/crates/cli/src/analyser/mod.rs @@ -1,770 +1,6 @@ -mod diagnostics; - -use crate::analyser::diagnostics::DiagnosticKind::{ - DuplicateDataTypeIdentifier, DuplicateFlowTypeIdentifier, DuplicateRuntimeParameterIdentifier, - EmptyGenericMapper, GenericKeyNotInMappingTarget, NullField, UndefinedDataTypeIdentifier, - UndefinedGenericKey, UndefinedTranslation, UnusedGenericKey, -}; -use crate::analyser::diagnostics::{Diagnose, DiagnosticKind, Reporter}; -use crate::parser::Parser; -use crate::parser::{Meta, MetaType, Reader}; -use tucana::shared::data_type_identifier::Type; -use tucana::shared::definition_data_type_rule::Config; -use tucana::shared::{DataTypeIdentifier, DefinitionDataType, FlowType, RuntimeFunctionDefinition}; - -#[derive(Clone)] -pub struct AnalysableDataType { - pub original_definition: Meta, - pub definition_data_type: DefinitionDataType, - pub id: i16, -} - -#[derive(Clone)] -pub struct AnalysableFlowType { - pub original_definition: Meta, - pub flow_type: FlowType, - pub id: i16, -} - -#[derive(Clone)] -pub struct AnalysableFunction { - pub original_definition: Meta, - pub function: RuntimeFunctionDefinition, - pub id: i16, -} - -pub struct Analyser { - reporter: Reporter, - pub data_types: Vec, - pub flow_types: Vec, - pub functions: Vec, -} - -impl Analyser { - pub fn new(path: &str) -> Analyser { - let mut reporter = Reporter::default(); - let reader = match Reader::from_path(path) { - Some(res) => res, - None => { - panic!("No definitions behind this path"); - } - }; - - let mut current_index = 0; - let mut collected_data_types: Vec = vec![]; - let mut collected_flow_types: Vec = vec![]; - let mut collected_functions: Vec = vec![]; - - for definition in reader.meta { - match definition.r#type { - MetaType::FlowType => { - current_index += 1; - match serde_json::from_str::(definition.definition_string.as_str()) { - Ok(flow_type) => collected_flow_types.push(AnalysableFlowType { - original_definition: definition.clone(), - flow_type, - id: current_index, - }), - Err(err) => { - let name = Parser::extract_identifier( - definition.definition_string.as_str(), - MetaType::FlowType, - ); - let diagnose = Diagnose::new( - name, - definition.clone(), - DiagnosticKind::DeserializationError { - description: err.to_string(), - }, - ); - reporter.add_report(diagnose); - } - } - } - MetaType::DataType => { - current_index += 1; - match serde_json::from_str::( - definition.definition_string.as_str(), - ) { - Ok(data_type) => collected_data_types.push(AnalysableDataType { - original_definition: definition.clone(), - definition_data_type: data_type, - id: current_index, - }), - Err(err) => { - let name = Parser::extract_identifier( - definition.definition_string.as_str(), - MetaType::DataType, - ); - let diagnose = Diagnose::new( - name, - definition.clone(), - DiagnosticKind::DeserializationError { - description: err.to_string(), - }, - ); - reporter.add_report(diagnose); - } - } - } - MetaType::RuntimeFunction => { - current_index += 1; - match serde_json::from_str::( - definition.definition_string.as_str(), - ) { - Ok(function) => collected_functions.push(AnalysableFunction { - original_definition: definition.clone(), - function, - id: current_index, - }), - Err(err) => { - let name = Parser::extract_identifier( - definition.definition_string.as_str(), - MetaType::RuntimeFunction, - ); - let diagnose = Diagnose::new( - name, - definition.clone(), - DiagnosticKind::DeserializationError { - description: err.to_string(), - }, - ); - reporter.add_report(diagnose); - } - } - } - } - } - - Self { - reporter, - data_types: collected_data_types, - functions: collected_functions, - flow_types: collected_flow_types, - } - } - - pub fn data_type_identifier_exists(&self, identifier: String, id: i16) -> bool { - for data_types in &self.data_types { - if id == data_types.id { - continue; - } - - if data_types.definition_data_type.identifier.to_lowercase() - != identifier.to_lowercase() - { - continue; - } - return true; - } - false - } - - /// Checks (recursively) if the defined DataTypes are correct - pub fn handle_data_type( - &mut self, - analysable_data_type: AnalysableDataType, - data_type_identifier: DataTypeIdentifier, - ) -> Vec { - let data_type = analysable_data_type.definition_data_type.clone(); - let id = analysable_data_type.id; - let mut result = vec![]; - - if let Some(r#type) = data_type_identifier.r#type { - match r#type { - Type::DataTypeIdentifier(identifier) => { - if !self.data_type_identifier_exists(identifier.clone(), id) { - self.reporter.add_report(Diagnose::new( - analysable_data_type.definition_data_type.identifier, - analysable_data_type.original_definition, - UndefinedDataTypeIdentifier { identifier }, - )); - } - } - Type::GenericType(generic) => { - if !self.data_type_identifier_exists(generic.data_type_identifier.clone(), id) { - self.reporter.add_report(Diagnose::new( - analysable_data_type.definition_data_type.clone().identifier, - analysable_data_type.original_definition.clone(), - UndefinedDataTypeIdentifier { - identifier: generic.data_type_identifier, - }, - )); - } - - if generic.generic_mappers.is_empty() { - self.reporter.add_report(Diagnose::new( - analysable_data_type.definition_data_type.clone().identifier, - analysable_data_type.original_definition.clone(), - EmptyGenericMapper, - )) - } - - for mapper in generic.generic_mappers { - if data_type.generic_keys.contains(&mapper.target) { - result.push(mapper.target.clone()) - } - - for source in mapper.source { - result.append( - &mut self.handle_data_type(analysable_data_type.clone(), source), - ) - } - } - } - Type::GenericKey(key) => result.push(key.clone()), - } - } else { - self.reporter.add_report(Diagnose::new( - analysable_data_type.definition_data_type.clone().identifier, - analysable_data_type.original_definition.clone(), - NullField { - field_name: String::from("data_type"), - }, - )); - } - - result - } - - pub fn analyse_data_type(&mut self, analysable_data_type: AnalysableDataType) { - let id = analysable_data_type.id; - let data_type = analysable_data_type.definition_data_type.clone(); - // Check if Identifier is duplicate - if self.data_type_identifier_exists(data_type.identifier.clone(), id) { - self.reporter.add_report(Diagnose::new( - analysable_data_type.definition_data_type.clone().identifier, - analysable_data_type.original_definition.clone(), - DuplicateDataTypeIdentifier { - identifier: data_type.identifier.clone(), - }, - )); - } - - // The variant 0 never should occur - if data_type.variant == 0 { - self.reporter.add_report(Diagnose::new( - analysable_data_type.definition_data_type.clone().identifier, - analysable_data_type.original_definition.clone(), - DiagnosticKind::ForbiddenVariant, - )); - } - - // Generic Keys are present. Search if they are referenced! - if !data_type.generic_keys.is_empty() { - let mut detected_generic_keys: Vec = vec![]; - - for optional_rule in &data_type.rules { - if let Some(config) = optional_rule.clone().config { - match config { - Config::ContainsKey(rule) => { - if let Some(data_type_identifier) = rule.data_type_identifier { - detected_generic_keys.append(&mut self.handle_data_type( - analysable_data_type.clone(), - data_type_identifier, - )) - } else { - self.reporter.add_report(Diagnose::new( - analysable_data_type.definition_data_type.clone().identifier, - analysable_data_type.original_definition.clone(), - NullField { - field_name: String::from( - "definition_data_type_contains_key_rule", - ), - }, - )); - } - } - Config::ContainsType(rule) => { - if let Some(data_type_identifier) = rule.data_type_identifier { - detected_generic_keys.append(&mut self.handle_data_type( - analysable_data_type.clone(), - data_type_identifier, - )) - } else { - self.reporter.add_report(Diagnose::new( - analysable_data_type.definition_data_type.clone().identifier, - analysable_data_type.original_definition.clone(), - NullField { - field_name: String::from( - "definition_data_type_contains_type_rule", - ), - }, - )); - } - } - Config::ItemOfCollection(rule) => { - if rule.items.is_empty() { - self.reporter.add_report(Diagnose::new( - analysable_data_type.definition_data_type.clone().identifier, - analysable_data_type.original_definition.clone(), - NullField { - field_name: String::from( - "definition_data_type_item_of_collection_rule", - ), - }, - )); - } - } - Config::NumberRange(_) => {} - Config::Regex(_) => {} - Config::InputTypes(rule) => { - if rule.input_types.is_empty() { - self.reporter.add_report(Diagnose::new( - analysable_data_type.definition_data_type.clone().identifier, - analysable_data_type.original_definition.clone(), - NullField { - field_name: String::from( - "definition_data_type_input_types_rule", - ), - }, - )); - } - - for input_type in &rule.input_types { - if let Some(data_type_identifier) = &input_type.data_type_identifier - { - detected_generic_keys.append(&mut self.handle_data_type( - analysable_data_type.clone(), - data_type_identifier.clone(), - )) - } else { - self.reporter.add_report(Diagnose::new( - analysable_data_type - .definition_data_type - .clone() - .identifier, - analysable_data_type.original_definition.clone(), - UndefinedDataTypeIdentifier { - identifier: data_type.identifier.clone(), - }, - )); - } - } - } - Config::ReturnType(rule) => { - if let Some(data_type_identifier) = &rule.data_type_identifier { - detected_generic_keys.append(&mut self.handle_data_type( - analysable_data_type.clone(), - data_type_identifier.clone(), - )) - } else { - self.reporter.add_report(Diagnose::new( - analysable_data_type.definition_data_type.clone().identifier, - analysable_data_type.original_definition.clone(), - NullField { - field_name: String::from( - "definition_data_type_return_type_rule", - ), - }, - )); - } - } - Config::ParentType(rule) => { - if let Some(data_type_identifier) = &rule.parent_type { - detected_generic_keys.append(&mut self.handle_data_type( - analysable_data_type.clone(), - data_type_identifier.clone(), - )) - } else { - self.reporter.add_report(Diagnose::new( - analysable_data_type.definition_data_type.clone().identifier, - analysable_data_type.original_definition.clone(), - NullField { - field_name: String::from( - "definition_data_type_parent_type_rule", - ), - }, - )); - } - } - } - } - } - - let defined_but_unused = data_type - .generic_keys - .iter() - .filter(|key| !detected_generic_keys.contains(key)) - .collect::>(); - let used_but_undefined = detected_generic_keys - .iter() - .filter(|key| !data_type.generic_keys.contains(key)) - .collect::>(); - - for key in defined_but_unused { - self.reporter.add_report(Diagnose::new( - analysable_data_type.definition_data_type.clone().identifier, - analysable_data_type.original_definition.clone(), - UnusedGenericKey { key: key.clone() }, - )); - } - - for key in used_but_undefined { - self.reporter.add_report(Diagnose::new( - analysable_data_type.definition_data_type.clone().identifier, - analysable_data_type.original_definition.clone(), - UndefinedGenericKey { key: key.clone() }, - )); - } - } else { - // Check here for any empty configs! - for rule in &data_type.rules { - if rule.config.is_none() { - self.reporter.add_report(Diagnose::new( - analysable_data_type.definition_data_type.clone().identifier, - analysable_data_type.original_definition.clone(), - NullField { - field_name: String::from("rule"), - }, - )); - } - } - } - - // Check if at least one Translation is present - if data_type.name.is_empty() { - self.reporter.add_report(Diagnose::new( - analysable_data_type.definition_data_type.clone().identifier, - analysable_data_type.original_definition.clone(), - UndefinedTranslation { - translation_field: String::from("name"), - }, - )); - } - } - - pub fn analyse_flow_type(&mut self, analysable_flow_type: AnalysableFlowType) { - let flow = analysable_flow_type.flow_type.clone(); - let original_definition = analysable_flow_type.original_definition; - let name = flow.identifier; - - // Check if at least one Translation is present - if flow.name.is_empty() { - self.reporter.add_report(Diagnose::new( - name.clone(), - original_definition.clone(), - UndefinedTranslation { - translation_field: String::from("name"), - }, - )); - } - - if flow.description.is_empty() { - self.reporter.add_report(Diagnose::new( - name.clone(), - original_definition.clone(), - UndefinedTranslation { - translation_field: String::from("description"), - }, - )); - } - - if flow.documentation.is_empty() { - self.reporter.add_report(Diagnose::new( - name.clone(), - original_definition.clone(), - UndefinedTranslation { - translation_field: String::from("documentation"), - }, - )); - } - - // Check if input identifier exists - if let Some(identifier) = flow.input_type_identifier - && !self.data_type_identifier_exists(identifier.clone(), -1) - { - self.reporter.add_report(Diagnose::new( - name.clone(), - original_definition.clone(), - UndefinedDataTypeIdentifier { identifier }, - )); - } - - // Check if return identifier exists - if let Some(identifier) = flow.return_type_identifier - && !self.data_type_identifier_exists(identifier.clone(), -1) - { - self.reporter.add_report(Diagnose::new( - name.clone(), - original_definition.clone(), - UndefinedDataTypeIdentifier { identifier }, - )); - } - - // Check if flow type identifier already exists - for flow_type in &self.flow_types { - if analysable_flow_type.id == flow_type.id { - continue; - } - - if flow_type.flow_type.identifier.to_lowercase() == name.clone().to_lowercase() { - self.reporter.add_report(Diagnose::new( - name.clone(), - original_definition.clone(), - DuplicateFlowTypeIdentifier { identifier: name }, - )); - break; - } - } - } - - pub fn analyse_runtime_function(&mut self, analysable_function: AnalysableFunction) { - let name = analysable_function.function.runtime_name.clone(); - let function = analysable_function.function; - let original = analysable_function.original_definition; - let id = analysable_function.id; - - // Check if at least one Translation is present - if function.name.is_empty() { - self.reporter.add_report(Diagnose::new( - name.clone(), - original.clone(), - UndefinedTranslation { - translation_field: String::from("name"), - }, - )); - } - - if function.description.is_empty() { - self.reporter.add_report(Diagnose::new( - name.clone(), - original.clone(), - UndefinedTranslation { - translation_field: String::from("description"), - }, - )); - } - - if function.documentation.is_empty() { - self.reporter.add_report(Diagnose::new( - name.clone(), - original.clone(), - UndefinedTranslation { - translation_field: String::from("documentation"), - }, - )); - } - - // Check if runtime function already exists - for func in &self.functions { - if func.id == id { - continue; - } - - if func.function.runtime_name.to_lowercase() == name.clone().to_lowercase() { - self.reporter.add_report(Diagnose::new( - name.clone(), - original.clone(), - DuplicateFlowTypeIdentifier { - identifier: name.clone(), - }, - )); - break; - } - } - - let mut detected_generic_keys: Vec = vec![]; - if let Some(identifier) = function.return_type_identifier { - detected_generic_keys.append(&mut self.handle_function_data_type_identifier( - name.clone(), - original.clone(), - identifier, - )); - } - - let mut parameter_names: Vec = vec![]; - for parameter in function.runtime_parameter_definitions { - // Check if at least one Translation is present - if parameter.name.is_empty() { - self.reporter.add_report(Diagnose::new( - name.clone(), - original.clone(), - UndefinedTranslation { - translation_field: String::from("name"), - }, - )); - } - - if parameter.description.is_empty() { - self.reporter.add_report(Diagnose::new( - name.clone(), - original.clone(), - UndefinedTranslation { - translation_field: String::from("description"), - }, - )); - } - - if parameter.documentation.is_empty() { - self.reporter.add_report(Diagnose::new( - name.clone(), - original.clone(), - UndefinedTranslation { - translation_field: String::from("documentation"), - }, - )); - } - - // Check if data_type exists - if let Some(identifier) = parameter.data_type_identifier { - detected_generic_keys.append(&mut self.handle_function_data_type_identifier( - name.clone(), - original.clone(), - identifier, - )); - } else { - self.reporter.add_report(Diagnose::new( - name.clone(), - original.clone(), - NullField { - field_name: String::from("data_type"), - }, - )); - } - - if parameter_names.contains(¶meter.runtime_name) { - self.reporter.add_report(Diagnose::new( - name.clone(), - original.clone(), - DuplicateRuntimeParameterIdentifier { - identifier: parameter.runtime_name.clone(), - }, - )); - } - - parameter_names.push(parameter.runtime_name); - } - - let defined_but_unused = function - .generic_keys - .iter() - .filter(|key| !detected_generic_keys.contains(key)) - .collect::>(); - let used_but_undefined = detected_generic_keys - .iter() - .filter(|key| !function.generic_keys.contains(key)) - .collect::>(); - - for key in defined_but_unused { - self.reporter.add_report(Diagnose::new( - name.clone(), - original.clone(), - UnusedGenericKey { key: key.clone() }, - )); - } - - for key in used_but_undefined { - self.reporter.add_report(Diagnose::new( - name.clone(), - original.clone(), - UndefinedGenericKey { key: key.clone() }, - )); - } - } - - fn handle_function_data_type_identifier( - &mut self, - name: String, - original: Meta, - identifier: DataTypeIdentifier, - ) -> Vec { - let mut result: Vec = vec![]; - if let Some(r#type) = identifier.r#type { - match r#type { - Type::DataTypeIdentifier(data_type) => { - if !self.data_type_identifier_exists(data_type.clone(), -1) { - self.reporter.add_report(Diagnose::new( - name.clone(), - original.clone(), - UndefinedDataTypeIdentifier { - identifier: data_type.clone(), - }, - )) - }; - } - Type::GenericType(generic_type) => { - if !self - .data_type_identifier_exists(generic_type.data_type_identifier.clone(), -1) - { - self.reporter.add_report(Diagnose::new( - name.clone(), - original.clone(), - UndefinedDataTypeIdentifier { - identifier: generic_type.data_type_identifier.clone(), - }, - )) - } - - if generic_type.generic_mappers.is_empty() { - self.reporter.add_report(Diagnose::new( - name.clone(), - original.clone(), - EmptyGenericMapper, - )) - } - - for mapper in &generic_type.generic_mappers { - for source in mapper.source.clone() { - result.append(&mut self.handle_function_data_type_identifier( - name.clone(), - original.clone(), - source, - )) - } - - if !self.generic_key_in_target( - mapper.target.clone(), - generic_type.data_type_identifier.clone(), - ) { - self.reporter.add_report(Diagnose::new( - name.clone(), - original.clone(), - GenericKeyNotInMappingTarget { - key: mapper.target.clone(), - target: generic_type.data_type_identifier.clone(), - }, - )) - } - } - } - Type::GenericKey(key) => result.push(key.clone()), - } - } - - result - } - - fn generic_key_in_target(&mut self, key: String, target: String) -> bool { - let data_types: Vec = self - .data_types - .iter() - .map(|d| d.definition_data_type.clone()) - .collect(); - for data_type in data_types { - if target.to_lowercase() != data_type.identifier.to_lowercase() { - continue; - } - - return data_type.generic_keys.contains(&key); - } - - false - } - - pub fn report(&mut self, will_exit: bool) { - for data_type in self.data_types.clone() { - self.analyse_data_type(data_type.clone()); - } - - for flow_type in self.flow_types.clone() { - self.analyse_flow_type(flow_type.clone()); - } - - for functions in self.functions.clone() { - self.analyse_runtime_function(functions.clone()); - } - - self.reporter.run_report(will_exit); - } -} +pub mod core; +mod data_type; +mod flow_type; +mod function; +mod index_identifier; +mod loader; diff --git a/crates/cli/src/command/feature.rs b/crates/cli/src/command/feature.rs index 5ae1db5..ea52cf6 100644 --- a/crates/cli/src/command/feature.rs +++ b/crates/cli/src/command/feature.rs @@ -1,4 +1,4 @@ -use crate::analyser::Analyser; +use crate::analyser::core::Analyser; use crate::formatter::{success, success_table}; use crate::parser::{Feature, Parser}; use crate::table::{feature_table, summary_table}; diff --git a/crates/cli/src/command/report.rs b/crates/cli/src/command/report.rs index b476380..30bf537 100644 --- a/crates/cli/src/command/report.rs +++ b/crates/cli/src/command/report.rs @@ -1,4 +1,4 @@ -use crate::analyser::Analyser; +use crate::analyser::core::Analyser; use crate::formatter::{success, success_table}; use crate::parser::Parser; use crate::table::summary_table; diff --git a/crates/cli/src/command/watch.rs b/crates/cli/src/command/watch.rs index af0c166..31960d2 100644 --- a/crates/cli/src/command/watch.rs +++ b/crates/cli/src/command/watch.rs @@ -1,4 +1,4 @@ -use crate::analyser::Analyser; +use crate::analyser::core::Analyser; use crate::formatter::{default, info}; use notify::event::ModifyKind; use notify::{EventKind, RecursiveMode, Watcher, recommended_watcher}; diff --git a/crates/cli/src/analyser/diagnostics.rs b/crates/cli/src/diagnostics/diagnose.rs similarity index 53% rename from crates/cli/src/analyser/diagnostics.rs rename to crates/cli/src/diagnostics/diagnose.rs index 4bb964c..86f83e5 100644 --- a/crates/cli/src/analyser/diagnostics.rs +++ b/crates/cli/src/diagnostics/diagnose.rs @@ -1,120 +1,15 @@ +use crate::diagnostics::kinds::DiagnosticKind; +use crate::diagnostics::kinds::DiagnosticKind::*; +use crate::diagnostics::severity::Severity; use crate::formatter::{error, warning}; use crate::parser::Meta; -use std::cmp::PartialEq; use std::path::Path; -use std::process::exit; - -#[derive(Default)] -pub struct Reporter { - diagnose: Vec, -} - -impl PartialEq for Severity { - fn eq(&self, other: &Self) -> bool { - match self { - Severity::Error => { - if let Severity::Error = other { - return true; - } - false - } - Severity::Warning => { - if let Severity::Warning = other { - return true; - } - false - } - Severity::Debug => { - if let Severity::Debug = other { - return true; - } - false - } - } - } -} - -impl Reporter { - pub fn add_report(&mut self, diagnose: Diagnose) { - self.diagnose.push(diagnose); - } - - pub fn run_report(&self, will_exit: bool) { - for error in &self.get_errors() { - println!("{}", error.print()); - } - - for warning in &self.get_warnings() { - println!("{}", warning.print()); - } - - if !self.get_errors().is_empty() && will_exit { - exit(1) - } - } - - pub fn get_errors(&self) -> Vec<&Diagnose> { - self.diagnose - .iter() - .filter(|p| p.kind.severity() == Severity::Error) - .collect() - } - - pub fn get_warnings(&self) -> Vec<&Diagnose> { - self.diagnose - .iter() - .filter(|p| p.kind.severity() == Severity::Warning) - .collect() - } -} - -pub enum Severity { - Error, - Warning, - Debug, -} +#[derive(Debug, Clone)] pub struct Diagnose { - kind: DiagnosticKind, - definition_name: String, - definition: Meta, -} - -pub enum DiagnosticKind { - DeserializationError { description: String }, - DuplicateDataTypeIdentifier { identifier: String }, - DuplicateFlowTypeIdentifier { identifier: String }, - DuplicateRuntimeFunctionIdentifier { identifier: String }, - DuplicateRuntimeParameterIdentifier { identifier: String }, - UndefinedDataTypeIdentifier { identifier: String }, - EmptyGenericMapper, - GenericKeyNotInMappingTarget { key: String, target: String }, - NullField { field_name: String }, - ForbiddenVariant, - UnusedGenericKey { key: String }, - UndefinedGenericKey { key: String }, - UndefinedTranslation { translation_field: String }, -} - -impl DiagnosticKind { - pub fn severity(&self) -> Severity { - use DiagnosticKind::*; - match self { - DeserializationError { .. } - | DuplicateDataTypeIdentifier { .. } - | DuplicateFlowTypeIdentifier { .. } - | DuplicateRuntimeFunctionIdentifier { .. } - | DuplicateRuntimeParameterIdentifier { .. } - | GenericKeyNotInMappingTarget { .. } - | EmptyGenericMapper - | UndefinedDataTypeIdentifier { .. } - | NullField { .. } - | ForbiddenVariant - | UnusedGenericKey { .. } - | UndefinedGenericKey { .. } => Severity::Error, - UndefinedTranslation { .. } => Severity::Warning, - } - } + pub kind: DiagnosticKind, + pub definition_name: String, + pub definition: Meta, } impl Diagnose { @@ -125,16 +20,8 @@ impl Diagnose { kind, } } - pub fn print(&self) -> String { - let path = format!( - "{}:{}:{}", - Path::new(&self.definition.path.clone()).display(), - 1, - 1 - ); - - use DiagnosticKind::*; + let path = format!("{}:{}:{}", Path::new(&self.definition.path).display(), 1, 1); match &self.kind { EmptyGenericMapper => error( format!( @@ -219,11 +106,15 @@ impl Diagnose { ), UndefinedTranslation { translation_field } => warning( format!( - "`{}` has an empty field (`{}`) of translations!", + "`{}` has an empty field (`{}`) of translations!", self.definition_name, translation_field ), &path, ), } } + + pub fn severity(&self) -> Severity { + self.kind.severity() + } } diff --git a/crates/cli/src/diagnostics/kinds.rs b/crates/cli/src/diagnostics/kinds.rs new file mode 100644 index 0000000..6469275 --- /dev/null +++ b/crates/cli/src/diagnostics/kinds.rs @@ -0,0 +1,39 @@ +use crate::diagnostics::severity::Severity; + +#[derive(Debug, Clone)] +pub enum DiagnosticKind { + DeserializationError { description: String }, + DuplicateDataTypeIdentifier { identifier: String }, + DuplicateFlowTypeIdentifier { identifier: String }, + DuplicateRuntimeFunctionIdentifier { identifier: String }, + DuplicateRuntimeParameterIdentifier { identifier: String }, + UndefinedDataTypeIdentifier { identifier: String }, + EmptyGenericMapper, + GenericKeyNotInMappingTarget { key: String, target: String }, + NullField { field_name: String }, + ForbiddenVariant, + UnusedGenericKey { key: String }, + UndefinedGenericKey { key: String }, + UndefinedTranslation { translation_field: String }, +} + +impl DiagnosticKind { + pub fn severity(&self) -> Severity { + use DiagnosticKind::*; + match self { + DeserializationError { .. } + | DuplicateDataTypeIdentifier { .. } + | DuplicateFlowTypeIdentifier { .. } + | DuplicateRuntimeFunctionIdentifier { .. } + | DuplicateRuntimeParameterIdentifier { .. } + | GenericKeyNotInMappingTarget { .. } + | EmptyGenericMapper + | UndefinedDataTypeIdentifier { .. } + | NullField { .. } + | ForbiddenVariant + | UnusedGenericKey { .. } + | UndefinedGenericKey { .. } => Severity::Error, + UndefinedTranslation { .. } => Severity::Warning, + } + } +} diff --git a/crates/cli/src/diagnostics/mod.rs b/crates/cli/src/diagnostics/mod.rs new file mode 100644 index 0000000..023ad0b --- /dev/null +++ b/crates/cli/src/diagnostics/mod.rs @@ -0,0 +1,4 @@ +pub mod diagnose; +pub mod kinds; +pub mod reporter; +pub mod severity; diff --git a/crates/cli/src/diagnostics/reporter.rs b/crates/cli/src/diagnostics/reporter.rs new file mode 100644 index 0000000..c0f951f --- /dev/null +++ b/crates/cli/src/diagnostics/reporter.rs @@ -0,0 +1,47 @@ +use crate::diagnostics::diagnose::Diagnose; +use crate::diagnostics::severity::Severity; +use std::process::exit; + +#[derive(Default)] +pub struct Reporter { + diagnostics: Vec, +} + +impl Reporter { + pub fn add(&mut self, d: Diagnose) { + self.diagnostics.push(d); + } + + pub fn errors(&self) -> impl Iterator { + self.diagnostics + .iter() + .filter(|d| d.severity() == Severity::Error) + } + + pub fn warnings(&self) -> impl Iterator { + self.diagnostics + .iter() + .filter(|d| d.severity() == Severity::Warning) + } + + pub fn is_empty(&self) -> bool { + self.diagnostics.is_empty() + } + + pub fn print(&self, will_exit: bool) { + for d in self.errors() { + println!("{}", d.print()); + } + for d in self.warnings() { + println!("{}", d.print()); + } + if self + .diagnostics + .iter() + .any(|d| d.severity() == Severity::Error) + && will_exit + { + exit(1) + } + } +} diff --git a/crates/cli/src/diagnostics/severity.rs b/crates/cli/src/diagnostics/severity.rs new file mode 100644 index 0000000..41be2e1 --- /dev/null +++ b/crates/cli/src/diagnostics/severity.rs @@ -0,0 +1,6 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Severity { + Error, + Warning, + Debug, +} diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index cac35fe..dafe930 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,5 +1,6 @@ use clap::{Parser as ClapParser, Subcommand}; +pub mod diagnostics; pub mod parser; mod analyser; diff --git a/crates/cli/src/parser.rs b/crates/cli/src/parser.rs index 6883ed3..2cd5369 100644 --- a/crates/cli/src/parser.rs +++ b/crates/cli/src/parser.rs @@ -149,7 +149,7 @@ pub struct Reader { pub meta: Vec, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Meta { pub name: String, pub r#type: MetaType,