@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.
- package/config/rule-analysis-strategies.js +3 -3
- package/config/rules/enhanced-rules-registry.json +40 -20
- package/core/cli-action-handler.js +2 -2
- package/core/config-merger.js +28 -6
- package/core/constants/defaults.js +1 -1
- package/core/file-targeting-service.js +72 -4
- package/core/output-service.js +21 -4
- package/engines/heuristic-engine.js +5 -0
- package/package.json +1 -1
- package/rules/common/C002_no_duplicate_code/README.md +115 -0
- package/rules/common/C002_no_duplicate_code/analyzer.js +615 -219
- package/rules/common/C002_no_duplicate_code/test-cases/api-handlers.ts +64 -0
- package/rules/common/C002_no_duplicate_code/test-cases/data-processor.ts +46 -0
- package/rules/common/C002_no_duplicate_code/test-cases/good-example.tsx +40 -0
- package/rules/common/C002_no_duplicate_code/test-cases/product-service.ts +57 -0
- package/rules/common/C002_no_duplicate_code/test-cases/user-service.ts +49 -0
- package/rules/common/C008/analyzer.js +40 -0
- package/rules/common/C008/config.json +20 -0
- package/rules/common/C008/ts-morph-analyzer.js +1067 -0
- package/rules/common/C018_no_throw_generic_error/analyzer.js +1 -1
- package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +27 -3
- package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +504 -162
- package/rules/common/C029_catch_block_logging/analyzer.js +499 -89
- package/rules/common/C033_separate_service_repository/README.md +131 -20
- package/rules/common/C033_separate_service_repository/analyzer.js +1 -1
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +417 -274
- package/rules/common/C041_no_sensitive_hardcode/analyzer.js +144 -254
- package/rules/common/C041_no_sensitive_hardcode/config.json +50 -0
- package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +575 -0
- package/rules/common/C067_no_hardcoded_config/analyzer.js +17 -16
- package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js +3477 -659
- package/rules/docs/C002_no_duplicate_code.md +276 -11
- package/rules/index.js +5 -1
- package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +266 -88
- package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +805 -0
- package/rules/security/S010_no_insecure_encryption/README.md +78 -0
- package/rules/security/S010_no_insecure_encryption/analyzer.js +463 -398
- package/rules/security/S013_tls_enforcement/README.md +51 -0
- package/rules/security/S013_tls_enforcement/analyzer.js +99 -0
- package/rules/security/S013_tls_enforcement/config.json +41 -0
- package/rules/security/S013_tls_enforcement/symbol-based-analyzer.js +339 -0
- package/rules/security/S014_tls_version_enforcement/README.md +354 -0
- package/rules/security/S014_tls_version_enforcement/analyzer.js +118 -0
- package/rules/security/S014_tls_version_enforcement/config.json +56 -0
- package/rules/security/S014_tls_version_enforcement/symbol-based-analyzer.js +194 -0
- package/rules/security/S055_content_type_validation/analyzer.js +121 -279
- package/rules/security/S055_content_type_validation/symbol-based-analyzer.js +346 -0
- package/rules/tests/C002_no_duplicate_code.test.js +111 -22
- package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +0 -755
- package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +0 -296
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// ❌ VIOLATION: Monkey coding - Copy-paste error handling
|
|
2
|
+
|
|
3
|
+
export class ApiHandlers {
|
|
4
|
+
// Get user by ID with error handling
|
|
5
|
+
async getUserById(id: number) {
|
|
6
|
+
try {
|
|
7
|
+
const response = await fetch(`/api/users/${id}`);
|
|
8
|
+
|
|
9
|
+
if (!response.ok) {
|
|
10
|
+
const error = await response.json();
|
|
11
|
+
console.error('Error fetching user:', error);
|
|
12
|
+
throw new Error(error.message || 'Failed to fetch user');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const data = await response.json();
|
|
16
|
+
return data;
|
|
17
|
+
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error('Network error:', error);
|
|
20
|
+
throw new Error('Network request failed');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Get product by ID - SAME error handling!
|
|
25
|
+
async getProductById(id: number) {
|
|
26
|
+
// DUPLICATE: Same try-catch, error handling pattern (monkey coding!)
|
|
27
|
+
try {
|
|
28
|
+
const response = await fetch(`/api/products/${id}`);
|
|
29
|
+
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
const error = await response.json();
|
|
32
|
+
console.error('Error fetching product:', error);
|
|
33
|
+
throw new Error(error.message || 'Failed to fetch product');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const data = await response.json();
|
|
37
|
+
return data;
|
|
38
|
+
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('Network error:', error);
|
|
41
|
+
throw new Error('Network request failed');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Get order by ID - same pattern
|
|
46
|
+
async getOrderById(id: number) {
|
|
47
|
+
try {
|
|
48
|
+
const response = await fetch(`/api/orders/${id}`);
|
|
49
|
+
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
const error = await response.json();
|
|
52
|
+
console.error('Error fetching order:', error);
|
|
53
|
+
throw new Error(error.message || 'Failed to fetch order');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const data = await response.json();
|
|
57
|
+
return data;
|
|
58
|
+
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error('Network error:', error);
|
|
61
|
+
throw new Error('Network request failed');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// ❌ VIOLATION: Monkey coding - Copy-paste data transformation
|
|
2
|
+
|
|
3
|
+
export class DataProcessor {
|
|
4
|
+
// Process user data from API
|
|
5
|
+
processUserData(rawData: any[]) {
|
|
6
|
+
return rawData
|
|
7
|
+
.filter(item => item !== null && item !== undefined)
|
|
8
|
+
.map(item => ({
|
|
9
|
+
id: item.id,
|
|
10
|
+
name: item.name?.trim() || 'Unknown',
|
|
11
|
+
email: item.email?.toLowerCase() || '',
|
|
12
|
+
status: item.active ? 'active' : 'inactive',
|
|
13
|
+
createdAt: new Date(item.created_at)
|
|
14
|
+
}))
|
|
15
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Process product data from API - SAME transformation logic!
|
|
19
|
+
processProductData(rawData: any[]) {
|
|
20
|
+
// DUPLICATE: Same filtering, mapping, sorting pattern (monkey coding!)
|
|
21
|
+
return rawData
|
|
22
|
+
.filter(item => item !== null && item !== undefined)
|
|
23
|
+
.map(item => ({
|
|
24
|
+
id: item.id,
|
|
25
|
+
name: item.name?.trim() || 'Unknown',
|
|
26
|
+
price: item.price || 0,
|
|
27
|
+
status: item.available ? 'active' : 'inactive',
|
|
28
|
+
createdAt: new Date(item.created_at)
|
|
29
|
+
}))
|
|
30
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Process order data - same pattern again
|
|
34
|
+
processOrderData(rawData: any[]) {
|
|
35
|
+
return rawData
|
|
36
|
+
.filter(item => item !== null && item !== undefined)
|
|
37
|
+
.map(item => ({
|
|
38
|
+
id: item.id,
|
|
39
|
+
customer: item.customer_name?.trim() || 'Unknown',
|
|
40
|
+
total: item.total_amount || 0,
|
|
41
|
+
status: item.paid ? 'active' : 'inactive',
|
|
42
|
+
createdAt: new Date(item.order_date)
|
|
43
|
+
}))
|
|
44
|
+
.sort((a, b) => a.customer.localeCompare(b.customer));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// ✅ GOOD: No violation - These are intentional patterns (different names, simple wrappers)
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
// Different button variants - intentional pattern, not duplicate
|
|
6
|
+
export function PrimaryButton({ className, ...props }: React.ComponentProps<'button'>) {
|
|
7
|
+
return (
|
|
8
|
+
<button
|
|
9
|
+
className={`btn-primary ${className}`}
|
|
10
|
+
{...props}
|
|
11
|
+
/>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function SecondaryButton({ className, ...props }: React.ComponentProps<'button'>) {
|
|
16
|
+
return (
|
|
17
|
+
<button
|
|
18
|
+
className={`btn-secondary ${className}`}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function DangerButton({ className, ...props }: React.ComponentProps<'button'>) {
|
|
25
|
+
return (
|
|
26
|
+
<button
|
|
27
|
+
className={`btn-danger ${className}`}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Short functions - below minimum lines
|
|
34
|
+
export function add(a: number, b: number) {
|
|
35
|
+
return a + b;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function subtract(a: number, b: number) {
|
|
39
|
+
return a - b;
|
|
40
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// ❌ VIOLATION: Monkey coding - Copy-paste calculation logic
|
|
2
|
+
|
|
3
|
+
export class ProductService {
|
|
4
|
+
// Calculate price with tax for online orders
|
|
5
|
+
calculateOnlinePrice(price: number, quantity: number) {
|
|
6
|
+
const subtotal = price * quantity;
|
|
7
|
+
const tax = subtotal * 0.1; // 10% tax
|
|
8
|
+
const shipping = subtotal > 100 ? 0 : 10; // Free shipping over $100
|
|
9
|
+
const discount = subtotal > 200 ? subtotal * 0.05 : 0; // 5% discount over $200
|
|
10
|
+
const total = subtotal + tax + shipping - discount;
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
subtotal,
|
|
14
|
+
tax,
|
|
15
|
+
shipping,
|
|
16
|
+
discount,
|
|
17
|
+
total
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Calculate price with tax for store orders - SAME calculation!
|
|
22
|
+
calculateStorePrice(price: number, quantity: number) {
|
|
23
|
+
// DUPLICATE: Exactly same calculation logic (monkey coding!)
|
|
24
|
+
const subtotal = price * quantity;
|
|
25
|
+
const tax = subtotal * 0.1; // 10% tax
|
|
26
|
+
const shipping = subtotal > 100 ? 0 : 10; // Free shipping over $100
|
|
27
|
+
const discount = subtotal > 200 ? subtotal * 0.05 : 0; // 5% discount over $200
|
|
28
|
+
const total = subtotal + tax + shipping - discount;
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
subtotal,
|
|
32
|
+
tax,
|
|
33
|
+
shipping,
|
|
34
|
+
discount,
|
|
35
|
+
total
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Wholesale pricing - slightly different but still copy-paste
|
|
40
|
+
calculateWholesalePrice(price: number, quantity: number) {
|
|
41
|
+
const subtotal = price * quantity;
|
|
42
|
+
const tax = subtotal * 0.1; // 10% tax
|
|
43
|
+
const shipping = subtotal > 100 ? 0 : 10; // Free shipping over $100
|
|
44
|
+
const discount = subtotal > 200 ? subtotal * 0.05 : 0; // 5% discount over $200
|
|
45
|
+
const wholesaleDiscount = quantity > 50 ? subtotal * 0.1 : 0; // Extra 10% for bulk
|
|
46
|
+
const total = subtotal + tax + shipping - discount - wholesaleDiscount;
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
subtotal,
|
|
50
|
+
tax,
|
|
51
|
+
shipping,
|
|
52
|
+
discount,
|
|
53
|
+
wholesaleDiscount,
|
|
54
|
+
total
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// ❌ VIOLATION: Monkey coding - Copy-paste entire functions
|
|
2
|
+
|
|
3
|
+
export class UserService {
|
|
4
|
+
// Function 1: Process user payment
|
|
5
|
+
async processUserPayment(userId: number, amount: number) {
|
|
6
|
+
const user = await this.getUserData(userId);
|
|
7
|
+
|
|
8
|
+
if (amount <= 0) {
|
|
9
|
+
throw new Error('Invalid payment amount');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const fee = amount * 0.029 + 0.30; // Stripe fee
|
|
13
|
+
const total = amount + fee;
|
|
14
|
+
|
|
15
|
+
const result = await this.chargeCard(user.cardId, total);
|
|
16
|
+
|
|
17
|
+
if (!result.success) {
|
|
18
|
+
throw new Error('Payment failed');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
await this.recordTransaction(userId, total, 'payment');
|
|
22
|
+
return { success: true, transactionId: result.id };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Function 2: Process user refund - EXACT COPY-PASTE!
|
|
26
|
+
async processUserRefund(userId: number, amount: number) {
|
|
27
|
+
const user = await this.getUserData(userId);
|
|
28
|
+
|
|
29
|
+
if (amount <= 0) {
|
|
30
|
+
throw new Error('Invalid payment amount');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const fee = amount * 0.029 + 0.30; // Stripe fee
|
|
34
|
+
const total = amount + fee;
|
|
35
|
+
|
|
36
|
+
const result = await this.chargeCard(user.cardId, total);
|
|
37
|
+
|
|
38
|
+
if (!result.success) {
|
|
39
|
+
throw new Error('Payment failed');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await this.recordTransaction(userId, total, 'payment');
|
|
43
|
+
return { success: true, transactionId: result.id };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private async getUserData(userId: number) { return {}; }
|
|
47
|
+
private async chargeCard(cardId: string, amount: number) { return { success: true, id: '123' }; }
|
|
48
|
+
private async recordTransaction(userId: number, amount: number, type: string) { }
|
|
49
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C008 Analyzer - Minimize Variable Scope (Declare Near Usage)
|
|
3
|
+
*
|
|
4
|
+
* Uses ts-morph (AST-based) for 100% accurate analysis with intelligent filtering:
|
|
5
|
+
* - Module-level constants (UPPER_CASE = value)
|
|
6
|
+
* - Redux Toolkit patterns (createAsyncThunk, createSlice)
|
|
7
|
+
* - Storybook meta exports
|
|
8
|
+
* - React hooks (useState, useEffect, etc.)
|
|
9
|
+
* - CSS-in-JS (keyframes, styled, css)
|
|
10
|
+
*
|
|
11
|
+
* Following Rule C005: Single responsibility - variable scope detection
|
|
12
|
+
* Following Rule C006: Verb-noun naming
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const C008TsMorphAnalyzer = require('./ts-morph-analyzer');
|
|
16
|
+
|
|
17
|
+
class C008Analyzer {
|
|
18
|
+
constructor(semanticEngine = null, options = {}) {
|
|
19
|
+
this.analyzer = new C008TsMorphAnalyzer(semanticEngine, options);
|
|
20
|
+
this.initialized = false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async initialize(semanticEngine = null) {
|
|
24
|
+
if (!this.initialized) {
|
|
25
|
+
await this.analyzer.initialize(semanticEngine);
|
|
26
|
+
this.initialized = true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async analyze(files, language, options = {}) {
|
|
31
|
+
// Ensure initialized before analysis
|
|
32
|
+
if (!this.initialized) {
|
|
33
|
+
await this.initialize();
|
|
34
|
+
}
|
|
35
|
+
return this.analyzer.analyze(files, language, options);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = C008Analyzer;
|
|
40
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ruleId": "C008",
|
|
3
|
+
"name": "Minimize Variable Scope - Declare Near Usage",
|
|
4
|
+
"description": "Variables should be declared as close as possible to where they are first used to improve code locality and reduce cognitive load",
|
|
5
|
+
"category": "code-quality",
|
|
6
|
+
"severity": "warning",
|
|
7
|
+
"languages": ["typescript", "javascript"],
|
|
8
|
+
"enabled": true,
|
|
9
|
+
"metadata": {
|
|
10
|
+
"tags": ["readability", "maintainability", "best-practice"],
|
|
11
|
+
"principleId": "CODE_QUALITY",
|
|
12
|
+
"version": "1.0.0",
|
|
13
|
+
"status": "active"
|
|
14
|
+
},
|
|
15
|
+
"options": {
|
|
16
|
+
"maxLineDistance": 10,
|
|
17
|
+
"allowTopOfBlock": true,
|
|
18
|
+
"ignoreConst": false
|
|
19
|
+
}
|
|
20
|
+
}
|