Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(eslint-plugin): [no-unnecessary-type-assertion] fix false negative for const variable declarations #8558

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 8 additions & 43 deletions packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts
abrahamguo marked this conversation as resolved.
Show resolved Hide resolved
Expand Up @@ -59,37 +59,6 @@ export default createRule<Options, MessageIds>({
const checker = services.program.getTypeChecker();
const compilerOptions = services.program.getCompilerOptions();

/**
* Sometimes tuple types don't have ObjectFlags.Tuple set, like when they're being matched against an inferred type.
* So, in addition, check if there are integer properties 0..n and no other numeric keys
*/
function couldBeTupleType(type: ts.ObjectType): boolean {
const properties = type.getProperties();

if (properties.length === 0) {
return false;
}
let i = 0;

for (; i < properties.length; ++i) {
const name = properties[i].name;

if (String(i) !== name) {
if (i === 0) {
// if there are no integer properties, this is not a tuple
return false;
}
break;
}
}
for (; i < properties.length; ++i) {
if (String(+properties[i].name) === properties[i].name) {
return false; // if there are any other numeric properties, this is not a tuple
}
}
return true;
}

/**
* Returns true if there's a chance the variable has been used before a value has been assigned to it
*/
Expand Down Expand Up @@ -242,20 +211,16 @@ export default createRule<Options, MessageIds>({

const castType = services.getTypeAtLocation(node);

const grandparent = node.parent.parent!;

if (
isTypeFlagSet(castType, ts.TypeFlags.Literal) ||
(tsutils.isObjectType(castType) &&
(tsutils.isObjectFlagSet(castType, ts.ObjectFlags.Tuple) ||
couldBeTupleType(castType)))
// It's not safe to remove a cast to a literal type, unless we are in a `const` variable declaration, as that
// type would otherwise be widened without the cast.
((grandparent.type === AST_NODE_TYPES.VariableDeclaration &&
grandparent.kind === 'const') ||
!castType.isLiteral()) &&
services.getTypeAtLocation(node.expression) === castType
) {
// It's not always safe to remove a cast to a literal type or tuple
// type, as those types are sometimes widened without the cast.
return;
abrahamguo marked this conversation as resolved.
Show resolved Hide resolved
}

const uncastType = services.getTypeAtLocation(node.expression);

if (uncastType === castType) {
context.report({
node,
messageId: 'unnecessaryAssertion',
Expand Down
Expand Up @@ -25,10 +25,21 @@ if (
const name = member.id as TSESTree.StringLiteral;
}
`,
`
type Bar = 'bar';
const data = {
x: 'foo' as 'foo',
y: 'bar' as Bar,
};
`,
"[1, 2, 3, 4, 5].map(x => [x, 'A' + x] as [number, string]);",
`
let x: Array<[number, string]> = [1, 2, 3, 4, 5].map(
x => [x, 'A' + x] as [number, string],
);
`,
'const foo = 3 as number;',
'const foo = <number>3;',
'const foo = <3>3;',
'const foo = 3 as 3;',
abrahamguo marked this conversation as resolved.
Show resolved Hide resolved
`
type Tuple = [3, 'hi', 'bye'];
const foo = [3, 'hi', 'bye'] as Tuple;
Expand Down Expand Up @@ -245,6 +256,38 @@ const item = <object>arr[0];
],

invalid: [
{
code: 'const foo = <3>3;',
output: 'const foo = 3;',
errors: [{ messageId: 'unnecessaryAssertion', line: 1, column: 13 }],
},
{
code: 'const foo = 3 as 3;',
output: 'const foo = 3;',
errors: [{ messageId: 'unnecessaryAssertion', line: 1, column: 13 }],
},
{
code: `
type Foo = 3;
const foo = <Foo>3;
`,
output: `
type Foo = 3;
const foo = 3;
`,
errors: [{ messageId: 'unnecessaryAssertion', line: 3, column: 21 }],
},
{
code: `
type Foo = 3;
const foo = 3 as Foo;
`,
output: `
type Foo = 3;
const foo = 3;
`,
errors: [{ messageId: 'unnecessaryAssertion', line: 3, column: 21 }],
},
{
code: `
const foo = 3;
Expand Down