use dotenv_core::{LineEntry, is_escaped};
use super::Check;
use crate::{LintKind, Warning};
pub(crate) struct SubstitutionKeyChecker<'a> {
template: &'a str,
}
impl SubstitutionKeyChecker<'_> {
fn message(&self, key: &str) -> String {
self.template.replace("{}", key)
}
}
impl Default for SubstitutionKeyChecker<'_> {
fn default() -> Self {
Self {
template: "The {} key is not assigned properly",
}
}
}
impl Check for SubstitutionKeyChecker<'_> {
fn run(&mut self, line: &LineEntry) -> Option<Warning> {
let mut value = match line.get_value().map(str::trim) {
Some(value) if !value.starts_with('\'') => value,
_ => return None,
};
while let Some(index) = value.find('$') {
let prefix = &value[..index];
let raw_key = &value[index + 1..];
let (initial_key, rest) = raw_key
.find('$')
.map(|i| raw_key.split_at(i))
.unwrap_or_else(|| (raw_key, ""));
let end_brace_index = initial_key.find('}');
let has_start_brace = initial_key.starts_with('{');
let has_end_brace = end_brace_index.is_some();
let is_incorrect_substitution = has_start_brace ^ has_end_brace
|| end_brace_index
.map(|i| &initial_key[1..i])
.filter(|key| key.contains(|c: char| !c.is_ascii_alphanumeric() && c != '_'))
.is_some();
if is_incorrect_substitution && !is_escaped(prefix) {
return Some(Warning::new(
line.number,
self.name(),
self.message(line.get_key()?),
));
}
value = rest;
}
None
}
fn name(&self) -> LintKind {
LintKind::SubstitutionKey
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::check_test;
#[test]
fn correct_substitution_key_test() {
check_test(
&mut SubstitutionKeyChecker::default(),
[
("ABC=$BAR", None),
("FOO=${BAR}", None),
("FOO=\"$BAR\"", None),
],
);
}
#[test]
fn incorrect_substitution_key_test() {
check_test(
&mut SubstitutionKeyChecker::default(),
[
("ABC=${BAR", Some("The ABC key is not assigned properly")),
("FOO=${BAR!}", Some("The FOO key is not assigned properly")),
("XYZ=$BAR}", Some("The XYZ key is not assigned properly")),
],
);
}
#[test]
fn multiple_substitution_key_test() {
check_test(
&mut SubstitutionKeyChecker::default(),
[
("ABC=${BAR}$XYZ", None),
("FOO=$ABC{${BAR}", None),
("BIZ=$FOO-$ABC", None),
],
);
}
#[test]
fn incorrect_multiple_substitution_key_test() {
check_test(
&mut SubstitutionKeyChecker::default(),
[
(
"ABC=${BAR$XYZ}",
Some("The ABC key is not assigned properly"),
),
(
"FOO=${ABC-$BAR}",
Some("The FOO key is not assigned properly"),
),
(
"XYZ=${FOO${BAR}",
Some("The XYZ key is not assigned properly"),
),
],
);
}
#[test]
fn escaped_incorrect_substitution_key_test() {
check_test(
&mut SubstitutionKeyChecker::default(),
[
("ABC=\\${BAR", None),
("FOO=\\$BAR}", None),
("FOO=\"\\${BAR\"", None),
("FOO=\"\\$BAR}", None),
],
);
}
}