@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.
- package/Cargo.lock +458 -0
- package/Cargo.toml +13 -0
- package/LICENSE +21 -0
- package/README.md +119 -0
- package/bin/niteo.js +51 -0
- package/package.json +27 -0
- package/scripts/build-binary.js +23 -0
- package/src/app.rs +132 -0
- package/src/cli.rs +43 -0
- package/src/config.rs +989 -0
- package/src/discovery.rs +42 -0
- package/src/git.rs +63 -0
- package/src/main.rs +13 -0
- package/src/report.rs +490 -0
- package/src/rules/max_directory_depth.rs +125 -0
- package/src/rules/max_file_exports.rs +478 -0
- package/src/rules/max_items_per_directory.rs +145 -0
- package/src/rules/min_items_per_directory.rs +146 -0
- package/src/rules/no_barrel_files.rs +284 -0
- package/src/rules/no_comments.rs +222 -0
- package/src/rules/no_console.rs +242 -0
- package/src/rules/no_debugger.rs +209 -0
- package/src/rules/no_default_export.rs +241 -0
- package/src/rules/no_duplicate_file_names.rs +196 -0
- package/src/rules/no_empty_directories.rs +467 -0
- package/src/rules/no_empty_interface.rs +280 -0
- package/src/rules/no_enums.rs +208 -0
- package/src/rules/no_eval.rs +268 -0
- package/src/rules/no_export_star.rs +252 -0
- package/src/rules/no_inline_types.rs +367 -0
- package/src/rules/no_interface.rs +570 -0
- package/src/rules/no_large_file.rs +98 -0
- package/src/rules/no_logic_in_barrel.rs +346 -0
- package/src/rules/no_logic_in_domain.rs +987 -0
- package/src/rules/no_mutable_exports.rs +253 -0
- package/src/rules/no_upward_import.rs +427 -0
- package/src/rules/prefer_satisfies.rs +319 -0
- package/src/rules.rs +247 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use crate::config::{RuleConfig, Severity};
|
|
4
|
+
use crate::rules::Violation;
|
|
5
|
+
|
|
6
|
+
const RULE_NAME: &str = "no-eval";
|
|
7
|
+
const MESSAGE: &str = "Disallow eval() and new Function() as they execute arbitrary 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_eval_call(bytes, cursor.index) => {
|
|
44
|
+
violations.push(eval_violation(file, &cursor, config.severity));
|
|
45
|
+
advance_past_eval(bytes, &mut cursor);
|
|
46
|
+
}
|
|
47
|
+
_ if starts_new_function(bytes, cursor.index) => {
|
|
48
|
+
violations.push(eval_violation(file, &cursor, config.severity));
|
|
49
|
+
advance_past_new_function(bytes, &mut cursor);
|
|
50
|
+
}
|
|
51
|
+
_ => cursor.advance(bytes),
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
violations
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#[derive(Debug, Clone, Copy)]
|
|
59
|
+
struct Cursor {
|
|
60
|
+
index: usize,
|
|
61
|
+
line: usize,
|
|
62
|
+
column: usize,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
impl Default for Cursor {
|
|
66
|
+
fn default() -> Self {
|
|
67
|
+
Self {
|
|
68
|
+
index: 0,
|
|
69
|
+
line: 1,
|
|
70
|
+
column: 1,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
impl Cursor {
|
|
76
|
+
fn advance(&mut self, bytes: &[u8]) {
|
|
77
|
+
if bytes[self.index] == b'\n' {
|
|
78
|
+
self.line += 1;
|
|
79
|
+
self.column = 1;
|
|
80
|
+
} else {
|
|
81
|
+
self.column += 1;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
self.index += 1;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fn eval_violation(file: &Path, cursor: &Cursor, severity: Severity) -> Violation {
|
|
89
|
+
Violation {
|
|
90
|
+
file: file.to_path_buf(),
|
|
91
|
+
line: Some(cursor.line),
|
|
92
|
+
column: Some(cursor.column),
|
|
93
|
+
rule: RULE_NAME,
|
|
94
|
+
message: MESSAGE,
|
|
95
|
+
severity,
|
|
96
|
+
detail: None,
|
|
97
|
+
subject: None,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
fn starts_eval_call(bytes: &[u8], index: usize) -> bool {
|
|
102
|
+
if !starts_keyword(bytes, index, b"eval") {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let after_eval = index + b"eval".len();
|
|
107
|
+
let next = bytes.get(after_eval);
|
|
108
|
+
|
|
109
|
+
matches!(next, Some(b'('))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn starts_new_function(bytes: &[u8], index: usize) -> bool {
|
|
113
|
+
if !starts_keyword(bytes, index, b"new") {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let after_new = index + b"new".len();
|
|
118
|
+
let next_index = skip_inline_whitespace(bytes, after_new);
|
|
119
|
+
|
|
120
|
+
if !starts_keyword(bytes, next_index, b"Function") {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let after_function = next_index + b"Function".len();
|
|
125
|
+
let next = bytes.get(after_function);
|
|
126
|
+
|
|
127
|
+
matches!(next, Some(b'('))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fn starts_keyword(bytes: &[u8], index: usize, keyword: &[u8]) -> bool {
|
|
131
|
+
bytes.get(index..index + keyword.len()) == Some(keyword)
|
|
132
|
+
&& !is_identifier_byte(bytes.get(index.wrapping_sub(1)).copied())
|
|
133
|
+
&& !is_identifier_byte(bytes.get(index + keyword.len()).copied())
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
fn is_identifier_byte(byte: Option<u8>) -> bool {
|
|
137
|
+
matches!(
|
|
138
|
+
byte,
|
|
139
|
+
Some(b'a'..=b'z') | Some(b'A'..=b'Z') | Some(b'0'..=b'9') | Some(b'_') | Some(b'$')
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
fn skip_inline_whitespace(bytes: &[u8], mut index: usize) -> usize {
|
|
144
|
+
while matches!(bytes.get(index), Some(b' ' | b'\t' | b'\r' | b'\n')) {
|
|
145
|
+
index += 1;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
index
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
fn advance_past_eval(bytes: &[u8], cursor: &mut Cursor) {
|
|
152
|
+
let target_index = cursor.index + b"eval".len();
|
|
153
|
+
while cursor.index < bytes.len() && cursor.index < target_index {
|
|
154
|
+
cursor.advance(bytes);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fn advance_past_new_function(bytes: &[u8], cursor: &mut Cursor) {
|
|
159
|
+
let after_new = cursor.index + b"new".len();
|
|
160
|
+
let next_index = skip_inline_whitespace_in_cursor(bytes, cursor, after_new);
|
|
161
|
+
|
|
162
|
+
let target_index = next_index + b"Function".len();
|
|
163
|
+
while cursor.index < bytes.len() && cursor.index < target_index {
|
|
164
|
+
cursor.advance(bytes);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fn skip_inline_whitespace_in_cursor(bytes: &[u8], cursor: &mut Cursor, target: usize) -> usize {
|
|
169
|
+
while cursor.index < bytes.len() && cursor.index < target {
|
|
170
|
+
cursor.advance(bytes);
|
|
171
|
+
}
|
|
172
|
+
cursor.index
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
fn skip_line_comment(bytes: &[u8], cursor: &mut Cursor) {
|
|
176
|
+
while cursor.index < bytes.len() && bytes[cursor.index] != b'\n' {
|
|
177
|
+
cursor.advance(bytes);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
fn skip_block_comment(bytes: &[u8], cursor: &mut Cursor) {
|
|
182
|
+
cursor.advance(bytes);
|
|
183
|
+
cursor.advance(bytes);
|
|
184
|
+
|
|
185
|
+
while cursor.index < bytes.len() {
|
|
186
|
+
let current = bytes[cursor.index];
|
|
187
|
+
let next = bytes.get(cursor.index + 1).copied();
|
|
188
|
+
|
|
189
|
+
cursor.advance(bytes);
|
|
190
|
+
|
|
191
|
+
if current == b'*' && next == Some(b'/') {
|
|
192
|
+
cursor.advance(bytes);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
#[cfg(test)]
|
|
199
|
+
mod tests {
|
|
200
|
+
use super::check_file;
|
|
201
|
+
use crate::config::{RuleConfig, Severity};
|
|
202
|
+
use std::path::Path;
|
|
203
|
+
|
|
204
|
+
#[test]
|
|
205
|
+
fn reports_eval_call() {
|
|
206
|
+
let violations = check_file(
|
|
207
|
+
Path::new("Component.tsx"),
|
|
208
|
+
"eval('code');\n",
|
|
209
|
+
&test_config(),
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
assert_eq!(violations.len(), 1);
|
|
213
|
+
assert_eq!(violations[0].line, Some(1));
|
|
214
|
+
assert_eq!(violations[0].column, Some(1));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#[test]
|
|
218
|
+
fn reports_new_function() {
|
|
219
|
+
let violations = check_file(
|
|
220
|
+
Path::new("Component.tsx"),
|
|
221
|
+
"new Function('return 1');\n",
|
|
222
|
+
&test_config(),
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
assert_eq!(violations.len(), 1);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#[test]
|
|
229
|
+
fn reports_new_function_with_space() {
|
|
230
|
+
let violations = check_file(
|
|
231
|
+
Path::new("Component.tsx"),
|
|
232
|
+
"new Function('return 1');\n",
|
|
233
|
+
&test_config(),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
assert_eq!(violations.len(), 1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
#[test]
|
|
240
|
+
fn ignores_eval_in_comments() {
|
|
241
|
+
let source = "// eval('code');\n/* new Function('test'); */\n";
|
|
242
|
+
let violations = check_file(Path::new("Component.tsx"), source, &test_config());
|
|
243
|
+
|
|
244
|
+
assert!(violations.is_empty());
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
#[test]
|
|
248
|
+
fn ignores_eval_in_strings() {
|
|
249
|
+
let source = r#"const text = "eval('hello')";"#;
|
|
250
|
+
let violations = check_file(Path::new("Component.tsx"), source, &test_config());
|
|
251
|
+
|
|
252
|
+
assert!(violations.is_empty());
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
#[test]
|
|
256
|
+
fn does_not_match_identifier_fragment() {
|
|
257
|
+
let source = "const evaluate = true;\nconst FunctionBuilder = class {};\n";
|
|
258
|
+
let violations = check_file(Path::new("Component.tsx"), source, &test_config());
|
|
259
|
+
|
|
260
|
+
assert!(violations.is_empty());
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
fn test_config() -> RuleConfig {
|
|
264
|
+
RuleConfig {
|
|
265
|
+
severity: Severity::Warn,
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use crate::config::{RuleConfig, Severity};
|
|
4
|
+
use crate::rules::Violation;
|
|
5
|
+
|
|
6
|
+
const RULE_NAME: &str = "no-export-star";
|
|
7
|
+
const MESSAGE: &str =
|
|
8
|
+
"Avoid export * because it hides the public API shape. Use explicit named re-exports.";
|
|
9
|
+
|
|
10
|
+
pub fn check_file(file: &Path, source: &str, config: &RuleConfig) -> Vec<Violation> {
|
|
11
|
+
let bytes = source.as_bytes();
|
|
12
|
+
let mut violations = Vec::new();
|
|
13
|
+
let mut cursor = Cursor::default();
|
|
14
|
+
let mut string_quote: Option<u8> = None;
|
|
15
|
+
|
|
16
|
+
while cursor.index < bytes.len() {
|
|
17
|
+
let current = bytes[cursor.index];
|
|
18
|
+
let next = bytes.get(cursor.index + 1).copied();
|
|
19
|
+
|
|
20
|
+
if let Some(quote) = string_quote {
|
|
21
|
+
if current == b'\\' {
|
|
22
|
+
cursor.advance(bytes);
|
|
23
|
+
if cursor.index < bytes.len() {
|
|
24
|
+
cursor.advance(bytes);
|
|
25
|
+
}
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if current == quote {
|
|
30
|
+
string_quote = None;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
cursor.advance(bytes);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
match (current, next) {
|
|
38
|
+
(b'\'', _) | (b'"', _) | (b'`', _) => {
|
|
39
|
+
string_quote = Some(current);
|
|
40
|
+
cursor.advance(bytes);
|
|
41
|
+
}
|
|
42
|
+
(b'/', Some(b'/')) => skip_line_comment(bytes, &mut cursor),
|
|
43
|
+
(b'/', Some(b'*')) => skip_block_comment(bytes, &mut cursor),
|
|
44
|
+
_ if starts_export_star(bytes, cursor.index) => {
|
|
45
|
+
violations.push(export_star_violation(file, &cursor, config.severity));
|
|
46
|
+
advance_past_export_star(bytes, &mut cursor);
|
|
47
|
+
}
|
|
48
|
+
_ => cursor.advance(bytes),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
violations
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#[derive(Debug, Clone, Copy)]
|
|
56
|
+
struct Cursor {
|
|
57
|
+
index: usize,
|
|
58
|
+
line: usize,
|
|
59
|
+
column: usize,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
impl Default for Cursor {
|
|
63
|
+
fn default() -> Self {
|
|
64
|
+
Self {
|
|
65
|
+
index: 0,
|
|
66
|
+
line: 1,
|
|
67
|
+
column: 1,
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
impl Cursor {
|
|
73
|
+
fn advance(&mut self, bytes: &[u8]) {
|
|
74
|
+
if bytes[self.index] == b'\n' {
|
|
75
|
+
self.line += 1;
|
|
76
|
+
self.column = 1;
|
|
77
|
+
} else {
|
|
78
|
+
self.column += 1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
self.index += 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fn export_star_violation(file: &Path, cursor: &Cursor, severity: Severity) -> Violation {
|
|
86
|
+
Violation {
|
|
87
|
+
file: file.to_path_buf(),
|
|
88
|
+
line: Some(cursor.line),
|
|
89
|
+
column: Some(cursor.column),
|
|
90
|
+
rule: RULE_NAME,
|
|
91
|
+
message: MESSAGE,
|
|
92
|
+
severity,
|
|
93
|
+
detail: None,
|
|
94
|
+
subject: None,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fn starts_export_star(bytes: &[u8], index: usize) -> bool {
|
|
99
|
+
if !starts_keyword(bytes, index, b"export") {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let mut next_index = index + b"export".len();
|
|
104
|
+
next_index = skip_inline_whitespace(bytes, next_index);
|
|
105
|
+
|
|
106
|
+
if !starts_keyword(bytes, next_index, b"*") {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let after_star = next_index + 1;
|
|
111
|
+
let after_ws = skip_inline_whitespace(bytes, after_star);
|
|
112
|
+
|
|
113
|
+
if starts_keyword(bytes, after_ws, b"as") {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let next_byte = bytes.get(after_star).copied();
|
|
118
|
+
!is_identifier_byte(next_byte)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fn starts_keyword(bytes: &[u8], index: usize, keyword: &[u8]) -> bool {
|
|
122
|
+
bytes.get(index..index + keyword.len()) == Some(keyword)
|
|
123
|
+
&& !is_identifier_byte(bytes.get(index.wrapping_sub(1)).copied())
|
|
124
|
+
&& !is_identifier_byte(bytes.get(index + keyword.len()).copied())
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fn is_identifier_byte(byte: Option<u8>) -> bool {
|
|
128
|
+
matches!(
|
|
129
|
+
byte,
|
|
130
|
+
Some(b'a'..=b'z') | Some(b'A'..=b'Z') | Some(b'0'..=b'9') | Some(b'_') | Some(b'$')
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fn skip_inline_whitespace(bytes: &[u8], mut index: usize) -> usize {
|
|
135
|
+
while matches!(bytes.get(index), Some(b' ' | b'\t' | b'\r' | b'\n')) {
|
|
136
|
+
index += 1;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
index
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
fn advance_past_export_star(bytes: &[u8], cursor: &mut Cursor) {
|
|
143
|
+
let target_index = cursor.index + b"export".len() + 1;
|
|
144
|
+
while cursor.index < bytes.len() && cursor.index < target_index {
|
|
145
|
+
cursor.advance(bytes);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
fn skip_line_comment(bytes: &[u8], cursor: &mut Cursor) {
|
|
150
|
+
while cursor.index < bytes.len() && bytes[cursor.index] != b'\n' {
|
|
151
|
+
cursor.advance(bytes);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
fn skip_block_comment(bytes: &[u8], cursor: &mut Cursor) {
|
|
156
|
+
cursor.advance(bytes);
|
|
157
|
+
cursor.advance(bytes);
|
|
158
|
+
|
|
159
|
+
while cursor.index < bytes.len() {
|
|
160
|
+
let current = bytes[cursor.index];
|
|
161
|
+
let next = bytes.get(cursor.index + 1).copied();
|
|
162
|
+
|
|
163
|
+
cursor.advance(bytes);
|
|
164
|
+
|
|
165
|
+
if current == b'*' && next == Some(b'/') {
|
|
166
|
+
cursor.advance(bytes);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
#[cfg(test)]
|
|
173
|
+
mod tests {
|
|
174
|
+
use super::check_file;
|
|
175
|
+
use crate::config::{RuleConfig, Severity};
|
|
176
|
+
use std::path::Path;
|
|
177
|
+
|
|
178
|
+
#[test]
|
|
179
|
+
fn reports_export_star() {
|
|
180
|
+
let violations = check_file(
|
|
181
|
+
Path::new("index.ts"),
|
|
182
|
+
"export * from './module';\n",
|
|
183
|
+
&test_config(),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
assert_eq!(violations.len(), 1);
|
|
187
|
+
assert_eq!(violations[0].line, Some(1));
|
|
188
|
+
assert_eq!(violations[0].column, Some(1));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
#[test]
|
|
192
|
+
fn reports_multiline_export_star() {
|
|
193
|
+
let violations = check_file(
|
|
194
|
+
Path::new("index.ts"),
|
|
195
|
+
"export\n * from './module';\n",
|
|
196
|
+
&test_config(),
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
assert_eq!(violations.len(), 1);
|
|
200
|
+
assert_eq!(violations[0].line, Some(1));
|
|
201
|
+
assert_eq!(violations[0].column, Some(1));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
#[test]
|
|
205
|
+
fn allows_named_re_exports() {
|
|
206
|
+
let violations = check_file(
|
|
207
|
+
Path::new("index.ts"),
|
|
208
|
+
"export { foo, bar } from './module';\n",
|
|
209
|
+
&test_config(),
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
assert!(violations.is_empty());
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
#[test]
|
|
216
|
+
fn allows_namespace_re_exports() {
|
|
217
|
+
let violations = check_file(
|
|
218
|
+
Path::new("index.ts"),
|
|
219
|
+
"export * as utils from './utils';\n",
|
|
220
|
+
&test_config(),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
assert!(violations.is_empty());
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
#[test]
|
|
227
|
+
fn ignores_export_star_in_comments_and_strings() {
|
|
228
|
+
let source = r#"// export * from './module';
|
|
229
|
+
const text = "export * from './module'";
|
|
230
|
+
/* export * from './module'; */
|
|
231
|
+
"#;
|
|
232
|
+
let violations = check_file(Path::new("index.ts"), source, &test_config());
|
|
233
|
+
|
|
234
|
+
assert!(violations.is_empty());
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
#[test]
|
|
238
|
+
fn does_not_match_star_in_expression() {
|
|
239
|
+
let source = r#"const exportStar = true;
|
|
240
|
+
const value = "before export * after";
|
|
241
|
+
"#;
|
|
242
|
+
let violations = check_file(Path::new("index.ts"), source, &test_config());
|
|
243
|
+
|
|
244
|
+
assert!(violations.is_empty());
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
fn test_config() -> RuleConfig {
|
|
248
|
+
RuleConfig {
|
|
249
|
+
severity: Severity::Warn,
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|