Skip to content

Commit 2bc187e

Browse files
Thanatos9404alan-agius4
authored andcommitted
fix(@angular-devkit/schematics): include the template path in errors thrown by content templates
Rethrow errors thrown while applying a content template, such as a `ReferenceError` from a misspelled variable or a `SyntaxError` inside `<% %>`, as `Failed to apply template "<path>": <error>` and keep the original error as `cause`. The message did not say which template failed, and the ng CLI printed only `foobat is not defined`. Closes #13192
1 parent 92f757d commit 2bc187e

2 files changed

Lines changed: 43 additions & 5 deletions

File tree

‎packages/angular_devkit/schematics/src/rules/template.ts‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,16 @@ export function applyContentTemplate<T>(options: T): FileOperator {
7171
}
7272
decodedContent = decodedContent.replace(/\r?\n/g, EOL);
7373

74-
return {
75-
path,
76-
content: Buffer.from(templateImpl(decodedContent, {})(options)),
77-
};
74+
try {
75+
return {
76+
path,
77+
content: Buffer.from(templateImpl(decodedContent, {})(options)),
78+
};
79+
} catch (e) {
80+
const message = e instanceof Error ? e.message : String(e);
81+
82+
throw new Error(`Failed to apply content template to "${path}": ${message}`, { cause: e });
83+
}
7884
};
7985
}
8086

‎packages/angular_devkit/schematics/src/rules/template_spec.ts‎

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
/* eslint-disable import/no-extraneous-dependencies */
1010
import { normalize } from '@angular-devkit/core';
11-
import { of as observableOf } from 'rxjs';
11+
import { lastValueFrom, of as observableOf } from 'rxjs';
1212
import { UnitTestTree } from '../../testing';
1313
import { SchematicContext } from '../engine/interface';
1414
import { HostTree } from '../tree/host-tree';
@@ -183,6 +183,23 @@ describe('contentTemplate', () => {
183183

184184
expect(applyContentTemplate({})(entry)).toBe(entry);
185185
});
186+
187+
it('names the template in errors thrown while applying it', () => {
188+
const operator = applyContentTemplate({ foobar: 1 });
189+
190+
expect(() => operator(_entry('/a/b/file.ts', 'a<%= foobat %>b'))).toThrowError(
191+
'Failed to apply content template to "/a/b/file.ts": foobat is not defined',
192+
);
193+
expect(() => operator(_entry('/a/b/file.ts', 'a<%= foobar unintended %>b'))).toThrowError(
194+
/^Failed to apply content template to "\/a\/b\/file\.ts": Unexpected identifier/,
195+
);
196+
});
197+
198+
it('keeps the original template error as the cause', () => {
199+
expect(() =>
200+
applyContentTemplate({})(_entry('/a/b/file.ts', 'a<%= foobat %>b')),
201+
).toThrowMatching((e) => e.cause instanceof ReferenceError);
202+
});
186203
});
187204

188205
describe('applyTemplateFiles', () => {
@@ -212,4 +229,19 @@ describe('applyTemplateFiles', () => {
212229
]);
213230
expect(tree.readContent('/a/b/file3')).toBe('hello 1 world');
214231
});
232+
233+
it('names the template file in errors', async () => {
234+
const tree = new UnitTestTree(new HostTree());
235+
tree.create('a/b/file__a__.template', 'hello <%= a.missing.length %> world');
236+
237+
const context: SchematicContext = {
238+
strategy: MergeStrategy.Default,
239+
} as SchematicContext;
240+
241+
await expectAsync(
242+
lastValueFrom(callRule(applyTemplates({ a: 'foo' }), observableOf(tree), context)),
243+
).toBeRejectedWithError(
244+
/^Failed to apply content template to "\/a\/b\/file__a__\.template": Cannot read /,
245+
);
246+
});
215247
});

0 commit comments

Comments
 (0)