Skip to content

Commit

Permalink
New: no-restricted-properties rule (fixes #3218) (#7017)
Browse files Browse the repository at this point in the history
* New: no-restricted-properties rule (fixes #3218)

* Updating to use the new format

* Updating the docs

* Fixing lint errors in the markdown

* Force Build

* Using Map instead of an object

* Using the property name getter from astUtils
  • Loading branch information
TheSavior authored and nzakas committed Sep 2, 2016
1 parent 00b3042 commit 46cb690
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 0 deletions.
1 change: 1 addition & 0 deletions conf/eslint.json
Expand Up @@ -88,6 +88,7 @@
"no-restricted-globals": "off",
"no-restricted-imports": "off",
"no-restricted-modules": "off",
"no-restricted-properties": "off",
"no-restricted-syntax": "off",
"no-return-assign": "off",
"no-script-url": "off",
Expand Down
74 changes: 74 additions & 0 deletions docs/rules/no-restricted-properties.md
@@ -0,0 +1,74 @@
# disallow certain object properties (no-restricted-properties)

Certain properties on objects may be disallowed in a codebase. This is useful for deprecating an API or restricting usage of a module's methods. For example, you may want to disallow using `describe.only` when using Mocha or telling people to use `Object.assign` instead of `_.extend`.


## Rule Details

This rule looks for accessing a given property key on a given object name, either when reading the property's value or invoking it as a function. You may specify an optional message to indicate an alternative API or a reason for the restriction.

### Options

This rule takes a list of objects, where the object name and property names are specified:

```json
{
"rules": {
"no-restricted-properties": [2, [{
"object": "disallowedObjectName",
"property": "disallowedPropertyName"
}]]
}
}
```

Multiple object/property values can be disallowed, and you can specify an optional message:

```json
{
"rules": {
"no-restricted-properties": [2, [{
"object": "disallowedObjectName",
"property": "disallowedPropertyName"
}, {
"object": "disallowedObjectName",
"property": "anotherDisallowedPropertyName",
"message": "Please use allowedObjectName.allowedPropertyName."
}]]
}
}
```

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

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

var example = disallowedObjectName.disallowedPropertyName; /*error Disallowed object property: disallowedObjectName.disallowedPropertyName.*/

disallowedObjectName.disallowedPropertyName(); /*error Disallowed object property: disallowedObjectName.disallowedPropertyName.*/
```

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

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

var example = disallowedObjectName.somePropertyName;

allowedObjectName.disallowedPropertyName();
```

## When Not To Use It

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

## Related Rules

* [no-restricted-syntax](no-restricted-syntax.md)
88 changes: 88 additions & 0 deletions lib/rules/no-restricted-properties.js
@@ -0,0 +1,88 @@
/**
* @fileoverview Rule to disallow certain object properties
* @author Will Klein & Eli White
*/

"use strict";

const astUtils = require("../ast-utils");

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: "disallow certain properties on certain objects",
category: "Node.js and CommonJS",
recommended: false
},

schema: {
type: "array",
items: {
type: "object",
properties: {
object: {
type: "string"
},
property: {
type: "string"
},
message: {
type: "string"
}
},
additionalProperties: false,
required: [
"object",
"property"
]
},
uniqueItems: true
}
},

create(context) {
const restrictedCalls = context.options;

if (restrictedCalls.length === 0) {
return {};
}

const restrictedProperties = restrictedCalls.reduce(function(restrictions, 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
});

return restrictions;
}, new Map());

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);

if (matchedObjectProperty) {
const message = matchedObjectProperty.message ? " " + matchedObjectProperty.message : "";

context.report(node, "'{{objectName}}.{{propertyName}}' is restricted from being used.{{message}}", {
objectName,
propertyName,
message
});
}
}
};
}
};
121 changes: 121 additions & 0 deletions tests/lib/rules/no-restricted-properties.js
@@ -0,0 +1,121 @@
/**
* @fileoverview Tests for no-restricted-properties rule.
* @author Will Klein & Eli White
*/

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const rule = require("../../../lib/rules/no-restricted-properties"),
RuleTester = require("../../../lib/testers/rule-tester");

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester();

ruleTester.run("no-restricted-properties", rule, {
valid: [
{
code: "someObject.someProperty",
options: [{
object: "someObject",
property: "disallowedProperty"
}]
}, {
code: "anotherObject.disallowedProperty",
options: [{
object: "someObject",
property: "disallowedProperty"
}]
}, {
code: "someObject.someProperty()",
options: [{
object: "someObject",
property: "disallowedProperty"
}]
}, {
code: "anotherObject.disallowedProperty()",
options: [{
object: "someObject",
property: "disallowedProperty"
}]
}, {
code: "anotherObject.disallowedProperty()",
options: [{
object: "someObject",
property: "disallowedProperty",
message: "Please use someObject.allowedProperty instead."
}]
}, {
code: "anotherObject['disallowedProperty']()",
options: [{
object: "someObject",
property: "disallowedProperty"
}]
}, {
code: "obj.toString",
options: [{
object: "obj",
property: "__proto__"
}]
}, {
code: "toString.toString",
options: [{
object: "obj",
property: "foo"
}]
}, {
code: "obj.toString",
options: [{
object: "obj",
property: "foo"
}]
}
],

invalid: [
{
code: "someObject.disallowedProperty",
options: [{
object: "someObject",
property: "disallowedProperty"
}],
errors: [{
message: "'someObject.disallowedProperty' is restricted from being used.",
type: "MemberExpression"
}]
}, {
code: "someObject.disallowedProperty",
options: [{
object: "someObject",
property: "disallowedProperty",
message: "Please use someObject.allowedProperty instead."
}],
errors: [{
message: "'someObject.disallowedProperty' is restricted from being used. Please use someObject.allowedProperty instead.",
type: "MemberExpression"
}]
}, {
code: "someObject.disallowedProperty; anotherObject.anotherDisallowedProperty()",
options: [{
object: "someObject",
property: "disallowedProperty"
}, {
object: "anotherObject",
property: "anotherDisallowedProperty"
}],
errors: [{
message: "'someObject.disallowedProperty' is restricted from being used.",
type: "MemberExpression"
}, {
message: "'anotherObject.anotherDisallowedProperty' is restricted from being used.",
type: "MemberExpression"
}]
}
]
});

0 comments on commit 46cb690

Please sign in to comment.