@sun-asterisk/sunlint 1.3.26 → 1.3.28
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/rules/enhanced-rules-registry.json +101 -17
- package/config/rules/rules-registry-generated.json +22 -22
- package/origin-rules/security-en.md +351 -338
- package/package.json +1 -1
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +73 -21
- package/rules/common/C017_constructor_logic/symbol-based-analyzer.js +206 -2
- package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +553 -58
- package/rules/common/C029_catch_block_logging/analyzer.js +47 -12
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +35 -15
- package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +9 -5
- package/rules/security/S003_open_redirect_protection/README.md +371 -0
- package/rules/security/S003_open_redirect_protection/analyzer.js +135 -0
- package/rules/security/S003_open_redirect_protection/config.json +58 -0
- package/rules/security/S003_open_redirect_protection/symbol-based-analyzer.js +884 -0
- package/rules/security/S004_sensitive_data_logging/analyzer.js +135 -0
- package/rules/security/S004_sensitive_data_logging/config.json +62 -0
- package/rules/security/S004_sensitive_data_logging/symbol-based-analyzer.js +592 -0
- package/rules/security/S005_no_origin_auth/analyzer.js +97 -148
- package/rules/security/S005_no_origin_auth/config.json +28 -67
- package/rules/security/S005_no_origin_auth/symbol-based-analyzer.js +708 -0
- package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +170 -31
- package/rules/security/S010_no_insecure_encryption/analyzer.js +8 -2
- package/rules/security/S012_hardcoded_secrets/analyzer.js +149 -0
- package/rules/security/S012_hardcoded_secrets/config.json +75 -0
- package/rules/security/S012_hardcoded_secrets/symbol-based-analyzer.js +1204 -0
- package/rules/security/S013_tls_enforcement/symbol-based-analyzer.js +87 -0
- package/rules/security/S017_use_parameterized_queries/analyzer.js +11 -78
- package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +1146 -1
- package/rules/security/S019_smtp_injection_protection/analyzer.js +120 -0
- package/rules/security/S019_smtp_injection_protection/config.json +35 -0
- package/rules/security/S019_smtp_injection_protection/symbol-based-analyzer.js +687 -0
- package/rules/security/S020_no_eval_dynamic_code/analyzer.js +55 -130
- package/rules/security/S020_no_eval_dynamic_code/symbol-based-analyzer.js +4 -19
- package/rules/security/S022_escape_output_context/README.md +254 -0
- package/rules/security/S022_escape_output_context/analyzer.js +510 -0
- package/rules/security/S022_escape_output_context/config.json +229 -0
- package/rules/security/S023_no_json_injection/analyzer.js +15 -0
- package/rules/security/S023_no_json_injection/ast-analyzer.js +18 -3
- package/rules/security/S023_no_json_injection/config.json +133 -0
- package/rules/security/S024_xpath_xxe_protection/regex-based-analyzer.js +41 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +67 -8
- package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +29 -6
- package/rules/security/S029_csrf_protection/config.json +127 -0
- package/rules/security/S030_directory_browsing_protection/regex-based-analyzer.js +160 -28
- package/rules/security/S030_directory_browsing_protection/symbol-based-analyzer.js +81 -19
- package/rules/security/S031_secure_session_cookies/analyzer.js +20 -2
- package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +100 -0
- package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +8 -1
- package/rules/security/S032_httponly_session_cookies/analyzer.js +2 -2
- package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +115 -0
- package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +39 -10
- package/rules/security/S036_lfi_rfi_protection/analyzer.js +224 -0
- package/rules/security/S036_lfi_rfi_protection/config.json +20 -0
- package/rules/security/S040_session_fixation_protection/analyzer.js +153 -0
- package/rules/security/S040_session_fixation_protection/config.json +20 -0
- package/rules/security/S042_require_re_authentication_for_long_lived/README.md +83 -0
- package/rules/security/S042_require_re_authentication_for_long_lived/analyzer.js +153 -0
- package/rules/security/S042_require_re_authentication_for_long_lived/config.json +41 -0
- package/rules/security/S042_require_re_authentication_for_long_lived/symbol-based-analyzer.js +1139 -0
- package/rules/security/S043_password_changes_invalidate_all_sessions/README.md +107 -0
- package/rules/security/S043_password_changes_invalidate_all_sessions/analyzer.js +153 -0
- package/rules/security/S043_password_changes_invalidate_all_sessions/config.json +41 -0
- package/rules/security/S043_password_changes_invalidate_all_sessions/symbol-based-analyzer.js +541 -0
- package/docs/COMMAND-EXAMPLES.md +0 -390
- package/docs/FILE_LIMITS_COMPLETION_REPORT.md +0 -151
- package/docs/FOLDER_STRUCTURE.md +0 -59
- package/docs/SIMPLIFIED_USAGE_GUIDE.md +0 -208
- package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +0 -541
- package/rules/security/S020_no_eval_dynamic_code/regex-based-analyzer.js +0 -307
package/package.json
CHANGED
|
@@ -12,31 +12,83 @@ const { CommentDetector } = require('../../utils/rule-helpers');
|
|
|
12
12
|
|
|
13
13
|
class C003NoVagueAbbreviations {
|
|
14
14
|
constructor(options = {}) {
|
|
15
|
+
// Organize abbreviations by framework/category for better maintainability
|
|
16
|
+
const abbreviationsByCategory = {
|
|
17
|
+
// Core technical & web standards
|
|
18
|
+
core: [
|
|
19
|
+
'id', 'url', 'uri', 'api', 'ui', 'db', 'fs', 'os', 'io', 'ai', 'ml',
|
|
20
|
+
'dom', 'xhr', 'spa', 'pwa', 'seo', 'cdn', 'ssl', 'tls'
|
|
21
|
+
],
|
|
22
|
+
|
|
23
|
+
// File formats & protocols
|
|
24
|
+
formats: [
|
|
25
|
+
'json', 'xml', 'html', 'css', 'pdf', 'csv', 'tsv',
|
|
26
|
+
'png', 'jpg', 'gif', 'svg', 'mp4', 'mp3', 'zip', 'tar'
|
|
27
|
+
],
|
|
28
|
+
|
|
29
|
+
// Network protocols
|
|
30
|
+
protocols: [
|
|
31
|
+
'http', 'https', 'ftp', 'smtp', 'tcp', 'udp', 'sql'
|
|
32
|
+
],
|
|
33
|
+
|
|
34
|
+
// Programming languages
|
|
35
|
+
languages: [
|
|
36
|
+
'js', 'ts', 'py', 'rb', 'go', 'rs', 'kt', 'cs', 'vb', 'sh'
|
|
37
|
+
],
|
|
38
|
+
|
|
39
|
+
// Frontend frameworks & tools
|
|
40
|
+
frontend: [
|
|
41
|
+
'jsx', 'tsx', 'vue', 'scss', 'less'
|
|
42
|
+
],
|
|
43
|
+
|
|
44
|
+
// Testing abbreviations
|
|
45
|
+
testing: [
|
|
46
|
+
'qa', 'ci', 'cd', 'pr', 'it', 'ut', 'e2e',
|
|
47
|
+
// Test variants from user feedback
|
|
48
|
+
'qa1', 'qa2', 'ci1', 'ci2', 'it2', 'tsx2'
|
|
49
|
+
],
|
|
50
|
+
|
|
51
|
+
// Database & ORM
|
|
52
|
+
database: [
|
|
53
|
+
'orm', 'ddl', 'dml', 'etl', 'olap', 'oltp', 'sql'
|
|
54
|
+
],
|
|
55
|
+
|
|
56
|
+
// TypeORM specific
|
|
57
|
+
typeorm: [
|
|
58
|
+
'qb' // QueryBuilder - common in TypeORM
|
|
59
|
+
],
|
|
60
|
+
|
|
61
|
+
// Business & project management
|
|
62
|
+
business: [
|
|
63
|
+
'kpi', 'roi', 'sla', 'poc', 'mvp', 'b2b', 'b2c', 'crm', 'erp'
|
|
64
|
+
],
|
|
65
|
+
|
|
66
|
+
// Common development terms
|
|
67
|
+
dev: [
|
|
68
|
+
'config', 'env', 'app', 'btn', 'img', 'src', 'dest',
|
|
69
|
+
'req', 'res', 'ctx', 'auth', 'log', 'err', 'msg', 'key'
|
|
70
|
+
],
|
|
71
|
+
|
|
72
|
+
// Math & measurements
|
|
73
|
+
math: [
|
|
74
|
+
'min', 'max', 'len', 'num', 'str', 'ms'
|
|
75
|
+
],
|
|
76
|
+
|
|
77
|
+
// Common context terms
|
|
78
|
+
context: [
|
|
79
|
+
'value', 'result', 'response', 'request', 'data',
|
|
80
|
+
'item', 'element', 'object', 'async', 'length'
|
|
81
|
+
]
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Combine all abbreviations from categories
|
|
85
|
+
const allAbbreviations = Object.values(abbreviationsByCategory).flat();
|
|
86
|
+
|
|
15
87
|
this.options = {
|
|
16
88
|
allowedSingleChar: new Set(options.allowedSingleChar || [
|
|
17
89
|
'i', 'j', 'k', 'x', 'y', 'z', 'n', 'm', 't', 'v', 'r', 'e', 'p', 'w', 'h'
|
|
18
90
|
]),
|
|
19
|
-
allowedAbbreviations: new Set(options.allowedAbbreviations ||
|
|
20
|
-
// Technical abbreviations from user feedback
|
|
21
|
-
'id', 'url', 'uri', 'api', 'ui', 'db', 'fs', 'os', 'io', 'ai', 'ml', 'qa', 'ci', 'cd', 'pr',
|
|
22
|
-
'jwt', 'uuid', 'json', 'xml', 'html', 'css', 'sql', 'http', 'https', 'ftp', 'smtp', 'tcp', 'udp',
|
|
23
|
-
'pdf', 'csv', 'tsv', 'png', 'jpg', 'gif', 'svg', 'mp4', 'mp3', 'zip', 'tar',
|
|
24
|
-
'js', 'ts', 'py', 'rb', 'go', 'rs', 'kt', 'cs', 'vb', 'sh',
|
|
25
|
-
'dom', 'xhr', 'spa', 'pwa', 'seo', 'cdn', 'ssl', 'tls',
|
|
26
|
-
'orm', 'ddl', 'dml', 'etl', 'olap', 'oltp',
|
|
27
|
-
'kpi', 'roi', 'sla', 'poc', 'mvp', 'b2b', 'b2c', 'crm', 'erp',
|
|
28
|
-
'jsx', 'tsx', 'vue', 'scss', 'less',
|
|
29
|
-
'it', 'ut', 'e2e',
|
|
30
|
-
// Common development terms
|
|
31
|
-
'config', 'env', 'app', 'btn', 'img', 'src', 'dest', 'req', 'res', 'ctx',
|
|
32
|
-
'min', 'max', 'len', 'num', 'str', 'auth', 'log', 'err', 'msg', 'key',
|
|
33
|
-
// Add the variants from user feedback cases
|
|
34
|
-
'qa1', 'ci1', 'tsx2', 'it2', 'qa2', 'ci2',
|
|
35
|
-
// Common test/function context terms
|
|
36
|
-
'value', 'result', 'response', 'request', 'data', 'item', 'element', 'object',
|
|
37
|
-
// Common programming terms
|
|
38
|
-
'async', 'length', 'ms'
|
|
39
|
-
]),
|
|
91
|
+
allowedAbbreviations: new Set(options.allowedAbbreviations || allAbbreviations),
|
|
40
92
|
minLength: options.minLength || 2,
|
|
41
93
|
strictMode: options.strictMode || false
|
|
42
94
|
};
|
|
@@ -72,6 +72,158 @@ class C017SymbolBasedAnalyzer {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Check if the file is a Data Transfer Object (DTO)
|
|
77
|
+
* Uses multiple detection strategies:
|
|
78
|
+
* 1. Decorators (@DTO, @DataTransferObject)
|
|
79
|
+
* 2. Inheritance (extends BaseDTO, extends DTO)
|
|
80
|
+
* 3. Interface implementation (implements IDTO)
|
|
81
|
+
* 4. JSDoc annotations (@dto, @data-transfer-object)
|
|
82
|
+
* 5. File location (in dto/ or dtos/ folder)
|
|
83
|
+
* 6. Structural pattern analysis
|
|
84
|
+
* 7. Filename pattern (.dto.ts, .dto.js)
|
|
85
|
+
* 8. Class name pattern (ending with DTO/Dto)
|
|
86
|
+
*/
|
|
87
|
+
isDataTransferObject(filePath, constructor) {
|
|
88
|
+
try {
|
|
89
|
+
const classDecl = constructor.getParent();
|
|
90
|
+
if (!classDecl) return false;
|
|
91
|
+
|
|
92
|
+
// Strategy 1: Check decorators
|
|
93
|
+
const decorators = classDecl.getDecorators?.() || [];
|
|
94
|
+
for (const decorator of decorators) {
|
|
95
|
+
const decoratorName = decorator.getName();
|
|
96
|
+
if (decoratorName && /^(DTO|DataTransferObject|Dto)$/i.test(decoratorName)) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Strategy 2: Check inheritance (extends BaseDTO, DTO, etc.)
|
|
102
|
+
const heritage = classDecl.getExtends?.();
|
|
103
|
+
if (heritage) {
|
|
104
|
+
const baseClassName = heritage.getText();
|
|
105
|
+
if (/\b(Base)?(DTO|Dto|DataTransferObject)\b/.test(baseClassName)) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Strategy 3: Check interface implementation
|
|
111
|
+
const implementations = classDecl.getImplements?.() || [];
|
|
112
|
+
for (const impl of implementations) {
|
|
113
|
+
const interfaceName = impl.getText();
|
|
114
|
+
if (/^I?(DTO|Dto|DataTransferObject)$/i.test(interfaceName)) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Strategy 4: Check JSDoc tags
|
|
120
|
+
const jsDocs = classDecl.getJsDocs?.() || [];
|
|
121
|
+
for (const jsDoc of jsDocs) {
|
|
122
|
+
const tags = jsDoc.getTags?.() || [];
|
|
123
|
+
for (const tag of tags) {
|
|
124
|
+
const tagName = tag.getTagName();
|
|
125
|
+
if (tagName && /^(dto|data-transfer-object|transfer-object)$/i.test(tagName)) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Also check comment text
|
|
130
|
+
const comment = jsDoc.getDescription?.() || '';
|
|
131
|
+
if (/@(dto|data-transfer-object)\b/i.test(comment)) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Strategy 5: Check file location (dto/ or dtos/ folder)
|
|
137
|
+
if (filePath.match(/[\/\\](dto|dtos|transfer-objects?)[\/\\]/i)) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Strategy 6: Structural pattern analysis
|
|
142
|
+
if (this.hasDataTransferObjectStructure(classDecl)) {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Strategy 7: Check filename pattern
|
|
147
|
+
if (filePath.match(/\.dto\.(ts|js)$/i)) {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Strategy 8: Check class name pattern
|
|
152
|
+
const className = classDecl.getName?.() || '';
|
|
153
|
+
if (className.match(/(DTO|Dto)$/)) {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
} catch (error) {
|
|
158
|
+
// Ignore errors, continue with fallback checks
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Analyze class structure to detect DTO pattern
|
|
166
|
+
* DTOs typically have:
|
|
167
|
+
* - Mostly public properties (data fields)
|
|
168
|
+
* - Few methods (only simple transformations)
|
|
169
|
+
* - Constructor that accepts plain data object
|
|
170
|
+
* - No injected dependencies (services, repositories)
|
|
171
|
+
*/
|
|
172
|
+
hasDataTransferObjectStructure(classDecl) {
|
|
173
|
+
try {
|
|
174
|
+
const properties = classDecl.getProperties();
|
|
175
|
+
const methods = classDecl.getMethods();
|
|
176
|
+
const constructor = classDecl.getConstructors()[0];
|
|
177
|
+
|
|
178
|
+
// DTO should have at least some properties
|
|
179
|
+
if (properties.length === 0) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Count public properties vs total properties
|
|
184
|
+
const publicProperties = properties.filter(prop => {
|
|
185
|
+
const modifiers = prop.getModifiers().map(m => m.getText());
|
|
186
|
+
return !modifiers.includes('private') && !modifiers.includes('protected');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// DTO pattern: Most properties are public (data fields)
|
|
190
|
+
const publicPropertyRatio = publicProperties.length / properties.length;
|
|
191
|
+
if (publicPropertyRatio < 0.5) {
|
|
192
|
+
return false; // Too many private properties for a DTO
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// DTO pattern: Few or no methods (excluding constructor)
|
|
196
|
+
if (methods.length > 3) {
|
|
197
|
+
return false; // Too many methods for a DTO
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Check constructor parameters
|
|
201
|
+
if (constructor) {
|
|
202
|
+
const params = constructor.getParameters();
|
|
203
|
+
|
|
204
|
+
// DTO pattern: Usually 1-2 parameters (data object, optional config)
|
|
205
|
+
if (params.length > 2) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Check if parameters are dependency injections (services, repositories)
|
|
210
|
+
for (const param of params) {
|
|
211
|
+
const paramType = param.getType().getText();
|
|
212
|
+
// If injecting services/repositories, not a DTO
|
|
213
|
+
if (/(Service|Repository|Controller|Manager|Handler|Provider)/.test(paramType)) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Passed all structural checks - likely a DTO
|
|
220
|
+
return true;
|
|
221
|
+
|
|
222
|
+
} catch (error) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
75
227
|
/**
|
|
76
228
|
* Analyze a constructor for business logic violations
|
|
77
229
|
*/
|
|
@@ -80,10 +232,15 @@ class C017SymbolBasedAnalyzer {
|
|
|
80
232
|
if (!body) return;
|
|
81
233
|
|
|
82
234
|
const statements = body.getStatements();
|
|
235
|
+
const isDTO = this.isDataTransferObject(filePath, constructor);
|
|
236
|
+
|
|
237
|
+
if (verbose && isDTO) {
|
|
238
|
+
console.log(` 📦 [C017] DTO class detected: ${filePath} - allowing private method calls`);
|
|
239
|
+
}
|
|
83
240
|
|
|
84
241
|
for (const statement of statements) {
|
|
85
242
|
// Check for method calls (instance methods)
|
|
86
|
-
if (this.containsMethodCall(statement, verbose)) {
|
|
243
|
+
if (this.containsMethodCall(statement, verbose, isDTO, constructor)) {
|
|
87
244
|
const { line, column } = this.getStatementPosition(statement);
|
|
88
245
|
|
|
89
246
|
violations.push({
|
|
@@ -206,10 +363,46 @@ class C017SymbolBasedAnalyzer {
|
|
|
206
363
|
}
|
|
207
364
|
}
|
|
208
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Check if a method is a private method in the class
|
|
368
|
+
*/
|
|
369
|
+
isPrivateMethod(methodName, constructor) {
|
|
370
|
+
try {
|
|
371
|
+
const classDecl = constructor.getParent();
|
|
372
|
+
if (!classDecl) return false;
|
|
373
|
+
|
|
374
|
+
// Get all methods in the class
|
|
375
|
+
const methods = classDecl.getMethods();
|
|
376
|
+
|
|
377
|
+
for (const method of methods) {
|
|
378
|
+
const name = method.getName();
|
|
379
|
+
if (name === methodName) {
|
|
380
|
+
// Check if method has private modifier
|
|
381
|
+
const modifiers = method.getModifiers();
|
|
382
|
+
for (const modifier of modifiers) {
|
|
383
|
+
if (modifier.getText() === 'private') {
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
// In TypeScript, methods starting with # are also private
|
|
388
|
+
if (name.startsWith('#')) {
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
} catch (error) {
|
|
395
|
+
// Ignore errors, default to not private
|
|
396
|
+
}
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
|
|
209
400
|
/**
|
|
210
401
|
* Check if statement contains instance method calls
|
|
402
|
+
* @param {boolean} isDTO - If true and method is private, allow the call (DTO pattern)
|
|
403
|
+
* @param {object} constructor - Constructor node to check method visibility
|
|
211
404
|
*/
|
|
212
|
-
containsMethodCall(statement, verbose = false) {
|
|
405
|
+
containsMethodCall(statement, verbose = false, isDTO = false, constructor = null) {
|
|
213
406
|
// Get all call expressions in the statement
|
|
214
407
|
const callExpressions = statement.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
215
408
|
|
|
@@ -234,6 +427,17 @@ class C017SymbolBasedAnalyzer {
|
|
|
234
427
|
if (!safePatterns.includes(methodName)) {
|
|
235
428
|
// Check if it's a config service or environment variable access
|
|
236
429
|
if (!this.isSafeConfigAccess(callExpr)) {
|
|
430
|
+
// DTO Exception: Allow private method calls in DTOs for data transformation
|
|
431
|
+
if (isDTO && constructor) {
|
|
432
|
+
const isPrivate = this.isPrivateMethod(methodName, constructor);
|
|
433
|
+
if (isPrivate) {
|
|
434
|
+
if (verbose) {
|
|
435
|
+
console.log(` ✅ [C017] DTO private method allowed: ${callText}`);
|
|
436
|
+
}
|
|
437
|
+
continue; // Skip this violation for DTO private methods
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
237
441
|
if (verbose) {
|
|
238
442
|
console.log(` 🔧 Method call detected: ${callText}`);
|
|
239
443
|
}
|