use crate::types::{
ApiSurface, BehavioralChangeKind, BodyAnalysisResult, BreakingVerdict, Caller, ChangedFunction,
EvidenceType, FunctionSpec, Reference, StructuralChange, Symbol, SymbolKind, TestDiff,
TestFile, Visibility,
};
use anyhow::Result;
use serde::{de::DeserializeOwned, Serialize};
use std::collections::BTreeSet;
use std::fmt::Debug;
use std::path::Path;
pub trait BehaviorAnalyzer {
fn infer_spec(&self, function_body: &str, signature: &str) -> Result<FunctionSpec>;
fn infer_spec_with_test_context(
&self,
function_body: &str,
signature: &str,
test_context: &TestDiff,
) -> Result<FunctionSpec>;
fn specs_are_breaking(&self, old: &FunctionSpec, new: &FunctionSpec)
-> Result<BreakingVerdict>;
fn check_propagation(
&self,
caller_body: &str,
caller_signature: &str,
callee_name: &str,
evidence_description: &str,
) -> Result<bool>;
}
pub trait LanguageSemantics {
fn is_member_addition_breaking(&self, container: &Symbol, member: &Symbol) -> bool;
fn same_family(&self, a: &Symbol, b: &Symbol) -> bool;
fn same_identity(&self, a: &Symbol, b: &Symbol) -> bool;
fn visibility_rank(&self, v: Visibility) -> u8;
fn parse_union_values(&self, _type_str: &str) -> Option<BTreeSet<String>> {
None
}
fn is_async_wrapper(&self, _type_str: &str) -> bool {
false
}
fn format_import_change(&self, symbol: &str, old_path: &str, new_path: &str) -> String {
format!(
"replace import of `{}` from `{}` with `{}`",
symbol, old_path, new_path,
)
}
fn post_process(&self, _changes: &mut Vec<StructuralChange>) {}
fn hierarchy(&self) -> Option<&dyn HierarchySemantics> {
None
}
fn renames(&self) -> Option<&dyn RenameSemantics> {
None
}
fn body_analyzer(&self) -> Option<&dyn BodyAnalysisSemantics> {
None
}
}
pub trait HierarchySemantics {
fn family_source_paths(&self, repo: &Path, git_ref: &str, family_name: &str) -> Vec<String>;
fn family_name_from_symbols(&self, symbols: &[&Symbol]) -> Option<String>;
fn cross_family_relationships(
&self,
repo: &Path,
git_ref: &str,
) -> Vec<(String, String, String)>;
fn related_family_content(
&self,
repo: &Path,
git_ref: &str,
family_name: &str,
relationship_names: &[String],
) -> Option<String>;
fn is_hierarchy_candidate(&self, sym: &Symbol) -> bool;
fn min_components_for_hierarchy(&self) -> usize {
2
}
}
pub trait RenameSemantics {
fn sample_removed_constants<'a>(
&self,
removed: &[&'a str],
_added: &[&'a str],
) -> Vec<&'a str> {
removed.iter().take(30).copied().collect()
}
fn sample_added_constants<'a>(&self, _removed: &[&'a str], added: &[&'a str]) -> Vec<&'a str> {
added.iter().take(30).copied().collect()
}
fn min_removed_for_constant_inference(&self) -> usize {
50
}
fn min_removed_for_interface_inference(&self) -> usize {
2
}
}
pub trait BodyAnalysisSemantics {
fn analyze_changed_body(
&self,
old_body: &str,
new_body: &str,
func_name: &str,
file_path: &str,
) -> Vec<BodyAnalysisResult>;
}
pub trait MessageFormatter {
fn describe(&self, change: &StructuralChange) -> String;
}
pub trait Language: LanguageSemantics + MessageFormatter + Send + Sync + 'static {
type Category: Debug + Clone + Serialize + DeserializeOwned + Eq + std::hash::Hash + Send + Sync;
type ManifestChangeType: Debug
+ Clone
+ Serialize
+ DeserializeOwned
+ Eq
+ PartialEq
+ Send
+ Sync;
type Evidence: Debug + Clone + Serialize + DeserializeOwned + Send + Sync;
type ReportData: Debug + Clone + Serialize + DeserializeOwned + Send + Sync;
const RENAMEABLE_SYMBOL_KINDS: &'static [SymbolKind];
const NAME: &'static str;
const MANIFEST_FILES: &'static [&'static str];
const SOURCE_FILE_PATTERNS: &'static [&'static str];
fn extract(&self, repo: &Path, git_ref: &str) -> Result<ApiSurface>;
fn parse_changed_functions(
&self,
repo: &Path,
from_ref: &str,
to_ref: &str,
) -> Result<Vec<ChangedFunction>>;
fn find_callers(&self, file: &Path, symbol_name: &str) -> Result<Vec<Caller>>;
fn find_references(&self, file: &Path, symbol_name: &str) -> Result<Vec<Reference>>;
fn find_tests(&self, repo: &Path, source_file: &Path) -> Result<Vec<TestFile>>;
fn diff_test_assertions(
&self,
repo: &Path,
test_file: &TestFile,
from_ref: &str,
to_ref: &str,
) -> Result<TestDiff>;
fn diff_manifest_content(old: &str, new: &str) -> Vec<crate::types::ManifestChange<Self>>
where
Self: Sized;
fn should_exclude_from_analysis(path: &Path) -> bool;
fn build_report(
results: &crate::types::AnalysisResult<Self>,
repo: &Path,
from_ref: &str,
to_ref: &str,
) -> crate::types::AnalysisReport<Self>
where
Self: Sized;
fn behavioral_change_kind(&self, _evidence_type: &EvidenceType) -> BehavioralChangeKind {
BehavioralChangeKind::Function
}
fn extract_referenced_symbols(&self, _description: &str) -> Vec<String> {
vec![]
}
fn display_name(&self, qualified_name: &str) -> String {
qualified_name.to_string()
}
}
pub fn diff_surfaces_with_semantics(
old: &ApiSurface,
new: &ApiSurface,
semantics: &dyn LanguageSemantics,
) -> Vec<StructuralChange> {
crate::diff::diff_surfaces_with_semantics(old, new, semantics)
}
pub fn diff_surfaces(old: &ApiSurface, new: &ApiSurface) -> Vec<StructuralChange> {
crate::diff::diff_surfaces(old, new)
}