2 * @fileoverview Rule to flag adding properties to native object's prototypes.
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 const astUtils
= require("./utils/ast-utils");
13 const globals
= require("globals");
15 //------------------------------------------------------------------------------
17 //------------------------------------------------------------------------------
19 const propertyDefinitionMethods
= new Set(["defineProperty", "defineProperties"]);
21 //------------------------------------------------------------------------------
23 //------------------------------------------------------------------------------
30 description
: "disallow extending native types",
31 category
: "Best Practices",
33 url
: "https://eslint.org/docs/rules/no-extend-native"
48 additionalProperties
: false
53 unexpected
: "{{builtin}} prototype is read only, properties should not be added."
59 const config
= context
.options
[0] || {};
60 const exceptions
= new Set(config
.exceptions
|| []);
61 const modifiedBuiltins
= new Set(
62 Object
.keys(globals
.builtin
)
63 .filter(builtin
=> builtin
[0].toUpperCase() === builtin
[0])
64 .filter(builtin
=> !exceptions
.has(builtin
))
68 * Reports a lint error for the given node.
69 * @param {ASTNode} node The node to report.
70 * @param {string} builtin The name of the native builtin being extended.
73 function reportNode(node
, builtin
) {
76 messageId
: "unexpected",
84 * Check to see if the `prototype` property of the given object
85 * identifier node is being accessed.
86 * @param {ASTNode} identifierNode The Identifier representing the object
88 * @returns {boolean} True if the identifier is the object of a
89 * MemberExpression and its `prototype` property is being accessed,
92 function isPrototypePropertyAccessed(identifierNode
) {
95 identifierNode
.parent
&&
96 identifierNode
.parent
.type
=== "MemberExpression" &&
97 identifierNode
.parent
.object
=== identifierNode
&&
98 astUtils
.getStaticPropertyName(identifierNode
.parent
) === "prototype"
103 * Checks that an identifier is an object of a prototype whose member
104 * is being assigned in an AssignmentExpression.
105 * Example: Object.prototype.foo = "bar"
106 * @param {ASTNode} identifierNode The identifier to check.
107 * @returns {boolean} True if the identifier's prototype is modified.
109 function isInPrototypePropertyAssignment(identifierNode
) {
111 isPrototypePropertyAccessed(identifierNode
) &&
112 identifierNode
.parent
.parent
.type
=== "MemberExpression" &&
113 identifierNode
.parent
.parent
.parent
.type
=== "AssignmentExpression" &&
114 identifierNode
.parent
.parent
.parent
.left
=== identifierNode
.parent
.parent
119 * Checks that an identifier is an object of a prototype whose member
120 * is being extended via the Object.defineProperty() or
121 * Object.defineProperties() methods.
122 * Example: Object.defineProperty(Array.prototype, "foo", ...)
123 * Example: Object.defineProperties(Array.prototype, ...)
124 * @param {ASTNode} identifierNode The identifier to check.
125 * @returns {boolean} True if the identifier's prototype is modified.
127 function isInDefinePropertyCall(identifierNode
) {
129 isPrototypePropertyAccessed(identifierNode
) &&
130 identifierNode
.parent
.parent
.type
=== "CallExpression" &&
131 identifierNode
.parent
.parent
.arguments
[0] === identifierNode
.parent
&&
132 identifierNode
.parent
.parent
.callee
.type
=== "MemberExpression" &&
133 identifierNode
.parent
.parent
.callee
.object
.type
=== "Identifier" &&
134 identifierNode
.parent
.parent
.callee
.object
.name
=== "Object" &&
135 identifierNode
.parent
.parent
.callee
.property
.type
=== "Identifier" &&
136 propertyDefinitionMethods
.has(identifierNode
.parent
.parent
.callee
.property
.name
)
141 * Check to see if object prototype access is part of a prototype
142 * extension. There are three ways a prototype can be extended:
143 * 1. Assignment to prototype property (Object.prototype.foo = 1)
144 * 2. Object.defineProperty()/Object.defineProperties() on a prototype
145 * If prototype extension is detected, report the AssignmentExpression
146 * or CallExpression node.
147 * @param {ASTNode} identifierNode The Identifier representing the object
148 * which prototype is being accessed and possibly extended.
151 function checkAndReportPrototypeExtension(identifierNode
) {
152 if (isInPrototypePropertyAssignment(identifierNode
)) {
154 // Identifier --> MemberExpression --> MemberExpression --> AssignmentExpression
155 reportNode(identifierNode
.parent
.parent
.parent
, identifierNode
.name
);
156 } else if (isInDefinePropertyCall(identifierNode
)) {
158 // Identifier --> MemberExpression --> CallExpression
159 reportNode(identifierNode
.parent
.parent
, identifierNode
.name
);
166 const globalScope
= context
.getScope();
168 modifiedBuiltins
.forEach(builtin
=> {
169 const builtinVar
= globalScope
.set.get(builtin
);
171 if (builtinVar
&& builtinVar
.references
) {
172 builtinVar
.references
173 .map(ref
=> ref
.identifier
)
174 .forEach(checkAndReportPrototypeExtension
);