Skip to content

fix(eslint-plugin): [no-unnecessary-type-assertion] should report readonly class properties with a literal initializer #10618

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

33 changes: 21 additions & 12 deletions packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,23 +156,32 @@ export default createRule<Options, MessageIds>({
);
}

function isImplicitlyNarrowedConstDeclaration({
function isTemplateLiteralWithExpressions(expression: TSESTree.Expression) {
return (
expression.type === AST_NODE_TYPES.TemplateLiteral &&
expression.expressions.length !== 0
);
}

function isImplicitlyNarrowedLiteralDeclaration({
expression,
parent,
}: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion): boolean {
/**
* Even on `const` variable declarations, template literals with expressions can sometimes be widened without a type assertion.
* @see https://github.com/typescript-eslint/typescript-eslint/issues/8737
*/
if (isTemplateLiteralWithExpressions(expression)) {
return false;
}

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const maybeDeclarationNode = parent.parent!;
const isTemplateLiteralWithExpressions =
expression.type === AST_NODE_TYPES.TemplateLiteral &&
expression.expressions.length !== 0;

return (
maybeDeclarationNode.type === AST_NODE_TYPES.VariableDeclaration &&
maybeDeclarationNode.kind === 'const' &&
/**
* Even on `const` variable declarations, template literals with expressions can sometimes be widened without a type assertion.
* @see https://github.com/typescript-eslint/typescript-eslint/issues/8737
*/
!isTemplateLiteralWithExpressions
(maybeDeclarationNode.type === AST_NODE_TYPES.VariableDeclaration &&
maybeDeclarationNode.kind === 'const') ||
(parent.type === AST_NODE_TYPES.PropertyDefinition && parent.readonly)
);
}

Expand Down Expand Up @@ -225,7 +234,7 @@ export default createRule<Options, MessageIds>({
const typeIsUnchanged = isTypeUnchanged(uncastType, castType);

const wouldSameTypeBeInferred = castType.isLiteral()
? isImplicitlyNarrowedConstDeclaration(node)
? isImplicitlyNarrowedLiteralDeclaration(node)
: !isConstAssertion(node.typeAnnotation);

if (typeIsUnchanged && wouldSameTypeBeInferred) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,28 @@ const y: number = x!;
const x: number | null = null;
class Foo {
prop: number = x!;
}
`,
`
class T {
a = 'a' as const;
}
`,
`
class T {
a = 3 as 3;
}
`,
`
const foo = 'foo';

class T {
readonly test = \`\${foo}\` as const;
}
`,
`
class T {
readonly a = { foo: 'foo' } as const;
}
`,
`
Expand Down Expand Up @@ -1138,6 +1160,82 @@ var x = 1;
},
{
code: `
class T {
readonly a = 'a' as const;
}
`,
errors: [
{
line: 3,
messageId: 'unnecessaryAssertion',
},
],
output: `
class T {
readonly a = 'a';
}
`,
},
{
code: `
class T {
readonly a = 3 as 3;
}
`,
errors: [
{
line: 3,
messageId: 'unnecessaryAssertion',
},
],
output: `
class T {
readonly a = 3;
}
`,
},
{
code: `
type S = 10;

class T {
readonly a = 10 as S;
}
`,
errors: [
{
line: 5,
messageId: 'unnecessaryAssertion',
},
],
output: `
type S = 10;

class T {
readonly a = 10;
}
`,
},
{
code: `
class T {
readonly a = (3 + 5) as number;
}
`,
errors: [
{
line: 3,
messageId: 'unnecessaryAssertion',
},
],
output: `
class T {
readonly a = (3 + 5);
}
`,
},
{
code: `
const a = '';
const b: string | undefined = (a ? undefined : a)!;
`,
Expand Down
Loading