]> git.proxmox.com Git - pve-eslint.git/blame - eslint/lib/rules/no-extend-native.js
bump version to 8.41.0-3
[pve-eslint.git] / eslint / lib / rules / no-extend-native.js
CommitLineData
eb39fafa
DC
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
12const astUtils = require("./utils/ast-utils");
13const globals = require("globals");
14
eb39fafa
DC
15//------------------------------------------------------------------------------
16// Rule Definition
17//------------------------------------------------------------------------------
18
34eeec05 19/** @type {import('../shared/types').Rule} */
eb39fafa
DC
20module.exports = {
21 meta: {
22 type: "suggestion",
23
24 docs: {
8f9d1d4d 25 description: "Disallow extending native types",
eb39fafa 26 recommended: false,
f2a92ac6 27 url: "https://eslint.org/docs/latest/rules/no-extend-native"
eb39fafa
DC
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] || {};
f2a92ac6 54 const sourceCode = context.sourceCode;
eb39fafa
DC
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 /**
6f036462
TL
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.
eb39fafa 102 */
6f036462
TL
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
eb39fafa
DC
109 );
110 }
111
112 /**
6f036462
TL
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()`.
eb39fafa 116 */
6f036462
TL
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)
eb39fafa
DC
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) {
6f036462
TL
137 if (!isPrototypePropertyAccessed(identifierNode)) {
138 return; // This is not `*.prototype` access.
139 }
140
141 /*
456be15e 142 * `identifierNode.parent` is a MemberExpression `*.prototype`.
6f036462
TL
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)) {
eb39fafa 151
6f036462
TL
152 // `*.prototype` -> MemberExpression -> AssignmentExpression
153 reportNode(prototypeNode.parent.parent, identifierNode.name);
154 } else if (isInDefinePropertyCall(prototypeNode)) {
eb39fafa 155
6f036462
TL
156 // `*.prototype` -> CallExpression
157 reportNode(prototypeNode.parent, identifierNode.name);
eb39fafa
DC
158 }
159 }
160
161 return {
162
f2a92ac6
DC
163 "Program:exit"(node) {
164 const globalScope = sourceCode.getScope(node);
eb39fafa
DC
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};