diff --git a/README.md b/README.md index 5dad59b..62aab9a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,20 @@ # Barrel Roll -Visual Studio Code extension to automatically export types, functions, constants, and classes through barrel files. +[![CI](https://github.com/Coderrob/barrel-roll/actions/workflows/ci.yml/badge.svg)](https://github.com/Coderrob/barrel-roll/actions/workflows/ci.yml) +[![VS Code Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/Coderrob.barrel-roll)](https://marketplace.visualstudio.com/items?itemName=Coderrob.barrel-roll) +[![VS Code Marketplace Installs](https://img.shields.io/visual-studio-marketplace/i/Coderrob.barrel-roll)](https://marketplace.visualstudio.com/items?itemName=Coderrob.barrel-roll) +[![License: Apache-2.0](https://img.shields.io/github/license/Coderrob/barrel-roll)](LICENSE) +[![Coverage](https://img.shields.io/badge/coverage-94.8%25-4c1)](https://github.com/Coderrob/barrel-roll/actions/workflows/ci.yml) +[![Quality Checks](https://img.shields.io/badge/quality--checks-eslint%20%7C%20madge%20%7C%20jscpd-1f6feb)](package.json) + +Barrel Roll is a Visual Studio Code extension that makes barrel file creation and upkeep effortless. Right-click any folder, pick a Barrel Roll command, and the extension assembles a curated `index.ts` that reflects the exports your module actually exposes—no tedious manual wiring, no temptation to `export *` the entire directory. + +Whether you need a single barrel refreshed or an entire tree brought into alignment, Barrel Roll keeps your exports clean, consistent, and ready for real work. It discovers TypeScript exports, connects child barrels to their parents, and prevents duplicate re-exports so your team can focus on building features instead of shuffling files. ## Features - **Right-click Generation**: Right-click any folder in the VS Code explorer to generate or update an `index.ts` barrel file -- **Recursive Commands**: Generate barrels for a single folder, for an entire subtree, or sanitize existing barrels via dedicated context menu entries +- **Two Command Modes**: Choose between updating just the selected directory or traversing the full subtree via dedicated context menu entries - **Recursive Barrels**: Automatically walks child folders, generating barrels for every directory and wiring parent barrels to re-export their children - **Smart Export Detection**: Automatically detects and exports all TypeScript exports (classes, interfaces, types, functions, constants, enums) - **Clean Architecture**: Follows SOLID principles for maintainability and extensibility @@ -17,25 +26,24 @@ Visual Studio Code extension to automatically export types, functions, constants ### From VS Code Marketplace 1. Open VS Code -2. Go to Extensions (Ctrl+Shift+X / Cmd+Shift+X) -3. Search for "Barrel Roll" -4. Click Install +1. Go to Extensions (Ctrl+Shift+X / Cmd+Shift+X) +1. Search for "Barrel Roll" +1. Click Install ### From VSIX 1. Download the latest `.vsix` file from the [releases page](https://github.com/Coderrob/barrel-roll/releases) -2. In VS Code, go to Extensions -3. Click the `...` menu and select "Install from VSIX..." -4. Select the downloaded file +1. In VS Code, go to Extensions +1. Click the `...` menu and select "Install from VSIX..." +1. Select the downloaded file ## Usage 1. Right-click on any folder in the VS Code explorer -2. Select one of the Barrel Roll commands: - - `Barrel Roll: Generate/Update index.ts` (current folder only) - - `Barrel Roll: Generate/Update index.ts (Recursive)` (folder and all subfolders) - - `Barrel Roll: Update Existing index.ts Files` (refresh existing barrels without adding new ones) -3. The extension will: +1. Select one of the Barrel Roll commands: + - `Barrel Roll: Update Barrel Directory` (updates only the selected folder) + - `Barrel Roll: Update Barrel Directory (Recursive)` (updates the selected folder and all subfolders) +1. The extension will: - Scan all `.ts`/`.tsx` files in the folder (excluding `index.ts` and declaration files) - Recursively process each subfolder and generate its `index.ts` - Extract all exported items @@ -145,4 +153,4 @@ Contributions are welcome! Please feel free to submit a Pull Request. ## License -MIT +Apache-2.0 diff --git a/badges/coverage.svg b/badges/coverage.svg index a1823ef..0d60a6c 100644 --- a/badges/coverage.svg +++ b/badges/coverage.svg @@ -1 +1 @@ -Coverage: 84.88%Coverage84.88% \ No newline at end of file +Coverage: 94.8%Coverage94.8% \ No newline at end of file diff --git a/src/core/barrel/barrel-content.builder.test.ts b/src/core/barrel/barrel-content.builder.test.ts index 4bb07ab..a079848 100644 --- a/src/core/barrel/barrel-content.builder.test.ts +++ b/src/core/barrel/barrel-content.builder.test.ts @@ -92,6 +92,19 @@ describe('BarrelContentBuilder', () => { ); }); + it('should ignore undefined entries produced by legacy callers', () => { + const entries = new Map(); + entries.set('ghost.ts', undefined as unknown as BarrelEntry); + entries.set('echo.ts', { + kind: BarrelEntryKind.File, + exports: [{ kind: BarrelExportKind.Value, name: 'Echo' }], + }); + + const result = builder.buildContent(entries, ''); + + assert.strictEqual(result.trim(), "export { Echo } from './echo';"); + }); + const parentDirectoryCases: Array> = [ new Map([['../outside', { kind: BarrelEntryKind.Directory }]]), new Map([ diff --git a/src/core/barrel/barrel-file.generator.test.ts b/src/core/barrel/barrel-file.generator.test.ts index 2090ebd..cdb1880 100644 --- a/src/core/barrel/barrel-file.generator.test.ts +++ b/src/core/barrel/barrel-file.generator.test.ts @@ -112,6 +112,36 @@ describe('BarrelFileGenerator', () => { assert.strictEqual(exists, false); }); + it('should only recurse into subdirectories that already contain barrels when updating', async () => { + const generator = new BarrelFileGenerator(); + const rootUri = { fsPath: tmpDir } as unknown as Uri; + + const keepDir = path.join(tmpDir, 'keep'); + const skipDir = path.join(tmpDir, 'skip'); + + await fileSystem.ensureDirectory(keepDir); + await fileSystem.ensureDirectory(skipDir); + + await fileSystem.writeFile(path.join(keepDir, 'keep.ts'), 'export const keep = 1;'); + await fileSystem.writeFile(path.join(skipDir, 'skip.ts'), 'export const skip = 1;'); + + await fileSystem.writeFile(path.join(keepDir, INDEX_FILENAME), "export * from '../legacy';"); + + await generator.generateBarrelFile(rootUri, { + recursive: true, + mode: BarrelGenerationMode.UpdateExisting, + }); + + const keepIndex = await fileSystem.readFile(path.join(keepDir, INDEX_FILENAME)); + assert.strictEqual(keepIndex, ["export { keep } from './keep';", ''].join('\n')); + + const skipIndexExists = await fileSystem.fileExists(path.join(skipDir, INDEX_FILENAME)); + assert.strictEqual(skipIndexExists, false); + + const rootIndex = await fileSystem.readFile(path.join(tmpDir, INDEX_FILENAME)); + assert.strictEqual(rootIndex, ["export * from './keep';", ''].join('\n')); + }); + it('should throw when no TypeScript files are present and recursion is disabled', async () => { const generator = new BarrelFileGenerator(); const emptyDirUri = { fsPath: tmpDir } as unknown as Uri; diff --git a/src/core/parser/export.parser.test.ts b/src/core/parser/export.parser.test.ts index 6e444dd..356d6f3 100644 --- a/src/core/parser/export.parser.test.ts +++ b/src/core/parser/export.parser.test.ts @@ -76,5 +76,18 @@ describe('ExportParser', () => { ); }); } + + it('should merge duplicate exports and prefer value exports over type-only', () => { + const source = ` + export type { Hotel }; + export { Hotel }; + `; + + const exports = parser.extractExports(source); + const hotel = exports.find((entry) => entry.name === 'Hotel'); + + assert.ok(hotel); + assert.strictEqual(hotel.typeOnly, false); + }); }); });