use super::makefile::MakefileItem;
use crate::lossless::{remove_with_preceding_comments, VariableDefinition};
use crate::SyntaxKind::*;
use rowan::ast::AstNode;
use rowan::{GreenNodeBuilder, SyntaxNode};
fn rebuild_node(builder: &mut GreenNodeBuilder, node: &crate::lossless::SyntaxNode) {
builder.start_node(node.kind().into());
for child in node.children_with_tokens() {
match child {
rowan::NodeOrToken::Token(token) => {
builder.token(token.kind().into(), token.text());
}
rowan::NodeOrToken::Node(child_node) => {
rebuild_node(builder, &child_node);
}
}
}
builder.finish_node();
}
impl VariableDefinition {
pub fn name(&self) -> Option<String> {
self.syntax().children_with_tokens().find_map(|it| {
it.as_token().and_then(|it| {
if it.kind() == IDENTIFIER && it.text() != "export" {
Some(it.text().to_string())
} else {
None
}
})
})
}
pub fn is_export(&self) -> bool {
self.syntax()
.children_with_tokens()
.any(|it| it.as_token().is_some_and(|token| token.text() == "export"))
}
pub fn assignment_operator(&self) -> Option<String> {
self.syntax().children_with_tokens().find_map(|it| {
it.as_token().and_then(|token| {
if token.kind() == OPERATOR {
Some(token.text().to_string())
} else {
None
}
})
})
}
pub fn raw_value(&self) -> Option<String> {
self.syntax()
.children()
.find(|it| it.kind() == EXPR)
.map(|it| it.text().into())
}
pub fn parent(&self) -> Option<MakefileItem> {
self.syntax().parent().and_then(MakefileItem::cast)
}
pub fn remove(&mut self) {
if let Some(parent) = self.syntax().parent() {
remove_with_preceding_comments(self.syntax(), &parent);
}
}
pub fn set_assignment_operator(&mut self, op: &str) {
let mut builder = GreenNodeBuilder::new();
builder.start_node(VARIABLE.into());
for child in self.syntax().children_with_tokens() {
match child {
rowan::NodeOrToken::Token(token) if token.kind() == OPERATOR => {
builder.token(OPERATOR.into(), op);
}
rowan::NodeOrToken::Token(token) => {
builder.token(token.kind().into(), token.text());
}
rowan::NodeOrToken::Node(node) => {
rebuild_node(&mut builder, &node);
}
}
}
builder.finish_node();
let new_variable = SyntaxNode::new_root_mut(builder.finish());
let index = self.syntax().index();
if let Some(parent) = self.syntax().parent() {
parent.splice_children(index..index + 1, vec![new_variable.clone().into()]);
*self = VariableDefinition::cast(
parent
.children_with_tokens()
.nth(index)
.and_then(|it| it.into_node())
.unwrap(),
)
.unwrap();
}
}
pub fn set_value(&mut self, new_value: &str) {
let expr_index = self
.syntax()
.children()
.find(|it| it.kind() == EXPR)
.map(|it| it.index());
if let Some(expr_idx) = expr_index {
let mut builder = GreenNodeBuilder::new();
builder.start_node(EXPR.into());
builder.token(IDENTIFIER.into(), new_value);
builder.finish_node();
let new_expr = SyntaxNode::new_root_mut(builder.finish());
self.syntax()
.splice_children(expr_idx..expr_idx + 1, vec![new_expr.into()]);
}
}
}
#[cfg(test)]
mod tests {
use crate::lossless::Makefile;
#[test]
fn test_variable_parent() {
let makefile: Makefile = "VAR = value\n".parse().unwrap();
let var = makefile.variable_definitions().next().unwrap();
let parent = var.parent();
assert!(parent.is_none());
}
#[test]
fn test_assignment_operator_simple() {
let makefile: Makefile = "VAR = value\n".parse().unwrap();
let var = makefile.variable_definitions().next().unwrap();
assert_eq!(var.assignment_operator(), Some("=".to_string()));
}
#[test]
fn test_assignment_operator_recursive() {
let makefile: Makefile = "VAR := value\n".parse().unwrap();
let var = makefile.variable_definitions().next().unwrap();
assert_eq!(var.assignment_operator(), Some(":=".to_string()));
}
#[test]
fn test_assignment_operator_conditional() {
let makefile: Makefile = "VAR ?= value\n".parse().unwrap();
let var = makefile.variable_definitions().next().unwrap();
assert_eq!(var.assignment_operator(), Some("?=".to_string()));
}
#[test]
fn test_assignment_operator_append() {
let makefile: Makefile = "VAR += value\n".parse().unwrap();
let var = makefile.variable_definitions().next().unwrap();
assert_eq!(var.assignment_operator(), Some("+=".to_string()));
}
#[test]
fn test_assignment_operator_export() {
let makefile: Makefile = "export VAR := value\n".parse().unwrap();
let var = makefile.variable_definitions().next().unwrap();
assert_eq!(var.assignment_operator(), Some(":=".to_string()));
}
#[test]
fn test_set_assignment_operator_simple_to_conditional() {
let makefile: Makefile = "VAR = value\n".parse().unwrap();
let mut var = makefile.variable_definitions().next().unwrap();
var.set_assignment_operator("?=");
assert_eq!(var.assignment_operator(), Some("?=".to_string()));
assert_eq!(makefile.code(), "VAR ?= value\n");
}
#[test]
fn test_set_assignment_operator_recursive_to_conditional() {
let makefile: Makefile = "VAR := value\n".parse().unwrap();
let mut var = makefile.variable_definitions().next().unwrap();
var.set_assignment_operator("?=");
assert_eq!(var.assignment_operator(), Some("?=".to_string()));
assert_eq!(makefile.code(), "VAR ?= value\n");
}
#[test]
fn test_set_assignment_operator_preserves_export() {
let makefile: Makefile = "export VAR := value\n".parse().unwrap();
let mut var = makefile.variable_definitions().next().unwrap();
var.set_assignment_operator("?=");
assert_eq!(var.assignment_operator(), Some("?=".to_string()));
assert!(var.is_export());
assert_eq!(makefile.code(), "export VAR ?= value\n");
}
#[test]
fn test_set_assignment_operator_preserves_whitespace() {
let makefile: Makefile = "VAR := value\n".parse().unwrap();
let mut var = makefile.variable_definitions().next().unwrap();
var.set_assignment_operator("?=");
assert_eq!(var.assignment_operator(), Some("?=".to_string()));
assert_eq!(makefile.code(), "VAR ?= value\n");
}
#[test]
fn test_set_assignment_operator_preserves_value() {
let makefile: Makefile = "VAR := old_value\n".parse().unwrap();
let mut var = makefile.variable_definitions().next().unwrap();
var.set_assignment_operator("=");
assert_eq!(var.assignment_operator(), Some("=".to_string()));
assert_eq!(var.raw_value(), Some("old_value".to_string()));
assert_eq!(makefile.code(), "VAR = old_value\n");
}
#[test]
fn test_set_assignment_operator_to_triple_colon() {
let makefile: Makefile = "VAR := value\n".parse().unwrap();
let mut var = makefile.variable_definitions().next().unwrap();
var.set_assignment_operator("::=");
assert_eq!(var.assignment_operator(), Some("::=".to_string()));
assert_eq!(makefile.code(), "VAR ::= value\n");
}
#[test]
fn test_combined_operations() {
let makefile: Makefile = "export VAR := old_value\n".parse().unwrap();
let mut var = makefile.variable_definitions().next().unwrap();
var.set_assignment_operator("?=");
assert_eq!(var.assignment_operator(), Some("?=".to_string()));
var.set_value("new_value");
assert_eq!(var.raw_value(), Some("new_value".to_string()));
assert!(var.is_export());
assert_eq!(var.name(), Some("VAR".to_string()));
assert_eq!(makefile.code(), "export VAR ?= new_value\n");
}
#[test]
fn test_set_assignment_operator_preserves_shell_call() {
let makefile: Makefile = "DEB_HOST_ARCH := $(shell dpkg-architecture -qDEB_HOST_ARCH)\n"
.parse()
.unwrap();
let mut var = makefile.variable_definitions().next().unwrap();
var.set_assignment_operator("?=");
assert_eq!(var.assignment_operator(), Some("?=".to_string()));
assert_eq!(
makefile.code(),
"DEB_HOST_ARCH ?= $(shell dpkg-architecture -qDEB_HOST_ARCH)\n"
);
}
}