From f6273be372303672c07efe33cfb86c9aee42f167 Mon Sep 17 00:00:00 2001 From: Will Klein Date: Thu, 14 Apr 2016 16:17:06 -0600 Subject: [PATCH] New: no-restricted-properties rule (fixes #3218) --- conf/eslint.json | 1 + docs/rules/README.md | 1 + docs/rules/no-restricted-properties.md | 73 +++++++++++++++ lib/rules/no-restricted-properties.js | 72 +++++++++++++++ tests/lib/rules/no-restricted-properties.js | 98 +++++++++++++++++++++ 5 files changed, 245 insertions(+) create mode 100644 docs/rules/no-restricted-properties.md create mode 100644 lib/rules/no-restricted-properties.js create mode 100644 tests/lib/rules/no-restricted-properties.js diff --git a/conf/eslint.json b/conf/eslint.json index 21af92e0da98..9df920f8d94f 100755 --- a/conf/eslint.json +++ b/conf/eslint.json @@ -85,6 +85,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", diff --git a/docs/rules/README.md b/docs/rules/README.md index 3e49ebf471dc..b6ea052f99e2 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -190,6 +190,7 @@ These rules are purely matters of style and are quite subjective. * [no-nested-ternary](no-nested-ternary.md): disallow nested ternary expressions * [no-new-object](no-new-object.md): disallow `Object` constructors * [no-plusplus](no-plusplus.md): disallow the unary operators `++` and `--` +* [no-restricted-properties](no-restricted-properties.md) - disallow use of specified object properties * [no-restricted-syntax](no-restricted-syntax.md): disallow specified syntax * [no-spaced-func](no-spaced-func.md): disallow spacing between `function` identifiers and their applications (fixable) * [no-ternary](no-ternary.md): disallow ternary operators diff --git a/docs/rules/no-restricted-properties.md b/docs/rules/no-restricted-properties.md new file mode 100644 index 000000000000..2c3c2f4afabc --- /dev/null +++ b/docs/rules/no-restricted-properties.md @@ -0,0 +1,73 @@ +# 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. 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. + +## Rule Details + +This rule is aimed at disallowing certain object properties from your code. + +### 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." + }]] + } +} +``` + +The following patterns are considered problems: + +```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.*/ +``` + +The following patterns are not considered problems: + +```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) diff --git a/lib/rules/no-restricted-properties.js b/lib/rules/no-restricted-properties.js new file mode 100644 index 000000000000..6a8200d602cd --- /dev/null +++ b/lib/rules/no-restricted-properties.js @@ -0,0 +1,72 @@ +/** + * @fileoverview Rule to disallow certain object properties + * @author Will Klein + * @copyright 2016 Will Klein. All rights reserved. + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = function(context) { + if (context.options.length === 0) { + return {}; + } + + var restrictedProperties = context.options.reduce(function(restrictions, option) { + var objectName = option.object; + var propertyName = option.property; + + restrictions[objectName] = restrictions[objectName] || {}; + restrictions[objectName][propertyName] = { + message: option.message + }; + + return restrictions; + }, {}); + + return { + "MemberExpression": function(node) { + var objectName = node.object && node.object.name; + var propertyName = node.property && node.property.name; + var matchedObject = restrictedProperties[objectName]; + var matchedObjectProperty = matchedObject && matchedObject[propertyName]; + + if (matchedObjectProperty) { + var message = matchedObjectProperty.message ? " " + matchedObjectProperty.message : ""; + + context.report(node, "'{{objectName}}.{{propertyName}}' is restricted from being used.{{message}}", { + objectName: objectName, + propertyName: propertyName, + message: message + }); + } + } + }; +}; + +module.exports.schema = { + "type": "array", + "items": { + "type": "object", + "properties": { + "object": { + "type": "string" + }, + "property": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "object", + "property" + ] + }, + "uniqueItems": true +}; diff --git a/tests/lib/rules/no-restricted-properties.js b/tests/lib/rules/no-restricted-properties.js new file mode 100644 index 000000000000..7012b4dcb238 --- /dev/null +++ b/tests/lib/rules/no-restricted-properties.js @@ -0,0 +1,98 @@ +/** + * @fileoverview Tests for no-restricted-properties rule. + * @author Will Klein + * @copyright 2016 Will Klein. All rights reserved. + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +var rule = require("../../../lib/rules/no-restricted-properties"), + RuleTester = require("../../../lib/testers/rule-tester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +var 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." + }] + } + ], + + 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" + }] + } + ] +});