• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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