Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 38 additions & 8 deletions ai-slop-detector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ class AISlopDetector {
},
{
id: 'missing_error_handling',
pattern: /(fetch|axios|http)\s*\(/g,
pattern: /\b(fetch|axios|http)\s*\(/g,
message: "Potential missing error handling for promise. Consider adding try/catch or .catch().",
severity: 'medium',
description: 'Detects calls that might need error handling',
Expand All @@ -289,7 +289,7 @@ class AISlopDetector {
},
{
id: 'todo_comment',
pattern: /(TODO|FIXME|HACK|XXX|BUG)\b/g,
pattern: /\b(TODO|FIXME|HACK|XXX|BUG)\b/g,
message: "Found TODO/FIXME/HACK comment indicating incomplete implementation.",
severity: 'medium',
description: 'Detects incomplete implementation markers'
Expand Down Expand Up @@ -495,7 +495,8 @@ class AISlopDetector {
'**/lib/**', // Generated library files
'scripts/ai-slop-detector.ts', // Exclude the detector script itself to avoid false positives
'ai-slop-detector.ts', // Also exclude when in root directory
'improved-ai-slop-detector.ts' // Exclude the improved detector script to avoid false positives
'improved-ai-slop-detector.ts', // Exclude the improved detector script to avoid false positives
...this.customIgnorePaths
]
});

Expand Down Expand Up @@ -729,21 +730,36 @@ class AISlopDetector {

// Special handling for missing error handling - look for properly handled fetch calls
if (pattern.id === 'missing_error_handling') {
const fullLine = line.trim();
// Skip matches inside comment lines (single-line, JSDoc, block)
if (fullLine.startsWith('//') || fullLine.startsWith('*') || fullLine.startsWith('/*')) {
continue;
}
// Check if this fetch call is part of a properly handled async function
const isProperlyHandled = this.isFetchCallProperlyHandled(lines, i, match.index);
if (isProperlyHandled) {
continue; // Skip this fetch call as it's properly handled
continue;
}
}

// Special handling for unsafe_double_type_assertion - skip legitimate UI library patterns
if (pattern.id === 'unsafe_double_type_assertion') {
// Check the full line context to identify potentially legitimate patterns
const fullLine = line.trim();
// Skip patterns that are actually safe (as unknown as Type) since we changed the regex
// but double-check to be extra sure
// Skip patterns that are actually safe (as unknown as Type)
if (fullLine.includes('as unknown as')) {
continue; // This is actually safe - skip it
continue;
}
// Skip matches inside comment lines (e.g., "as soon as React")
if (fullLine.startsWith('//') || fullLine.startsWith('*') || fullLine.startsWith('/*')) {
continue;
}
// Skip matches where the first word after "as" is a common English word
// indicating natural language rather than a type assertion
// e.g., "as soon as React hydrates" — "soon" is English, not a type
const firstWord = match[0].match(/^as\s+(\w+)/i)?.[1]?.toLowerCase();
const englishWords = ['soon', 'quick', 'quickly', 'fast', 'smooth', 'long', 'much', 'little', 'well', 'good', 'bad', 'easy', 'hard', 'simple', 'clear', 'many', 'few', 'close', 'far', 'near'];
if (firstWord && englishWords.includes(firstWord)) {
continue;
}
}

Expand All @@ -756,6 +772,20 @@ class AISlopDetector {
continue;
}

// Skip console calls guarded by a conditional on the same line
// e.g., if (isDev) console.log('debug');
if (/^if\s*\(/.test(fullLine)) {
continue;
}

// Skip console calls inside a conditional block opened on a prior line
if (i > 0) {
const prevLine = lines[i - 1].trim();
if (/^if\s*\(/.test(prevLine) && (prevLine.includes('{') || fullLine.startsWith('{') === false)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The previous-line guard condition is effectively always true for most console.* lines because fullLine.startsWith('{') === false usually holds, so any log directly after an if (...) line is skipped even when it is not actually inside a dev guard. Tighten this condition to only skip explicit dev/debug guard lines. [logic error]

Severity Level: Major ⚠️
- ⚠️ Unconditional console logs after if-lines may be missed.
- ⚠️ Slightly reduces accuracy of production_console_log detection.
- ⚠️ Current repo fixtures do not exercise this specific pattern.
Suggested change
if (/^if\s*\(/.test(prevLine) && (prevLine.includes('{') || fullLine.startsWith('{') === false)) {
if (/^if\s*\([^)]*(isDev|process\.env\.DEBUG|NODE_ENV)\b[^)]*\)\s*\{?\s*$/.test(prevLine)) {
Steps of Reproduction ✅
1. Create a new source file in this repo, for example `example-console.ts` in the project
root, with the following code (so it will be scanned by the detector's `targetExtensions`
at `ai-slop-detector.ts:75`):

   ```ts

   if (userLoaded) handleUser(user);

   console.log('User data', user);

This mirrors a realistic production pattern where an unconditional log line follows an
if (...) statement on the preceding line.

  1. Run the CLI by executing node ai-slop-detector.ts from the repo root, which calls
    runIfMain() at ai-slop-detector.ts:1307, constructs new AISlopDetector(rootDir), and
    invokes detect() at ai-slop-detector.ts:443.

  2. Inside detect(), findAllFiles() at ai-slop-detector.ts:473-521 will include
    example-console.ts, and analyzeFile(filePath) at ai-slop-detector.ts:631 will read
    and split the file into lines, then iterate each line and each detectionPatterns entry
    including production_console_log defined at ai-slop-detector.ts:282-288.

  3. When processing the console.log('User data', user); line in example-console.ts, the
    production_console_log regex matches, and the special-handling block at
    ai-slop-detector.ts:765-799 runs: prevLine is the preceding if (userLoaded) handleUser(user); line, so /^if\s*\(/.test(prevLine) is true, and because prevLine
    does not contain { while fullLine.startsWith('{') === false is also true, the
    condition

    if (/^if\s*\(/.test(prevLine) && (prevLine.includes('{') || fullLine.startsWith('{') === false)) { continue; } at ai-slop-detector.ts:783-784 evaluates to true and the
    log is skipped. This demonstrates that any console.* line immediately following an
    if (...) line without a brace is treated as "guarded" and ignored, even though the
    log is not actually inside a conditional block. Note that the current repository's
    fixtures (e.g. tests/fixtures/false-positives.ts:129-145) intentionally mark
    conditionally-guarded logs as non-issues, so this overly-broad guard does not currently
    break any existing test fixture but can cause false negatives for consumer projects
    using this pattern.

</details>
<details>
<summary><b>Prompt for AI Agent 🤖 </b></summary>

```mdx
This is a comment left during a code review.

**Path:** ai-slop-detector.ts
**Line:** 783:783
**Comment:**
	*Logic Error: The previous-line guard condition is effectively always true for most `console.*` lines because `fullLine.startsWith('{') === false` usually holds, so any log directly after an `if (...)` line is skipped even when it is not actually inside a dev guard. Tighten this condition to only skip explicit dev/debug guard lines.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
👍 | 👎

continue;
}
}
Comment on lines +782 to +787
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Conditional guard logic has an inverted condition that may cause false negatives.

The condition fullLine.startsWith('{') === false is checking if the current line does NOT start with {. Combined with prevLine.includes('{'), the logic is:

  • Skip if previous line starts with if ( AND (previous line has { OR current line doesn't start with {)

This effectively skips almost all console calls when the previous line is an if statement, even if the console call is not actually inside the if block. For example:

if (condition) { doSomething(); }
console.log('not guarded'); // Would be incorrectly skipped

The heuristic is intentionally loose to avoid false positives, but it may now cause false negatives (missing real issues).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ai-slop-detector.ts` around lines 781 - 786, The guard that skips console
checks when the previous line is an if-statement is inverted: change the clause
using fullLine.startsWith('{') === false to require the current line to start
with '{' (i.e., use fullLine.startsWith('{')) so the overall condition becomes
(!prevLine || /^if\s*\(/.test(prevLine) && (prevLine.includes('{') ||
fullLine.startsWith('{'))). Update the logic around prevLine and fullLine in
ai-slop-detector.ts (the block where prevLine is set from lines[i - 1] and the
if (/^if\s*\(/...) check occurs) so it only skips when the console is actually
inside a braced if block or the block opener is on the previous line.


// Skip general debugging logs that might be intentional in development
if (fullLine.includes('console.log(') &&
(fullLine.includes('Debug') || fullLine.includes('debug') || fullLine.includes('debug:'))) {
Expand Down
Loading
Loading