Skip to content

Commit

Permalink
fix: adds support for nested destructured objects in
Browse files Browse the repository at this point in the history
o-restricted-properties

Fixes: eslint#16412
  • Loading branch information
arka1002 committed Nov 30, 2023
1 parent b29a16b commit 3e9868f
Show file tree
Hide file tree
Showing 3 changed files with 261 additions and 29 deletions.
108 changes: 83 additions & 25 deletions lib/rules/no-restricted-properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,25 @@ module.exports = {
}
});

/**
* gets the properties which are present in both the map of restricted properties(global or with an object) and in the parsed source code
* @param {any} restricted they can be either globally restricted or passed with objects
* @param {string[]} propertyNames the list of properties used in the code
* @returns {(undefined | { name: string, message: string | undefined }[])} the list of matched properties with their error / warning messages
*/
function getMatchingProperties(restricted, propertyNames) {
const matchedProperties = [];

propertyNames.forEach(property => {
if (!restricted.has(property)) {
return;
}
matchedProperties.push({ name: property, ...restricted.get(property) });
});

return matchedProperties.length === 0 ? false : matchedProperties;
}

/**
* Checks to see whether a property access is restricted, and reports it if so.
* @param {ASTNode} node The node to report
Expand All @@ -113,29 +132,46 @@ module.exports = {
return;
}
const matchedObject = restrictedProperties.get(objectName);
const matchedObjectProperty = matchedObject ? matchedObject.get(propertyName) : globallyRestrictedObjects.get(objectName);
const globalMatchedProperty = globallyRestrictedProperties.get(propertyName);
const matchedObjectProperty = matchedObject ? getMatchingProperties(matchedObject, propertyName) : false;
const globalMatchedObject = globallyRestrictedObjects.get(objectName);
const globalMatchedProperty = getMatchingProperties(globallyRestrictedProperties, propertyName);

if (matchedObjectProperty) {
const message = matchedObjectProperty.message ? ` ${matchedObjectProperty.message}` : "";
for (const props of matchedObjectProperty) {
const message = props.message ? ` ${props.message}` : "";

context.report({
node,
messageId: "restrictedObjectProperty",
data: {
objectName,
propertyName: props.name,
message
}
});
}
} else if (globalMatchedProperty) {
for (const props of globalMatchedProperty) {
const message = props.message ? ` ${props.message}` : "";

context.report({
node,
messageId: "restrictedProperty",
data: {
propertyName: props.name,
message
}
});
}
} else if (globalMatchedObject) {
const message = globalMatchedObject.message ? ` ${globalMatchedObject.message}` : "";

context.report({
node,
messageId: "restrictedObjectProperty",
data: {
objectName,
propertyName,
message
}
});
} else if (globalMatchedProperty) {
const message = globalMatchedProperty.message ? ` ${globalMatchedProperty.message}` : "";

context.report({
node,
messageId: "restrictedProperty",
data: {
propertyName,
propertyName: propertyName[0],
message
}
});
Expand All @@ -148,34 +184,56 @@ module.exports = {
* @returns {undefined}
*/
function checkDestructuringAssignment(node) {
if (node.right.type === "Identifier") {
const objectName = node.right.name;
if (node.right.type === "Identifier" || node.right.type === "CallExpression") {
const objectName = node.right.type === "Identifier" ? node.right.name : "";

if (node.left.type === "ObjectPattern") {
node.left.properties.forEach(property => {
checkPropertyAccess(node.left, objectName, astUtils.getStaticPropertyName(property));
checkPropertyAccess(node.left, objectName, astUtils.getAllProperties(node.left.properties));
}
if (node.left.type === "ArrayPattern") {
node.left.elements.forEach(element => {
if (element && element.type === "ObjectPattern") {
checkPropertyAccess(element, objectName, astUtils.getAllProperties(element.properties));
}
});
}
}
}

return {
MemberExpression(node) {
checkPropertyAccess(node, node.object && node.object.name, astUtils.getStaticPropertyName(node));
checkPropertyAccess(node, node.object && node.object.name, [astUtils.getStaticPropertyName(node)]);
},
VariableDeclarator(node) {
if (node.init && node.init.type === "Identifier") {
const objectName = node.init.name;
if (node.init && (node.init.type === "Identifier" || node.init.type === "CallExpression")) {
const objectName = node.init.type === "Identifier" ? node.init.name : "";

if (node.id.type === "ObjectPattern") {
node.id.properties.forEach(property => {
checkPropertyAccess(node.id, objectName, astUtils.getStaticPropertyName(property));
checkPropertyAccess(node.id, objectName, astUtils.getAllProperties(node.id.properties));
}

if (node.id.type === "ArrayPattern") {
node.id.elements.forEach(element => {
if (element && element.type === "ObjectPattern") {
checkPropertyAccess(element, objectName, astUtils.getAllProperties(element.properties));
}
});
}
}
},
AssignmentExpression: checkDestructuringAssignment,
AssignmentPattern: checkDestructuringAssignment
AssignmentPattern: checkDestructuringAssignment,
ArrowFunctionExpression(node) {
for (const param of node.params) {
if (param.type === "ObjectPattern") {
checkPropertyAccess(param, "", astUtils.getAllProperties(param.properties));
} else if (param.type === "AssignmentPattern" && param.left.type === "ObjectPattern") {
const objectName = param.right.type === "Identifier" ? param.right.name : "";

checkPropertyAccess(param, objectName, astUtils.getAllProperties(param.left.properties));
}
}
}
};
}
};
46 changes: 46 additions & 0 deletions lib/rules/utils/ast-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,51 @@ function getStaticPropertyName(node) {
return null;
}

/**
* Gets all the properties used in object destructuring at a given node.
* The node can only be a Property.
* For examples :-
* { a } = m // => [ "a" ]
* { a, b } = m // => [ "a", "b" ]
* { a: { c } = {} } = m // => ["a", "c"]
* { a: { b: c, d }, k } = m // => ["a", "b", "d", "k"]
* @param {ASTNode[]} propArray the list of properties which are passed
* @returns {string[]} the list of all properties which are used
*/
function getAllProperties(propArray) {
const propsUsed = [];

/**
* Collects the properties used in an array.
* For example - if the given code is
* { a, c: { d, x: { z } },v }
* it get stored in the array as [ "a", "c", "d", "x", "z", "v" ]
* @param {ASTNode[]} properties the array of nodes
* @returns {undefined}
*/
function traverse(properties) {
for (const node of properties) {
if (node.type !== "Property") {
return;
}
if (node.key.type === "Identifier" && !node.computed) {
propsUsed.push(node.key.name);
} else {
propsUsed.push(getStaticStringValue(node.key));
}
if (node.value.type === "ObjectPattern") {
traverse(node.value.properties);
}
if (node.value.type === "AssignmentPattern" && node.value.left.type === "ObjectPattern") {
traverse(node.value.left.properties);
}
}
}
traverse(propArray);
return propsUsed;
}


/**
* Retrieve `ChainExpression#expression` value if the given node a `ChainExpression` node. Otherwise, pass through it.
* @param {ASTNode} node The node to address.
Expand Down Expand Up @@ -2266,6 +2311,7 @@ module.exports = {
isNullLiteral,
getStaticStringValue,
getStaticPropertyName,
getAllProperties,
skipChainExpression,
isSpecificId,
isSpecificMemberAccess,
Expand Down
136 changes: 132 additions & 4 deletions tests/lib/rules/no-restricted-properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,6 @@ ruleTester.run("no-restricted-properties", rule, {
code: "let {unrelated} = foo;",
options: [{ object: "foo", property: "bar" }],
parserOptions: { ecmaVersion: 6 }
}, {
code: "let {baz: {bar: qux}} = foo;",
options: [{ object: "foo", property: "bar" }],
parserOptions: { ecmaVersion: 6 }
}, {
code: "let {bar} = foo.baz;",
options: [{ object: "foo", property: "bar" }],
Expand Down Expand Up @@ -546,6 +542,138 @@ ruleTester.run("no-restricted-properties", rule, {
},
type: "MemberExpression"
}]
}, {
code: "const { bar: { bad } = {} } = foo;",
options: [{ property: "bad" }],
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "restrictedProperty",
data: {
propertyName: "bad",
message: ""
}
}]
}, {
code: "const { bar: { bad } } = foo;",
options: [{ property: "bad" }],
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "restrictedProperty",
data: {
propertyName: "bad",
message: ""
}
}]
}, {
code: "const { bad } = foo();",
options: [{ property: "bad" }],
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "restrictedProperty",
data: {
propertyName: "bad",
message: ""
}
}]
}, {
code: "({ bad } = foo());",
options: [{ property: "bad" }],
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "restrictedProperty",
data: {
propertyName: "bad",
message: ""
}
}]
}, {
code: "({ bar: { bad } } = foo);",
options: [{ property: "bad" }],
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "restrictedProperty",
data: {
propertyName: "bad",
message: ""
}
}]
}, {
code: "({ bar: { bad } = {} } = foo);",
options: [{ property: "bad" }],
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "restrictedProperty",
data: {
propertyName: "bad",
message: ""
}
}]
}, {
code: "({ bad }) => {};",
options: [{ property: "bad" }],
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "restrictedProperty",
data: {
propertyName: "bad",
message: ""
}
}]
}, {
code: "({ bad } = {}) => {};",
options: [{ property: "bad" }],
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "restrictedProperty",
data: {
propertyName: "bad",
message: ""
}
}]
}, {
code: "({ bad: bar }) => {};",
options: [{ property: "bad" }],
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "restrictedProperty",
data: {
propertyName: "bad",
message: ""
}
}]
}, {
code: "({ bar: { bad } = {} }) => {};",
options: [{ property: "bad" }],
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "restrictedProperty",
data: {
propertyName: "bad",
message: ""
}
}]
}, {
code: "[{ bad }] = foo;",
options: [{ property: "bad" }],
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "restrictedProperty",
data: {
propertyName: "bad",
message: ""
}
}]
}, {
code: "const [{ bad }] = foo;",
options: [{ property: "bad" }],
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "restrictedProperty",
data: {
propertyName: "bad",
message: ""
}
}]
}
]
});

0 comments on commit 3e9868f

Please sign in to comment.