no-restricted-properties.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. /**
  2. * @fileoverview Rule to disallow certain object properties
  3. * @author Will Klein & Eli White
  4. */
  5. "use strict";
  6. const astUtils = require("./utils/ast-utils");
  7. //------------------------------------------------------------------------------
  8. // Rule Definition
  9. //------------------------------------------------------------------------------
  10. /** @type {import('../types').Rule.RuleModule} */
  11. module.exports = {
  12. meta: {
  13. type: "suggestion",
  14. docs: {
  15. description: "Disallow certain properties on certain objects",
  16. recommended: false,
  17. url: "https://eslint.org/docs/latest/rules/no-restricted-properties",
  18. },
  19. schema: {
  20. type: "array",
  21. items: {
  22. type: "object",
  23. properties: {
  24. object: {
  25. type: "string",
  26. },
  27. property: {
  28. type: "string",
  29. },
  30. allowObjects: {
  31. type: "array",
  32. items: {
  33. type: "string",
  34. },
  35. uniqueItems: true,
  36. },
  37. allowProperties: {
  38. type: "array",
  39. items: {
  40. type: "string",
  41. },
  42. uniqueItems: true,
  43. },
  44. message: {
  45. type: "string",
  46. },
  47. },
  48. anyOf: [
  49. {
  50. required: ["object"],
  51. },
  52. {
  53. required: ["property"],
  54. },
  55. ],
  56. not: {
  57. anyOf: [
  58. { required: ["allowObjects", "object"] },
  59. { required: ["allowProperties", "property"] },
  60. ],
  61. },
  62. additionalProperties: false,
  63. },
  64. uniqueItems: true,
  65. },
  66. messages: {
  67. restrictedObjectProperty:
  68. // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
  69. "'{{objectName}}.{{propertyName}}' is restricted from being used.{{allowedPropertiesMessage}}{{message}}",
  70. restrictedProperty:
  71. // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
  72. "'{{propertyName}}' is restricted from being used.{{allowedObjectsMessage}}{{message}}",
  73. },
  74. },
  75. create(context) {
  76. const restrictedCalls = context.options;
  77. if (restrictedCalls.length === 0) {
  78. return {};
  79. }
  80. const restrictedProperties = new Map();
  81. const globallyRestrictedObjects = new Map();
  82. const globallyRestrictedProperties = new Map();
  83. restrictedCalls.forEach(option => {
  84. const objectName = option.object;
  85. const propertyName = option.property;
  86. if (typeof objectName === "undefined") {
  87. globallyRestrictedProperties.set(propertyName, {
  88. allowObjects: option.allowObjects,
  89. message: option.message,
  90. });
  91. } else if (typeof propertyName === "undefined") {
  92. globallyRestrictedObjects.set(objectName, {
  93. allowProperties: option.allowProperties,
  94. message: option.message,
  95. });
  96. } else {
  97. if (!restrictedProperties.has(objectName)) {
  98. restrictedProperties.set(objectName, new Map());
  99. }
  100. restrictedProperties.get(objectName).set(propertyName, {
  101. message: option.message,
  102. });
  103. }
  104. });
  105. /**
  106. * Checks if a name is in the allowed list.
  107. * @param {string} name The name to check
  108. * @param {string[]} [allowedList] The list of allowed names
  109. * @returns {boolean} True if the name is allowed, false otherwise
  110. */
  111. function isAllowed(name, allowedList) {
  112. if (!allowedList) {
  113. return false;
  114. }
  115. return allowedList.includes(name);
  116. }
  117. /**
  118. * Checks to see whether a property access is restricted, and reports it if so.
  119. * @param {ASTNode} node The node to report
  120. * @param {string} objectName The name of the object
  121. * @param {string} propertyName The name of the property
  122. * @returns {undefined}
  123. */
  124. function checkPropertyAccess(node, objectName, propertyName) {
  125. if (propertyName === null) {
  126. return;
  127. }
  128. const matchedObject = restrictedProperties.get(objectName);
  129. const matchedObjectProperty = matchedObject
  130. ? matchedObject.get(propertyName)
  131. : globallyRestrictedObjects.get(objectName);
  132. const globalMatchedProperty =
  133. globallyRestrictedProperties.get(propertyName);
  134. if (
  135. matchedObjectProperty &&
  136. !isAllowed(propertyName, matchedObjectProperty.allowProperties)
  137. ) {
  138. const message = matchedObjectProperty.message
  139. ? ` ${matchedObjectProperty.message}`
  140. : "";
  141. const allowedPropertiesMessage =
  142. matchedObjectProperty.allowProperties
  143. ? ` Only these properties are allowed: ${matchedObjectProperty.allowProperties.join(", ")}.`
  144. : "";
  145. context.report({
  146. node,
  147. messageId: "restrictedObjectProperty",
  148. data: {
  149. objectName,
  150. propertyName,
  151. message,
  152. allowedPropertiesMessage,
  153. },
  154. });
  155. } else if (
  156. globalMatchedProperty &&
  157. !isAllowed(objectName, globalMatchedProperty.allowObjects)
  158. ) {
  159. const message = globalMatchedProperty.message
  160. ? ` ${globalMatchedProperty.message}`
  161. : "";
  162. const allowedObjectsMessage = globalMatchedProperty.allowObjects
  163. ? ` Property '${propertyName}' is only allowed on these objects: ${globalMatchedProperty.allowObjects.join(", ")}.`
  164. : "";
  165. context.report({
  166. node,
  167. messageId: "restrictedProperty",
  168. data: {
  169. propertyName,
  170. message,
  171. allowedObjectsMessage,
  172. },
  173. });
  174. }
  175. }
  176. return {
  177. MemberExpression(node) {
  178. checkPropertyAccess(
  179. node,
  180. node.object && node.object.name,
  181. astUtils.getStaticPropertyName(node),
  182. );
  183. },
  184. ObjectPattern(node) {
  185. let objectName = null;
  186. if (node.parent.type === "VariableDeclarator") {
  187. if (
  188. node.parent.init &&
  189. node.parent.init.type === "Identifier"
  190. ) {
  191. objectName = node.parent.init.name;
  192. }
  193. } else if (
  194. node.parent.type === "AssignmentExpression" ||
  195. node.parent.type === "AssignmentPattern"
  196. ) {
  197. if (node.parent.right.type === "Identifier") {
  198. objectName = node.parent.right.name;
  199. }
  200. }
  201. node.properties.forEach(property => {
  202. checkPropertyAccess(
  203. node,
  204. objectName,
  205. astUtils.getStaticPropertyName(property),
  206. );
  207. });
  208. },
  209. };
  210. },
  211. };