144 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			144 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @fileoverview Rule to disallow unused labels.
 | |
|  * @author Toru Nagashima
 | |
|  */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| // Requirements
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| const astUtils = require("./utils/ast-utils");
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| // Rule Definition
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| /** @type {import('../shared/types').Rule} */
 | |
| module.exports = {
 | |
|     meta: {
 | |
|         type: "suggestion",
 | |
| 
 | |
|         docs: {
 | |
|             description: "Disallow unused labels",
 | |
|             recommended: true,
 | |
|             url: "https://eslint.org/docs/latest/rules/no-unused-labels"
 | |
|         },
 | |
| 
 | |
|         schema: [],
 | |
| 
 | |
|         fixable: "code",
 | |
| 
 | |
|         messages: {
 | |
|             unused: "'{{name}}:' is defined but never used."
 | |
|         }
 | |
|     },
 | |
| 
 | |
|     create(context) {
 | |
|         const sourceCode = context.sourceCode;
 | |
|         let scopeInfo = null;
 | |
| 
 | |
|         /**
 | |
|          * Adds a scope info to the stack.
 | |
|          * @param {ASTNode} node A node to add. This is a LabeledStatement.
 | |
|          * @returns {void}
 | |
|          */
 | |
|         function enterLabeledScope(node) {
 | |
|             scopeInfo = {
 | |
|                 label: node.label.name,
 | |
|                 used: false,
 | |
|                 upper: scopeInfo
 | |
|             };
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Checks if a `LabeledStatement` node is fixable.
 | |
|          * For a node to be fixable, there must be no comments between the label and the body.
 | |
|          * Furthermore, is must be possible to remove the label without turning the body statement into a
 | |
|          * directive after other fixes are applied.
 | |
|          * @param {ASTNode} node The node to evaluate.
 | |
|          * @returns {boolean} Whether or not the node is fixable.
 | |
|          */
 | |
|         function isFixable(node) {
 | |
| 
 | |
|             /*
 | |
|              * Only perform a fix if there are no comments between the label and the body. This will be the case
 | |
|              * when there is exactly one token/comment (the ":") between the label and the body.
 | |
|              */
 | |
|             if (sourceCode.getTokenAfter(node.label, { includeComments: true }) !==
 | |
|                 sourceCode.getTokenBefore(node.body, { includeComments: true })) {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             // Looking for the node's deepest ancestor which is not a `LabeledStatement`.
 | |
|             let ancestor = node.parent;
 | |
| 
 | |
|             while (ancestor.type === "LabeledStatement") {
 | |
|                 ancestor = ancestor.parent;
 | |
|             }
 | |
| 
 | |
|             if (ancestor.type === "Program" ||
 | |
|                 (ancestor.type === "BlockStatement" && astUtils.isFunction(ancestor.parent))) {
 | |
|                 const { body } = node;
 | |
| 
 | |
|                 if (body.type === "ExpressionStatement" &&
 | |
|                     ((body.expression.type === "Literal" && typeof body.expression.value === "string") ||
 | |
|                     astUtils.isStaticTemplateLiteral(body.expression))) {
 | |
|                     return false; // potential directive
 | |
|                 }
 | |
|             }
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Removes the top of the stack.
 | |
|          * At the same time, this reports the label if it's never used.
 | |
|          * @param {ASTNode} node A node to report. This is a LabeledStatement.
 | |
|          * @returns {void}
 | |
|          */
 | |
|         function exitLabeledScope(node) {
 | |
|             if (!scopeInfo.used) {
 | |
|                 context.report({
 | |
|                     node: node.label,
 | |
|                     messageId: "unused",
 | |
|                     data: node.label,
 | |
|                     fix: isFixable(node) ? fixer => fixer.removeRange([node.range[0], node.body.range[0]]) : null
 | |
|                 });
 | |
|             }
 | |
| 
 | |
|             scopeInfo = scopeInfo.upper;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Marks the label of a given node as used.
 | |
|          * @param {ASTNode} node A node to mark. This is a BreakStatement or
 | |
|          *      ContinueStatement.
 | |
|          * @returns {void}
 | |
|          */
 | |
|         function markAsUsed(node) {
 | |
|             if (!node.label) {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             const label = node.label.name;
 | |
|             let info = scopeInfo;
 | |
| 
 | |
|             while (info) {
 | |
|                 if (info.label === label) {
 | |
|                     info.used = true;
 | |
|                     break;
 | |
|                 }
 | |
|                 info = info.upper;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return {
 | |
|             LabeledStatement: enterLabeledScope,
 | |
|             "LabeledStatement:exit": exitLabeledScope,
 | |
|             BreakStatement: markAsUsed,
 | |
|             ContinueStatement: markAsUsed
 | |
|         };
 | |
|     }
 | |
| };
 |