]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/no-extend-native.js
import 8.41.0 source
[pve-eslint.git] / eslint / lib / rules / no-extend-native.js
1 /**
2 * @fileoverview Rule to flag adding properties to native object's prototypes.
3 * @author David Nelson
4 */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils");
13 const globals = require("globals");
14
15 //------------------------------------------------------------------------------
16 // Rule Definition
17 //------------------------------------------------------------------------------
18
19 /** @type {import('../shared/types').Rule} */
20 module.exports = {
21 meta: {
22 type: "suggestion",
23
24 docs: {
25 description: "Disallow extending native types",
26 recommended: false,
27 url: "https://eslint.org/docs/latest/rules/no-extend-native"
28 },
29
30 schema: [
31 {
32 type: "object",
33 properties: {
34 exceptions: {
35 type: "array",
36 items: {
37 type: "string"
38 },
39 uniqueItems: true
40 }
41 },
42 additionalProperties: false
43 }
44 ],
45
46 messages: {
47 unexpected: "{{builtin}} prototype is read only, properties should not be added."
48 }
49 },
50
51 create(context) {
52
53 const config = context.options[0] || {};
54 const sourceCode = context.sourceCode;
55 const exceptions = new Set(config.exceptions || []);
56 const modifiedBuiltins = new Set(
57 Object.keys(globals.builtin)
58 .filter(builtin => builtin[0].toUpperCase() === builtin[0])
59 .filter(builtin => !exceptions.has(builtin))
60 );
61
62 /**
63 * Reports a lint error for the given node.
64 * @param {ASTNode} node The node to report.
65 * @param {string} builtin The name of the native builtin being extended.
66 * @returns {void}
67 */
68 function reportNode(node, builtin) {
69 context.report({
70 node,
71 messageId: "unexpected",
72 data: {
73 builtin
74 }
75 });
76 }
77
78 /**
79 * Check to see if the `prototype` property of the given object
80 * identifier node is being accessed.
81 * @param {ASTNode} identifierNode The Identifier representing the object
82 * to check.
83 * @returns {boolean} True if the identifier is the object of a
84 * MemberExpression and its `prototype` property is being accessed,
85 * false otherwise.
86 */
87 function isPrototypePropertyAccessed(identifierNode) {
88 return Boolean(
89 identifierNode &&
90 identifierNode.parent &&
91 identifierNode.parent.type === "MemberExpression" &&
92 identifierNode.parent.object === identifierNode &&
93 astUtils.getStaticPropertyName(identifierNode.parent) === "prototype"
94 );
95 }
96
97 /**
98 * Check if it's an assignment to the property of the given node.
99 * Example: `*.prop = 0` // the `*` is the given node.
100 * @param {ASTNode} node The node to check.
101 * @returns {boolean} True if an assignment to the property of the node.
102 */
103 function isAssigningToPropertyOf(node) {
104 return (
105 node.parent.type === "MemberExpression" &&
106 node.parent.object === node &&
107 node.parent.parent.type === "AssignmentExpression" &&
108 node.parent.parent.left === node.parent
109 );
110 }
111
112 /**
113 * Checks if the given node is at the first argument of the method call of `Object.defineProperty()` or `Object.defineProperties()`.
114 * @param {ASTNode} node The node to check.
115 * @returns {boolean} True if the node is at the first argument of the method call of `Object.defineProperty()` or `Object.defineProperties()`.
116 */
117 function isInDefinePropertyCall(node) {
118 return (
119 node.parent.type === "CallExpression" &&
120 node.parent.arguments[0] === node &&
121 astUtils.isSpecificMemberAccess(node.parent.callee, "Object", /^definePropert(?:y|ies)$/u)
122 );
123 }
124
125 /**
126 * Check to see if object prototype access is part of a prototype
127 * extension. There are three ways a prototype can be extended:
128 * 1. Assignment to prototype property (Object.prototype.foo = 1)
129 * 2. Object.defineProperty()/Object.defineProperties() on a prototype
130 * If prototype extension is detected, report the AssignmentExpression
131 * or CallExpression node.
132 * @param {ASTNode} identifierNode The Identifier representing the object
133 * which prototype is being accessed and possibly extended.
134 * @returns {void}
135 */
136 function checkAndReportPrototypeExtension(identifierNode) {
137 if (!isPrototypePropertyAccessed(identifierNode)) {
138 return; // This is not `*.prototype` access.
139 }
140
141 /*
142 * `identifierNode.parent` is a MemberExpression `*.prototype`.
143 * If it's an optional member access, it may be wrapped by a `ChainExpression` node.
144 */
145 const prototypeNode =
146 identifierNode.parent.parent.type === "ChainExpression"
147 ? identifierNode.parent.parent
148 : identifierNode.parent;
149
150 if (isAssigningToPropertyOf(prototypeNode)) {
151
152 // `*.prototype` -> MemberExpression -> AssignmentExpression
153 reportNode(prototypeNode.parent.parent, identifierNode.name);
154 } else if (isInDefinePropertyCall(prototypeNode)) {
155
156 // `*.prototype` -> CallExpression
157 reportNode(prototypeNode.parent, identifierNode.name);
158 }
159 }
160
161 return {
162
163 "Program:exit"(node) {
164 const globalScope = sourceCode.getScope(node);
165
166 modifiedBuiltins.forEach(builtin => {
167 const builtinVar = globalScope.set.get(builtin);
168
169 if (builtinVar && builtinVar.references) {
170 builtinVar.references
171 .map(ref => ref.identifier)
172 .forEach(checkAndReportPrototypeExtension);
173 }
174 });
175 }
176 };
177
178 }
179 };