Smart Duplicate File
Duplicate files intelligently - automatically rename classes, functions, and identifiers to match the new filename. I guess that doesn't make it a duplicate anymore. But when do you ever need the same exact thing twice?
Features
Customizable prefixes/suffixes
FirstController → SecondController will replace not just the class name, but also all occurrences of First to Second
Smart word boundaries
Short names like Id won't accidentally match inside words like valid
Plural handling
Renaming Child → Person correctly transforms Children → People. Supports pluralization via conventions and many irregular forms (this does not mean complete coverage of English though)
Encoding preservation
Automatically detects and preserves file encoding (UTF-8, UTF-16, etc.) and BOM markers
Easily extensible test suite to verify behavior
Supported Naming Conventions
Automatically handles variants, even multiple conventions in the same file
- PascalCase
- camelCase
- snake_case
- kebab-case
- space separated
- Title Case
- SCREAMING_SNAKE_CASE
Examples
C# Class
Original: BankController.cs
using System.Collections.Generic;
public class Bank
{
public int Id { get; set; }
public string Name { get; set; }
}
public class BankController : IBaseController<Bank>
{
private readonly DbContext _context;
public BankController(DbContext context)
{
_context = context;
}
public Bank GetById(int id)
{
return _context.Set<Bank>().Find(id);
}
public IEnumerable<Bank> GetAll()
{
return _context.Set<Bank>().ToList();
}
public void Create(Bank bank)
{
_context.Set<Bank>().Add(bank);
_context.SaveChanges();
}
public void Update(Bank bank)
{
_context.Set<Bank>().Update(bank);
_context.SaveChanges();
}
public void Delete(Bank bank)
{
_context.Set<Bank>().Remove(bank);
_context.SaveChanges();
}
}
// BankController handles all bank-related operations
// Banks are stored in the database
After duplicating to ProductController.cs
using System.Collections.Generic;
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ProductController : IBaseController<Product>
{
private readonly DbContext _context;
public ProductController(DbContext context)
{
_context = context;
}
public Product GetById(int id)
{
return _context.Set<Product>().Find(id);
}
public IEnumerable<Product> GetAll()
{
return _context.Set<Product>().ToList();
}
public void Create(Product product)
{
_context.Set<Product>().Add(product);
_context.SaveChanges();
}
public void Update(Product product)
{
_context.Set<Product>().Update(product);
_context.SaveChanges();
}
public void Delete(Product product)
{
_context.Set<Product>().Remove(product);
_context.SaveChanges();
}
}
// ProductController handles all product-related operations
// Products are stored in the database
TypeScript React Component
Original: user-profile.tsx
export interface UserProfileProps {
userId: string;
}
export const UserProfile: React.FC<UserProfileProps> = ({ userId }) => {
return <div className="user-profile">Profile for {userId}</div>;
};
export default UserProfile;
After duplicating to admin-profile.tsx
export interface AdminProfileProps {
userId: string;
}
export const AdminProfile: React.FC<AdminProfileProps> = ({ userId }) => {
return <div className="admin-profile">Profile for {userId}</div>;
};
export default AdminProfile;
Intelligently converted:
user-profile (kebab-case) → admin-profile
UserProfile (PascalCase) → AdminProfile
UserProfileProps → AdminProfileProps
Python Class
Original: data_validator.py
class DataValidator:
def __init__(self):
self.name = "DataValidator"
def validate(self, data):
"""DataValidator implementation"""
return True
# creates the data validator
def create_data_validator():
return DataValidator()
After duplicating to schema_validator.py
class SchemaValidator:
def __init__(self):
self.name = "SchemaValidator"
def validate(self, data):
"""SchemaValidator implementation"""
return True
# creates the schema validator
def create_schema_validator():
return SchemaValidator()
All references including snake_case function names and comments updated automatically.
Usage
Quick Start
- Right-click on a file in the Explorer (or in the editor)
- Select Smart Duplicate (
Ctrl+K Ctrl+D)
- Enter the new filename
- Review the diff
Access Points
- Explorer context menu - Right-click any file
- Editor context menu - Right-click in an open file
- Command Palette - Search for "Smart Duplicate"
Settings
| Setting |
Default |
Description |
smartDuplicateFile.postDuplicationAction |
compareWithOriginal |
Action after duplicating: openFile, compareWithOriginal (diff view), or openSideBySide |
smartDuplicateFile.strippablePrefixes |
["use", "get", "set", "is", "has"] |
Prefixes to strip when replacing (e.g., useBankAccount → useCurrency replaces BankAccount with Currency) |
smartDuplicateFile.strippableSuffixes |
["Store", "Service", ...] |
Suffixes to strip when replacing (e.g., UserStore → ProductStore replaces User with Product) |
Access settings via Settings (Ctrl+,) → search "Smart Duplicate File".
Tips
- Use the default diff view to review changes before saving
- Update
strippablePrefixes and strippableSuffixes with ones common in your codebase
- Works best with consistent naming conventions
💡 A note on clean code: Many replacements this extension performs (like updating comments that repeat class names) wouldn't be necessary with cleaner code practices. Comments like // creates the user validator or """UserValidator implementation""" are often redundant - the code should be self-documenting. If you find yourself needing extensive comment replacements, consider whether they add value in the first place.
Contributing
Contributions are welcome! Here's how to get started:
Development Setup
git clone https://github.com/FreHu/vscode-duplicate-plus-plus.git
cd vscode-duplicate-plus-plus
npm install
Press F5 in VS Code to launch the Extension Development Host. This may show some errors because there are invalid .ts files used as test data. They can be ignored.
Running Tests
npm test
Test Structure
Each test is composed of an input/output file pair in src/test/fixtures/:
To add a new test case:
- Create
your-test-name.in.ext with the input content
- Create
your-test-name.out.ext with the expected output
- Add the test case to
src/test/extension.test.ts
Known Issues
None, but this simply can't cover everything. If you feel like you found
- a replacement which seems doable but currently isn't handled
- a replacement which shouldn't occur
Then open an issue and include a test case.
Release Notes
0.0.1
Initial release:
- Intelligent multi-convention filename replacement
- Configurable prefix/suffix stripping
- Diff view for reviewing changes
- Context menu integration (Explorer & Editor)