feat: No Explicit Any (#8601)

## Summary
- Add `typescript/no-explicit-any` rule to `.oxlintrc.json` to enforce
no explicit `any` types
- Fix all 40 instances of explicit `any` throughout the codebase
- Improve type safety with proper TypeScript types

## Changes Made

### Configuration
- Added `typescript/no-explicit-any` rule to `.oxlintrc.json`

### Type Fixes
- Replaced `any` with `unknown` for truly unknown types
- Updated generic type parameters to use `unknown` defaults instead of
`any`
- Fixed method `this` parameters to avoid variance issues
- Updated component props to match new generic types
- Fixed test mocks to use proper type assertions

### Key Files Modified
- `src/types/treeExplorerTypes.ts`: Updated TreeExplorerNode interface
generics
- `src/platform/settings/types.ts`: Fixed SettingParams generic default
- `src/lib/litegraph/src/LGraph.ts`: Fixed ParamsArray type constraint
- `src/extensions/core/electronAdapter.ts`: Fixed onChange callbacks
- `src/views/GraphView.vue`: Added proper type imports
- Multiple test files: Fixed type assertions and mocks

## Test Plan
- [x] All lint checks pass (`pnpm lint`)
- [x] TypeScript compilation succeeds (`pnpm typecheck`)
- [x] Pre-commit hooks pass
- [x] No regression in functionality

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8601-feat-add-typescript-no-explicit-any-rule-and-fix-all-instances-2fd6d73d365081fd9beef75d5a6daf5b)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
This commit is contained in:
Johnpaul Chiwetelu
2026-02-12 00:13:48 +01:00
committed by GitHub
parent 92b7437d86
commit 4fc1d2ef5b
28 changed files with 242 additions and 151 deletions

View File

@@ -6,7 +6,23 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { computed, ref } from 'vue'
import NodeConflictDialogContent from '@/workbench/extensions/manager/components/manager/NodeConflictDialogContent.vue'
import type { ConflictDetectionResult } from '@/workbench/extensions/manager/types/conflictDetectionTypes'
import type {
ConflictDetail,
ConflictDetectionResult
} from '@/workbench/extensions/manager/types/conflictDetectionTypes'
// Type for component VM
interface NodeConflictDialogVM {
importFailedExpanded: boolean
conflictsExpanded: boolean
extensionsExpanded: boolean
allConflictDetails: ConflictDetail[]
importFailedConflicts: string[]
}
function getVM(wrapper: ReturnType<typeof mount>): NodeConflictDialogVM {
return wrapper.vm as Partial<NodeConflictDialogVM> as NodeConflictDialogVM
}
// Mock getConflictMessage utility
vi.mock('@/utils/conflictMessageUtil', () => ({
@@ -288,25 +304,28 @@ describe('NodeConflictDialogContent', () => {
await importFailedHeader.trigger('click')
// Verify import failed panel is open
expect((wrapper.vm as any).importFailedExpanded).toBe(true)
expect((wrapper.vm as any).conflictsExpanded).toBe(false)
expect((wrapper.vm as any).extensionsExpanded).toBe(false)
const vm1 = getVM(wrapper)
expect(vm1.importFailedExpanded).toBe(true)
expect(vm1.conflictsExpanded).toBe(false)
expect(vm1.extensionsExpanded).toBe(false)
// Open conflicts panel
await conflictsHeader.trigger('click')
// Verify conflicts panel is open and others are closed
expect((wrapper.vm as any).importFailedExpanded).toBe(false)
expect((wrapper.vm as any).conflictsExpanded).toBe(true)
expect((wrapper.vm as any).extensionsExpanded).toBe(false)
const vm2 = getVM(wrapper)
expect(vm2.importFailedExpanded).toBe(false)
expect(vm2.conflictsExpanded).toBe(true)
expect(vm2.extensionsExpanded).toBe(false)
// Open extensions panel
await extensionsHeader.trigger('click')
// Verify extensions panel is open and others are closed
expect((wrapper.vm as any).importFailedExpanded).toBe(false)
expect((wrapper.vm as any).conflictsExpanded).toBe(false)
expect((wrapper.vm as any).extensionsExpanded).toBe(true)
const vm3 = getVM(wrapper)
expect(vm3.importFailedExpanded).toBe(false)
expect(vm3.conflictsExpanded).toBe(false)
expect(vm3.extensionsExpanded).toBe(true)
})
})
@@ -451,10 +470,12 @@ describe('NodeConflictDialogContent', () => {
const wrapper = createWrapper()
// Verify that import_failed conflicts are filtered out from main conflicts
const vm = wrapper.vm as any
const vm = getVM(wrapper)
expect(vm.allConflictDetails).toHaveLength(3) // Should not include import_failed
expect(
vm.allConflictDetails.every((c: any) => c.type !== 'import_failed')
vm.allConflictDetails.every(
(c: ConflictDetail) => c.type !== 'import_failed'
)
).toBe(true)
})
@@ -463,7 +484,7 @@ describe('NodeConflictDialogContent', () => {
const wrapper = createWrapper()
// Verify that only import_failed packages are extracted
const vm = wrapper.vm as any
const vm = getVM(wrapper)
expect(vm.importFailedConflicts).toHaveLength(1)
expect(vm.importFailedConflicts[0]).toBe('Test Package 3')
})

View File

@@ -75,7 +75,7 @@ describe('PackVersionBadge', () => {
const mountComponent = ({
props = {}
}: Record<string, any> = {}): VueWrapper => {
}: { props?: Record<string, unknown> } = {}): VueWrapper => {
const i18n = createI18n({
legacy: false,
locale: 'en',

View File

@@ -17,6 +17,14 @@ import enMessages from '@/locales/en/main.json' with { type: 'json' }
import PackVersionSelectorPopover from './PackVersionSelectorPopover.vue'
interface PackVersionSelectorVM {
getVersionCompatibility: (version: string) => unknown
}
function getVM(wrapper: VueWrapper): PackVersionSelectorVM {
return wrapper.vm as Partial<PackVersionSelectorVM> as PackVersionSelectorVM
}
// Default mock versions for reference
const defaultMockVersions = [
{
@@ -106,7 +114,7 @@ describe('PackVersionSelectorPopover', () => {
const mountComponent = ({
props = {}
}: Record<string, any> = {}): VueWrapper => {
}: { props?: Record<string, unknown> } = {}): VueWrapper => {
const i18n = createI18n({
legacy: false,
locale: 'en',
@@ -481,7 +489,7 @@ describe('PackVersionSelectorPopover', () => {
mockCheckNodeCompatibility.mockClear()
// Trigger compatibility check by accessing getVersionCompatibility
const vm = wrapper.vm as any
const vm = getVM(wrapper)
vm.getVersionCompatibility('1.0.0')
// Verify that checkNodeCompatibility was called with correct data
@@ -569,7 +577,7 @@ describe('PackVersionSelectorPopover', () => {
})
await waitForPromises()
const vm = wrapper.vm as any
const vm = getVM(wrapper)
// Clear previous calls from component mounting/rendering
mockCheckNodeCompatibility.mockClear()

View File

@@ -17,7 +17,7 @@ vi.mock('es-toolkit/compat', async () => {
const actual = await vi.importActual('es-toolkit/compat')
return {
...actual,
debounce: <T extends (...args: any[]) => any>(fn: T) => fn
debounce: <T extends (...args: unknown[]) => unknown>(fn: T) => fn
}
})
@@ -61,7 +61,10 @@ describe('PackEnableToggle', () => {
const mountComponent = ({
props = {},
installedPacks = {}
}: Record<string, any> = {}): VueWrapper => {
}: {
props?: Record<string, unknown>
installedPacks?: Record<string, unknown>
} = {}): VueWrapper => {
const i18n = createI18n({
legacy: false,
locale: 'en',
@@ -73,7 +76,9 @@ describe('PackEnableToggle', () => {
enablePack: mockEnablePack,
disablePack: mockDisablePack,
installedPacks
} as any)
} as Partial<ReturnType<typeof useComfyManagerStore>> as ReturnType<
typeof useComfyManagerStore
>)
return mount(PackEnableToggle, {
props: {