chore(eslint): add ESLint toolchain and clean src to strict 0/0 (#1165)

* chore(eslint): add eslint toolchain and configs

* fix(eslint): resolve gradual-config errors (rules-of-hooks, no-empty, prefer-const, …)

* chore(eslint): clear unused vars in config, contexts, app and test

* chore(eslint): clear unused vars in api and music-network

* chore(eslint): clear unused vars in utils

* chore(eslint): clear unused vars in store

* chore(eslint): clear unused vars in cover and hooks

* chore(eslint): clear unused vars in components

* chore(eslint): clear unused vars in pages

* chore(eslint): remove explicit any in src

* chore(eslint): align react-hooks exhaustive-deps

* chore(eslint): zero gradual config on src

* chore(eslint): strict hook rules in store and utils

* chore(eslint): strict hook rules in hooks and cover

* chore(eslint): strict hook rules in components

* chore(eslint): strict hook rules in pages and contexts

* chore(eslint): document scripts ignore in eslint config

* chore(eslint): add lint script and zero strict findings

* chore(eslint): address review round 1 (gradual 0/0, per-site disable reasons)

* chore(eslint): tighten two set-state disable comments (review round 2 LOW)

* chore(nix): sync npmDepsHash with package-lock.json

* docs(changelog): add Under the Hood entry for ESLint setup (PR #1165)

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Psychotoxical
2026-06-24 01:33:34 +02:00
committed by GitHub
parent c7d76af790
commit 7c724a642f
258 changed files with 2987 additions and 601 deletions
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { beforeEach, describe, expect, it } from 'vitest';
import { MusicNetworkRuntime } from './MusicNetworkRuntime';
import type { MusicNetworkStore, RuntimeHost } from './store';
import { __resetWires, registerWire } from '../registry/wireRegistry';
@@ -38,8 +38,8 @@ function toArray<T>(v: T | T[] | undefined | null): T[] {
return Array.isArray(v) ? v : [v];
}
function topTags(raw: any, max = 5): string[] {
return toArray(raw).map((tg: any) => String(tg.name)).slice(0, max);
function topTags(raw: unknown, max = 5): string[] {
return toArray(raw).map(tg => String((tg as { name?: unknown }).name)).slice(0, max);
}
class AudioscrobblerWireImpl implements EnrichmentWire {
@@ -181,7 +181,7 @@ class AudioscrobblerWireImpl implements EnrichmentWire {
async getSimilarArtists(ctx: WireContext, name: string): Promise<string[]> {
try {
const data = await audioscrobblerCall(ctx, { method: 'artist.getSimilar', artist: name, limit: '50' }, false, true);
return toArray(data?.similarartists?.artist).map((a: any) => a.name as string);
return toArray(data?.similarartists?.artist).map(a => a.name as string);
} catch {
return [];
}
@@ -257,7 +257,7 @@ class AudioscrobblerWireImpl implements EnrichmentWire {
period,
limit: String(limit),
}, false, true);
return toArray(data?.[collection]?.[node]).map((it: any) => ({
return toArray(data?.[collection]?.[node]).map(it => ({
name: it.name,
playcount: it.playcount,
...(kind === 'artists' ? {} : { artist: it.artist?.name ?? '' }),
@@ -275,7 +275,7 @@ class AudioscrobblerWireImpl implements EnrichmentWire {
sk: ctx.sessionKey,
limit: String(limit),
}, false, true);
return toArray(data?.recenttracks?.track).map((t: any) => ({
return toArray(data?.recenttracks?.track).map(t => ({
name: t.name,
artist: t.artist?.['#text'] ?? t.artist?.name ?? '',
album: t.album?.['#text'] ?? '',
@@ -26,12 +26,18 @@ const SESSION_INVALID_MESSAGE = /authentication failed|invalid (session|token)/i
/**
* Calls the Audioscrobbler endpoint. `sign` adds an api_sig; `get` uses GET
* instead of a form POST. Throws MusicNetworkError on failure.
*
* Returns the raw decoded JSON. Last.fm / Audioscrobbler responses are
* deeply-nested, loosely-documented and vary per method; the wire narrows and
* coerces fields at each use, so precisely typing every response is not
* tractable for this external API we do not control.
*/
export async function audioscrobblerCall(
ep: AudioscrobblerEndpoint,
params: Record<string, string>,
sign = false,
get = false,
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- external Last.fm JSON, shape not under our control (see doc above)
): Promise<any> {
const entries = Object.entries(params) as [string, string][];
return invokeTransport(
@@ -65,7 +65,7 @@ class ListenBrainzWireImpl implements ScrobbleWire {
const caps: CapabilitySet = {};
try {
const data = await listenBrainzCall(endpoint(ctx), '/1/validate-token');
const valid = data?.valid === true;
const valid = (data as { valid?: boolean })?.valid === true;
caps.scrobble = { status: valid ? 'yes' : 'error', message: valid ? undefined : 'Token invalid' };
caps.nowPlaying = { status: valid ? 'yes' : 'error' };
} catch (e) {
@@ -19,7 +19,7 @@ export async function listenBrainzCall(
ep: ListenBrainzEndpoint,
path: string,
jsonBody?: unknown,
): Promise<any> {
): Promise<unknown> {
return invokeTransport(
'listenbrainz_request',
{
+1 -1
View File
@@ -17,7 +17,7 @@ export async function malojaCall(
path: string,
jsonBody?: unknown,
query: [string, string][] = [],
): Promise<any> {
): Promise<unknown> {
return invokeTransport(
'maloja_request',
{
@@ -27,7 +27,7 @@ export interface TransportAuthRule {
* Invoke a provider transport command. On failure, throws the auth-class
* MusicNetworkError when `auth.match` recognises the message, otherwise NETWORK.
*/
export async function invokeTransport<T = any>(
export async function invokeTransport<T = unknown>(
command: string,
args: Record<string, unknown>,
auth?: TransportAuthRule,