
sonarqube-best-practices
by JordiNodeJS
A modern API for The Simpsons built with Next.js 16, React 19, and Neon PostgreSQL.
SKILL.md
name: sonarqube-best-practices description: SonarLint best practices for Next.js 16 applications. Covers pre-merge quality checks, issue severity prioritization, common fixes for TypeScript/Next.js, and when to defer minor issues. Use when performing code quality analysis, fixing SonarLint warnings, or setting up quality gates.
SonarLint Best Practices for Next.js 16
Best practices for using SonarLint to maintain code quality in The Simpsons API (Next.js 16 + TypeScript).
When to Use This Skill
Use this skill when:
- Creating a new PR and need pre-merge quality check
- Fixing code quality issues flagged by SonarLint
- Setting up quality gates for CI/CD
- Understanding which SonarLint issues to fix vs defer
Pre-Merge SonarLint Workflow
1. Analyze Modified Files
# Get all TypeScript files modified in PR
git diff --name-only main...your-branch | grep -E "\.(ts|tsx)$"
# Analyze each file (use VS Code SonarLint extension)
# Or use available tools in your environment
2. Categorize Issues by Severity
| Severity | Action | Timeline |
|---|---|---|
| 🔴 BLOCKER | Must fix | Before merge |
| 🟠 CRITICAL | Must fix | Before merge |
| 🟡 MAJOR | Should fix | Before merge |
| 🔵 MINOR | Can defer | With justification |
| ⚪ INFO | Optional | Per team standards |
3. Common Issues and Fixes
Issue: "Replace Error with TypeError"
When: Type validation errors Fix:
// ❌ Before
if (typeof input !== "number") {
throw new Error("Expected number");
}
// ✅ After
if (typeof input !== "number") {
throw new TypeError("Expected number, got " + typeof input);
}
Exception: Domain validation should use domain exceptions
// ✅ Correct for business rules
if (rating < 1 || rating > 5) {
throw new ValidationException("Rating must be 1-5");
}
Issue: "Avoid using 'any' type"
Production Code:
// ❌ Wrong
function process(data: any): any {
return data;
}
// ✅ Correct - Use generics
function process<T>(data: T): T {
return data;
}
// ✅ Correct - Use unknown when type is truly unknown
function process(data: unknown): ProcessedData {
if (typeof data !== "object") {
throw new TypeError("Expected object");
}
// ... narrow type and process
}
Test Mocks:
// ✅ Acceptable with comment
// @ts-expect-error - Test mock intentionally incomplete for flexibility
const mockRepo: any = { findById: vi.fn() };
// ✅ Better - Use Partial<T>
const mockRepo: Partial<EpisodeRepository> = {
findById: vi.fn().mockResolvedValue(mockEpisode),
};
Issue: "Preserve Exception Types"
Critical Pattern from PR #14:
// ❌ Wrong - Loses type information
catch (error) {
if (error instanceof ValidationException) {
throw new Error(error.message); // Lost field, code, metadata
}
}
// ✅ Correct - Preserves exception type
catch (error) {
if (error instanceof ValidationException || error instanceof DomainException) {
throw error; // Full type info preserved for client
}
if (error instanceof Error) {
throw error; // Preserve stack trace
}
throw new Error("Unexpected error");
}
Why This Matters:
- Client code can catch specific exception types
- Domain exception metadata (field, code) is preserved
- Better debugging with full stack traces
- Type-safe error handling throughout the stack
Project-Specific Guidelines
When to Defer Minor Issues
Acceptable deferrals:
- Test mock
anytypes - If mock needs flexibility - Generic Error in infrastructure - If wrapping external libraries
- Console.log in dev utilities - If for debugging only
Document deferrals:
// @ts-expect-error - SonarLint: Using 'any' for test flexibility
// Justification: Mock needs to work with multiple use case types
const mockFactory: any = { create: vi.fn() };
Quality Gates
Before PR Creation:
pnpm test # All tests pass
pnpm build # Build succeeds
pnpm tsc --noEmit # Type check clean
# SonarLint analysis # Zero blockers/critical
During Code Review:
- All blockers fixed
- All critical fixed
- Major issues addressed or justified
- Deferrals documented
Error Handling Standards (Validated)
Preserve Domain Exception Types
Pattern from PR #14 SonarLint fixes:
// app/_actions/episodes.ts
export async function trackEpisode(episodeId: number, rating: number) {
return withAuthenticatedRLS(prisma, async (tx, user) => {
try {
const useCase = UseCaseFactory.createTrackEpisodeUseCase();
await useCase.execute({ episodeId, rating }, user.id);
revalidatePath(`/episodes/${episodeId}`);
return { success: true };
} catch (error) {
// ✅ Preserve all domain exceptions
if (error instanceof ValidationException) {
throw error; // Preserves: field, message, code
}
if (error instanceof NotFoundException) {
throw error; // Preserves: entityType, entityId
}
if (error instanceof DomainException) {
throw error; // Base class for all domain exceptions
}
if (error instanceof Error) {
throw error; // Preserve stack trace
}
throw new Error("Failed to track episode");
}
});
}
Client can now handle specific types:
// app/_components/EpisodeTracker.tsx
try {
await trackEpisode(episodeId, rating);
toast.success("Episode tracked!");
} catch (error) {
if (error instanceof ValidationException) {
// Show field-specific error
toast.error(`${error.field}: ${error.message}`);
} else if (error instanceof NotFoundException) {
toast.error(`${error.entityType} not found`);
} else {
toast.error("Something went wrong");
}
}
Type Safety Rules
Production Code
- ✅ Zero
anytypes allowed - ✅ Use
unknownfor truly dynamic data, then narrow - ✅ Use
Partial<T>for optional fields - ✅ Use generics for flexible types
- ❌ No implicit
anyfrom missing types
Test Code
- ✅ Prefer
Partial<Interface>for mocks - ✅ Use
@ts-expect-erroronly when necessary - ✅ Document WHY
anyis used - ❌ Don't use
anywithout comment
Examples:
// ✅ Production - Use Partial<T>
function updateUser(id: string, updates: Partial<User>) {
// ...
}
// ✅ Production - Use unknown
function parseJson(input: unknown): ParsedData {
if (typeof input !== "string") {
throw new TypeError("Expected string");
}
return JSON.parse(input);
}
// ✅ Test - Document any usage
// @ts-expect-error - Test mock intentionally uses any for flexibility
const mockUseCase: any = {
execute: vi.fn().mockResolvedValue({ success: true }),
};
// ✅ Test - Better with Partial
const mockUseCase: Partial<TrackEpisodeUseCase> = {
execute: vi.fn().mockResolvedValue({ success: true }),
};
Integration with CI/CD
GitHub Actions (Future)
# .github/workflows/quality-check.yml
name: Code Quality
on:
pull_request:
branches: [main]
jobs:
sonarqube:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: SonarQube Analysis
uses: sonarsource/sonarqube-scan-action@master
with:
args: >
-Dsonar.projectKey=thesimpsonsapi
-Dsonar.qualitygate.wait=true
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Local Pre-Push Hook
# .git/hooks/pre-push
#!/bin/bash
echo "Running SonarLint analysis..."
# Add SonarLint CLI check here
# Exit 1 if blockers/critical found
Common Patterns from PR #14
Files Fixed
- app/_actions/collections.ts - 2 fixes
- app/_actions/episodes.ts - 1 fix
- app/_actions/diary.ts - 2 fixes
- app/_actions/social.ts - 1 fix
- vitest.setup.ts - 1 fix
Pattern: Server Action Error Handling
Before (loses type):
catch (error) {
if (error instanceof ValidationException) {
throw new Error(error.message);
}
throw new Error("Failed");
}
After (preserves type):
catch (error) {
if (error instanceof ValidationException || error instanceof DomainException) {
throw error;
}
if (error instanceof Error) {
throw error;
}
throw new Error("Failed");
}
Pattern: Test Mock Flexibility
Vitest Setup:
// vitest.setup.ts
vi.mock("next/image", () => ({
// ❌ Before: any type
default: (props: any) => props,
// ✅ After: explicit type
default: (props: Record<string, unknown>) => props,
}));
Resources
スコア
総合スコア
リポジトリの品質指標に基づく評価
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
レビュー
レビュー機能は近日公開予定です