-
Notifications
You must be signed in to change notification settings - Fork 29.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
151 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
) | ||
); | ||
} |