[eslint] Allow `any` in rest parameters.

This enables the `ignoreRestArgs` setting[^1] for the ESLint rule
@typescript-eslint/no-explicit-any, which allows the use of `any`
in rest parameters (which is actually a valid use case for `any`)
and thereby reduces the number of pragmas in the code base where
we need to explicitly silence ESLint about this.

Drive-by-fixes: Refine some uses of `any` that can now be removed, and
refactor the heap snapshot worker proxy to make it easier to read and
type properly.

[^1]: https://typescript-eslint.io/rules/no-explicit-any/#ignorerestargs

Bug: b:319814509, chromium:1172300
Change-Id: I713f1fbf2a91348d422fb567c50d08d870e348cf
Doc: http://go/chrome-devtools:eslint-pain-points-design
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5204645
Reviewed-by: Philip Pfaffe <[email protected]>
Commit-Queue: Benedikt Meurer <[email protected]>
Reviewed-by: Simon Zünd <[email protected]>
diff --git a/.eslintrc.js b/.eslintrc.js
index 6daeb50..a81c7dd 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -115,7 +115,12 @@
     '@typescript-eslint/naming-convention':
         [2, {'selector': 'interface', 'format': ['PascalCase'], 'custom': {'regex': '^I[A-Z]', 'match': false}}],
     '@typescript-eslint/explicit-member-accessibility': [0],
-    '@typescript-eslint/no-explicit-any': 2,
+    '@typescript-eslint/no-explicit-any': [
+      "error",
+      {
+        "ignoreRestArgs": true
+      }
+    ],
 
     // Closure does not properly typecheck default exports
     'import/no-default-export': 2,
diff --git a/front_end/core/common/Object.ts b/front_end/core/common/Object.ts
index 7bb741a..e7fd384 100644
--- a/front_end/core/common/Object.ts
+++ b/front_end/core/common/Object.ts
@@ -115,7 +115,6 @@
   }
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
 type Constructor = new (...args: any[]) => {};
 
 // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
diff --git a/front_end/core/common/Revealer.ts b/front_end/core/common/Revealer.ts
index 036d278..3bcf7ee 100644
--- a/front_end/core/common/Revealer.ts
+++ b/front_end/core/common/Revealer.ts
@@ -153,7 +153,6 @@
 }
 
 export interface RevealerRegistration<T> {
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
   contextTypes: () => Array<abstract new(...any: any[]) => T>;
   loadRevealer: () => Promise<Revealer<T>>;
   destination?: RevealerDestination;
diff --git a/front_end/models/extensions/ExtensionAPI.ts b/front_end/models/extensions/ExtensionAPI.ts
index 1a01f35..f678096 100644
--- a/front_end/models/extensions/ExtensionAPI.ts
+++ b/front_end/models/extensions/ExtensionAPI.ts
@@ -360,12 +360,7 @@
     nextObjectId(): string;
   }
 
-  // We cannot use the stronger `unknown` type in place of `any` in the following type definition. The type is used as
-  // the right-hand side of `extends` in a few places, which doesn't narrow `unknown`. Without narrowing, overload
-  // resolution and meaningful type inference of arguments break, for example.
-  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  export type Callable = (...args: any) => void;
+  export type Callable = (...args: any[]) => void;
 
   export interface EventSink<ListenerT extends Callable> extends PublicAPI.Chrome.DevTools.EventSink<ListenerT> {
     _type: string;
diff --git a/front_end/panels/profiler/HeapSnapshotProxy.ts b/front_end/panels/profiler/HeapSnapshotProxy.ts
index e2ad0bd..4426fde 100644
--- a/front_end/panels/profiler/HeapSnapshotProxy.ts
+++ b/front_end/panels/profiler/HeapSnapshotProxy.ts
@@ -29,8 +29,9 @@
  */
 
 import * as Common from '../../core/common/common.js';
-import type * as HeapSnapshotModel from '../../models/heap_snapshot_model/heap_snapshot_model.js';
 import * as i18n from '../../core/i18n/i18n.js';
+import type * as HeapSnapshotModel from '../../models/heap_snapshot_model/heap_snapshot_model.js';
+
 import {type ChildrenProvider} from './ChildrenProvider.js';
 
 const UIStrings = {
@@ -91,19 +92,22 @@
     this.postMessage({callId: this.nextCallId++, disposition: 'dispose', objectId: objectId});
   }
 
-  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  evaluateForTest(script: string, callback: (arg0: any) => void): void {
+  evaluateForTest(script: string, callback: (...arg0: any[]) => void): void {
     const callId = this.nextCallId++;
     this.callbacks.set(callId, callback);
     this.postMessage({callId: callId, disposition: 'evaluateForTest', source: script});
   }
 
   callFactoryMethod<T extends Object>(
-      callback: ((...arg0: unknown[]) => void)|null, objectId: string, methodName: string,
-      proxyConstructor: new(...arg1: unknown[]) => T): Object|null {
+      callback: null, objectId: string, methodName: string, proxyConstructor: new(...arg1: any[]) => T,
+      ...methodArguments: any[]): T;
+  callFactoryMethod<T extends Object>(
+      callback: ((...arg0: any[]) => void), objectId: string, methodName: string,
+      proxyConstructor: new(...arg1: any[]) => T, ...methodArguments: any[]): null;
+  callFactoryMethod<T extends Object>(
+      callback: ((...arg0: any[]) => void)|null, objectId: string, methodName: string,
+      proxyConstructor: new(...arg1: any[]) => T, ...methodArguments: any[]): T|null {
     const callId = this.nextCallId++;
-    const methodArguments = Array.prototype.slice.call(arguments, 4);
     const newObjectId = this.nextObjectId++;
 
     if (callback) {
@@ -131,11 +135,9 @@
     return new proxyConstructor(this, newObjectId);
   }
 
-  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  callMethod(callback: (arg0: any) => void, objectId: string, methodName: string): void {
+  callMethod(callback: (...arg0: any[]) => void, objectId: string, methodName: string, ...methodArguments: any[]):
+      void {
     const callId = this.nextCallId++;
-    const methodArguments = Array.prototype.slice.call(arguments, 3);
     if (callback) {
       this.callbacks.set(callId, callback);
     }
@@ -222,19 +224,6 @@
     this.objectId = objectId;
   }
 
-  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  callWorker(workerMethodName: string, args: any[]): any {
-    args.splice(1, 0, this.objectId);
-    // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    const worker = (this.worker as any)[workerMethodName];
-    if (!worker) {
-      throw new Error(`Could not find worker with name ${workerMethodName}.`);
-    }
-    return worker.apply(this.worker, args);
-  }
-
   dispose(): void {
     this.worker.disposeObject(this.objectId);
   }
@@ -243,21 +232,20 @@
     this.worker.dispose();
   }
 
-  callFactoryMethod<T>(
-      // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
-      // eslint-disable-next-line @typescript-eslint/no-explicit-any
-      _callback: ((...arg0: any[]) => void)|null, _methodName: string, _proxyConstructor: new(...arg1: any[]) => T,
-      // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
-      // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/naming-convention
-      ..._var_args: any[]): T {
-    return this.callWorker('callFactoryMethod', Array.prototype.slice.call(arguments, 0));
+  callFactoryMethod<T extends Object>(methodName: string, proxyConstructor: new(...arg1: any[]) => T, ...args: any[]):
+      T {
+    return this.worker.callFactoryMethod(null, String(this.objectId), methodName, proxyConstructor, ...args);
   }
 
-  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/naming-convention
-  callMethodPromise<T>(_methodName: string, ..._var_args: any[]): Promise<T> {
-    const args = Array.prototype.slice.call(arguments);
-    return new Promise(resolve => this.callWorker('callMethod', [resolve, ...args]));
+  callFactoryMethodPromise<T extends Object>(
+      methodName: string, proxyConstructor: new(...arg1: any[]) => T, ...args: any[]): Promise<T> {
+    return new Promise(
+        resolve =>
+            this.worker.callFactoryMethod(resolve, String(this.objectId), methodName, proxyConstructor, ...args));
+  }
+
+  callMethodPromise<T>(methodName: string, ...args: any[]): Promise<T> {
+    return new Promise(resolve => this.worker.callMethod(resolve, String(this.objectId), methodName, ...args));
   }
 }
 
@@ -278,8 +266,7 @@
 
   async close(): Promise<void> {
     await this.callMethodPromise('close');
-    const snapshotProxy = await new Promise<HeapSnapshotProxy>(
-        resolve => this.callFactoryMethod(resolve, 'buildSnapshot', HeapSnapshotProxy));
+    const snapshotProxy = await this.callFactoryMethodPromise('buildSnapshot', HeapSnapshotProxy);
     this.dispose();
     // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
     // @ts-expect-error
@@ -329,32 +316,28 @@
   }
 
   createEdgesProvider(nodeIndex: number): HeapSnapshotProviderProxy {
-    return this.callFactoryMethod(null, 'createEdgesProvider', HeapSnapshotProviderProxy, nodeIndex);
+    return this.callFactoryMethod('createEdgesProvider', HeapSnapshotProviderProxy, nodeIndex);
   }
 
   createRetainingEdgesProvider(nodeIndex: number): HeapSnapshotProviderProxy {
-    return this.callFactoryMethod(null, 'createRetainingEdgesProvider', HeapSnapshotProviderProxy, nodeIndex);
+    return this.callFactoryMethod('createRetainingEdgesProvider', HeapSnapshotProviderProxy, nodeIndex);
   }
 
-  createAddedNodesProvider(baseSnapshotId: string, className: string): HeapSnapshotProviderProxy|null {
-    return this.callFactoryMethod(
-        null, 'createAddedNodesProvider', HeapSnapshotProviderProxy, baseSnapshotId, className);
+  createAddedNodesProvider(baseSnapshotId: string, className: string): HeapSnapshotProviderProxy {
+    return this.callFactoryMethod('createAddedNodesProvider', HeapSnapshotProviderProxy, baseSnapshotId, className);
   }
 
-  createDeletedNodesProvider(nodeIndexes: number[]): HeapSnapshotProviderProxy|null {
-    return this.callFactoryMethod(null, 'createDeletedNodesProvider', HeapSnapshotProviderProxy, nodeIndexes);
+  createDeletedNodesProvider(nodeIndexes: number[]): HeapSnapshotProviderProxy {
+    return this.callFactoryMethod('createDeletedNodesProvider', HeapSnapshotProviderProxy, nodeIndexes);
   }
 
-  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  createNodesProvider(filter: (arg0: any) => boolean): HeapSnapshotProviderProxy|null {
-    return this.callFactoryMethod(null, 'createNodesProvider', HeapSnapshotProviderProxy, filter);
+  createNodesProvider(filter: (...args: any[]) => boolean): HeapSnapshotProviderProxy {
+    return this.callFactoryMethod('createNodesProvider', HeapSnapshotProviderProxy, filter);
   }
 
   createNodesProviderForClass(className: string, nodeFilter: HeapSnapshotModel.HeapSnapshotModel.NodeFilter):
-      HeapSnapshotProviderProxy|null {
-    return this.callFactoryMethod(
-        null, 'createNodesProviderForClass', HeapSnapshotProviderProxy, className, nodeFilter);
+      HeapSnapshotProviderProxy {
+    return this.callFactoryMethod('createNodesProviderForClass', HeapSnapshotProviderProxy, className, nodeFilter);
   }
 
   allocationTracesTops(): Promise<HeapSnapshotModel.HeapSnapshotModel.SerializedAllocationNode[]> {
diff --git a/front_end/ui/components/legacy_wrapper/LegacyWrapper.ts b/front_end/ui/components/legacy_wrapper/LegacyWrapper.ts
index 509b97a..6ba1af8 100644
--- a/front_end/ui/components/legacy_wrapper/LegacyWrapper.ts
+++ b/front_end/ui/components/legacy_wrapper/LegacyWrapper.ts
@@ -16,7 +16,6 @@
   }
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
 type Constructor<T extends UI.Widget.Widget> = new (...args: any[]) => T;
 
 export type LegacyWrapper<T extends UI.Widget.Widget, Component extends WrappableComponent<T>> = {
@@ -29,7 +28,6 @@
   return new class extends base {
     #component: Component;
 
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
     constructor(..._args: any[]) {
       super(/* isWebComponent=*/ true);
       this.#component = component;
diff --git a/front_end/ui/legacy/Context.ts b/front_end/ui/legacy/Context.ts
index 0c1e85a..62d979d 100644
--- a/front_end/ui/legacy/Context.ts
+++ b/front_end/ui/legacy/Context.ts
@@ -10,8 +10,6 @@
 let contextInstance: Context|undefined;
 
 interface ConstructorFn<T> {
-  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
   new(...args: any[]): T;
 }
 
diff --git a/front_end/ui/legacy/ContextMenu.ts b/front_end/ui/legacy/ContextMenu.ts
index e315db1..d2312ed 100644
--- a/front_end/ui/legacy/ContextMenu.ts
+++ b/front_end/ui/legacy/ContextMenu.ts
@@ -698,8 +698,7 @@
 }
 
 export interface ProviderRegistration<T> {
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  contextTypes: () => Array<abstract new(...any: any) => T>;
+  contextTypes: () => Array<abstract new(...any: any[]) => T>;
   loadProvider: () => Promise<Provider<T>>;
   experiment?: Root.Runtime.ExperimentName;
 }
diff --git a/front_end/ui/legacy/Fragment.ts b/front_end/ui/legacy/Fragment.ts
index 424e90a..91a4071 100644
--- a/front_end/ui/legacy/Fragment.ts
+++ b/front_end/ui/legacy/Fragment.ts
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-/* eslint-disable @typescript-eslint/no-explicit-any */
-
 function getNodeData(node: Node): string {
   return (node as unknown as {
            data: string,
@@ -143,6 +141,7 @@
     return {template, binds};
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
   private static render(template: Template, values: any[]): Fragment {
     const content = template.template.ownerDocument.importNode(template.template.content, true);
     const resultElement = (content.firstChild === content.lastChild ? content.firstChild : content) as Element;
@@ -190,6 +189,7 @@
     return result;
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
   private static nodeForValue(value: any): Node {
     if (value instanceof Node) {
       return value;