use super::{MakeAst, MakeItem, Transformation};
fn collect_targets_for_analysis(ast: &MakeAst) -> Vec<(&String, &Vec<String>)> {
let mut targets = Vec::new();
for item in &ast.items {
match item {
MakeItem::Target { name, recipe, .. } => {
targets.push((name, recipe));
}
MakeItem::PatternRule {
target_pattern,
recipe,
..
} => {
targets.push((target_pattern, recipe));
}
_ => {}
}
}
targets
}
fn detect_bashisms(targets: &[(&String, &Vec<String>)]) -> Vec<Transformation> {
let mut transformations = Vec::new();
for (target_name, recipes) in targets {
for recipe in *recipes {
if recipe.contains("[[") {
transformations.push(Transformation::DetectBashism {
target_name: (*target_name).clone(),
construct: "[[".to_string(),
posix_alternative: "Use [ instead of [[ for POSIX compliance".to_string(),
safe: false,
});
}
if recipe.contains("$((") {
transformations.push(Transformation::DetectBashism {
target_name: (*target_name).clone(),
construct: "$(())".to_string(),
posix_alternative: "Use expr for POSIX arithmetic".to_string(),
safe: false,
});
}
}
}
transformations
}
fn detect_platform_specific(targets: &[(&String, &Vec<String>)]) -> Vec<Transformation> {
let mut transformations = Vec::new();
for (target_name, recipes) in targets {
for recipe in *recipes {
if recipe.contains("uname") {
transformations.push(Transformation::DetectPlatformSpecific {
target_name: (*target_name).clone(),
command: "uname".to_string(),
reason: "uname is platform-specific; consider using configure scripts"
.to_string(),
safe: false,
});
}
if recipe.contains("/proc/") {
transformations.push(Transformation::DetectPlatformSpecific {
target_name: (*target_name).clone(),
command: "/proc/".to_string(),
reason: "/proc/ is Linux-specific and not portable".to_string(),
safe: false,
});
}
if recipe.contains("ifconfig") {
transformations.push(Transformation::DetectPlatformSpecific {
target_name: (*target_name).clone(),
command: "ifconfig".to_string(),
reason: "ifconfig is deprecated and platform-specific".to_string(),
safe: false,
});
}
}
}
transformations
}
fn detect_shell_specific(targets: &[(&String, &Vec<String>)]) -> Vec<Transformation> {
let mut transformations = Vec::new();
for (target_name, recipes) in targets {
for recipe in *recipes {
if recipe.contains("source ") {
transformations.push(Transformation::DetectShellSpecific {
target_name: (*target_name).clone(),
feature: "source".to_string(),
posix_alternative: "Use . instead of source for POSIX compliance".to_string(),
safe: false,
});
}
if recipe.contains("declare") {
transformations.push(Transformation::DetectShellSpecific {
target_name: (*target_name).clone(),
feature: "declare".to_string(),
posix_alternative: "Use regular variable assignment for POSIX compliance"
.to_string(),
safe: false,
});
}
}
}
transformations
}
fn detect_nonportable_flags(targets: &[(&String, &Vec<String>)]) -> Vec<Transformation> {
let mut transformations = Vec::new();
for (target_name, recipes) in targets {
for recipe in *recipes {
if recipe.contains("--preserve") {
transformations.push(Transformation::DetectNonPortableFlags {
target_name: (*target_name).clone(),
command: "cp".to_string(),
flag: "--preserve".to_string(),
reason: "--preserve is a GNU extension; use -p for portability".to_string(),
safe: false,
});
}
if recipe.contains("--color") {
transformations.push(Transformation::DetectNonPortableFlags {
target_name: (*target_name).clone(),
command: "ls/grep".to_string(),
flag: "--color".to_string(),
reason: "--color is a GNU extension; not portable".to_string(),
safe: false,
});
}
}
}
transformations
}
fn detect_nonportable_echo(targets: &[(&String, &Vec<String>)]) -> Vec<Transformation> {
let mut transformations = Vec::new();
for (target_name, recipes) in targets {
for recipe in *recipes {
if recipe.contains("echo -e") {
transformations.push(Transformation::DetectNonPortableEcho {
target_name: (*target_name).clone(),
command: "echo -e".to_string(),
safe: false,
});
}
if recipe.contains("echo -n") {
transformations.push(Transformation::DetectNonPortableEcho {
target_name: (*target_name).clone(),
command: "echo -n".to_string(),
safe: false,
});
}
}
}
transformations
}
fn detect_sed_inplace(targets: &[(&String, &Vec<String>)]) -> Vec<Transformation> {
let mut transformations = Vec::new();
for (target_name, recipes) in targets {
for recipe in *recipes {
if recipe.contains("sed -i") {
transformations.push(Transformation::DetectNonPortableFlags {
target_name: (*target_name).clone(),
command: "sed".to_string(),
flag: "-i".to_string(),
reason: "sed -i is a GNU extension; use temp file for portability".to_string(),
safe: false,
});
}
}
}
transformations
}
pub(super) fn analyze_portability(ast: &MakeAst) -> Vec<Transformation> {
let targets = collect_targets_for_analysis(ast);
let mut transformations = Vec::new();
transformations.extend(detect_bashisms(&targets));
transformations.extend(detect_platform_specific(&targets));
transformations.extend(detect_shell_specific(&targets));
transformations.extend(detect_nonportable_flags(&targets));
transformations.extend(detect_nonportable_echo(&targets));
transformations.extend(detect_sed_inplace(&targets));
transformations
}