valid-lazy.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /**
  2. * @fileoverview Ensures that definitions and uses of properties on the
  3. * ``lazy`` object are valid.
  4. *
  5. * This Source Code Form is subject to the terms of the Mozilla Public
  6. * License, v. 2.0. If a copy of the MPL was not distributed with this
  7. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  8. */
  9. "use strict";
  10. const helpers = require("../helpers");
  11. const items = [
  12. "loader",
  13. "XPCOMUtils",
  14. "Integration",
  15. "ChromeUtils",
  16. "DevToolsUtils",
  17. "Object",
  18. "Reflect",
  19. ];
  20. const callExpressionDefinitions = [
  21. /^loader\.lazyGetter\(lazy, "(\w+)"/,
  22. /^loader\.lazyServiceGetter\(lazy, "(\w+)"/,
  23. /^loader\.lazyRequireGetter\(lazy, "(\w+)"/,
  24. /^XPCOMUtils\.defineLazyGetter\(lazy, "(\w+)"/,
  25. /^Integration\.downloads\.defineESModuleGetter\(lazy, "(\w+)"/,
  26. /^XPCOMUtils\.defineLazyModuleGetter\(lazy, "(\w+)"/,
  27. /^ChromeUtils\.defineModuleGetter\(lazy, "(\w+)"/,
  28. /^XPCOMUtils\.defineLazyPreferenceGetter\(lazy, "(\w+)"/,
  29. /^XPCOMUtils\.defineLazyProxy\(lazy, "(\w+)"/,
  30. /^XPCOMUtils\.defineLazyScriptGetter\(lazy, "(\w+)"/,
  31. /^XPCOMUtils\.defineLazyServiceGetter\(lazy, "(\w+)"/,
  32. /^XPCOMUtils\.defineConstant\(lazy, "(\w+)"/,
  33. /^DevToolsUtils\.defineLazyModuleGetter\(lazy, "(\w+)"/,
  34. /^DevToolsUtils\.defineLazyGetter\(lazy, "(\w+)"/,
  35. /^Object\.defineProperty\(lazy, "(\w+)"/,
  36. /^Reflect\.defineProperty\(lazy, "(\w+)"/,
  37. ];
  38. const callExpressionMultiDefinitions = [
  39. "ChromeUtils.defineESModuleGetters(lazy,",
  40. "XPCOMUtils.defineLazyModuleGetters(lazy,",
  41. "XPCOMUtils.defineLazyServiceGetters(lazy,",
  42. "Object.defineProperties(lazy,",
  43. "loader.lazyRequireGetter(lazy,",
  44. ];
  45. module.exports = {
  46. meta: {
  47. docs: {
  48. url:
  49. "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/valid-lazy.html",
  50. },
  51. messages: {
  52. duplicateSymbol: "Duplicate symbol {{name}} being added to lazy.",
  53. incorrectType: "Unexpected literal for property name {{name}}",
  54. unknownProperty: "Unknown lazy member property {{name}}",
  55. unusedProperty: "Unused lazy property {{name}}",
  56. topLevelAndUnconditional:
  57. "Lazy property {{name}} is used at top-level unconditionally. It should be non-lazy.",
  58. },
  59. type: "problem",
  60. },
  61. create(context) {
  62. let lazyProperties = new Map();
  63. let unknownProperties = [];
  64. function addProp(node, name) {
  65. if (lazyProperties.has(name)) {
  66. context.report({
  67. node,
  68. messageId: "duplicateSymbol",
  69. data: { name },
  70. });
  71. return;
  72. }
  73. lazyProperties.set(name, { used: false, node });
  74. }
  75. function setPropertiesFromArgument(node, arg) {
  76. if (arg.type === "ObjectExpression") {
  77. for (let prop of arg.properties) {
  78. if (prop.key.type == "Literal") {
  79. context.report({
  80. node,
  81. messageId: "incorrectType",
  82. data: { name: prop.key.value },
  83. });
  84. continue;
  85. }
  86. addProp(node, prop.key.name);
  87. }
  88. } else if (arg.type === "ArrayExpression") {
  89. for (let prop of arg.elements) {
  90. if (prop.type != "Literal") {
  91. continue;
  92. }
  93. addProp(node, prop.value);
  94. }
  95. }
  96. }
  97. return {
  98. VariableDeclarator(node) {
  99. if (
  100. node.id.type === "Identifier" &&
  101. node.id.name == "lazy" &&
  102. node.init.type == "CallExpression" &&
  103. node.init.callee.name == "createLazyLoaders"
  104. ) {
  105. setPropertiesFromArgument(node, node.init.arguments[0]);
  106. }
  107. },
  108. CallExpression(node) {
  109. if (
  110. node.callee.type != "MemberExpression" ||
  111. (node.callee.object.type == "MemberExpression" &&
  112. !items.includes(node.callee.object.object.name)) ||
  113. (node.callee.object.type != "MemberExpression" &&
  114. !items.includes(node.callee.object.name))
  115. ) {
  116. return;
  117. }
  118. let source;
  119. try {
  120. source = helpers.getASTSource(node);
  121. } catch (e) {
  122. return;
  123. }
  124. for (let reg of callExpressionDefinitions) {
  125. let match = source.match(reg);
  126. if (match) {
  127. if (lazyProperties.has(match[1])) {
  128. context.report({
  129. node,
  130. messageId: "duplicateSymbol",
  131. data: { name: match[1] },
  132. });
  133. return;
  134. }
  135. lazyProperties.set(match[1], { used: false, node });
  136. break;
  137. }
  138. }
  139. if (
  140. callExpressionMultiDefinitions.some(expr =>
  141. source.startsWith(expr)
  142. ) &&
  143. node.arguments[1]
  144. ) {
  145. setPropertiesFromArgument(node, node.arguments[1]);
  146. }
  147. },
  148. MemberExpression(node) {
  149. if (node.computed || node.object.type !== "Identifier") {
  150. return;
  151. }
  152. let name;
  153. if (node.object.name == "lazy") {
  154. name = node.property.name;
  155. } else {
  156. return;
  157. }
  158. let property = lazyProperties.get(name);
  159. if (!property) {
  160. // These will be reported on Program:exit - some definitions may
  161. // be after first use, so we need to wait until we've processed
  162. // the whole file before reporting.
  163. unknownProperties.push({ name, node });
  164. } else {
  165. property.used = true;
  166. }
  167. if (
  168. helpers.getIsTopLevelAndUnconditionallyExecuted(
  169. context.getAncestors()
  170. )
  171. ) {
  172. context.report({
  173. node,
  174. messageId: "topLevelAndUnconditional",
  175. data: { name },
  176. });
  177. }
  178. },
  179. "Program:exit": function() {
  180. for (let { name, node } of unknownProperties) {
  181. let property = lazyProperties.get(name);
  182. if (!property) {
  183. context.report({
  184. node,
  185. messageId: "unknownProperty",
  186. data: { name },
  187. });
  188. } else {
  189. property.used = true;
  190. }
  191. }
  192. for (let [name, property] of lazyProperties.entries()) {
  193. if (!property.used) {
  194. context.report({
  195. node: property.node,
  196. messageId: "unusedProperty",
  197. data: { name },
  198. });
  199. }
  200. }
  201. },
  202. };
  203. },
  204. };