///|
/// Static-analysis result associated with one named rule definition.
pub(all) struct RuleProgramAnalysis {
name : String
severity : Severity
expression : String
analysis : ProgramAnalysis
} derive(Eq, Debug, ToJson, FromJson)
///|
/// Aggregate static information for a compiled rule set.
pub(all) struct RuleSetAnalysis {
rule_count : Int
total_nodes : Int
maximum_ast_depth : Int
estimated_cost : Int
warning_count : Int
note_count : Int
called_functions : Array[String]
referenced_paths : Array[String]
rules : Array[RuleProgramAnalysis]
} derive(Eq, Debug, ToJson, FromJson)
///|
pub fn RuleSetAnalysis::to_json_string(
self : RuleSetAnalysis,
indent? : Int = 2,
) -> String {
self.to_json().stringify(indent~)
}
///|
/// Look up a named rule's analysis. Rule names are compared exactly.
pub fn RuleSetAnalysis::find_rule(
self : RuleSetAnalysis,
name : String,
) -> RuleProgramAnalysis? {
for rule in self.rules {
if rule.name == name {
return Some(rule)
}
}
None
}
///|
/// Test whether any rule in the set references an exact normalized path.
pub fn RuleSetAnalysis::references_path(
self : RuleSetAnalysis,
path : String,
) -> Bool {
self.referenced_paths.contains(path)
}
///|
/// Test whether any rule in the set calls the named function.
pub fn RuleSetAnalysis::uses_function(
self : RuleSetAnalysis,
name : String,
) -> Bool {
self.called_functions.contains(name)
}
///|
/// Return true when at least one rule has a warning-level finding.
pub fn RuleSetAnalysis::has_warnings(self : RuleSetAnalysis) -> Bool {
self.warning_count > 0
}
///|
fn append_unique_strings(
target : Array[String],
values : Array[String],
) -> Unit {
for value in values {
if !target.contains(value) {
target.push(value)
}
}
}
///|
/// Analyze every compiled rule and combine its data dependencies and calls.
pub fn RuleSet::analyze(self : RuleSet) -> RuleSetAnalysis {
let rules = []
let called_functions = []
let referenced_paths = []
let mut total_nodes = 0
let mut maximum_ast_depth = 0
let mut estimated_cost = 0
let mut warning_count = 0
let mut note_count = 0
for rule in self.rules {
let analysis = analyze(rule.program)
total_nodes = total_nodes + analysis.node_count
estimated_cost = estimated_cost + analysis.estimated_cost
warning_count = warning_count + analysis.warning_count()
note_count = note_count + analysis.note_count()
if analysis.ast_depth > maximum_ast_depth {
maximum_ast_depth = analysis.ast_depth
}
append_unique_strings(called_functions, analysis.called_functions)
append_unique_strings(referenced_paths, analysis.referenced_paths)
rules.push({
name: rule.definition.name,
severity: rule.definition.severity,
expression: rule.definition.expression,
analysis,
})
}
{
rule_count: rules.length(),
total_nodes,
maximum_ast_depth,
estimated_cost,
warning_count,
note_count,
called_functions,
referenced_paths,
rules,
}
}