Skip to content

JS-483 Filter rules by detected ES version#6573

Merged
zglicz merged 6 commits intomasterfrom
js-1452/ecma-version-rule-filtering
Mar 16, 2026
Merged

JS-483 Filter rules by detected ES version#6573
zglicz merged 6 commits intomasterfrom
js-1452/ecma-version-rule-filtering

Conversation

@zglicz
Copy link
Copy Markdown
Contributor

@zglicz zglicz commented Mar 12, 2026

Summary

  • Rules tagged with an ES version in RSPEC (e.g. es2022) are now automatically disabled when the project's detected ES version is lower than required
  • Builds on the ES version detection from computeLibJson (target + package.json signals)
  • ~60 rules now carry a requiredEcmaVersion field in their generated metadata

Changes

  • tools/generate-eslint-meta.ts + template: extract es20XX tag from RSPEC, emit requiredEcmaVersion in every generated-meta.ts
  • options.ts: add esLibToYear() helper to read ES year from normalized lib filenames
  • analyzeWithProgram.ts / analyzeWithIncrementalProgram.ts: extract detectedEsYear after program creation and thread it down the call chain
  • analyzeFile.ts / analysis.ts / analyzer.ts: propagate detectedEsYear as optional field
  • linter.ts: filter rules by requiredEcmaVersion in getRulesForFile(), include detectedEsYear in rules cache key

Test plan

  • Rules tagged es2022 (e.g. S7755 prefer-at) are disabled for projects detected as ES2020 or lower
  • Rules tagged es2015 are disabled for projects with no ES signals (falls back to esnext → all rules enabled)
  • When detectedEsYear is null (esnext fallback or no program), all rules remain enabled

🤖 Generated with Claude Code

…n signals

Rules tagged with an ES version in RSPEC (e.g. es2022) are now automatically
disabled when the project's detected ES version is lower than required.

- tools/generate-eslint-meta.ts: extract es20XX tag from RSPEC, emit
  requiredEcmaVersion field in generated-meta.ts
- tools/templates/ts/generated-meta.template: add requiredEcmaVersion export
- Regenerate all generated-meta.ts files (~60 rules now carry the field)
- options.ts: add esLibToYear() helper to extract ES year from normalized lib
- analyzeWithProgram.ts / analyzeWithIncrementalProgram.ts: extract
  detectedEsYear from program lib after program creation, thread it down
- analyzeFile.ts / analysis.ts / analyzer.ts: propagate detectedEsYear
- linter.ts: filter rules by requiredEcmaVersion in getRulesForFile(),
  include detectedEsYear in rules cache key

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@zglicz zglicz requested a review from a team March 12, 2026 14:29
@hashicorp-vault-sonar-prod
Copy link
Copy Markdown

hashicorp-vault-sonar-prod Bot commented Mar 12, 2026

JS-483

- options.test.ts: tests for esLibToYear covering normal years,
  esnext, undefined, and empty lib arrays
- linter/index.test.ts: tests for getRulesForFile ES version filtering
  using S7755 (requiredEcmaVersion=2022) — disabled below ES2022,
  enabled at ES2022, and enabled when no year detected (esnext fallback)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 12, 2026

Ruling Report

Code no longer flagged (778 issues)

S6606

TypeScript/src/harness/harness.ts:1144

  1142 |             }
  1143 | 
> 1144 |             const useCaseSensitiveFileNames = options.useCaseSensitiveFileNames !== undefined ? options.useCaseSensitiveFileNames : Harness.IO.useCaseSensitiveFileNames();
  1145 |             const programFiles: TestFile[] = inputFiles.slice();
  1146 |             // Files from built\local that are requested by test "@includeBuiltFiles" to be in the context.

TypeScript/src/harness/harness.ts:1912

  1910 | 
  1911 |             /* tslint:disable:no-null-keyword */
> 1912 |             if (actual === null) {
  1913 |             /* tslint:enable:no-null-keyword */
  1914 |                 actual = NoContent;

TypeScript/src/harness/projectsRunner.ts:159

   157 |             function getSourceFileText(fileName: string): string {
   158 |                 const text = getSourceFileTextImpl(fileName);
>  159 |                 return text !== undefined ? text : getSourceFileTextImpl(ts.getNormalizedAbsolutePath(fileName, getCurrentDirectory()));
   160 |             }
   161 | 

TypeScript/src/server/editorServices.ts:980

   978 |                 compilerOptions,
   979 |                 /*languageServiceEnabled*/ !this.exceededTotalSizeLimitForNonTsFiles(projectFileName, compilerOptions, files, externalFilePropertyReader),
>  980 |                 options.compileOnSave === undefined ? true : options.compileOnSave);
   981 | 
   982 |             this.addFilesToProjectAndUpdateGraph(project, files, externalFilePropertyReader, /*clientFileName*/ undefined, typeAcquisition, /*configFileErrors*/ undefined);

TypeScript/src/server/editorServices.ts:1008

  1006 |                 projectOptions.wildcardDirectories,
  1007 |                 /*languageServiceEnabled*/ !sizeLimitExceeded,
> 1008 |                 projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave);
  1009 | 
  1010 |             this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors);

TypeScript/src/server/editorServices.ts:1212

  1210 | 
  1211 |                     if (openedByClient) {
> 1212 |                         if (fileContent === undefined) {
  1213 |                             // if file is opened by client and its content is not specified - use file text
  1214 |                             fileContent = this.host.readFile(fileName) || "";

TypeScript/src/server/project.ts:1092

  1090 |             }
  1091 |             else {
> 1092 |                 if (newTypeAcquisition.enable === undefined) {
  1093 |                     // if autoDiscovery was not specified by the caller - set it based on the content of the project
  1094 |                     newTypeAcquisition.enable = allRootFilesAreJsOrDts(this);

TypeScript/src/server/scriptInfo.ts:129

   127 |         private switchToScriptVersionCache(newText?: string): ScriptVersionCache {
   128 |             if (!this.svc) {
>  129 |                 this.svc = ScriptVersionCache.fromString(this.host, newText !== undefined ? newText : this.getOrLoadText());
   130 |                 this.svcVersion++;
   131 |                 this.text = undefined;

TypeScript/src/server/session.ts:1009

  1007 | 
  1008 |         private getPosition(args: protocol.FileLocationRequestArgs, scriptInfo: ScriptInfo): number {
> 1009 |             return args.position !== undefined ? args.position : scriptInfo.lineOffsetToPosition(args.line, args.offset);
  1010 |         }
  1011 | 

TypeScript/src/server/session.ts:1505

  1503 | 
  1504 |             function getStartPosition() {
> 1505 |                 return args.startPosition !== undefined ? args.startPosition : scriptInfo.lineOffsetToPosition(args.startLine, args.startOffset);
  1506 |             }
  1507 | 

...and 768 more

📋 View full report

Code no longer flagged (778)

S6606

S7757

S7765

S7781

github-actions Bot and others added 3 commits March 13, 2026 09:24
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…aces

- S7735: flip program != null to program == null (positive condition first)
- S4325: remove unnecessary 'as string[] | undefined' cast on .lib
- S121: add braces to single-line if in esLibToYear

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Two bugs:
1. Returned the first 4-digit year match instead of the maximum.
   User tsconfigs can list multiple years (e.g. ['es2015','es2017','es2019']);
   the effective ES level is the highest one.
2. When a merged program contains both a year lib and esnext
   (e.g. [es2020, esnext]), returned the year instead of null.
   esnext means no ES version restriction regardless of other entries.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
@sonarqube-next
Copy link
Copy Markdown

@zglicz zglicz merged commit 1b905f1 into master Mar 16, 2026
41 checks passed
@zglicz zglicz deleted the js-1452/ecma-version-rule-filtering branch March 16, 2026 10:25
@vdiez vdiez changed the title JS-1452 Filter rules by detected ES version JS-483 Filter rules by detected ES version Mar 16, 2026
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.

2 participants