From 1da15ebb0ed630a5cf909514710d6a5a925bc242 Mon Sep 17 00:00:00 2001 From: abdelhadi703 Date: Sun, 11 Oct 2026 14:55:48 +0200 Subject: [PATCH 1/2] Use the clang-tidy version in use for check documentation links --- Extension/CHANGELOG.md | 1 + .../LanguageServer/clangTidyDocumentation.ts | 19 +++++++++++++++ Extension/src/LanguageServer/codeAnalysis.ts | 4 ++-- Extension/src/LanguageServer/settings.ts | 15 +++++++++++- Extension/src/common.ts | 9 +++++++ .../test/unit/clangTidyDocumentation.test.ts | 24 +++++++++++++++++++ 6 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 Extension/src/LanguageServer/clangTidyDocumentation.ts create mode 100644 Extension/test/unit/clangTidyDocumentation.test.ts diff --git a/Extension/CHANGELOG.md b/Extension/CHANGELOG.md index b8c8b62db..2aa10e3eb 100644 --- a/Extension/CHANGELOG.md +++ b/Extension/CHANGELOG.md @@ -15,6 +15,7 @@ * Reduce CPU usage when resolving headers in large Windows workspaces. ### Bug Fixes +* Fix clang-tidy check documentation links pointing at the bundled LLVM release instead of the clang-tidy version in use. [#12804](https://github.com/microsoft/vscode-cpptools/issues/12804) * Fix 'Go to Declaration' and 'Go to Definition' returning the current location instead of an available counterpart. [#14509](https://github.com/microsoft/vscode-cpptools/issues/14509) * Update MIEngine to fix debugger failures with synthetic GDB stack frames. [#14741](https://github.com/microsoft/vscode-cpptools/issues/14741) * Thanks for the contribution. [@tzcnt](https://github.com/tzcnt) [PR MIEngine#1605](https://github.com/microsoft/MIEngine/pull/1605) diff --git a/Extension/src/LanguageServer/clangTidyDocumentation.ts b/Extension/src/LanguageServer/clangTidyDocumentation.ts new file mode 100644 index 000000000..6915ad775 --- /dev/null +++ b/Extension/src/LanguageServer/clangTidyDocumentation.ts @@ -0,0 +1,19 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. + * See 'LICENSE' in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +/** + * The clang-tidy documentation is published for every LLVM release, so the URL of a check's + * page depends on the release the clang-tidy binary in use comes from. Checks added in a + * newer release have no page at all in an older one. When the version in use could not be + * determined, fall back to the release bundled with the extension. + */ +export const fallbackClangTidyDocVersion: string = '23.1.0'; + +const versionPattern: RegExp = /^[0-9]+\.[0-9]+\.[0-9]+$/; + +export function getClangTidyDocumentationUrl(docPage: string, clangTidyVersion?: string): string { + const version: string = clangTidyVersion && versionPattern.test(clangTidyVersion) ? clangTidyVersion : fallbackClangTidyDocVersion; + return `https://releases.llvm.org/${version}/tools/clang/tools/extra/docs/clang-tidy/${docPage}`; +} diff --git a/Extension/src/LanguageServer/codeAnalysis.ts b/Extension/src/LanguageServer/codeAnalysis.ts index 61cc8cf06..c911573df 100644 --- a/Extension/src/LanguageServer/codeAnalysis.ts +++ b/Extension/src/LanguageServer/codeAnalysis.ts @@ -6,6 +6,7 @@ import * as vscode from 'vscode'; import { NotificationType, Range } from 'vscode-languageclient'; import * as nls from 'vscode-nls'; +import { getClangTidyDocumentationUrl } from './clangTidyDocumentation'; import { Location, WorkspaceEdit } from './commonTypes'; import { CppSourceStr } from './extension'; import { LanguageClient } from './languageClient'; @@ -379,8 +380,7 @@ export function publishCodeAnalysisDiagnostics(params: PublishCodeAnalysisDiagno const checksPage: string = dashIndex > 0 ? primaryCode.substring(dashIndex + 1) : primaryCode; docPage = `checks${checksGroup}/${checksPage}.html`; } - // TODO: This should be checking the clang-tidy version used to better support usage of older versions. - const primaryDocUri: vscode.Uri = vscode.Uri.parse(`https://releases.llvm.org/23.1.0/tools/clang/tools/extra/docs/clang-tidy/${docPage}`); + const primaryDocUri: vscode.Uri = vscode.Uri.parse(getClangTidyDocumentationUrl(docPage, new CppSettings().clangTidyVersion)); diagnostic.code = { value: identifier.code, target: primaryDocUri }; if (new CppSettings().clangTidyCodeActionShowDocumentation) { diff --git a/Extension/src/LanguageServer/settings.ts b/Extension/src/LanguageServer/settings.ts index 499431d09..50bacb00e 100644 --- a/Extension/src/LanguageServer/settings.ts +++ b/Extension/src/LanguageServer/settings.ts @@ -12,7 +12,7 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import * as which from 'which'; import * as util from '../common'; -import { getCachedClangFormatPath, getCachedClangTidyPath, getExtensionFilePath, getRawSetting, isArray, isArrayOfString, isBoolean, isNumber, isObject, isString, isValidMapping, setCachedClangFormatPath, setCachedClangTidyPath } from '../common'; +import { getCachedClangFormatPath, getCachedClangTidyPath, getCachedClangTidyVersion, getExtensionFilePath, getRawSetting, isArray, isArrayOfString, isBoolean, isNumber, isObject, isString, isValidMapping, setCachedClangFormatPath, setCachedClangTidyPath, setCachedClangTidyVersion } from '../common'; import { isWindows } from '../constants'; import * as telemetry from '../telemetry'; import { cachedEditorConfigLookups, DefaultClient, hasTrustedCompilerPaths } from './client'; @@ -279,6 +279,10 @@ export class CppSettings extends Settings { return this.getClangPath(false); } + public get clangTidyVersion(): string | undefined { + return getCachedClangTidyVersion(); + } + public get clangFormatPath(): string | undefined { return this.getClangPath(true); } @@ -303,6 +307,7 @@ export class CppSettings extends Settings { } else { // Attempt to invoke both our own version of clang-* to see if we can successfully execute it, and to get its version. let bundledVersion: string; + let clangVersion: string | undefined; try { const bundledPath: string = getExtensionFilePath(`./LLVM/bin/${clangName}`); const output: string = execFileSync(bundledPath, ['--version']).toString(); @@ -322,10 +327,18 @@ export class CppSettings extends Settings { if (semver.ltr(userVersion, bundledVersion)) { path = ""; setCachedClangPath(path); + clangVersion = bundledVersion; + } else { + clangVersion = userVersion; } } catch { path = ""; setCachedClangPath(path); + clangVersion = bundledVersion; + } + + if (!isFormat && clangVersion) { + setCachedClangTidyVersion(clangVersion); } } } diff --git a/Extension/src/common.ts b/Extension/src/common.ts index 700d8ab44..1022d2b8c 100644 --- a/Extension/src/common.ts +++ b/Extension/src/common.ts @@ -60,6 +60,15 @@ export function setCachedClangTidyPath(path: string): void { cachedClangTidyPath = path; } +let cachedClangTidyVersion: string | undefined; +export function getCachedClangTidyVersion(): string | undefined { + return cachedClangTidyVersion; +} + +export function setCachedClangTidyVersion(version: string): void { + cachedClangTidyVersion = version; +} + // Use this package.json to read values export const packageJson: any = vscode.extensions.getExtension("ms-vscode.cpptools")?.packageJSON; diff --git a/Extension/test/unit/clangTidyDocumentation.test.ts b/Extension/test/unit/clangTidyDocumentation.test.ts new file mode 100644 index 000000000..70437745e --- /dev/null +++ b/Extension/test/unit/clangTidyDocumentation.test.ts @@ -0,0 +1,24 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. + * See 'LICENSE' in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import { describe, it } from 'mocha'; +import { strictEqual } from 'node:assert'; +import { fallbackClangTidyDocVersion, getClangTidyDocumentationUrl } from '../../src/LanguageServer/clangTidyDocumentation'; + +const bundledDocUrl: string = `https://releases.llvm.org/${fallbackClangTidyDocVersion}/tools/clang/tools/extra/docs/clang-tidy`; + +describe('clang-tidy documentation URLs', () => { + it('points at the clang-tidy version in use', () => { + strictEqual( + getClangTidyDocumentationUrl('checks/readability/use-std-min-max.html', '19.1.0'), + 'https://releases.llvm.org/19.1.0/tools/clang/tools/extra/docs/clang-tidy/checks/readability/use-std-min-max.html'); + }); + + it('falls back to the bundled version when the version in use is unknown', () => { + strictEqual(getClangTidyDocumentationUrl('checks/misc/include-cleaner.html'), `${bundledDocUrl}/checks/misc/include-cleaner.html`); + strictEqual(getClangTidyDocumentationUrl('checks/misc/include-cleaner.html', ''), `${bundledDocUrl}/checks/misc/include-cleaner.html`); + strictEqual(getClangTidyDocumentationUrl('checks/misc/include-cleaner.html', '23.1'), `${bundledDocUrl}/checks/misc/include-cleaner.html`); + }); +}); From 807394292cd9f89a8b722479e6497b31fb9cf8a6 Mon Sep 17 00:00:00 2001 From: abdelhadi703 Date: Wed, 14 Oct 2026 00:35:41 +0200 Subject: [PATCH 2/2] Use the .1.0 release of the clang-tidy major for documentation links Only the .1.0 release of each major has a published documentation page: 23.1.0 and 22.1.0 answer, 23.1.1 and 22.1.3 return 404. The version in use is reduced to its major.1.0 release before it goes into the URL. Also drops the changelog entry, since those are written separately before a release, and stops reporting the cached version for a resource that sets its own codeAnalysis.clangTidy.path -- that cached version comes from a binary the resource is not running. --- Extension/CHANGELOG.md | 1 - .../LanguageServer/clangTidyDocumentation.ts | 21 ++++++++++++------- Extension/src/LanguageServer/codeAnalysis.ts | 2 +- Extension/src/LanguageServer/settings.ts | 6 +++++- .../test/unit/clangTidyDocumentation.test.ts | 9 ++++++++ 5 files changed, 29 insertions(+), 10 deletions(-) diff --git a/Extension/CHANGELOG.md b/Extension/CHANGELOG.md index 2aa10e3eb..b8c8b62db 100644 --- a/Extension/CHANGELOG.md +++ b/Extension/CHANGELOG.md @@ -15,7 +15,6 @@ * Reduce CPU usage when resolving headers in large Windows workspaces. ### Bug Fixes -* Fix clang-tidy check documentation links pointing at the bundled LLVM release instead of the clang-tidy version in use. [#12804](https://github.com/microsoft/vscode-cpptools/issues/12804) * Fix 'Go to Declaration' and 'Go to Definition' returning the current location instead of an available counterpart. [#14509](https://github.com/microsoft/vscode-cpptools/issues/14509) * Update MIEngine to fix debugger failures with synthetic GDB stack frames. [#14741](https://github.com/microsoft/vscode-cpptools/issues/14741) * Thanks for the contribution. [@tzcnt](https://github.com/tzcnt) [PR MIEngine#1605](https://github.com/microsoft/MIEngine/pull/1605) diff --git a/Extension/src/LanguageServer/clangTidyDocumentation.ts b/Extension/src/LanguageServer/clangTidyDocumentation.ts index 6915ad775..1c9a30b1e 100644 --- a/Extension/src/LanguageServer/clangTidyDocumentation.ts +++ b/Extension/src/LanguageServer/clangTidyDocumentation.ts @@ -4,16 +4,23 @@ * ------------------------------------------------------------------------------------------ */ /** - * The clang-tidy documentation is published for every LLVM release, so the URL of a check's - * page depends on the release the clang-tidy binary in use comes from. Checks added in a - * newer release have no page at all in an older one. When the version in use could not be - * determined, fall back to the release bundled with the extension. + * The clang-tidy documentation is published per major LLVM release, so a check's page lives + * under the .1.0 release of the major the binary in use comes from: 23.1.0 and 22.1.0 have + * pages, 23.1.1 and 22.1.3 do not. Checks added in a newer major have no page in an older one, + * so the URL follows the binary in use rather than the release bundled with the extension. + * When the version in use could not be determined, fall back to the bundled release. */ export const fallbackClangTidyDocVersion: string = '23.1.0'; -const versionPattern: RegExp = /^[0-9]+\.[0-9]+\.[0-9]+$/; +const versionPattern: RegExp = /^([0-9]+)\.[0-9]+\.[0-9]+$/; + +/** The release a clang-tidy version's documentation was published under. */ +function documentationRelease(clangTidyVersion: string): string | undefined { + const match: RegExpMatchArray | null = clangTidyVersion.match(versionPattern); + return match ? `${match[1]}.1.0` : undefined; +} export function getClangTidyDocumentationUrl(docPage: string, clangTidyVersion?: string): string { - const version: string = clangTidyVersion && versionPattern.test(clangTidyVersion) ? clangTidyVersion : fallbackClangTidyDocVersion; - return `https://releases.llvm.org/${version}/tools/clang/tools/extra/docs/clang-tidy/${docPage}`; + const release: string | undefined = clangTidyVersion ? documentationRelease(clangTidyVersion) : undefined; + return `https://releases.llvm.org/${release ?? fallbackClangTidyDocVersion}/tools/clang/tools/extra/docs/clang-tidy/${docPage}`; } diff --git a/Extension/src/LanguageServer/codeAnalysis.ts b/Extension/src/LanguageServer/codeAnalysis.ts index c911573df..e6f7a2b23 100644 --- a/Extension/src/LanguageServer/codeAnalysis.ts +++ b/Extension/src/LanguageServer/codeAnalysis.ts @@ -380,7 +380,7 @@ export function publishCodeAnalysisDiagnostics(params: PublishCodeAnalysisDiagno const checksPage: string = dashIndex > 0 ? primaryCode.substring(dashIndex + 1) : primaryCode; docPage = `checks${checksGroup}/${checksPage}.html`; } - const primaryDocUri: vscode.Uri = vscode.Uri.parse(getClangTidyDocumentationUrl(docPage, new CppSettings().clangTidyVersion)); + const primaryDocUri: vscode.Uri = vscode.Uri.parse(getClangTidyDocumentationUrl(docPage, new CppSettings(realUri).clangTidyVersion)); diagnostic.code = { value: identifier.code, target: primaryDocUri }; if (new CppSettings().clangTidyCodeActionShowDocumentation) { diff --git a/Extension/src/LanguageServer/settings.ts b/Extension/src/LanguageServer/settings.ts index 50bacb00e..c5916dbdc 100644 --- a/Extension/src/LanguageServer/settings.ts +++ b/Extension/src/LanguageServer/settings.ts @@ -280,7 +280,11 @@ export class CppSettings extends Settings { } public get clangTidyVersion(): string | undefined { - return getCachedClangTidyVersion(); + // The cached version comes from an auto-discovered binary, so it says nothing about the + // clang-tidy a resource carrying its own path is running. Reporting it would point that + // resource's documentation links at the wrong release, so leave the version unknown and + // let the caller fall back. + return changeBlankStringToUndefined(this.getAsStringOrUndefined("codeAnalysis.clangTidy.path")) ? undefined : getCachedClangTidyVersion(); } public get clangFormatPath(): string | undefined { diff --git a/Extension/test/unit/clangTidyDocumentation.test.ts b/Extension/test/unit/clangTidyDocumentation.test.ts index 70437745e..a6fccc710 100644 --- a/Extension/test/unit/clangTidyDocumentation.test.ts +++ b/Extension/test/unit/clangTidyDocumentation.test.ts @@ -16,6 +16,15 @@ describe('clang-tidy documentation URLs', () => { 'https://releases.llvm.org/19.1.0/tools/clang/tools/extra/docs/clang-tidy/checks/readability/use-std-min-max.html'); }); + it('uses the .1.0 release of that major, since patch releases have no page', () => { + strictEqual( + getClangTidyDocumentationUrl('checks/readability/use-std-min-max.html', '23.1.1'), + 'https://releases.llvm.org/23.1.0/tools/clang/tools/extra/docs/clang-tidy/checks/readability/use-std-min-max.html'); + strictEqual( + getClangTidyDocumentationUrl('checks/misc/include-cleaner.html', '22.1.3'), + 'https://releases.llvm.org/22.1.0/tools/clang/tools/extra/docs/clang-tidy/checks/misc/include-cleaner.html'); + }); + it('falls back to the bundled version when the version in use is unknown', () => { strictEqual(getClangTidyDocumentationUrl('checks/misc/include-cleaner.html'), `${bundledDocUrl}/checks/misc/include-cleaner.html`); strictEqual(getClangTidyDocumentationUrl('checks/misc/include-cleaner.html', ''), `${bundledDocUrl}/checks/misc/include-cleaner.html`);