@sun-asterisk/sunlint 1.3.1 → 1.3.2
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/CHANGELOG.md +47 -0
- package/CONTRIBUTING.md +210 -1691
- package/config/rule-analysis-strategies.js +17 -1
- package/config/rules/enhanced-rules-registry.json +369 -1135
- package/config/rules/rules-registry-generated.json +1 -1
- package/core/enhanced-rules-registry.js +2 -1
- package/core/semantic-engine.js +15 -3
- package/core/semantic-rule-base.js +4 -2
- package/engines/heuristic-engine.js +65 -4
- package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
- package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
- package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
- package/origin-rules/common-en.md +11 -7
- package/package.json +1 -1
- package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
- package/rules/common/C006_function_naming/analyzer.js +29 -3
- package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
- package/rules/common/C010_limit_block_nesting/config.json +64 -0
- package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
- package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
- package/rules/common/C013_no_dead_code/analyzer.js +75 -177
- package/rules/common/C013_no_dead_code/config.json +61 -0
- package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
- package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
- package/rules/common/C014_dependency_injection/analyzer.js +48 -313
- package/rules/common/C014_dependency_injection/config.json +26 -0
- package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
- package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
- package/rules/common/C018_no_throw_generic_error/config.json +50 -0
- package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
- package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
- package/rules/common/C019_log_level_usage/analyzer.js +110 -317
- package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
- package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
- package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
- package/rules/common/C023_no_duplicate_variable/config.json +50 -0
- package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
- package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
- package/rules/common/C035_error_logging_context/analyzer.js +3 -1
- package/rules/index.js +5 -1
- package/rules/security/S009_no_insecure_encryption/README.md +158 -0
- package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
- package/rules/security/S009_no_insecure_encryption/config.json +55 -0
- package/rules/security/S010_no_insecure_encryption/README.md +224 -0
- package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
- package/rules/security/S010_no_insecure_encryption/config.json +48 -0
- package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
- package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
- package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
- package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
- package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
- package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
- package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
- package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
- package/rules/security/S055_content_type_validation/README.md +176 -0
- package/rules/security/S055_content_type_validation/analyzer.js +312 -0
- package/rules/security/S055_content_type_validation/config.json +48 -0
- package/rules/utils/rule-helpers.js +140 -1
- package/scripts/consolidate-config.js +116 -0
- package/config/rules/S027-categories.json +0 -122
- package/config/rules/rules-registry.json +0 -777
- package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
|
@@ -1,64 +1,362 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* C002_no_duplicate_code -
|
|
2
|
+
* C002_no_duplicate_code - Enhanced Regex-based Rule Analyzer
|
|
3
3
|
* Category: coding
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Detects duplicate code blocks longer than specified threshold (default: 10 lines)
|
|
6
|
+
* Uses regex-based approach with proper comment filtering for multi-language support
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const {
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { CommentDetector } = require('../../utils/rule-helpers');
|
|
12
12
|
|
|
13
13
|
class C002_no_duplicate_codeAnalyzer {
|
|
14
14
|
constructor(config = {}) {
|
|
15
|
-
this.config =
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
this.config = {
|
|
16
|
+
minLines: config.minLines || 5,
|
|
17
|
+
ignoreComments: config.ignoreComments !== false,
|
|
18
|
+
ignoreWhitespace: config.ignoreWhitespace !== false,
|
|
19
|
+
ignoreEmptyLines: config.ignoreEmptyLines !== false,
|
|
20
|
+
similarityThreshold: config.similarityThreshold || 0.80, // 80% similarity
|
|
21
|
+
...config
|
|
22
|
+
};
|
|
23
|
+
this.codeBlocks = new Map();
|
|
24
|
+
this.reportedBlocks = new Set();
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
/**
|
|
21
|
-
* Analyze code
|
|
22
|
-
* @param {
|
|
23
|
-
* @param {string}
|
|
24
|
-
* @param {Object}
|
|
28
|
+
* Analyze files for duplicate code violations (heuristic engine interface)
|
|
29
|
+
* @param {Array} files - Array of file paths
|
|
30
|
+
* @param {string} language - Programming language
|
|
31
|
+
* @param {Object} options - Analysis options
|
|
25
32
|
* @returns {Array} Array of violations
|
|
26
33
|
*/
|
|
27
|
-
analyze(
|
|
34
|
+
analyze(files, language, options = {}) {
|
|
28
35
|
const violations = [];
|
|
29
36
|
|
|
30
|
-
// TODO: Implement heuristic analysis logic
|
|
31
|
-
// This should replicate the ESLint rule behavior using pattern matching
|
|
32
|
-
|
|
33
37
|
try {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
console.log(`[C002 DEBUG] Analyzing ${files.length} files for duplicate code`);
|
|
39
|
+
|
|
40
|
+
// Reset state for new analysis
|
|
41
|
+
this.reset();
|
|
42
|
+
|
|
43
|
+
// Collect all code blocks from all files
|
|
44
|
+
const allCodeBlocks = [];
|
|
45
|
+
|
|
46
|
+
for (const filePath of files) {
|
|
47
|
+
console.log(`[C002 DEBUG] Processing file: ${filePath}`);
|
|
48
|
+
const content = this.readFileContent(filePath);
|
|
49
|
+
if (content) {
|
|
50
|
+
console.log(`[C002 DEBUG] File content length: ${content.length}`);
|
|
51
|
+
const codeBlocks = this.extractCodeBlocks(content, filePath);
|
|
52
|
+
console.log(`[C002 DEBUG] Extracted ${codeBlocks.length} code blocks from ${filePath}`);
|
|
53
|
+
codeBlocks.forEach((block, i) => {
|
|
54
|
+
console.log(`[C002 DEBUG] Block ${i}: ${block.type} at lines ${block.startLine}-${block.endLine} (${block.lineCount} lines)`);
|
|
55
|
+
});
|
|
56
|
+
allCodeBlocks.push(...codeBlocks);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(`[C002 DEBUG] Total code blocks: ${allCodeBlocks.length}`);
|
|
61
|
+
|
|
62
|
+
// Find duplicates across all files
|
|
63
|
+
const duplicates = this.findDuplicates(allCodeBlocks);
|
|
64
|
+
console.log(`[C002 DEBUG] Found ${duplicates.length} duplicate groups`);
|
|
65
|
+
|
|
66
|
+
// Generate violations for each file
|
|
67
|
+
files.forEach(filePath => {
|
|
68
|
+
duplicates.forEach(duplicate => {
|
|
69
|
+
const fileViolations = this.createViolations(duplicate, filePath);
|
|
70
|
+
console.log(`[C002 DEBUG] Created ${fileViolations.length} violations for ${filePath}`);
|
|
71
|
+
violations.push(...fileViolations);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
47
74
|
|
|
48
75
|
} catch (error) {
|
|
49
|
-
console.warn(`Error analyzing
|
|
76
|
+
console.warn(`Error analyzing files with C002:`, error.message, error.stack);
|
|
50
77
|
}
|
|
51
78
|
|
|
79
|
+
console.log(`[C002 DEBUG] Total violations: ${violations.length}`);
|
|
52
80
|
return violations;
|
|
53
81
|
}
|
|
54
82
|
|
|
55
83
|
/**
|
|
56
|
-
*
|
|
57
|
-
* @
|
|
84
|
+
* Read file content safely
|
|
85
|
+
* @param {string} filePath - Path to file
|
|
86
|
+
* @returns {string|null} File content or null if error
|
|
87
|
+
*/
|
|
88
|
+
readFileContent(filePath) {
|
|
89
|
+
try {
|
|
90
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.warn(`C002: Cannot read file ${filePath}:`, error.message);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Extract code blocks from content
|
|
99
|
+
* @param {string} content - File content
|
|
100
|
+
* @param {string} filePath - File path for context
|
|
101
|
+
* @returns {Array} Array of code blocks with metadata
|
|
102
|
+
*/
|
|
103
|
+
extractCodeBlocks(content, filePath) {
|
|
104
|
+
const lines = content.split('\n');
|
|
105
|
+
const blocks = [];
|
|
106
|
+
|
|
107
|
+
// Extract function blocks, class methods, etc.
|
|
108
|
+
const functionPattern = /^\s*(function\s+\w+|const\s+\w+\s*=\s*(async\s+)?\([^)]*\)\s*=>|class\s+\w+|\w+\s*\([^)]*\)\s*:\s*[^{]*\{)/;
|
|
109
|
+
let currentBlock = null;
|
|
110
|
+
let braceLevel = 0;
|
|
111
|
+
|
|
112
|
+
lines.forEach((line, index) => {
|
|
113
|
+
const lineNum = index + 1;
|
|
114
|
+
const trimmedLine = line.trim();
|
|
115
|
+
|
|
116
|
+
// Use CommentDetector to filter out comments
|
|
117
|
+
const filteredLines = CommentDetector.filterCommentLines([line]);
|
|
118
|
+
if (filteredLines[0].isComment) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Skip empty lines if configured
|
|
123
|
+
if (this.config.ignoreEmptyLines && !trimmedLine) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Detect function/method/class start
|
|
128
|
+
if (functionPattern.test(trimmedLine)) {
|
|
129
|
+
currentBlock = {
|
|
130
|
+
startLine: lineNum,
|
|
131
|
+
lines: [line],
|
|
132
|
+
filePath: filePath,
|
|
133
|
+
type: this.detectBlockType(trimmedLine)
|
|
134
|
+
};
|
|
135
|
+
braceLevel = (line.match(/{/g) || []).length - (line.match(/}/g) || []).length;
|
|
136
|
+
} else if (currentBlock) {
|
|
137
|
+
currentBlock.lines.push(line);
|
|
138
|
+
braceLevel += (line.match(/{/g) || []).length - (line.match(/}/g) || []).length;
|
|
139
|
+
|
|
140
|
+
// End of block
|
|
141
|
+
if (braceLevel <= 0) {
|
|
142
|
+
currentBlock.endLine = lineNum;
|
|
143
|
+
currentBlock.lineCount = currentBlock.lines.length;
|
|
144
|
+
|
|
145
|
+
// Only consider blocks that meet minimum line requirement
|
|
146
|
+
if (currentBlock.lineCount >= this.config.minLines) {
|
|
147
|
+
currentBlock.normalizedCode = this.normalizeCode(currentBlock.lines.join('\n'));
|
|
148
|
+
if (currentBlock.normalizedCode.length > 20) { // Skip if too short after normalization
|
|
149
|
+
blocks.push(currentBlock);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
currentBlock = null;
|
|
153
|
+
braceLevel = 0;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return blocks;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Detect the type of code block
|
|
163
|
+
* @param {string} line - First line of the block
|
|
164
|
+
* @returns {string} Block type
|
|
165
|
+
*/
|
|
166
|
+
detectBlockType(line) {
|
|
167
|
+
if (line.includes('function')) return 'function';
|
|
168
|
+
if (line.includes('class')) return 'class';
|
|
169
|
+
if (line.includes('interface')) return 'interface';
|
|
170
|
+
if (line.includes('=>')) return 'arrow-function';
|
|
171
|
+
return 'method';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Normalize code for comparison
|
|
176
|
+
* @param {string} code - Raw code
|
|
177
|
+
* @returns {string} Normalized code
|
|
178
|
+
*/
|
|
179
|
+
normalizeCode(code) {
|
|
180
|
+
let normalized = code;
|
|
181
|
+
|
|
182
|
+
if (this.config.ignoreComments) {
|
|
183
|
+
// Remove single line comments (// comments)
|
|
184
|
+
normalized = normalized.replace(/\/\/.*$/gm, '');
|
|
185
|
+
// Remove multi-line comments (/* comments */)
|
|
186
|
+
normalized = normalized.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
187
|
+
// Remove # comments (for other languages)
|
|
188
|
+
normalized = normalized.replace(/#.*$/gm, '');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (this.config.ignoreWhitespace) {
|
|
192
|
+
// Normalize whitespace
|
|
193
|
+
normalized = normalized
|
|
194
|
+
.replace(/\s+/g, ' ') // Multiple spaces to single space
|
|
195
|
+
.replace(/\s*{\s*/g, '{') // Remove spaces around braces
|
|
196
|
+
.replace(/\s*}\s*/g, '}')
|
|
197
|
+
.replace(/\s*;\s*/g, ';') // Remove spaces around semicolons
|
|
198
|
+
.replace(/\s*,\s*/g, ',') // Remove spaces around commas
|
|
199
|
+
.trim();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (this.config.ignoreEmptyLines) {
|
|
203
|
+
// Remove empty lines
|
|
204
|
+
normalized = normalized
|
|
205
|
+
.split('\n')
|
|
206
|
+
.filter(line => line.trim().length > 0)
|
|
207
|
+
.join('\n');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log(`[C002 DEBUG] Normalized code block:
|
|
211
|
+
${normalized}
|
|
212
|
+
---`);
|
|
213
|
+
|
|
214
|
+
return normalized;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Find duplicate code blocks
|
|
219
|
+
* @param {Array} blocks - Array of code blocks
|
|
220
|
+
* @returns {Array} Array of duplicate groups
|
|
221
|
+
*/
|
|
222
|
+
findDuplicates(blocks) {
|
|
223
|
+
const duplicateGroups = [];
|
|
224
|
+
const processedBlocks = new Set();
|
|
225
|
+
|
|
226
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
227
|
+
if (processedBlocks.has(i)) continue;
|
|
228
|
+
|
|
229
|
+
const currentBlock = blocks[i];
|
|
230
|
+
const duplicates = [currentBlock];
|
|
231
|
+
|
|
232
|
+
for (let j = i + 1; j < blocks.length; j++) {
|
|
233
|
+
if (processedBlocks.has(j)) continue;
|
|
234
|
+
|
|
235
|
+
const otherBlock = blocks[j];
|
|
236
|
+
const similarity = this.calculateSimilarity(
|
|
237
|
+
currentBlock.normalizedCode,
|
|
238
|
+
otherBlock.normalizedCode
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
if (similarity >= this.config.similarityThreshold) {
|
|
242
|
+
duplicates.push(otherBlock);
|
|
243
|
+
processedBlocks.add(j);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (duplicates.length > 1) {
|
|
248
|
+
duplicateGroups.push(duplicates);
|
|
249
|
+
processedBlocks.add(i);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return duplicateGroups;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Calculate similarity between two code strings
|
|
258
|
+
* @param {string} code1 - First code string
|
|
259
|
+
* @param {string} code2 - Second code string
|
|
260
|
+
* @returns {number} Similarity ratio (0-1)
|
|
261
|
+
*/
|
|
262
|
+
calculateSimilarity(code1, code2) {
|
|
263
|
+
if (code1 === code2) return 1.0;
|
|
264
|
+
|
|
265
|
+
// Use Levenshtein distance for similarity calculation
|
|
266
|
+
const longer = code1.length > code2.length ? code1 : code2;
|
|
267
|
+
const shorter = code1.length > code2.length ? code2 : code1;
|
|
268
|
+
|
|
269
|
+
if (longer.length === 0) return 1.0;
|
|
270
|
+
|
|
271
|
+
const distance = this.levenshteinDistance(longer, shorter);
|
|
272
|
+
return (longer.length - distance) / longer.length;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Calculate Levenshtein distance between two strings
|
|
277
|
+
* @param {string} str1 - First string
|
|
278
|
+
* @param {string} str2 - Second string
|
|
279
|
+
* @returns {number} Edit distance
|
|
280
|
+
*/
|
|
281
|
+
levenshteinDistance(str1, str2) {
|
|
282
|
+
const matrix = Array(str2.length + 1).fill().map(() => Array(str1.length + 1).fill(0));
|
|
283
|
+
|
|
284
|
+
for (let i = 0; i <= str1.length; i++) matrix[0][i] = i;
|
|
285
|
+
for (let j = 0; j <= str2.length; j++) matrix[j][0] = j;
|
|
286
|
+
|
|
287
|
+
for (let j = 1; j <= str2.length; j++) {
|
|
288
|
+
for (let i = 1; i <= str1.length; i++) {
|
|
289
|
+
const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
|
|
290
|
+
matrix[j][i] = Math.min(
|
|
291
|
+
matrix[j - 1][i] + 1, // deletion
|
|
292
|
+
matrix[j][i - 1] + 1, // insertion
|
|
293
|
+
matrix[j - 1][i - 1] + cost // substitution
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return matrix[str2.length][str1.length];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Create violation objects for duplicate code
|
|
303
|
+
* @param {Array} duplicateGroup - Group of duplicate blocks
|
|
304
|
+
* @param {string} filePath - Current file path
|
|
305
|
+
* @returns {Array} Array of violation objects
|
|
306
|
+
*/
|
|
307
|
+
createViolations(duplicateGroup, filePath) {
|
|
308
|
+
const violations = [];
|
|
309
|
+
|
|
310
|
+
duplicateGroup.forEach((block, index) => {
|
|
311
|
+
// Skip if not in current file or already reported
|
|
312
|
+
if (block.filePath !== filePath) return;
|
|
313
|
+
|
|
314
|
+
const blockId = `${block.filePath}:${block.startLine}-${block.endLine}`;
|
|
315
|
+
if (this.reportedBlocks.has(blockId)) return;
|
|
316
|
+
|
|
317
|
+
this.reportedBlocks.add(blockId);
|
|
318
|
+
|
|
319
|
+
violations.push({
|
|
320
|
+
ruleId: 'C002',
|
|
321
|
+
severity: 'error',
|
|
322
|
+
message: `Duplicate ${block.type} found (${block.lineCount} lines). Consider extracting into a shared function or module. Found ${duplicateGroup.length} similar blocks.`,
|
|
323
|
+
line: block.startLine,
|
|
324
|
+
column: 1,
|
|
325
|
+
endLine: block.endLine,
|
|
326
|
+
endColumn: 1,
|
|
327
|
+
filePath: filePath, // Add filePath field for engine compatibility
|
|
328
|
+
data: {
|
|
329
|
+
lineCount: block.lineCount,
|
|
330
|
+
blockType: block.type,
|
|
331
|
+
duplicateCount: duplicateGroup.length,
|
|
332
|
+
locations: duplicateGroup.map(b => `${path.basename(b.filePath)}:${b.startLine}-${b.endLine}`)
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
return violations;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Reset analyzer state for new analysis session
|
|
342
|
+
*/
|
|
343
|
+
reset() {
|
|
344
|
+
this.codeBlocks.clear();
|
|
345
|
+
this.reportedBlocks.clear();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Get configuration for this rule
|
|
350
|
+
* @returns {Object} Configuration object
|
|
58
351
|
*/
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
352
|
+
getConfig() {
|
|
353
|
+
return {
|
|
354
|
+
minLines: this.config.minLines,
|
|
355
|
+
ignoreComments: this.config.ignoreComments,
|
|
356
|
+
ignoreWhitespace: this.config.ignoreWhitespace,
|
|
357
|
+
ignoreEmptyLines: this.config.ignoreEmptyLines,
|
|
358
|
+
similarityThreshold: this.config.similarityThreshold
|
|
359
|
+
};
|
|
62
360
|
}
|
|
63
361
|
}
|
|
64
362
|
|