use convert_case::Boundary;
use convert_case::Case;
use convert_case::Converter;
use wdl_analysis::Diagnostics;
use wdl_analysis::Example;
use wdl_analysis::LabeledSnippet;
use wdl_analysis::VisitReason;
use wdl_analysis::Visitor;
use wdl_ast::AstNode;
use wdl_ast::AstToken;
use wdl_ast::Diagnostic;
use wdl_ast::Span;
use wdl_ast::SyntaxKind;
use wdl_ast::SyntaxNode;
use wdl_ast::v1::StructDefinition;
use crate::Rule;
use crate::Tag;
use crate::TagSet;
const ID: &str = "PascalCase";
fn use_pascal_case(name: &str, properly_cased_name: &str, span: Span) -> Diagnostic {
Diagnostic::warning(format!("struct name `{name}` is not PascalCase"))
.with_rule(ID)
.with_label("this name must be PascalCase", span)
.with_fix(format!("replace `{name}` with `{properly_cased_name}`"))
}
#[derive(Default, Debug, Clone, Copy)]
pub struct PascalCaseRule;
impl Rule for PascalCaseRule {
fn id(&self) -> &'static str {
ID
}
fn description(&self) -> &'static str {
"Ensures that structs are defined with PascalCase names."
}
fn explanation(&self) -> &'static str {
"Struct names should be in PascalCase. Maintaining a consistent naming convention makes \
the code easier to read and understand."
}
fn examples(&self) -> &'static [Example] {
&[Example {
negative: LabeledSnippet {
label: Some("Struct names should be in PascalCase"),
snippet: r#"version 1.2
struct registered_user {
String name
}
"#,
},
revised: Some(LabeledSnippet {
label: None,
snippet: r#"version 1.2
struct RegisteredUser {
String name
}
"#,
}),
}]
}
fn tags(&self) -> TagSet {
TagSet::new(&[Tag::Naming, Tag::Style, Tag::Clarity])
}
fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]> {
Some(&[
SyntaxKind::VersionStatementNode,
SyntaxKind::StructDefinitionNode,
])
}
fn related_rules(&self) -> &'static [&'static str] {
&["SnakeCase"]
}
}
fn check_name(
name: &str,
span: Span,
diagnostics: &mut Diagnostics,
node: &SyntaxNode,
exceptable_nodes: &Option<&'static [SyntaxKind]>,
) {
let converter = Converter::new()
.remove_boundaries(&[Boundary::DigitLower, Boundary::LowerDigit])
.to_case(Case::Pascal);
let properly_cased_name = converter.convert(name);
if name != properly_cased_name {
diagnostics.exceptable_add(
use_pascal_case(name, &properly_cased_name, span),
node,
exceptable_nodes,
);
}
}
impl Visitor for PascalCaseRule {
fn reset(&mut self) {
*self = Self;
}
fn struct_definition(
&mut self,
diagnostics: &mut Diagnostics,
reason: VisitReason,
def: &StructDefinition,
) {
if reason == VisitReason::Exit {
return;
}
let name = def.name();
check_name(
name.text(),
name.span(),
diagnostics,
def.inner(),
&self.exceptable_nodes(),
);
}
}