# no-unnecessary-type-assertion
JavaScript-based TypeScript type assertions (`@type`) are helpful when you have TypeScript running against JavaScript files with the `checkJs`/`allowJs` options. However,
they become redundant when the type is equal to or more broad than the type which
TypeScript infers for the expression.
An assertion on an `any` expression (for instance a property read off
`JSON.parse(s)`) is never reported: `any` is assignable to every type, so a
concrete assertion on it is narrowing rather than redundant. The same holds for
structureless placeholders — `never`, `null`, `undefined`, an empty array or
object, `any[]`, and unions built only from those (e.g. `never[] | {}` from
`cond ? [] : {}`) — where the assertion is supplying the real shape.
Checks both a leading `@type` on a `VariableDeclaration` (e.g.
`/** @type {number} */ const x = 5;`) and an inline JSDoc cast around a
parenthesized expression (e.g. `const x = /** @type {number} */ (5);`).
For a `VariableDeclaration` the fixer removes the redundant `@type` tag,
deleting the whole JSDoc block if nothing else is left in it. For an inline
cast the fixer removes the comment and unwraps the parentheses
(`const x = /** @type {5} */ (5);` becomes `const x = 5;`); it is skipped
where the parentheses are load-bearing for precedence or Automatic Semicolon
Insertion (ASI). An inline `/** @type {const} */` cast is only reported (and
fixed) on a `const` declarator, where the literal type is inferred anyway;
elsewhere (a `let`/`var` binding, a `return`, an object-property value, etc.) the
cast suppresses widening, so it is doing real work and is left alone.
Generic `call()`/`new` expressions whose type arguments are inferred (e.g.
`document.querySelectorAll(sel)`, which defaults to `NodeListOf`) are
never reported: the `@type` supplies the contextual type TypeScript uses to
infer those arguments, so the inferred and asserted types always coincide and a
real narrowing (to `NodeListOf`, say) cannot be told apart from a
redundant one. Template literals with interpolations (`` `${x}Reference` ``) are
skipped for the same reason — they widen to `string` on their own, but the
assertion contextually narrows them to a template-literal type.
The `unknown` half of a "cast through `unknown`"
(`/** @type {Foo} */ (/** @type {unknown} */ (x))`) is likewise never reported:
it is the bridge that lets the outer assertion reach an otherwise-incompatible
type, so it is load-bearing despite `unknown` being broader than everything.
A cast whose comment sits on an inner parenthesized sub-expression that is then
a member or argument of the outer cast's operand
(`/** @type {DOMException} */ (reader.error).message`) is skipped entirely,
since removing or unwrapping it would target the wrong expression.
**Note that this experimental rule requires that the `typescript` package is installed.
You must also install and point to the `typescript-eslint` parser, targeting your
JavaScript + JSDoc files. Note also that this rule runs fairly slowly.**
```js
// eslint.config.js
import {
parser as typescriptEslintParser,
} from 'typescript-eslint';
export default [
{
languageOptions: {
parser: typescriptEslintParser,
parserOptions: {
projectService: {
allowDefaultProject: [
'*.js',
],
},
tsconfigRootDir: import.meta.dirname,
}
},
rules: {
'jsdoc/no-unnecessary-type-assertion': ['error', {
// You can change these defaults
checkLiteralConstAssertions: false,
enableFixer: true,
treatAnyAsRedundant: false,
typesToIgnore: [],
}]
}
}
];
```
## Options
A single options object has the following properties.
### checkLiteralConstAssertions
Whether to check `const` type assertions as redundant
### enableFixer
Whether to enable the fixer that removes the redundant `@type` tag (and the JSDoc block if it becomes empty). Defaults to `true`.
### treatAnyAsRedundant
Whether to treat `any` type casts as redundant
### typesToIgnore
An array list of types to ignore
|||
|---|---|
|Context|`VariableDeclaration`; inline `/** @type */` casts|
|Tags|`type`|
|Recommended|false|
|Options|`checkLiteralConstAssertions`, `enableFixer`, `treatAnyAsRedundant`, `typesToIgnore`|
## Failing examples
The following patterns are considered problems:
````ts
/**
* @type {5}
*/
const a = 5;
// Message: The @type tag declaring "5" is redundant as TypeScript infers it automatically.
/**
* This is a special comment.
* @type {5}
*/
const a = 5;
// Message: The @type tag declaring "5" is redundant as TypeScript infers it automatically.
/**
* @type {string}
*/
const a = 'hello';
// Message: The @type tag declaring "string" is redundant as TypeScript infers it automatically.
/**
* @type {string}
*/
const a = 'hello';
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"typesToIgnore":["number","boolean"]}]
// Message: The @type tag declaring "string" is redundant as TypeScript infers it automatically.
/**
* @type {const}
*/
const a = 'hello';
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"checkLiteralConstAssertions":true}]
// Message: The @type tag declaring "const" is redundant as TypeScript infers it automatically for literals.
/**
* @type {const}
*/
const a = true;
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"checkLiteralConstAssertions":true}]
// Message: The @type tag declaring "const" is redundant as TypeScript infers it automatically for literals.
/**
* @param {"a"|15} b
*/
function quux (b) {
/**
* @type {const}
*/
const a = b;
}
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"checkLiteralConstAssertions":true}]
// Message: The @type tag declaring "const" is redundant as TypeScript infers it automatically for literals.
/**
* @param {true} a
*/
function quux (a) {
/**
* @type {boolean}
*/
const b = a;
}
// Message: The @type tag declaring "boolean" is redundant as TypeScript infers it automatically.
const a = /** @type {any} */ (5);
/**
* @type {any}
*/
const b = a;
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"treatAnyAsRedundant":true}]
// Message: The @type tag declaring "any" is redundant as TypeScript infers it automatically.
const a = /** @type {5} */ (5);
// Message: The @type tag declaring "5" is redundant as TypeScript infers it automatically.
const a = /** @type {boolean} */ (true);
/**
* @type {true}
*/
const b = a;
// Message: The @type tag declaring "boolean" is redundant as TypeScript infers it automatically.
foo(/** @type {number[]} */ ([1, 2]));
// Message: The @type tag declaring "number[]" is redundant as TypeScript infers it automatically.
const d = /** @type {Date} */ (new Date());
// Message: The @type tag declaring "Date" is redundant as TypeScript infers it automatically.
const u = /** @type {unknown} */ (5);
// Message: The @type tag declaring "unknown" is redundant as TypeScript infers it automatically.
let a;
a = /** @type {5} */ (5);
// Message: The @type tag declaring "5" is redundant as TypeScript infers it automatically.
const a = /** @type {const} */ (5);
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"checkLiteralConstAssertions":true}]
// Message: The @type tag declaring "const" is redundant as TypeScript infers it automatically for literals.
const arr = [1];
arr[/** @type {0} */ (0)];
// Message: The @type tag declaring "0" is redundant as TypeScript infers it automatically.
const a = globalThis.b ? /** @type {5} */ (5) : 6;
// Message: The @type tag declaring "5" is redundant as TypeScript infers it automatically.
const a = /** @type {5} */ (5);
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"enableFixer":false}]
// Message: The @type tag declaring "5" is redundant as TypeScript infers it automatically.
/** @type {{prop: string}} */
const mapPaths = {prop: "text"};
// Message: The @type tag declaring "{prop: string}" is redundant as TypeScript infers it automatically.
/** @type {string} */
const a = 'x', b = 2;
// Message: The @type tag declaring "string" is redundant as TypeScript infers it automatically.
/**
* Keep me.
* @type {string}
*/
const a = 'hello';
// Message: The @type tag declaring "string" is redundant as TypeScript infers it automatically.
/**
* @type {string}
*/
const a = 'hello';
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"enableFixer":false}]
// Message: The @type tag declaring "string" is redundant as TypeScript infers it automatically.
````
## Passing examples
The following patterns are not considered problems:
````ts
/**
* @param {boolean} a
*/
function quux (a) {
/**
* @type {true}
*/
const b = a;
}
const a = /** @type {string} */ (5);
const a = /** @type {any} */ (5);
const a = /** @type {5} */ (5);
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"typesToIgnore":["5"]}]
/**
* @type {() => void}
*/
function quux () {}
let a = /** @type {5} */ (5);
let a = /** @type {const} */ (5);
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"checkLiteralConstAssertions":true}]
foo(/** @type {const} */ (5));
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"checkLiteralConstAssertions":true}]
const f = () => /** @type {const} */ (5);
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"checkLiteralConstAssertions":true}]
/**
* @type {string}
*/
const a = 5;
/**
* @type {any}
*/
const a = 5;
const a = /** @type {any} */ (5);
/**
* @type {any}
*/
const b = a;
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"treatAnyAsRedundant":false}]
/**
* @type {const}
*/
const a = 'hello';
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"checkLiteralConstAssertions":false}]
/**
* @type {string}
*/
const a = 'hello';
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"typesToIgnore":["number","string"]}]
/**
* @param {string} b
*/
function quux (b) {
/**
* @type {const}
*/
const a = b;
}
// "jsdoc/no-unnecessary-type-assertion": ["error"|"warn", {"checkLiteralConstAssertions":true}]
/** @type {string[]} */
const mapPaths = [];
/** @type {{prop?: string}} */
const mapPaths = {};
/** @type {{prop?: string}} */
const mapPaths = {prop: "text"};
/** @type {string} */
const a = 5, b = 'x';
/**
* @param {string} sel
* @returns {HTMLElement[]}
*/
const $$ = (sel) => [...(/** @type {NodeListOf} */ (
document.querySelectorAll(sel)
))];
/**
* @param {string} sel
*/
const q = (sel) => {
/** @type {NodeListOf} */
const els = document.querySelectorAll(sel);
return els;
};
const p = /** @type {Promise} */ (Promise.resolve(5));
const m = /** @type {Map} */ (new Map());
let match = null;
// ...
const mtch = /** @type {RegExpMatchArray} */ (
/** @type {unknown} */ (match)
);
/**
* @typedef {"Int8Array"|"Uint8Array"|"Uint8ClampedArray"|
* "Int16Array"|"Uint16Array"|"Int32Array"|"Uint32Array"|
* "Float32Array"|"Float64Array"|"BigInt64Array"|
* "BigUint64Array"} TypedArray
*/
/**
* @param {string} s
*/
const parse = (s) => {
const bufferSourceClass = 'someClass';
const o = JSON.parse(s);
return getTypedArray(
/** @type {TypedArray} */ (o.typedArray ?? bufferSourceClass)
);
};
/**
* @param {string} s
*/
const setter = (s) => {
const o = JSON.parse(s);
typedArray.set(...(
/**
* @type {[
* array: Array & Array,
* offset?: number | undefined
* ]}
*/ (o.set)
));
};
const reader = new FileReader();
reader.addEventListener(
'error',
async function () {
await dialogs.alert(/** @type {string} */ (
/** @type {DOMException} */ (reader.error).message
));
}
);
const parsed = JSON.parse('{}');
const z = /** @type {string} */ (
/** @type {{y: string}} */ (parsed).y
);
const aType = 'array';
const newType = /** @type {"arrayReference"|"objectReference"} */ (
`${aType}Reference`
);
const aType = 'array';
/** @type {"arrayReference"|"objectReference"} */
const newType = `${aType}Reference`;
/** @type {{[key: (string|number)]: any}} */
const retObj = this.array && !this.record ? [] : {};
/** @type {({[key: string]: any})|any[]} */
const ret = someString === 'object' ? {} : [];
const list = /** @type {string[] | undefined} */ (
Math.random() ? [] : undefined
);
const a = /* @type {number} */ (3 + 5);
````