archunit/metrics/extraction/model.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
use std::path::{Path, PathBuf};
/// A Rust type declaration category.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum TypeKind {
/// A `struct` declaration.
Struct,
/// An `enum` declaration.
Enum,
/// A `union` declaration.
Union,
/// A `trait` declaration.
Trait,
}
/// One associated function whose signature has a `self` receiver.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct MethodInfo {
pub(crate) name: String,
pub(crate) accessed_fields: Vec<String>,
pub(crate) has_body: bool,
}
impl MethodInfo {
/// Returns the declared method name.
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
/// Returns the sorted field names referenced as `self.field` in the method body.
#[must_use]
pub fn accessed_fields(&self) -> &[String] {
&self.accessed_fields
}
/// Returns whether the declaration has a body.
#[must_use]
pub const fn has_body(&self) -> bool {
self.has_body
}
}
/// One declared data field and the methods that syntactically access it.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct FieldInfo {
pub(crate) name: String,
pub(crate) accessed_by: Vec<String>,
}
impl FieldInfo {
/// Returns the field name. Tuple fields use their zero-based index.
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
/// Returns sorted method names containing a `self.<field>` access.
#[must_use]
pub fn accessed_by(&self) -> &[String] {
&self.accessed_by
}
}
/// One inherent or trait implementation block.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ImplInfo {
pub(crate) target_type: String,
pub(crate) trait_name: Option<String>,
pub(crate) file_path: String,
pub(crate) methods: Vec<MethodInfo>,
pub(crate) associated_functions: Vec<String>,
}
impl ImplInfo {
/// Returns the syntactic implementation target path.
#[must_use]
pub fn target_type(&self) -> &str {
&self.target_type
}
/// Returns the implemented trait path, or `None` for an inherent impl.
#[must_use]
pub fn trait_name(&self) -> Option<&str> {
self.trait_name.as_deref()
}
/// Returns the normalized source-file identifier.
#[must_use]
pub fn file_path(&self) -> &str {
&self.file_path
}
/// Returns functions in this impl that have a `self` receiver.
#[must_use]
pub fn methods(&self) -> &[MethodInfo] {
&self.methods
}
/// Returns receiver-free associated function names.
#[must_use]
pub fn associated_functions(&self) -> &[String] {
&self.associated_functions
}
/// Returns whether this is a trait implementation.
#[must_use]
pub const fn is_trait_impl(&self) -> bool {
self.trait_name.is_some()
}
}
/// Metrics information for one Rust type declaration.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct TypeInfo {
pub(crate) name: String,
pub(crate) file_path: String,
pub(crate) kind: TypeKind,
pub(crate) methods: Vec<MethodInfo>,
pub(crate) inherent_methods: Vec<MethodInfo>,
pub(crate) fields: Vec<FieldInfo>,
pub(crate) associated_functions: Vec<String>,
}
impl TypeInfo {
/// Returns the module-qualified name available from source syntax.
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
/// Returns the unqualified declared name.
#[must_use]
pub fn simple_name(&self) -> &str {
self.name.rsplit("::").next().unwrap_or(self.name.as_str())
}
/// Returns the normalized source-file identifier.
#[must_use]
pub fn file_path(&self) -> &str {
&self.file_path
}
/// Returns the declaration category.
#[must_use]
pub const fn kind(&self) -> TypeKind {
self.kind
}
/// Returns functions with a `self` receiver declared by or associated with this type.
#[must_use]
pub fn methods(&self) -> &[MethodInfo] {
&self.methods
}
/// Returns only methods declared in unambiguously associated inherent impl blocks.
///
/// Trait declarations and trait-impl methods remain visible through [`Self::methods`] but do
/// not participate in this collection.
#[must_use]
pub fn inherent_methods(&self) -> &[MethodInfo] {
&self.inherent_methods
}
/// Returns declared data fields.
#[must_use]
pub fn fields(&self) -> &[FieldInfo] {
&self.fields
}
/// Returns receiver-free associated function names.
#[must_use]
pub fn associated_functions(&self) -> &[String] {
&self.associated_functions
}
}
/// Extracted counts and type information for one Rust source file.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct FileMetricsInfo {
pub(crate) path: String,
pub(crate) lines_of_code: usize,
pub(crate) statements: usize,
pub(crate) imports: usize,
pub(crate) concrete_types: usize,
pub(crate) functions: usize,
pub(crate) traits: usize,
pub(crate) impl_blocks: usize,
pub(crate) macros: usize,
pub(crate) associated_functions: usize,
pub(crate) types: Vec<TypeInfo>,
pub(crate) impls: Vec<ImplInfo>,
}
impl FileMetricsInfo {
/// Returns the normalized source-file identifier.
#[must_use]
pub fn path(&self) -> &str {
&self.path
}
/// Returns physical lines containing non-comment source text.
#[must_use]
pub const fn lines_of_code(&self) -> usize {
self.lines_of_code
}
/// Returns syntax-tree items plus executable block statements.
#[must_use]
pub const fn statements(&self) -> usize {
self.statements
}
/// Returns `use` and `extern crate` item count.
#[must_use]
pub const fn imports(&self) -> usize {
self.imports
}
/// Returns struct, enum, and union declaration count.
#[must_use]
pub const fn concrete_types(&self) -> usize {
self.concrete_types
}
/// Returns free-function count.
#[must_use]
pub const fn functions(&self) -> usize {
self.functions
}
/// Returns trait declaration count.
#[must_use]
pub const fn traits(&self) -> usize {
self.traits
}
/// Returns inherent and trait impl-block count.
#[must_use]
pub const fn impl_blocks(&self) -> usize {
self.impl_blocks
}
/// Returns macro invocation count, including macro definitions represented by `syn`.
#[must_use]
pub const fn macros(&self) -> usize {
self.macros
}
/// Returns receiver-free functions declared in traits and impl blocks.
#[must_use]
pub const fn associated_functions(&self) -> usize {
self.associated_functions
}
/// Returns type declarations in the file.
#[must_use]
pub fn types(&self) -> &[TypeInfo] {
&self.types
}
/// Returns impl blocks in the file.
#[must_use]
pub fn impls(&self) -> &[ImplInfo] {
&self.impls
}
pub(crate) fn retain_types(&mut self, predicate: impl Fn(&TypeInfo) -> bool) {
self.types.retain(predicate);
self.concrete_types = self
.types
.iter()
.filter(|type_info| type_info.kind != TypeKind::Trait)
.count();
self.traits = self
.types
.iter()
.filter(|type_info| type_info.kind == TypeKind::Trait)
.count();
let names = self
.types
.iter()
.map(|type_info| type_info.name.as_str())
.collect::<Vec<_>>();
self.impls.retain(|impl_info| {
names.iter().any(|name| {
**name == impl_info.target_type
|| name.rsplit("::").next() == impl_info.target_type.rsplit("::").next()
})
});
}
}
/// A deterministic metrics snapshot for one Cargo project selection.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ProjectMetricsInfo {
pub(crate) root: PathBuf,
pub(crate) files: Vec<FileMetricsInfo>,
pub(crate) types: Vec<TypeInfo>,
}
impl ProjectMetricsInfo {
/// Returns the Cargo workspace root.
#[must_use]
pub fn root(&self) -> &Path {
&self.root
}
/// Returns selected source files sorted by identifier.
#[must_use]
pub fn files(&self) -> &[FileMetricsInfo] {
&self.files
}
/// Returns selected types sorted by file and qualified name.
#[must_use]
pub fn types(&self) -> &[TypeInfo] {
&self.types
}
pub(crate) fn from_files(root: PathBuf, mut files: Vec<FileMetricsInfo>) -> Self {
files.sort_by(|left, right| left.path.cmp(&right.path));
let mut types = files
.iter()
.flat_map(|file| file.types.iter().cloned())
.collect::<Vec<_>>();
types.sort_by(|left, right| {
left.file_path
.cmp(&right.file_path)
.then_with(|| left.name.cmp(&right.name))
});
Self { root, files, types }
}
}