Skip to content

Commit

Permalink
Update: Make no-restricted-properties more flexible (fixes #7137) (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
not-an-aardvark authored and mysticatea committed Sep 19, 2016
1 parent 0fdf23c commit 95c777a
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 25 deletions.
50 changes: 50 additions & 0 deletions docs/rules/no-restricted-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,32 @@ Multiple object/property values can be disallowed, and you can specify an option
}
```

If the object name is omitted, the property is disallowed for all objects:

```json
{
"rules": {
"no-restricted-properties": [2, {
"property": "__defineGetter__",
"message": "Please use Object.defineProperty instead."
}]
}
}
```

If the property name is omitted, accessing any property of the given object is disallowed:

```json
{
"rules": {
"no-restricted-properties": [2, {
"object": "require",
"message": "Please call require() directly."
}]
}
}
```

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

```js
Expand All @@ -52,6 +78,22 @@ var example = disallowedObjectName.disallowedPropertyName; /*error Disallowed ob
disallowedObjectName.disallowedPropertyName(); /*error Disallowed object property: disallowedObjectName.disallowedPropertyName.*/
```

```js
/* eslint no-restricted-properties: [2, {
"property": "__defineGetter__"
}] */

foo.__defineGetter__(bar, baz);
```

```js
/* eslint no-restricted-properties: [2, {
"object": "require"
}] */

require.resolve('foo');
```

Examples of **correct** code for this rule:

```js
Expand All @@ -65,6 +107,14 @@ var example = disallowedObjectName.somePropertyName;
allowedObjectName.disallowedPropertyName();
```

```js
/* eslint no-restricted-properties: [2, {
"object": "require"
}] */

require('foo');
```

## When Not To Use It

If you don't have any object/property combinations to restrict, you should not use this rule.
Expand Down
83 changes: 58 additions & 25 deletions lib/rules/no-restricted-properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,39 @@ module.exports = {
schema: {
type: "array",
items: {
type: "object",
properties: {
object: {
type: "string"
anyOf: [ // `object` and `property` are both optional, but at least one of them must be provided.
{
type: "object",
properties: {
object: {
type: "string"
},
property: {
type: "string"
},
message: {
type: "string"
}
},
additionalProperties: false,
required: ["object"]
},
property: {
type: "string"
},
message: {
type: "string"
{
type: "object",
properties: {
object: {
type: "string"
},
property: {
type: "string"
},
message: {
type: "string"
}
},
additionalProperties: false,
required: ["property"]
}
},
additionalProperties: false,
required: [
"object",
"property"
]
},
uniqueItems: true
Expand All @@ -51,27 +68,36 @@ module.exports = {
return {};
}

const restrictedProperties = restrictedCalls.reduce(function(restrictions, option) {
const restrictedProperties = new Map();
const globallyRestrictedObjects = new Map();
const globallyRestrictedProperties = new Map();

restrictedCalls.forEach(option => {
const objectName = option.object;
const propertyName = option.property;

if (!restrictions.has(objectName)) {
restrictions.set(objectName, new Map());
}

restrictions.get(objectName).set(propertyName, {
message: option.message
});
if (typeof objectName === "undefined") {
globallyRestrictedProperties.set(propertyName, {message: option.message});
} else if (typeof propertyName === "undefined") {
globallyRestrictedObjects.set(objectName, {message: option.message});
} else {
if (!restrictedProperties.has(objectName)) {
restrictedProperties.set(objectName, new Map());
}

return restrictions;
}, new Map());
restrictedProperties.get(objectName).set(propertyName, {
message: option.message
});
}
});

return {
MemberExpression(node) {
const objectName = node.object && node.object.name;
const propertyName = astUtils.getStaticPropertyName(node);
const matchedObject = restrictedProperties.get(objectName);
const matchedObjectProperty = matchedObject && matchedObject.get(propertyName);
const matchedObjectProperty = matchedObject ? matchedObject.get(propertyName) : globallyRestrictedObjects.get(objectName);
const globalMatchedProperty = globallyRestrictedProperties.get(propertyName);

if (matchedObjectProperty) {
const message = matchedObjectProperty.message ? " " + matchedObjectProperty.message : "";
Expand All @@ -81,6 +107,13 @@ module.exports = {
propertyName,
message
});
} else if (globalMatchedProperty) {
const message = globalMatchedProperty.message ? " " + globalMatchedProperty.message : "";

context.report(node, "'{{propertyName}}' is restricted from being used.{{message}}", {
propertyName,
message
});
}
}
};
Expand Down
83 changes: 83 additions & 0 deletions tests/lib/rules/no-restricted-properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,26 @@ ruleTester.run("no-restricted-properties", rule, {
object: "obj",
property: "foo"
}]
}, {
code: "foo.bar",
options: [{
property: "baz"
}]
}, {
code: "foo.bar",
options: [{
object: "baz"
}]
}, {
code: "foo()",
options: [{
object: "foo"
}]
}, {
code: "foo;",
options: [{
object: "foo"
}]
}
],

Expand Down Expand Up @@ -116,6 +136,69 @@ ruleTester.run("no-restricted-properties", rule, {
message: "'anotherObject.anotherDisallowedProperty' is restricted from being used.",
type: "MemberExpression"
}]
}, {
code: "foo.__proto__",
options: [{
property: "__proto__",
message: "Please use Object.getPrototypeOf instead."
}],
errors: [{
message: "'__proto__' is restricted from being used. Please use Object.getPrototypeOf instead.",
type: "MemberExpression"
}]
}, {
code: "foo['__proto__']",
options: [{
property: "__proto__",
message: "Please use Object.getPrototypeOf instead."
}],
errors: [{
message: "'__proto__' is restricted from being used. Please use Object.getPrototypeOf instead.",
type: "MemberExpression"
}]
}, {
code: "foo.bar.baz;",
options: [{ object: "foo" }],
errors: [{ message: "'foo.bar' is restricted from being used.", type: "MemberExpression" }]
}, {
code: "foo.bar();",
options: [{ object: "foo" }],
errors: [{ message: "'foo.bar' is restricted from being used.", type: "MemberExpression" }]
}, {
code: "foo.bar.baz();",
options: [{ object: "foo" }],
errors: [{ message: "'foo.bar' is restricted from being used.", type: "MemberExpression" }]
}, {
code: "foo.bar.baz;",
options: [{ property: "bar" }],
errors: [{ message: "'bar' is restricted from being used.", type: "MemberExpression" }]
}, {
code: "foo.bar();",
options: [{ property: "bar" }],
errors: [{ message: "'bar' is restricted from being used.", type: "MemberExpression" }]
}, {
code: "foo.bar.baz();",
options: [{ property: "bar" }],
errors: [{ message: "'bar' is restricted from being used.", type: "MemberExpression" }]
}, {
code: "require.call({}, 'foo')",
options: [{
object: "require",
message: "Please call require() directly."
}],
errors: [{
message: "'require.call' is restricted from being used. Please call require() directly.",
type: "MemberExpression"
}]
}, {
code: "require['resolve']",
options: [{
object: "require"
}],
errors: [{
message: "'require.resolve' is restricted from being used.",
type: "MemberExpression"
}]
}
]
});

0 comments on commit 95c777a

Please sign in to comment.