@typescript-eslint/eslint-plugin
Version:
TypeScript plugin for ESLint
307 lines (306 loc) • 14.2 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("@typescript-eslint/utils");
const tsutils = __importStar(require("ts-api-utils"));
const ts = __importStar(require("typescript"));
const util_1 = require("../util");
exports.default = (0, util_1.createRule)({
name: 'no-unnecessary-type-conversion',
meta: {
type: 'suggestion',
docs: {
description: 'Disallow conversion idioms when they do not change the type or value of the expression',
requiresTypeChecking: true,
},
hasSuggestions: true,
messages: {
suggestRemove: 'Remove the type conversion.',
suggestSatisfies: 'Instead, assert that the value satisfies the {{type}} type.',
unnecessaryTypeConversion: '{{violation}} does not change the type or value of the {{type}}.',
},
schema: [],
},
defaultOptions: [],
create(context) {
function doesUnderlyingTypeMatchFlag(type, typeFlag) {
return tsutils
.unionConstituents(type)
.every(t => (0, util_1.isTypeFlagSet)(t, typeFlag));
}
const services = (0, util_1.getParserServices)(context);
function handleUnaryOperator(node, typeFlag, typeString, violation, isDoubleOperator) {
const outerNode = isDoubleOperator ? node.parent : node;
const type = services.getTypeAtLocation(node.argument);
if (doesUnderlyingTypeMatchFlag(type, typeFlag)) {
const wrappingFixerParams = {
node: outerNode,
innerNode: [node.argument],
sourceCode: context.sourceCode,
};
context.report({
loc: {
start: outerNode.loc.start,
end: {
column: node.loc.start.column + 1,
line: node.loc.start.line,
},
},
messageId: 'unnecessaryTypeConversion',
data: { type: typeString, violation },
suggest: [
{
messageId: 'suggestRemove',
fix: (0, util_1.getWrappingFixer)(wrappingFixerParams),
},
{
messageId: 'suggestSatisfies',
data: { type: typeString },
fix: (0, util_1.getWrappingFixer)({
...wrappingFixerParams,
wrap: expr => `${expr} satisfies ${typeString}`,
}),
},
],
});
}
}
return {
'AssignmentExpression[operator = "+="]'(node) {
if (node.right.type === utils_1.AST_NODE_TYPES.Literal &&
node.right.value === '' &&
doesUnderlyingTypeMatchFlag(services.getTypeAtLocation(node.left), ts.TypeFlags.StringLike)) {
const wrappingFixerParams = {
node,
innerNode: [node.left],
sourceCode: context.sourceCode,
};
context.report({
node,
messageId: 'unnecessaryTypeConversion',
data: {
type: 'string',
violation: "Concatenating a string with ''",
},
suggest: [
{
messageId: 'suggestRemove',
fix: node.parent.type === utils_1.AST_NODE_TYPES.ExpressionStatement
? (fixer) => [
fixer.removeRange([
node.parent.range[0],
node.parent.range[1],
]),
]
: (0, util_1.getWrappingFixer)(wrappingFixerParams),
},
{
messageId: 'suggestSatisfies',
data: { type: 'string' },
fix: (0, util_1.getWrappingFixer)({
...wrappingFixerParams,
wrap: expr => `${expr} satisfies string`,
}),
},
],
});
}
},
'BinaryExpression[operator = "+"]'(node) {
if (node.right.type === utils_1.AST_NODE_TYPES.Literal &&
node.right.value === '' &&
doesUnderlyingTypeMatchFlag(services.getTypeAtLocation(node.left), ts.TypeFlags.StringLike)) {
const wrappingFixerParams = {
node,
innerNode: [node.left],
sourceCode: context.sourceCode,
};
context.report({
loc: {
start: node.left.loc.end,
end: node.loc.end,
},
messageId: 'unnecessaryTypeConversion',
data: {
type: 'string',
violation: "Concatenating a string with ''",
},
suggest: [
{
messageId: 'suggestRemove',
fix: (0, util_1.getWrappingFixer)(wrappingFixerParams),
},
{
messageId: 'suggestSatisfies',
data: { type: 'string' },
fix: (0, util_1.getWrappingFixer)({
...wrappingFixerParams,
wrap: expr => `${expr} satisfies string`,
}),
},
],
});
}
else if (node.left.type === utils_1.AST_NODE_TYPES.Literal &&
node.left.value === '' &&
doesUnderlyingTypeMatchFlag(services.getTypeAtLocation(node.right), ts.TypeFlags.StringLike)) {
const wrappingFixerParams = {
node,
innerNode: [node.right],
sourceCode: context.sourceCode,
};
context.report({
loc: {
start: node.loc.start,
end: node.right.loc.start,
},
messageId: 'unnecessaryTypeConversion',
data: {
type: 'string',
violation: "Concatenating '' with a string",
},
suggest: [
{
messageId: 'suggestRemove',
fix: (0, util_1.getWrappingFixer)(wrappingFixerParams),
},
{
messageId: 'suggestSatisfies',
data: { type: 'string' },
fix: (0, util_1.getWrappingFixer)({
...wrappingFixerParams,
wrap: expr => `${expr} satisfies string`,
}),
},
],
});
}
},
CallExpression(node) {
const nodeCallee = node.callee;
const builtInTypeFlags = {
BigInt: ts.TypeFlags.BigIntLike,
Boolean: ts.TypeFlags.BooleanLike,
Number: ts.TypeFlags.NumberLike,
String: ts.TypeFlags.StringLike,
};
if (nodeCallee.type !== utils_1.AST_NODE_TYPES.Identifier ||
!(nodeCallee.name in builtInTypeFlags)) {
return;
}
const typeFlag = builtInTypeFlags[nodeCallee.name];
const scope = context.sourceCode.getScope(node);
const variable = scope.set.get(nodeCallee.name);
if (!!variable?.defs.length ||
!doesUnderlyingTypeMatchFlag((0, util_1.getConstrainedTypeAtLocation)(services, node.arguments[0]), typeFlag)) {
return;
}
const wrappingFixerParams = {
node,
innerNode: [node.arguments[0]],
sourceCode: context.sourceCode,
};
const typeString = nodeCallee.name.toLowerCase();
context.report({
node: nodeCallee,
messageId: 'unnecessaryTypeConversion',
data: {
type: nodeCallee.name.toLowerCase(),
violation: `Passing a ${typeString} to ${nodeCallee.name}()`,
},
suggest: [
{
messageId: 'suggestRemove',
fix: (0, util_1.getWrappingFixer)(wrappingFixerParams),
},
{
messageId: 'suggestSatisfies',
data: { type: typeString },
fix: (0, util_1.getWrappingFixer)({
...wrappingFixerParams,
wrap: expr => `${expr} satisfies ${typeString}`,
}),
},
],
});
},
'CallExpression > MemberExpression.callee > Identifier[name = "toString"].property'(node) {
const memberExpr = node.parent;
const type = (0, util_1.getConstrainedTypeAtLocation)(services, memberExpr.object);
if (doesUnderlyingTypeMatchFlag(type, ts.TypeFlags.StringLike)) {
const wrappingFixerParams = {
node: memberExpr.parent,
innerNode: [memberExpr.object],
sourceCode: context.sourceCode,
};
context.report({
loc: {
start: memberExpr.property.loc.start,
end: memberExpr.parent.loc.end,
},
messageId: 'unnecessaryTypeConversion',
data: {
type: 'string',
violation: "Calling a string's .toString() method",
},
suggest: [
{
messageId: 'suggestRemove',
fix: (0, util_1.getWrappingFixer)(wrappingFixerParams),
},
{
messageId: 'suggestSatisfies',
data: { type: 'string' },
fix: (0, util_1.getWrappingFixer)({
...wrappingFixerParams,
wrap: expr => `${expr} satisfies string`,
}),
},
],
});
}
},
'UnaryExpression[operator = "!"] > UnaryExpression[operator = "!"]'(node) {
handleUnaryOperator(node, ts.TypeFlags.BooleanLike, 'boolean', 'Using !! on a boolean', true);
},
'UnaryExpression[operator = "+"]'(node) {
handleUnaryOperator(node, ts.TypeFlags.NumberLike, 'number', 'Using the unary + operator on a number', false);
},
'UnaryExpression[operator = "~"] > UnaryExpression[operator = "~"]'(node) {
handleUnaryOperator(node, ts.TypeFlags.NumberLike, 'number', 'Using ~~ on a number', true);
},
};
},
});