Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New: no-restricted-properties rule (fixes #3218) #7017

Merged
merged 7 commits into from
Sep 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions conf/eslint.json
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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"
}]
}
]
});