Skip to content

Commit 786bcd1

Browse files
authored
feat: add allowProperties option to no-restricted-properties (#19772)
1 parent 05b66d0 commit 786bcd1

File tree

4 files changed

+206
-11
lines changed

4 files changed

+206
-11
lines changed

docs/src/rules/no-restricted-properties.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,21 @@ If you want to restrict a property globally but allow specific objects to use it
8585
}
8686
```
8787

88-
Note that the `allowObjects` option cannot be used together with the `object` option since they are mutually exclusive.
88+
If you want to restrict all properties on an object except for specific ones, you can use the `allowProperties` option:
89+
90+
```json
91+
{
92+
"rules": {
93+
"no-restricted-properties": [2, {
94+
"object": "config",
95+
"allowProperties": ["settings", "version"],
96+
"message": "Accessing other properties is restricted."
97+
}]
98+
}
99+
}
100+
```
101+
102+
Note that the `allowObjects` option cannot be used together with the `object` option since they are mutually exclusive. Similarly, the `allowProperties` option cannot be used together with the `property` option since they are also mutually exclusive.
89103

90104
Examples of **incorrect** code for this rule:
91105

@@ -145,6 +159,20 @@ myArray.push(5);
145159

146160
:::
147161

162+
::: incorrect
163+
164+
```js
165+
/* eslint no-restricted-properties: [2, {
166+
"object": "config",
167+
"allowProperties": ["settings", "version"]
168+
}] */
169+
170+
config.apiKey = "12345";
171+
config.timeout = 5000;
172+
```
173+
174+
:::
175+
148176
Examples of **correct** code for this rule:
149177

150178
::: correct
@@ -188,6 +216,20 @@ history.push('/about');
188216

189217
:::
190218

219+
::: correct
220+
221+
```js
222+
/* eslint no-restricted-properties: [2, {
223+
"object": "config",
224+
"allowProperties": ["settings", "version"]
225+
}] */
226+
227+
config.settings = { theme: "dark" };
228+
config.version = "1.0.0";
229+
```
230+
231+
:::
232+
191233
## When Not To Use It
192234

193235
If you don't have any object/property combinations to restrict, you should not use this rule.

lib/rules/no-restricted-properties.js

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ module.exports = {
4040
},
4141
uniqueItems: true,
4242
},
43+
allowProperties: {
44+
type: "array",
45+
items: {
46+
type: "string",
47+
},
48+
uniqueItems: true,
49+
},
4350
message: {
4451
type: "string",
4552
},
@@ -53,7 +60,10 @@ module.exports = {
5360
},
5461
],
5562
not: {
56-
required: ["allowObjects", "object"],
63+
anyOf: [
64+
{ required: ["allowObjects", "object"] },
65+
{ required: ["allowProperties", "property"] },
66+
],
5767
},
5868
additionalProperties: false,
5969
},
@@ -92,6 +102,7 @@ module.exports = {
92102
});
93103
} else if (typeof propertyName === "undefined") {
94104
globallyRestrictedObjects.set(objectName, {
105+
allowProperties: option.allowProperties,
95106
message: option.message,
96107
});
97108
} else {
@@ -106,17 +117,17 @@ module.exports = {
106117
});
107118

108119
/**
109-
* Checks if an object name is in the allowed objects list.
110-
* @param {string} objectName The name of the object to check
111-
* @param {string[]} [allowObjects] The list of objects to allow
112-
* @returns {boolean} True if the object is allowed, false otherwise
120+
* Checks if a name is in the allowed list.
121+
* @param {string} name The name to check
122+
* @param {string[]} [allowedList] The list of allowed names
123+
* @returns {boolean} True if the name is allowed, false otherwise
113124
*/
114-
function isAllowedObject(objectName, allowObjects) {
115-
if (!allowObjects) {
125+
function isAllowed(name, allowedList) {
126+
if (!allowedList) {
116127
return false;
117128
}
118129

119-
return allowObjects.includes(objectName);
130+
return allowedList.includes(name);
120131
}
121132

122133
/**
@@ -137,7 +148,10 @@ module.exports = {
137148
const globalMatchedProperty =
138149
globallyRestrictedProperties.get(propertyName);
139150

140-
if (matchedObjectProperty) {
151+
if (
152+
matchedObjectProperty &&
153+
!isAllowed(propertyName, matchedObjectProperty.allowProperties)
154+
) {
141155
const message = matchedObjectProperty.message
142156
? ` ${matchedObjectProperty.message}`
143157
: "";
@@ -153,7 +167,7 @@ module.exports = {
153167
});
154168
} else if (
155169
globalMatchedProperty &&
156-
!isAllowedObject(objectName, globalMatchedProperty.allowObjects)
170+
!isAllowed(objectName, globalMatchedProperty.allowObjects)
157171
) {
158172
const message = globalMatchedProperty.message
159173
? ` ${globalMatchedProperty.message}`

lib/types/rules.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3470,6 +3470,11 @@ export interface ESLintRules extends Linter.RulesRecord {
34703470
allowObjects?: string[];
34713471
message?: string | undefined;
34723472
}
3473+
| {
3474+
object: string;
3475+
allowProperties?: string[];
3476+
message?: string | undefined;
3477+
}
34733478
>,
34743479
]
34753480
>;

tests/lib/rules/no-restricted-properties.js

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,65 @@ ruleTester.run("no-restricted-properties", rule, {
279279
options: [{ property: "baz", allowObjects: ["foo"] }],
280280
languageOptions: { ecmaVersion: 6 },
281281
},
282+
{
283+
code: "someObject.disallowedProperty",
284+
options: [
285+
{
286+
object: "someObject",
287+
allowProperties: ["disallowedProperty"],
288+
},
289+
],
290+
},
291+
{
292+
code: "someObject.disallowedProperty; someObject.anotherDisallowedProperty();",
293+
options: [
294+
{
295+
object: "someObject",
296+
allowProperties: [
297+
"disallowedProperty",
298+
"anotherDisallowedProperty",
299+
],
300+
},
301+
],
302+
},
303+
{
304+
code: "someObject.disallowedProperty()",
305+
options: [
306+
{
307+
object: "someObject",
308+
allowProperties: ["disallowedProperty"],
309+
},
310+
],
311+
},
312+
{
313+
code: "someObject['disallowedProperty']()",
314+
options: [
315+
{
316+
object: "someObject",
317+
allowProperties: ["disallowedProperty"],
318+
},
319+
],
320+
},
321+
{
322+
code: "let {bar} = foo;",
323+
options: [
324+
{
325+
object: "foo",
326+
allowProperties: ["bar"],
327+
},
328+
],
329+
languageOptions: { ecmaVersion: 6 },
330+
},
331+
{
332+
code: "let {baz: bar} = foo;",
333+
options: [
334+
{
335+
object: "foo",
336+
allowProperties: ["baz"],
337+
},
338+
],
339+
languageOptions: { ecmaVersion: 6 },
340+
},
282341
],
283342

284343
invalid: [
@@ -988,5 +1047,80 @@ ruleTester.run("no-restricted-properties", rule, {
9881047
},
9891048
],
9901049
},
1050+
{
1051+
code: "someObject.disallowedProperty",
1052+
options: [
1053+
{
1054+
object: "someObject",
1055+
allowProperties: ["allowedProperty"],
1056+
},
1057+
],
1058+
errors: [
1059+
{
1060+
messageId: "restrictedObjectProperty",
1061+
data: {
1062+
objectName: "someObject",
1063+
propertyName: "disallowedProperty",
1064+
message: "",
1065+
},
1066+
type: "MemberExpression",
1067+
},
1068+
],
1069+
},
1070+
{
1071+
code: "someObject.disallowedProperty",
1072+
options: [
1073+
{
1074+
object: "someObject",
1075+
allowProperties: ["allowedProperty"],
1076+
message: "Please use someObject.allowedProperty instead.",
1077+
},
1078+
],
1079+
errors: [
1080+
{
1081+
messageId: "restrictedObjectProperty",
1082+
data: {
1083+
objectName: "someObject",
1084+
propertyName: "disallowedProperty",
1085+
message:
1086+
" Please use someObject.allowedProperty instead.",
1087+
},
1088+
type: "MemberExpression",
1089+
},
1090+
],
1091+
},
1092+
{
1093+
code: "someObject.disallowedProperty; anotherObject.anotherDisallowedProperty()",
1094+
options: [
1095+
{
1096+
object: "someObject",
1097+
allowProperties: ["anotherDisallowedProperty"],
1098+
},
1099+
{
1100+
object: "anotherObject",
1101+
allowProperties: ["disallowedProperty"],
1102+
},
1103+
],
1104+
errors: [
1105+
{
1106+
messageId: "restrictedObjectProperty",
1107+
data: {
1108+
objectName: "someObject",
1109+
propertyName: "disallowedProperty",
1110+
message: "",
1111+
},
1112+
type: "MemberExpression",
1113+
},
1114+
{
1115+
messageId: "restrictedObjectProperty",
1116+
data: {
1117+
objectName: "anotherObject",
1118+
propertyName: "anotherDisallowedProperty",
1119+
message: "",
1120+
},
1121+
type: "MemberExpression",
1122+
},
1123+
],
1124+
},
9911125
],
9921126
});

0 commit comments

Comments
 (0)