use crate::code_lint::ast::ParsedFile;
use crate::code_lint::ast::rust::collect_associated_items_after_methods;
use crate::code_lint::contract::{CodeRule, RuleTarget};
use crate::diagnostic::{Diagnostic, Language, RuleName, ViolationTemplate, violation_template};
use crate::rule_declaration::{
Classification, Consensus, Declaration, Example, ImpactedQuality, Precision, Reference,
RuleDoc, RuleOptions, Topic,
};
use std::path::Path;
const TEMPLATE: ViolationTemplate = violation_template! {
summary: "Associated item `{name}` of `{class}` is declared after a `fn` item.",
rationale: "Placing associated types or constants after `fn` items hides the type-level parameters and constants that method signatures in `{class}` depend on.",
suggestion: "Move `{name}` to the top of the `{class}` `trait` or `impl` block, above `{anchor}` (starts on line {line}).",
};
pub const RULE: CodeRule = CodeRule {
declaration: Declaration {
name: RuleName("associated-item-after-method"),
template: &TEMPLATE,
languages: &[Language::Rust],
options: RuleOptions::code_rule(()),
classification: Classification {
topics: &[Topic::DECLARATION_ORDER],
precision: Precision::Exact,
consensus: Consensus::Unopinionated,
impacted_quality: ImpactedQuality::Maintainability,
},
doc: RuleDoc {
summary: "Flags Rust associated `type` and `const` items declared after `fn` items in an `impl` or `trait` block.",
what_it_does: indoc::indoc! {r"
Flags an associated `type` or `const` declared after a `fn` in the same Rust `impl`
or `trait` block, such as `const MAX: usize = 8;` below `fn len(&self) -> usize`."},
why_is_this_bad: indoc::indoc! {r"
Associated types (`type Output = ...;`) and associated constants (`const MAX: usize
= ...;`) parameterize the signatures and contracts of the methods in a `trait` or
`impl` block. When they are placed after `fn` items, a reader encounters `Self::Output`
or `Self::MAX` in method signatures before seeing what type or constant those names
refer to.
Declare all associated `type` and `const` items at the top of the `trait` or `impl`
block, before any `fn` items."},
known_problems: Some(indoc::indoc! {r"
Associated items generated by a macro invocation are not seen, so a macro call
placed after a `fn` is never flagged."}),
references: &[Reference {
title: "The Rust Reference: Associated Items",
url: "https://doc.rust-lang.org/reference/items/associated-items.html",
}],
examples: &[Example {
language: Language::Rust,
flagged: indoc::indoc! {r"
pub trait Decoder {
fn decode(&self, input: &str) -> Self::Output;
type Output;
}
"},
flagged_span: "Output",
fixed: indoc::indoc! {r"
pub trait Decoder {
type Output;
fn decode(&self, input: &str) -> Self::Output;
}
"},
}],
},
},
target: RuleTarget::SourceOnly,
check: check_file,
};
fn check_file(rule: &CodeRule, path: &Path, file: &ParsedFile, (): ()) -> Vec<Diagnostic> {
collect_associated_items_after_methods(file)
.into_iter()
.map(|item| {
let line = item.first_method.line.to_string();
rule.diagnostic_at_node(
path,
&item.node,
&[
("name", &item.name),
("class", &item.container_name),
("anchor", &item.first_method.name),
("line", &line),
],
)
})
.collect()
}
#[cfg(test)]
crate::test_utils::rule_test!(
RULE,
{
Rust => {
pass: [
associated_items_before_methods => r#"
pub trait Codec {
type Input;
type Output;
const VERSION: u32 = 1;
fn encode(&self, payload: Self::Input) -> Self::Output;
}
pub struct FrameCodec;
impl Codec for FrameCodec {
type Input = u32;
type Output = u64;
const VERSION: u32 = 2;
fn encode(&self, payload: Self::Input) -> Self::Output {
u64::from(payload)
}
}
impl FrameCodec {
pub const DEFAULT_LIMIT: usize = 16;
pub fn limit(&self) -> usize {
Self::DEFAULT_LIMIT
}
}
"#,
macro_call_after_fn_exempt => r#"
macro_rules! declare_marker {
() => {};
}
pub struct Service;
impl Service {
pub fn run(&self) {}
declare_marker!();
}
"#,
local_items_inside_fn_exempt => r#"
pub struct Service;
impl Service {
pub fn run(&self) -> usize {
type LocalCount = usize;
const STEP: LocalCount = 1;
STEP
}
}
"#,
separate_impl_blocks_checked_independently => r#"
pub struct Buffer;
impl Buffer {
pub fn clear(&self) {}
}
impl Buffer {
pub const CAPACITY: usize = 64;
}
"#,
inline_test_fn_exempt => r#"
pub struct Reader;
impl Reader {
/// Test-only helper method.
#[cfg(test)]
fn test_helper(&self) {}
pub const HEADER_SIZE: usize = 8;
}
"#,
],
fail: [
assoc_type_after_fn_in_trait => r#"
pub trait Stream {
fn next_item(&mut self) -> Option<Self::Item>;
type Item;
}
"# => "Item",
assoc_const_after_fn_in_inherent_impl => r#"
pub struct Worker;
impl Worker {
pub fn max_retries(&self) -> usize {
Self::MAX_RETRIES
}
pub const MAX_RETRIES: usize = 3;
}
"# => "MAX_RETRIES",
assoc_type_after_fn_in_trait_impl => r#"
pub trait Handler {
type Output;
fn handle(&self) -> Self::Output;
}
pub struct Processor;
impl Handler for Processor {
fn handle(&self) -> Self::Output {
1
}
type Output = u32;
}
"# => "Output",
],
},
}
);