UNPKG

eslint-plugin-typescript

Version:
304 lines (269 loc) 10 kB
/** * @fileoverview Prevent TypeScript-specific variables being falsely marked as unused * @author James Henry */ "use strict"; /** * Record that a particular variable has been used in code * * @param {Object} context The current rule context. * @param {string} name The name of the variable to mark as used. * @returns {boolean} True if the variable was found and marked as used, false if not. */ function markVariableAsUsed(context, name) { let scope = context.getScope(); let variables; let i; let len; let found = false; // Special Node.js scope means we need to start one level deeper if (scope.type === "global") { while (scope.childScopes.length) { scope = scope.childScopes[0]; } } do { variables = scope.variables; for (i = 0, len = variables.length; i < len; i++) { if (variables[i].name === name) { variables[i].eslintUsed = true; found = true; } } scope = scope.upper; } while (scope); return found; } //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ module.exports = { meta: { docs: { description: "Prevent TypeScript-specific constructs from being erroneously flagged as unused", category: "TypeScript", recommended: true, url: "https://github.com/nzakas/eslint-plugin-typescript/blob/master/docs/rules/no-unused-vars.md" }, schema: [] }, create(context) { //---------------------------------------------------------------------- // Helpers //---------------------------------------------------------------------- /** * Checks the given node type annotation and marks it as used. * @param {ASTNode} node the relevant AST node. * @returns {void} * @private */ function markTypeAnnotationAsUsed(node) { const annotation = node.typeAnnotation || node; switch (annotation.type) { case "Identifier": { markVariableAsUsed(context, annotation.name); break; } case "TSArrayType": { markTypeAnnotationAsUsed(annotation.elementType); break; } case "TSQualifiedName": { markTypeAnnotationAsUsed(annotation.left); markTypeAnnotationAsUsed(annotation.right); break; } case "TSTypeReference": { if (annotation.typeName.type === "TSArrayType") { markTypeAnnotationAsUsed( annotation.typeName.elementType ); } else if (annotation.typeName.type === "TSQualifiedName") { markTypeAnnotationAsUsed(annotation.typeName); } else { markVariableAsUsed(context, annotation.typeName.name); if ( annotation.typeParameters && annotation.typeParameters.params ) { annotation.typeParameters.params.forEach(param => { markTypeAnnotationAsUsed(param); }); } } break; } case "TSTypeLiteral": { annotation.members.forEach(member => { if (member.typeAnnotation) { markTypeAnnotationAsUsed(member.typeAnnotation); } }); break; } case "TSUnionType": case "TSIntersectionType": annotation.types.forEach(type => { markTypeAnnotationAsUsed(type); }); break; default: break; } } /** * Checks the given decorator and marks it as used. * @param {ASTNode} node The relevant AST node. * @returns {void} * @private */ function markDecoratorAsUsed(node) { /** * Decorator */ if (node.name) { markVariableAsUsed(context, node.name); return; } if (node.expression && node.expression.name) { markVariableAsUsed(context, node.expression.name); return; } /** * Decorator Factory */ if (node.callee && node.callee.name) { markVariableAsUsed(context, node.callee.name); } if ( node.expression && node.expression.callee && node.expression.callee.name ) { markVariableAsUsed(context, node.expression.callee.name); } } /** * Checks the given interface and marks it as used. * Generic arguments are also included in the check. * @param {ASTNode} node The relevant AST node. * @returns {void} * @private */ function markImplementedInterfaceAsUsed(node) { if (!node || !node.id || !node.id.name) { return; } markVariableAsUsed(context, node.id.name); if (!node.typeParameters || !node.typeParameters.params) { return; } node.typeParameters.params.forEach(markTypeAnnotationAsUsed); } /** * Checks the given class has a super class and marks it as used. * Generic arguments are also included in the check. * @param {ASTNode} node The relevant AST node. * @returns {void} * @private */ function markSuperClassAsUsed(node) { if (!node.superClass) { return; } markVariableAsUsed(context, node.superClass.name); if (!node.superTypeParameters || !node.superTypeParameters.params) { return; } node.superTypeParameters.params.forEach(markTypeAnnotationAsUsed); } /** * Checks the given interface and marks it as used. * Generic arguments are also included in the check. * This is used when interfaces are extending other interfaces. * @param {ASTNode} node the relevant AST node. * @returns {void} * @private */ function markExtendedInterfaceAsUsed(node) { if (!node || !node.id || !node.id.name) { return; } markVariableAsUsed(context, node.id.name); if (!node.typeParameters || !node.typeParameters.params) { return; } node.typeParameters.params.forEach(markTypeAnnotationAsUsed); } /** * Checks the given function return type and marks it as used. * @param {ASTNode} node the relevant AST node. * @returns {void} * @private */ function markFunctionReturnTypeAsUsed(node) { if (node.returnType) { markTypeAnnotationAsUsed(node.returnType); } } /** * Checks the given class and marks super classes, interfaces and decoratores as used. * @param {ASTNode} node the relevant AST node. * @returns {void} * @private */ function markClassOptionsAsUsed(node) { markSuperClassAsUsed(node); if (node.implements) { node.implements.forEach(markImplementedInterfaceAsUsed); } if (node.decorators) { node.decorators.forEach(markDecoratorAsUsed); } } //---------------------------------------------------------------------- // Public //---------------------------------------------------------------------- return { Identifier(node) { if (node.typeAnnotation) { markTypeAnnotationAsUsed(node.typeAnnotation); } if (node.decorators) { node.decorators.forEach(markDecoratorAsUsed); } }, TSTypeAnnotation(node) { if (node.typeAnnotation) { markTypeAnnotationAsUsed(node.typeAnnotation); } }, FunctionDeclaration: markFunctionReturnTypeAsUsed, FunctionExpression: markFunctionReturnTypeAsUsed, ArrowFunctionExpression: markFunctionReturnTypeAsUsed, CallExpression(node) { if (node.typeParameters && node.typeParameters.params) { node.typeParameters.params.forEach( markTypeAnnotationAsUsed ); } }, Decorator: markDecoratorAsUsed, TSInterfaceHeritage: markExtendedInterfaceAsUsed, ClassDeclaration: markClassOptionsAsUsed, ClassExpression: markClassOptionsAsUsed, ObjectPattern(node) { if (node.typeAnnotation) { markTypeAnnotationAsUsed(node.typeAnnotation); } }, MethodDefinition(node) { if (node.decorators) { node.decorators.forEach(markDecoratorAsUsed); } } }; } };