Skip to content

Commit 58571d3

Browse files
committed
chore(sonar): align project quality gate and fix regex findings
- enforce xConfig Sonar exclusions, profile rules, and new-code gate conditions - replace Sonar-flagged regex parsers with small string parsers - document verified SonarQube server state
1 parent 4ec4a61 commit 58571d3

6 files changed

Lines changed: 302 additions & 38 deletions

File tree

dist/autodarts-xconfig.user.js

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5933,12 +5933,44 @@
59335933
if (!normalized.startsWith("/")) {
59345934
normalized = `/${normalized}`;
59355935
}
5936-
normalized = normalized.replace(/[?#].*$/, "").replaceAll(/\/{2,}/g, "/");
5936+
normalized = collapseRepeatedSlashes(stripRouteSuffix(normalized));
59375937
if (normalized.length > 1) {
5938-
normalized = normalized.replace(/\/+$/, "");
5938+
normalized = trimTrailingSlashes(normalized);
59395939
}
59405940
return normalized;
59415941
}
5942+
function stripRouteSuffix(pathValue) {
5943+
const queryIndex = pathValue.indexOf("?");
5944+
const hashIndex = pathValue.indexOf("#");
5945+
const suffixIndexes = [queryIndex, hashIndex].filter((index) => index >= 0);
5946+
if (!suffixIndexes.length) {
5947+
return pathValue;
5948+
}
5949+
return pathValue.slice(0, Math.min(...suffixIndexes));
5950+
}
5951+
function collapseRepeatedSlashes(pathValue) {
5952+
let collapsed = "";
5953+
let previousWasSlash = false;
5954+
for (const char of pathValue) {
5955+
if (char === "/") {
5956+
if (!previousWasSlash) {
5957+
collapsed += char;
5958+
}
5959+
previousWasSlash = true;
5960+
continue;
5961+
}
5962+
collapsed += char;
5963+
previousWasSlash = false;
5964+
}
5965+
return collapsed;
5966+
}
5967+
function trimTrailingSlashes(pathValue) {
5968+
let endIndex = pathValue.length;
5969+
while (endIndex > 1 && pathValue[endIndex - 1] === "/") {
5970+
endIndex -= 1;
5971+
}
5972+
return pathValue.slice(0, endIndex);
5973+
}
59425974
function normalizeMatchId(value) {
59435975
return String(value || "").trim().toLowerCase();
59445976
}
@@ -9634,7 +9666,8 @@
96349666
max: 3.15
96359667
});
96369668
var TRANSFORM_SIGNATURE_STEP_PX = 0.5;
9637-
var APPLIED_ZOOM_TRANSFORM_PATTERN = /(?:^|\s)translate\(\s*([-+]?\d*\.?\d+)px,\s*([-+]?\d*\.?\d+)px\)\s+scale\(\s*([-+]?\d*\.?\d+)\s*\)\s*$/;
9669+
var TRANSLATE_PREFIX = "translate(";
9670+
var SCALE_PREFIX = "scale(";
96389671
function parseViewBox(svgNode) {
96399672
if (!svgNode || typeof svgNode.getAttribute !== "function") {
96409673
return {
@@ -10097,7 +10130,7 @@
1009710130
const hasAppliedZoomTransform = targetNode.classList?.contains?.(ZOOM_CLASS) && Boolean(parseAppliedZoomTransform(currentTransform));
1009810131
state.targetStyleSnapshot = {
1009910132
node: targetNode,
10100-
transform: hasAppliedZoomTransform ? currentTransform.replace(APPLIED_ZOOM_TRANSFORM_PATTERN, "").trim() : currentTransform,
10133+
transform: hasAppliedZoomTransform ? stripAppliedZoomTransform(currentTransform) : currentTransform,
1010110134
transition: hasAppliedZoomTransform ? "" : String(targetNode.style.transition || ""),
1010210135
transformOrigin: hasAppliedZoomTransform ? "" : String(targetNode.style.transformOrigin || ""),
1010310136
willChange: hasAppliedZoomTransform ? "" : String(targetNode.style.willChange || "")
@@ -10301,13 +10334,35 @@
1030110334
return normalizeRect(measuredRect) || fallbackRect;
1030210335
}
1030310336
function parseAppliedZoomTransform(transformValue) {
10304-
const match = String(transformValue || "").match(APPLIED_ZOOM_TRANSFORM_PATTERN);
10305-
if (!match) {
10337+
const rawTransform = String(transformValue || "").trim();
10338+
const translateIndex = rawTransform.lastIndexOf(TRANSLATE_PREFIX);
10339+
if (translateIndex < 0) {
10340+
return null;
10341+
}
10342+
if (translateIndex > 0 && String(rawTransform[translateIndex - 1] || "").trim()) {
1030610343
return null;
1030710344
}
10308-
const tx = Number(match[1]);
10309-
const ty = Number(match[2]);
10310-
const scale = Number(match[3]);
10345+
const translateStart = translateIndex + TRANSLATE_PREFIX.length;
10346+
const translateEnd = rawTransform.indexOf(")", translateStart);
10347+
if (translateEnd < 0) {
10348+
return null;
10349+
}
10350+
const afterTranslate = rawTransform.slice(translateEnd + 1).trimStart();
10351+
if (!afterTranslate.startsWith(SCALE_PREFIX)) {
10352+
return null;
10353+
}
10354+
const scaleStart = SCALE_PREFIX.length;
10355+
const scaleEnd = afterTranslate.indexOf(")", scaleStart);
10356+
if (scaleEnd < 0 || afterTranslate.slice(scaleEnd + 1).trim()) {
10357+
return null;
10358+
}
10359+
const translateParts = rawTransform.slice(translateStart, translateEnd).split(",");
10360+
if (translateParts.length !== 2) {
10361+
return null;
10362+
}
10363+
const tx = parseCssPixelValue(translateParts[0]);
10364+
const ty = parseCssPixelValue(translateParts[1]);
10365+
const scale = Number(afterTranslate.slice(scaleStart, scaleEnd).trim());
1031110366
if (!(Number.isFinite(tx) && Number.isFinite(ty) && Number.isFinite(scale) && scale > 1)) {
1031210367
return null;
1031310368
}
@@ -10317,6 +10372,21 @@
1031710372
scale
1031810373
};
1031910374
}
10375+
function parseCssPixelValue(value) {
10376+
const normalized = String(value || "").trim();
10377+
if (!normalized.endsWith("px")) {
10378+
return Number.NaN;
10379+
}
10380+
return Number(normalized.slice(0, -2).trim());
10381+
}
10382+
function stripAppliedZoomTransform(transformValue) {
10383+
const rawTransform = String(transformValue || "").trim();
10384+
const translateIndex = rawTransform.lastIndexOf(TRANSLATE_PREFIX);
10385+
if (translateIndex < 0 || !parseAppliedZoomTransform(rawTransform)) {
10386+
return rawTransform;
10387+
}
10388+
return rawTransform.slice(0, translateIndex).trim();
10389+
}
1032010390
function resolveCurrentAppliedZoomTransform(targetNode, measuredNode) {
1032110391
if (!targetNode?.classList?.contains?.(ZOOM_CLASS)) {
1032210392
return null;

docs/sonarqube/2026-04-19-server-config.md

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# SonarQube Server Config For autodarts-xconfig
22

33
Date: `2026-04-19`
4+
Last verified: `2026-04-24`
45
Project key: `xConfig`
56
Project name: `autodarts-xconfig`
67
JS profile: `autodarts-xconfig JS`
@@ -17,34 +18,45 @@ The following changes were applied directly on the SonarQube server:
1718
- Updated `javascript:S3776` to severity `MAJOR` with threshold `25`
1819
- Updated `javascript:S2004` to severity `MAJOR` with max nesting `4`
1920
- Updated `javascript:S6582` to severity `MINOR`
21+
- Confirmed `javascript:S1128` is active
22+
- Confirmed `javascript:S125` is active
2023
- Set `sonar.exclusions` to:
2124
`dist/**`, `src/legacy-backups/**`, `src/vendors/anime.min.cjs`, `src/vendors/canvas-confetti.browser.js`
2225
- Set `sonar.cpd.exclusions` to:
2326
`src/vendors/**`, `src/legacy-backups/**`
2427
- Set `sonar.coverage.exclusions` to:
2528
`loader/**`, `scripts/**`, `src/vendors/**`, `src/legacy-backups/**`
29+
- Assigned quality gate `autodarts-xconfig` to project `xConfig`
30+
- Added new-code quality gate conditions:
31+
- `new_security_review_rating > 1`
32+
- `new_blocker_violations > 0`
33+
- `new_critical_violations > 0`
2634

2735
## Verified Outcome
2836

29-
After a fresh Sonar analysis:
37+
After a fresh Sonar analysis on `2026-04-24`:
3038

31-
- Open issues dropped from `259` to `117`
32-
- Open `CRITICAL` issues dropped from `10` to `0`
39+
- Quality gate is `OK`
40+
- Open issues are `0`
41+
- Open Security Hotspots to review are `16`
3342
- JS profile active rule count is now `375`
34-
- Quality gate remains `OK`
43+
- New-code duplicated lines density is `1.59774`
44+
- New-code Blocker issues are `0`
45+
- New-code Critical issues are `0`
46+
- New-code Security Review Rating is `A`
47+
- Coverage remains `0.0`; no LCOV report is currently imported
3548

3649
## Intentional Non-Changes
3750

3851
The following were intentionally left unchanged:
3952

40-
- Quality gate conditions
4153
- New code definition
4254
- CSS profile
4355
- HTML profile
4456

4557
Reason:
4658

47-
- The current gate already focuses on new code and remains stable after the rule cleanup.
59+
- The current gate already focuses on new code.
4860
- Coverage import is still `0.0`, so adding a coverage gate now would create noise rather than value.
4961
- The repository is overwhelmingly JS/MJS. Standalone CSS/HTML analysis is currently marginal for this project.
5062

@@ -58,10 +70,7 @@ Example:
5870
$env:SONARQUBE_URL = "http://your-sonarqube-server:9000"
5971
$env:SONARQUBE_TOKEN = "your-token"
6072
61-
pwsh ./ops/sonarqube/apply-xconfig-server-config.ps1 `
62-
-SonarQubeUrl $env:SONARQUBE_URL `
63-
-SonarQubeToken $env:SONARQUBE_TOKEN `
64-
-RunAnalysis
73+
.\ops\sonarqube\apply-xconfig-server-config.ps1 -RunAnalysis
6574
```
6675

6776
## Next Sensible Step

ops/sonarqube/apply-xconfig-server-config.ps1

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
param(
2-
[Parameter(Mandatory = $true)]
3-
[string]$SonarQubeUrl,
2+
[string]$SonarQubeUrl = $env:SONARQUBE_URL,
43

5-
[Parameter(Mandatory = $true)]
6-
[string]$SonarQubeToken,
4+
[string]$SonarQubeToken = $env:SONARQUBE_TOKEN,
75

86
[string]$ProjectKey = "xConfig",
97
[string]$JsProfileName = "autodarts-xconfig JS",
8+
[string]$QualityGateName = "autodarts-xconfig",
109
[switch]$RunAnalysis
1110
)
1211

1312
$ErrorActionPreference = "Stop"
1413

14+
if (-not $SonarQubeUrl) {
15+
throw "SONARQUBE_URL must be set."
16+
}
17+
18+
if (-not $SonarQubeToken) {
19+
throw "SONARQUBE_TOKEN must be set."
20+
}
21+
1522
function New-AuthHeader {
1623
param([string]$Token)
1724

@@ -31,6 +38,16 @@ function Invoke-SonarPost {
3138
Invoke-RestMethod -Headers $Headers -Method Post -Uri "$BaseUrl$Path" -Body $Body | Out-Null
3239
}
3340

41+
function Invoke-SonarGet {
42+
param(
43+
[hashtable]$Headers,
44+
[string]$BaseUrl,
45+
[string]$Path
46+
)
47+
48+
Invoke-RestMethod -Headers $Headers -Uri "$BaseUrl$Path"
49+
}
50+
3451
function Set-SonarMultiValue {
3552
param(
3653
[hashtable]$Headers,
@@ -59,10 +76,48 @@ function Set-SonarMultiValue {
5976
-Body $body | Out-Null
6077
}
6178

79+
function Set-SonarGateCondition {
80+
param(
81+
[hashtable]$Headers,
82+
[string]$BaseUrl,
83+
[string]$GateName,
84+
[string]$Metric,
85+
[string]$Op,
86+
[string]$ErrorThreshold
87+
)
88+
89+
$gate = Invoke-SonarGet `
90+
-Headers $Headers `
91+
-BaseUrl $BaseUrl `
92+
-Path "/api/qualitygates/show?name=$([uri]::EscapeDataString($GateName))"
93+
94+
$existing = @($gate.conditions) | Where-Object { $_.metric -eq $Metric } | Select-Object -First 1
95+
96+
if ($existing) {
97+
if ($existing.op -ne $Op -or [string]$existing.error -ne $ErrorThreshold) {
98+
Invoke-SonarPost -Headers $Headers -BaseUrl $BaseUrl -Path "/api/qualitygates/update_condition" -Body @{
99+
id = $existing.id
100+
metric = $Metric
101+
op = $Op
102+
error = $ErrorThreshold
103+
}
104+
}
105+
106+
return
107+
}
108+
109+
Invoke-SonarPost -Headers $Headers -BaseUrl $BaseUrl -Path "/api/qualitygates/create_condition" -Body @{
110+
gateName = $GateName
111+
metric = $Metric
112+
op = $Op
113+
error = $ErrorThreshold
114+
}
115+
}
116+
62117
$baseUrl = $SonarQubeUrl.TrimEnd("/")
63118
$headers = New-AuthHeader -Token $SonarQubeToken
64119

65-
$profiles = Invoke-RestMethod -Headers $headers -Uri "$baseUrl/api/qualityprofiles/search?project=$ProjectKey"
120+
$profiles = Invoke-SonarGet -Headers $headers -BaseUrl $baseUrl -Path "/api/qualityprofiles/search?project=$ProjectKey"
66121
$jsProfile = $profiles.profiles | Where-Object { $_.language -eq "js" -and $_.name -eq $JsProfileName } | Select-Object -First 1
67122

68123
if (-not $jsProfile) {
@@ -105,6 +160,16 @@ foreach ($rule in $deactivateRules) {
105160
}
106161
}
107162

163+
Invoke-SonarPost -Headers $headers -BaseUrl $baseUrl -Path "/api/qualityprofiles/activate_rule" -Body @{
164+
key = $profileKey
165+
rule = "javascript:S1128"
166+
}
167+
168+
Invoke-SonarPost -Headers $headers -BaseUrl $baseUrl -Path "/api/qualityprofiles/activate_rule" -Body @{
169+
key = $profileKey
170+
rule = "javascript:S125"
171+
}
172+
108173
Invoke-SonarPost -Headers $headers -BaseUrl $baseUrl -Path "/api/qualityprofiles/activate_rule" -Body @{
109174
key = $profileKey
110175
rule = "javascript:S3776"
@@ -125,6 +190,36 @@ Invoke-SonarPost -Headers $headers -BaseUrl $baseUrl -Path "/api/qualityprofiles
125190
severity = "MINOR"
126191
}
127192

193+
$gate = $null
194+
195+
try {
196+
$gate = Invoke-SonarGet `
197+
-Headers $headers `
198+
-BaseUrl $baseUrl `
199+
-Path "/api/qualitygates/show?name=$([uri]::EscapeDataString($QualityGateName))"
200+
} catch {
201+
$gate = $null
202+
}
203+
204+
if (-not $gate) {
205+
Invoke-SonarPost -Headers $headers -BaseUrl $baseUrl -Path "/api/qualitygates/create" -Body @{
206+
name = $QualityGateName
207+
}
208+
}
209+
210+
Invoke-SonarPost -Headers $headers -BaseUrl $baseUrl -Path "/api/qualitygates/select" -Body @{
211+
projectKey = $ProjectKey
212+
gateName = $QualityGateName
213+
}
214+
215+
Set-SonarGateCondition -Headers $headers -BaseUrl $baseUrl -GateName $QualityGateName -Metric "new_reliability_rating" -Op "GT" -ErrorThreshold "1"
216+
Set-SonarGateCondition -Headers $headers -BaseUrl $baseUrl -GateName $QualityGateName -Metric "new_security_rating" -Op "GT" -ErrorThreshold "1"
217+
Set-SonarGateCondition -Headers $headers -BaseUrl $baseUrl -GateName $QualityGateName -Metric "new_security_review_rating" -Op "GT" -ErrorThreshold "1"
218+
Set-SonarGateCondition -Headers $headers -BaseUrl $baseUrl -GateName $QualityGateName -Metric "new_maintainability_rating" -Op "GT" -ErrorThreshold "1"
219+
Set-SonarGateCondition -Headers $headers -BaseUrl $baseUrl -GateName $QualityGateName -Metric "new_duplicated_lines_density" -Op "GT" -ErrorThreshold "3"
220+
Set-SonarGateCondition -Headers $headers -BaseUrl $baseUrl -GateName $QualityGateName -Metric "new_blocker_violations" -Op "GT" -ErrorThreshold "0"
221+
Set-SonarGateCondition -Headers $headers -BaseUrl $baseUrl -GateName $QualityGateName -Metric "new_critical_violations" -Op "GT" -ErrorThreshold "0"
222+
128223
if ($RunAnalysis) {
129224
$env:SONAR_HOST_URL = $baseUrl
130225
$env:SONAR_TOKEN = $SonarQubeToken
@@ -134,10 +229,12 @@ if ($RunAnalysis) {
134229
$summary = [ordered]@{
135230
project = $ProjectKey
136231
jsProfile = $JsProfileName
137-
activeRuleCount = (Invoke-RestMethod -Headers $headers -Uri "$baseUrl/api/qualityprofiles/search?project=$ProjectKey").profiles |
232+
qualityGate = $QualityGateName
233+
activeRuleCount = (Invoke-SonarGet -Headers $headers -BaseUrl $baseUrl -Path "/api/qualityprofiles/search?project=$ProjectKey").profiles |
138234
Where-Object { $_.language -eq "js" -and $_.name -eq $JsProfileName } |
139235
Select-Object -ExpandProperty activeRuleCount
140-
settings = Invoke-RestMethod -Headers $headers -Uri "$baseUrl/api/settings/values?component=$ProjectKey&keys=sonar.exclusions,sonar.cpd.exclusions,sonar.coverage.exclusions"
236+
settings = Invoke-SonarGet -Headers $headers -BaseUrl $baseUrl -Path "/api/settings/values?component=$ProjectKey&keys=sonar.exclusions,sonar.cpd.exclusions,sonar.coverage.exclusions"
237+
gate = Invoke-SonarGet -Headers $headers -BaseUrl $baseUrl -Path "/api/qualitygates/show?name=$([uri]::EscapeDataString($QualityGateName))"
141238
}
142239

143240
$summary | ConvertTo-Json -Depth 8

sonar-project.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ sonar.projectName=autodarts-xconfig
88
sonar.sources=src,loader,scripts
99
sonar.tests=tests
1010
sonar.test.inclusions=tests/**
11+
12+
sonar.exclusions=dist/**,src/legacy-backups/**,src/vendors/anime.min.cjs,src/vendors/canvas-confetti.browser.js
13+
sonar.cpd.exclusions=src/vendors/**,src/legacy-backups/**
14+
sonar.coverage.exclusions=loader/**,scripts/**,src/vendors/**,src/legacy-backups/**

0 commit comments

Comments
 (0)