diff --git a/packages/snaps-utils/coverage.json b/packages/snaps-utils/coverage.json index 128a3f13dd..00e4552dce 100644 --- a/packages/snaps-utils/coverage.json +++ b/packages/snaps-utils/coverage.json @@ -2,5 +2,5 @@ "branches": 99.76, "functions": 99, "lines": 98.68, - "statements": 97.27 + "statements": 97.28 } diff --git a/packages/snaps-utils/src/manifest/validators/unused-exports.test.ts b/packages/snaps-utils/src/manifest/validators/unused-exports.test.ts index 780ae34415..df8f6ef629 100644 --- a/packages/snaps-utils/src/manifest/validators/unused-exports.test.ts +++ b/packages/snaps-utils/src/manifest/validators/unused-exports.test.ts @@ -91,4 +91,36 @@ describe('unusedExports', () => { ), ); }); + + it('does not report if the Snap exports a handler and requests permission for it in the manifest', async () => { + const report = jest.fn(); + assert(unusedExports.semanticCheck); + + const files = getMockSnapFiles({ + manifest: getSnapManifest({ + initialPermissions: { + 'endowment:page-home': {}, + 'endowment:rpc': {}, + 'endowment:lifecycle-hooks': {}, + }, + }), + manifestPath: __filename, + }); + + await unusedExports.semanticCheck(files, { + report, + options: { + exports: ['onRpcRequest', 'onHomePage', 'onInstall'], + handlerEndowments: { + onRpcRequest: 'endowment:rpc', + onHomePage: 'endowment:page-home', + onInstall: 'endowment:lifecycle-hooks', + onUpdate: 'endowment:lifecycle-hooks', + onUserInput: null, + }, + }, + }); + + expect(report).toHaveBeenCalledTimes(0); + }); }); diff --git a/packages/snaps-utils/src/manifest/validators/unused-exports.ts b/packages/snaps-utils/src/manifest/validators/unused-exports.ts index d1f9c77ede..eb4ef237c2 100644 --- a/packages/snaps-utils/src/manifest/validators/unused-exports.ts +++ b/packages/snaps-utils/src/manifest/validators/unused-exports.ts @@ -17,6 +17,16 @@ export const unusedExports: ValidatorMeta = { return; } + // Endowments used based on the exports from the Snap. This is used to + // filter endowments that are used by multiple handlers, e.g., the lifecycle + // handlers. + const usedEndowments = Object.entries(handlerEndowments) + .filter( + ([handler, endowment]) => + endowment === null || exports.includes(handler), + ) + .map(([, endowment]) => endowment); + const unusedHandlers = Object.entries(handlerEndowments) .filter(([handler, endowment]) => { if (endowment === null) { @@ -39,9 +49,11 @@ export const unusedExports: ValidatorMeta = { } return ( + !usedEndowments.includes(endowment) && files.manifest.result.initialPermissions[ endowment as keyof InitialPermissions - ] && !exports.includes(handler) + ] && + !exports.includes(handler) ); }, );