pub mod action_metadata;
pub mod ast;
pub mod availability;
pub mod config;
pub mod cron;
pub mod expr;
pub mod parse;
pub mod reusable_workflow;
pub mod rules;
#[cfg(test)]
mod tests;
pub mod yaml;
use std::fmt;
use std::path::PathBuf;
pub use config::Config;
use action_metadata::LocalActionsCache;
use reusable_workflow::LocalReusableWorkflowCache;
use rules::Rule;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LintError {
pub message: String,
pub line: u32,
pub column: u32,
pub kind: &'static str,
}
impl fmt::Display for LintError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}:{}: {} [{}]",
self.line, self.column, self.message, self.kind
)
}
}
pub fn sort_and_dedup(errors: &mut Vec<LintError>) {
errors.sort_by(|a, b| {
a.line
.cmp(&b.line)
.then(a.column.cmp(&b.column))
.then(a.message.cmp(&b.message))
});
errors.dedup_by(|a, b| a.line == b.line && a.column == b.column && a.message == b.message);
}
pub struct Linter {
config: Option<Config>,
actions: LocalActionsCache,
workflows: LocalReusableWorkflowCache,
}
impl Linter {
pub fn new(root: Option<PathBuf>, cwd: PathBuf, config: Option<Config>) -> Self {
Self {
config,
actions: LocalActionsCache::new(root.clone()),
workflows: LocalReusableWorkflowCache::new(root, cwd),
}
}
pub fn lint(&self, path: &str, source: &[u8]) -> Vec<LintError> {
let (workflow, mut all) = parse::parse(source);
if let Some(w) = workflow {
let config = self.config.as_ref();
let mut rules: Vec<Box<dyn Rule + '_>> = vec![
Box::new(rules::matrix::RuleMatrix::new()),
Box::new(rules::credentials::RuleCredentials::new()),
Box::new(rules::shell_name::RuleShellName::new()),
Box::new(rules::runner_label::RuleRunnerLabel::new(config)),
Box::new(rules::events::RuleEvents::new()),
Box::new(rules::job_needs::RuleJobNeeds::new()),
Box::new(rules::action::RuleAction::new(&self.actions)),
Box::new(rules::env_var::RuleEnvVar::new()),
Box::new(rules::id::RuleId::new()),
Box::new(rules::glob::RuleGlob::new()),
Box::new(rules::permissions::RulePermissions::new()),
Box::new(rules::workflow_call::RuleWorkflowCall::new(
path,
&self.workflows,
)),
Box::new(rules::expression::RuleExpression::new(
&self.actions,
&self.workflows,
config,
)),
Box::new(rules::deprecated_commands::RuleDeprecatedCommands::new()),
Box::new(rules::if_cond::RuleIfCond::new()),
];
all.extend(rules::visit(&w, &mut rules));
}
if let Some(config) = &self.config {
all = config.filter_errors(path, all);
}
sort_and_dedup(&mut all);
all
}
}