Skip to content
Merged
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
80 changes: 80 additions & 0 deletions crates/cli/src/analyser/core.rs
Original file line number Diff line number Diff line change
@@ -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<AnalysableDataType>,
pub flow_types: Vec<AnalysableFlowType>,
pub functions: Vec<AnalysableFunction>,
}

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<i16>) -> 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 },
));
}
}
180 changes: 180 additions & 0 deletions crates/cli/src/analyser/data_type.rs
Original file line number Diff line number Diff line change
@@ -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<String> = 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<String>,
) {
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);
}
}
}
100 changes: 100 additions & 0 deletions crates/cli/src/analyser/flow_type.rs
Original file line number Diff line number Diff line change
@@ -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 },
));
}
}
}
Loading