use std::collections::BTreeMap;
use blends_domain::ast::{AstEdge, AstNode};
use blends_domain::syntax::{
FileInstanceData, FileStructData, FileStructValue, SyntaxEdge, SyntaxNode,
};
use blends_domain::{Ast, NodeId};
use serde_json::{Map, Value};
trait Attr {
fn attr(&self) -> Option<Value>;
}
impl Attr for NodeId {
fn attr(&self) -> Option<Value> {
Some(Value::from(self.0))
}
}
impl Attr for String {
fn attr(&self) -> Option<Value> {
Some(Value::String(self.clone()))
}
}
impl Attr for Vec<String> {
fn attr(&self) -> Option<Value> {
Some(Value::from(self.clone()))
}
}
impl<T: Attr> Attr for Option<T> {
fn attr(&self) -> Option<Value> {
self.as_ref()?.attr()
}
}
impl Attr for BTreeMap<String, FileStructData> {
fn attr(&self) -> Option<Value> {
Some(struct_children_to_json(self))
}
}
impl Attr for BTreeMap<String, BTreeMap<String, FileInstanceData>> {
fn attr(&self) -> Option<Value> {
Some(instances_to_json(self))
}
}
fn insert_attr(attrs: &mut BTreeMap<String, Value>, key: &str, field: &impl Attr) {
if let Some(value) = field.attr() {
attrs.insert(key.to_owned(), value);
}
}
macro_rules! attrs {
() => { BTreeMap::<String, Value>::new() };
($($key:literal => $field:expr),* $(,)?) => {{
let mut attrs: BTreeMap<String, Value> = BTreeMap::new();
$(insert_attr(&mut attrs, $key, $field);)*
attrs
}};
}
pub fn ast_node_attrs(node: &AstNode) -> BTreeMap<String, Value> {
let mut attrs = BTreeMap::new();
attrs.insert("label_l".to_owned(), Value::from(node.line.to_string()));
attrs.insert("label_c".to_owned(), Value::from(node.col.to_string()));
attrs.insert("label_type".to_owned(), Value::from(node.kind.clone()));
if let Some(text) = &node.text {
attrs.insert("label_text".to_owned(), Value::from(text.clone()));
}
for (name, child) in &node.fields {
attrs.insert(name.clone(), Value::from(child.0));
}
attrs
}
pub fn ast_edge_attrs(edge: AstEdge) -> BTreeMap<String, Value> {
let mut attrs = BTreeMap::new();
let Ast = edge.kind;
attrs.insert("label_ast".to_owned(), Value::from("AST"));
attrs.insert(
"label_index".to_owned(),
Value::from(edge.index.to_string()),
);
attrs
}
pub fn sorted_object(attrs: BTreeMap<String, Value>) -> Value {
let mut map = Map::new();
for (key, value) in attrs {
map.insert(key, value);
}
Value::Object(map)
}
fn file_struct_to_json(data: &FileStructData) -> Value {
let mut attrs = BTreeMap::new();
attrs.insert("node".to_owned(), Value::from(data.node.0));
attrs.insert("type".to_owned(), Value::from(data.kind.clone()));
attrs.insert(
"data".to_owned(),
match &data.data {
FileStructValue::MethodName(name) => Value::from(name.clone()),
FileStructValue::Children(children) => struct_children_to_json(children),
},
);
if let Some(node_range) = &data.node_range {
attrs.insert(
"node_range".to_owned(),
Value::from(node_range.iter().map(|id| id.0).collect::<Vec<_>>()),
);
}
sorted_object(attrs)
}
fn struct_children_to_json(children: &BTreeMap<String, FileStructData>) -> Value {
let mut map = Map::new();
for (name, data) in children {
map.insert(name.clone(), file_struct_to_json(data));
}
Value::Object(map)
}
fn instances_to_json(instances: &BTreeMap<String, BTreeMap<String, FileInstanceData>>) -> Value {
let mut classes = Map::new();
for (class_name, class_instances) in instances {
let mut variables = Map::new();
for (variable_name, data) in class_instances {
let mut fields = BTreeMap::new();
fields.insert("object".to_owned(), Value::from(data.object.clone()));
fields.insert("source".to_owned(), Value::from(data.source.clone()));
fields.insert(
"source_type".to_owned(),
Value::from(data.source_type.clone()),
);
variables.insert(variable_name.clone(), sorted_object(fields));
}
classes.insert(class_name.clone(), Value::Object(variables));
}
Value::Object(classes)
}
#[allow(
clippy::too_many_lines,
reason = "exhaustive per-variant attribute export; grows with each migrated node type"
)]
pub fn syntax_node_attrs(node: &SyntaxNode) -> Option<BTreeMap<String, Value>> {
let mut attrs = match node {
SyntaxNode::Argument
| SyntaxNode::ArgumentList
| SyntaxNode::ArrayInitializer
| SyntaxNode::Break
| SyntaxNode::CatchDeclaration
| SyntaxNode::ClassBody
| SyntaxNode::Continue
| SyntaxNode::Default
| SyntaxNode::DeclarationBlock
| SyntaxNode::ExecutionBlock
| SyntaxNode::ExpressionStatement
| SyntaxNode::File
| SyntaxNode::JsxElement
| SyntaxNode::Modifiers
| SyntaxNode::ParameterList
| SyntaxNode::ParenthesizedExpression
| SyntaxNode::SwitchBody => attrs! {},
SyntaxNode::If {
condition_id,
true_id,
false_id,
initializer,
} => attrs! {
"condition_id" => condition_id,
"true_id" => true_id,
"false_id" => false_id,
"initializer_id" => initializer,
},
SyntaxNode::ForStatement {
block_id,
initializer_id,
condition_id,
update_id,
} => attrs! {
"block_id" => block_id,
"initializer_id" => initializer_id,
"condition_id" => condition_id,
"update_id" => update_id,
},
SyntaxNode::ForEachStatement {
variable_id,
iterable_item_id,
block_id,
} => attrs! {
"variable_id" => variable_id,
"iterable_item_id" => iterable_item_id,
"block_id" => block_id,
},
SyntaxNode::SwitchStatement { block_id, value_id } => attrs! {
"block_id" => block_id,
"value_id" => value_id,
},
SyntaxNode::TernaryOperation {
condition_id,
true_id,
false_id,
} => attrs! {
"condition_id" => condition_id,
"true_id" => true_id,
"false_id" => false_id,
},
SyntaxNode::SwitchSection { case_expression } => attrs! {
"case_expression" => case_expression,
},
SyntaxNode::DoStatement {
block_id,
condition_id,
} => attrs! {
"block_id" => block_id,
"condition_id" => condition_id,
},
SyntaxNode::MethodInvocation {
expression,
object,
symbol_scope,
expression_id,
arguments_id,
object_id,
block_id,
receiver_type_fqn,
} => attrs! {
"expression" => expression,
"object" => object,
"symbol_scope" => symbol_scope,
"expression_id" => expression_id,
"arguments_id" => arguments_id,
"object_id" => object_id,
"block_id" => block_id,
"receiver_type_fqn" => receiver_type_fqn,
},
SyntaxNode::ReservedWord { value } | SyntaxNode::This { value } => attrs! {
"value" => value,
},
SyntaxNode::Attribute { name } => attrs! {
"name" => name,
},
SyntaxNode::Import {
expression,
alias,
method_name,
import_type,
} => attrs! {
"expression" => expression,
"label_alias" => alias,
"method_name" => method_name,
"import_type" => import_type,
},
SyntaxNode::UsingStatement {
block_id,
declaration_id,
} => attrs! {
"block_id" => block_id,
"declaration_id" => declaration_id,
},
SyntaxNode::ObjectCreation {
name,
arguments_id,
initializer_id,
} => attrs! {
"name" => name,
"arguments_id" => arguments_id,
"initializer_id" => initializer_id,
},
SyntaxNode::BinaryOperation {
operator,
left_id,
right_id,
} => attrs! {
"operator" => operator,
"left_id" => left_id,
"right_id" => right_id,
},
SyntaxNode::NamedArgument {
value_id,
argument_name,
} => attrs! {
"value_id" => value_id,
"argument_name" => argument_name,
},
SyntaxNode::UnaryExpression {
operator,
operand_id,
} => attrs! {
"operator" => operator,
"operand_id" => operand_id,
},
SyntaxNode::Assignment {
variable_id,
value_id,
operator,
} => attrs! {
"variable_id" => variable_id,
"value_id" => value_id,
"operator" => operator,
},
SyntaxNode::MemberAccess {
member,
expression,
expression_id,
symbol_scope,
} => attrs! {
"member" => member,
"expression" => expression,
"expression_id" => expression_id,
"symbol_scope" => symbol_scope,
},
SyntaxNode::ElementAccess {
expression_id,
arguments_id,
} => attrs! {
"expression_id" => expression_id,
"arguments_id" => arguments_id,
},
SyntaxNode::AwaitExpression { expression_id } => attrs! {
"expression_id" => expression_id,
},
SyntaxNode::Annotation { name, arguments_id } => attrs! {
"name" => name,
"arguments_id" => arguments_id,
},
SyntaxNode::Return { value_id } => attrs! {
"value_id" => value_id,
},
SyntaxNode::ThrowStatement { expression_id } => attrs! {
"expression_id" => expression_id,
},
SyntaxNode::WhileStatement {
block_id,
condition_id,
} => attrs! {
"block_id" => block_id,
"condition_id" => condition_id,
},
SyntaxNode::ElseClause { block_id } => attrs! {
"block_id" => block_id,
},
SyntaxNode::RestPattern { value_id } | SyntaxNode::SpreadElement { value_id } => attrs! {
"value_id" => value_id,
},
SyntaxNode::TryStatement {
block_id,
resources_id,
} => attrs! {
"block_id" => block_id,
"resources_id" => resources_id,
},
SyntaxNode::CatchClause {
block_id,
catch_declaration,
} => attrs! {
"block_id" => block_id,
"catch_declaration" => catch_declaration,
},
SyntaxNode::FinallyClause { block_id } => attrs! {
"block_id" => block_id,
},
SyntaxNode::Class {
name,
block_id,
modifiers_id,
inherited_class,
access_modifiers,
} => attrs! {
"name" => name,
"block_id" => block_id,
"modifiers_id" => modifiers_id,
"inherited_class" => inherited_class,
"access_modifiers" => access_modifiers,
},
SyntaxNode::Comment { comment } => attrs! {
"comment" => comment,
},
SyntaxNode::Declaration {
name,
block_id,
access_modifiers,
} => attrs! {
"name" => name,
"block_id" => block_id,
"access_modifiers" => access_modifiers,
},
SyntaxNode::Literal { value, value_type } => attrs! {
"value" => value,
"value_type" => value_type,
},
SyntaxNode::Metadata {
path,
structure,
instances,
imports,
package,
} => attrs! {
"path" => path,
"structure" => structure,
"instances" => instances,
"imports" => imports,
"package" => package,
},
SyntaxNode::MethodDeclaration {
name,
access_modifiers,
block_id,
modifiers_id,
parameters_id,
} => attrs! {
"name" => name,
"access_modifiers" => access_modifiers,
"block_id" => block_id,
"modifiers_id" => modifiers_id,
"parameters_id" => parameters_id,
},
SyntaxNode::MissingNode { node_type } => attrs! {
"node_type" => node_type,
},
SyntaxNode::Namespace { name, block_id } => attrs! {
"name" => name,
"block_id" => block_id,
},
SyntaxNode::NewExpression {
constructor_id,
arguments_id,
} => attrs! {
"constructor_id" => constructor_id,
"arguments_id" => arguments_id,
},
SyntaxNode::Object { name, tf_reference } => attrs! {
"name" => name,
"tf_reference" => tf_reference,
},
SyntaxNode::Parameter {
variable,
variable_type,
value_id,
parameter_mode,
} => attrs! {
"variable" => variable,
"variable_type" => variable_type,
"value_id" => value_id,
"parameter_mode" => parameter_mode,
},
SyntaxNode::VariableDeclaration {
variable,
variable_type,
value_id,
variable_id,
access_modifier,
} => attrs! {
"variable" => variable,
"variable_type" => variable_type,
"value_id" => value_id,
"variable_id" => variable_id,
"access_modifier" => access_modifier,
},
SyntaxNode::Pair { key_id, value_id } => attrs! {
"key_id" => key_id,
"value_id" => value_id,
},
SyntaxNode::SymbolLookup {
symbol,
symbol_scope,
value,
} => attrs! {
"symbol" => symbol,
"symbol_scope" => symbol_scope,
"value" => value,
},
SyntaxNode::ModuleImport { expression, alias } => attrs! {
"expression" => expression,
"label_alias" => alias,
},
SyntaxNode::Export {
expression,
declaration_id,
} => attrs! {
"expression" => expression,
"declaration_id" => declaration_id,
},
_ => return None,
};
attrs.insert("label_type".to_owned(), Value::from(node.label_type()));
Some(attrs)
}
pub fn syntax_edge_attrs(edge: SyntaxEdge) -> BTreeMap<String, Value> {
let mut attrs = BTreeMap::new();
if edge.ast.is_some() {
attrs.insert("label_ast".to_owned(), Value::from("AST"));
}
if edge.cfg.is_some() {
attrs.insert("label_cfg".to_owned(), Value::from("CFG"));
}
attrs
}