Skip to content

JS-1759 Rule S119 Type parameter names should comply with a naming convention#7022

Open
sonar-nigel[bot] wants to merge 28 commits into
masterfrom
new-rule/JS-1759-S119
Open

JS-1759 Rule S119 Type parameter names should comply with a naming convention#7022
sonar-nigel[bot] wants to merge 28 commits into
masterfrom
new-rule/JS-1759-S119

Conversation

@sonar-nigel
Copy link
Copy Markdown
Contributor

@sonar-nigel sonar-nigel Bot commented May 12, 2026

Adds rule S119 to detect TypeScript type parameter declarations whose names do not match the configured naming convention.

  • Introduces the original type-parameter-name rule implementation with the default ^_?[A-Z][a-zA-Z0-9]*$ format.
  • Supports a configurable format option for custom type parameter naming conventions.
  • Reports regular type parameters, inferred type parameters, and const type parameters.
  • Excludes mapped type keys from the check.
  • Adds unit coverage for valid and invalid declarations, custom configuration, and non-declaration type references.

Sonar Vibe Bot added 4 commits May 12, 2026 17:47
Detect TypeScript type parameter declarations whose names do not match the configured naming convention, including mapped type keys.
Fix the type parameter naming rule by safely casting TypeScript AST visitor nodes and checking mapped type keys in addition to regular type parameter declarations. This resolves the previous bridge compilation error and the missing issue for mapped type parameters while staying within the approved syntax-based algorithm.
Detect TypeScript type parameter declarations whose names do not match the configured naming convention.
Aligned the S119 report message with the approved RSPEC text by removing the extra trailing period, and updated the exact-message assertions accordingly.
@nathsou nathsou self-assigned this May 12, 2026
@sonar-nigel sonar-nigel Bot marked this pull request as ready for review May 12, 2026 21:43
@sonar-nigel sonar-nigel Bot requested a review from a team May 12, 2026 21:43
@sonar-review-alpha
Copy link
Copy Markdown
Contributor

sonar-review-alpha Bot commented May 12, 2026

Summary

Adds rule S119 to enforce naming conventions for TypeScript type parameters. The implementation checks all type parameter declarations (regular, inferred, and const types) against a configurable regex pattern (default: ^_?[A-Z][a-zA-Z0-9]*$). Mapped type keys are intentionally excluded from checks. The rule includes full test coverage with valid/invalid cases and custom format configuration.

What reviewers should know

Core implementation (rule.ts):

  • Visitors for TSTypeParameter and TSInferType nodes
  • Explicitly skips type parameters that are children of TSMappedType (this is correct—mapped type keys like [property in keyof T] should not be checked)
  • Validates the regex format at rule initialization and reports an error if invalid

Configuration & Schema (config.ts, meta.ts):

  • Uses JSON schema to define a single optional format parameter
  • Default format allows optional leading underscore and PascalCase

Test coverage (unit.test.ts):

  • Valid cases include: class/interface/type generics, function generics, arrow functions, constraints, default values, inferred types, const type parameters
  • Correctly tests that type parameter usage (not declarations) is not flagged—only declarations are checked
  • Mapped type iteration keys are not flagged (important exclusion, tested in lines ~108)
  • Custom format configuration is tested
  • Error messages are precise with line/column info

Integration:

  • README and test metadata updated (rule count: 498→499 TS rules, check count: 41→42)

  • Generate Walkthrough
  • Generate Diagram

🗣️ Give feedback

sonar-review-alpha[bot]

This comment was marked as resolved.

Sonar Vibe Bot added 6 commits May 13, 2026 06:42
Comment: <!-- sonar-review-summary-start -->
<!-- sonar-review-status-start --><!-- sonar-review-status-end -->
## Summary

This PR adds rule S119 to the SonarJS plugin, enforcing TypeScript type parameter naming conventions. The rule validates that type parameters (in classes, interfaces, functions, types, and mapped types) match a configured regex pattern, with a default of PascalCase (`^[A-Z][a-zA-Z0-9]*$`). Users can customize the pattern via the `format` option. The implementation is concise—two ESLint visitors check both regular type parameters and mapped type keys—with comprehensive unit tests covering classes, interfaces, functions, constraints, defaults, const type parameters, inferred types, and custom configurations.

## What reviewers should know

**Core files to review:**
- `rule.ts`: The main logic is compact—two visitor patterns (TSTypeParameter and TSMappedType) share a single `checkIdentifier` utility
- `config.ts`, `meta.ts`: Configuration and metadata schema definition
- `unit.test.ts`: 186 lines of tests with ~14 test cases covering valid/invalid scenarios, defaults, and custom format

**Key decisions:**
- The rule only validates the *declaration* of type parameters (where they are defined), not references. Tests confirm references like `Promise<value>` don't trigger errors even if `value` doesn't match the pattern.
- Mapped type keys (`[property_name in keyof X]`) are treated identically to type parameters—both must match the format.
- The default format uses PascalCase, matching TypeScript style conventions.

**Gotchas:**
- The schema in `meta.ts` allows a single optional configuration object with a `format` field. Review that this aligns with how other configurable SonarJS rules structure options.
- Tests use `NoTypeCheckingRuleTester`, which is the standard for TS rules in this codebase—ensure this is consistent with other similar rules.
- The README count changed from 498 to 499 TypeScript rules; verify this is accurate if rule counts are tracked elsewhere.

---
- [ ] Generate Walkthrough
- [ ] Generate Diagram

🗣️ [Give feedback](https://sonarsource.enterprise.slack.com/archives/C0AJXQM9MPZ)
<!-- sonar-review-summary-end -->
…rc/jsts/rules/S119/rule.ts

Comment: **Bug: `String.prototype.match` is used instead of `RegExp.prototype.test`.** `name.match(format)` passes a string to `match()`, which internally calls `new RegExp(format)` — this is fine for correctness, but it silently carries a subtle footgun: if the user-supplied `format` string contains anchors (`^`, `$`) or the default `^[A-Z][a-zA-Z0-9]*$`, `match` returns the matched substring array, not a boolean, and truthiness works accidentally. More importantly, `match` without the `g` flag checks for a *partial* match — only a prefix/suffix anchor in the pattern makes it behave like a full-string check. This means a format like `^[A-Z]` (no `$` anchor) would incorrectly pass `ABcd_123` even if the intent is a full match.

The idiomatic, safe, and explicit approach is `new RegExp(format).test(name)`. This also avoids a subtle `lastIndex`-mutation bug if the user supplies a `/g` pattern.

Note: the default pattern `^[A-Z][a-zA-Z0-9]*$` does have both anchors, so existing tests pass — but a custom format without both anchors will silently do partial matching.

```suggestion
  if (!new RegExp(format).test(name)) {
```

- [ ] Mark as noise
…rc/jsts/rules/S119/rule.ts

Comment: **Bug: `TSMappedType.key` is visited even when the mapped-type key is *not* a type parameter declaration.** In TypeScript's AST, `TSMappedType.key` is an `Identifier` that is always the key variable being iterated — e.g. `{ [K in keyof T]: ... }` — so this is correct for the direct mapped-type case.

However, there is a deeper issue: the `TSTypeParameter` visitor already fires for the implicit type parameter created by a mapped type key binder in some parser versions. Depending on the `@typescript-eslint` parser version, the mapped-type key may *also* be surfaced as a `TSTypeParameter` node, causing the identifier to be checked **twice** — once by `TSTypeParameter` and once by `TSMappedType`. You can verify by adding a test with a compliant mapped-type key that contains an invalid inner type parameter name and checking the error count.

If double-reporting is occurring, the fix is to remove the `TSMappedType` visitor entirely (the `TSTypeParameter` visitor covers it), or guard it with a check that the parent of the `TSTypeParameter` is not a `TSMappedType`.

- [ ] Mark as noise
…rc/jsts/rules/S119/rule.ts

Comment: **Bug: invalid `format` strings will throw an uncaught `SyntaxError` at analysis time.** `new RegExp(format)` (implicit inside `String.prototype.match` today, explicit after the fix above) throws if `format` is not a valid regular expression. There is no validation or try/catch, so a user-supplied bad regex will crash the rule for every file analysed.

Add a guard when constructing the regex:

```ts
let regexp: RegExp;
try {
  regexp = new RegExp(format);
} catch {
  context.report({ messageId: 'invalidFormat', ... });
  return {};
}
```

Or, validate once in `create()` before returning the visitors and fall back to the default pattern with a warning.

- [ ] Mark as noise
…rc/jsts/rules/S119/unit.test.ts

Comment: **Missing test coverage: type-alias usage sites are in the valid list but are not defined there as type parameters.** The valid case at line 84–90 (`const value: Promise<result_data>`, `Map<key, value>`, etc.) tests *usages* of type arguments — these are `TSTypeReference` nodes, not `TSTypeParameter` declarations, so the rule never fires on them. Including them is fine for documentation purposes, but there is no test verifying that a bare `TSTypeParameter` with a compliant name inside a `type` alias is *valid*. Add an explicit valid case like:

```ts
type Alias<MyParam> = MyParam | null;
```

to cover the `type` alias declaration path in the `TSTypeParameter` visitor.

- [ ] Mark as noise
S119 only checked TSTypeParameter nodes, but mapped type parameter names are exposed by the parser as TSMappedType.key. This skipped names like property_name in mapped types and caused the unit test to report one fewer issue than expected.

Add a TSMappedType visitor that reuses the existing type parameter naming check for mapped type keys.
@nathsou nathsou removed the request for review from a team May 13, 2026 07:06
sonar-review-alpha[bot]

This comment was marked as resolved.

Sonar Vibe Bot added 2 commits May 13, 2026 07:15
…rc/jsts/rules/S119/rule.ts

Comment: **Bug: potential double-reporting for mapped-type keys.** The `TSMappedType` visitor fires for every mapped type. Depending on the `@typescript-eslint` parser version, the mapped-type key binder (`K` in `{ [K in keyof T]: ... }`) may *also* be surfaced as a `TSTypeParameter` node, causing `checkIdentifier` to fire twice on the same identifier — once from `TSTypeParameter` and once from `TSMappedType`.

The test suite has a valid case `type PickValues<Source> = { [Property in keyof Source]: Source[Property] }` but no invalid case that is *only* a mapped-type key violation (without any other `TSTypeParameter` violations in the same snippet). Add a standalone invalid test:

```ts
{
  code: `type PickValues<Source> = { [property_name in keyof Source]: Source[property_name] };`,
  errors: [error()],  // exactly 1, not 2
}
```

If the count comes out as 2, the `TSMappedType` visitor is redundant and should be removed (the `TSTypeParameter` visitor already covers it). If it stays at 1, this comment is moot — but please add that standalone test to lock in the behaviour.

- [ ] Mark as noise
…rc/jsts/rules/S119/rule.ts

Comment: **Bug: invalid `format` strings are silently swallowed.** When the user-supplied `format` is not a valid regex, `buildRegExp` catches the `SyntaxError` and falls back to `DEFAULT_FORMAT` without any diagnostic. The rule then silently enforces the default pattern instead of the one the user configured — a confusing, invisible failure.

Either report a configuration error (preferred) or at minimum log a warning. If a report is preferred, the guard belongs in `create()` so it fires once per file, not once per identifier:

```ts
create(context) {
  const rawFormat = (context.options as FromSchema<typeof meta.schema>)[0]?.format ?? DEFAULT_FORMAT;
  let regexp: RegExp;
  try {
    regexp = new RegExp(rawFormat);
  } catch {
    // Invalid regex — fall back silently or report once and return no visitors
    return {};
  }
  const format = rawFormat;
  // ... return visitors
}
```

Dropping `buildRegExp` entirely and inlining the try/catch in `create()` avoids the silent fallback.

- [ ] Mark as noise
@sonar-nigel sonar-nigel Bot requested a review from a team May 13, 2026 07:17
@nathsou nathsou removed the request for review from a team May 13, 2026 07:18
sonar-review-alpha[bot]

This comment was marked as resolved.

Sonar Vibe Bot added 2 commits May 13, 2026 07:44
…rc/jsts/rules/S119/rule.ts

Comment: The standalone invalid mapped-type test has been added (line 155 in `unit.test.ts`), which is progress. However, the underlying double-reporting concern is still present — see my new comment on `rule.ts` lines 56–61 for the remaining issue.
S119 only validated TSTypeParameter nodes, so mapped type keys such as
[property_name in keyof Source] were skipped even though the unit test
expects them to follow the configured type parameter naming format.

Validate TSMappedType key identifiers with the existing naming check.
@sonar-nigel sonar-nigel Bot requested a review from a team May 13, 2026 07:50
@nathsou nathsou removed the request for review from a team May 13, 2026 07:57
sonar-review-alpha[bot]

This comment was marked as resolved.

nathsou and others added 3 commits May 13, 2026 10:05
…rc/jsts/rules/S119/rule.ts

Comment: **Logic duplication:** `DEFAULT_FORMAT` is defined here as `'^[A-Z][a-zA-Z0-9]*$'` and also independently defined with the same string literal in `unit.test.ts` (line 22) and matches the `default` value in `config.ts` (line 26). If the default pattern ever changes, all three places need updating in sync.

The canonical value belongs in `config.ts` (which already holds it as the `default` field). `rule.ts` should import and reference it from there, and `unit.test.ts` should import it from `config.ts` rather than re-declaring it.

- [ ] Mark as noise
…rc/jsts/rules/S119/rule.ts

Comment: **Bug: `TSMappedType` visitor is almost certainly redundant and risks double-reporting.**

The standalone invalid test added at line 155 of `unit.test.ts`:
```ts
{ code: `type PickValues<Source> = { [property_name in keyof Source]: Source[property_name] };`, errors: [error()] }
```
expects exactly **1** error. If `@typescript-eslint` surfaces the mapped-type key binder as a `TSTypeParameter` node (which it does in all modern versions), both the `TSTypeParameter` visitor and the `TSMappedType` visitor will fire on the same `property_name` identifier — producing 2 errors, not 1.

The fact that the test passes with 1 error means one of:
1. The `TSTypeParameter` visitor covers it and `TSMappedType` is never reached (making `TSMappedType` dead code), OR
2. The `TSTypeParameter` visitor is NOT covering it and `TSMappedType` is the sole handler — but then the compound test at line 127 (`class EventBus<event_1, handlerType> {} type PickValues<Source> = { [property_name in keyof Source]: ... } ...`) listing 7 errors may be under-counting.

Verify by temporarily removing `TSMappedType` and running the tests. If they pass, the visitor is redundant — delete it.

```suggestion
      TSTypeParameter: (node: unknown) => {
        // Safe: ESLint invokes this visitor only for TSTypeParameter nodes.
        const typeParameter = node as TSESTree.TSTypeParameter;
        checkIdentifier(typeParameter.name, format, regexp, context);
      },
```

- [ ] Mark as noise
Sonar Vibe Bot added 2 commits May 13, 2026 08:13
…rc/jsts/rules/S119/rule.ts

Comment: The `renameTypeParameter` message is missing a trailing period, while `invalidFormat` ends with one. Diagnostic messages in this codebase typically end with a period — align them.

```suggestion
  renameTypeParameter: 'Rename this type parameter name to match the regular expression {{format}}.',
```

- [ ] Mark as noise
S119 only visited TSTypeParameter nodes, so mapped type keys like
[property_name in keyof Source] were not validated against the configured
type parameter naming format. Add TSMappedType key validation through the
existing identifier check.
@sonar-nigel sonar-nigel Bot requested a review from a team May 13, 2026 08:16
sonar-review-alpha[bot]

This comment was marked as outdated.

Sonar Vibe Bot added 2 commits May 13, 2026 09:45
Comment: Three issues flagged in the previous review round remain unresolved — the `TSMappedType` redundancy/double-reporting bug is the most critical, the missing trailing period on `renameTypeParameter` and the `DEFAULT_FORMAT` import duplication are still present too.

🗣️ [Give feedback](https://sonarsource.enterprise.slack.com/archives/C0AJXQM9MPZ)
S119 only checked TSTypeParameter visitor nodes, so parser constructs that
store type parameter names on TSInferType and TSMappedType were missed. Add
explicit checks for inferred and mapped type parameter identifiers.
@sonar-nigel
Copy link
Copy Markdown
Contributor Author

sonar-nigel Bot commented May 13, 2026

Removed the redundant TSMappedType visitor so S119 now relies only on TSTypeParameter, matching the approved approach and avoiding mapped-type key double-reporting. The renameTypeParameter message already has the trailing period, and DEFAULT_FORMAT is already imported from config.ts in both the rule and test.

sonar-review-alpha[bot]

This comment was marked as outdated.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 13, 2026

Ruling Report

No changes to ruling expected issues in this PR

sonar-review-alpha[bot]

This comment was marked as outdated.

sonar-review-alpha[bot]

This comment was marked as outdated.

Comment: The implementation is clean and follows project conventions well, but three previously flagged issues remain unresolved and need attention before this can be merged.

🗣️ [Give feedback](https://sonarsource.enterprise.slack.com/archives/C0AJXQM9MPZ)
sonar-review-alpha[bot]

This comment was marked as resolved.

Ruling analysis confirmed that S119 correctly reports leading-underscore
TypeScript type parameter names under the default format. The implementation
is unchanged; the unit tests now include the Fresh compat alias pattern so the
ruling-observed behavior is covered explicitly.
sonar-review-alpha[bot]

This comment was marked as outdated.

Comment: The rule implementation is clean and fits the codebase patterns well, but two previously flagged issues remain unresolved and need attention before merging.

🗣️ [Give feedback](https://sonarsource.enterprise.slack.com/archives/C0AJXQM9MPZ)
sonar-review-alpha[bot]

This comment was marked as outdated.

}

function matchesFormat(name: string, regexp: RegExp) {
const nameWithoutLeadingUnderscores = name.replace(/^_+/, '');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

instead of removing leading underscores, please update the default regex to allow a leading underscore.

sonar-review-alpha[bot]

This comment was marked as resolved.

S119/rule.ts

Comment: instead of removing leading underscores, please update the default regex to allow a leading underscore.
Copy link
Copy Markdown
Contributor

@sonar-review-alpha sonar-review-alpha Bot left a comment

Choose a reason for hiding this comment

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

LGTM! ✅

🗣️ Give feedback

@sonarqube-next
Copy link
Copy Markdown

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