strict.js 10 KB

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