prototype
MAmattpocock/skills318k近 7 天新增 9,301 个 star
Use when fixing SonarQube issues in apps/frontend or writing Sonar-clean code. Contains the full enforced rule set with examples and quick fixes.
Use this skill when fixing SonarQube issues in apps/frontend, or when writing new code that must pass Sonar checks. Contains the complete rule set enforced in this repo with examples and quick fixes.
TRIGGER: any mention of "sonar", "code quality", "lint issues", or when writing new frontend code.
Run all three in order before declaring any task done. Never skip.
# 1. Type check — from apps/frontend/
npx tsc --noemit
# 2. Lint — from repo root
pnpm --filter frontend run lint
# 3. Prefer targeted tests for files you modified; full Jest runs are allowed if the user explicitly asks or if you are validating shared test infrastructure
pnpm --filter frontend run test -- --testPathPatterns="<ModifiedComponentName>"
Full suite (pnpm run test with no filter) is discouraged by default. Use it only when the user explicitly asks, when triaging repo-wide breakage, or when validating shared test infrastructure. Playwright and accessibility runs are allowed whenever relevant.
The SonarCloud frontend project is the source of truth for open issues and security hotspots.
_sonar stage of .github/workflows/ci.yaml, reusing the coverage the test stage already produced..github/workflows/sonar-cloud-analysis.yml runs nightly as the type-aware backstop.CLAUDE.md); never let them first surface on the PR.as SomeType | string when SomeType already covers all values.void somePromise() — either await it or .then() it.as X type assertions where TypeScript can infer..closest<HTMLElement>(selector) generic overload instead of as HTMLElement | null.RefObject<T> not MutableRefObject<T> unless you need to mutate .current externally.const [value, setValue] = useState(...).const state = useState(...).const [, setter] = useState(...) — use useRef instead when value is never read:// Bad
const [, setBlurred] = useState(false);
// Good
const blurredRef = useRef(false);
const setBlurred = (v: boolean) => {
blurredRef.current = v;
};
set + PascalCase(valueName). E.g. [items, setItems].defaultProps / mock objects — otherwise TypeScript will error on excess properties.<button> in tests — use <span>.<button> inside <button>.onClick/onKeyDown on <dialog> — move to inner <div>.role="group" when native structure is already semantic.Sonar flags a raw text node immediately followed by a sibling element inside the same parent as "ambiguous spacing before next element". Fix by wrapping the text node in a JSX expression:
// Bad — raw text node adjacent to <span>
<span>
Finance
<span className="text-text-tertiary">{` (${count})`}</span>
</span>
// Good — text wrapped in expression, no ambiguity
<span>
{"Finance"}
<span className="text-text-tertiary">{` (${count})`}</span>
</span>
This applies anywhere a bare string literal sits next to a JSX child element inside the same container.
Prefer native elements over ARIA roles:
| Instead of | Use |
|---|---|
<div role="article"> | <article> |
<div role="region" aria-label="..."> | <section aria-label="..."> |
<div role="list"> / <div role="listitem"> | <ul> / <li> |
<div role="dialog"> | <dialog open> |
<div role="button"> | <button> |
<ul>, children must become <li> (including empty-state placeholders).<article>, drag handler type → React.DragEvent<HTMLElement>.<div>/<span> with onClick must also have role, tabIndex={0}, onKeyDown.const before return.value={a ? b ? 'X' : 'Y' : b ? 'Z' : 'W'}): extract to a named module-level helper function placed before the component, not an inline const. This keeps the component body clean and the logic reusable.// Bad — nested ternary inline in prop
value={gender === 'female' ? isNeutered ? 'Spayed' : 'Not spayed' : isNeutered ? 'Neutered' : 'Not neutered'}
// Good — module-level helper
const getNeuteredStatusLabel = (gender: string | undefined, isNeutered: boolean | undefined): string => {
if (gender === 'female') return isNeutered ? 'Spayed' : 'Not spayed';
return isNeutered ? 'Neutered' : 'Not neutered';
};
// then in JSX:
value={getNeuteredStatusLabel(companion.gender, companion.isneutered)}
// If array is only used for .includes() → convert to Set
const OPTIONS = new Set<Foo>(['A', 'B', 'C']);
if (OPTIONS.has(value)) { ... }
| Old | New |
|---|---|
window | globalThis.window |
typeof x === 'undefined' | x === undefined |
str.replace(/foo/g, ...) | str.replaceAll('foo', ...) |
/^foo/.test(str) | str.startsWith('foo') |
arr[arr.length - 1] | arr.at(-1) |
.filter(...).pop() | .findLast(...) |
str.match(regex) | RegExp.exec(str) |
arr.findIndex(x => x === val) >= 0 | arr.includes(val) |
.sort() when original mustn't change | .toSorted() |
{ ...{} } | remove empty spread |
arr.length > 0 && arr.every(...) | arr.every(...) |
Additional constraints:
replaceAll with a RegExp, the regex must be global (/g) or runtime errors occur./[,]/g -> ',').String.raw to reduce escaping bugs.else if instead of else { if (...) { ... } } to satisfy Sonar readability rules.All drag handlers shared across element types: use React.DragEvent<HTMLElement> not HTMLDivElement.
/* Bad — same selector twice */
.foo,
.bar {
border: 1px solid #eee;
}
.foo,
.bar {
padding: 18px;
}
/* Good — merged */
.foo,
.bar {
border: 1px solid #eee;
padding: 18px;
}
!== undefined ? value : fallback to === undefined ? fallback : value.if / else if branches that set the same value into a single combined condition.?? (nullish coalescing) rather than || when the left-hand side is an optional chain — a?.b ?? c not a?.b || c (avoids falsiness bugs with empty strings and 0).String(value ?? '') where value is typed unknown, guard the type first to avoid [object Object]: typeof value === 'string' || typeof value === 'number' ? String(value) : ''.!parsed.hostname?.includes('.') not !parsed.hostname || !parsed.hostname.includes('.').onClick on <img>, <div>, or <span> directly.<button> with type="button" and move the handler there. Set disabled instead of guarding inside the handler.role + tabIndex + onKeyDown approach when wrapping in a <button> would break layout (e.g. inside a <label>).<dialog> MigrationWhen converting <div role="dialog"> to <dialog open>:
open attribute — without it the browser hides the element.m-0 w-full h-full max-w-none border-0 to neutralise native <dialog> styles.aria-modal="true" — it is implied on <dialog> and can cause double-announcement.onClick/onKeyDown directly on <dialog> — keep interactive handlers on inner elements.fixed inset-0 overlay pattern still works on <dialog open> when combined with the overrides above.Extracting setState updaters (fixes nesting > 4 in onChange callbacks):
// Bad — 5 levels deep inline
onChange={(event) =>
setEditor((prev) =>
prev == null ? prev : { ...prev, service: { ...prev.service, name: event.target.value } }
)
}
// Good — named handler outside JSX
const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setEditor((prev) =>
prev == null ? prev : { ...prev, service: { ...prev.service, name: event.target.value } }
);
};
// then: onChange={handleNameChange}
Reducing cognitive complexity in guard functions — extract sub-conditions into named helpers:
// Bad — one large function with nested if/else
const resolveRedirect = (...) => {
if (owner) {
if (!verified) {
if (step < 3) { ... }
if (...) { ... }
...
}
}
};
// Good — extract unverified-owner branch
const resolveUnverifiedOwnerRedirect = (step, profileStep, pathname, orgId) => {
if (step < 3) return `/create-org?orgId=${orgId}`;
if (profileStep < 3 && pathname !== '/team-onboarding') return `/team-onboarding?orgId=${orgId}`;
if (isUnverifiedPathAllowed(pathname)) return '';
return '/dashboard';
};
Extracting inner .map() callbacks (fixes nesting inside setFormData updaters):
// Bad — nested .map() inside setState callback inside useEffect
setFormData((prev) => {
const next = prev.map((item) => {
return items.map((s) => ({ ...s, id: '', organisationId: orgId })); // 5 levels
});
});
// Good — module-level helper
const buildItem = (s: Template, orgId: string): Service => ({
...s,
id: '',
organisationId: orgId,
});
// then: .map((s) => buildItem(s, orgId))
Prefer these fixes before inventing a local workaround:
onChange, .map(), and setState updaters into named helpers.else { if (...) } chains by rewriting them as direct positive branches.Set for membership checks, especially when the code only calls .includes().window usage with globalThis.window and prefer ?? for optional-chain fallbacks.{"..."} to avoid ambiguous spacing warnings.role="option" on a custom rich-content button (Sonar S6819: "Use <option> instead of the option role"). Native <option> cannot hold badges/check icons, so swap to a real toggle button:
role="option" + aria-selected, use aria-pressed={isSelected} on the <button>.getByRole('option', { name }) no longer match. Use getByRole('button', { name, pressed: true|false }). Single-select dropdowns whose options became plain <button>{label}</button> use getByRole('button', { name }) (no pressed).Do not pass function X directly to .reduce(…) (S7060): wrap it — items.reduce((acc, item) => fn(acc, item), []). But that wrapper can then trip the nesting > 4 rule when it sits inside useEffect → setTimeout → .then → reduce. Fix by hoisting a module-level mapXToY = (items) => items.reduce(...) and calling it from a flat .then.S107): convert positional params to a single typed props object (type FooArgs = {...}; const foo = ({ a, b, … }: FooArgs) => …). Update the call site to pass one object literal; rename callback args to the new prop names there.S3923): both ternary branches are identical (e.g. isBatch ? errors?.[name] : errors?.[name]). Collapse to the single expression — this is a real bug flag, not a smell.Pick<T, 'a' | 'b' | …> used as a param type: extract type FooContext = Pick<T, …> above the function and reference it.[object Object] stringification with empty-string semantics: when guarding String(value) for unknown, preserve prior falsy behaviour. if (value) treated ''/0 as empty; a typeof === 'string' | 'number' guard does not — add if (!text) return []; (or equivalent) so empty input still yields the empty result the tests assert.MultiSelectTriggerContent) and lift className/aria ternaries into a named getXClassName(...) helper. Extract keyboard/active-index logic into a custom hook (useActiveOption) that returns { activeIndex, handleKeyDown, … }.x && x.length > 0 ? x : enc.x ternaries: add a generic preferNonEmpty(next, current) helper and collapse each field to a single call. Note the inverted documents case (=== undefined || length === 0 ? enc : patch) is also just preferNonEmpty.LabelDropdown/MultiSelectDropdown render the option list through a portal gated on a portalStyle computed from getBoundingClientRect() in a layout effect — that stays null in jsdom, so the options never mount. Tests that need to pick an option must mock the dropdown to render options/placeholder inline (see AppointmentDetailsSection.test.tsx and BookAppointment.test.tsx mocks). Do not try to findByRole a real portal option in jsdom — it will time out. Also watch for placeholder renames flowing into these mocks (e.g. Service → Services / Packages).
eslint --fix will auto-fix some of these but NOT semantic HTML, complexity, or accessibility issues — fix those manually.npx tsc --noemit + pnpm --filter frontend run lint before marking resolved.h-[100px] → h-25, z-[5000] → z-5000, max-w-[220px] → max-w-55). Fix these when the IDE flags them.MAmattpocock/skills318k近 7 天新增 9,301 个 star
AFaffaan-m/ECC299k近 7 天新增 5,106 个 star
FIfirecrawl/firecrawl200k近 7 天新增 2,106 个 star
ANanthropics/skills189k近 7 天新增 2,932 个 star
用 React、Tailwind 和 shadcn/ui 构建多组件页面。
ANanthropics/skills189k近 7 天新增 2,932 个 star