mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-22 14:24:13 +08:00
feat: improve autofix suggestion detection for PR comments
Add detection logic to recognize actionable suggestions in PR comments, even when they come from bot accounts like claude[bot]. This addresses the issue where the autofix workflow incorrectly classified clear bug fix suggestions as not actionable. New features: - detectActionableSuggestion(): Analyzes comment body for actionable patterns - Detects GitHub inline committable suggestions (```suggestion blocks) - Detects clear bug fix language patterns (e.g., "should be", "change to", "replace with") - Detects code alternatives and fix recommendations - Returns confidence level (high/medium/low) and reason for the determination - checkContainsActionableSuggestion(): Context-aware wrapper for GitHub events - checkContainsTriggerOrActionableSuggestion(): Combined trigger and suggestion check - checkIsActionableForAutofix(): Convenience function for autofix workflows - extractSuggestionCode(): Extracts code from GitHub suggestion blocks This enables workflows to automatically apply suggestions from code review comments, improving the developer experience for PR reviews.
This commit is contained in:
@@ -10,6 +10,11 @@ import {
|
|||||||
isPullRequestReviewCommentEvent,
|
isPullRequestReviewCommentEvent,
|
||||||
} from "../context";
|
} from "../context";
|
||||||
import type { ParsedGitHubContext } from "../context";
|
import type { ParsedGitHubContext } from "../context";
|
||||||
|
import {
|
||||||
|
detectActionableSuggestion,
|
||||||
|
isCommentActionableForAutofix,
|
||||||
|
type ActionableSuggestionResult,
|
||||||
|
} from "../../utils/detect-actionable-suggestion";
|
||||||
|
|
||||||
export function checkContainsTrigger(context: ParsedGitHubContext): boolean {
|
export function checkContainsTrigger(context: ParsedGitHubContext): boolean {
|
||||||
const {
|
const {
|
||||||
@@ -146,3 +151,89 @@ export async function checkTriggerAction(context: ParsedGitHubContext) {
|
|||||||
core.setOutput("contains_trigger", containsTrigger.toString());
|
core.setOutput("contains_trigger", containsTrigger.toString());
|
||||||
return containsTrigger;
|
return containsTrigger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the context contains an actionable suggestion that can be automatically fixed.
|
||||||
|
* This is useful for autofix workflows that want to respond to code review suggestions,
|
||||||
|
* even when they come from bot accounts like claude[bot].
|
||||||
|
*
|
||||||
|
* @param context - The parsed GitHub context
|
||||||
|
* @returns Detection result with confidence level and reason
|
||||||
|
*/
|
||||||
|
export function checkContainsActionableSuggestion(
|
||||||
|
context: ParsedGitHubContext,
|
||||||
|
): ActionableSuggestionResult {
|
||||||
|
// Extract comment body based on event type
|
||||||
|
let commentBody: string | undefined;
|
||||||
|
|
||||||
|
if (isPullRequestReviewCommentEvent(context)) {
|
||||||
|
commentBody = context.payload.comment.body;
|
||||||
|
} else if (isIssueCommentEvent(context)) {
|
||||||
|
commentBody = context.payload.comment.body;
|
||||||
|
} else if (isPullRequestReviewEvent(context)) {
|
||||||
|
commentBody = context.payload.review.body ?? undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return detectActionableSuggestion(commentBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enhanced trigger check that also considers actionable suggestions.
|
||||||
|
* This function first checks for the standard trigger phrase, and if not found,
|
||||||
|
* optionally checks for actionable suggestions when `checkSuggestions` is true.
|
||||||
|
*
|
||||||
|
* @param context - The parsed GitHub context
|
||||||
|
* @param checkSuggestions - Whether to also check for actionable suggestions (default: false)
|
||||||
|
* @returns Whether the action should be triggered
|
||||||
|
*/
|
||||||
|
export function checkContainsTriggerOrActionableSuggestion(
|
||||||
|
context: ParsedGitHubContext,
|
||||||
|
checkSuggestions: boolean = false,
|
||||||
|
): boolean {
|
||||||
|
// First, check for standard trigger
|
||||||
|
if (checkContainsTrigger(context)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If checkSuggestions is enabled, also check for actionable suggestions
|
||||||
|
if (checkSuggestions) {
|
||||||
|
const suggestionResult = checkContainsActionableSuggestion(context);
|
||||||
|
if (suggestionResult.isActionable) {
|
||||||
|
console.log(
|
||||||
|
`Comment contains actionable suggestion: ${suggestionResult.reason} (confidence: ${suggestionResult.confidence})`,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a PR comment is actionable for autofix purposes.
|
||||||
|
* This is a convenience function for workflows that want to automatically
|
||||||
|
* apply suggestions from code review comments.
|
||||||
|
*
|
||||||
|
* @param context - The parsed GitHub context
|
||||||
|
* @returns Whether the comment should be treated as actionable for autofix
|
||||||
|
*/
|
||||||
|
export function checkIsActionableForAutofix(
|
||||||
|
context: ParsedGitHubContext,
|
||||||
|
): boolean {
|
||||||
|
// Only applicable to PR review comment events
|
||||||
|
if (!isPullRequestReviewCommentEvent(context)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commentBody = context.payload.comment.body;
|
||||||
|
const authorUsername = context.payload.comment.user?.login;
|
||||||
|
|
||||||
|
return isCommentActionableForAutofix(commentBody, authorUsername);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-export the types and functions from the utility module for convenience
|
||||||
|
export {
|
||||||
|
detectActionableSuggestion,
|
||||||
|
isCommentActionableForAutofix,
|
||||||
|
type ActionableSuggestionResult,
|
||||||
|
} from "../../utils/detect-actionable-suggestion";
|
||||||
|
|||||||
213
src/utils/detect-actionable-suggestion.ts
Normal file
213
src/utils/detect-actionable-suggestion.ts
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects if a PR comment contains actionable suggestions that can be automatically fixed.
|
||||||
|
*
|
||||||
|
* This module identifies:
|
||||||
|
* 1. GitHub inline committable suggestions (```suggestion blocks)
|
||||||
|
* 2. Clear bug fix suggestions with specific patterns
|
||||||
|
* 3. Code fix recommendations with explicit changes
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patterns that indicate a comment contains a GitHub inline committable suggestion.
|
||||||
|
* These are code blocks that GitHub renders with a "Commit suggestion" button.
|
||||||
|
*/
|
||||||
|
const COMMITTABLE_SUGGESTION_PATTERN = /```suggestion\b[\s\S]*?```/i;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patterns that indicate a clear, actionable bug fix suggestion.
|
||||||
|
* These phrases typically precede concrete fix recommendations.
|
||||||
|
*/
|
||||||
|
const BUG_FIX_PATTERNS = [
|
||||||
|
// Direct fix suggestions
|
||||||
|
/\bshould\s+(?:be|use|return|change\s+to)\b/i,
|
||||||
|
/\bchange\s+(?:this\s+)?to\b/i,
|
||||||
|
/\breplace\s+(?:this\s+)?with\b/i,
|
||||||
|
/\buse\s+(?:this\s+)?instead\b/i,
|
||||||
|
/\binstead\s+of\s+.*?,?\s*use\b/i,
|
||||||
|
|
||||||
|
// Bug identification with fix
|
||||||
|
/\b(?:bug|issue|error|problem):\s*.*(?:fix|change|update|replace)/i,
|
||||||
|
/\bfix(?:ed)?\s+by\s+(?:chang|replac|updat)/i,
|
||||||
|
/\bto\s+fix\s+(?:this|the)\b/i,
|
||||||
|
|
||||||
|
// Explicit code changes
|
||||||
|
/\bthe\s+(?:correct|proper|right)\s+(?:code|syntax|value|approach)\s+(?:is|would\s+be)\b/i,
|
||||||
|
/\bshould\s+(?:read|look\s+like)\b/i,
|
||||||
|
|
||||||
|
// Missing/wrong patterns
|
||||||
|
/\bmissing\s+(?:a\s+)?(?:semicolon|bracket|parenthesis|quote|import|return|await)\b/i,
|
||||||
|
/\bextra\s+(?:semicolon|bracket|parenthesis|quote)\b/i,
|
||||||
|
/\bwrong\s+(?:type|value|variable|import|parameter)\b/i,
|
||||||
|
/\btypo\s+(?:in|here)\b/i,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patterns that suggest code alternatives (less strong than direct fixes but still actionable).
|
||||||
|
*/
|
||||||
|
const CODE_ALTERNATIVE_PATTERNS = [
|
||||||
|
/```[\w]*\n[\s\S]+?\n```/, // Any code block (might contain the fix)
|
||||||
|
/\b(?:try|consider)\s+(?:using|changing|replacing)\b/i,
|
||||||
|
/\bhere'?s?\s+(?:the|a)\s+(?:fix|solution|correction)\b/i,
|
||||||
|
/\b(?:correct|fixed|updated)\s+(?:version|code|implementation)\b/i,
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface ActionableSuggestionResult {
|
||||||
|
/** Whether the comment contains an actionable suggestion */
|
||||||
|
isActionable: boolean;
|
||||||
|
/** Whether the comment contains a GitHub inline committable suggestion */
|
||||||
|
hasCommittableSuggestion: boolean;
|
||||||
|
/** Whether the comment contains clear bug fix language */
|
||||||
|
hasBugFixSuggestion: boolean;
|
||||||
|
/** Whether the comment contains code alternatives */
|
||||||
|
hasCodeAlternative: boolean;
|
||||||
|
/** Confidence level: 'high', 'medium', or 'low' */
|
||||||
|
confidence: "high" | "medium" | "low";
|
||||||
|
/** Reason for the determination */
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects if a comment contains actionable suggestions that can be automatically fixed.
|
||||||
|
*
|
||||||
|
* @param commentBody - The body of the PR comment to analyze
|
||||||
|
* @returns Object with detection results and confidence level
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const result = detectActionableSuggestion("```suggestion\nfixed code\n```");
|
||||||
|
* // { isActionable: true, hasCommittableSuggestion: true, confidence: 'high', ... }
|
||||||
|
*
|
||||||
|
* const result2 = detectActionableSuggestion("You should use `const` instead of `let` here");
|
||||||
|
* // { isActionable: true, hasBugFixSuggestion: true, confidence: 'medium', ... }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function detectActionableSuggestion(
|
||||||
|
commentBody: string | undefined | null,
|
||||||
|
): ActionableSuggestionResult {
|
||||||
|
if (!commentBody) {
|
||||||
|
return {
|
||||||
|
isActionable: false,
|
||||||
|
hasCommittableSuggestion: false,
|
||||||
|
hasBugFixSuggestion: false,
|
||||||
|
hasCodeAlternative: false,
|
||||||
|
confidence: "low",
|
||||||
|
reason: "Empty or missing comment body",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for GitHub inline committable suggestion (highest confidence)
|
||||||
|
const hasCommittableSuggestion =
|
||||||
|
COMMITTABLE_SUGGESTION_PATTERN.test(commentBody);
|
||||||
|
if (hasCommittableSuggestion) {
|
||||||
|
return {
|
||||||
|
isActionable: true,
|
||||||
|
hasCommittableSuggestion: true,
|
||||||
|
hasBugFixSuggestion: false,
|
||||||
|
hasCodeAlternative: false,
|
||||||
|
confidence: "high",
|
||||||
|
reason: "Contains GitHub inline committable suggestion (```suggestion)",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for clear bug fix patterns (medium-high confidence)
|
||||||
|
const matchedBugFixPattern = BUG_FIX_PATTERNS.find((pattern) =>
|
||||||
|
pattern.test(commentBody),
|
||||||
|
);
|
||||||
|
if (matchedBugFixPattern) {
|
||||||
|
// Higher confidence if also contains a code block
|
||||||
|
const hasCodeBlock = CODE_ALTERNATIVE_PATTERNS[0].test(commentBody);
|
||||||
|
return {
|
||||||
|
isActionable: true,
|
||||||
|
hasCommittableSuggestion: false,
|
||||||
|
hasBugFixSuggestion: true,
|
||||||
|
hasCodeAlternative: hasCodeBlock,
|
||||||
|
confidence: hasCodeBlock ? "high" : "medium",
|
||||||
|
reason: hasCodeBlock
|
||||||
|
? "Contains clear bug fix suggestion with code example"
|
||||||
|
: "Contains clear bug fix suggestion",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for code alternatives (medium confidence)
|
||||||
|
const matchedAlternativePattern = CODE_ALTERNATIVE_PATTERNS.find((pattern) =>
|
||||||
|
pattern.test(commentBody),
|
||||||
|
);
|
||||||
|
if (matchedAlternativePattern) {
|
||||||
|
return {
|
||||||
|
isActionable: true,
|
||||||
|
hasCommittableSuggestion: false,
|
||||||
|
hasBugFixSuggestion: false,
|
||||||
|
hasCodeAlternative: true,
|
||||||
|
confidence: "medium",
|
||||||
|
reason: "Contains code alternative or fix suggestion",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isActionable: false,
|
||||||
|
hasCommittableSuggestion: false,
|
||||||
|
hasBugFixSuggestion: false,
|
||||||
|
hasCodeAlternative: false,
|
||||||
|
confidence: "low",
|
||||||
|
reason: "No actionable suggestion patterns detected",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a comment should be treated as actionable for autofix purposes,
|
||||||
|
* even if it comes from a bot account like claude[bot].
|
||||||
|
*
|
||||||
|
* This is particularly useful for workflows that want to automatically apply
|
||||||
|
* suggestions from code review comments.
|
||||||
|
*
|
||||||
|
* @param commentBody - The body of the PR comment
|
||||||
|
* @param authorUsername - The username of the comment author
|
||||||
|
* @returns Whether the comment should be treated as actionable
|
||||||
|
*/
|
||||||
|
export function isCommentActionableForAutofix(
|
||||||
|
commentBody: string | undefined | null,
|
||||||
|
authorUsername?: string,
|
||||||
|
): boolean {
|
||||||
|
const result = detectActionableSuggestion(commentBody);
|
||||||
|
|
||||||
|
// If it's already clearly actionable (high confidence), return true
|
||||||
|
if (result.confidence === "high") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For medium confidence, be more lenient
|
||||||
|
if (result.confidence === "medium" && result.isActionable) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the suggested code from a GitHub inline committable suggestion block.
|
||||||
|
*
|
||||||
|
* @param commentBody - The body of the PR comment
|
||||||
|
* @returns The suggested code content, or null if no suggestion block found
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const code = extractSuggestionCode("```suggestion\nconst x = 1;\n```");
|
||||||
|
* // "const x = 1;"
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function extractSuggestionCode(
|
||||||
|
commentBody: string | undefined | null,
|
||||||
|
): string | null {
|
||||||
|
if (!commentBody) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = commentBody.match(/```suggestion\b\n?([\s\S]*?)```/i);
|
||||||
|
if (match && match[1] !== undefined) {
|
||||||
|
return match[1].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
309
test/detect-actionable-suggestion.test.ts
Normal file
309
test/detect-actionable-suggestion.test.ts
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
import { describe, expect, it } from "bun:test";
|
||||||
|
import {
|
||||||
|
detectActionableSuggestion,
|
||||||
|
isCommentActionableForAutofix,
|
||||||
|
extractSuggestionCode,
|
||||||
|
} from "../src/utils/detect-actionable-suggestion";
|
||||||
|
|
||||||
|
describe("detectActionableSuggestion", () => {
|
||||||
|
describe("GitHub inline committable suggestions", () => {
|
||||||
|
it("should detect suggestion blocks with high confidence", () => {
|
||||||
|
const comment = `Here's a fix:
|
||||||
|
\`\`\`suggestion
|
||||||
|
const x = 1;
|
||||||
|
\`\`\``;
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
expect(result.hasCommittableSuggestion).toBe(true);
|
||||||
|
expect(result.confidence).toBe("high");
|
||||||
|
expect(result.reason).toContain("committable suggestion");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should detect suggestion blocks with multiple lines", () => {
|
||||||
|
const comment = `\`\`\`suggestion
|
||||||
|
function foo() {
|
||||||
|
return bar();
|
||||||
|
}
|
||||||
|
\`\`\``;
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
expect(result.hasCommittableSuggestion).toBe(true);
|
||||||
|
expect(result.confidence).toBe("high");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should detect suggestion blocks case-insensitively", () => {
|
||||||
|
const comment = `\`\`\`SUGGESTION
|
||||||
|
const x = 1;
|
||||||
|
\`\`\``;
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
expect(result.hasCommittableSuggestion).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not confuse regular code blocks with suggestion blocks", () => {
|
||||||
|
const comment = `\`\`\`javascript
|
||||||
|
const x = 1;
|
||||||
|
\`\`\``;
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.hasCommittableSuggestion).toBe(false);
|
||||||
|
// But it should still detect the code alternative
|
||||||
|
expect(result.hasCodeAlternative).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("bug fix suggestions", () => {
|
||||||
|
it('should detect "should be" patterns', () => {
|
||||||
|
const comment = "This should be `const` instead of `let`";
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
expect(result.hasBugFixSuggestion).toBe(true);
|
||||||
|
expect(result.confidence).toBe("medium");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect "change to" patterns', () => {
|
||||||
|
const comment = "Change this to use async/await";
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
expect(result.hasBugFixSuggestion).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect "replace with" patterns', () => {
|
||||||
|
const comment = "Replace this with Array.from()";
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
expect(result.hasBugFixSuggestion).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect "use instead" patterns', () => {
|
||||||
|
const comment = "Use this instead of the deprecated method";
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
expect(result.hasBugFixSuggestion).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect "instead of X, use Y" patterns', () => {
|
||||||
|
const comment = "Instead of forEach, use map";
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
expect(result.hasBugFixSuggestion).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect "to fix this" patterns', () => {
|
||||||
|
const comment = "To fix this, you need to add the await keyword";
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
expect(result.hasBugFixSuggestion).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect "the correct code is" patterns', () => {
|
||||||
|
const comment = "The correct code would be: return null;";
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
expect(result.hasBugFixSuggestion).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect "missing semicolon" patterns', () => {
|
||||||
|
const comment = "Missing a semicolon at the end";
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
expect(result.hasBugFixSuggestion).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect "typo" patterns', () => {
|
||||||
|
const comment = "Typo here: teh should be the";
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
expect(result.hasBugFixSuggestion).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect "wrong type" patterns', () => {
|
||||||
|
const comment = "Wrong type here, should be string not number";
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
expect(result.hasBugFixSuggestion).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have high confidence when bug fix suggestion includes code block", () => {
|
||||||
|
const comment = `You should use const here:
|
||||||
|
\`\`\`javascript
|
||||||
|
const x = 1;
|
||||||
|
\`\`\``;
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
expect(result.hasBugFixSuggestion).toBe(true);
|
||||||
|
expect(result.hasCodeAlternative).toBe(true);
|
||||||
|
expect(result.confidence).toBe("high");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("code alternatives", () => {
|
||||||
|
it('should detect "try using" patterns', () => {
|
||||||
|
const comment = "Try using Array.map() instead";
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect "here\'s the fix" patterns', () => {
|
||||||
|
const comment = "Here's the fix for this issue";
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should detect code blocks as potential alternatives", () => {
|
||||||
|
const comment = `Try this approach:
|
||||||
|
\`\`\`
|
||||||
|
const result = [];
|
||||||
|
\`\`\``;
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
expect(result.hasCodeAlternative).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("non-actionable comments", () => {
|
||||||
|
it("should not flag general questions", () => {
|
||||||
|
const comment = "Why is this returning undefined?";
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(false);
|
||||||
|
expect(result.confidence).toBe("low");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not flag simple observations", () => {
|
||||||
|
const comment = "This looks interesting";
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not flag approval comments", () => {
|
||||||
|
const comment = "LGTM! :+1:";
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle empty comments", () => {
|
||||||
|
const result = detectActionableSuggestion("");
|
||||||
|
expect(result.isActionable).toBe(false);
|
||||||
|
expect(result.reason).toContain("Empty");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle null comments", () => {
|
||||||
|
const result = detectActionableSuggestion(null);
|
||||||
|
expect(result.isActionable).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle undefined comments", () => {
|
||||||
|
const result = detectActionableSuggestion(undefined);
|
||||||
|
expect(result.isActionable).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("edge cases", () => {
|
||||||
|
it("should handle comments with both suggestion block and bug fix language", () => {
|
||||||
|
const comment = `This should be fixed. Here's the suggestion:
|
||||||
|
\`\`\`suggestion
|
||||||
|
const x = 1;
|
||||||
|
\`\`\``;
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
// Suggestion block takes precedence (high confidence)
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
expect(result.hasCommittableSuggestion).toBe(true);
|
||||||
|
expect(result.confidence).toBe("high");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle very long comments", () => {
|
||||||
|
const longContent = "a".repeat(10000);
|
||||||
|
const comment = `${longContent}
|
||||||
|
\`\`\`suggestion
|
||||||
|
const x = 1;
|
||||||
|
\`\`\``;
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
expect(result.hasCommittableSuggestion).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle comments with special characters", () => {
|
||||||
|
const comment =
|
||||||
|
"You should be using `const` here! @#$%^&* Change this to `let`";
|
||||||
|
const result = detectActionableSuggestion(comment);
|
||||||
|
expect(result.isActionable).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isCommentActionableForAutofix", () => {
|
||||||
|
it("should return true for high confidence suggestions", () => {
|
||||||
|
const comment = `\`\`\`suggestion
|
||||||
|
const x = 1;
|
||||||
|
\`\`\``;
|
||||||
|
expect(isCommentActionableForAutofix(comment)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true for medium confidence suggestions", () => {
|
||||||
|
const comment = "You should use const here instead of let";
|
||||||
|
expect(isCommentActionableForAutofix(comment)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false for non-actionable comments", () => {
|
||||||
|
const comment = "This looks fine to me";
|
||||||
|
expect(isCommentActionableForAutofix(comment)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle bot authors correctly", () => {
|
||||||
|
const comment = `\`\`\`suggestion
|
||||||
|
const x = 1;
|
||||||
|
\`\`\``;
|
||||||
|
// Should still return true even for bot authors
|
||||||
|
expect(isCommentActionableForAutofix(comment, "claude[bot]")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle empty comments", () => {
|
||||||
|
expect(isCommentActionableForAutofix("")).toBe(false);
|
||||||
|
expect(isCommentActionableForAutofix(null)).toBe(false);
|
||||||
|
expect(isCommentActionableForAutofix(undefined)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("extractSuggestionCode", () => {
|
||||||
|
it("should extract code from suggestion block", () => {
|
||||||
|
const comment = `Here's a fix:
|
||||||
|
\`\`\`suggestion
|
||||||
|
const x = 1;
|
||||||
|
\`\`\``;
|
||||||
|
expect(extractSuggestionCode(comment)).toBe("const x = 1;");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should extract multi-line code from suggestion block", () => {
|
||||||
|
const comment = `\`\`\`suggestion
|
||||||
|
function foo() {
|
||||||
|
return bar();
|
||||||
|
}
|
||||||
|
\`\`\``;
|
||||||
|
expect(extractSuggestionCode(comment)).toBe(
|
||||||
|
"function foo() {\n return bar();\n}",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle empty suggestion blocks", () => {
|
||||||
|
const comment = `\`\`\`suggestion
|
||||||
|
\`\`\``;
|
||||||
|
expect(extractSuggestionCode(comment)).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return null for comments without suggestion blocks", () => {
|
||||||
|
const comment = "Just a regular comment";
|
||||||
|
expect(extractSuggestionCode(comment)).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return null for empty comments", () => {
|
||||||
|
expect(extractSuggestionCode("")).toBe(null);
|
||||||
|
expect(extractSuggestionCode(null)).toBe(null);
|
||||||
|
expect(extractSuggestionCode(undefined)).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not extract from regular code blocks", () => {
|
||||||
|
const comment = `\`\`\`javascript
|
||||||
|
const x = 1;
|
||||||
|
\`\`\``;
|
||||||
|
expect(extractSuggestionCode(comment)).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user