| /** |
| * @fileoverview Rule to control usage of strict mode directives. |
| * @author Brandon Mills |
| */ |
| |
| "use strict"; |
| |
| //------------------------------------------------------------------------------ |
| // Requirements |
| //------------------------------------------------------------------------------ |
| |
| const astUtils = require("./utils/ast-utils"); |
| |
| //------------------------------------------------------------------------------ |
| // Helpers |
| //------------------------------------------------------------------------------ |
| |
| /** |
| * Gets all of the Use Strict Directives in the Directive Prologue of a group of |
| * statements. |
| * @param {ASTNode[]} statements Statements in the program or function body. |
| * @returns {ASTNode[]} All of the Use Strict Directives. |
| */ |
| function getUseStrictDirectives(statements) { |
| const directives = []; |
| |
| for (let i = 0; i < statements.length; i++) { |
| const statement = statements[i]; |
| |
| if ( |
| statement.type === "ExpressionStatement" && |
| statement.expression.type === "Literal" && |
| statement.expression.value === "use strict" |
| ) { |
| directives[i] = statement; |
| } else { |
| break; |
| } |
| } |
| |
| return directives; |
| } |
| |
| /** |
| * Checks whether a given parameter is a simple parameter. |
| * |
| * @param {ASTNode} node - A pattern node to check. |
| * @returns {boolean} `true` if the node is an Identifier node. |
| */ |
| function isSimpleParameter(node) { |
| return node.type === "Identifier"; |
| } |
| |
| /** |
| * Checks whether a given parameter list is a simple parameter list. |
| * |
| * @param {ASTNode[]} params - A parameter list to check. |
| * @returns {boolean} `true` if the every parameter is an Identifier node. |
| */ |
| function isSimpleParameterList(params) { |
| return params.every(isSimpleParameter); |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Rule Definition |
| //------------------------------------------------------------------------------ |
| |
| module.exports = { |
| meta: { |
| type: "suggestion", |
| |
| docs: { |
| description: "require or disallow strict mode directives", |
| category: "Strict Mode", |
| recommended: false, |
| url: "https://eslint.org/docs/rules/strict" |
| }, |
| |
| schema: [ |
| { |
| enum: ["never", "global", "function", "safe"] |
| } |
| ], |
| |
| fixable: "code", |
| messages: { |
| function: "Use the function form of 'use strict'.", |
| global: "Use the global form of 'use strict'.", |
| multiple: "Multiple 'use strict' directives.", |
| never: "Strict mode is not permitted.", |
| unnecessary: "Unnecessary 'use strict' directive.", |
| module: "'use strict' is unnecessary inside of modules.", |
| implied: "'use strict' is unnecessary when implied strict mode is enabled.", |
| unnecessaryInClasses: "'use strict' is unnecessary inside of classes.", |
| nonSimpleParameterList: "'use strict' directive inside a function with non-simple parameter list throws a syntax error since ES2016.", |
| wrap: "Wrap {{name}} in a function with 'use strict' directive." |
| } |
| }, |
| |
| create(context) { |
| |
| const ecmaFeatures = context.parserOptions.ecmaFeatures || {}, |
| scopes = [], |
| classScopes = []; |
| let mode = context.options[0] || "safe"; |
| |
| if (ecmaFeatures.impliedStrict) { |
| mode = "implied"; |
| } else if (mode === "safe") { |
| mode = ecmaFeatures.globalReturn ? "global" : "function"; |
| } |
| |
| /** |
| * Determines whether a reported error should be fixed, depending on the error type. |
| * @param {string} errorType The type of error |
| * @returns {boolean} `true` if the reported error should be fixed |
| */ |
| function shouldFix(errorType) { |
| return errorType === "multiple" || errorType === "unnecessary" || errorType === "module" || errorType === "implied" || errorType === "unnecessaryInClasses"; |
| } |
| |
| /** |
| * Gets a fixer function to remove a given 'use strict' directive. |
| * @param {ASTNode} node The directive that should be removed |
| * @returns {Function} A fixer function |
| */ |
| function getFixFunction(node) { |
| return fixer => fixer.remove(node); |
| } |
| |
| /** |
| * Report a slice of an array of nodes with a given message. |
| * @param {ASTNode[]} nodes Nodes. |
| * @param {string} start Index to start from. |
| * @param {string} end Index to end before. |
| * @param {string} messageId Message to display. |
| * @param {boolean} fix `true` if the directive should be fixed (i.e. removed) |
| * @returns {void} |
| */ |
| function reportSlice(nodes, start, end, messageId, fix) { |
| nodes.slice(start, end).forEach(node => { |
| context.report({ node, messageId, fix: fix ? getFixFunction(node) : null }); |
| }); |
| } |
| |
| /** |
| * Report all nodes in an array with a given message. |
| * @param {ASTNode[]} nodes Nodes. |
| * @param {string} messageId Message id to display. |
| * @param {boolean} fix `true` if the directive should be fixed (i.e. removed) |
| * @returns {void} |
| */ |
| function reportAll(nodes, messageId, fix) { |
| reportSlice(nodes, 0, nodes.length, messageId, fix); |
| } |
| |
| /** |
| * Report all nodes in an array, except the first, with a given message. |
| * @param {ASTNode[]} nodes Nodes. |
| * @param {string} messageId Message id to display. |
| * @param {boolean} fix `true` if the directive should be fixed (i.e. removed) |
| * @returns {void} |
| */ |
| function reportAllExceptFirst(nodes, messageId, fix) { |
| reportSlice(nodes, 1, nodes.length, messageId, fix); |
| } |
| |
| /** |
| * Entering a function in 'function' mode pushes a new nested scope onto the |
| * stack. The new scope is true if the nested function is strict mode code. |
| * @param {ASTNode} node The function declaration or expression. |
| * @param {ASTNode[]} useStrictDirectives The Use Strict Directives of the node. |
| * @returns {void} |
| */ |
| function enterFunctionInFunctionMode(node, useStrictDirectives) { |
| const isInClass = classScopes.length > 0, |
| isParentGlobal = scopes.length === 0 && classScopes.length === 0, |
| isParentStrict = scopes.length > 0 && scopes[scopes.length - 1], |
| isStrict = useStrictDirectives.length > 0; |
| |
| if (isStrict) { |
| if (!isSimpleParameterList(node.params)) { |
| context.report({ node: useStrictDirectives[0], messageId: "nonSimpleParameterList" }); |
| } else if (isParentStrict) { |
| context.report({ node: useStrictDirectives[0], messageId: "unnecessary", fix: getFixFunction(useStrictDirectives[0]) }); |
| } else if (isInClass) { |
| context.report({ node: useStrictDirectives[0], messageId: "unnecessaryInClasses", fix: getFixFunction(useStrictDirectives[0]) }); |
| } |
| |
| reportAllExceptFirst(useStrictDirectives, "multiple", true); |
| } else if (isParentGlobal) { |
| if (isSimpleParameterList(node.params)) { |
| context.report({ node, messageId: "function" }); |
| } else { |
| context.report({ |
| node, |
| messageId: "wrap", |
| data: { name: astUtils.getFunctionNameWithKind(node) } |
| }); |
| } |
| } |
| |
| scopes.push(isParentStrict || isStrict); |
| } |
| |
| /** |
| * Exiting a function in 'function' mode pops its scope off the stack. |
| * @returns {void} |
| */ |
| function exitFunctionInFunctionMode() { |
| scopes.pop(); |
| } |
| |
| /** |
| * Enter a function and either: |
| * - Push a new nested scope onto the stack (in 'function' mode). |
| * - Report all the Use Strict Directives (in the other modes). |
| * @param {ASTNode} node The function declaration or expression. |
| * @returns {void} |
| */ |
| function enterFunction(node) { |
| const isBlock = node.body.type === "BlockStatement", |
| useStrictDirectives = isBlock |
| ? getUseStrictDirectives(node.body.body) : []; |
| |
| if (mode === "function") { |
| enterFunctionInFunctionMode(node, useStrictDirectives); |
| } else if (useStrictDirectives.length > 0) { |
| if (isSimpleParameterList(node.params)) { |
| reportAll(useStrictDirectives, mode, shouldFix(mode)); |
| } else { |
| context.report({ node: useStrictDirectives[0], messageId: "nonSimpleParameterList" }); |
| reportAllExceptFirst(useStrictDirectives, "multiple", true); |
| } |
| } |
| } |
| |
| const rule = { |
| Program(node) { |
| const useStrictDirectives = getUseStrictDirectives(node.body); |
| |
| if (node.sourceType === "module") { |
| mode = "module"; |
| } |
| |
| if (mode === "global") { |
| if (node.body.length > 0 && useStrictDirectives.length === 0) { |
| context.report({ node, messageId: "global" }); |
| } |
| reportAllExceptFirst(useStrictDirectives, "multiple", true); |
| } else { |
| reportAll(useStrictDirectives, mode, shouldFix(mode)); |
| } |
| }, |
| FunctionDeclaration: enterFunction, |
| FunctionExpression: enterFunction, |
| ArrowFunctionExpression: enterFunction |
| }; |
| |
| if (mode === "function") { |
| Object.assign(rule, { |
| |
| // Inside of class bodies are always strict mode. |
| ClassBody() { |
| classScopes.push(true); |
| }, |
| "ClassBody:exit"() { |
| classScopes.pop(); |
| }, |
| |
| "FunctionDeclaration:exit": exitFunctionInFunctionMode, |
| "FunctionExpression:exit": exitFunctionInFunctionMode, |
| "ArrowFunctionExpression:exit": exitFunctionInFunctionMode |
| }); |
| } |
| |
| return rule; |
| } |
| }; |