Skip to content

Commit

Permalink
New: Disallow use of Object.prototype methods on objects (fixes eslin…
Browse files Browse the repository at this point in the history
  • Loading branch information
DrewML authored and nzakas committed May 18, 2016
1 parent 53754ec commit 04bd586
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 0 deletions.
1 change: 1 addition & 0 deletions conf/eslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"no-process-env": "off",
"no-process-exit": "off",
"no-proto": "off",
"no-prototype-builtins": "off",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-restricted-globals": "off",
Expand Down
1 change: 1 addition & 0 deletions docs/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ These rules relate to possible syntax or logic errors in JavaScript code:
* [no-irregular-whitespace](no-irregular-whitespace.md): disallow irregular whitespace outside of strings and comments (recommended)
* [no-negated-in-lhs](no-negated-in-lhs.md): disallow negating the left operand in `in` expressions (recommended)
* [no-obj-calls](no-obj-calls.md): disallow calling global object properties as functions (recommended)
* [no-prototype-builtins](no-prototype-builtins.md): Disallow use of `Object.prototypes` builtins directly
* [no-regex-spaces](no-regex-spaces.md): disallow multiple spaces in regular expression literals (recommended)
* [no-sparse-arrays](no-sparse-arrays.md): disallow sparse arrays (recommended)
* [no-unexpected-multiline](no-unexpected-multiline.md): disallow confusing multiline expressions (recommended)
Expand Down
35 changes: 35 additions & 0 deletions docs/rules/no-prototype-builtins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Disallow use of Object.prototypes builtins directly (no-prototype-builtins)

In ECMAScript 5.1, `Object.create` was added, which enables the creation of objects with a specified `[[Prototype]]`. `Object.create(null)` is a common pattern used to create objects that will be used as a Map. This can lead to errors when it is assumed that objects will have properties from `Object.prototype`. This rule prevents calling `Object.prototype` methods directly from an object.

## Rule Details

This rule disallows calling some `Object.prototype` methods directly on object instances.

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

```js
/*eslint no-prototype-built-ins: "error"*/

var hasBarProperty = foo.hasOwnProperty("bar");

var isPrototypeOfBar = foo.isPrototypeOf(bar);

var barIsEnumerable = foo.propertyIsEnumerable("bar");
```

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

```js
/*eslint no-prototype-built-ins: "error"*/

var hasBarProperty = {}.hasOwnProperty.call(foo, "bar");

var isPrototypeOfBar = {}.isPrototypeOf.call(foo, bar);

var barIsEnumerable = {}.propertyIsEnumerable.call(foo, "bar");
```

## When Not To Use It

You may want to turn this rule off if you will never use an object that shadows an `Object.prototype` method or which does not inherit from `Object.prototype`.
52 changes: 52 additions & 0 deletions lib/rules/no-prototype-builtins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* @fileoverview Rule to disallow use of Object.prototype builtins on objects
* @author Andrew Levine
*/
"use strict";

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

module.exports = {
meta: {
docs: {
description: "disallow calling some Object.prototype methods directly on objects",
category: "Possible Errors",
recommended: false
}
},

create: function(context) {
var DISALLOWED_PROPS = [
"hasOwnProperty",
"isPrototypeOf",
"propertyIsEnumerable"
];

/**
* Reports if a disallowed property is used in a CallExpression
* @param {ASTNode} node The CallExpression node.
* @returns {void}
*/
function disallowBuiltIns(node) {
if (node.callee.type !== "MemberExpression" || node.callee.computed) {
return;
}
var propName = node.callee.property.name;

if (DISALLOWED_PROPS.indexOf(propName) > -1) {
context.report({
message: "Do not access Object.prototype method '{{prop}}' from target object.",
loc: node.callee.property.loc.start,
data: {prop: propName},
node: node
});
}
}

return {
CallExpression: disallowBuiltIns
};
}
};
88 changes: 88 additions & 0 deletions tests/lib/rules/no-prototype-builtins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* @fileoverview Tests for no-prototype-built-ins
* @author Andrew Levine
*/

"use strict";

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

var rule = require("../../../lib/rules/no-prototype-builtins"),
RuleTester = require("../../../lib/testers/rule-tester");

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
var ruleTester = new RuleTester();

var valid = [
{ code: "Object.prototype.hasOwnProperty.call(foo, 'bar')" },
{ code: "Object.prototype.isPrototypeOf.call(foo, 'bar')" },
{ code: "Object.prototype.propertyIsEnumerable.call(foo, 'bar')" },
{ code: "Object.prototype.hasOwnProperty.apply(foo, ['bar'])" },
{ code: "Object.prototype.isPrototypeOf.apply(foo, ['bar'])" },
{ code: "Object.prototype.propertyIsEnumerable.apply(foo, ['bar'])" },
{ code: "hasOwnProperty(foo, 'bar')" },
{ code: "isPrototypeOf(foo, 'bar')" },
{ code: "propertyIsEnumerable(foo, 'bar')" },
{ code: "({}.hasOwnProperty.call(foo, 'bar'))" },
{ code: "({}.isPrototypeOf.call(foo, 'bar'))" },
{ code: "({}.propertyIsEnumerable.call(foo, 'bar'))" },
{ code: "({}.hasOwnProperty.apply(foo, ['bar']))" },
{ code: "({}.isPrototypeOf.apply(foo, ['bar']))" },
{ code: "({}.propertyIsEnumerable.apply(foo, ['bar']))" }
];

var invalid = [
{
code: "foo.hasOwnProperty('bar')",
errors: [{
line: 1,
column: 5,
message: "Do not access Object.prototype method 'hasOwnProperty' from target object.",
type: "CallExpression"
}]
},
{
code: "foo.isPrototypeOf('bar')",
errors: [{
line: 1,
column: 5,
message: "Do not access Object.prototype method 'isPrototypeOf' from target object.",
type: "CallExpression"
}]
},
{
code: "foo.propertyIsEnumerable('bar')",
errors: [{
line: 1,
column: 5,
message: "Do not access Object.prototype method 'propertyIsEnumerable' from target object."
}]
},
{
code: "foo.bar.hasOwnProperty('bar')",
errors: [{
line: 1,
column: 9,
message: "Do not access Object.prototype method 'hasOwnProperty' from target object.",
type: "CallExpression"
}]
},
{
code: "foo.bar.baz.isPrototypeOf('bar')",
errors: [{
line: 1,
column: 13,
message: "Do not access Object.prototype method 'isPrototypeOf' from target object.",
type: "CallExpression"
}]
}
];

ruleTester.run("no-prototype-builtins", rule, {
valid: valid,
invalid: invalid
});

0 comments on commit 04bd586

Please sign in to comment.