Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support destructuring in harden-exports #2404

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,4 @@ dist/
/packages-graph*
api-docs

.aider*
2 changes: 1 addition & 1 deletion packages/eslint-plugin/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// Requirements
//------------------------------------------------------------------------------

var requireIndex = require('requireindex');
const requireIndex = require('requireindex');

//------------------------------------------------------------------------------
// Plugin Definition
Expand Down
58 changes: 45 additions & 13 deletions packages/eslint-plugin/lib/rules/harden-exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,39 @@ module.exports = {
/** @type {string[]} */
let exportNames = [];
if (exportNode.declaration) {
// @ts-expect-error xxx typedef
if (exportNode.declaration.declarations) {
// @ts-expect-error xxx typedef
if (exportNode.declaration.type === 'VariableDeclaration') {
for (const declaration of exportNode.declaration.declarations) {
if (declaration.id.type === 'ObjectPattern') {
for (const prop of declaration.id.properties) {
exportNames.push(prop.key.name);
if (prop.type === 'RestElement') {
console.warn('Rest elements are not supported');
continue;
}
if (prop.value.type === 'Identifier') {
exportNames.push(prop.value.name);
} else if (
prop.value.type === 'AssignmentPattern' &&
prop.value.left.type === 'Identifier'
) {
exportNames.push(prop.value.left.name);
}
}
} else {
} else if (declaration.id.type === 'ArrayPattern') {
for (const element of declaration.id.elements) {
if (element && element.type === 'Identifier') {
exportNames.push(element.name);
}
}
} else if (declaration.id.type === 'Identifier') {
exportNames.push(declaration.id.name);
}
}
} else if (exportNode.declaration.type === 'FunctionDeclaration') {
const nodeName = exportNode.declaration.id?.name ?? '<missing>';
context.report({
node: exportNode,
// The 'function' keyword hoisting makes the valuable mutable before it can be hardened.
message: `Export '${exportNode.declaration.id.name}' should be a const declaration with an arrow function.`,
message: `Export '${nodeName}' should be a const declaration with an arrow function.`,
});
}
} else if (exportNode.specifiers) {
Expand All @@ -73,17 +89,33 @@ module.exports = {

const missingHardenCalls = [];
for (const exportName of exportNames) {
const hasHardenCall = sourceCode.ast.body.some(statement => {
return (
const hasHardenCall = sourceCode.ast.body.some(
statement =>
statement.type === 'ExpressionStatement' &&
statement.expression.type === 'CallExpression' &&
// @ts-expect-error xxx typedef
statement.expression.callee.type === 'Identifier' &&
statement.expression.callee.name === 'harden' &&
statement.expression.arguments.length === 1 &&
// @ts-expect-error xxx typedef
statement.expression.arguments[0].name === exportName
);
});
((statement.expression.arguments[0].type === 'Identifier' &&
statement.expression.arguments[0].name === exportName) ||
// @ts-expect-error XXX non-overlapping
(statement.expression.arguments[0].type === 'ObjectPattern' &&
// @ts-expect-error XXX non-overlapping
statement.expression.arguments[0].properties.some(
prop =>
prop.value.type === 'Identifier' &&
prop.value.name === exportName,
)) ||
// @ts-expect-error XXX non-overlapping
(statement.expression.arguments[0].type === 'ArrayPattern' &&
// @ts-expect-error XXX non-overlapping
statement.expression.arguments[0].elements.some(
element =>
element &&
element.type === 'Identifier' &&
element.name === exportName,
))),
);

if (!hasHardenCall) {
missingHardenCalls.push(exportName);
Expand Down
45 changes: 45 additions & 0 deletions packages/eslint-plugin/test/harden-exports.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ harden(getEnvironmentOptionsList);
harden(environmentOptionsListHas);
`,
},
{
code: `
export const { propName: exportName } = objWithPropName;
harden(exportName);
`,
},
{
code: `
export const [ item1, item2 ] = [fn1, fn2];
harden(item1);
harden(item2);
`,
},
];

const invalid = [
Expand Down Expand Up @@ -180,6 +193,38 @@ harden(getEnvironmentOptionsList);
harden(environmentOptionsListHas);
`,
},
{
code: `
export const { propName: exportName } = objWithPropName;
`,
errors: [
{
message:
"Named export 'exportName' should be followed by a call to 'harden'.",
},
],
output: `
export const { propName: exportName } = objWithPropName;
harden(exportName);
`,
},
{
code: `
export const [ item1, item2 ] = [fn1, fn2];
harden(item1);
`,
errors: [
{
message:
"Named export 'item2' should be followed by a call to 'harden'.",
},
],
output: `
export const [ item1, item2 ] = [fn1, fn2];
harden(item2);
harden(item1);
`,
},
];

const jsTester = new RuleTester({
Expand Down
Loading