use crate::action::{Action, ActionIssue, IssueLevel, Linter};
use crate::error::Result as ModelResult;
use crate::model::shapes::{HasTraits, ShapeKind};
use crate::model::values::Value;
use crate::model::{HasIdentity, Identifier, Model, ShapeID};
use heck::{CamelCase, MixedCase};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::str::FromStr;
linter_or_validator_defn! {
NamingConventions,
r#"This will report any violations of the naming
conventions described in the Smithy [style guide](https://awslabs.github.io/smithy/1.0/guides/style-guide.html?highlight=naming#naming).
* `Shape` names should be in UpperCamelCase.
* `Member` names and `Trait` names should be in lowerCamelCase."# }
linter_or_validator_defn! {
UnwelcomeTerms
{
checked: RefCell<HashSet<Identifier>>
},
"This will report any use of any unwelcome, or problematic terms in names."
}
pub fn run_linter_actions(
linters: &mut [Box<dyn Linter>],
model: &Model,
fail_fast: bool,
) -> ModelResult<Vec<ActionIssue>> {
let mut issues: Vec<ActionIssue> = Default::default();
for linter in linters.iter_mut() {
linter.check(model)?;
let new_issues = linter.issues_mut();
issues.append(new_issues);
if fail_fast
&& new_issues
.iter()
.any(|issue| issue.level > IssueLevel::Warning)
{
break;
}
}
Ok(issues)
}
linter_or_validator_default_impl! { NamingConventions }
linter_or_validator_action_impl! { NamingConventions, "NamingConventions" }
impl Linter for NamingConventions {
fn check(&mut self, model: &Model) -> ModelResult<()> {
for shape in model.shapes() {
if shape.has_trait(&ShapeID::from_str("smithy.api#trait").unwrap()) {
self.check_trait_name(shape.id());
} else {
self.check_shape_name(shape.id(), false);
}
self.check_applied_trait_names(shape.traits());
match shape.body() {
ShapeKind::Structure(body) | ShapeKind::Union(body) => {
for member in body.members() {
self.check_member_name(member.id(), shape.id());
self.check_shape_name(member.target(), true);
self.check_applied_trait_names(member.traits());
}
}
_ => {}
}
}
Ok(())
}
}
impl NamingConventions {
fn check_shape_name(&mut self, id: &ShapeID, reference: bool) {
let shape_name = id.shape_name().to_string();
if shape_name.to_camel_case() != shape_name {
self.issues.push(ActionIssue::info_at(
&self.label(),
&format!(
"{} shape names should conform to UpperCamelCase, i.e. {}",
if reference {
"References to"
} else {
"Defined"
},
shape_name.to_camel_case()
),
id.clone(),
));
}
}
fn check_trait_name(&mut self, id: &ShapeID) {
let shape_name = id.shape_name().to_string();
if shape_name.to_mixed_case() != shape_name {
self.issues.push(ActionIssue::info_at(
&self.label(),
&format!(
"Trait names should conform to lowerCamelCase, i.e. {}",
shape_name.to_mixed_case()
),
id.clone(),
));
}
}
fn check_member_name(&mut self, id: &Identifier, parent: &ShapeID) {
let shape_name = id.to_string();
if shape_name.to_mixed_case() != shape_name {
self.issues.push(ActionIssue::info_at(
&self.label(),
&format!(
"Member names should conform to lowerCamelCase, i.e. {}",
shape_name.to_mixed_case()
),
parent.clone(),
));
}
}
fn check_applied_trait_names(&mut self, ids: &HashMap<ShapeID, Option<Value>>) {
for id in ids.keys() {
self.check_trait_name(id);
}
}
}
linter_or_validator_default_impl! { UnwelcomeTerms { checked: RefCell::new(Default::default()) } }
linter_or_validator_action_impl! { UnwelcomeTerms, "UnwelcomeTerms" }
impl Linter for UnwelcomeTerms {
fn check(&mut self, model: &Model) -> ModelResult<()> {
for shape in model.shapes() {
let shape_id = shape.id();
self.check_shape_id(shape_id);
for id in shape.traits().keys() {
self.check_shape_id(id);
}
match shape.body() {
ShapeKind::Structure(body) | ShapeKind::Union(body) => {
self.check_shape_id(shape.id());
for member in body.members() {
self.check_identifier(member.id(), Some(shape.id()));
self.check_shape_id(member.target());
for id in member.traits().keys() {
self.check_shape_id(id);
}
}
}
_ => {}
}
}
Ok(())
}
}
impl UnwelcomeTerms {
fn check_shape_id(&mut self, shape_id: &ShapeID) {
for id in shape_id.namespace().split() {
self.check_identifier(&id, Some(shape_id));
}
self.check_identifier(&shape_id.shape_name(), Some(shape_id));
if let Some(member_name) = shape_id.member_name() {
self.check_identifier(member_name, Some(shape_id));
}
}
#[inline]
fn check_identifier(&mut self, id: &Identifier, in_shape: Option<&ShapeID>) {
if !self.checked.borrow().contains(id) {
let _ = self.checked.borrow_mut().insert(id.clone());
for word in terms::split_words(&id.to_string()) {
if terms::is_unwelcome_term(&word) {
self.issues.push(match in_shape {
None => ActionIssue::warning(
&self.label(),
&format!(
"The term '{}' is considered either insensitive, divisive, or otherwise unwelcome",
word
)),
Some(in_shape) => ActionIssue::warning_at(
&self.label(),
&format!(
"The term '{}' is considered either insensitive, divisive, or otherwise unwelcome",
word
), in_shape.clone())
})
}
}
}
}
}
mod terms;