diff --git a/packages/compiler-cli/ngcc/src/rendering/esm_rendering_formatter.ts b/packages/compiler-cli/ngcc/src/rendering/esm_rendering_formatter.ts index 8a2e4786d6..1d20382882 100644 --- a/packages/compiler-cli/ngcc/src/rendering/esm_rendering_formatter.ts +++ b/packages/compiler-cli/ngcc/src/rendering/esm_rendering_formatter.ts @@ -99,9 +99,17 @@ export class EsmRenderingFormatter implements RenderingFormatter { } else { nodesToRemove.forEach(node => { // remove any trailing comma - const end = (output.slice(node.getEnd(), node.getEnd() + 1) === ',') ? - node.getEnd() + 1 : - node.getEnd(); + const nextSibling = getNextSiblingInArray(node, items); + let end: number; + + if (nextSibling !== null && + output.slice(nextSibling.getFullStart() - 1, nextSibling.getFullStart()) === ',') { + end = nextSibling.getFullStart() - 1 + nextSibling.getLeadingTriviaWidth(); + } else if (output.slice(node.getEnd(), node.getEnd() + 1) === ',') { + end = node.getEnd() + 1; + } else { + end = node.getEnd(); + } output.remove(node.getFullStart(), end); }); } @@ -214,3 +222,8 @@ function generateImportString( const importAs = importPath ? importManager.generateNamedImport(importPath, importName) : null; return importAs ? `${importAs.moduleImport}.${importAs.symbol}` : `${importName}`; } + +function getNextSiblingInArray(node: T, array: ts.NodeArray): T|null { + const index = array.indexOf(node); + return index !== -1 && array.length > index + 1 ? array[index + 1] : null; +} diff --git a/packages/compiler-cli/ngcc/test/rendering/esm_rendering_formatter_spec.ts b/packages/compiler-cli/ngcc/test/rendering/esm_rendering_formatter_spec.ts index 960e857531..bda5e1da02 100644 --- a/packages/compiler-cli/ngcc/test/rendering/esm_rendering_formatter_spec.ts +++ b/packages/compiler-cli/ngcc/test/rendering/esm_rendering_formatter_spec.ts @@ -288,6 +288,28 @@ A.decorators = [ .toContain(`{ type: Directive, args: [{ selector: '[c]' }] }`); }); + it('should handle a decorator with a trailing comment', () => { + const text = ` +import {Directive} from '@angular/core'; +export class A {} +A.decorators = [ + { type: Directive, args: [{ selector: '[a]' }] }, + { type: OtherA } +]; + `; + const file = {name: _('/node_modules/test-package/index.js'), contents: text}; + const {decorationAnalyses, sourceFile, renderer} = setup([file]); + const output = new MagicString(text); + const compiledClass = + decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !; + const decorator = compiledClass.decorators ![0]; + const decoratorsToRemove = new Map(); + decoratorsToRemove.set(decorator.node.parent !, [decorator.node]); + renderer.removeDecorators(output, decoratorsToRemove); + // The decorator should have been removed correctly. + expect(output.toString()).toContain('A.decorators = [ { type: OtherA }'); + }); + it('should delete the decorator (and its container if there are no other decorators left) that was matched in the analysis', () => {