Topics
Recent articles

Knowledge Systems

Sonar Duplication in TypeScript Static Sites

Learn how SonarQube scope affects duplication metrics in TypeScript static sites, and why moving repeated records to excluded JSON changes the report without deduplicating repository data.

Table of Contents1 sections
Laptop screen displaying code, perfect for tech-focused projects.
Laptop screen displaying code, perfect for tech-focused projects.

SonarQube Cloud reports Overall Code and New Code separately. Overall Code includes old and new code, while New Code is limited to the configured new-code period. A low New Code duplication value therefore does not show that the main branch has little historical duplication. Compare duplicated_lines_density with new_duplicated_lines_density and record the project, branch, analysis date, and commit for each result.

The files counted by a scan depend on its configured analysis scope. sonar.sources sets the initial source scope. Source exclusions remove files from analysis, while sonar.cpd.exclusions removes files only from duplication checks. Repeated records in TypeScript can contribute to code duplication. Moving records to JSON changes what Sonar reports only when those JSON files are outside the analysis scope or excluded. That can lower a code metric without removing repeated values from the repository, so treat it as a scope decision rather than proof that the data was deduplicated. For a related implementation, see Static Analysis Lint Detekt Ci.

To manage this architecture safely, separate the decision about where data lives from the scanner’s scope. TypeScript registries provide inline type checking and autocompletion. JSON files can make large static datasets easier to review, but excluding them from analysis also means the duplication metric no longer describes those files. TypeScript’s resolveJsonModule option lets code import JSON and infers a type from its static shape. That compile-time type does not validate data at runtime. Add a runtime schema check when data can change independently of the build, and use parity tests when moving an existing registry so records are not lost or altered. Move data because the structure fits the application, not just to improve a Sonar number. For a related implementation, see Reclaim Macos Developer Storage Safely.

Consider an architectural example from a data-heavy static site using Astro and TypeScript. In the initial implementation, timer durations, audio envelope configurations, and category tags lived inside a shared TypeScript constants file. While convenient, this approach exposed raw data arrays directly to the linter and the code duplication scanner. Refactoring this setup involves moving raw payloads into structured JSON files and enabling JSON-module resolution. The sample data and TypeScript API below show a typed boundary with separate runtime validation.

{
  "timerDefaultDuration": 300,
  "allowedCategories": [
    "focus",
    "short-break",
    "long-break"
  ],
  "audioEnvelope": {
    "attack": 0.05,
    "release": 0.1
  }
}

When a build depends on externalized JSON, validate the complete shape before using it. This example checks every field in the declared interface. A real migration should also include a parity check against the old registry when one exists.

import registryData from './timer-registry.json';

interface TimerRegistry {
  timerDefaultDuration: number;
  allowedCategories: string[];
  audioEnvelope: {
    attack: number;
    release: number;
  };
}

function isTimerRegistry(value: unknown): value is TimerRegistry {
  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
    return false;
  }

  const record = value as Record<string, unknown>;
  const envelope = record.audioEnvelope;
  if (typeof envelope !== 'object' || envelope === null || Array.isArray(envelope)) {
    return false;
  }

  const duration = record.timerDefaultDuration;
  const categories = record.allowedCategories;
  const audio = envelope as Record<string, unknown>;
  return typeof duration === 'number'
    && Number.isInteger(duration)
    && Array.isArray(categories)
    && categories.every((category) => typeof category === 'string')
    && typeof audio.attack === 'number'
    && Number.isFinite(audio.attack)
    && typeof audio.release === 'number'
    && Number.isFinite(audio.release);
}

if (!isTimerRegistry(registryData)) {
  throw new Error('Invalid timer registry');
}

export const registry: TimerRegistry = registryData;

For this case study, the authenticated SonarQube Cloud analysis of the main branch completed on 2026-10-07 and reported Overall Code duplication of 0.0%, with zero duplicated lines and blocks across 9,034 analyzed lines of code. The Quality Gate status was OK. These are dated measurements for the files included by that project’s analysis configuration. JSON registry files were excluded, so the result does not say that every value in the repository was unique. Sonar’s New Code and Overall Code measures answer different questions; report the exact metric and scan date instead of treating one as a substitute for the other. If a dashboard displays a different Overall value, first compare the selected project, branch, and analysis date, then inspect the analyzer scope and exclusions.

Continue Exploring

You Might Also Like

View all articles