Skip to main content

luaur_analysis/functions/
lint_comments.rs

1use crate::functions::emit_warning::emit_warning;
2use crate::functions::fuzzy_match::fuzzy_match;
3use crate::records::lint_context::LintContext;
4use core::ffi::c_char;
5use luaur_ast::records::hot_comment::HotComment;
6use luaur_config::enums::code::Code;
7use luaur_config::records::lint_warning::LintWarning;
8
9pub fn lint_comments(context: &mut LintContext, hotcomments: &[HotComment]) {
10    let mut seen_mode = false;
11
12    for hc in hotcomments {
13        // We reserve --!<space> for various informational (non-directive) comments
14        if hc.content.is_empty()
15            || hc.content.as_bytes().first() == Some(&b' ')
16            || hc.content.as_bytes().first() == Some(&b'\t')
17        {
18            continue;
19        }
20
21        if !hc.header {
22            emit_warning(
23                context,
24                Code::Code_CommentDirective,
25                hc.location,
26                format_args!("Comment directive is ignored because it is placed after the first non-comment token")
27            );
28        } else {
29            let space_pos = hc.content.find(|c| c == ' ' || c == '\t');
30
31            let first = if let Some(pos) = space_pos {
32                &hc.content[..pos]
33            } else {
34                &hc.content[..]
35            };
36
37            if first == "nolint" {
38                let notspace_pos = if let Some(pos) = space_pos {
39                    hc.content[pos..]
40                        .find(|c| c != ' ' && c != '\t')
41                        .map(|p| pos + p)
42                } else {
43                    None
44                };
45
46                if space_pos.is_none() || notspace_pos.is_none() {
47                    // disables all lints
48                } else if LintWarning::parse_name(&hc.content[notspace_pos.unwrap()..])
49                    == Code::Code_Unknown
50                {
51                    let rule = &hc.content[notspace_pos.unwrap()..];
52
53                    // skip Unknown
54                    let suggestion_ptr =
55                        fuzzy_match(rule, K_WARNING_NAMES.as_ptr(), K_WARNING_NAMES.len());
56                    if !suggestion_ptr.is_null() {
57                        let suggestion =
58                            unsafe { core::ffi::CStr::from_ptr(suggestion_ptr).to_string_lossy() };
59                        emit_warning(
60                            context,
61                            Code::Code_CommentDirective,
62                            hc.location,
63                            format_args!("nolint directive refers to unknown lint rule '{}'; did you mean '{}'?", rule, suggestion)
64                        );
65                    } else {
66                        emit_warning(
67                            context,
68                            Code::Code_CommentDirective,
69                            hc.location,
70                            format_args!("nolint directive refers to unknown lint rule '{}'", rule),
71                        );
72                    }
73                }
74            } else if first == "nocheck" || first == "nonstrict" || first == "strict" {
75                if space_pos.is_some() {
76                    emit_warning(
77                        context,
78                        Code::Code_CommentDirective,
79                        hc.location,
80                        format_args!("Comment directive with the type checking mode has extra symbols at the end of the line")
81                    );
82                } else if seen_mode {
83                    emit_warning(
84                        context,
85                        Code::Code_CommentDirective,
86                        hc.location,
87                        format_args!(
88                            "Comment directive with the type checking mode has already been used"
89                        ),
90                    );
91                } else {
92                    seen_mode = true;
93                }
94            } else if first == "optimize" {
95                let notspace_pos = if let Some(pos) = space_pos {
96                    hc.content[pos..]
97                        .find(|c| c != ' ' && c != '\t')
98                        .map(|p| pos + p)
99                } else {
100                    None
101                };
102
103                if space_pos.is_none() || notspace_pos.is_none() {
104                    emit_warning(
105                        context,
106                        Code::Code_CommentDirective,
107                        hc.location,
108                        format_args!("optimize directive requires an optimization level"),
109                    );
110                } else {
111                    let level = &hc.content[notspace_pos.unwrap()..];
112
113                    if level != "0" && level != "1" && level != "2" {
114                        emit_warning(
115                            context,
116                            Code::Code_CommentDirective,
117                            hc.location,
118                            format_args!("optimize directive uses unknown optimization level '{}', 0..2 expected", level)
119                        );
120                    }
121                }
122            } else if first == "native" {
123                if space_pos.is_some() {
124                    emit_warning(
125                        context,
126                        Code::Code_CommentDirective,
127                        hc.location,
128                        format_args!("native directive has extra symbols at the end of the line"),
129                    );
130                }
131            } else {
132                const K_HOT_COMMENTS: [*const c_char; 6] = [
133                    c"nolint".as_ptr(),
134                    c"nocheck".as_ptr(),
135                    c"nonstrict".as_ptr(),
136                    c"strict".as_ptr(),
137                    c"optimize".as_ptr(),
138                    c"native".as_ptr(),
139                ];
140
141                let suggestion_ptr =
142                    fuzzy_match(first, K_HOT_COMMENTS.as_ptr(), K_HOT_COMMENTS.len());
143                if !suggestion_ptr.is_null() {
144                    let suggestion =
145                        unsafe { core::ffi::CStr::from_ptr(suggestion_ptr).to_string_lossy() };
146                    emit_warning(
147                        context,
148                        Code::Code_CommentDirective,
149                        hc.location,
150                        format_args!(
151                            "Unknown comment directive '{}'; did you mean '{}'?",
152                            first, suggestion
153                        ),
154                    );
155                } else {
156                    emit_warning(
157                        context,
158                        Code::Code_CommentDirective,
159                        hc.location,
160                        format_args!("Unknown comment directive '{}'", first),
161                    );
162                }
163            }
164        }
165    }
166}
167
168// kWarningNames array from LinterConfig.h, offset by 1 (skip "Unknown"),
169// matching C++ `kWarningNames + 1` with size `Code__Count - 1`.
170const K_WARNING_NAMES: [*const c_char; 29] = [
171    c"UnknownGlobal".as_ptr(),
172    c"DeprecatedGlobal".as_ptr(),
173    c"GlobalUsedAsLocal".as_ptr(),
174    c"LocalShadow".as_ptr(),
175    c"SameLineStatement".as_ptr(),
176    c"MultiLineStatement".as_ptr(),
177    c"LocalUnused".as_ptr(),
178    c"FunctionUnused".as_ptr(),
179    c"ImportUnused".as_ptr(),
180    c"BuiltinGlobalWrite".as_ptr(),
181    c"PlaceholderRead".as_ptr(),
182    c"UnreachableCode".as_ptr(),
183    c"UnknownType".as_ptr(),
184    c"ForRange".as_ptr(),
185    c"UnbalancedAssignment".as_ptr(),
186    c"ImplicitReturn".as_ptr(),
187    c"DuplicateLocal".as_ptr(),
188    c"FormatString".as_ptr(),
189    c"TableLiteral".as_ptr(),
190    c"UninitializedLocal".as_ptr(),
191    c"DuplicateFunction".as_ptr(),
192    c"DeprecatedApi".as_ptr(),
193    c"TableOperations".as_ptr(),
194    c"DuplicateCondition".as_ptr(),
195    c"MisleadingAndOr".as_ptr(),
196    c"CommentDirective".as_ptr(),
197    c"IntegerParsing".as_ptr(),
198    c"ComparisonPrecedence".as_ptr(),
199    c"RedundantNativeAttribute".as_ptr(),
200];