diff --git a/lib/util/edit.test.ts b/lib/util/edit.test.ts index fb2abab..34fe9d2 100644 --- a/lib/util/edit.test.ts +++ b/lib/util/edit.test.ts @@ -289,6 +289,53 @@ function a2() {}`, }); describe('class declaration', () => { + it('should not remove decorators when exports are deleted', () => { + const fileService = new MemoryFileService(); + + fileService.set('/app/main.ts', ``); + fileService.set( + '/app/a.ts', + `@myDecorator +export class A {}`, + ); + fileService.set( + '/app/b.ts', + `@myDecorator +export default class B {}`, + ); + fileService.set( + '/app/c.ts', + `@firstDecorator +@secondDecorator(() => [WithArgument]) +export default class C {}`, + ); + + edit({ + fileService, + recursive, + entrypoints: ['/app/main.ts'], + }); + + assert.equal( + fileService.get('/app/a.ts'), + `@myDecorator +class A {}`, + ); + + assert.equal( + fileService.get('/app/b.ts'), + `@myDecorator +class B {}`, + ); + + assert.equal( + fileService.get('/app/c.ts'), + `@firstDecorator +@secondDecorator(() => [WithArgument]) +class C {}`, + ); + }); + it('should not remove export for class if its used in some other file', () => { const fileService = new MemoryFileService(); diff --git a/lib/util/parseFile.ts b/lib/util/parseFile.ts index 7d2cce5..8b6430d 100644 --- a/lib/util/parseFile.ts +++ b/lib/util/parseFile.ts @@ -83,27 +83,47 @@ const getChange = ( }; } - // we want to correctly remove 'default' when its a default export so we get the syntaxList node instead of the exportKeyword node - // note: the first syntaxList node should contain the export keyword + /** + * The syntax list contains the keywords that can be found before the actual declaration. + * We want to remove everything that is not a decorator. + */ const syntaxListIndex = node .getChildren() .findIndex((n) => n.kind === ts.SyntaxKind.SyntaxList); const syntaxList = node.getChildren()[syntaxListIndex]; + + if (!syntaxList) { + throw new Error('syntaxList missing'); + } + + const firstKeywordToDeleteIndex = syntaxList + .getChildren() + .findIndex((n) => n.kind !== ts.SyntaxKind.Decorator); + + const firstKeywordToDelete = + syntaxList.getChildren()[firstKeywordToDeleteIndex]; + + if (!firstKeywordToDelete) { + throw new Error( + 'Unexpected syntax list when looking for keywords after decorators', + ); + } + const nextSibling = node.getChildren()[syntaxListIndex + 1]; - if (!syntaxList || !nextSibling) { - throw new Error('Unexpected syntax'); + if (!nextSibling) { + throw new Error('No sibling after syntax list'); } return { code: node .getSourceFile() .getFullText() - .slice(syntaxList.getStart(), nextSibling.getStart()), + .slice(firstKeywordToDelete.getStart(), nextSibling.getStart()), span: { - start: syntaxList.getStart(), - length: nextSibling.getStart() - syntaxList.getStart(), + start: firstKeywordToDelete.getStart(), + length: nextSibling.getStart() - firstKeywordToDelete.getStart(), }, }; };