strict.js 8.9 KB

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