Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
trigger ci
  • Loading branch information
rluvaton committed Jul 20, 2023
commit 976b24fc7415c46c2d134d25446d06525c825d1e
6 changes: 6 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ module.exports = {
},
] },
},
{
files: ['lib/**/*.js', 'lib/**/*.cjs', 'lib/**/*.mjs'],
rules: {
'node-core/set-proto-to-null-in-object': 'error',
},
},
],
rules: {
// ESLint built-in rules
Expand Down
140 changes: 140 additions & 0 deletions tools/eslint-rules/set-proto-to-null-in-object.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
'use strict';

module.exports = {
meta: {
messages: {
error: 'Add `__proto__: null` to object',
},
fixable: 'code',
},
create: function(context) {
return {
ObjectExpression(node) {
// Not adding __proto__ to module.exports as it will break a lot of libraries
if (isModuleExportsObject(node) || isModifiedExports(node)) {
return;
}

const properties = node.properties;
let hasProto = false;

for (const property of properties) {

if (!property.key) {
continue;
}

if (property.key.type === 'Identifier' && property.key.name === '__proto__') {
hasProto = true;
break;
}

if (property.key.type === 'Literal' && property.key.value === '__proto__') {
hasProto = true;
break;
}
}

if (hasProto) {
return;
}

if (properties.length > 0) {
// If the object has properties but no __proto__ property
context.report({
node,
message: 'Every object must have __proto__: null',
fix: function(fixer) {
// Generate the fix suggestion to add __proto__: null
const sourceCode = context.getSourceCode();
const firstProperty = properties[0];
const firstPropertyToken = sourceCode.getFirstToken(firstProperty);


const isMultiLine = properties.length === 1 ?
// If the object has only one property,
// it's multiline if the property is not on the same line as the object parenthesis
properties[0].loc.start.line !== node.loc.start.line :
// If the object has more than one property,
// it's multiline if the first and second properties are not on the same line
properties[0].loc.start.line !== properties[1].loc.start.line;

const fixText = `__proto__: null,${isMultiLine ? '\n' : ' '}`;

// Insert the fix suggestion before the first property
return fixer.insertTextBefore(firstPropertyToken, fixText);
},
});
}

if (properties.length === 0) {
// If the object is empty and missing __proto__
context.report({
node,
message: 'Every empty object must have __proto__: null',
fix: function(fixer) {
// Generate the fix suggestion to create the object with __proto__: null
const fixText = '{ __proto__: null }';

// Replace the empty object with the fix suggestion
return fixer.replaceText(node, fixText);
},
});
}
},
};
},
};

// Helper function to check if the object is `module.exports`
function isModuleExportsObject(node) {
return (
node.parent &&
node.parent.type === 'AssignmentExpression' &&
node.parent.left &&
node.parent.left.type === 'MemberExpression' &&
node.parent.left.object &&
node.parent.left.object.name === 'module' &&
node.parent.left.property &&
node.parent.left.property.name === 'exports'
);
}

function isModifiedExports(node) {
return (
node.parent &&
(isObjectAssignCall(node.parent) || isObjectDefinePropertiesCall(node.parent))
);
}

// Helper function to check if the node represents an ObjectAssign call
function isObjectAssignCall(node) {
return (
node.type === 'CallExpression' &&
node.callee &&
node.callee.type === 'Identifier' &&
node.callee.name === 'ObjectAssign' &&
node.arguments.length > 1 &&
node.arguments.some((arg) =>
arg.type === 'MemberExpression' &&
arg.object.name === 'module' &&
arg.property.name === 'exports',
)
);
}

// Helper function to check if the node represents an ObjectDefineProperties call
function isObjectDefinePropertiesCall(node) {
return (
node.type === 'CallExpression' &&
node.callee &&
node.callee.type === 'Identifier' &&
node.callee.name === 'ObjectDefineProperties' &&
node.arguments.length > 1 &&
node.arguments.some((arg) =>
arg.type === 'MemberExpression' &&
arg.object.name === 'module' &&
arg.property.name === 'exports',
)
);
}