Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add tag linter. #304

Merged
merged 9 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
37 changes: 35 additions & 2 deletions cli/src/commands/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,18 +182,36 @@ pub fn exec_check(
linters::rule_name(re)?.error(config.rule_name.error));
}

// Prefer allowed list over the regex, as it is more explicit.
if !config.tags.allowed.is_empty() {
compiler.add_linter(
linters::Tags::from_list(config.tags.allowed.clone())
.error(config.tags.error));
} else if let Some(re) = config
.tags
.regexp
.as_ref()
.filter(|re| !re.is_empty()) {
compiler.add_linter(
linters::Tags::from_regex(re)?.error(config.tags.error)
);
}

compiler.colorize_errors(io::stdout().is_tty());

match compiler.add_source(src) {
Ok(compiler) => {
if compiler.warnings().is_empty() {
if compiler.warnings().is_empty() &&
compiler.linter_errors().is_empty() {
state.files_passed.fetch_add(1, Ordering::Relaxed);
lines.push(format!(
"[ {} ] {}",
"PASS".paint(Green).bold(),
file_path.display()
));
} else {
}

if !compiler.warnings().is_empty() {
state.warnings.fetch_add(
compiler.warnings().len(),
Ordering::Relaxed,
Expand All @@ -207,6 +225,21 @@ pub fn exec_check(
eprintln!("{}", warning);
}
}

if !compiler.linter_errors().is_empty() {
state.errors.fetch_add(
compiler.linter_errors().len(),
Ordering::Relaxed,
);
lines.push(format!(
"[ {} ] {}",
"ERROR".paint(Red).bold(),
file_path.display()
));
for err in compiler.linter_errors() {
eprintln!("{}", err);
}
}
}
Err(err) => {
state.errors.fetch_add(1, Ordering::Relaxed);
Expand Down
27 changes: 22 additions & 5 deletions cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,30 +69,47 @@ pub enum MetaValueType {
Hash,
}

/// Format specific configuration information.
/// Linter specific configuration information.
#[derive(Deserialize, Serialize, Debug, Default)]
#[serde(deny_unknown_fields)]
pub struct CheckConfig {
/// Meta specific formatting information.
/// Meta specific linting information.
// Note: Using a BTreeMap here because we want a consistent ordering when
// we iterate over it, so that warnings always appear in the same order.
pub metadata: BTreeMap<String, MetadataConfig>,
/// Rule name formatting information.
/// Rule name linting information.
pub rule_name: RuleNameConfig,
/// Tag linting information.
pub tags: TagConfig,
}

/// Format specific configuration information.
/// Allowed tag names in the linter.
#[derive(Deserialize, Serialize, Debug, Default)]
#[serde(deny_unknown_fields)]
pub struct TagConfig {
/// List of allowed tags.
pub allowed: Vec<String>,
/// Regexp that must match all tags.
pub regexp: Option<String>,
/// If `true`, an incorrect tag name will raise an error instead of a
/// warning.
#[serde(default)]
pub error: bool,
}

/// Rule name linter specific configuration information.
#[derive(Deserialize, Serialize, Debug, Default)]
#[serde(deny_unknown_fields)]
pub struct RuleNameConfig {
/// Regexp used to validate the rule name.
pub regexp: Option<String>,
/// If `true`, an incorrect rule anme will raise an error instead of a
/// If `true`, an incorrect rule name will raise an error instead of a
/// warning.
#[serde(default)]
pub error: bool,
}

/// Metadata linter specific configuration information.
#[derive(Deserialize, Serialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct MetadataConfig {
Expand Down
70 changes: 67 additions & 3 deletions lib/src/compiler/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ pub enum CompileError {
InvalidRegexp(Box<InvalidRegexp>),
InvalidRegexpModifier(Box<InvalidRegexpModifier>),
InvalidRuleName(Box<InvalidRuleName>),
InvalidTag(Box<InvalidTag>),
InvalidUTF8(Box<InvalidUTF8>),
MethodNotAllowedInWith(Box<MethodNotAllowedInWith>),
MismatchingTypes(Box<MismatchingTypes>),
Expand All @@ -84,6 +85,7 @@ pub enum CompileError {
UnknownIdentifier(Box<UnknownIdentifier>),
UnknownModule(Box<UnknownModule>),
UnknownPattern(Box<UnknownPattern>),
UnknownTag(Box<UnknownTag>),
UnusedPattern(Box<UnusedPattern>),
WrongArguments(Box<WrongArguments>),
WrongType(Box<WrongType>),
Expand Down Expand Up @@ -685,7 +687,7 @@ pub struct InvalidMetadata {
/// ## Example
///
/// ```text
/// warning[missing_metadata]: required metadata is missing
/// error[E038]: required metadata is missing
/// --> test.yar:12:6
/// |
/// 12 | rule pants {
Expand Down Expand Up @@ -716,7 +718,7 @@ pub struct MissingMetadata {
/// ## Example
///
/// ```text
/// warning[invalid_rule_name]: rule name does not match regex `APT_.*`
/// error[E039]: rule name does not match regex `APT_.*`
/// --> test.yar:13:6
/// |
/// 13 | rule pants {
Expand All @@ -726,7 +728,7 @@ pub struct MissingMetadata {
#[derive(ErrorStruct, Clone, Debug, PartialEq, Eq)]
#[associated_enum(CompileError)]
#[error(
code = "E038",
code = "E039",
title = "rule name does not match regex `{regex}`"
)]
#[label(
Expand All @@ -739,6 +741,68 @@ pub struct InvalidRuleName {
regex: String,
}

/// Unknown tag. This is only used if the compiler is configured to check
/// for required tags (see: [`crate::linters::Tags`]).
///
/// ## Example
///
/// ```text
/// error[E040]: Tag not in allowed list
/// --> rules/test.yara:1:10
/// |
/// 1 | rule a : foo {
/// | ^^^ tag `foo` not in allowed list
/// |
/// = note: Allowed tags: test, bar
/// ```
#[derive(ErrorStruct, Clone, Debug, PartialEq, Eq)]
#[associated_enum(CompileError)]
#[error(
code = "E040",
title = "Tag not in allowed list"
)]
#[label(
"tag `{name}` not in allowed list",
tag_loc
)]
#[footer(note)]
pub struct UnknownTag {
report: Report,
tag_loc: CodeLoc,
name: String,
note: Option<String>,
}

/// Tag does not match regex. This is only used if the compiler is configured to
/// check for it (see: [`crate::linters::Tags`]).
///
/// ## Example
///
/// ```text
/// error[E041]: Tag does not match regex `bar`
/// --> rules/test.yara:1:10
/// |
/// 1 | rule a : foo {
/// | ^^^ tag `foo` does not match regex `bar`
/// |
/// ```
#[derive(ErrorStruct, Clone, Debug, PartialEq, Eq)]
#[associated_enum(CompileError)]
#[error(
code = "E041",
title = "Tag does not match regex `{regex}`"
)]
#[label(
"tag `{name}` does not match regex `{regex}`",
tag_loc
)]
pub struct InvalidTag {
report: Report,
tag_loc: CodeLoc,
name: String,
regex: String,
}

/// A custom error has occurred.
#[derive(ErrorStruct, Clone, Debug, PartialEq, Eq)]
#[associated_enum(CompileError)]
Expand Down
Loading
Loading