Skip to main content

wdl_lint/rules/
doc_comment_tabs.rs

1//! A lint rule for detecting tab characters in doc comments.
2
3use wdl_analysis::Diagnostics;
4use wdl_analysis::Example;
5use wdl_analysis::LabeledSnippet;
6use wdl_analysis::Visitor;
7use wdl_ast::AstToken;
8use wdl_ast::Comment;
9use wdl_ast::CommentKind;
10use wdl_ast::Diagnostic;
11use wdl_ast::Span;
12use wdl_ast::TreeToken;
13
14use crate::Rule;
15use crate::Tag;
16use crate::TagSet;
17
18/// The identifier for the doc comment tabs rule.
19const ID: &str = "DocCommentTabs";
20
21/// Creates a diagnostic for a group of tab characters.
22fn tab_in_doc_comment(span: Span) -> Diagnostic {
23    Diagnostic::warning("tabs in doc comments are not recommended")
24        .with_rule(ID)
25        .with_highlight(span)
26        .with_help("consider replacing tabs with spaces")
27}
28
29/// Detects tab characters inside doc comments.
30#[derive(Default, Debug, Clone, Copy)]
31pub struct DocCommentTabsRule;
32
33impl Rule for DocCommentTabsRule {
34    fn id(&self) -> &'static str {
35        ID
36    }
37
38    fn description(&self) -> &'static str {
39        "Ensures that doc comments do not contain tab characters."
40    }
41
42    fn explanation(&self) -> &'static str {
43        "Tabs render with different widths depending on the viewer. Doc comments should use spaces \
44         instead of tabs to ensure consistent rendering."
45    }
46
47    fn examples(&self) -> &'static [Example] {
48        &[Example {
49            negative: LabeledSnippet {
50                label: None,
51                snippet: r#"version 1.3
52
53# Using tabs for alignment
54
55##  {
56##		"foo": 123,
57##		^^^^^
58##	}
59workflow example {
60    meta {
61        description: 123
62    }
63}"#,
64            },
65            revised: Some(LabeledSnippet {
66                label: None,
67                snippet: r#"version 1.3
68
69# Using spaces for alignment
70
71## {
72##     "foo": 123,
73##     ^^^^^
74## }
75workflow example {
76    meta {
77        description: "123"
78    }
79}"#,
80            }),
81        }]
82    }
83
84    fn tags(&self) -> TagSet {
85        TagSet::new(&[Tag::Style, Tag::Clarity])
86    }
87
88    fn exceptable_nodes(&self) -> Option<&'static [wdl_ast::SyntaxKind]> {
89        None
90    }
91
92    fn related_rules(&self) -> &'static [&'static str] {
93        &[]
94    }
95}
96
97impl Visitor for DocCommentTabsRule {
98    fn reset(&mut self) {
99        *self = Self;
100    }
101
102    fn comment(&mut self, diagnostics: &mut Diagnostics, comment: &Comment) {
103        if comment.kind() != CommentKind::Documentation {
104            return;
105        }
106        let text = comment.text();
107
108        let mut i = 0;
109        let bytes = text.as_bytes();
110
111        while i < bytes.len() {
112            if bytes[i] == b'\t' {
113                let start_offset = i;
114
115                while i < bytes.len() && bytes[i] == b'\t' {
116                    i += 1;
117                }
118
119                let len = i - start_offset;
120
121                let absolute_start = comment.span().start() + start_offset;
122
123                diagnostics.exceptable_add(
124                    tab_in_doc_comment(Span::new(absolute_start, len)),
125                    &TreeToken::parent(comment.inner()),
126                    &self.exceptable_nodes(),
127                );
128            } else {
129                i += 1;
130            }
131        }
132    }
133}