thuban 0.3.1 → 0.3.3
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/README.md +137 -102
- package/cli.js +644 -93
- package/package.json +5 -2
- package/packages/scanner/alert-manager.js +398 -398
- package/packages/scanner/code-scanner.js +758 -713
- package/packages/scanner/copy-paste-detector.js +33 -7
- package/packages/scanner/dependency-graph.js +541 -536
- package/packages/scanner/drift-detector.js +423 -423
- package/packages/scanner/file-collector.js +64 -0
- package/packages/scanner/file-watcher.js +323 -323
- package/packages/scanner/ghost-code-detector.js +80 -5
- package/packages/scanner/hallucination-detector.js +189 -19
- package/packages/scanner/health-checker.js +586 -586
- package/packages/scanner/index.js +100 -92
- package/packages/scanner/license-manager.js +15 -2
- package/packages/scanner/master-health-checker.js +513 -513
- package/packages/scanner/monitor-notifier.js +64 -0
- package/packages/scanner/monitor-service.js +103 -0
- package/packages/scanner/monitor-store.js +117 -0
- package/packages/scanner/pre-commit-gate.js +1 -1
- package/packages/scanner/scan-diff.js +50 -0
- package/packages/scanner/scan-runner.js +172 -0
- package/packages/scanner/secret-scanner.js +612 -0
- package/packages/scanner/secret-scanner.test.js +103 -0
- package/packages/scanner/sentinel-core.js +616 -608
- package/packages/scanner/sentinel-knowledge.js +322 -322
- package/packages/scanner/tech-debt-analyzer.js +46 -28
- package/packages/scanner/widget-generator.js +415 -415
|
@@ -65,12 +65,16 @@ class GhostCodeDetector {
|
|
|
65
65
|
continue;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// Strip comments and strings before counting references to avoid false negatives
|
|
69
|
+
// from functions that appear only in commented-out code
|
|
70
|
+
const strippedContent = this._stripCommentsAndStrings(content);
|
|
71
|
+
|
|
68
72
|
for (const fn of functions) {
|
|
69
73
|
if (fn.name.length < 3) continue; // Skip very short names (likely false positives)
|
|
70
74
|
|
|
71
75
|
// Count how many times this function name appears (excluding its definition)
|
|
72
76
|
const regex = new RegExp(`\\b${this._escapeRegex(fn.name)}\\b`, 'g');
|
|
73
|
-
const matches =
|
|
77
|
+
const matches = strippedContent.match(regex);
|
|
74
78
|
if (matches) {
|
|
75
79
|
const relPath = path.relative(this.rootPath, file);
|
|
76
80
|
// Subtract 1 if this is the file where it's defined (the definition itself)
|
|
@@ -119,7 +123,7 @@ class GhostCodeDetector {
|
|
|
119
123
|
_extractFunction(line, lineIndex, lines) {
|
|
120
124
|
const trimmed = line.trim();
|
|
121
125
|
|
|
122
|
-
// function name(
|
|
126
|
+
// JavaScript/TypeScript: function name(
|
|
123
127
|
let match = trimmed.match(/^(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/);
|
|
124
128
|
if (match) {
|
|
125
129
|
return {
|
|
@@ -130,7 +134,7 @@ class GhostCodeDetector {
|
|
|
130
134
|
};
|
|
131
135
|
}
|
|
132
136
|
|
|
133
|
-
// const name = (async) function
|
|
137
|
+
// JavaScript/TypeScript: const name = (async) function
|
|
134
138
|
match = trimmed.match(/^(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s+)?function/);
|
|
135
139
|
if (match) {
|
|
136
140
|
return {
|
|
@@ -141,7 +145,7 @@ class GhostCodeDetector {
|
|
|
141
145
|
};
|
|
142
146
|
}
|
|
143
147
|
|
|
144
|
-
// const name = (...) =>
|
|
148
|
+
// JavaScript/TypeScript: const name = (...) =>
|
|
145
149
|
match = trimmed.match(/^(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s+)?\(?/);
|
|
146
150
|
if (match && (trimmed.includes('=>') || (lineIndex + 1 < lines.length && lines[lineIndex + 1].includes('=>')))) {
|
|
147
151
|
return {
|
|
@@ -152,7 +156,51 @@ class GhostCodeDetector {
|
|
|
152
156
|
};
|
|
153
157
|
}
|
|
154
158
|
|
|
155
|
-
//
|
|
159
|
+
// Kotlin: fun functionName(
|
|
160
|
+
match = trimmed.match(/^(?:private\s+|public\s+|internal\s+|protected\s+)?(?:suspend\s+)?fun\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*[(<]/);
|
|
161
|
+
if (match) {
|
|
162
|
+
return {
|
|
163
|
+
name: match[1],
|
|
164
|
+
type: 'kotlin-fun',
|
|
165
|
+
isExport: !trimmed.startsWith('private'),
|
|
166
|
+
isLifecycle: this._isLifecycle(match[1]),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Rust: fn function_name(
|
|
171
|
+
match = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?fn\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*[(<]/);
|
|
172
|
+
if (match) {
|
|
173
|
+
return {
|
|
174
|
+
name: match[1],
|
|
175
|
+
type: 'rust-fn',
|
|
176
|
+
isExport: /^pub/.test(trimmed),
|
|
177
|
+
isLifecycle: this._isLifecycle(match[1]),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// PHP: function functionName( or public/private/protected function functionName(
|
|
182
|
+
match = trimmed.match(/^(?:(?:public|private|protected|static)\s+)*function\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/);
|
|
183
|
+
if (match) {
|
|
184
|
+
return {
|
|
185
|
+
name: match[1],
|
|
186
|
+
type: 'php-function',
|
|
187
|
+
isExport: !/private/.test(trimmed),
|
|
188
|
+
isLifecycle: this._isLifecycle(match[1]),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Ruby: def method_name or def self.method_name
|
|
193
|
+
match = trimmed.match(/^def\s+(?:self\.)?([a-zA-Z_][a-zA-Z0-9_?!]*)\s*[(\s]?/);
|
|
194
|
+
if (match) {
|
|
195
|
+
return {
|
|
196
|
+
name: match[1],
|
|
197
|
+
type: 'ruby-def',
|
|
198
|
+
isExport: !match[1].startsWith('_'),
|
|
199
|
+
isLifecycle: this._isLifecycle(match[1]),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Class method: name(
|
|
156
204
|
match = trimmed.match(/^(?:async\s+)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\([^)]*\)\s*{/);
|
|
157
205
|
if (match && !['if', 'for', 'while', 'switch', 'catch', 'constructor', 'render', 'toString'].includes(match[1])) {
|
|
158
206
|
return {
|
|
@@ -177,6 +225,7 @@ class GhostCodeDetector {
|
|
|
177
225
|
|
|
178
226
|
_isLifecycle(name) {
|
|
179
227
|
const lifecycle = [
|
|
228
|
+
// JS/TS lifecycle
|
|
180
229
|
'constructor', 'render', 'componentDidMount', 'componentWillUnmount',
|
|
181
230
|
'componentDidUpdate', 'shouldComponentUpdate', 'getSnapshotBeforeUpdate',
|
|
182
231
|
'getDerivedStateFromProps', 'useEffect', 'useState', 'useMemo',
|
|
@@ -184,6 +233,15 @@ class GhostCodeDetector {
|
|
|
184
233
|
'setup', 'teardown', 'beforeEach', 'afterEach', 'beforeAll', 'afterAll',
|
|
185
234
|
'init', 'destroy', 'configure', 'bootstrap', 'main',
|
|
186
235
|
'get', 'set', 'post', 'put', 'delete', 'patch', 'handle',
|
|
236
|
+
// Kotlin/Java lifecycle
|
|
237
|
+
'onCreate', 'onStart', 'onResume', 'onPause', 'onStop', 'onDestroy',
|
|
238
|
+
'onCreateView', 'onViewCreated', 'onDestroyView', 'hashCode', 'equals',
|
|
239
|
+
// Rust lifecycle / trait methods
|
|
240
|
+
'new', 'default', 'drop', 'fmt', 'from', 'into', 'clone', 'copy',
|
|
241
|
+
// Ruby lifecycle
|
|
242
|
+
'initialize', 'to_s', 'to_str', 'inspect', 'freeze', 'dup',
|
|
243
|
+
// PHP lifecycle
|
|
244
|
+
'__construct', '__destruct', '__toString', '__get', '__set', '__call',
|
|
187
245
|
];
|
|
188
246
|
return lifecycle.includes(name) || name.startsWith('_') || name.startsWith('on');
|
|
189
247
|
}
|
|
@@ -221,6 +279,23 @@ class GhostCodeDetector {
|
|
|
221
279
|
_escapeRegex(str) {
|
|
222
280
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
223
281
|
}
|
|
282
|
+
|
|
283
|
+
_stripCommentsAndStrings(content) {
|
|
284
|
+
// Replace string literals with placeholder (preserve length approximately)
|
|
285
|
+
let result = content
|
|
286
|
+
// Template literals
|
|
287
|
+
.replace(/`[^`\\]*(?:\\[\s\S][^`\\]*)*`/g, '""')
|
|
288
|
+
// Double-quoted strings
|
|
289
|
+
.replace(/"[^"\\\n]*(?:\\.[^"\\\n]*)*"/g, '""')
|
|
290
|
+
// Single-quoted strings
|
|
291
|
+
.replace(/'[^'\\\n]*(?:\\.[^'\\\n]*)*'/g, "''")
|
|
292
|
+
// Block comments (JS/Java/Go/Rust/PHP/Kotlin)
|
|
293
|
+
.replace(/\/\*[\s\S]*?\*\//g, ' ')
|
|
294
|
+
// Line comments: //, #
|
|
295
|
+
.replace(/\/\/[^\n]*/g, ' ')
|
|
296
|
+
.replace(/#[^\n]*/g, ' ');
|
|
297
|
+
return result;
|
|
298
|
+
}
|
|
224
299
|
}
|
|
225
300
|
|
|
226
301
|
module.exports = GhostCodeDetector;
|
|
@@ -73,9 +73,9 @@ class HallucinationDetector {
|
|
|
73
73
|
this.pythonPhantomAPIs = [
|
|
74
74
|
{ pattern: /os\.path\.exists_sync\s*\(/, suggestion: 'Use os.path.exists() — exists_sync does not exist in Python', name: 'os.path.exists_sync', id: 'PY_HALL001' },
|
|
75
75
|
{ pattern: /json\.tryParse\s*\(/, suggestion: 'Use json.loads() with try/except', name: 'json.tryParse', id: 'PY_HALL002' },
|
|
76
|
-
{ pattern:
|
|
77
|
-
{ pattern:
|
|
78
|
-
{ pattern:
|
|
76
|
+
{ pattern: /\w+\.flatMap\s*\(/, suggestion: 'Python lists have no flatMap — use list comprehension or itertools.chain.from_iterable()', name: 'list.flatMap', id: 'PY_HALL003' },
|
|
77
|
+
{ pattern: /\w+\.merge\s*\((?!.*\bself\b)/, suggestion: 'Use {**dict1, **dict2} or dict1 | dict2 (3.9+)', name: 'dict.merge', id: 'PY_HALL004' },
|
|
78
|
+
{ pattern: /\w+\.format_map\s*\(/, suggestion: 'str.format_map exists but is rarely correct — did you mean .format()?', name: 'string.format_map misuse', id: 'PY_HALL005' },
|
|
79
79
|
{ pattern: /from\s+collections\s+import\s+OrderedDict.*#.*maintain\s+order/i, suggestion: 'Regular dict maintains order since Python 3.7 — OrderedDict is unnecessary', name: 'Unnecessary OrderedDict', id: 'PY_HALL006' },
|
|
80
80
|
{ pattern: /async\s+def\s+\w+.*asyncio\.sleep\s*\(\s*0\s*\)\s*#.*yield/i, suggestion: 'asyncio.sleep(0) to yield is a code smell — review async design', name: 'asyncio.sleep(0) hack', id: 'PY_HALL007' },
|
|
81
81
|
{ pattern: /import\s+tensorflow\.v2/, suggestion: 'tensorflow.v2 is not a real module — use import tensorflow', name: 'tensorflow.v2', id: 'PY_HALL008' },
|
|
@@ -104,13 +104,13 @@ class HallucinationDetector {
|
|
|
104
104
|
{ pattern: /import\s+\*/, id: 'PY_SMELL007', name: 'Wildcard Import', message: 'Wildcard import — pollutes namespace, hides dependencies' },
|
|
105
105
|
];
|
|
106
106
|
|
|
107
|
-
// ─── Go
|
|
108
|
-
this.
|
|
109
|
-
{ pattern: /ioutil\.ReadFile\s*\(/, suggestion: 'ioutil removed in Go 1.16 — use os.ReadFile()', name: 'ioutil.ReadFile', id: '
|
|
110
|
-
{ pattern: /ioutil\.WriteFile\s*\(/, suggestion: 'ioutil removed in Go 1.16 — use os.WriteFile()', name: 'ioutil.WriteFile', id: '
|
|
111
|
-
{ pattern: /ioutil\.TempDir\s*\(/, suggestion: 'ioutil removed in Go 1.16 — use os.MkdirTemp()', name: 'ioutil.TempDir', id: '
|
|
112
|
-
{ pattern: /ioutil\.ReadAll\s*\(/, suggestion: 'ioutil removed in Go 1.16 — use io.ReadAll()', name: 'ioutil.ReadAll', id: '
|
|
113
|
-
{ pattern: /strings\.Title\s*\(/, suggestion: 'strings.Title deprecated in Go 1.18 — use cases.Title from golang.org/x/text', name: 'strings.Title', id: '
|
|
107
|
+
// ─── Go deprecated APIs (formerly labelled phantom — they exist but are removed) ─
|
|
108
|
+
this.goDeprecated = [
|
|
109
|
+
{ pattern: /ioutil\.ReadFile\s*\(/, suggestion: 'ioutil removed in Go 1.16 — use os.ReadFile()', since: 'Go 1.16', name: 'ioutil.ReadFile', id: 'GO_DEPR001' },
|
|
110
|
+
{ pattern: /ioutil\.WriteFile\s*\(/, suggestion: 'ioutil removed in Go 1.16 — use os.WriteFile()', since: 'Go 1.16', name: 'ioutil.WriteFile', id: 'GO_DEPR002' },
|
|
111
|
+
{ pattern: /ioutil\.TempDir\s*\(/, suggestion: 'ioutil removed in Go 1.16 — use os.MkdirTemp()', since: 'Go 1.16', name: 'ioutil.TempDir', id: 'GO_DEPR003' },
|
|
112
|
+
{ pattern: /ioutil\.ReadAll\s*\(/, suggestion: 'ioutil removed in Go 1.16 — use io.ReadAll()', since: 'Go 1.16', name: 'ioutil.ReadAll', id: 'GO_DEPR004' },
|
|
113
|
+
{ pattern: /strings\.Title\s*\(/, suggestion: 'strings.Title deprecated in Go 1.18 — use cases.Title from golang.org/x/text', since: 'Go 1.18', name: 'strings.Title', id: 'GO_DEPR005' },
|
|
114
114
|
];
|
|
115
115
|
|
|
116
116
|
this.goSmells = [
|
|
@@ -120,15 +120,42 @@ class HallucinationDetector {
|
|
|
120
120
|
];
|
|
121
121
|
|
|
122
122
|
// ─── Rust phantom APIs ──────────────────────────────────
|
|
123
|
+
this.rustPhantomAPIs = [
|
|
124
|
+
{ pattern: /std::fs::File::open_async\s*\(/, suggestion: 'Use tokio::fs::File::open() for async file I/O', name: 'File::open_async', id: 'RS_HALL001' },
|
|
125
|
+
{ pattern: /Vec::flatten\s*\(/, suggestion: 'Use .into_iter().flatten() — Vec has no flatten() method', name: 'Vec::flatten', id: 'RS_HALL002' },
|
|
126
|
+
{ pattern: /HashMap::get_or_default\s*\(/, suggestion: 'Use .get().unwrap_or(&default) or entry().or_insert()', name: 'HashMap::get_or_default', id: 'RS_HALL003' },
|
|
127
|
+
{ pattern: /String::from_utf8_lossy_owned\s*\(/, suggestion: 'Use String::from_utf8_lossy().into_owned()', name: 'String::from_utf8_lossy_owned', id: 'RS_HALL004' },
|
|
128
|
+
{ pattern: /\.async_iter\s*\(/, suggestion: 'Use futures::stream::iter() from the futures crate', name: '.async_iter()', id: 'RS_HALL005' },
|
|
129
|
+
{ pattern: /std::net::TcpStream::connect_async\s*\(/, suggestion: 'Use tokio::net::TcpStream::connect()', name: 'TcpStream::connect_async', id: 'RS_HALL006' },
|
|
130
|
+
{ pattern: /std::thread::sleep_async\s*\(/, suggestion: 'Use tokio::time::sleep() for async sleep', name: 'thread::sleep_async', id: 'RS_HALL007' },
|
|
131
|
+
{ pattern: /Vec::remove_all\s*\(/, suggestion: 'Use .retain(|x| condition) or .clear()', name: 'Vec::remove_all', id: 'RS_HALL008' },
|
|
132
|
+
{ pattern: /str::split_whitespace_n\s*\(/, suggestion: 'Use .splitn(n, char::is_whitespace) instead', name: 'str::split_whitespace_n', id: 'RS_HALL009' },
|
|
133
|
+
{ pattern: /\.map_async\s*\(/, suggestion: 'Use futures::future::join_all() or tokio::task::spawn', name: '.map_async()', id: 'RS_HALL010' },
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
this.rustDeprecated = [
|
|
137
|
+
{ pattern: /std::sync::ONCE_INIT/, suggestion: 'std::sync::ONCE_INIT removed — use Once::new()', since: 'Rust 1.38', name: 'ONCE_INIT', id: 'RS_DEPR001' },
|
|
138
|
+
{ pattern: /\bstd::mem::uninitialized\s*\(/, suggestion: 'std::mem::uninitialized() removed — use MaybeUninit::uninit()', since: 'Rust 1.39', name: 'mem::uninitialized', id: 'RS_DEPR002' },
|
|
139
|
+
{ pattern: /\btry!\s*\(/, suggestion: 'try!() macro removed — use the ? operator', since: 'Rust 2018', name: 'try!() macro', id: 'RS_DEPR003' },
|
|
140
|
+
{ pattern: /extern\s+crate\s+std\s*;/, suggestion: 'extern crate std is implicit since Rust 2018 edition', since: 'Rust 2018', name: 'extern crate std', id: 'RS_DEPR004' },
|
|
141
|
+
{ pattern: /extern\s+crate\s+alloc\s*;/, suggestion: 'extern crate alloc is implicit in 2018+ edition', since: 'Rust 2018', name: 'extern crate alloc', id: 'RS_DEPR005' },
|
|
142
|
+
{ pattern: /std::error::Error::cause\s*\(/, suggestion: '.cause() deprecated — use .source() instead', since: 'Rust 1.33', name: 'Error::cause()', id: 'RS_DEPR006' },
|
|
143
|
+
];
|
|
144
|
+
|
|
123
145
|
this.rustSmells = [
|
|
124
146
|
{ pattern: /todo!\s*\(\s*\)/, id: 'RS_SMELL001', name: 'todo! macro', message: 'todo!() macro — will panic at runtime' },
|
|
125
147
|
{ pattern: /unimplemented!\s*\(\s*\)/, id: 'RS_SMELL002', name: 'unimplemented! macro', message: 'unimplemented!() macro — will panic at runtime' },
|
|
126
148
|
{ pattern: /\/\/\s*TODO:?\s*(implement|add|fix|complete|finish)/i, id: 'RS_SMELL003', name: 'Placeholder TODO', message: 'AI-generated placeholder — likely unfinished' },
|
|
127
149
|
{ pattern: /unwrap\(\).*unwrap\(\)/, id: 'RS_SMELL004', name: 'Double Unwrap', message: 'Chained unwrap() — will panic on None/Err' },
|
|
128
150
|
{ pattern: /localhost:\d{4}(?!.*(?:dev|test|local|development))/i, id: 'RS_SMELL005', name: 'Hardcoded Localhost', message: 'Hardcoded localhost URL — will fail in production' },
|
|
151
|
+
{ pattern: /unsafe\s*\{/, id: 'RS_SMELL006', name: 'Unsafe Block', message: 'unsafe block — verify memory safety guarantees' },
|
|
152
|
+
{ pattern: /\.unwrap\(\)\s*;/, id: 'RS_SMELL007', name: 'Bare unwrap()', message: '.unwrap() will panic on None/Err — use ? or match' },
|
|
153
|
+
{ pattern: /println!\s*\(\s*["'].*[Dd]ebug/, id: 'RS_SMELL008', name: 'Debug println!', message: 'Debug println! left in code — use the log crate' },
|
|
154
|
+
{ pattern: /eprintln!\s*\(\s*["'].*[Dd]ebug/, id: 'RS_SMELL009', name: 'Debug eprintln!', message: 'Debug eprintln! left in code — use the log crate' },
|
|
155
|
+
{ pattern: /#\[allow\(dead_code\)\]/, id: 'RS_SMELL010', name: 'Suppressed Dead Code', message: '#[allow(dead_code)] suppresses warnings — remove unused code' },
|
|
129
156
|
];
|
|
130
157
|
|
|
131
|
-
// ─── Java
|
|
158
|
+
// ─── Java smells ─────────────────────────────────────────
|
|
132
159
|
this.javaSmells = [
|
|
133
160
|
{ pattern: /System\.out\.println\s*\(.*debug/i, id: 'JAVA_SMELL001', name: 'Debug Println', message: 'Debug System.out.println — use a logger' },
|
|
134
161
|
{ pattern: /\/\/\s*TODO:?\s*(implement|add|fix|complete|finish)/i, id: 'JAVA_SMELL002', name: 'Placeholder TODO', message: 'AI-generated placeholder — likely unfinished' },
|
|
@@ -137,12 +164,132 @@ class HallucinationDetector {
|
|
|
137
164
|
{ pattern: /localhost:\d{4}(?!.*(?:dev|test|local|development))/i, id: 'JAVA_SMELL005', name: 'Hardcoded Localhost', message: 'Hardcoded localhost URL — will fail in production' },
|
|
138
165
|
];
|
|
139
166
|
|
|
167
|
+
// ─── C# smells (separate from Java) ─────────────────────
|
|
168
|
+
this.csharpSmells = [
|
|
169
|
+
{ pattern: /Console\.WriteLine\s*\(.*[Dd]ebug/i, id: 'CS_SMELL001', name: 'Debug WriteLine', message: 'Debug Console.WriteLine — use ILogger or Debug.Write' },
|
|
170
|
+
{ pattern: /\/\/\s*TODO:?\s*(implement|add|fix|complete|finish)/i, id: 'CS_SMELL002', name: 'Placeholder TODO', message: 'AI-generated placeholder — likely unfinished' },
|
|
171
|
+
{ pattern: /throw\s+new\s+(?:NotImplementedException)\s*\(/, id: 'CS_SMELL003', name: 'Not Implemented', message: 'Stub implementation — will throw at runtime' },
|
|
172
|
+
{ pattern: /catch\s*\(\s*Exception\s+\w+\s*\)\s*\{\s*\}/, id: 'CS_SMELL004', name: 'Empty Catch', message: 'Empty catch block — swallows all exceptions' },
|
|
173
|
+
{ pattern: /localhost:\d{4}(?!.*(?:dev|test|local|development))/i, id: 'CS_SMELL005', name: 'Hardcoded Localhost', message: 'Hardcoded localhost URL — will fail in production' },
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
// ─── Kotlin phantom APIs ──────────────────────────────────
|
|
177
|
+
this.kotlinPhantomAPIs = [
|
|
178
|
+
{ pattern: /\.flatMap\s*\{[^}]*\}(?!.*asSequence)/, suggestion: 'Use .flatMap { } — verify this is not confusing sequence vs list behavior', name: 'flatMap confusion', id: 'KT_HALL001' },
|
|
179
|
+
{ pattern: /listOf\(\)\.stream\s*\(/, suggestion: 'Kotlin lists have no .stream() — use .asSequence() or direct collection ops', name: 'listOf().stream()', id: 'KT_HALL002' },
|
|
180
|
+
{ pattern: /String\.format\s*\(/, suggestion: 'Prefer Kotlin string templates "$variable" over String.format()', name: 'String.format Java-style', id: 'KT_HALL003' },
|
|
181
|
+
{ pattern: /\bObject\s*\(\s*\)/, suggestion: 'Kotlin uses Any() not Object() — or use object keyword for singletons', name: 'Object() Java-style', id: 'KT_HALL004' },
|
|
182
|
+
{ pattern: /\.forEach\s*\(\s*::println\s*\)(?!.*debug)/i, suggestion: 'println is for debug output — use a logger', name: 'println via forEach', id: 'KT_HALL005' },
|
|
183
|
+
{ pattern: /coroutineScope\s*\{\s*launch\s*\{[^}]*\}\s*\}(?!\s*\.join)/, suggestion: 'Unjoined coroutine in coroutineScope may cause issues — ensure structured concurrency', name: 'Unawaited launch', id: 'KT_HALL006' },
|
|
184
|
+
{ pattern: /Dispatchers\.IO\s*\+\s*Dispatchers/, suggestion: 'Combining Dispatchers is not valid — use one dispatcher at a time', name: 'Combined Dispatchers', id: 'KT_HALL007' },
|
|
185
|
+
{ pattern: /\.toList\(\)\.stream\(\)/, suggestion: 'Use Kotlin sequences (.asSequence()) instead of .toList().stream()', name: 'toList().stream()', id: 'KT_HALL008' },
|
|
186
|
+
{ pattern: /companion object.*getInstance/i, suggestion: 'Kotlin idiom for singletons is object keyword, not companion object + getInstance()', name: 'Java-style singleton', id: 'KT_HALL009' },
|
|
187
|
+
{ pattern: /lateinit var.*\?/, suggestion: 'lateinit properties cannot be nullable — remove ? or use by lazy {}', name: 'lateinit nullable', id: 'KT_HALL010' },
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
this.kotlinDeprecated = [
|
|
191
|
+
{ pattern: /\bapply\s+plugin:\s*['"]kotlin-android-extensions['"]/, suggestion: 'kotlin-android-extensions deprecated — use view binding or Jetpack ViewBinding', since: 'Kotlin 1.8', name: 'kotlin-android-extensions', id: 'KT_DEPR001' },
|
|
192
|
+
{ pattern: /\bkotlinx\.android\.synthetic/, suggestion: 'Synthetic imports deprecated — use view binding or findViewById', since: 'Kotlin 1.8', name: 'Synthetic imports', id: 'KT_DEPR002' },
|
|
193
|
+
{ pattern: /\bCoroutineScope\(EmptyCoroutineContext\)/, suggestion: 'EmptyCoroutineContext coroutine scope is error-prone — use viewModelScope or lifecycleScope', since: 'Kotlin Coroutines 1.6', name: 'EmptyCoroutineContext scope', id: 'KT_DEPR003' },
|
|
194
|
+
{ pattern: /\bBuildersKt\.launch\b/, suggestion: 'Internal BuildersKt APIs are deprecated — use coroutineScope { launch {} }', since: 'Kotlin Coroutines 1.5', name: 'BuildersKt.launch', id: 'KT_DEPR004' },
|
|
195
|
+
{ pattern: /\basyncLazy\s*\{/, suggestion: 'asyncLazy {} is not a standard Kotlin API — use lazy {} or async {}', since: 'Kotlin 1.6', name: 'asyncLazy', id: 'KT_DEPR005' },
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
this.kotlinSmells = [
|
|
199
|
+
{ pattern: /\bprintln\s*\(/, id: 'KT_SMELL001', name: 'Debug println', message: 'println() is for debug output — use a proper logger' },
|
|
200
|
+
{ pattern: /\/\/\s*TODO:?\s*(implement|add|fix|complete|finish)/i, id: 'KT_SMELL002', name: 'Placeholder TODO', message: 'AI-generated placeholder — likely unfinished' },
|
|
201
|
+
{ pattern: /TODO\s*\(\s*\)/, id: 'KT_SMELL003', name: 'TODO() stub', message: 'TODO() will throw NotImplementedError at runtime' },
|
|
202
|
+
{ pattern: /!!\s*\./, id: 'KT_SMELL004', name: 'Non-null assertion chain', message: '!! non-null assertion — will throw NullPointerException if null' },
|
|
203
|
+
{ pattern: /\bas\s+\w+\b(?!.*\?)/, suggestion: 'Unsafe cast — prefer safe cast (as? Type) with null check', id: 'KT_SMELL005', name: 'Unsafe cast', message: 'Unsafe cast with as — will throw ClassCastException if wrong type' },
|
|
204
|
+
{ pattern: /localhost:\d{4}(?!.*(?:dev|test|local|development))/i, id: 'KT_SMELL006', name: 'Hardcoded Localhost', message: 'Hardcoded localhost URL — will fail in production' },
|
|
205
|
+
{ pattern: /catch\s*\(\s*e:\s*Exception\s*\)\s*\{\s*\}/, id: 'KT_SMELL007', name: 'Empty Catch', message: 'Empty catch block — swallows all exceptions' },
|
|
206
|
+
{ pattern: /throw\s+NotImplementedError\s*\(/, id: 'KT_SMELL008', name: 'Not Implemented', message: 'Stub implementation — will throw at runtime' },
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
// ─── PHP phantom APIs ─────────────────────────────────────
|
|
210
|
+
this.phpPhantomAPIs = [
|
|
211
|
+
{ pattern: /array_flatten\s*\(/, suggestion: 'PHP has no array_flatten() — use array_merge(...$array) or a recursive function', name: 'array_flatten', id: 'PHP_HALL001' },
|
|
212
|
+
{ pattern: /str_contains_all\s*\(/, suggestion: 'PHP has no str_contains_all() — use multiple str_contains() calls', name: 'str_contains_all', id: 'PHP_HALL002' },
|
|
213
|
+
{ pattern: /array_unique_values\s*\(/, suggestion: 'PHP has no array_unique_values() — use array_values(array_unique())', name: 'array_unique_values', id: 'PHP_HALL003' },
|
|
214
|
+
{ pattern: /\$pdo->fetchAll\s*\(/, suggestion: 'fetchAll() is on PDOStatement not PDO — call $stmt->fetchAll()', name: 'PDO::fetchAll', id: 'PHP_HALL004' },
|
|
215
|
+
{ pattern: /json_decode_safe\s*\(/, suggestion: 'PHP has no json_decode_safe() — wrap json_decode() with json_last_error() check', name: 'json_decode_safe', id: 'PHP_HALL005' },
|
|
216
|
+
{ pattern: /str_replace_all\s*\(/, suggestion: 'PHP has no str_replace_all() — use str_replace() which already replaces all occurrences', name: 'str_replace_all', id: 'PHP_HALL006' },
|
|
217
|
+
{ pattern: /array_map_keys\s*\(/, suggestion: 'PHP has no array_map_keys() — use array_combine(array_map(...), array_keys())', name: 'array_map_keys', id: 'PHP_HALL007' },
|
|
218
|
+
{ pattern: /\$request->getJson\s*\(/, suggestion: 'Not a standard PHP function — use json_decode(file_get_contents("php://input"))', name: '$request->getJson', id: 'PHP_HALL008' },
|
|
219
|
+
{ pattern: /Date::now\s*\(/, suggestion: 'PHP has no Date::now() — use new DateTime() or time()', name: 'Date::now', id: 'PHP_HALL009' },
|
|
220
|
+
{ pattern: /\bawait\s+/, suggestion: 'PHP has no await keyword — use synchronous code or ReactPHP for async', name: 'await keyword', id: 'PHP_HALL010' },
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
this.phpDeprecated = [
|
|
224
|
+
{ pattern: /\bmysql_connect\s*\(/, suggestion: 'mysql_* functions removed in PHP 7 — use PDO or MySQLi', since: 'PHP 7.0', name: 'mysql_connect', id: 'PHP_DEPR001' },
|
|
225
|
+
{ pattern: /\bmysql_query\s*\(/, suggestion: 'mysql_* functions removed in PHP 7 — use PDO or MySQLi', since: 'PHP 7.0', name: 'mysql_query', id: 'PHP_DEPR002' },
|
|
226
|
+
{ pattern: /\bmysql_fetch_array\s*\(/, suggestion: 'mysql_* functions removed in PHP 7 — use PDO or MySQLi', since: 'PHP 7.0', name: 'mysql_fetch_array', id: 'PHP_DEPR003' },
|
|
227
|
+
{ pattern: /\bereg\s*\(/, suggestion: 'ereg() removed in PHP 7 — use preg_match()', since: 'PHP 7.0', name: 'ereg()', id: 'PHP_DEPR004' },
|
|
228
|
+
{ pattern: /\bsplit\s*\(/, suggestion: 'split() removed in PHP 7 — use preg_split() or explode()', since: 'PHP 7.0', name: 'split()', id: 'PHP_DEPR005' },
|
|
229
|
+
{ pattern: /\bcreate_function\s*\(/, suggestion: 'create_function() removed in PHP 8 — use anonymous functions (closures)', since: 'PHP 8.0', name: 'create_function', id: 'PHP_DEPR006' },
|
|
230
|
+
{ pattern: /\bmagic_quotes_gpc\b/, suggestion: 'magic_quotes_gpc removed in PHP 7 — sanitize inputs manually', since: 'PHP 7.0', name: 'magic_quotes_gpc', id: 'PHP_DEPR007' },
|
|
231
|
+
{ pattern: /\beach\s*\(/, suggestion: 'each() deprecated in PHP 7.2, removed in PHP 8 — use foreach', since: 'PHP 8.0', name: 'each()', id: 'PHP_DEPR008' },
|
|
232
|
+
{ pattern: /\bmcrypt_/, suggestion: 'mcrypt removed in PHP 7.2 — use OpenSSL functions', since: 'PHP 7.2', name: 'mcrypt functions', id: 'PHP_DEPR009' },
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
this.phpSmells = [
|
|
236
|
+
{ pattern: /\beval\s*\(/, id: 'PHP_SMELL001', name: 'eval() Usage', message: 'eval() is dangerous — code injection risk' },
|
|
237
|
+
{ pattern: /\bextract\s*\(\s*\$_(?:GET|POST|REQUEST)/, id: 'PHP_SMELL002', name: 'extract() from superglobal', message: 'extract($_GET/_POST) — variable injection vulnerability' },
|
|
238
|
+
{ pattern: /\$\$\w+/, id: 'PHP_SMELL003', name: 'Variable variable', message: 'Variable variables ($$var) — hard to trace and injection risk' },
|
|
239
|
+
{ pattern: /\bshell_exec\s*\(/, id: 'PHP_SMELL004', name: 'shell_exec()', message: 'shell_exec() — remote code execution risk if input unsanitized' },
|
|
240
|
+
{ pattern: /\bsystem\s*\(/, id: 'PHP_SMELL005', name: 'system()', message: 'system() — remote code execution risk if input unsanitized' },
|
|
241
|
+
{ pattern: /\bpassthru\s*\(/, id: 'PHP_SMELL006', name: 'passthru()', message: 'passthru() — remote code execution risk if input unsanitized' },
|
|
242
|
+
{ pattern: /\/\/\s*TODO:?\s*(implement|add|fix|complete|finish)/i, id: 'PHP_SMELL007', name: 'Placeholder TODO', message: 'AI-generated placeholder — likely unfinished' },
|
|
243
|
+
{ pattern: /localhost:\d{4}(?!.*(?:dev|test|local|development))/i, id: 'PHP_SMELL008', name: 'Hardcoded Localhost', message: 'Hardcoded localhost URL — will fail in production' },
|
|
244
|
+
{ pattern: /\$_(?:GET|POST|REQUEST)\[.*\].*SELECT.*\./i, id: 'PHP_SMELL009', name: 'SQL Injection Risk', message: 'User input directly in SQL string — use prepared statements' },
|
|
245
|
+
{ pattern: /\bvar_dump\s*\(/, id: 'PHP_SMELL010', name: 'Debug var_dump', message: 'var_dump() is for debug only — remove before production' },
|
|
246
|
+
];
|
|
247
|
+
|
|
248
|
+
// ─── Ruby phantom APIs ────────────────────────────────────
|
|
249
|
+
this.rubyPhantomAPIs = [
|
|
250
|
+
{ pattern: /\.flatten_map\s*\{/, suggestion: 'Ruby has no .flatten_map — use .flat_map { }', name: '.flatten_map', id: 'RB_HALL001' },
|
|
251
|
+
{ pattern: /Array\.of\s*\(/, suggestion: 'Ruby has no Array.of() — use Array() or []', name: 'Array.of()', id: 'RB_HALL002' },
|
|
252
|
+
{ pattern: /Hash\.from_array\s*\(/, suggestion: 'Ruby has no Hash.from_array() — use array.to_h or Hash[array]', name: 'Hash.from_array', id: 'RB_HALL003' },
|
|
253
|
+
{ pattern: /\.async\s*\{/, suggestion: 'Ruby has no .async {} — use Thread.new or concurrent-ruby gem', name: '.async block', id: 'RB_HALL004' },
|
|
254
|
+
{ pattern: /String\.format\s*\(/, suggestion: 'Ruby has no String.format() — use string interpolation or % operator', name: 'String.format', id: 'RB_HALL005' },
|
|
255
|
+
{ pattern: /\bpromise\s*\{/, suggestion: 'Ruby has no built-in promise {} — use concurrent-ruby gem or Thread', name: 'promise block', id: 'RB_HALL006' },
|
|
256
|
+
{ pattern: /\.try!\s*\(/, suggestion: 'Ruby has no .try!() — use &. (safe navigation) or Rails .try()', name: '.try!()', id: 'RB_HALL007' },
|
|
257
|
+
{ pattern: /JSON\.safe_parse\s*\(/, suggestion: 'Ruby has no JSON.safe_parse — use JSON.parse with rescue', name: 'JSON.safe_parse', id: 'RB_HALL008' },
|
|
258
|
+
{ pattern: /ActiveRecord::Base\.execute\s*\(/, suggestion: 'ActiveRecord::Base has no .execute() — use connection.execute() or where()', name: 'Base.execute', id: 'RB_HALL009' },
|
|
259
|
+
{ pattern: /\.collect_map\s*\{/, suggestion: 'Ruby has no .collect_map — use .map { }.flatten(1) or .flat_map', name: '.collect_map', id: 'RB_HALL010' },
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
this.rubyDeprecated = [
|
|
263
|
+
{ pattern: /\brequire\s+['"]thread['"]/, suggestion: 'require "thread" is deprecated — Thread is now built-in without require', since: 'Ruby 2.0', name: 'require "thread"', id: 'RB_DEPR001' },
|
|
264
|
+
{ pattern: /\bObject#type\b|\.type\s*==/, suggestion: '.type is deprecated — use .class or .is_a?', since: 'Ruby 1.8', name: 'Object#type', id: 'RB_DEPR002' },
|
|
265
|
+
{ pattern: /\bERB::Util\.html_escape\b/, suggestion: 'ERB::Util.html_escape deprecated — use CGI.escapeHTML or h() in views', since: 'Ruby 2.6', name: 'ERB::Util.html_escape', id: 'RB_DEPR003' },
|
|
266
|
+
{ pattern: /\bFile\.exists\?\s*\(/, suggestion: 'File.exists? deprecated — use File.exist? (no s)', since: 'Ruby 2.2', name: 'File.exists?', id: 'RB_DEPR004' },
|
|
267
|
+
{ pattern: /\bDir\.exists\?\s*\(/, suggestion: 'Dir.exists? deprecated — use Dir.exist? (no s)', since: 'Ruby 2.2', name: 'Dir.exists?', id: 'RB_DEPR005' },
|
|
268
|
+
{ pattern: /\bObject#returning\b|\s+returning\s+/, suggestion: 'Object#returning removed from Rails core — use tap { |obj| }', since: 'Rails 3.0', name: 'Object#returning', id: 'RB_DEPR006' },
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
this.rubySmells = [
|
|
272
|
+
{ pattern: /\bputs\s+(?!STDOUT)/, id: 'RB_SMELL001', name: 'Debug puts', message: 'puts is for debug output — use Rails logger or a logging library' },
|
|
273
|
+
{ pattern: /\bp\s+\w/, id: 'RB_SMELL002', name: 'Debug p()', message: 'p() is for debug output — remove before production' },
|
|
274
|
+
{ pattern: /\beval\s*\(/, id: 'RB_SMELL003', name: 'eval() Usage', message: 'eval() is dangerous — code injection risk' },
|
|
275
|
+
{ pattern: /\bsystem\s*\(/, id: 'RB_SMELL004', name: 'system() Shell Call', message: 'system() call — command injection risk if input unsanitized' },
|
|
276
|
+
{ pattern: /`[^`]+`/, id: 'RB_SMELL005', name: 'Backtick Shell Exec', message: 'Backtick shell execution — command injection risk' },
|
|
277
|
+
{ pattern: /\bexec\s*\(/, id: 'RB_SMELL006', name: 'exec() call', message: 'exec() replaces the process — ensure this is intentional' },
|
|
278
|
+
{ pattern: /\brescue\s*$|\brescue\s+Exception\b/, id: 'RB_SMELL007', name: 'Bare rescue', message: 'Bare rescue catches all exceptions including fatal ones — be specific' },
|
|
279
|
+
{ pattern: /["'].*#\{.*sql.*\}.*["']/i, id: 'RB_SMELL008', name: 'SQL String Interpolation', message: 'SQL built with string interpolation — use parameterized queries' },
|
|
280
|
+
{ pattern: /\/\/\s*TODO:?\s*(implement|add|fix|complete|finish)/i, id: 'RB_SMELL009', name: 'Placeholder TODO', message: 'AI-generated placeholder — likely unfinished' },
|
|
281
|
+
{ pattern: /raise\s+NotImplementedError/, id: 'RB_SMELL010', name: 'Not Implemented', message: 'Stub implementation — will raise at runtime' },
|
|
282
|
+
];
|
|
283
|
+
|
|
140
284
|
// Language extensions mapping
|
|
141
285
|
this.jsExtensions = new Set(['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs']);
|
|
142
286
|
this.pyExtensions = new Set(['.py', '.pyw']);
|
|
143
287
|
this.goExtensions = new Set(['.go']);
|
|
144
288
|
this.rustExtensions = new Set(['.rs']);
|
|
145
|
-
this.javaExtensions = new Set(['.java', '.kt'
|
|
289
|
+
this.javaExtensions = new Set(['.java', '.kt']);
|
|
290
|
+
this.csharpExtensions = new Set(['.cs']);
|
|
291
|
+
this.phpExtensions = new Set(['.php']);
|
|
292
|
+
this.rubyExtensions = new Set(['.rb']);
|
|
146
293
|
|
|
147
294
|
// Patterns suggesting AI-generated code with common issues
|
|
148
295
|
this.aiCodeSmells = [
|
|
@@ -160,13 +307,13 @@ class HallucinationDetector {
|
|
|
160
307
|
{ pattern: /new Buffer\s*\(/, suggestion: 'Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe()', since: 'Node 6', name: 'new Buffer()', id: 'DEPR_API001' },
|
|
161
308
|
{ pattern: /require\s*\(\s*['"]sys['"]\s*\)/, suggestion: 'Use require("util") — sys was removed in Node 1.0', since: 'Node 1.0', name: 'require("sys")', id: 'DEPR_API002' },
|
|
162
309
|
{ pattern: /fs\.exists\s*\((?!Sync)/, suggestion: 'Use fs.access() or fs.stat() — fs.exists() is deprecated', since: 'Node 1.0', name: 'fs.exists()', id: 'DEPR_API003' },
|
|
163
|
-
{ pattern: /domain\.create\s*\(/, suggestion: 'Use async_hooks or try/catch — domain module is deprecated', since: 'Node 4', name: 'domain.create()', id: 'DEPR_API004' },
|
|
164
|
-
{ pattern: /require\s*\(\s*['"]punycode['"]\s*\)/, suggestion: 'Use userland punycode package — built-in is deprecated', since: 'Node 7', name: 'require("punycode")', id: 'DEPR_API005' },
|
|
310
|
+
{ pattern: /domain\.create\s*\(/, suggestion: 'Use async_hooks or try/catch — domain module is deprecated', since: 'Node 4', name: 'domain.create()', id: 'DEPR_API004', fixable: false },
|
|
311
|
+
{ pattern: /require\s*\(\s*['"]punycode['"]\s*\)/, suggestion: 'Use userland punycode package — built-in is deprecated', since: 'Node 7', name: 'require("punycode")', id: 'DEPR_API005', fixable: false },
|
|
165
312
|
{ pattern: /url\.parse\s*\(/, suggestion: 'Use new URL() — url.parse() is legacy', since: 'Node 11', name: 'url.parse()', id: 'DEPR_API006' },
|
|
166
|
-
{ pattern: /querystring\.(parse|stringify)\s*\(/, suggestion: 'Use URLSearchParams — querystring is legacy', since: 'Node 14', name: 'querystring', id: 'DEPR_API007' },
|
|
313
|
+
{ pattern: /querystring\.(parse|stringify)\s*\(/, suggestion: 'Use URLSearchParams — querystring is legacy', since: 'Node 14', name: 'querystring', id: 'DEPR_API007', fixable: false },
|
|
167
314
|
{ pattern: /util\.isArray\s*\(/, suggestion: 'Use Array.isArray() — util type checks are deprecated', since: 'Node 4', name: 'util.isArray()', id: 'DEPR_API008' },
|
|
168
315
|
{ pattern: /util\.isFunction\s*\(/, suggestion: 'Use typeof fn === "function"', since: 'Node 4', name: 'util.isFunction()', id: 'DEPR_API009' },
|
|
169
|
-
{ pattern: /util\.pump\s*\(/, suggestion: 'Use stream.pipeline() — util.pump() was removed', since: 'Node 1.0', name: 'util.pump()', id: 'DEPR_API010' },
|
|
316
|
+
{ pattern: /util\.pump\s*\(/, suggestion: 'Use stream.pipeline() — util.pump() was removed', since: 'Node 1.0', name: 'util.pump()', id: 'DEPR_API010', fixable: false },
|
|
170
317
|
];
|
|
171
318
|
}
|
|
172
319
|
|
|
@@ -202,8 +349,11 @@ class HallucinationDetector {
|
|
|
202
349
|
const isGo = this.goExtensions.has(ext);
|
|
203
350
|
const isRust = this.rustExtensions.has(ext);
|
|
204
351
|
const isJava = this.javaExtensions.has(ext);
|
|
352
|
+
const isCSharp = this.csharpExtensions.has(ext);
|
|
353
|
+
const isPHP = this.phpExtensions.has(ext);
|
|
354
|
+
const isRuby = this.rubyExtensions.has(ext);
|
|
205
355
|
|
|
206
|
-
if (!isJS && !isPython && !isGo && !isRust && !isJava) continue;
|
|
356
|
+
if (!isJS && !isPython && !isGo && !isRust && !isJava && !isCSharp && !isPHP && !isRuby) continue;
|
|
207
357
|
|
|
208
358
|
const relPath = path.relative(this.config.rootPath, file);
|
|
209
359
|
|
|
@@ -246,13 +396,33 @@ class HallucinationDetector {
|
|
|
246
396
|
this._checkLanguagePatterns(relPath, content, lines, results, ignoredLines, this.pythonPhantomAPIs, 'phantomAPIs');
|
|
247
397
|
this._checkLanguagePatterns(relPath, content, lines, results, ignoredLines, this.pythonDeprecated, 'deprecatedAPIs');
|
|
248
398
|
this._checkLanguageSmells(relPath, content, lines, results, ignoredLines, this.pythonSmells);
|
|
399
|
+
} else if (isPHP) {
|
|
400
|
+
this._checkLanguagePatterns(relPath, content, lines, results, ignoredLines, this.phpPhantomAPIs, 'phantomAPIs');
|
|
401
|
+
this._checkLanguagePatterns(relPath, content, lines, results, ignoredLines, this.phpDeprecated, 'deprecatedAPIs');
|
|
402
|
+
this._checkLanguageSmells(relPath, content, lines, results, ignoredLines, this.phpSmells);
|
|
403
|
+
} else if (isRuby) {
|
|
404
|
+
this._checkLanguagePatterns(relPath, content, lines, results, ignoredLines, this.rubyPhantomAPIs, 'phantomAPIs');
|
|
405
|
+
this._checkLanguagePatterns(relPath, content, lines, results, ignoredLines, this.rubyDeprecated, 'deprecatedAPIs');
|
|
406
|
+
this._checkLanguageSmells(relPath, content, lines, results, ignoredLines, this.rubySmells);
|
|
249
407
|
} else if (isGo) {
|
|
250
|
-
this._checkLanguagePatterns(relPath, content, lines, results, ignoredLines, this.
|
|
408
|
+
this._checkLanguagePatterns(relPath, content, lines, results, ignoredLines, this.goDeprecated, 'deprecatedAPIs');
|
|
251
409
|
this._checkLanguageSmells(relPath, content, lines, results, ignoredLines, this.goSmells);
|
|
252
410
|
} else if (isRust) {
|
|
411
|
+
this._checkLanguagePatterns(relPath, content, lines, results, ignoredLines, this.rustPhantomAPIs, 'phantomAPIs');
|
|
412
|
+
this._checkLanguagePatterns(relPath, content, lines, results, ignoredLines, this.rustDeprecated, 'deprecatedAPIs');
|
|
253
413
|
this._checkLanguageSmells(relPath, content, lines, results, ignoredLines, this.rustSmells);
|
|
254
414
|
} else if (isJava) {
|
|
255
|
-
|
|
415
|
+
// Java and Kotlin share javaExtensions (.java, .kt)
|
|
416
|
+
const isKotlin = ext === '.kt';
|
|
417
|
+
if (isKotlin) {
|
|
418
|
+
this._checkLanguagePatterns(relPath, content, lines, results, ignoredLines, this.kotlinPhantomAPIs, 'phantomAPIs');
|
|
419
|
+
this._checkLanguagePatterns(relPath, content, lines, results, ignoredLines, this.kotlinDeprecated, 'deprecatedAPIs');
|
|
420
|
+
this._checkLanguageSmells(relPath, content, lines, results, ignoredLines, this.kotlinSmells);
|
|
421
|
+
} else {
|
|
422
|
+
this._checkLanguageSmells(relPath, content, lines, results, ignoredLines, this.javaSmells);
|
|
423
|
+
}
|
|
424
|
+
} else if (isCSharp) {
|
|
425
|
+
this._checkLanguageSmells(relPath, content, lines, results, ignoredLines, this.csharpSmells);
|
|
256
426
|
}
|
|
257
427
|
|
|
258
428
|
} catch (e) { /* skip unreadable */ }
|