UNPKG

eslint-plugin-etc

Version:
122 lines (121 loc) 5.28 kB
"use strict"; const eslint_etc_1 = require("eslint-etc"); const tsutils = require("tsutils"); const utils_1 = require("../utils"); const rule = (0, utils_1.ruleCreator)({ defaultOptions: [], meta: { docs: { description: "Forbids type parameters without inference sites and type parameters that don't add type safety to declarations.", recommended: false, }, fixable: undefined, hasSuggestions: false, messages: { canReplace: "Type parameter '{{name}}' is not used to enforce a constraint between types and can be replaced with '{{replacement}}'.", cannotInfer: "Type parameter '{{name}}' cannot be inferred from any parameter.", }, schema: [], type: "problem", }, name: "no-misused-generics", create: (context) => { const { esTreeNodeToTSNodeMap } = (0, eslint_etc_1.getParserServices)(context); let usage; function checkSignature(node) { const tsNode = esTreeNodeToTSNodeMap.get(node); if (tsutils.isSignatureDeclaration(tsNode) && tsNode.typeParameters !== undefined) { checkTypeParameters(tsNode.typeParameters, tsNode); } } function checkTypeParameters(typeParameters, signature) { if (usage === undefined) { usage = tsutils.collectVariableUsage(signature.getSourceFile()); } outer: for (const typeParameter of typeParameters) { let usedInParameters = false; let usedInReturnOrExtends = tsutils.isFunctionWithBody(signature); for (const use of usage.get(typeParameter.name).uses) { if (use.location.pos > signature.parameters.pos && use.location.pos < signature.parameters.end) { if (usedInParameters) { continue outer; } usedInParameters = true; } else if (!usedInReturnOrExtends) { usedInReturnOrExtends = use.location.pos > signature.parameters.end || isUsedInConstraint(use.location, typeParameters); } } if (!usedInParameters) { context.report({ data: { name: typeParameter.name.text, }, loc: (0, eslint_etc_1.getLoc)(typeParameter), messageId: "cannotInfer", }); } else if (!usedInReturnOrExtends && !isConstrainedByOtherTypeParameter(typeParameter, typeParameters)) { context.report({ data: { name: typeParameter.name.text, replacement: typeParameter.constraint ? typeParameter.constraint.getText(signature.getSourceFile()) : "unknown", }, loc: (0, eslint_etc_1.getLoc)(typeParameter), messageId: "canReplace", }); } } } function isConstrainedByOtherTypeParameter(current, all) { if (current.constraint === undefined) { return false; } for (const typeParameter of all) { if (typeParameter === current) { continue; } for (const use of usage.get(typeParameter.name).uses) { if (use.location.pos >= current.constraint.pos && use.location.pos < current.constraint.end) { return true; } } } return false; } function isUsedInConstraint(use, typeParameters) { for (const typeParameter of typeParameters) { if (typeParameter.constraint !== undefined && use.pos >= typeParameter.constraint.pos && use.pos < typeParameter.constraint.end) { return true; } } return false; } return { ArrowFunctionExpression: checkSignature, FunctionDeclaration: checkSignature, FunctionExpression: checkSignature, MethodDefinition: checkSignature, "Program:exit": () => (usage = undefined), TSCallSignatureDeclaration: checkSignature, TSConstructorType: checkSignature, TSConstructSignatureDeclaration: checkSignature, TSDeclareFunction: checkSignature, TSFunctionType: checkSignature, TSIndexSignature: checkSignature, TSMethodSignature: checkSignature, TSPropertySignature: checkSignature, }; }, }); module.exports = rule;