Editor's Note
webapp-review
Webapp review — analyse your current branch or an existing PR for DRY violations, missing abstractions, SOLID issues, module boundary violations, and extensibility gaps in this NX monorepo.
Install
npx skills add https://github.com/SonarSource/sonarqube-webapp --skill webapp-reviewRun webapp review: $ARGUMENTS
Parse arguments:
--pr <number or URL>— review a specific PR's diff instead of current branch; accepts a bare number (5855) or a full GitHub URL (https://github.com/…/pull/5855) — extract the number from the URL path before proceeding;--baseis ignored when this is set sincegh pr diffalways diffs against the PR's own base branch--base <branch>— override the base branch when diffing the current branch (default: auto-detected main or master); has no effect when--pris provided
Step 1: Get the diff
The two modes — --pr and current-branch — use different data sources. Do not mix them.
Mode A: --pr
The PR already exists on GitHub. All file content must come from the PR's head ref, not the local working tree.
# Fetch the PR ref (works for both same-repo and fork PRs)
git fetch origin pull/{pr}/head:pr-{pr}
# Get the diff (changed file list and full diff)
gh pr diff {pr} --name-only
gh pr diff {pr}
For each changed file that looks architecturally significant (query hooks, adapters, route definitions, React contexts, page templates, API services, page-level components), read its full content from the fetched PR ref, not from disk:
git show pr-{pr}:<file_path>
Mode B: Current branch (no --pr)
Review everything on the current branch that diverges from the base — including uncommitted work.
git rev-parse --abbrev-ref HEAD
Determine the merge-base. If --base was provided, use it directly; otherwise auto-detect:
MERGE_BASE=$(git merge-base HEAD {base})
# or when auto-detecting:
MERGE_BASE=$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD origin/master)
Get the diff from the merge-base to the working tree (this includes committed, staged, and unstaged changes):
git diff $MERGE_BASE --name-only
git diff $MERGE_BASE
For each changed file that looks architecturally significant (query hooks, adapters, route definitions, React contexts, page templates, API services, page-level components), read the full file from disk to understand context beyond what changed.
Step 2: Analyse for general design issues
For each finding note: file path, approximate line, and whether it's a must fix (correctness risk) or worth addressing (design smell that will compound).
Duplication (DRY)
- Same 3+ lines of logic appearing in 3 or more files? Flag it. Duplication between just 2 files is often coincidental — don't extract unrelated code into shared helpers just because it looks similar.
- Two components or hooks structurally identical (same props, same state, same render logic)?
- A constant defined in one place but hardcoded as a string/literal elsewhere?
- Same feature-flag check written independently in multiple components?
Missing or better abstractions
- A
switch/ifchain on the same value repeated across multiple files? Consider a lookup map, config object, or composition pattern instead of branching. - Multiple hooks or helpers all keyed on the same discriminator? They may collapse into a single hook with a config parameter.
- Trace "what files need to change to add the next variant". More than two is a signal to redesign.
SOLID
- S: A component or hook mixing unrelated concerns (data fetching, presentation, routing logic)?
- O: How many files need to change to add the next variant? Hardcoded conditionals = violation. Extensible patterns (maps, config objects, composition) = satisfied.
- D: Tightly coupled to specific implementations instead of using hooks, props, or the adapter layer for indirection?
Consistency between similar implementations
- Two implementations of the same adapter or hook using different approaches to get their inputs?
- Similar components with different null handling, loading state, or error patterns?
Correctness / subtle bugs
- Hardcoded limits interacting with configurable values in a way that could silently produce wrong results?
- String-based mappings (splitting, prefix matching) that break silently if a value changes?
- Extra HTTP/DB calls firing on every request when the data is already in scope?
Step 3: Analyse for monorepo-specific issues
This is an NX monorepo with SonarQube Server (SQS) and SonarQube Cloud (SQC) products sharing code. Load the webapp-code-sharing skill if you need a deeper understanding of the module structure.
Module boundary violations
- Code in
libs/sharedimporting directly fromprivate/— shared code must never depend on private code. - Code in
libs/sharedimporting fromapps/sq-server*orapps/sq-cloud*— shared code must not depend on app-level code. - Code in
apps/sq-serverimporting directly fromprivate/— it must go through thelibs/sq-server-addonsbridge. - Code in
sq-cloudimporting fromsq-server*— cloud must not depend on server code. - A new feature in
private/libs/feature-*not wired throughlibs/sq-server-addons— SQS won't be able to load it. - Public code that would break if the
private/folder were removed entirely.
Adapter alignment
- A new or modified adapter in
sq-server-adapterswithout a corresponding update insq-cloud-adapters(or vice versa) — the type signatures must stay compatible. - Adapters diverging in approach: one adapter fetching data inline while the other uses a query hook, or different null-handling strategies for the same interface.
- New shared code using the
~adaptersalias — verify that both adapter implementations actually exist and export the expected symbols.
Code placement
- New code landing in
apps/sq-server/srcorprivate/apps/sq-cloud/srcthat is generic enough to belong inlibs/sharedor alibs/feature-*module. - Utility functions or types duplicated between the two apps instead of being extracted to
libs/shared. - A new feature module created inside
apps/instead oflibs/feature-*— features should be shareable by default. - Changes touching similarly-named files in both
apps/sq-serverandprivate/apps/sq-cloud— consider moving the shared logic tolibs/sharedinstead of duplicating changes across both apps.
Echoes and design system migration
- New code using the old theming system (
themeColorhelper) — should use Echoes design tokens instead. - New code importing from the legacy
design-systempackage — should use@sonarsource/echoes-reactcomponents. - Raw hex colours or hardcoded font sizes instead of Echoes CSS variables via
cssVar. - Custom Tailwind classes used for styling that could be replaced by semantic Echoes component props (
isSubtle,size,colorOverride).
React Query patterns
- Custom wrapper hooks that return hand-built objects instead of the standard TanStack Query result shape.
- Mutations wrapped with custom
isPending/mutatefields instead of usinguseMutation({ mutationFn })withmutateAsync. - New query hooks that duplicate existing queries instead of calling them with a
selectparameter for data transformation.
Codebase conventions
- New code using the legacy
translateortranslateWithParameterhelpers — should useformatMessageor<FormattedMessage>instead. - Prefer testing-library assertions over snapshot tests when the behavior is easily assertable.
- Private feature tests in public test files must be wrapped in
// BEGIN-PRIVATE-FEATURE-TESTS/// END-PRIVATE-FEATURE-TESTSmarkers. - Flag no-value stylistic changes like adding blank lines, reordering imports, or reformatting untouched code — these add noise to the diff without improving the code.
- New dependencies added to
package.jsonwithout a corresponding entry inpackage.json.md.
Step 4: For each finding, assess the tradeoff
Before reporting, ask: what does acting on this actually cost?
- Scope: one-liner fix, single-class refactor, or multi-module change?
- Complexity trade: does the fix remove complexity in one place but add it somewhere else?
- Timing: incremental, or requires a bigger bang?
- Risk: does it touch a hot path or a tested boundary?
Include the tradeoff in every finding. A finding without a tradeoff is just a complaint.
Step 5: Report findings
Must fix:
{file}:{line}— {what breaks and why} → fix: {concrete suggestion} | cost: {tradeoff}
Worth addressing (will compound if left):
{file}:{line}— {the smell} → suggestion: {cleaner approach} | cost: {tradeoff}
Looks good:
- {something specific that was done well}
Keep each finding to 2-3 sentences. If no must-fix items, say so clearly.
Closing prompt
If --pr was used (reviewing an existing PR):
Ask: "Want me to address any of these, or post these findings as a comment on the PR?"
If the answer is to post: use gh pr comment {pr} --body "{findings}". Do not offer to create a new PR — one already exists.
If reviewing the current branch (no --pr):
Ask: "Want to address any of these before opening the PR, or shall I go ahead and create it?"
If the answer is to create it: gh pr create --title "{title}" --body "{body}". Mention any deferred items briefly in the PR body so reviewers are aware.
Categories