@sun-asterisk/sunlint 1.3.16 → 1.3.17

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 (50) hide show
  1. package/config/rule-analysis-strategies.js +3 -3
  2. package/config/rules/enhanced-rules-registry.json +40 -20
  3. package/core/cli-action-handler.js +2 -2
  4. package/core/config-merger.js +28 -6
  5. package/core/constants/defaults.js +1 -1
  6. package/core/file-targeting-service.js +72 -4
  7. package/core/output-service.js +21 -4
  8. package/engines/heuristic-engine.js +5 -0
  9. package/package.json +1 -1
  10. package/rules/common/C002_no_duplicate_code/README.md +115 -0
  11. package/rules/common/C002_no_duplicate_code/analyzer.js +615 -219
  12. package/rules/common/C002_no_duplicate_code/test-cases/api-handlers.ts +64 -0
  13. package/rules/common/C002_no_duplicate_code/test-cases/data-processor.ts +46 -0
  14. package/rules/common/C002_no_duplicate_code/test-cases/good-example.tsx +40 -0
  15. package/rules/common/C002_no_duplicate_code/test-cases/product-service.ts +57 -0
  16. package/rules/common/C002_no_duplicate_code/test-cases/user-service.ts +49 -0
  17. package/rules/common/C008/analyzer.js +40 -0
  18. package/rules/common/C008/config.json +20 -0
  19. package/rules/common/C008/ts-morph-analyzer.js +1067 -0
  20. package/rules/common/C018_no_throw_generic_error/analyzer.js +1 -1
  21. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +27 -3
  22. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +504 -162
  23. package/rules/common/C029_catch_block_logging/analyzer.js +499 -89
  24. package/rules/common/C033_separate_service_repository/README.md +131 -20
  25. package/rules/common/C033_separate_service_repository/analyzer.js +1 -1
  26. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +417 -274
  27. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +144 -254
  28. package/rules/common/C041_no_sensitive_hardcode/config.json +50 -0
  29. package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +575 -0
  30. package/rules/common/C067_no_hardcoded_config/analyzer.js +17 -16
  31. package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js +3477 -659
  32. package/rules/docs/C002_no_duplicate_code.md +276 -11
  33. package/rules/index.js +5 -1
  34. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +266 -88
  35. package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +805 -0
  36. package/rules/security/S010_no_insecure_encryption/README.md +78 -0
  37. package/rules/security/S010_no_insecure_encryption/analyzer.js +463 -398
  38. package/rules/security/S013_tls_enforcement/README.md +51 -0
  39. package/rules/security/S013_tls_enforcement/analyzer.js +99 -0
  40. package/rules/security/S013_tls_enforcement/config.json +41 -0
  41. package/rules/security/S013_tls_enforcement/symbol-based-analyzer.js +339 -0
  42. package/rules/security/S014_tls_version_enforcement/README.md +354 -0
  43. package/rules/security/S014_tls_version_enforcement/analyzer.js +118 -0
  44. package/rules/security/S014_tls_version_enforcement/config.json +56 -0
  45. package/rules/security/S014_tls_version_enforcement/symbol-based-analyzer.js +194 -0
  46. package/rules/security/S055_content_type_validation/analyzer.js +121 -279
  47. package/rules/security/S055_content_type_validation/symbol-based-analyzer.js +346 -0
  48. package/rules/tests/C002_no_duplicate_code.test.js +111 -22
  49. package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +0 -755
  50. package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +0 -296
@@ -0,0 +1,346 @@
1
+ /**
2
+ * S055
3
+ * REST services must:
4
+ * 1. Check the Content-Type header in incoming requests.
5
+ * 2. Accept only supported types, such as:
6
+ * application/json
7
+ * application/xml (only if required)
8
+ * 3. Reject unsupported or unexpected types, e.g.:
9
+ * text/plain
10
+ * multipart/form-data (unless explicitly required)
11
+ * 4. Log all rejected requests for security and debugging.
12
+ * 5. Avoid blindly trusting framework parsing (e.g., using body-parser or @Body() decorators without validation).
13
+ */
14
+
15
+ const { SyntaxKind } = require('ts-morph');
16
+
17
+ class S055SymbolBasedAnalyzer {
18
+ constructor(semanticEngine = null) {
19
+ this.ruleId = "S055";
20
+ this.ruleName = 'Validate input Content-Type in REST services';
21
+ this.semanticEngine = semanticEngine;
22
+ this.verbose = false;
23
+ this.skipPatterns = [
24
+ /test\//, /tests\//, /__tests__\//, /\.test\./, /\.spec\./,
25
+ /node_modules\//, /build\//, /dist\//, /\.next\//, /coverage\//,
26
+ /vendor\//, /mocks\//, /\.mock\./,
27
+ /config\//, /configs\//, /\.config\./,
28
+ /public\//, /static\//, /assets\//,
29
+ ];
30
+
31
+ // Patterns to identify REST endpoints
32
+ this.restDecorators = ['@Post', '@Put', '@Patch', '@Get', '@Delete'];
33
+ this.expressPatterns = ['app.post', 'app.put', 'app.patch', 'router.post', 'router.put', 'router.patch'];
34
+
35
+ // Content-Type validation patterns
36
+ this.contentTypeChecks = [
37
+ 'Content-Type', 'content-type', 'contentType',
38
+ 'req.is(', 'request.is(',
39
+ 'application/json', 'application/xml'
40
+ ];
41
+
42
+ // Patterns for valid exceptions (GOOD cases)
43
+ this.validExceptions = {
44
+ // File upload decorators/middleware
45
+ fileUpload: [
46
+ '@UseInterceptors(FileInterceptor',
47
+ '@UseInterceptors(FilesInterceptor',
48
+ '@UseInterceptors(FileFieldsInterceptor',
49
+ '@UseInterceptors(AnyFilesInterceptor',
50
+ 'multer(',
51
+ 'upload.single',
52
+ 'upload.array',
53
+ 'upload.fields',
54
+ 'multipart/form-data'
55
+ ],
56
+ // Custom interceptors for validation
57
+ customInterceptors: [
58
+ '@UseInterceptors(ContentTypeInterceptor',
59
+ '@UseInterceptors(ValidationInterceptor',
60
+ '@UseInterceptors(ContentTypeValidation',
61
+ '@UseGuards(ContentTypeGuard',
62
+ 'ContentTypeValidator',
63
+ 'validateContentType'
64
+ ],
65
+ // Middleware patterns
66
+ middleware: [
67
+ 'contentTypeMiddleware',
68
+ 'validateContentType',
69
+ 'checkContentType'
70
+ ]
71
+ };
72
+ }
73
+
74
+ async initialize(semanticEngine = null) {
75
+ if (semanticEngine) {
76
+ this.semanticEngine = semanticEngine;
77
+ }
78
+ this.verbose = semanticEngine?.verbose || false;
79
+
80
+ if (process.env.SUNLINT_DEBUG) {
81
+ console.log(`🔧 [S055 Symbol-Based] Analyzer initialized, verbose: ${this.verbose}`);
82
+ }
83
+ }
84
+
85
+ async analyzeFileBasic(filePath, options = {}) {
86
+ // This is the main entry point called by the hybrid analyzer
87
+ return await this.analyzeFileWithSymbols(filePath, options);
88
+ }
89
+
90
+ analyzeFileWithSymbols(filePath, options = {}) {
91
+ const violations = [];
92
+
93
+ // Enable verbose mode if requested
94
+ const verbose = options.verbose || this.verbose;
95
+
96
+ if (!this.semanticEngine?.project) {
97
+ if (verbose) {
98
+ console.warn('[S055 Symbol-Based] No semantic engine available, skipping analysis');
99
+ }
100
+ return violations;
101
+ }
102
+
103
+ if (this.shouldIgnoreFile(filePath)) {
104
+ if (verbose) console.log(`[${this.ruleId}] Ignoring ${filePath}`);
105
+ return violations;
106
+ }
107
+
108
+ if (verbose) {
109
+ console.log(`🔍 [S055 Symbol-Based] Starting analysis for ${filePath}`);
110
+ }
111
+
112
+ try {
113
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
114
+ if (!sourceFile) {
115
+ return violations;
116
+ }
117
+
118
+ // Find all methods and functions
119
+ const methods = sourceFile.getDescendantsOfKind(SyntaxKind.MethodDeclaration);
120
+ const functions = sourceFile.getDescendantsOfKind(SyntaxKind.FunctionDeclaration);
121
+ const arrowFunctions = sourceFile.getDescendantsOfKind(SyntaxKind.ArrowFunction);
122
+ const functionExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.FunctionExpression);
123
+
124
+ // Check all methods
125
+ methods.forEach(method => this.checkMethod(method, sourceFile, violations));
126
+
127
+ // Check all functions
128
+ [...functions, ...arrowFunctions, ...functionExpressions].forEach(func => {
129
+ this.checkFunction(func, sourceFile, violations);
130
+ });
131
+
132
+ // Check Express-style route handlers
133
+ this.checkExpressRoutes(sourceFile, violations);
134
+
135
+ if (verbose) {
136
+ console.log(`🔍 [S055 Symbol-Based] Total violations found: ${violations.length}`);
137
+ }
138
+
139
+ return violations;
140
+ } catch (error) {
141
+ if (verbose) {
142
+ console.warn(`[S055 Symbol-Based] Analysis failed for ${filePath}:`, error.message);
143
+ }
144
+
145
+ return violations;
146
+ }
147
+ }
148
+
149
+ checkMethod(method, sourceFile, violations) {
150
+ // Check if it's a REST endpoint (NestJS, Spring-style decorators)
151
+ const decorators = method.getDecorators();
152
+ const isRestEndpoint = decorators.some(dec => {
153
+ const name = dec.getName();
154
+ return this.restDecorators.some(pattern => name.includes(pattern.substring(1)));
155
+ });
156
+
157
+ if (!isRestEndpoint) {
158
+ return;
159
+ }
160
+
161
+ // GOOD CASE: Check for file upload interceptors
162
+ if (this.hasFileUploadHandling(method, decorators)) {
163
+ if (this.verbose) {
164
+ console.log(`✅ [S055] Method ${method.getName()} has file upload handling - GOOD`);
165
+ }
166
+ return;
167
+ }
168
+
169
+ // GOOD CASE: Check for custom content-type interceptors/guards
170
+ if (this.hasCustomContentTypeValidation(method, decorators)) {
171
+ if (this.verbose) {
172
+ console.log(`✅ [S055] Method ${method.getName()} has custom interceptor - GOOD`);
173
+ }
174
+ return;
175
+ }
176
+
177
+ // Check if POST, PUT, or PATCH (methods that accept body)
178
+ const hasBodyDecorator = decorators.some(dec => {
179
+ const name = dec.getName();
180
+ return ['Post', 'Put', 'Patch'].includes(name);
181
+ });
182
+
183
+ if (hasBodyDecorator) {
184
+ this.checkContentTypeValidation(method, sourceFile, violations);
185
+ }
186
+ }
187
+
188
+ checkFunction(func, sourceFile, violations) {
189
+ const parent = func.getParent();
190
+
191
+ // Check if it's a route handler (has req/request parameter)
192
+ const params = func.getParameters();
193
+ const hasRequestParam = params.some(p => {
194
+ const name = p.getName();
195
+ return name === 'req' || name === 'request';
196
+ });
197
+
198
+ if (!hasRequestParam) {
199
+ return;
200
+ }
201
+
202
+ // GOOD CASE: Check for middleware usage
203
+ if (this.hasMiddlewareValidation(func, sourceFile)) {
204
+ if (this.verbose) {
205
+ console.log(`✅ [S055] Function has middleware validation - GOOD`);
206
+ }
207
+ return;
208
+ }
209
+
210
+ this.checkContentTypeValidation(func, sourceFile, violations);
211
+ }
212
+
213
+ checkExpressRoutes(sourceFile, violations) {
214
+ // Find all call expressions like app.post(), router.put(), etc.
215
+ const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
216
+
217
+ callExpressions.forEach(call => {
218
+ const expr = call.getExpression();
219
+ const text = expr.getText();
220
+
221
+ const isRestRoute = this.expressPatterns.some(pattern => text.includes(pattern));
222
+
223
+ if (!isRestRoute) {
224
+ return;
225
+ }
226
+
227
+ // GOOD CASE: Check for multer or file upload middleware
228
+ const args = call.getArguments();
229
+ if (this.hasFileUploadMiddleware(args)) {
230
+ if (this.verbose) {
231
+ console.log(`✅ [S055] Express route has file upload middleware - GOOD`);
232
+ }
233
+ return;
234
+ }
235
+
236
+ // GOOD CASE: Check for content-type validation middleware
237
+ if (this.hasContentTypeMiddleware(args)) {
238
+ if (this.verbose) {
239
+ console.log(`✅ [S055] Express route has content-type middleware - GOOD`);
240
+ }
241
+ return;
242
+ }
243
+
244
+ // Get the handler function (usually the last argument)
245
+ const handler = args[args.length - 1];
246
+
247
+ if (handler) {
248
+ this.checkContentTypeValidation(handler, sourceFile, violations);
249
+ }
250
+ });
251
+ }
252
+
253
+ hasFileUploadHandling(node, decorators) {
254
+ const nodeText = node.getText();
255
+
256
+ // Check decorators
257
+ const hasFileDecorator = decorators?.some(dec => {
258
+ const text = dec.getText();
259
+ return this.validExceptions.fileUpload.some(pattern => text.includes(pattern));
260
+ });
261
+
262
+ // Check method body
263
+ const hasFileInBody = this.validExceptions.fileUpload.some(pattern =>
264
+ nodeText.includes(pattern)
265
+ );
266
+
267
+ return hasFileDecorator || hasFileInBody;
268
+ }
269
+
270
+ hasCustomContentTypeValidation(node, decorators) {
271
+ const nodeText = node.getText();
272
+
273
+ // Check decorators
274
+ const hasInterceptor = decorators?.some(dec => {
275
+ const text = dec.getText();
276
+ return this.validExceptions.customInterceptors.some(pattern => text.includes(pattern));
277
+ });
278
+
279
+ // Check method body
280
+ const hasValidatorInBody = this.validExceptions.customInterceptors.some(pattern =>
281
+ nodeText.includes(pattern)
282
+ );
283
+
284
+ return hasInterceptor || hasValidatorInBody;
285
+ }
286
+
287
+ hasMiddlewareValidation(func, sourceFile) {
288
+ // Look for middleware usage in the same file or parent scope
289
+ const parentScope = func.getParent();
290
+ const scopeText = parentScope?.getText() || '';
291
+
292
+ return this.validExceptions.middleware.some(pattern =>
293
+ scopeText.includes(pattern)
294
+ );
295
+ }
296
+
297
+ hasFileUploadMiddleware(args) {
298
+ return args.some(arg => {
299
+ const text = arg.getText();
300
+ return this.validExceptions.fileUpload.some(pattern => text.includes(pattern));
301
+ });
302
+ }
303
+
304
+ hasContentTypeMiddleware(args) {
305
+ // Check if any middleware argument validates content-type
306
+ return args.some(arg => {
307
+ const text = arg.getText();
308
+ return this.validExceptions.middleware.some(pattern => text.includes(pattern)) ||
309
+ this.validExceptions.customInterceptors.some(pattern => text.includes(pattern));
310
+ });
311
+ }
312
+
313
+ checkContentTypeValidation(node, sourceFile, violations) {
314
+ const bodyText = node.getText();
315
+
316
+ // Check if Content-Type validation exists
317
+ const hasContentTypeCheck = this.contentTypeChecks.some(pattern =>
318
+ bodyText.includes(pattern)
319
+ );
320
+
321
+ if (!hasContentTypeCheck) {
322
+ const startLine = node.getStartLineNumber();
323
+ const name = node.getKind() === SyntaxKind.MethodDeclaration
324
+ ? node.getName()
325
+ : 'anonymous function';
326
+
327
+ violations.push({
328
+ ruleId: this.ruleId,
329
+ ruleName: this.ruleName,
330
+ severity: 'medium',
331
+ message: `REST endpoint '${name}' does not validate Content-Type header. Add validation for 'application/json' or other expected types.`,
332
+ line: startLine,
333
+ column: node.getStart() - node.getStartLinePos() + 1,
334
+ filePath: sourceFile.getFilePath(),
335
+ type: 'missing_content_type_validation',
336
+ details: 'Consider adding Content-Type validation using req.is("application/json") or checking req.headers["content-type"] before processing request body.'
337
+ });
338
+ }
339
+ }
340
+
341
+ shouldIgnoreFile(filePath) {
342
+ return this.skipPatterns.some((pattern) => pattern.test(filePath));
343
+ }
344
+ }
345
+
346
+ module.exports = S055SymbolBasedAnalyzer;
@@ -3,48 +3,137 @@
3
3
  * Tests for heuristic rule analyzer
4
4
  */
5
5
 
6
- const C002_no_duplicate_codeAnalyzer = require('./analyzer');
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+ const C002_no_duplicate_codeAnalyzer = require('../common/C002_no_duplicate_code/analyzer');
7
9
 
8
10
  describe('C002_no_duplicate_code Heuristic Rule', () => {
9
11
  let analyzer;
12
+ const fixturesPath = path.join(__dirname, '../../examples/rule-test-fixtures/rules/C002_no_duplicate_code');
10
13
 
11
14
  beforeEach(() => {
12
- analyzer = new C002_no_duplicate_codeAnalyzer();
15
+ analyzer = new C002_no_duplicate_codeAnalyzer({ minLines: 10, similarityThreshold: 0.85 });
13
16
  });
14
17
 
15
- describe('Valid Code', () => {
16
- test('should not report violations for valid code', () => {
17
- const code = `
18
- // TODO: Add valid code examples
19
- `;
18
+ describe('Configuration', () => {
19
+ test('should have correct default configuration', () => {
20
+ const config = analyzer.getConfig();
21
+ expect(config.minLines).toBe(10);
22
+ expect(config.similarityThreshold).toBe(0.85);
23
+ expect(config.ignoreComments).toBe(true);
24
+ expect(config.ignoreWhitespace).toBe(true);
25
+ });
26
+ });
20
27
 
21
- const violations = analyzer.analyze(code, 'test.js');
22
- expect(violations).toHaveLength(0);
28
+ describe('Valid Code - No Duplicates', () => {
29
+ test('should not report violations for clean code with no duplicates', () => {
30
+ const testFile = path.join(fixturesPath, 'clean/no-duplicates.ts');
31
+ if (fs.existsSync(testFile)) {
32
+ const violations = analyzer.analyze([testFile], 'typescript');
33
+ expect(violations).toHaveLength(0);
34
+ }
35
+ });
36
+
37
+ test('should not report violations for properly extracted utilities', () => {
38
+ const testFile = path.join(fixturesPath, 'clean/extracted-utilities.ts');
39
+ if (fs.existsSync(testFile)) {
40
+ const violations = analyzer.analyze([testFile], 'typescript');
41
+ expect(violations).toHaveLength(0);
42
+ }
43
+ });
44
+
45
+ test('should not report violations for inheritance pattern', () => {
46
+ const testFile = path.join(fixturesPath, 'clean/inheritance-pattern.ts');
47
+ if (fs.existsSync(testFile)) {
48
+ const violations = analyzer.analyze([testFile], 'typescript');
49
+ expect(violations).toHaveLength(0);
50
+ }
51
+ });
52
+
53
+ test('should not report violations for composition pattern', () => {
54
+ const testFile = path.join(fixturesPath, 'clean/composition-pattern.ts');
55
+ if (fs.existsSync(testFile)) {
56
+ const violations = analyzer.analyze([testFile], 'typescript');
57
+ expect(violations).toHaveLength(0);
58
+ }
23
59
  });
24
60
  });
25
61
 
26
- describe('Invalid Code', () => {
27
- test('should report violations for invalid code', () => {
28
- const code = `
29
- // TODO: Add invalid code examples
30
- `;
62
+ describe('Invalid Code - With Duplicates', () => {
63
+ test('should report violations for duplicate validation logic', () => {
64
+ const testFile = path.join(fixturesPath, 'violations/duplicate-with-comments.ts');
65
+ if (fs.existsSync(testFile)) {
66
+ const violations = analyzer.analyze([testFile], 'typescript');
67
+ expect(violations.length).toBeGreaterThan(0);
68
+ expect(violations[0].ruleId).toBe('C002');
69
+ expect(violations[0].message).toContain('Duplicate');
70
+ expect(violations[0].data.suggestions).toBeDefined();
71
+ }
72
+ });
31
73
 
32
- const violations = analyzer.analyze(code, 'test.js');
33
- expect(violations.length).toBeGreaterThan(0);
34
- expect(violations[0].ruleId).toBe('C002_no_duplicate_code');
74
+ test('should report violations for duplicate repository logic', () => {
75
+ const testFile = path.join(fixturesPath, 'violations/duplicate-repository-logic.ts');
76
+ if (fs.existsSync(testFile)) {
77
+ const violations = analyzer.analyze([testFile], 'typescript');
78
+ expect(violations.length).toBeGreaterThan(0);
79
+ expect(violations[0].message).toContain('Duplicate');
80
+ }
81
+ });
82
+
83
+ test('should report violations for duplicate error handling', () => {
84
+ const testFile = path.join(fixturesPath, 'violations/duplicate-error-handling.ts');
85
+ if (fs.existsSync(testFile)) {
86
+ const violations = analyzer.analyze([testFile], 'typescript');
87
+ expect(violations.length).toBeGreaterThan(0);
88
+ }
89
+ });
90
+ });
91
+
92
+ describe('Suggestions', () => {
93
+ test('should provide context-aware suggestions', () => {
94
+ const testFile = path.join(fixturesPath, 'violations/duplicate-with-comments.ts');
95
+ if (fs.existsSync(testFile)) {
96
+ const violations = analyzer.analyze([testFile], 'typescript');
97
+ if (violations.length > 0) {
98
+ expect(violations[0].data.suggestions).toBeDefined();
99
+ expect(Array.isArray(violations[0].data.suggestions)).toBe(true);
100
+ expect(violations[0].data.suggestions.length).toBeGreaterThan(0);
101
+ }
102
+ }
35
103
  });
36
104
  });
37
105
 
38
106
  describe('Edge Cases', () => {
39
- test('should handle empty code', () => {
40
- const violations = analyzer.analyze('', 'test.js');
107
+ test('should handle empty file list', () => {
108
+ const violations = analyzer.analyze([], 'typescript');
41
109
  expect(violations).toHaveLength(0);
42
110
  });
43
111
 
44
- test('should handle syntax errors gracefully', () => {
45
- const code = 'invalid javascript syntax {{{';
46
- const violations = analyzer.analyze(code, 'test.js');
112
+ test('should handle non-existent files gracefully', () => {
113
+ const violations = analyzer.analyze(['/non/existent/file.ts'], 'typescript');
47
114
  expect(Array.isArray(violations)).toBe(true);
48
115
  });
116
+
117
+ test('should handle files with only comments', () => {
118
+ const tempFile = path.join(__dirname, 'temp-comments-only.ts');
119
+ fs.writeFileSync(tempFile, '// Only comments\n/* More comments */\n');
120
+ const violations = analyzer.analyze([tempFile], 'typescript');
121
+ expect(violations).toHaveLength(0);
122
+ if (fs.existsSync(tempFile)) {
123
+ fs.unlinkSync(tempFile);
124
+ }
125
+ });
126
+ });
127
+
128
+ describe('Cross-file Detection', () => {
129
+ test('should detect duplicates across multiple files', () => {
130
+ const file1 = path.join(fixturesPath, 'violations/duplicate-repository-logic.ts');
131
+ const file2 = path.join(fixturesPath, 'violations/duplicate-error-handling.ts');
132
+
133
+ if (fs.existsSync(file1) && fs.existsSync(file2)) {
134
+ const violations = analyzer.analyze([file1, file2], 'typescript');
135
+ expect(Array.isArray(violations)).toBe(true);
136
+ }
137
+ });
49
138
  });
50
139
  });