wdl_lint/rules/
doc_comment_tabs.rs1use 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
18const ID: &str = "DocCommentTabs";
20
21fn 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#[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}