1/** 2 * @fileoverview Rule to control usage of strict mode directives. 3 * @author Brandon Mills 4 */ 5 6"use strict"; 7 8//------------------------------------------------------------------------------ 9// Requirements 10//------------------------------------------------------------------------------ 11 12const astUtils = require("./utils/ast-utils"); 13 14//------------------------------------------------------------------------------ 15// Helpers 16//------------------------------------------------------------------------------ 17 18/** 19 * Gets all of the Use Strict Directives in the Directive Prologue of a group of 20 * statements. 21 * @param {ASTNode[]} statements Statements in the program or function body. 22 * @returns {ASTNode[]} All of the Use Strict Directives. 23 */ 24function getUseStrictDirectives(statements) { 25 const directives = []; 26 27 for (let i = 0; i < statements.length; i++) { 28 const statement = statements[i]; 29 30 if ( 31 statement.type === "ExpressionStatement" && 32 statement.expression.type === "Literal" && 33 statement.expression.value === "use strict" 34 ) { 35 directives[i] = statement; 36 } else { 37 break; 38 } 39 } 40 41 return directives; 42} 43 44/** 45 * Checks whether a given parameter is a simple parameter. 46 * @param {ASTNode} node A pattern node to check. 47 * @returns {boolean} `true` if the node is an Identifier node. 48 */ 49function isSimpleParameter(node) { 50 return node.type === "Identifier"; 51} 52 53/** 54 * Checks whether a given parameter list is a simple parameter list. 55 * @param {ASTNode[]} params A parameter list to check. 56 * @returns {boolean} `true` if the every parameter is an Identifier node. 57 */ 58function isSimpleParameterList(params) { 59 return params.every(isSimpleParameter); 60} 61 62//------------------------------------------------------------------------------ 63// Rule Definition 64//------------------------------------------------------------------------------ 65 66module.exports = { 67 meta: { 68 type: "suggestion", 69 70 docs: { 71 description: "require or disallow strict mode directives", 72 category: "Strict Mode", 73 recommended: false, 74 url: "https://eslint.org/docs/rules/strict" 75 }, 76 77 schema: [ 78 { 79 enum: ["never", "global", "function", "safe"] 80 } 81 ], 82 83 fixable: "code", 84 messages: { 85 function: "Use the function form of 'use strict'.", 86 global: "Use the global form of 'use strict'.", 87 multiple: "Multiple 'use strict' directives.", 88 never: "Strict mode is not permitted.", 89 unnecessary: "Unnecessary 'use strict' directive.", 90 module: "'use strict' is unnecessary inside of modules.", 91 implied: "'use strict' is unnecessary when implied strict mode is enabled.", 92 unnecessaryInClasses: "'use strict' is unnecessary inside of classes.", 93 nonSimpleParameterList: "'use strict' directive inside a function with non-simple parameter list throws a syntax error since ES2016.", 94 wrap: "Wrap {{name}} in a function with 'use strict' directive." 95 } 96 }, 97 98 create(context) { 99 100 const ecmaFeatures = context.parserOptions.ecmaFeatures || {}, 101 scopes = [], 102 classScopes = []; 103 let mode = context.options[0] || "safe"; 104 105 if (ecmaFeatures.impliedStrict) { 106 mode = "implied"; 107 } else if (mode === "safe") { 108 mode = ecmaFeatures.globalReturn ? "global" : "function"; 109 } 110 111 /** 112 * Determines whether a reported error should be fixed, depending on the error type. 113 * @param {string} errorType The type of error 114 * @returns {boolean} `true` if the reported error should be fixed 115 */ 116 function shouldFix(errorType) { 117 return errorType === "multiple" || errorType === "unnecessary" || errorType === "module" || errorType === "implied" || errorType === "unnecessaryInClasses"; 118 } 119 120 /** 121 * Gets a fixer function to remove a given 'use strict' directive. 122 * @param {ASTNode} node The directive that should be removed 123 * @returns {Function} A fixer function 124 */ 125 function getFixFunction(node) { 126 return fixer => fixer.remove(node); 127 } 128 129 /** 130 * Report a slice of an array of nodes with a given message. 131 * @param {ASTNode[]} nodes Nodes. 132 * @param {string} start Index to start from. 133 * @param {string} end Index to end before. 134 * @param {string} messageId Message to display. 135 * @param {boolean} fix `true` if the directive should be fixed (i.e. removed) 136 * @returns {void} 137 */ 138 function reportSlice(nodes, start, end, messageId, fix) { 139 nodes.slice(start, end).forEach(node => { 140 context.report({ node, messageId, fix: fix ? getFixFunction(node) : null }); 141 }); 142 } 143 144 /** 145 * Report all nodes in an array with a given message. 146 * @param {ASTNode[]} nodes Nodes. 147 * @param {string} messageId Message id to display. 148 * @param {boolean} fix `true` if the directive should be fixed (i.e. removed) 149 * @returns {void} 150 */ 151 function reportAll(nodes, messageId, fix) { 152 reportSlice(nodes, 0, nodes.length, messageId, fix); 153 } 154 155 /** 156 * Report all nodes in an array, except the first, with a given message. 157 * @param {ASTNode[]} nodes Nodes. 158 * @param {string} messageId Message id to display. 159 * @param {boolean} fix `true` if the directive should be fixed (i.e. removed) 160 * @returns {void} 161 */ 162 function reportAllExceptFirst(nodes, messageId, fix) { 163 reportSlice(nodes, 1, nodes.length, messageId, fix); 164 } 165 166 /** 167 * Entering a function in 'function' mode pushes a new nested scope onto the 168 * stack. The new scope is true if the nested function is strict mode code. 169 * @param {ASTNode} node The function declaration or expression. 170 * @param {ASTNode[]} useStrictDirectives The Use Strict Directives of the node. 171 * @returns {void} 172 */ 173 function enterFunctionInFunctionMode(node, useStrictDirectives) { 174 const isInClass = classScopes.length > 0, 175 isParentGlobal = scopes.length === 0 && classScopes.length === 0, 176 isParentStrict = scopes.length > 0 && scopes[scopes.length - 1], 177 isStrict = useStrictDirectives.length > 0; 178 179 if (isStrict) { 180 if (!isSimpleParameterList(node.params)) { 181 context.report({ node: useStrictDirectives[0], messageId: "nonSimpleParameterList" }); 182 } else if (isParentStrict) { 183 context.report({ node: useStrictDirectives[0], messageId: "unnecessary", fix: getFixFunction(useStrictDirectives[0]) }); 184 } else if (isInClass) { 185 context.report({ node: useStrictDirectives[0], messageId: "unnecessaryInClasses", fix: getFixFunction(useStrictDirectives[0]) }); 186 } 187 188 reportAllExceptFirst(useStrictDirectives, "multiple", true); 189 } else if (isParentGlobal) { 190 if (isSimpleParameterList(node.params)) { 191 context.report({ node, messageId: "function" }); 192 } else { 193 context.report({ 194 node, 195 messageId: "wrap", 196 data: { name: astUtils.getFunctionNameWithKind(node) } 197 }); 198 } 199 } 200 201 scopes.push(isParentStrict || isStrict); 202 } 203 204 /** 205 * Exiting a function in 'function' mode pops its scope off the stack. 206 * @returns {void} 207 */ 208 function exitFunctionInFunctionMode() { 209 scopes.pop(); 210 } 211 212 /** 213 * Enter a function and either: 214 * - Push a new nested scope onto the stack (in 'function' mode). 215 * - Report all the Use Strict Directives (in the other modes). 216 * @param {ASTNode} node The function declaration or expression. 217 * @returns {void} 218 */ 219 function enterFunction(node) { 220 const isBlock = node.body.type === "BlockStatement", 221 useStrictDirectives = isBlock 222 ? getUseStrictDirectives(node.body.body) : []; 223 224 if (mode === "function") { 225 enterFunctionInFunctionMode(node, useStrictDirectives); 226 } else if (useStrictDirectives.length > 0) { 227 if (isSimpleParameterList(node.params)) { 228 reportAll(useStrictDirectives, mode, shouldFix(mode)); 229 } else { 230 context.report({ node: useStrictDirectives[0], messageId: "nonSimpleParameterList" }); 231 reportAllExceptFirst(useStrictDirectives, "multiple", true); 232 } 233 } 234 } 235 236 const rule = { 237 Program(node) { 238 const useStrictDirectives = getUseStrictDirectives(node.body); 239 240 if (node.sourceType === "module") { 241 mode = "module"; 242 } 243 244 if (mode === "global") { 245 if (node.body.length > 0 && useStrictDirectives.length === 0) { 246 context.report({ node, messageId: "global" }); 247 } 248 reportAllExceptFirst(useStrictDirectives, "multiple", true); 249 } else { 250 reportAll(useStrictDirectives, mode, shouldFix(mode)); 251 } 252 }, 253 FunctionDeclaration: enterFunction, 254 FunctionExpression: enterFunction, 255 ArrowFunctionExpression: enterFunction 256 }; 257 258 if (mode === "function") { 259 Object.assign(rule, { 260 261 // Inside of class bodies are always strict mode. 262 ClassBody() { 263 classScopes.push(true); 264 }, 265 "ClassBody:exit"() { 266 classScopes.pop(); 267 }, 268 269 "FunctionDeclaration:exit": exitFunctionInFunctionMode, 270 "FunctionExpression:exit": exitFunctionInFunctionMode, 271 "ArrowFunctionExpression:exit": exitFunctionInFunctionMode 272 }); 273 } 274 275 return rule; 276 } 277}; 278