1/** 2 * @fileoverview Rule to disallow certain object properties 3 * @author Will Klein & Eli White 4 */ 5 6"use strict"; 7 8const astUtils = require("./utils/ast-utils"); 9 10//------------------------------------------------------------------------------ 11// Rule Definition 12//------------------------------------------------------------------------------ 13 14module.exports = { 15 meta: { 16 type: "suggestion", 17 18 docs: { 19 description: "disallow certain properties on certain objects", 20 category: "Best Practices", 21 recommended: false, 22 url: "https://eslint.org/docs/rules/no-restricted-properties" 23 }, 24 25 schema: { 26 type: "array", 27 items: { 28 anyOf: [ // `object` and `property` are both optional, but at least one of them must be provided. 29 { 30 type: "object", 31 properties: { 32 object: { 33 type: "string" 34 }, 35 property: { 36 type: "string" 37 }, 38 message: { 39 type: "string" 40 } 41 }, 42 additionalProperties: false, 43 required: ["object"] 44 }, 45 { 46 type: "object", 47 properties: { 48 object: { 49 type: "string" 50 }, 51 property: { 52 type: "string" 53 }, 54 message: { 55 type: "string" 56 } 57 }, 58 additionalProperties: false, 59 required: ["property"] 60 } 61 ] 62 }, 63 uniqueItems: true 64 }, 65 66 messages: { 67 // eslint-disable-next-line eslint-plugin/report-message-format 68 restrictedObjectProperty: "'{{objectName}}.{{propertyName}}' is restricted from being used.{{message}}", 69 // eslint-disable-next-line eslint-plugin/report-message-format 70 restrictedProperty: "'{{propertyName}}' is restricted from being used.{{message}}" 71 } 72 }, 73 74 create(context) { 75 const restrictedCalls = context.options; 76 77 if (restrictedCalls.length === 0) { 78 return {}; 79 } 80 81 const restrictedProperties = new Map(); 82 const globallyRestrictedObjects = new Map(); 83 const globallyRestrictedProperties = new Map(); 84 85 restrictedCalls.forEach(option => { 86 const objectName = option.object; 87 const propertyName = option.property; 88 89 if (typeof objectName === "undefined") { 90 globallyRestrictedProperties.set(propertyName, { message: option.message }); 91 } else if (typeof propertyName === "undefined") { 92 globallyRestrictedObjects.set(objectName, { message: option.message }); 93 } else { 94 if (!restrictedProperties.has(objectName)) { 95 restrictedProperties.set(objectName, new Map()); 96 } 97 98 restrictedProperties.get(objectName).set(propertyName, { 99 message: option.message 100 }); 101 } 102 }); 103 104 /** 105 * Checks to see whether a property access is restricted, and reports it if so. 106 * @param {ASTNode} node The node to report 107 * @param {string} objectName The name of the object 108 * @param {string} propertyName The name of the property 109 * @returns {undefined} 110 */ 111 function checkPropertyAccess(node, objectName, propertyName) { 112 if (propertyName === null) { 113 return; 114 } 115 const matchedObject = restrictedProperties.get(objectName); 116 const matchedObjectProperty = matchedObject ? matchedObject.get(propertyName) : globallyRestrictedObjects.get(objectName); 117 const globalMatchedProperty = globallyRestrictedProperties.get(propertyName); 118 119 if (matchedObjectProperty) { 120 const message = matchedObjectProperty.message ? ` ${matchedObjectProperty.message}` : ""; 121 122 context.report({ 123 node, 124 messageId: "restrictedObjectProperty", 125 data: { 126 objectName, 127 propertyName, 128 message 129 } 130 }); 131 } else if (globalMatchedProperty) { 132 const message = globalMatchedProperty.message ? ` ${globalMatchedProperty.message}` : ""; 133 134 context.report({ 135 node, 136 messageId: "restrictedProperty", 137 data: { 138 propertyName, 139 message 140 } 141 }); 142 } 143 } 144 145 /** 146 * Checks property accesses in a destructuring assignment expression, e.g. `var foo; ({foo} = bar);` 147 * @param {ASTNode} node An AssignmentExpression or AssignmentPattern node 148 * @returns {undefined} 149 */ 150 function checkDestructuringAssignment(node) { 151 if (node.right.type === "Identifier") { 152 const objectName = node.right.name; 153 154 if (node.left.type === "ObjectPattern") { 155 node.left.properties.forEach(property => { 156 checkPropertyAccess(node.left, objectName, astUtils.getStaticPropertyName(property)); 157 }); 158 } 159 } 160 } 161 162 return { 163 MemberExpression(node) { 164 checkPropertyAccess(node, node.object && node.object.name, astUtils.getStaticPropertyName(node)); 165 }, 166 VariableDeclarator(node) { 167 if (node.init && node.init.type === "Identifier") { 168 const objectName = node.init.name; 169 170 if (node.id.type === "ObjectPattern") { 171 node.id.properties.forEach(property => { 172 checkPropertyAccess(node.id, objectName, astUtils.getStaticPropertyName(property)); 173 }); 174 } 175 } 176 }, 177 AssignmentExpression: checkDestructuringAssignment, 178 AssignmentPattern: checkDestructuringAssignment 179 }; 180 } 181}; 182