Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions packages/angular_devkit/schematics/src/rules/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,9 @@ export function applyContentTemplate<T>(options: T): FileOperator {
return (entry: FileEntry) => {
const { path, content } = entry;

let decodedContent: string;
try {
const decodedContent = decoder.decode(content).replace(/\r?\n/g, EOL);

return {
path,
content: Buffer.from(templateImpl(decodedContent, {})(options)),
};
decodedContent = decoder.decode(content);
} catch (e) {
// The second part should not be needed. But Jest does not support instanceof correctly.
// See: https://github.com/jestjs/jest/issues/2549
Expand All @@ -73,6 +69,18 @@ export function applyContentTemplate<T>(options: T): FileOperator {

throw e;
}
decodedContent = decodedContent.replace(/\r?\n/g, EOL);

try {
return {
path,
content: Buffer.from(templateImpl(decodedContent, {})(options)),
};
} catch (e) {
const message = e instanceof Error ? e.message : String(e);

throw new Error(`Failed to apply content template to "${path}": ${message}`, { cause: e });
}
};
}

Expand Down
46 changes: 45 additions & 1 deletion packages/angular_devkit/schematics/src/rules/template_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

/* eslint-disable import/no-extraneous-dependencies */
import { normalize } from '@angular-devkit/core';
import { of as observableOf } from 'rxjs';
import { lastValueFrom, of as observableOf } from 'rxjs';
import { UnitTestTree } from '../../testing';
import { SchematicContext } from '../engine/interface';
import { HostTree } from '../tree/host-tree';
Expand Down Expand Up @@ -171,6 +171,35 @@ describe('contentTemplate', () => {
expect(_applyContentTemplate('a<%= \'a\' + "b" %>b', {})).toBe('aabb');
expect(_applyContentTemplate('a<%= "\\n" + "b" %>b', {})).toBe('a\nbb');
});

it('does not ignore type errors thrown by a template', () => {
expect(() => _applyContentTemplate('a<%= value.length %>b', { value: undefined })).toThrowError(
/Cannot read properties of undefined/,
);
});

it('leaves content that is not valid UTF-8 unchanged', () => {
const entry = { path: normalize('/a/b/image.png'), content: Buffer.from([0xff, 0xfe, 0xfd]) };

expect(applyContentTemplate({})(entry)).toBe(entry);
});

it('names the template in errors thrown while applying it', () => {
const operator = applyContentTemplate({ foobar: 1 });

expect(() => operator(_entry('/a/b/file.ts', 'a<%= foobat %>b'))).toThrowError(
'Failed to apply content template to "/a/b/file.ts": foobat is not defined',
);
expect(() => operator(_entry('/a/b/file.ts', 'a<%= foobar unintended %>b'))).toThrowError(
/^Failed to apply content template to "\/a\/b\/file\.ts": Unexpected identifier/,
);
});

it('keeps the original template error as the cause', () => {
expect(() =>
applyContentTemplate({})(_entry('/a/b/file.ts', 'a<%= foobat %>b')),
).toThrowMatching((e) => e.cause instanceof ReferenceError);
});
});

describe('applyTemplateFiles', () => {
Expand Down Expand Up @@ -200,4 +229,19 @@ describe('applyTemplateFiles', () => {
]);
expect(tree.readContent('/a/b/file3')).toBe('hello 1 world');
});

it('names the template file in errors', async () => {
const tree = new UnitTestTree(new HostTree());
tree.create('a/b/file__a__.template', 'hello <%= a.missing.length %> world');

const context: SchematicContext = {
strategy: MergeStrategy.Default,
} as SchematicContext;

await expectAsync(
lastValueFrom(callRule(applyTemplates({ a: 'foo' }), observableOf(tree), context)),
).toBeRejectedWithError(
/^Failed to apply content template to "\/a\/b\/file__a__\.template": Cannot read /,
);
});
});
Loading