UNPKG

@hyperlane-xyz/utils

Version:

General utilities and types for the Hyperlane network

258 lines 10.4 kB
import { expect } from 'chai'; import fs from 'fs'; import path from 'path'; import sinon from 'sinon'; import rule from './no-restricted-imports-from-exports.js'; describe('no-restricted-imports-from-exports rule', () => { // Mock file contents to simulate index exports const exportedFileContents = { './src/index.ts': "export { componentA } from './components/componentA';\nexport { componentB } from './components/componentB';", './src/index-fs.ts': "export { fileUtil } from './utils/fileUtil';", }; // Path mapping for test fixtures const pathMappings = { './src/index.ts': './src/index.ts', './src/index-fs.ts': './src/index-fs.ts', 'components/componentA': './src/components/componentA', 'components/componentB': './src/components/componentB', 'components/nested/componentA': './src/components/nested/componentA', 'utils/fileUtil': './src/utils/fileUtil', 'utils/otherUtil': './src/utils/otherUtil', }; function setupStubs() { // Stub fs.readFileSync to return mock file contents sinon .stub(fs, 'readFileSync') .callsFake((filePath) => typeof filePath === 'string' ? exportedFileContents[filePath] || '' : ''); // Stub path.resolve to simplify path resolution in tests sinon.stub(path, 'resolve').callsFake(function (...args) { const lastArg = args[args.length - 1]; if (typeof lastArg !== 'string') { return String(lastArg); } for (const [pattern, mapping] of Object.entries(pathMappings)) { if (lastArg.includes(pattern)) { return mapping; } } return lastArg; }); sinon .stub(path, 'dirname') .callsFake((filePath) => filePath.substring(0, filePath.lastIndexOf('/') || 0)); } // Helper function to test import validations in different scenarios function verifyImport({ sourceFile, importPath, expectedError, shouldPass = false, options = {}, }) { const errors = []; const context = { getFilename: () => sourceFile, cwd: () => '.', report: (err) => errors.push(err), options: [options], }; const ruleInstance = rule.create(context); if (ruleInstance?.ImportDeclaration) { ruleInstance.ImportDeclaration({ type: 'ImportDeclaration', source: { type: 'Literal', value: importPath, raw: `'${importPath}'`, }, specifiers: [], parent: {}, }); } if (expectedError || !shouldPass) { expect(errors.length).to.equal(1, `Import from ${sourceFile} to ${importPath} should report an error`); if (expectedError) { expect(errors[0].messageId).to.equal(expectedError, `Import should report error type ${expectedError}`); } } else { expect(errors.length).to.equal(0, `Import from ${sourceFile} to ${importPath} should not report any errors`); } } beforeEach(() => { setupStubs(); }); afterEach(() => { // Restore all stubs sinon.restore(); }); describe('Non-exported files - Files not included in index exports', () => { it('should allow importing node modules in non-exported files', () => { verifyImport({ sourceFile: './src/other.ts', importPath: 'fs', shouldPass: true, }); }); it('should allow importing any modules in non-exported files without restrictions', () => { const sourceFile = './src/other.ts'; verifyImport({ sourceFile, importPath: './components/other', shouldPass: true, }); verifyImport({ sourceFile, importPath: './utils/fileUtil', shouldPass: true, }); verifyImport({ sourceFile, importPath: 'path', shouldPass: true, }); }); }); describe('Exported files - Files that are exported through index.ts', () => { const exportedComponentFile = './src/components/componentA.ts'; it('should allow importing regular modules from exported files', () => { verifyImport({ sourceFile: exportedComponentFile, importPath: './components/other', shouldPass: true, }); }); it('should disallow importing node modules from exported files to prevent side effects', () => { verifyImport({ sourceFile: exportedComponentFile, importPath: 'fs', expectedError: 'restrictedNodeImport', }); verifyImport({ sourceFile: exportedComponentFile, importPath: 'path', expectedError: 'restrictedNodeImport', }); verifyImport({ sourceFile: exportedComponentFile, importPath: 'crypto', expectedError: 'restrictedNodeImport', }); }); it('should disallow importing from fs-indexed modules in exported components', () => { verifyImport({ sourceFile: exportedComponentFile, importPath: './utils/fileUtil', expectedError: 'restrictedFsImport', }); }); it('should handle relative imports correctly and detect restricted fs imports', () => { verifyImport({ sourceFile: exportedComponentFile, importPath: '../utils/fileUtil', expectedError: 'restrictedFsImport', }); }); it('should allow importing scoped packages even from exported components', () => { verifyImport({ sourceFile: exportedComponentFile, importPath: '@scoped/fs-module', shouldPass: true, }); }); it('should allow files exported from index-fs.ts to import node modules directly', () => { const fileUtilPath = './src/utils/fileUtil.ts'; verifyImport({ sourceFile: fileUtilPath, importPath: 'fs', shouldPass: true, }); verifyImport({ sourceFile: fileUtilPath, importPath: 'crypto', shouldPass: true, }); }); }); describe('Edge cases', () => { const exportedComponentFile = './src/components/componentA.ts'; it('should handle subpaths of node modules correctly and restrict them in exported files', () => { verifyImport({ sourceFile: exportedComponentFile, importPath: 'fs/promises', expectedError: 'restrictedNodeImport', }); verifyImport({ sourceFile: exportedComponentFile, importPath: 'path/posix', expectedError: 'restrictedNodeImport', }); }); it('should allow similarly named packages that are not actual node modules', () => { verifyImport({ sourceFile: exportedComponentFile, importPath: 'fstest', shouldPass: true, }); verifyImport({ sourceFile: exportedComponentFile, importPath: 'fs-test', shouldPass: true, }); verifyImport({ sourceFile: exportedComponentFile, importPath: 'path-extra', shouldPass: true, }); }); it('should handle non-existent files gracefully without throwing errors', () => { verifyImport({ sourceFile: exportedComponentFile, importPath: './non-existent-file', shouldPass: true, }); }); }); describe('Custom entry points', () => { const customExportedFileContents = { './src/main.ts': "export { customComponent } from './components/customComponent';", './src/restricted.ts': "export { restrictedUtil } from './utils/restrictedUtil';", }; beforeEach(() => { // Add custom file contents to the existing exportedFileContents Object.assign(exportedFileContents, customExportedFileContents); // Add custom path mappings Object.assign(pathMappings, { './src/main.ts': './src/main.ts', './src/restricted.ts': './src/restricted.ts', 'components/customComponent': './src/components/customComponent', 'utils/restrictedUtil': './src/utils/restrictedUtil', }); }); it('should respect custom main and restricted entry points', () => { const options = { mainEntry: './src/main.ts', restrictedEntry: './src/restricted.ts', }; // Test with custom entry points verifyImport({ sourceFile: './src/components/customComponent.ts', importPath: './utils/restrictedUtil', expectedError: 'restrictedFsImport', options, }); // Should still work for node modules verifyImport({ sourceFile: './src/components/customComponent.ts', importPath: 'fs', expectedError: 'restrictedNodeImport', options, }); }); it('should fall back to defaults when no options provided', () => { // Test with default entry points verifyImport({ sourceFile: './src/components/componentA.ts', importPath: './utils/fileUtil', expectedError: 'restrictedFsImport', }); }); }); }); //# sourceMappingURL=no-restricted-imports-from-exports.test.js.map