strict.js 10 KB

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