Eslint-plugin-sonarjs: S125 (no-commented-code) never reports under parserOptions.projectService

What language is this for?

TypeScript (the same applies to JavaScript).

Which rule?

S125 — sonarjs/no-commented-code, “sections of code should not be commented out”.

Why do you believe it’s a false-negative?

sonarjs/no-commented-code reports nothing at all when the host project uses typescript-eslint’s project-service mode (parserOptions.projectService). The rule stays silent even when configured as error, and nothing in the output indicates it is inert. The same project, same plugin version, reports normally under the legacy parserOptions.project.

The cause is in the synthetic re-parse the rule uses to decide whether a comment contains code, and it affects every comment in every file, so in this mode the rule can never raise an issue. Details and a one-line fix are below. 4.2.0 is the latest published release and the same code is on current master, so a version bump is not a workaround.

Are you using SonarQube Cloud / SonarQube Server / Community Build / SonarQube for IDE?

None of these, and not in connected mode. This is the standalone eslint-plugin-sonarjs npm package, version 4.2.0 (also reproduced on 4.1.0), run from the ESLint CLI.

This looks specific to the ESLint plugin, which re-parses using the host project’s own parserOptions. Server-side S125 runs inside Sonar’s analyzer and does not go through a user ESLint config, so I don’t believe SonarQube Server or Cloud is affected. I have not tested that directly.

How can we reproduce the problem?

Minimal repo: GitHub - stevensacks/sonarjs-s125-projectservice-repro: Minimal reproduction: eslint-plugin-sonarjs S125 (no-commented-code) never reports under parserOptions.projectService · GitHub

npm install, then:

$ ./node_modules/.bin/eslint src/example.ts
$ echo $?
0

Expected: one sonarjs/no-commented-code issue on the commented-out block at src/example.ts:3. Actual: no output, exit code 0.

The rule is enabled — eslint --print-config src/example.ts reports "sonarjs/no-commented-code": [2] with parserOptions: {"projectService": true, ...}.

src/example.ts:

export const activeCode = (a: number, b: number): number => a + b;

// const total = computeTotal(amount, rate);
// if (total > 0) {
//   return total * 2;
// }

eslint.config.mjs:

import sonarjs from 'eslint-plugin-sonarjs';
import tseslint from 'typescript-eslint';

export default [
  {
    files: ['src/**/*.ts'],
    languageOptions: {
      parser: tseslint.parser,
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
    plugins: {sonarjs},
    rules: {
      'sonarjs/no-commented-code': 'error',
    },
  },
];

tsconfig.json is a plain include: ["src"] with strict: true and noEmit: true.

Two toggles in the repo isolate the cause.

Toggle A — make the synthetic filename resolvable, patching nothing. Add a real placeholder.ts at the project root containing export {}; and add it to the tsconfig include. Same command, same plugin version, no plugin code modified:

$ ./node_modules/.bin/eslint src/example.ts
/path/to/repro/src/example.ts
  3:1  error  Remove this commented out code  sonarjs/no-commented-code

✖ 1 problem (1 error, 0 warnings)
$ echo $?
1

Toggle B — legacy project mode. Replace projectService: true with project: ['./tsconfig.json'], everything else identical:

$ ./node_modules/.bin/eslint --config eslint.legacy.mjs src/example.ts
/path/to/repro/src/example.ts
  3:1  error  Remove this commented out code  sonarjs/no-commented-code

✖ 1 problem (1 error, 0 warnings)
$ echo $?
1

The rule also reports normally with no project awareness configured at all (languageOptions: {parser: tseslint.parser} and nothing else), which confirms the commented block clears the rule’s code-recognition heuristic and that the silence in the first run is not about the comment itself.

Root cause

packages/analysis/src/jsts/rules/S125/rule.ts (line numbers from master at commit 65b0c2a).

S125 decides whether a comment contains code by re-parsing the comment body as a standalone snippet under a synthetic filename, placeholder<ext>. containsCode() (line 173) builds the parser options at lines 178-183:

const options = {
  ...context.languageOptions?.parserOptions,
  filePath: `placeholder${path.extname(context.filename)}`,
  programs: undefined,
  project: undefined,
};

programs and project are cleared, but projectService survives the spread. The project service is therefore asked about placeholder.ts, which is not a file in the project, and rejects it. Calling the parser directly with exactly these options gives:

Error: /path/to/repro/placeholder.ts was not found by the project service.
Consider either including it in the tsconfig.json or including it in allowDefaultProject.

parseWithRuleParser() (line 256) swallows that throw at line 265:

} catch {
  return null;
}

containsCode() reads the resulting null as “not code” and returns false. The repo includes a diagnose.mjs that prints the swallowed error and shows the same snippet parsing successfully once projectService is cleared, if it’s useful to see the failing step without running ESLint.

Impact

A rule configured as error reports nothing and gives no indication that it is disabled in effect, so commented-out code passes review silently. Project-service mode is the direction typescript-eslint recommends, and some shared configs enable it by default, so a project can be affected without anyone having written projectService themselves.

Suggested fix

 const options = {
   ...context.languageOptions?.parserOptions,
   filePath: `placeholder${path.extname(context.filename)}`,
   programs: undefined,
   project: undefined,
+  projectService: undefined,
 };

The rule only needs to parse the snippet, never to type-check it, so all project awareness can be dropped.

Separately, and as the reason this is easy to miss: the bare catch { return null; } at line 265 makes a parser incompatibility indistinguishable from “this comment is not code”. Surfacing that error somewhere, even behind a debug flag, would make the next such incompatibility visible instead of silent.

Versions

eslint-plugin-sonarjs 4.2.0 (also reproduced on 4.1.0)
eslint 9.39.5
typescript-eslint 8.65.0
typescript 5.9.3
node v24.18.0
macOS 26.5.2 arm64

Hi @stevensacks,

Welcome to the Community!

Thanks for your feedback and report on this issue. I’ve flagged it for our internal teams to review.

Best regards,

Stevan

Hi @stevensacks,

Thank you for reporting this. We confirmed that this configuration causes S125 to miss commented-out code, so this is a real false negative.

The fix is tracked in JS-2228.

We appreciate the clear reproduction steps, they were very helpful.
-Francois