From 2b52b93770dfcaac28b8c19fef6b21c8933f11f6 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:35:01 +0000 Subject: [PATCH] Git - enable ESLint rule for git extensions (#277156) * Initial commit with all exceptions * First pass of fixing * Add ignored files explicitly --- eslint.config.js | 16 ++++++++++++- extensions/git/src/api/extension.ts | 6 ++--- extensions/git/src/askpass-main.ts | 2 +- extensions/git/src/cache.ts | 2 +- extensions/git/src/commands.ts | 24 +++++++++---------- extensions/git/src/decorationProvider.ts | 2 +- extensions/git/src/decorators.ts | 4 ++-- extensions/git/src/git-editor-main.ts | 2 +- extensions/git/src/git.ts | 13 +++++----- extensions/git/src/gitEditor.ts | 4 +++- extensions/git/src/ipc/ipcClient.ts | 2 +- extensions/git/src/ipc/ipcServer.ts | 2 +- extensions/git/src/main.ts | 4 ++-- extensions/git/src/model.ts | 17 ++++--------- extensions/git/src/operation.ts | 2 +- extensions/git/src/repository.ts | 10 ++++---- extensions/github/src/remoteSourceProvider.ts | 10 +++++++- 17 files changed, 67 insertions(+), 55 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index cc402245e25..ac4dbe12aab 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -350,12 +350,26 @@ export default tseslint.config( 'local/code-no-in-operator': 'warn', } }, - // vscode TS: strict no explicit `any` + // Strict no explicit `any` { files: [ + // Extensions + 'extensions/git/src/**/*.ts', + 'extensions/git-base/src/**/*.ts', + 'extensions/github/src/**/*.ts', + // vscode 'src/**/*.ts', ], ignores: [ + // Extensions + 'extensions/git/src/commands.ts', + 'extensions/git/src/decorators.ts', + 'extensions/git/src/git.ts', + 'extensions/git/src/repository.ts', + 'extensions/git/src/util.ts', + 'extensions/git-base/src/decorators.ts', + 'extensions/github/src/util.ts', + // vscode d.ts 'src/vs/amdX.ts', 'src/vs/monaco.d.ts', 'src/vscode-dts/**', diff --git a/extensions/git/src/api/extension.ts b/extensions/git/src/api/extension.ts index 3bbb717e23f..a716fa00dae 100644 --- a/extensions/git/src/api/extension.ts +++ b/extensions/git/src/api/extension.ts @@ -9,13 +9,13 @@ import { ApiRepository, ApiImpl } from './api1'; import { Event, EventEmitter } from 'vscode'; import { CloneManager } from '../cloneManager'; -function deprecated(original: any, context: ClassMemberDecoratorContext) { - if (context.kind !== 'method') { +function deprecated(original: unknown, context: ClassMemberDecoratorContext) { + if (typeof original !== 'function' || context.kind !== 'method') { throw new Error('not supported'); } const key = context.name.toString(); - return function (this: any, ...args: any[]): any { + return function (this: unknown, ...args: unknown[]) { console.warn(`Git extension API method '${key}' is deprecated.`); return original.apply(this, args); }; diff --git a/extensions/git/src/askpass-main.ts b/extensions/git/src/askpass-main.ts index cb93adf2821..21402fbaf34 100644 --- a/extensions/git/src/askpass-main.ts +++ b/extensions/git/src/askpass-main.ts @@ -6,7 +6,7 @@ import * as fs from 'fs'; import { IPCClient } from './ipc/ipcClient'; -function fatal(err: any): void { +function fatal(err: unknown): void { console.error('Missing or invalid credentials.'); console.error(err); process.exit(1); diff --git a/extensions/git/src/cache.ts b/extensions/git/src/cache.ts index df0c0df5561..ad2db75edc8 100644 --- a/extensions/git/src/cache.ts +++ b/extensions/git/src/cache.ts @@ -132,7 +132,7 @@ class LinkedMap implements Map { return item.value; } - forEach(callbackfn: (value: V, key: K, map: LinkedMap) => void, thisArg?: any): void { + forEach(callbackfn: (value: V, key: K, map: LinkedMap) => void, thisArg?: unknown): void { const state = this._state; let current = this._head; while (current) { diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index db7b2d04945..79a3184bd36 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -9,7 +9,7 @@ import { Command, commands, Disposable, MessageOptions, Position, QuickPickItem, import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import { ForcePushMode, GitErrorCodes, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote, Branch, Ref } from './api/git'; -import { Git, Stash, Worktree } from './git'; +import { Git, GitError, Stash, Worktree } from './git'; import { Model } from './model'; import { GitResourceGroup, Repository, Resource, ResourceGroupType } from './repository'; import { DiffEditorSelectionHunkToolbarContext, LineChange, applyLineChanges, getIndexDiffInformation, getModifiedRange, getWorkingTreeDiffInformation, intersectDiffWithRange, invertLineChange, toLineChanges, toLineRanges, compareLineChanges } from './staging'; @@ -365,8 +365,8 @@ interface ScmCommand { const Commands: ScmCommand[] = []; function command(commandId: string, options: ScmCommandOptions = {}): Function { - return (value: any, context: ClassMethodDecoratorContext) => { - if (context.kind !== 'method') { + return (value: unknown, context: ClassMethodDecoratorContext) => { + if (typeof value !== 'function' || context.kind !== 'method') { throw new Error('not supported'); } const key = context.name.toString(); @@ -3591,10 +3591,8 @@ export class CommandCenter { } } - @command('git.createWorktree') - async createWorktree(repository: any): Promise { - repository = this.model.getRepository(repository); - + @command('git.createWorktree', { repository: true }) + async createWorktree(repository?: Repository): Promise { if (!repository) { // Single repository/submodule/worktree if (this.model.repositories.length === 1) { @@ -3786,9 +3784,9 @@ export class CommandCenter { this.globalState.update(`${CommandCenter.WORKTREE_ROOT_KEY}:${repository.root}`, worktreeRoot); } } catch (err) { - if (err.gitErrorCode === GitErrorCodes.WorktreeAlreadyExists) { + if (err instanceof GitError && err.gitErrorCode === GitErrorCodes.WorktreeAlreadyExists) { await this.handleWorktreeAlreadyExists(err); - } else if (err.gitErrorCode === GitErrorCodes.WorktreeBranchAlreadyUsed) { + } else if (err instanceof GitError && err.gitErrorCode === GitErrorCodes.WorktreeBranchAlreadyUsed) { await this.handleWorktreeBranchAlreadyUsed(err); } else { throw err; @@ -3798,8 +3796,8 @@ export class CommandCenter { } } - private async handleWorktreeBranchAlreadyUsed(err: any): Promise { - const match = err.stderr.match(/fatal: '([^']+)' is already used by worktree at '([^']+)'/); + private async handleWorktreeBranchAlreadyUsed(err: GitError): Promise { + const match = err.stderr?.match(/fatal: '([^']+)' is already used by worktree at '([^']+)'/); if (!match) { return; @@ -3810,8 +3808,8 @@ export class CommandCenter { await this.handleWorktreeConflict(path, message); } - private async handleWorktreeAlreadyExists(err: any): Promise { - const match = err.stderr.match(/fatal: '([^']+)'/); + private async handleWorktreeAlreadyExists(err: GitError): Promise { + const match = err.stderr?.match(/fatal: '([^']+)'/); if (!match) { return; diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index ea4f031e0f9..b8b5fc26723 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -32,7 +32,7 @@ class GitIgnoreDecorationProvider implements FileDecorationProvider { private disposables: Disposable[] = []; constructor(private model: Model) { - const onDidChangeRepository = anyEvent( + const onDidChangeRepository = anyEvent( filterEvent(workspace.onDidSaveTextDocument, e => /\.gitignore$|\.git\/info\/exclude$/.test(e.uri.path)), model.onDidOpenRepository, model.onDidCloseRepository diff --git a/extensions/git/src/decorators.ts b/extensions/git/src/decorators.ts index cd1c7d72d3b..0e59a849ed2 100644 --- a/extensions/git/src/decorators.ts +++ b/extensions/git/src/decorators.ts @@ -6,8 +6,8 @@ import { done } from './util'; function decorate(decorator: (fn: Function, key: string) => Function): Function { - return function (original: any, context: ClassMethodDecoratorContext) { - if (context.kind === 'method' || context.kind === 'getter' || context.kind === 'setter') { + return function (original: unknown, context: ClassMethodDecoratorContext) { + if (typeof original === 'function' && (context.kind === 'method' || context.kind === 'getter' || context.kind === 'setter')) { return decorator(original, context.name.toString()); } throw new Error('not supported'); diff --git a/extensions/git/src/git-editor-main.ts b/extensions/git/src/git-editor-main.ts index eb4da4a40b5..80615b56e5a 100644 --- a/extensions/git/src/git-editor-main.ts +++ b/extensions/git/src/git-editor-main.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IPCClient } from './ipc/ipcClient'; -function fatal(err: any): void { +function fatal(err: unknown): void { console.error(err); process.exit(1); } diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index a299df96e47..44654bd4897 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -117,7 +117,7 @@ function findGitDarwin(onValidate: (path: string) => boolean): Promise { } // must check if XCode is installed - cp.exec('xcode-select -p', (err: any) => { + cp.exec('xcode-select -p', (err) => { if (err && err.code === 2) { // git is not installed, and launching /usr/bin/git // will prompt the user to install it @@ -1975,11 +1975,12 @@ export class Repository { } } - private async handleCommitError(commitErr: any): Promise { - if (/not possible because you have unmerged files/.test(commitErr.stderr || '')) { + + private async handleCommitError(commitErr: unknown): Promise { + if (commitErr instanceof GitError && /not possible because you have unmerged files/.test(commitErr.stderr || '')) { commitErr.gitErrorCode = GitErrorCodes.UnmergedChanges; throw commitErr; - } else if (/Aborting commit due to empty commit message/.test(commitErr.stderr || '')) { + } else if (commitErr instanceof GitError && /Aborting commit due to empty commit message/.test(commitErr.stderr || '')) { commitErr.gitErrorCode = GitErrorCodes.EmptyCommitMessage; throw commitErr; } @@ -2113,8 +2114,8 @@ export class Repository { const pathsByGroup = groupBy(paths.map(sanitizePath), p => path.dirname(p)); const groups = Object.keys(pathsByGroup).map(k => pathsByGroup[k]); - const limiter = new Limiter(5); - const promises: Promise[] = []; + const limiter = new Limiter>(5); + const promises: Promise>[] = []; const args = ['clean', '-f', '-q']; for (const paths of groups) { diff --git a/extensions/git/src/gitEditor.ts b/extensions/git/src/gitEditor.ts index 6291e5152a7..cbbea2c6d78 100644 --- a/extensions/git/src/gitEditor.ts +++ b/extensions/git/src/gitEditor.ts @@ -34,7 +34,7 @@ export class GitEditor implements IIPCHandler, ITerminalEnvironmentProvider { }; } - async handle({ commitMessagePath }: GitEditorRequest): Promise { + async handle({ commitMessagePath }: GitEditorRequest): Promise { if (commitMessagePath) { const uri = Uri.file(commitMessagePath); const doc = await workspace.openTextDocument(uri); @@ -49,6 +49,8 @@ export class GitEditor implements IIPCHandler, ITerminalEnvironmentProvider { }); }); } + + return Promise.resolve(false); } getEnv(): { [key: string]: string } { diff --git a/extensions/git/src/ipc/ipcClient.ts b/extensions/git/src/ipc/ipcClient.ts index f623b3f7b6f..9aab55e44a3 100644 --- a/extensions/git/src/ipc/ipcClient.ts +++ b/extensions/git/src/ipc/ipcClient.ts @@ -19,7 +19,7 @@ export class IPCClient { this.ipcHandlePath = ipcHandlePath; } - call(request: any): Promise { + call(request: unknown): Promise { const opts: http.RequestOptions = { socketPath: this.ipcHandlePath, path: `/${this.handlerName}`, diff --git a/extensions/git/src/ipc/ipcServer.ts b/extensions/git/src/ipc/ipcServer.ts index a7142fe22e1..5e56f9ceef5 100644 --- a/extensions/git/src/ipc/ipcServer.ts +++ b/extensions/git/src/ipc/ipcServer.ts @@ -25,7 +25,7 @@ function getIPCHandlePath(id: string): string { } export interface IIPCHandler { - handle(request: any): Promise; + handle(request: unknown): Promise; } export async function createIPCServer(context?: string): Promise { diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 30c8fbaacdb..535c0f2f30e 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -29,9 +29,9 @@ import { GitCommitInputBoxCodeActionsProvider, GitCommitInputBoxDiagnosticsManag import { GitBlameController } from './blame'; import { CloneManager } from './cloneManager'; -const deactivateTasks: { (): Promise }[] = []; +const deactivateTasks: { (): Promise }[] = []; -export async function deactivate(): Promise { +export async function deactivate(): Promise { for (const task of deactivateTasks) { await task(); } diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index b2c536e5e07..f553132f2b0 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -227,7 +227,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi return Promise.resolve(); } - return eventToPromise(filterEvent(this.onDidChangeState, s => s === 'initialized')) as Promise; + return eventToPromise(filterEvent(this.onDidChangeState, s => s === 'initialized') as Event) as Promise; } private remoteSourcePublishers = new Set(); @@ -835,7 +835,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi commands.executeCommand('setContext', 'operationInProgress', operationInProgress); }; - const operationEvent = anyEvent(repository.onDidRunOperation as Event, repository.onRunOperation as Event); + const operationEvent = anyEvent(repository.onDidRunOperation as Event, repository.onRunOperation as Event); const operationListener = operationEvent(() => updateOperationInProgressContext()); updateOperationInProgressContext(); @@ -901,11 +901,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi return pick && pick.repository; } - getRepository(sourceControl: SourceControl): Repository | undefined; - getRepository(resourceGroup: SourceControlResourceGroup): Repository | undefined; - getRepository(path: string): Repository | undefined; - getRepository(resource: Uri): Repository | undefined; - getRepository(hint: any): Repository | undefined { + getRepository(hint: SourceControl | SourceControlResourceGroup | Uri | string): Repository | undefined { const liveRepository = this.getOpenRepository(hint); return liveRepository && liveRepository.repository; } @@ -932,12 +928,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi } } - private getOpenRepository(repository: Repository): OpenRepository | undefined; - private getOpenRepository(sourceControl: SourceControl): OpenRepository | undefined; - private getOpenRepository(resourceGroup: SourceControlResourceGroup): OpenRepository | undefined; - private getOpenRepository(path: string): OpenRepository | undefined; - private getOpenRepository(resource: Uri): OpenRepository | undefined; - private getOpenRepository(hint: any): OpenRepository | undefined { + private getOpenRepository(hint: SourceControl | SourceControlResourceGroup | Repository | Uri | string): OpenRepository | undefined { if (!hint) { return undefined; } diff --git a/extensions/git/src/operation.ts b/extensions/git/src/operation.ts index eaa91d4a047..4519c1f335b 100644 --- a/extensions/git/src/operation.ts +++ b/extensions/git/src/operation.ts @@ -200,7 +200,7 @@ export const Operation = { export interface OperationResult { operation: Operation; - error: any; + error: unknown; } interface IOperationManager { diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 6e61ef6f47d..4be995d8023 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -692,11 +692,7 @@ interface BranchProtectionMatcher { } export interface IRepositoryResolver { - getRepository(sourceControl: SourceControl): Repository | undefined; - getRepository(resourceGroup: SourceControlResourceGroup): Repository | undefined; - getRepository(path: string): Repository | undefined; - getRepository(resource: Uri): Repository | undefined; - getRepository(hint: any): Repository | undefined; + getRepository(hint: SourceControl | SourceControlResourceGroup | Uri | string): Repository | undefined; } export class Repository implements Disposable { @@ -940,7 +936,9 @@ export class Repository implements Disposable { : repository.kind === 'worktree' && repository.dotGit.commonPath ? path.dirname(repository.dotGit.commonPath) : undefined; - const parent = this.repositoryResolver.getRepository(parentRoot)?.sourceControl; + const parent = parentRoot + ? this.repositoryResolver.getRepository(parentRoot)?.sourceControl + : undefined; // Icon const icon = repository.kind === 'submodule' diff --git a/extensions/github/src/remoteSourceProvider.ts b/extensions/github/src/remoteSourceProvider.ts index 291a3f1a6ba..bed2bb1aa6b 100644 --- a/extensions/github/src/remoteSourceProvider.ts +++ b/extensions/github/src/remoteSourceProvider.ts @@ -10,7 +10,15 @@ import { Octokit } from '@octokit/rest'; import { getRepositoryFromQuery, getRepositoryFromUrl } from './util.js'; import { getBranchLink, getVscodeDevHost } from './links.js'; -function asRemoteSource(raw: any): RemoteSource { +type RemoteSourceResponse = { + readonly full_name: string; + readonly description: string | null; + readonly stargazers_count: number; + readonly clone_url: string; + readonly ssh_url: string; +}; + +function asRemoteSource(raw: RemoteSourceResponse): RemoteSource { const protocol = workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol'); return { name: `$(github) ${raw.full_name}`,