@@ -154,6 +154,57 @@ export default iterateJsdoc(({
154154 'The @type tag declaring "{{ type }}" is redundant as TypeScript infers it automatically for literals.' :
155155 'The @type tag declaring "{{ type }}" is redundant as TypeScript infers it automatically.' ;
156156
157+ /**
158+ * Whether `inferredType` is a generic reference carrying `any` type arguments
159+ * that `assertedType` replaces with concrete ones (e.g. an untyped
160+ * `document.querySelectorAll(sel)` giving `NodeListOf<any>`, asserted as
161+ * `NodeListOf<HTMLElement>`, or `new Map()` giving `Map<any, any>`). Such an
162+ * assertion supplies real type information, so it is not redundant even though
163+ * `any` leaves the two types mutually assignable.
164+ * @param {any } inferredType `ts.Type`
165+ * @param {any } assertedType `ts.Type`
166+ * @returns {boolean }
167+ */
168+ const tightensAnyTypeArgument = ( inferredType , assertedType ) => {
169+ // Only ever called for an object type, which always carries `objectFlags`.
170+ if ( ( inferredType . objectFlags & ts . ObjectFlags . Reference ) === 0 ) {
171+ return false ;
172+ }
173+
174+ const assertedTypeArguments = checker . getTypeArguments ( assertedType ) ;
175+
176+ return checker . getTypeArguments ( inferredType ) . some ( ( inferredTypeArgument , index ) => {
177+ return ( inferredTypeArgument . flags & ts . TypeFlags . Any ) !== 0 &&
178+ assertedTypeArguments [ index ] !== undefined &&
179+ ( assertedTypeArguments [ index ] . flags & ts . TypeFlags . Any ) === 0 ;
180+ } ) ;
181+ } ;
182+
183+ /**
184+ * A generic call/`new` expression takes its type arguments partly from the
185+ * surrounding contextual type, which under a `@type` (a cast, or a
186+ * declaration) is the asserted type itself. `getTypeAtLocation` then just
187+ * echoes the asserted type back, so a genuine tightening looks redundant
188+ * (`document.querySelectorAll(sel)` is really `NodeListOf<Element>`, not the
189+ * asserted `NodeListOf<HTMLElement>`). The uncontaminated type cannot be
190+ * recovered here, so such expressions are left alone.
191+ * @param {any } tsExpression `ts.Node`
192+ * @returns {boolean }
193+ */
194+ const isGenericCall = ( tsExpression ) => {
195+ if ( ! ts . isCallExpression ( tsExpression ) && ! ts . isNewExpression ( tsExpression ) ) {
196+ return false ;
197+ }
198+
199+ const signature = /** @type {any } */ (
200+ checker . getResolvedSignature ( tsExpression )
201+ ) ;
202+ return Boolean (
203+ signature &&
204+ ( signature . typeParameters ?? signature . target ?. typeParameters ) ?. length ,
205+ ) ;
206+ } ;
207+
157208 /**
158209 * Whether the JSDoc-asserted type adds nothing over the type TypeScript
159210 * already infers for the expression it is attached to.
@@ -209,6 +260,10 @@ export default iterateJsdoc(({
209260 return false ;
210261 }
211262
263+ if ( tightensAnyTypeArgument ( rawInferredType , rawAssertedType ) ) {
264+ return false ;
265+ }
266+
212267 // Objects: strip the literal-initialization flags, then require structural
213268 // equivalence in both directions, so `{prop: string}` vs `{prop: string}` is
214269 // redundant while `{prop?: string}` vs `{prop: string}` fails backward.
@@ -256,9 +311,12 @@ export default iterateJsdoc(({
256311 return ;
257312 }
258313
259- const declInferredType = checker . getTypeAtLocation (
260- services . esTreeNodeToTSNodeMap . get ( decl . init ) ,
261- ) ;
314+ const declInitTsNode = services . esTreeNodeToTSNodeMap . get ( decl . init ) ;
315+ if ( isGenericCall ( declInitTsNode ) ) {
316+ return ;
317+ }
318+
319+ const declInferredType = checker . getTypeAtLocation ( declInitTsNode ) ;
262320 const declAssertedType = checker . getTypeFromTypeNode ( jsdocTypeNode ) ;
263321
264322 if ( isRedundantAssertion ( declInferredType , declAssertedType ) ) {
@@ -285,6 +343,10 @@ export default iterateJsdoc(({
285343 return ;
286344 }
287345
346+ if ( isGenericCall ( exprTsNode ) ) {
347+ return ;
348+ }
349+
288350 const parent = /** @type {any } */ ( node . parent ) ;
289351 const declaration = parent . type === 'VariableDeclarator' ? parent . parent : null ;
290352
0 commit comments