@frozenproductions/niteo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/Cargo.lock +458 -0
  2. package/Cargo.toml +13 -0
  3. package/LICENSE +21 -0
  4. package/README.md +119 -0
  5. package/bin/niteo.js +51 -0
  6. package/package.json +27 -0
  7. package/scripts/build-binary.js +23 -0
  8. package/src/app.rs +132 -0
  9. package/src/cli.rs +43 -0
  10. package/src/config.rs +989 -0
  11. package/src/discovery.rs +42 -0
  12. package/src/git.rs +63 -0
  13. package/src/main.rs +13 -0
  14. package/src/report.rs +490 -0
  15. package/src/rules/max_directory_depth.rs +125 -0
  16. package/src/rules/max_file_exports.rs +478 -0
  17. package/src/rules/max_items_per_directory.rs +145 -0
  18. package/src/rules/min_items_per_directory.rs +146 -0
  19. package/src/rules/no_barrel_files.rs +284 -0
  20. package/src/rules/no_comments.rs +222 -0
  21. package/src/rules/no_console.rs +242 -0
  22. package/src/rules/no_debugger.rs +209 -0
  23. package/src/rules/no_default_export.rs +241 -0
  24. package/src/rules/no_duplicate_file_names.rs +196 -0
  25. package/src/rules/no_empty_directories.rs +467 -0
  26. package/src/rules/no_empty_interface.rs +280 -0
  27. package/src/rules/no_enums.rs +208 -0
  28. package/src/rules/no_eval.rs +268 -0
  29. package/src/rules/no_export_star.rs +252 -0
  30. package/src/rules/no_inline_types.rs +367 -0
  31. package/src/rules/no_interface.rs +570 -0
  32. package/src/rules/no_large_file.rs +98 -0
  33. package/src/rules/no_logic_in_barrel.rs +346 -0
  34. package/src/rules/no_logic_in_domain.rs +987 -0
  35. package/src/rules/no_mutable_exports.rs +253 -0
  36. package/src/rules/no_upward_import.rs +427 -0
  37. package/src/rules/prefer_satisfies.rs +319 -0
  38. package/src/rules.rs +247 -0
@@ -0,0 +1,222 @@
1
+ use std::path::Path;
2
+
3
+ use crate::config::{CommentsRuleConfig, Severity};
4
+ use crate::rules::Violation;
5
+
6
+ const RULE_NAME: &str = "no-comments";
7
+ const MESSAGE: &str = "Remove implementation comments or convert them to allowed documentation.";
8
+
9
+ pub fn check_file(file: &Path, source: &str, config: &CommentsRuleConfig) -> Vec<Violation> {
10
+ let bytes = source.as_bytes();
11
+ let mut violations = Vec::new();
12
+ let mut cursor = Cursor::default();
13
+ let mut string_quote: Option<u8> = None;
14
+
15
+ while cursor.index < bytes.len() {
16
+ let current = bytes[cursor.index];
17
+ let next = bytes.get(cursor.index + 1).copied();
18
+
19
+ if let Some(quote) = string_quote {
20
+ if current == b'\\' {
21
+ cursor.advance(bytes);
22
+ if cursor.index < bytes.len() {
23
+ cursor.advance(bytes);
24
+ }
25
+ continue;
26
+ }
27
+
28
+ if current == quote {
29
+ string_quote = None;
30
+ }
31
+
32
+ cursor.advance(bytes);
33
+ continue;
34
+ }
35
+
36
+ match (current, next) {
37
+ (b'\'', _) | (b'"', _) | (b'`', _) => {
38
+ string_quote = Some(current);
39
+ cursor.advance(bytes);
40
+ }
41
+ (b'/', Some(b'/')) => {
42
+ if should_report_line_comment(bytes, cursor.index, config) {
43
+ violations.push(comment_violation(file, &cursor, config.severity));
44
+ }
45
+ skip_line_comment(bytes, &mut cursor);
46
+ }
47
+ (b'/', Some(b'*')) => {
48
+ if should_report_block_comment(bytes, cursor.index, config) {
49
+ violations.push(comment_violation(file, &cursor, config.severity));
50
+ }
51
+ skip_block_comment(bytes, &mut cursor);
52
+ }
53
+ _ => {
54
+ cursor.advance(bytes);
55
+ }
56
+ }
57
+ }
58
+
59
+ violations
60
+ }
61
+
62
+ #[derive(Debug, Clone, Copy)]
63
+ struct Cursor {
64
+ index: usize,
65
+ line: usize,
66
+ column: usize,
67
+ }
68
+
69
+ impl Default for Cursor {
70
+ fn default() -> Self {
71
+ Self {
72
+ index: 0,
73
+ line: 1,
74
+ column: 1,
75
+ }
76
+ }
77
+ }
78
+
79
+ impl Cursor {
80
+ fn advance(&mut self, bytes: &[u8]) {
81
+ if bytes[self.index] == b'\n' {
82
+ self.line += 1;
83
+ self.column = 1;
84
+ } else {
85
+ self.column += 1;
86
+ }
87
+
88
+ self.index += 1;
89
+ }
90
+ }
91
+
92
+ fn comment_violation(file: &Path, cursor: &Cursor, severity: Severity) -> Violation {
93
+ Violation {
94
+ file: file.to_path_buf(),
95
+ line: Some(cursor.line),
96
+ column: Some(cursor.column),
97
+ rule: RULE_NAME,
98
+ message: MESSAGE,
99
+ severity,
100
+ detail: None,
101
+ subject: None,
102
+ }
103
+ }
104
+
105
+ fn should_report_line_comment(bytes: &[u8], index: usize, config: &CommentsRuleConfig) -> bool {
106
+ !config.allow_doc_comments || !is_doc_line_comment(bytes, index)
107
+ }
108
+
109
+ fn should_report_block_comment(bytes: &[u8], index: usize, config: &CommentsRuleConfig) -> bool {
110
+ !config.allow_doc_comments || !is_doc_block_comment(bytes, index)
111
+ }
112
+
113
+ fn is_doc_line_comment(bytes: &[u8], index: usize) -> bool {
114
+ bytes.get(index + 2) == Some(&b'/')
115
+ }
116
+
117
+ fn is_doc_block_comment(bytes: &[u8], index: usize) -> bool {
118
+ bytes.get(index + 2) == Some(&b'*')
119
+ }
120
+
121
+ fn skip_line_comment(bytes: &[u8], cursor: &mut Cursor) {
122
+ while cursor.index < bytes.len() && bytes[cursor.index] != b'\n' {
123
+ cursor.advance(bytes);
124
+ }
125
+ }
126
+
127
+ fn skip_block_comment(bytes: &[u8], cursor: &mut Cursor) {
128
+ cursor.advance(bytes);
129
+ cursor.advance(bytes);
130
+
131
+ while cursor.index < bytes.len() {
132
+ let current = bytes[cursor.index];
133
+ let next = bytes.get(cursor.index + 1).copied();
134
+
135
+ cursor.advance(bytes);
136
+
137
+ if current == b'*' && next == Some(b'/') {
138
+ cursor.advance(bytes);
139
+ break;
140
+ }
141
+ }
142
+ }
143
+
144
+ #[cfg(test)]
145
+ mod tests {
146
+ use super::check_file;
147
+ use crate::config::{CommentsRuleConfig, Severity};
148
+ use std::path::Path;
149
+
150
+ #[test]
151
+ fn finds_line_comments() {
152
+ let violations = check_file(
153
+ Path::new("example.ts"),
154
+ "const value = 1 // no\n",
155
+ &test_config(),
156
+ );
157
+
158
+ assert_eq!(violations.len(), 1);
159
+ assert_eq!(violations[0].line, Some(1));
160
+ assert_eq!(violations[0].column, Some(17));
161
+ }
162
+
163
+ #[test]
164
+ fn finds_block_comments() {
165
+ let violations = check_file(
166
+ Path::new("example.ts"),
167
+ "const value = /* no */ 1\n",
168
+ &test_config(),
169
+ );
170
+
171
+ assert_eq!(violations.len(), 1);
172
+ assert_eq!(violations[0].line, Some(1));
173
+ assert_eq!(violations[0].column, Some(15));
174
+ }
175
+
176
+ #[test]
177
+ fn finds_tsx_comments() {
178
+ let source = "export function View() {\n return <div>{/* no */}</div>\n}\n";
179
+ let violations = check_file(Path::new("example.tsx"), source, &test_config());
180
+
181
+ assert_eq!(violations.len(), 1);
182
+ assert_eq!(violations[0].line, Some(2));
183
+ assert_eq!(violations[0].column, Some(16));
184
+ }
185
+
186
+ #[test]
187
+ fn ignores_comment_markers_inside_strings() {
188
+ let source = r#"const url = "https://example.com"
189
+ const block = "/* not a comment */"
190
+ "#;
191
+ let violations = check_file(Path::new("example.ts"), source, &test_config());
192
+
193
+ assert!(violations.is_empty());
194
+ }
195
+
196
+ #[test]
197
+ fn ignores_doc_comments_when_allowed() {
198
+ let source = "/// User model\n/** Component docs */\nconst value = 1\n";
199
+ let violations = check_file(Path::new("example.ts"), source, &test_config());
200
+
201
+ assert!(violations.is_empty());
202
+ }
203
+
204
+ #[test]
205
+ fn reports_doc_comments_when_disallowed() {
206
+ let config = CommentsRuleConfig {
207
+ severity: Severity::Warn,
208
+ allow_doc_comments: false,
209
+ };
210
+ let source = "/// User model\n/** Component docs */\nconst value = 1\n";
211
+ let violations = check_file(Path::new("example.ts"), source, &config);
212
+
213
+ assert_eq!(violations.len(), 2);
214
+ }
215
+
216
+ fn test_config() -> CommentsRuleConfig {
217
+ CommentsRuleConfig {
218
+ severity: Severity::Warn,
219
+ allow_doc_comments: true,
220
+ }
221
+ }
222
+ }
@@ -0,0 +1,242 @@
1
+ use std::path::Path;
2
+
3
+ use crate::config::NoConsoleRuleConfig;
4
+ use crate::rules::Violation;
5
+
6
+ const RULE_NAME: &str = "no-console";
7
+ const MESSAGE: &str = "Disallow console statements outside allowed file patterns.";
8
+
9
+ pub fn check_file(file: &Path, source: &str, config: &NoConsoleRuleConfig) -> Vec<Violation> {
10
+ let bytes = source.as_bytes();
11
+ let mut violations = Vec::new();
12
+ let mut cursor = Cursor::default();
13
+ let mut string_quote: Option<u8> = None;
14
+
15
+ while cursor.index < bytes.len() {
16
+ let current = bytes[cursor.index];
17
+ let next = bytes.get(cursor.index + 1).copied();
18
+
19
+ if let Some(quote) = string_quote {
20
+ if current == b'\\' {
21
+ cursor.advance(bytes);
22
+ if cursor.index < bytes.len() {
23
+ cursor.advance(bytes);
24
+ }
25
+ continue;
26
+ }
27
+
28
+ if current == quote {
29
+ string_quote = None;
30
+ }
31
+
32
+ cursor.advance(bytes);
33
+ continue;
34
+ }
35
+
36
+ match (current, next) {
37
+ (b'\'', _) | (b'"', _) | (b'`', _) => {
38
+ string_quote = Some(current);
39
+ cursor.advance(bytes);
40
+ }
41
+ (b'/', Some(b'/')) => skip_line_comment(bytes, &mut cursor),
42
+ (b'/', Some(b'*')) => skip_block_comment(bytes, &mut cursor),
43
+ _ if starts_console_call(bytes, cursor.index) => {
44
+ if !is_file_allowed(file, config) {
45
+ violations.push(console_violation(file, &cursor, config.severity));
46
+ }
47
+ advance_past_console(bytes, &mut cursor);
48
+ }
49
+ _ => cursor.advance(bytes),
50
+ }
51
+ }
52
+
53
+ violations
54
+ }
55
+
56
+ #[derive(Debug, Clone, Copy)]
57
+ struct Cursor {
58
+ index: usize,
59
+ line: usize,
60
+ column: usize,
61
+ }
62
+
63
+ impl Default for Cursor {
64
+ fn default() -> Self {
65
+ Self {
66
+ index: 0,
67
+ line: 1,
68
+ column: 1,
69
+ }
70
+ }
71
+ }
72
+
73
+ impl Cursor {
74
+ fn advance(&mut self, bytes: &[u8]) {
75
+ if bytes[self.index] == b'\n' {
76
+ self.line += 1;
77
+ self.column = 1;
78
+ } else {
79
+ self.column += 1;
80
+ }
81
+
82
+ self.index += 1;
83
+ }
84
+ }
85
+
86
+ fn console_violation(file: &Path, cursor: &Cursor, severity: crate::config::Severity) -> Violation {
87
+ Violation {
88
+ file: file.to_path_buf(),
89
+ line: Some(cursor.line),
90
+ column: Some(cursor.column),
91
+ rule: RULE_NAME,
92
+ message: MESSAGE,
93
+ severity,
94
+ detail: None,
95
+ subject: None,
96
+ }
97
+ }
98
+
99
+ fn starts_console_call(bytes: &[u8], index: usize) -> bool {
100
+ if !starts_keyword(bytes, index, b"console") {
101
+ return false;
102
+ }
103
+
104
+ let after_console = index + b"console".len();
105
+ let next = bytes.get(after_console);
106
+
107
+ matches!(next, Some(b'.' | b'['))
108
+ }
109
+
110
+ fn starts_keyword(bytes: &[u8], index: usize, keyword: &[u8]) -> bool {
111
+ bytes.get(index..index + keyword.len()) == Some(keyword)
112
+ && !is_identifier_byte(bytes.get(index.wrapping_sub(1)).copied())
113
+ && !is_identifier_byte(bytes.get(index + keyword.len()).copied())
114
+ }
115
+
116
+ fn is_identifier_byte(byte: Option<u8>) -> bool {
117
+ matches!(
118
+ byte,
119
+ Some(b'a'..=b'z') | Some(b'A'..=b'Z') | Some(b'0'..=b'9') | Some(b'_') | Some(b'$')
120
+ )
121
+ }
122
+
123
+ fn advance_past_console(bytes: &[u8], cursor: &mut Cursor) {
124
+ let target_index = cursor.index + b"console".len();
125
+ while cursor.index < bytes.len() && cursor.index < target_index {
126
+ cursor.advance(bytes);
127
+ }
128
+ }
129
+
130
+ fn skip_line_comment(bytes: &[u8], cursor: &mut Cursor) {
131
+ while cursor.index < bytes.len() && bytes[cursor.index] != b'\n' {
132
+ cursor.advance(bytes);
133
+ }
134
+ }
135
+
136
+ fn skip_block_comment(bytes: &[u8], cursor: &mut Cursor) {
137
+ cursor.advance(bytes);
138
+ cursor.advance(bytes);
139
+
140
+ while cursor.index < bytes.len() {
141
+ let current = bytes[cursor.index];
142
+ let next = bytes.get(cursor.index + 1).copied();
143
+
144
+ cursor.advance(bytes);
145
+
146
+ if current == b'*' && next == Some(b'/') {
147
+ cursor.advance(bytes);
148
+ break;
149
+ }
150
+ }
151
+ }
152
+
153
+ fn is_file_allowed(file: &Path, config: &NoConsoleRuleConfig) -> bool {
154
+ let file_str = file.to_string_lossy();
155
+ for pattern in &config.allow_patterns {
156
+ if file_str.contains(pattern) {
157
+ return true;
158
+ }
159
+ }
160
+ false
161
+ }
162
+
163
+ #[cfg(test)]
164
+ mod tests {
165
+ use super::check_file;
166
+ use crate::config::{NoConsoleRuleConfig, Severity};
167
+ use std::path::Path;
168
+
169
+ #[test]
170
+ fn reports_console_log() {
171
+ let violations = check_file(
172
+ Path::new("Component.tsx"),
173
+ "console.log('hello');\n",
174
+ &test_config(),
175
+ );
176
+
177
+ assert_eq!(violations.len(), 1);
178
+ assert_eq!(violations[0].line, Some(1));
179
+ assert_eq!(violations[0].column, Some(1));
180
+ }
181
+
182
+ #[test]
183
+ fn reports_console_warn() {
184
+ let violations = check_file(
185
+ Path::new("Component.tsx"),
186
+ "console.warn('warning');\n",
187
+ &test_config(),
188
+ );
189
+
190
+ assert_eq!(violations.len(), 1);
191
+ }
192
+
193
+ #[test]
194
+ fn reports_console_error() {
195
+ let violations = check_file(
196
+ Path::new("Component.tsx"),
197
+ "console.error('error');\n",
198
+ &test_config(),
199
+ );
200
+
201
+ assert_eq!(violations.len(), 1);
202
+ }
203
+
204
+ #[test]
205
+ fn allows_console_in_service_files() {
206
+ let config = NoConsoleRuleConfig {
207
+ severity: Severity::Warn,
208
+ allow_patterns: vec![".service.ts".to_string()],
209
+ };
210
+
211
+ let violations = check_file(
212
+ Path::new("api.service.ts"),
213
+ "console.log('hello');\n",
214
+ &config,
215
+ );
216
+
217
+ assert!(violations.is_empty());
218
+ }
219
+
220
+ #[test]
221
+ fn ignores_console_in_comments() {
222
+ let source = "// console.log('hello');\n/* console.warn('test'); */\n";
223
+ let violations = check_file(Path::new("Component.tsx"), source, &test_config());
224
+
225
+ assert!(violations.is_empty());
226
+ }
227
+
228
+ #[test]
229
+ fn ignores_console_in_strings() {
230
+ let source = r#"const text = "console.log('hello')";"#;
231
+ let violations = check_file(Path::new("Component.tsx"), source, &test_config());
232
+
233
+ assert!(violations.is_empty());
234
+ }
235
+
236
+ fn test_config() -> NoConsoleRuleConfig {
237
+ NoConsoleRuleConfig {
238
+ severity: Severity::Warn,
239
+ allow_patterns: vec![],
240
+ }
241
+ }
242
+ }
@@ -0,0 +1,209 @@
1
+ use std::path::Path;
2
+
3
+ use crate::config::{RuleConfig, Severity};
4
+ use crate::rules::Violation;
5
+
6
+ const RULE_NAME: &str = "no-debugger";
7
+ const MESSAGE: &str = "Remove debugger statements before committing code.";
8
+
9
+ pub fn check_file(file: &Path, source: &str, config: &RuleConfig) -> Vec<Violation> {
10
+ let bytes = source.as_bytes();
11
+ let mut violations = Vec::new();
12
+ let mut cursor = Cursor::default();
13
+ let mut string_quote: Option<u8> = None;
14
+
15
+ while cursor.index < bytes.len() {
16
+ let current = bytes[cursor.index];
17
+ let next = bytes.get(cursor.index + 1).copied();
18
+
19
+ if let Some(quote) = string_quote {
20
+ if current == b'\\' {
21
+ cursor.advance(bytes);
22
+ if cursor.index < bytes.len() {
23
+ cursor.advance(bytes);
24
+ }
25
+ continue;
26
+ }
27
+
28
+ if current == quote {
29
+ string_quote = None;
30
+ }
31
+
32
+ cursor.advance(bytes);
33
+ continue;
34
+ }
35
+
36
+ match (current, next) {
37
+ (b'\'', _) | (b'"', _) | (b'`', _) => {
38
+ string_quote = Some(current);
39
+ cursor.advance(bytes);
40
+ }
41
+ (b'/', Some(b'/')) => skip_line_comment(bytes, &mut cursor),
42
+ (b'/', Some(b'*')) => skip_block_comment(bytes, &mut cursor),
43
+ _ if starts_debugger(bytes, cursor.index) => {
44
+ violations.push(debugger_violation(file, &cursor, config.severity));
45
+ advance_past_debugger(bytes, &mut cursor);
46
+ }
47
+ _ => cursor.advance(bytes),
48
+ }
49
+ }
50
+
51
+ violations
52
+ }
53
+
54
+ #[derive(Debug, Clone, Copy)]
55
+ struct Cursor {
56
+ index: usize,
57
+ line: usize,
58
+ column: usize,
59
+ }
60
+
61
+ impl Default for Cursor {
62
+ fn default() -> Self {
63
+ Self {
64
+ index: 0,
65
+ line: 1,
66
+ column: 1,
67
+ }
68
+ }
69
+ }
70
+
71
+ impl Cursor {
72
+ fn advance(&mut self, bytes: &[u8]) {
73
+ if bytes[self.index] == b'\n' {
74
+ self.line += 1;
75
+ self.column = 1;
76
+ } else {
77
+ self.column += 1;
78
+ }
79
+
80
+ self.index += 1;
81
+ }
82
+ }
83
+
84
+ fn debugger_violation(file: &Path, cursor: &Cursor, severity: Severity) -> Violation {
85
+ Violation {
86
+ file: file.to_path_buf(),
87
+ line: Some(cursor.line),
88
+ column: Some(cursor.column),
89
+ rule: RULE_NAME,
90
+ message: MESSAGE,
91
+ severity,
92
+ detail: None,
93
+ subject: None,
94
+ }
95
+ }
96
+
97
+ fn starts_debugger(bytes: &[u8], index: usize) -> bool {
98
+ if !starts_keyword(bytes, index, b"debugger") {
99
+ return false;
100
+ }
101
+
102
+ let after_debugger = index + b"debugger".len();
103
+ let next = bytes.get(after_debugger);
104
+
105
+ matches!(next, Some(b';' | b'\n' | b'\r' | b' ' | b'\t'))
106
+ }
107
+
108
+ fn starts_keyword(bytes: &[u8], index: usize, keyword: &[u8]) -> bool {
109
+ bytes.get(index..index + keyword.len()) == Some(keyword)
110
+ && !is_identifier_byte(bytes.get(index.wrapping_sub(1)).copied())
111
+ && !is_identifier_byte(bytes.get(index + keyword.len()).copied())
112
+ }
113
+
114
+ fn is_identifier_byte(byte: Option<u8>) -> bool {
115
+ matches!(
116
+ byte,
117
+ Some(b'a'..=b'z') | Some(b'A'..=b'Z') | Some(b'0'..=b'9') | Some(b'_') | Some(b'$')
118
+ )
119
+ }
120
+
121
+ fn advance_past_debugger(bytes: &[u8], cursor: &mut Cursor) {
122
+ let target_index = cursor.index + b"debugger".len();
123
+ while cursor.index < bytes.len() && cursor.index < target_index {
124
+ cursor.advance(bytes);
125
+ }
126
+ }
127
+
128
+ fn skip_line_comment(bytes: &[u8], cursor: &mut Cursor) {
129
+ while cursor.index < bytes.len() && bytes[cursor.index] != b'\n' {
130
+ cursor.advance(bytes);
131
+ }
132
+ }
133
+
134
+ fn skip_block_comment(bytes: &[u8], cursor: &mut Cursor) {
135
+ cursor.advance(bytes);
136
+ cursor.advance(bytes);
137
+
138
+ while cursor.index < bytes.len() {
139
+ let current = bytes[cursor.index];
140
+ let next = bytes.get(cursor.index + 1).copied();
141
+
142
+ cursor.advance(bytes);
143
+
144
+ if current == b'*' && next == Some(b'/') {
145
+ cursor.advance(bytes);
146
+ break;
147
+ }
148
+ }
149
+ }
150
+
151
+ #[cfg(test)]
152
+ mod tests {
153
+ use super::check_file;
154
+ use crate::config::{RuleConfig, Severity};
155
+ use std::path::Path;
156
+
157
+ #[test]
158
+ fn reports_debugger_statement() {
159
+ let violations = check_file(Path::new("Component.tsx"), "debugger;\n", &test_config());
160
+
161
+ assert_eq!(violations.len(), 1);
162
+ assert_eq!(violations[0].line, Some(1));
163
+ assert_eq!(violations[0].column, Some(1));
164
+ }
165
+
166
+ #[test]
167
+ fn reports_debugger_without_semicolon() {
168
+ let violations = check_file(Path::new("Component.tsx"), "debugger\n", &test_config());
169
+
170
+ assert_eq!(violations.len(), 1);
171
+ }
172
+
173
+ #[test]
174
+ fn reports_debugger_with_space() {
175
+ let violations = check_file(Path::new("Component.tsx"), "debugger \n", &test_config());
176
+
177
+ assert_eq!(violations.len(), 1);
178
+ }
179
+
180
+ #[test]
181
+ fn ignores_debugger_in_comments() {
182
+ let source = "// debugger;\n/* debugger; */\n";
183
+ let violations = check_file(Path::new("Component.tsx"), source, &test_config());
184
+
185
+ assert!(violations.is_empty());
186
+ }
187
+
188
+ #[test]
189
+ fn ignores_debugger_in_strings() {
190
+ let source = r#"const text = "debugger";"#;
191
+ let violations = check_file(Path::new("Component.tsx"), source, &test_config());
192
+
193
+ assert!(violations.is_empty());
194
+ }
195
+
196
+ #[test]
197
+ fn does_not_match_identifier_fragment() {
198
+ let source = "const debuggerHelper = true;\n";
199
+ let violations = check_file(Path::new("Component.tsx"), source, &test_config());
200
+
201
+ assert!(violations.is_empty());
202
+ }
203
+
204
+ fn test_config() -> RuleConfig {
205
+ RuleConfig {
206
+ severity: Severity::Warn,
207
+ }
208
+ }
209
+ }