Skip to content

Commit 9f6f17c

Browse files
authored
fix(sonar): repair quality gate baseline and fix new-code bugs (#103)
Inject sonar.projectVersion from package.json so the new-code baseline resets each release; suppress S2245 (TestDataGenerator sample data) and S2871 (diff-engine internal canonical keys) as documented false positives. Fix real bugs: in-place column-sort mutation in the schema-diff engine, a tautological stroke ternary in SchemaDiagram, and keyboard-accessible saved-chart menu activation. Bumps version to 0.9.36.
1 parent 33a45ac commit 9f6f17c

7 files changed

Lines changed: 51 additions & 11 deletions

File tree

.github/workflows/ci.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,19 @@ jobs:
188188
echo "=== Sample record ==="
189189
head -20 coverage/lcov.info
190190
191+
# Report the package version so SonarCloud's "previous_version" new-code
192+
# definition resets its baseline at each release. Single source of truth:
193+
# package.json (node is preinstalled on the runner). Read into a step
194+
# output, then passed to the scanner via args (not shell-interpolated).
195+
- name: Resolve project version
196+
id: pkg
197+
run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
198+
191199
- name: SonarCloud Scan
192200
uses: SonarSource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6
193201
with:
194202
args: >
203+
-Dsonar.projectVersion=${{ steps.pkg.outputs.version }}
195204
-Dsonar.verbose=true
196205
env:
197206
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@libredb/studio",
3-
"version": "0.9.35",
3+
"version": "0.9.36",
44
"private": false,
55
"publishConfig": {
66
"access": "public"

sonar-project.properties

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ sonar.projectKey=libredb_libredb-studio
22
sonar.organization=libredb
33

44
sonar.projectName=libredb-studio
5-
sonar.projectVersion=0.6.38
5+
# sonar.projectVersion is intentionally NOT hardcoded here. CI injects it from
6+
# package.json (-Dsonar.projectVersion=<version>) so the "previous_version"
7+
# new-code definition resets its baseline at every release. A hardcoded value
8+
# freezes the baseline, making months of code count as "new" forever.
69

710
sonar.sources=src
811
sonar.tests=tests,e2e
@@ -19,3 +22,19 @@ sonar.cpd.exclusions=tests/**,e2e/**
1922
sonar.javascript.lcov.reportPaths=coverage/lcov.info
2023
sonar.typescript.tsconfigPath=tsconfig.json
2124
sonar.sourceEncoding=UTF-8
25+
26+
# S2245 (insecure pseudorandom number generator) is a false positive in
27+
# TestDataGenerator: Math.random there only produces fake sample rows (names,
28+
# addresses, lorem ipsum) to populate dev/test tables. No security, crypto, or
29+
# token use, so a CSPRNG is unwarranted. Suppress the rule for that file only.
30+
sonar.issue.ignore.multicriteria=e1,e2
31+
sonar.issue.ignore.multicriteria.e1.ruleKey=typescript:S2245
32+
sonar.issue.ignore.multicriteria.e1.resourceKey=src/components/TestDataGenerator.tsx
33+
34+
# S2871 wants String.localeCompare when sorting strings. In diff-engine the
35+
# .sort() calls build internal canonical keys for index-column SETS (used for
36+
# equality matching), not user-facing ordering. They must be deterministic and
37+
# locale-independent, so the default code-point sort is correct here and
38+
# localeCompare would be a bug. Suppress S2871 for this file only.
39+
sonar.issue.ignore.multicriteria.e2.ruleKey=typescript:S2871
40+
sonar.issue.ignore.multicriteria.e2.resourceKey=src/lib/schema-diff/diff-engine.ts

src/components/DataCharts.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,9 +772,10 @@ export function DataCharts({ result }: DataChartsProps) {
772772
{savedCharts.map((chart) => (
773773
<DropdownMenuItem
774774
key={chart.id}
775+
onSelect={() => loadSavedChart(chart)}
775776
className="text-xs cursor-pointer flex items-center justify-between gap-4"
776777
>
777-
<span onClick={() => loadSavedChart(chart)}>
778+
<span>
778779
{chart.name} <span className="text-zinc-600">({chart.chartType})</span>
779780
</span>
780781
<button

src/components/PivotTable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export function PivotTable({ result, onLoadQuery }: PivotTableProps) {
8484
colMap.get(colKey)!.push(value);
8585
}
8686

87-
const colKeys = colField ? Array.from(colValues).sort() : ["__all__"];
87+
const colKeys = colField ? Array.from(colValues).sort((a, b) => a.localeCompare(b)) : ["__all__"];
8888

8989
// Build pivot rows
9090
const pivotRows: { rowKey: string; values: Map<string, string> }[] = [];

src/components/SchemaDiagram.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,9 @@ function SchemaDiagramInner({ schema, onClose }: SchemaDiagramProps) {
217217
labelBgStyle: { fill: "#0d0d0d", fillOpacity: 0.8 },
218218
labelBgPadding: [4, 2] as [number, number],
219219
style: {
220-
stroke: isHighlighted ? "#3b82f6" : "#3b82f6",
220+
// Highlight is conveyed by strokeWidth + opacity below; the stroke
221+
// colour is the same blue in both states.
222+
stroke: "#3b82f6",
221223
strokeWidth: isHighlighted ? 2 : 1.5,
222224
opacity: selectedNode ? (isHighlighted ? 1 : 0.15) : 0.4,
223225
},

src/lib/schema-diff/diff-engine.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,22 @@ function diffColumns(sourceCols: ColumnSchema[], targetCols: ColumnSchema[]): Co
7979
function diffIndexes(sourceIndexes: IndexSchema[], targetIndexes: IndexSchema[]): IndexDiff[] {
8080
const diffs: IndexDiff[] = [];
8181

82+
// Build a stable map key from the column set. Copy before sorting so the
83+
// original column order is preserved for the diff messages below. Use the
84+
// default (code-point) sort, NOT localeCompare: this is an internal canonical
85+
// key for set matching, so it must be deterministic and locale-independent.
86+
// localeCompare would tie the key to the host locale. S2871 is suppressed for
87+
// this file in sonar-project.properties for exactly this reason.
88+
const columnKey = (idx: IndexSchema) => idx.name || [...idx.columns].sort().join(",");
89+
8290
const sourceMap = new Map<string, IndexSchema>();
8391
sourceIndexes.forEach((idx) => {
84-
const key = idx.name || idx.columns.sort().join(",");
85-
sourceMap.set(key, idx);
92+
sourceMap.set(columnKey(idx), idx);
8693
});
8794

8895
const targetMap = new Map<string, IndexSchema>();
8996
targetIndexes.forEach((idx) => {
90-
const key = idx.name || idx.columns.sort().join(",");
91-
targetMap.set(key, idx);
97+
targetMap.set(columnKey(idx), idx);
9298
});
9399

94100
for (const [key, idx] of targetMap) {
@@ -120,8 +126,11 @@ function diffIndexes(sourceIndexes: IndexSchema[], targetIndexes: IndexSchema[])
120126
if (!targetIdx) continue;
121127

122128
const changes: string[] = [];
123-
const sourceColStr = sourceIdx.columns.sort().join(",");
124-
const targetColStr = targetIdx.columns.sort().join(",");
129+
// Code-point sort (not localeCompare) so this equality check is deterministic
130+
// and locale-independent — see columnKey above.
131+
const sortedJoin = (cols: string[]) => [...cols].sort().join(",");
132+
const sourceColStr = sortedJoin(sourceIdx.columns);
133+
const targetColStr = sortedJoin(targetIdx.columns);
125134

126135
if (sourceColStr !== targetColStr) {
127136
changes.push(`Columns changed: (${sourceIdx.columns.join(", ")}) → (${targetIdx.columns.join(", ")})`);

0 commit comments

Comments
 (0)