RC-350 Unify ruling expectation file locations. by romainbrenguier · Pull Request #7922 · SonarSource/SonarJS · GitHub
Skip to content

RC-350 Unify ruling expectation file locations. - #7922

Draft
romainbrenguier wants to merge 1 commit into
masterfrom
romain/normalize-ruling-expectations
Draft

romainbrenguier wants to merge 1 commit into
masterfrom
romain/normalize-ruling-expectations

Conversation

@romainbrenguier

Copy link
Copy Markdown

Summary

  • Move ruling expectation files from its/ruling/src/test/expected/<project>/<language>-<ruleId>.json to its/ruling/src/test/resources/expected/<language>/<project>/<ruleId>.json
  • Update Java ruling test (LITS-based) to merge per-language expectation dirs into a flat directory at build time
  • Update JS/TS ruling pipeline (lits.ts, testProject.ts) to write and compare results using the new layout
  • Update CI scripts (sync-results.mjs, generate-report.mjs, build.yml) to use the new paths

Motivation

Normalize ruling expectation file locations so that it is consistent across all analyzers.

Test plan

  • JS/TS ruling tests pass (npm run ruling)
  • Java ruling tests pass (mvn test -Dtest=RulingTest)
  • lits.test.ts unit tests pass

🤖 Generated with Claude Code

Move ruling expectation files from
  its/ruling/src/test/expected/<project>/<language>-<ruleId>.json
to
  its/ruling/src/test/resources/expected/<language>/<project>/<ruleId>.json

This makes the layout consistent across analyzers. The Java ruling
test (LITS-based) reconstructs the flat per-project directory that
LITS expects at build time under target/expected/. The JS/TS ruling
pipeline (lits.ts) and the CI ruling bot scripts are updated to
read and write the new structure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@romainbrenguier
romainbrenguier requested a review from a team September 9, 2026 12:03
@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Sep 9, 2026

Copy link
Copy Markdown

@datadog-sonarsource

datadog-sonarsource Bot commented Sep 9, 2026

Copy link
Copy Markdown

Pipelines

⚠️ Warnings

Your PR has failed checks. Please review the issues below and take necessary action before merging.

🚦 5 Pipeline jobs failed

Build | Build SonarJS on Linux — 🔧 Needs a code fix, caused by this PR

View in Datadog · View in GitHub Actions

Type '{ same: boolean; equal: number; distinct: number; left: number; right: number; differences: number; total: number; totalFiles: number; totalDirs: number; diffSet: DiffSet; }' is missing required properties from type 'Result'.

Build | Build SonarJS on Windows — 🔧 Needs a code fix, caused by this PR

View in Datadog · View in GitHub Actions

Type '{ same: boolean; equal: number; distinct: number; left: number; right: number; differences: number; total: number; totalFiles: number; totalDirs: number; diffSet: DiffSet; }' is missing required properties from type 'Result'.

Build | Knip — 🔧 Needs a code fix, caused by this PR

View in Datadog · View in GitHub Actions

Type '{ same: boolean; equal: number; distinct: number; left: number; right: number; differences: number; total: number; totalFiles: number; totalDirs: number; diffSet: DiffSet; }' is missing properties from type 'Result' in packages/ruling/testProject.ts:131.

View all 5 failed jobs.

📋 Copy fix prompt
CI on my pull request is failing. Help me find and fix the root cause of each failing job below — they were flagged as caused by changes in this PR, so focus on the diff. For each job, explain the failure and propose a fix.

Before you start, set up the Datadog software-delivery tooling so you can
query the CI data yourself:

1. Check whether you already have the Datadog software-delivery MCP tools
   (e.g. a `search_datadog_ci_pipeline_events` tool) and the `unblock-pr` skill.
2. If either is missing, STOP and ask me for permission before installing
   anything. Do not install or run anything until I have said yes.
3. Only with my explicit approval, set up the Datadog software-delivery MCP
   server and skills by following:
     https://docs.datadoghq.com/getting_started/software_delivery_mcp_tools/
   then restart so the skill is picked up.
4. If I decline, skip all of the above and work from the context below alone.

Then run /unblock-pr — it will pull the CI data itself. The job context below is what we already know.

If /unblock-pr is not available — because I declined the setup above, or it did not install — work from the context below instead.

Datadog has already classified this failure as caused by changes in this PR.
Take that as given and work the fix:

1. Locate the change. Diff this branch against its base and find the change
   that produces this error. Explain the mechanism, don't just name a file:
     git fetch origin && git diff $(git merge-base origin/master HEAD)...HEAD
2. Reproduce it locally. Run the failing job's command or test before
   proposing anything.
3. Propose the smallest fix that addresses the root cause — not a workaround,
   not a broadened assertion, not a disabled or skipped test.
4. Re-run the same command to confirm, and say exactly what you ran.
5. If the failure turns out to be intermittent rather than deterministic, say
   so plainly instead of "fixing" it — that is a flaky test, and patching it
   hides the problem.

If the right move is to re-run the job rather than change code, use the job
link in the context below. For GitHub Actions: `gh run rerun <run-id> --failed`,
where the run ID is the number after `/runs/` in that URL (not the trailing
number, which is the job ID).

Branch: romain/normalize-ruling-expectations

Build | Build SonarJS on Linux
Commit: 3309986b15be26022a205dd8c27d36447e395b48
Error (code / build):
Type '{ same: boolean; equal: number; distinct: number; left: number; right: number; differences: number; total: number; totalFiles: number; totalDirs: number; diffSet: DiffSet; }' is missing required properties from type 'Result'.
CI job: https://github.com/SonarSource/SonarJS/actions/runs/34348822192/job/102457522849

Build | Build SonarJS on Windows
Commit: 3309986b15be26022a205dd8c27d36447e395b48
Error (code / build):
Type '{ same: boolean; equal: number; distinct: number; left: number; right: number; differences: number; total: number; totalFiles: number; totalDirs: number; diffSet: DiffSet; }' is missing required properties from type 'Result'.
CI job: https://github.com/SonarSource/SonarJS/actions/runs/34348822192/job/102457522799

Build | Knip
Commit: 3309986b15be26022a205dd8c27d36447e395b48
Error (code / build):
Type '{ same: boolean; equal: number; distinct: number; left: number; right: number; differences: number; total: number; totalFiles: number; totalDirs: number; diffSet: DiffSet; }' is missing properties from type 'Result' in packages/ruling/testProject.ts:131.
CI job: https://github.com/SonarSource/SonarJS/actions/runs/34348822192/job/102457522932

Plus 2 more failing jobs not shown here.

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 3309986 | Docs | View more details | Give us feedback!

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Ruling Report

New issues flagged (622982 issues)

CommentRegexTest

ace/demo/kitchen-sink/doclist.js:126

   124 |         return callback(doc.session);
   125 | 
>  126 |     // TODO: show load screen while waiting
   127 |     var path = doc.path;
   128 |     var parts = path.split("/");

ace/lib/ace/mode/css/csslint.js:1605

  1603 |                 tokenStream.mustMatch(Tokens.IDENT);
  1604 | 
> 1605 |                 // TODO: CSS3 Paged Media says only "left", "center", and "right" are allowed
  1606 | 
  1607 |                 return tokenStream.token().value;

ace/lib/ace/mode/css/csslint.js:4869

  4867 | 
  4868 |             // make sure this token is wanted
> 4869 |             // TODO: check channel
  4870 |             break;
  4871 |         }

ace/lib/ace/mode/css/csslint.js:5797

  5795 |     { name: "CHAR" },
  5796 | 
> 5797 |     // TODO: Needed?
  5798 |     // Not defined as tokens, but might as well be
  5799 |     {

ace/lib/ace/mode/css/csslint.js:7308

  7306 |             tt;
  7307 |         if (index > 0) {
> 7308 |             // TODO: Store 5 somewhere
  7309 |             if (index > 5) {
  7310 |                 throw new Error("Too much lookahead.");

ace/lib/ace/mode/html/saxparser.js:2462

  2460 | 	this.insertionModeName = "";
  2461 | 	this.originalInsertionMode = "";
> 2462 | 	this.inQuirksMode = false; // TODO quirks mode
  2463 | 	this.compatMode = "no quirks";
  2464 | 	this.framesetOk = true;

ace/lib/ace/mode/html/saxparser.js:2980

  2978 | 	modes.inHead.startTagMeta = function(name, attributes) {
  2979 | 		tree.insertSelfClosingElement(name, attributes);
> 2980 | 		// @todo process charset attributes
  2981 | 	};
  2982 | 

ace/lib/ace/mode/html/saxparser.js:3273

  3271 | 		var characters = buffer.takeRemaining();
  3272 | 		characters = characters.replace(/\u0000/g, function(match, index){
> 3273 | 			// @todo position
  3274 | 			tree.parseError("invalid-codepoint");
  3275 | 			return '';

ace/lib/ace/mode/html/saxparser.js:3354

  3352 | 
  3353 | 	modes.inBody.startTagListItem = function(name, attributes) {
> 3354 | 		/// @todo: Fix according to current spec. http://www.w3.org/TR/html5/tree-construction.html#parsing-main-inbody
  3355 | 		var stopNames = {li: ['li'], dd: ['dd', 'dt'], dt: ['dd', 'dt']};
  3356 | 		var stopName = stopNames[name];

ace/lib/ace/mode/html/saxparser.js:3366

  3364 | 			}
  3365 | 
> 3366 | 			// todo isScoping()
  3367 | 			if (node.isSpecial() && node.localName !== 'p' && node.localName !== 'address' && node.localName !== 'div')
  3368 | 				break;

...and 622972 more

📋 View full report

New issues flagged (622982)

CommentRegexTest

Comment on lines 44 to 54
Copy actual to expected:

```bash
cp -R target/actual/ src/test/expected/
cp -R target/expected/ src/test/resources/expected/
```

Review diff:

```bash
diff -rq src/test/expected target/actual
diff -rq src/test/resources/expected target/expected
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Bug: Docs tell you to copy target/expected back over the expectations

RulingTest writes fresh LITS results to target/actual/<project>/<language>-<rule>.json and uses target/expected/<project>/ only as a flat input copy of the committed expectations. So cp -R target/expected/ src/test/resources/expected/ copies the baseline back in the old flat shape (creating expected/<project>/javascript-S100.json next to the new expected/<language>/<project>/S100.json tree) and never picks up the new results, while diff -rq src/test/resources/expected target/expected compares the expectations against a rearranged copy of themselves — it can never show a real ruling change. DEV.md's surviving instruction to copy its/ruling/target/actual/ into its/ruling/src/test/resources/expected/ is wrong for the same reason (flat <language>-<rule>.json names, project-first nesting). Document the actual transposing step (or add a helper/ruling-sync-style script for the Java flow).

SKILL.md: diff the merged baseline against the LITS output, and point at a transposing sync step for updates.:

Review diff:

```bash
diff -rq target/expected target/actual
```

Update the expectations (transposes `<project>/<language>-<ruleId>.json` back to
`<language>/<project>/<ruleId>.json`):

```bash
for f in target/actual/*/*.json; do
  project=$(basename "$(dirname "$f")"); name=$(basename "$f")
  language=${name%%-*}; rule=${name#*-}
  mkdir -p "src/test/resources/expected/$language/$project"
  cp "$f" "src/test/resources/expected/$language/$project/$rule"
done
```
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

Comment on lines +307 to +321
private static Path mergeExpectedDir(String projectKey) throws IOException {
var targetDir = Path.of("target", "expected", projectKey);
Files.createDirectories(targetDir);
for (String language : LANGUAGES) {
var langDir = Path.of("src", "test", "resources", "expected", language, projectKey);
if (Files.isDirectory(langDir)) {
try (var files = Files.list(langDir)) {
files
.filter(p -> p.toString().endsWith(".json"))
.forEach(source -> {
var targetFile = targetDir.resolve(language + "-" + source.getFileName());
try {
Files.copy(source, targetFile, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new RuntimeException("Failed to copy expectation file: " + source, e);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Bug: mergeExpectedDir keeps stale files from previous runs

mergeExpectedDir only calls Files.createDirectories(targetDir) and copies with REPLACE_EXISTING, so any file left in target/expected/<project>/ from an earlier run survives. On a repeated local run after an expectation file was deleted or renamed (exactly the workflow of updating ruling), the stale <language>-<rule>.json is still handed to LITS as sonar.lits.dump.old and reported as a missing-issue difference, failing the test for a file that is no longer in the repository. Clear the directory before copying.

Delete previously merged files before repopulating the directory.:

private static Path mergeExpectedDir(String projectKey) throws IOException {
  var targetDir = Path.of("target", "expected", projectKey);
  if (Files.isDirectory(targetDir)) {
    try (var stale = Files.list(targetDir)) {
      for (var path : stale.toList()) {
        Files.deleteIfExists(path);
      }
    }
  }
  Files.createDirectories(targetDir);
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Sep 9, 2026

Copy link
Copy Markdown
CI failed: TypeScript compilation failure across multiple jobs due to a type mismatch in packages/ruling/testProject.ts where an object is missing properties required by the 'Result' type.

Overview

All 5 analyzed CI jobs failed with a consistent TypeScript compilation error (TS2740) in packages/ruling/testProject.ts during the build and test phases, caused by a type mismatch in the Result interface.

Failures

TypeScript Compilation Error (confidence: high)

  • Type: build
  • Affected jobs: 102457522857, 102457522870, 102457522849, 102457522932
  • Related to change: yes
  • Root cause: Line 131 in packages/ruling/testProject.ts assigns or returns an object missing several required properties (distinctFiles, equalFiles, leftFiles, rightFiles, etc.) demanded by the Result type definition.
  • Suggested fix: Update packages/ruling/testProject.ts to include all required properties of the Result type or adjust the type definitions accordingly.

Summary

  • Change-related failures: 5 build/compilation failures due to a missing type property mismatch introduced in the PR.
  • Infrastructure/flaky failures: 0 infrastructure or flaky failures.
  • Recommended action: The developer must fix the TypeScript error in packages/ruling/testProject.ts by supplying the required Result properties.
Code Review ⚠️ Changes requested 0 resolved / 4 findings

Refactors ruling expectation file paths to normalize layout across analyzers, but the implementation has three critical issues blocking merge: compareByLanguage crashes with ENOENT when one side is missing directories instead of gracefully reporting differences; ruling-debug-script.sh diffs incompatible tree shapes and produces no output even with real mismatches; and Java ruling documentation and RulingTest expect the old flat file layout, causing synced results to never update and diff comparisons to fail. Additionally, mergeExpectedDir retains stale files from previous runs that get incorrectly reported as missing issues.

⚠️ Bug: compareByLanguage crashes with ENOENT when one side is missing

📄 packages/ruling/testProject.ts:104-118 📄 packages/ruling/lits.ts:52-54 📄 packages/ruling/lits.ts:153-158

dir-compare@5.0.0's compare() starts with fs.promises.realpath() on both paths (node_modules/dir-compare/build/src/index.js, compare), so it rejects with ENOENT ... realpath as soon as one of the two directories does not exist. compareByLanguage only skips a language when both sides are missing, and writeIssues creates actual/<project>/<language>/ only when that language produced at least one issue (the unconditional fs.mkdir(actualPath) was also removed from writeResults). Concrete trigger: Ghost has expected/javascript/Ghost but no expected/typescript/Ghost; the moment the analyzer raises a single TS/CSS issue there, actual/Ghost/typescript exists while the expected dir does not and npm run ruling dies with a raw ENOENT instead of reporting the new issues — and symmetrically, if all CSS issues for tailwindcss disappear, actual/tailwindcss/css is never created and the existing expected/css/tailwindcss comparison throws. Compare against an empty placeholder directory instead of throwing so the whole side is reported as left-only/right-only entries.

Substitute an empty scratch directory for the missing side so differences are reported instead of throwing.
const emptyDir = join(actualBase, '.empty');

for (const language of LANGUAGES) {
    const expectedDir = join(expectedBase, language, projectName);
    const actualDir = join(actualPath, language);
    const expectedExists = fs.existsSync(expectedDir);
    const actualExists = fs.existsSync(actualDir);

    if (!expectedExists && !actualExists) {
      continue;
    }

    // dir-compare calls realpath() on both sides and fails on a missing path.
    fs.mkdirSync(emptyDir, { recursive: true });
    const result = await compare(
      expectedExists ? expectedDir : emptyDir,
      actualExists ? actualDir : emptyDir,
      { compareContent: true },
    );
⚠️ Bug: ruling-debug-script.sh diffs mismatched layouts, prints nothing

📄 tools/ruling-debug-script.sh:5-19 📄 .claude/skills/ruling/SKILL.md:30-35

The script only had its expected_dir path updated, but the two trees no longer have comparable shapes: expectations are now expected/<language>/<project>/<rule>.json while packages/ruling/actual is <project>/<language>/<rule>.json. diff -rq therefore emits only top-level Only in ...: javascript / Only in ...: Ghost lines — no differ lines at all — and the second loop's [ -f "$full_path" ] guard rejects those directory entries, so the script silently reports nothing even when ruling has real differences. Transpose the actual tree (or walk language/project pairs) before diffing.

Transpose actual into a temporary <language>/<project> tree before diffing.
expected_dir="its/ruling/src/test/resources/expected"
actual_dir="${1:-$default_actual_dir}"

# Actual results use <project>/<language>/<rule>.json while expectations use
# <language>/<project>/<rule>.json: transpose before diffing.
transposed_dir="$(mktemp -d)"
trap 'rm -rf "$transposed_dir"' EXIT
for project_dir in "$actual_dir"/*/; do
    project="$(basename "$project_dir")"
    for language_dir in "$project_dir"*/; do
        language="$(basename "$language_dir")"
        mkdir -p "$transposed_dir/$language/$project"
        cp "$language_dir"*.json "$transposed_dir/$language/$project/" 2>/dev/null || true
    done
done
actual_dir="$transposed_dir"
⚠️ Bug: Docs tell you to copy target/expected back over the expectations

📄 .claude/skills/ruling/SKILL.md:44-54 📄 docs/DEV.md:192-197 📄 its/ruling/src/test/java/org/sonar/javascript/it/RulingTest.java:275 📄 its/ruling/src/test/java/org/sonar/javascript/it/RulingTest.java:285-289 📄 its/ruling/src/test/java/org/sonar/javascript/it/RulingTest.java:303-317

RulingTest writes fresh LITS results to target/actual/<project>/<language>-<rule>.json and uses target/expected/<project>/ only as a flat input copy of the committed expectations. So cp -R target/expected/ src/test/resources/expected/ copies the baseline back in the old flat shape (creating expected/<project>/javascript-S100.json next to the new expected/<language>/<project>/S100.json tree) and never picks up the new results, while diff -rq src/test/resources/expected target/expected compares the expectations against a rearranged copy of themselves — it can never show a real ruling change. DEV.md's surviving instruction to copy its/ruling/target/actual/ into its/ruling/src/test/resources/expected/ is wrong for the same reason (flat <language>-<rule>.json names, project-first nesting). Document the actual transposing step (or add a helper/ruling-sync-style script for the Java flow).

SKILL.md: diff the merged baseline against the LITS output, and point at a transposing sync step for updates.
Review diff:

```bash
diff -rq target/expected target/actual
```

Update the expectations (transposes `<project>/<language>-<ruleId>.json` back to
`<language>/<project>/<ruleId>.json`):

```bash
for f in target/actual/*/*.json; do
  project=$(basename "$(dirname "$f")"); name=$(basename "$f")
  language=${name%%-*}; rule=${name#*-}
  mkdir -p "src/test/resources/expected/$language/$project"
  cp "$f" "src/test/resources/expected/$language/$project/$rule"
done
```
💡 Bug: mergeExpectedDir keeps stale files from previous runs

📄 its/ruling/src/test/java/org/sonar/javascript/it/RulingTest.java:307-321

mergeExpectedDir only calls Files.createDirectories(targetDir) and copies with REPLACE_EXISTING, so any file left in target/expected/<project>/ from an earlier run survives. On a repeated local run after an expectation file was deleted or renamed (exactly the workflow of updating ruling), the stale <language>-<rule>.json is still handed to LITS as sonar.lits.dump.old and reported as a missing-issue difference, failing the test for a file that is no longer in the repository. Clear the directory before copying.

Delete previously merged files before repopulating the directory.
private static Path mergeExpectedDir(String projectKey) throws IOException {
  var targetDir = Path.of("target", "expected", projectKey);
  if (Files.isDirectory(targetDir)) {
    try (var stale = Files.list(targetDir)) {
      for (var path : stale.toList()) {
        Files.deleteIfExists(path);
      }
    }
  }
  Files.createDirectories(targetDir);
🤖 Prompt for agents
Code Review: Refactors ruling expectation file paths to normalize layout across analyzers, but the implementation has three critical issues blocking merge: `compareByLanguage` crashes with ENOENT when one side is missing directories instead of gracefully reporting differences; `ruling-debug-script.sh` diffs incompatible tree shapes and produces no output even with real mismatches; and Java ruling documentation and `RulingTest` expect the old flat file layout, causing synced results to never update and diff comparisons to fail. Additionally, `mergeExpectedDir` retains stale files from previous runs that get incorrectly reported as missing issues.

1. ⚠️ Bug: compareByLanguage crashes with ENOENT when one side is missing
   Files: packages/ruling/testProject.ts:104-118, packages/ruling/lits.ts:52-54, packages/ruling/lits.ts:153-158

   `dir-compare@5.0.0`'s `compare()` starts with `fs.promises.realpath()` on *both* paths (node_modules/dir-compare/build/src/index.js, `compare`), so it rejects with `ENOENT ... realpath` as soon as one of the two directories does not exist. `compareByLanguage` only skips a language when *both* sides are missing, and `writeIssues` creates `actual/<project>/<language>/` only when that language produced at least one issue (the unconditional `fs.mkdir(actualPath)` was also removed from `writeResults`). Concrete trigger: `Ghost` has `expected/javascript/Ghost` but no `expected/typescript/Ghost`; the moment the analyzer raises a single TS/CSS issue there, `actual/Ghost/typescript` exists while the expected dir does not and `npm run ruling` dies with a raw ENOENT instead of reporting the new issues — and symmetrically, if all CSS issues for `tailwindcss` disappear, `actual/tailwindcss/css` is never created and the existing `expected/css/tailwindcss` comparison throws. Compare against an empty placeholder directory instead of throwing so the whole side is reported as left-only/right-only entries.

   Fix (Substitute an empty scratch directory for the missing side so differences are reported instead of throwing.):
   const emptyDir = join(actualBase, '.empty');
   
   for (const language of LANGUAGES) {
       const expectedDir = join(expectedBase, language, projectName);
       const actualDir = join(actualPath, language);
       const expectedExists = fs.existsSync(expectedDir);
       const actualExists = fs.existsSync(actualDir);
   
       if (!expectedExists && !actualExists) {
         continue;
       }
   
       // dir-compare calls realpath() on both sides and fails on a missing path.
       fs.mkdirSync(emptyDir, { recursive: true });
       const result = await compare(
         expectedExists ? expectedDir : emptyDir,
         actualExists ? actualDir : emptyDir,
         { compareContent: true },
       );

2. ⚠️ Bug: ruling-debug-script.sh diffs mismatched layouts, prints nothing
   Files: tools/ruling-debug-script.sh:5-19, .claude/skills/ruling/SKILL.md:30-35

   The script only had its `expected_dir` path updated, but the two trees no longer have comparable shapes: expectations are now `expected/<language>/<project>/<rule>.json` while `packages/ruling/actual` is `<project>/<language>/<rule>.json`. `diff -rq` therefore emits only top-level `Only in ...: javascript` / `Only in ...: Ghost` lines — no `differ` lines at all — and the second loop's `[ -f "$full_path" ]` guard rejects those directory entries, so the script silently reports nothing even when ruling has real differences. Transpose the actual tree (or walk language/project pairs) before diffing.

   Fix (Transpose actual into a temporary <language>/<project> tree before diffing.):
   expected_dir="its/ruling/src/test/resources/expected"
   actual_dir="${1:-$default_actual_dir}"
   
   # Actual results use <project>/<language>/<rule>.json while expectations use
   # <language>/<project>/<rule>.json: transpose before diffing.
   transposed_dir="$(mktemp -d)"
   trap 'rm -rf "$transposed_dir"' EXIT
   for project_dir in "$actual_dir"/*/; do
       project="$(basename "$project_dir")"
       for language_dir in "$project_dir"*/; do
           language="$(basename "$language_dir")"
           mkdir -p "$transposed_dir/$language/$project"
           cp "$language_dir"*.json "$transposed_dir/$language/$project/" 2>/dev/null || true
       done
   done
   actual_dir="$transposed_dir"

3. ⚠️ Bug: Docs tell you to copy target/expected back over the expectations
   Files: .claude/skills/ruling/SKILL.md:44-54, docs/DEV.md:192-197, its/ruling/src/test/java/org/sonar/javascript/it/RulingTest.java:275, its/ruling/src/test/java/org/sonar/javascript/it/RulingTest.java:285-289, its/ruling/src/test/java/org/sonar/javascript/it/RulingTest.java:303-317

   `RulingTest` writes fresh LITS results to `target/actual/<project>/<language>-<rule>.json` and uses `target/expected/<project>/` only as a flat *input* copy of the committed expectations. So `cp -R target/expected/ src/test/resources/expected/` copies the baseline back in the old flat shape (creating `expected/<project>/javascript-S100.json` next to the new `expected/<language>/<project>/S100.json` tree) and never picks up the new results, while `diff -rq src/test/resources/expected target/expected` compares the expectations against a rearranged copy of themselves — it can never show a real ruling change. DEV.md's surviving instruction to copy `its/ruling/target/actual/` into `its/ruling/src/test/resources/expected/` is wrong for the same reason (flat `<language>-<rule>.json` names, project-first nesting). Document the actual transposing step (or add a helper/`ruling-sync`-style script for the Java flow).

   Fix (SKILL.md: diff the merged baseline against the LITS output, and point at a transposing sync step for updates.):
   Review diff:
   
   ```bash
   diff -rq target/expected target/actual
   ```
   
   Update the expectations (transposes `<project>/<language>-<ruleId>.json` back to
   `<language>/<project>/<ruleId>.json`):
   
   ```bash
   for f in target/actual/*/*.json; do
     project=$(basename "$(dirname "$f")"); name=$(basename "$f")
     language=${name%%-*}; rule=${name#*-}
     mkdir -p "src/test/resources/expected/$language/$project"
     cp "$f" "src/test/resources/expected/$language/$project/$rule"
   done
   ```

4. 💡 Bug: mergeExpectedDir keeps stale files from previous runs
   Files: its/ruling/src/test/java/org/sonar/javascript/it/RulingTest.java:307-321

   `mergeExpectedDir` only calls `Files.createDirectories(targetDir)` and copies with `REPLACE_EXISTING`, so any file left in `target/expected/<project>/` from an earlier run survives. On a repeated local run after an expectation file was deleted or renamed (exactly the workflow of updating ruling), the stale `<language>-<rule>.json` is still handed to LITS as `sonar.lits.dump.old` and reported as a missing-issue difference, failing the test for a file that is no longer in the repository. Clear the directory before copying.

   Fix (Delete previously merged files before repopulating the directory.):
   private static Path mergeExpectedDir(String projectKey) throws IOException {
     var targetDir = Path.of("target", "expected", projectKey);
     if (Files.isDirectory(targetDir)) {
       try (var stale = Files.list(targetDir)) {
         for (var path : stale.toList()) {
           Files.deleteIfExists(path);
         }
       }
     }
     Files.createDirectories(targetDir);

Tip

Comment Gitar fix CI or enable auto-apply: gitar auto-apply:on

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.
Unblock → Override a blocking verdict and allow merging.

Comment with these commands to change the behavior for this request:

Auto-apply Compact Unblock
gitar auto-apply:on         
gitar display:verbose         
gitar unblock         

Was this helpful? React with 👍 / 👎 | Gitar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant