1/** 2 * @fileoverview Disallow Labeled Statements 3 * @author Nicholas C. Zakas 4 */ 5"use strict"; 6 7//------------------------------------------------------------------------------ 8// Requirements 9//------------------------------------------------------------------------------ 10 11const astUtils = require("./utils/ast-utils"); 12 13//------------------------------------------------------------------------------ 14// Rule Definition 15//------------------------------------------------------------------------------ 16 17module.exports = { 18 meta: { 19 type: "suggestion", 20 21 docs: { 22 description: "disallow labeled statements", 23 category: "Best Practices", 24 recommended: false, 25 url: "https://eslint.org/docs/rules/no-labels" 26 }, 27 28 schema: [ 29 { 30 type: "object", 31 properties: { 32 allowLoop: { 33 type: "boolean", 34 default: false 35 }, 36 allowSwitch: { 37 type: "boolean", 38 default: false 39 } 40 }, 41 additionalProperties: false 42 } 43 ], 44 45 messages: { 46 unexpectedLabel: "Unexpected labeled statement.", 47 unexpectedLabelInBreak: "Unexpected label in break statement.", 48 unexpectedLabelInContinue: "Unexpected label in continue statement." 49 } 50 }, 51 52 create(context) { 53 const options = context.options[0]; 54 const allowLoop = options && options.allowLoop; 55 const allowSwitch = options && options.allowSwitch; 56 let scopeInfo = null; 57 58 /** 59 * Gets the kind of a given node. 60 * @param {ASTNode} node A node to get. 61 * @returns {string} The kind of the node. 62 */ 63 function getBodyKind(node) { 64 if (astUtils.isLoop(node)) { 65 return "loop"; 66 } 67 if (node.type === "SwitchStatement") { 68 return "switch"; 69 } 70 return "other"; 71 } 72 73 /** 74 * Checks whether the label of a given kind is allowed or not. 75 * @param {string} kind A kind to check. 76 * @returns {boolean} `true` if the kind is allowed. 77 */ 78 function isAllowed(kind) { 79 switch (kind) { 80 case "loop": return allowLoop; 81 case "switch": return allowSwitch; 82 default: return false; 83 } 84 } 85 86 /** 87 * Checks whether a given name is a label of a loop or not. 88 * @param {string} label A name of a label to check. 89 * @returns {boolean} `true` if the name is a label of a loop. 90 */ 91 function getKind(label) { 92 let info = scopeInfo; 93 94 while (info) { 95 if (info.label === label) { 96 return info.kind; 97 } 98 info = info.upper; 99 } 100 101 /* istanbul ignore next: syntax error */ 102 return "other"; 103 } 104 105 //-------------------------------------------------------------------------- 106 // Public 107 //-------------------------------------------------------------------------- 108 109 return { 110 LabeledStatement(node) { 111 scopeInfo = { 112 label: node.label.name, 113 kind: getBodyKind(node.body), 114 upper: scopeInfo 115 }; 116 }, 117 118 "LabeledStatement:exit"(node) { 119 if (!isAllowed(scopeInfo.kind)) { 120 context.report({ 121 node, 122 messageId: "unexpectedLabel" 123 }); 124 } 125 126 scopeInfo = scopeInfo.upper; 127 }, 128 129 BreakStatement(node) { 130 if (node.label && !isAllowed(getKind(node.label.name))) { 131 context.report({ 132 node, 133 messageId: "unexpectedLabelInBreak" 134 }); 135 } 136 }, 137 138 ContinueStatement(node) { 139 if (node.label && !isAllowed(getKind(node.label.name))) { 140 context.report({ 141 node, 142 messageId: "unexpectedLabelInContinue" 143 }); 144 } 145 } 146 }; 147 148 } 149}; 150