Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 12 additions & 0 deletions internal/codeguard/checks/quality/quality_ai.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,19 @@ func typeScriptAIQualityFindings(ctx typeScriptScanContext) []core.Finding {
findings = append(findings, warnFinding(ctx.env, "quality.ai.swallowed-error", ctx.file, line, 1,
support.ScriptLabelForPath(ctx.file)+" catch block swallows the error without handling it"))
}
inJSDoc := false
for idx, line := range strings.Split(ctx.source, "\n") {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "/**") {
inJSDoc = !strings.Contains(trimmed, "*/")
continue
}
if inJSDoc {
if strings.Contains(trimmed, "*/") {
inJSDoc = false
}
continue
}
text, ok := extractScriptCommentText(line)
if !ok || !isNarrativeComment(text) {
continue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ var (
scriptTerminatorPattern = regexp.MustCompile(`^(?:return\b[^;{}]*;|throw\b[^;{}]*;|break\s*;|continue\s*;|return;?$|break$|continue$)`)
scriptBlockResumePattern = regexp.MustCompile(`^(?:\}|case\b|default\s*:|else\b|catch\b|finally\b)`)
scriptLocalFunctionPattern = regexp.MustCompile(`(?m)^[ \t]*(?:async[ \t]+)?function[ \t]+([A-Za-z_$][\w$]*)[ \t]*\(`)
scriptLocalConstPattern = regexp.MustCompile(`(?m)^[ \t]*(?:const|let)[ \t]+([A-Za-z_$][\w$]*)[ \t]*(?::[^=\n]+)?=[ \t]*(?:async[ \t]+)?(?:\([^\n]*\)|[A-Za-z_$][\w$]*)[ \t]*=>`)
scriptControlHeaderPattern = regexp.MustCompile(`^(?:if|while|for|with)\b.*\)[ \t]*$`)
)

// unreachableStatementFinding builds the shared dead-code finding emitted when
Expand All @@ -29,6 +31,7 @@ func scriptUnreachableFindings(env support.Context, file string, source string)
sanitized := sanitizeScriptSource(source)
depth := 0
pendingDepth := -1
previousWasUnbracedControlHeader := false
for idx, line := range strings.Split(sanitized, "\n") {
trimmed := strings.TrimSpace(line)
if trimmed == "" {
Expand All @@ -42,9 +45,10 @@ func scriptUnreachableFindings(env support.Context, file string, source string)
}
pendingDepth = -1
}
if scriptTerminatorPattern.MatchString(trimmed) && balancedParens(trimmed) {
if scriptTerminatorPattern.MatchString(trimmed) && balancedParens(trimmed) && !previousWasUnbracedControlHeader {
pendingDepth = depth
}
previousWasUnbracedControlHeader = scriptControlHeaderPattern.MatchString(trimmed) && !strings.Contains(trimmed, "{")
}
return findings
}
Expand All @@ -58,14 +62,14 @@ func balancedParens(line string) bool {
func scriptUnusedFunctionFindings(env support.Context, file string, source string) []core.Finding {
sanitized := sanitizeScriptSource(source)
findings := make([]core.Finding, 0)
for _, match := range scriptLocalFunctionPattern.FindAllStringSubmatchIndex(sanitized, -1) {
for _, match := range scriptLocalDeclarationMatches(sanitized) {
name := sanitized[match[2]:match[3]]
lineStart := strings.LastIndexByte(sanitized[:match[0]], '\n') + 1
declLine := sanitized[lineStart:lineEnd(sanitized, match[0])]
if strings.Contains(declLine, "export") {
continue
}
if countWordOccurrences(sanitized, name) > 1 {
if scriptLocalDeclarationIsReferenced(sanitized, name) {
continue
}
line := 1 + strings.Count(sanitized[:match[2]], "\n")
Expand All @@ -75,6 +79,18 @@ func scriptUnusedFunctionFindings(env support.Context, file string, source strin
return findings
}

func scriptLocalDeclarationIsReferenced(source string, name string) bool {
// JSX identifiers are references, not opaque markup. Check them directly
// before falling back to ordinary expression references.
jsxReference := regexp.MustCompile(`<\s*` + regexp.QuoteMeta(name) + `(?:\s|/|>)`)
return jsxReference.MatchString(source) || countWordOccurrences(source, name) > 1
}

func scriptLocalDeclarationMatches(source string) [][]int {
matches := append(scriptLocalFunctionPattern.FindAllStringSubmatchIndex(source, -1), scriptLocalConstPattern.FindAllStringSubmatchIndex(source, -1)...)
return matches
}

func lineEnd(source string, from int) int {
if idx := strings.IndexByte(source[from:], '\n'); idx >= 0 {
return from + idx
Expand Down
11 changes: 10 additions & 1 deletion internal/codeguard/checks/quality/quality_ai_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
var (
aiNarrativeCommentPattern = regexp.MustCompile(`(?i)^(initialize|create|set|get|call|return|check|convert|update|build|iterate|loop|run|assign|store)\b`)
aiRationalePattern = regexp.MustCompile(`(?i)\b(because|so that|why|ensure|ensures|avoid|must|needed|required|reason|safely|in order to)\b`)
aiRouteCommentPattern = regexp.MustCompile(`^(?:GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s+/`)
aiEmptyCatchPattern = regexp.MustCompile(`(?s)\bcatch\s*(?:\([^)]*\))?\s*\{\s*(?:(?://[^\n]*\n)|(?:/\*.*?\*/\s*))*\}`)
aiPythonPassExceptPattern = regexp.MustCompile(`(?m)^\s*except(?:\s+[^\n:]+)?\s*:\s*(?:#.*)?\n\s*(pass|\.\.\.)\b`)
)
Expand All @@ -20,13 +21,21 @@ func aiCheckEnabled(flag *bool) bool {

func isNarrativeComment(text string) bool {
trimmed := strings.TrimSpace(text)
if trimmed == "" || aiRationalePattern.MatchString(trimmed) || !aiNarrativeCommentPattern.MatchString(trimmed) {
if trimmed == "" || aiRationalePattern.MatchString(trimmed) || isCommentInstructionOrHeader(trimmed) || !aiNarrativeCommentPattern.MatchString(trimmed) {
return false
}
words := strings.Fields(trimmed)
return len(words) >= 2 && len(words) <= 10
}

func isCommentInstructionOrHeader(text string) bool {
lower := strings.ToLower(text)
return strings.HasPrefix(lower, "run:") ||
strings.HasPrefix(lower, "usage:") ||
strings.HasPrefix(lower, "example:") ||
aiRouteCommentPattern.MatchString(text)
}

func regexLineMatches(pattern *regexp.Regexp, source string) []int {
indices := pattern.FindAllStringIndex(source, -1)
lines := make([]int, 0, len(indices))
Expand Down
63 changes: 63 additions & 0 deletions tests/checks/quality_ai_dead_code_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,69 @@ function helper(): number {
assertFindingRuleAbsent(t, report, "Code Quality", "quality.ai.dead-code")
}

func TestQualityCheckAllowsJSXAndExpressionReferencesToLocalHelpers(t *testing.T) {
dir := t.TempDir()
writeFile(t, filepath.Join(dir, "page.tsx"), `function StatusBadge({ status }: { status: string }) {
return <span>{status}</span>;
}

const Row = ({ children }: { children: unknown }) => <div>{children}</div>;

function fmtDate(value: string): string {
return value;
}

export function Page({ status, nextReviewDate }: { status: string; nextReviewDate: string }) {
return <Row><StatusBadge status={status} />{fmtDate(nextReviewDate)}</Row>;
}
`)

cfg := qualityAITestConfig(dir, "quality-ai-tsx-local-references")
cfg.Targets[0].Language = "typescript"
report, err := codeguard.Run(context.Background(), cfg)
if err != nil {
t.Fatalf("run: %v", err)
}

assertFindingRuleAbsent(t, report, "Code Quality", "quality.ai.dead-code")
}

func TestQualityCheckAllowsStatementAfterUnbracedConditionalReturn(t *testing.T) {
dir := t.TempDir()
writeFile(t, filepath.Join(dir, "handler.ts"), `export function run(done: boolean): string {
if (done)
return "complete";
return "pending";
}
`)

cfg := qualityAITestConfig(dir, "quality-ai-ts-unbraced-return")
cfg.Targets[0].Language = "typescript"
report, err := codeguard.Run(context.Background(), cfg)
if err != nil {
t.Fatalf("run: %v", err)
}

assertFindingRuleAbsent(t, report, "Code Quality", "quality.ai.dead-code")
}

func TestQualityCheckWarnsForUnusedLocalTypeScriptArrowFunction(t *testing.T) {
dir := t.TempDir()
writeFile(t, filepath.Join(dir, "handler.ts"), `export const run = () => "ok";

const orphanHelper = () => "unused";
`)

cfg := qualityAITestConfig(dir, "quality-ai-ts-unused-arrow")
cfg.Targets[0].Language = "typescript"
report, err := codeguard.Run(context.Background(), cfg)
if err != nil {
t.Fatalf("run: %v", err)
}

assertFindingRulePresent(t, report, "Code Quality", "quality.ai.dead-code")
}

func TestQualityCheckHonorsDeadCodeToggle(t *testing.T) {
dir := t.TempDir()
writeFile(t, filepath.Join(dir, "unreachable.go"), `package sample
Expand Down
20 changes: 20 additions & 0 deletions tests/checks/quality_ai_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,26 @@ func buildClient() {}
assertFindingConfidence(t, report, "Code Quality", "quality.ai.narrative-comment", "low")
}

func TestQualityCheckAllowsUsefulTypeScriptComments(t *testing.T) {
dir := t.TempDir()
writeFile(t, filepath.Join(dir, "route.ts"), `/** GET /api/files/[versionId]/download — stream a single FileVersion. */
// Run: pnpm --filter @legal-nest/db exec tsx prisma/seed.ts
// Autoscroll on new turn in a useEffect
// Dispatch on entity type. Prisma accepts string indexing, but TS does not model it.
// Higher-is-worse fields default to desc; priorities and labels default to asc.
export function route() {}
`)

cfg := qualityAITestConfig(dir, "quality-ai-useful-comments")
cfg.Targets[0].Language = "typescript"
report, err := codeguard.Run(context.Background(), cfg)
if err != nil {
t.Fatalf("run: %v", err)
}

assertFindingRuleAbsent(t, report, "Code Quality", "quality.ai.narrative-comment")
}

func TestQualityCheckWarnsForEmptyCatchInTypeScript(t *testing.T) {
dir := t.TempDir()
writeFile(t, filepath.Join(dir, "handler.ts"), `export function run() {
Expand Down
Loading