Skip to content

Commit c8f2702

Browse files
committed
fix: preserve re-exports when all imports removed, scope findFiles to workspace folder
- Re-exports adjacent to imports are now preserved when all imports are removed as unused (else branch in generateTextEdits now outputs re-exports) - findTargetFilesInFolder uses RelativePattern to scope search to the correct workspace folder in multi-root workspaces - README: fix default grouping (was missing "Plains") - CLAUDE.md: fix test file listing and blankLinesAfterImports description
1 parent 136842e commit c8f2702

5 files changed

Lines changed: 69 additions & 12 deletions

File tree

CLAUDE.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,19 @@ mini-typescript-hero/ ← Project root
170170
171171
├── tests/ ← All test-related folders
172172
│ ├── unit/ ← Main extension tests (run with npm test)
173-
│ │ ├── import-manager.test.ts ← Core import manager tests
174-
│ │ ├── import-manager.*.test.ts ← Additional import manager tests
173+
│ │ ├── import-manager.test.ts ← Core import manager tests (~100 tests)
174+
│ │ ├── import-manager.*.test.ts ← Additional: blank-lines, edge-cases, indentation, etc.
175175
│ │ ├── import-grouping.test.ts ← Grouping logic tests
176-
│ │ ├── settings-migration.test.ts ← Migration tests
177-
│ │ └── test-helpers.ts ← Shared test utilities
176+
│ │ ├── import-organizer.test.ts ← Orchestrator/command tests
177+
│ │ ├── import-utilities.test.ts ← Sorting utility tests
178+
│ │ ├── configuration/ ← Config-related tests
179+
│ │ │ ├── settings-migration.test.ts ← Migration tests
180+
│ │ │ └── conflict-detector.test.ts ← Conflict detection tests
181+
│ │ ├── commands/
182+
│ │ │ └── batch-organizer.integration.test.ts ← Batch operation tests
183+
│ │ ├── test-helpers.ts ← Shared test utilities
184+
│ │ ├── test-types.ts ← Test type definitions
185+
│ │ └── *.test.ts ← Additional: manifest, perf, vscode defaults, etc.
178186
│ ├── comparison/ ← Old vs new comparison tests
179187
│ │ ├── old-extension/adapter.ts ← Adapter for old TypeScript Hero
180188
│ │ ├── new-extension/adapter.ts ← Adapter for new Mini TypeScript Hero
@@ -388,7 +396,7 @@ All settings are under `miniTypescriptHero.imports.*`:
388396
16. `excludePatterns` (string[]) - Glob patterns for files to exclude from import organization
389397

390398
### Blank Lines
391-
17. `blankLinesAfterImports` (one/two/preserve) - How many blank lines after imports (Note: Partially overridden in legacyMode for files with headers or leading blanks; otherwise respected)
399+
17. `blankLinesAfterImports` (one/two/preserve) - How many blank lines after imports (Note: This setting has no effect when legacyMode is true — legacy mode always uses 'preserve' behavior with special handling for headers and leading blanks)
392400

393401
### Behavior & Compatibility
394402
18. `organizeOnSave` (boolean) - Automatically organize imports when saving files

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ VS Code has a built-in "Organize Imports" that removes unused imports and sorts
115115

116116
### Example: Automatic vs Manual Grouping
117117

118-
**Mini TypeScript Hero** with default grouping (`["Modules", "Workspace"]`):
118+
**Mini TypeScript Hero** with default grouping (`["Plains", "Modules", "Workspace"]`):
119119

120120
```typescript
121121
import { Component, OnInit } from '@angular/core';

src/commands/batch-organizer.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
OutputChannel,
44
Progress,
55
ProgressLocation,
6+
RelativePattern,
67
TextEdit,
78
Uri,
89
window,
@@ -165,15 +166,17 @@ export class BatchOrganizer {
165166
.substring(workspaceFolder.uri.fsPath.length + 1)
166167
.replace(/\\/g, '/');
167168
// Handle case where folder IS the workspace root (relative path is empty)
168-
const include = relativePath
169+
const includeGlob = relativePath
169170
? `${relativePath}/**/*.{ts,tsx,js,jsx}`
170171
: '**/*.{ts,tsx,js,jsx}';
171-
const excludePatterns = this.getExcludePatterns(folderUri);
172+
const excludePatterns = this.getExcludePatterns(workspaceFolder.uri);
172173

173-
this.logger.appendLine(`[BatchOrganizer] Searching folder: ${include}`);
174+
this.logger.appendLine(`[BatchOrganizer] Searching folder: ${includeGlob}`);
174175
this.logger.appendLine(`[BatchOrganizer] Excluding: ${excludePatterns.join(', ')}`);
175176

176-
// Find all files (VS Code respects files.exclude by default)
177+
// Use RelativePattern to scope findFiles to the specific workspace folder
178+
// (plain string patterns search across ALL workspace roots in multi-root workspaces)
179+
const include = new RelativePattern(workspaceFolder, includeGlob);
177180
const allFiles = await workspace.findFiles(include, null);
178181

179182
// Manually filter files using exclude patterns

src/imports/import-manager.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -931,8 +931,27 @@ export class ImportManager {
931931
this.document.lineAt(importSectionEndLine).rangeIncludingLineBreak.end,
932932
);
933933
edits.push(TextEdit.replace(replaceRange, importText));
934+
} else if (this.reExports.length > 0 || commentsBetweenImports.length > 0) {
935+
// No imports, but re-exports and/or comments need to be preserved
936+
let replacementText = '';
937+
if (commentsBetweenImports.length > 0) {
938+
replacementText += commentsBetweenImports.join(this.eol);
939+
replacementText += this.eol;
940+
}
941+
if (this.reExports.length > 0) {
942+
replacementText += this.reExports.join(this.eol);
943+
replacementText += this.eol;
944+
}
945+
if (finalBlankLinesAfter > 0) {
946+
replacementText += this.eol.repeat(finalBlankLinesAfter);
947+
}
948+
const replaceRange = new Range(
949+
this.document.lineAt(importSectionStartLine).range.start,
950+
this.document.lineAt(importSectionEndLine).rangeIncludingLineBreak.end,
951+
);
952+
edits.push(TextEdit.replace(replaceRange, replacementText));
934953
} else {
935-
// No imports to insert, just delete the old import section
954+
// Truly nothing to keep — delete the old import section
936955
const deleteRange = new Range(
937956
this.document.lineAt(importSectionStartLine).range.start,
938957
this.document.lineAt(importSectionEndLine).rangeIncludingLineBreak.end,

tests/unit/import-manager.test.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3280,7 +3280,34 @@ export { Helper } from './helpers';
32803280
}
32813281
});
32823282

3283-
test('101. ignoredFromRemoval libraries have sorted specifiers', async () => {
3283+
test('101. Adjacent re-exports preserved when all imports are removed as unused', async () => {
3284+
// Bug: When all imports are unused but adjacent re-exports exist,
3285+
// the else branch in generateTextEdits() deletes the entire range
3286+
// without preserving re-exports.
3287+
const content = `import { A } from './a';
3288+
export { X } from './x';
3289+
3290+
const y = 1;
3291+
`;
3292+
3293+
const expected = `export { X } from './x';
3294+
3295+
const y = 1;
3296+
`;
3297+
3298+
const doc = await createTempDocument(content);
3299+
try {
3300+
const manager = new ImportManager(doc, config);
3301+
const edits = await manager.organizeImports();
3302+
const result = await applyEditsToDocument(doc, edits);
3303+
3304+
assert.strictEqual(result, expected, 'Adjacent re-exports must be preserved when all imports are removed');
3305+
} finally {
3306+
await deleteTempDocument(doc);
3307+
}
3308+
});
3309+
3310+
test('102. ignoredFromRemoval libraries have sorted specifiers', async () => {
32843311
// Verify that libraries in ignoredFromRemoval list still get their specifiers sorted
32853312
// This ensures consistent formatting even when imports are protected from removal
32863313

0 commit comments

Comments
 (0)