Skip to content

Commit

Permalink
add eslint rule
Browse files Browse the repository at this point in the history
  • Loading branch information
rluvaton committed Jul 20, 2023
1 parent 648dced commit 59f24db
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 1 deletion.
10 changes: 10 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,16 @@ module.exports = {
},
] },
},
{
files: [
'lib/internal/test_runner/**/*.js',
'lib/internal/test_runner/**/*.cjs',
'lib/internal/test_runner/**/*.mjs',
],
rules: {
'node-core/set-proto-to-null-in-object': 'error',
},
},
],
rules: {
// ESLint built-in rules
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/test_runner/tests_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class TestsStream extends Readable {

[kEmitMessage](type, data) {
this.emit(type, data);
this.#tryPush({ type, data });
this.#tryPush({ __proto__: null, type, data });
}

#tryPush(message) {
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',
)
);
}

0 comments on commit 59f24db

Please sign in to comment.