Skip to content

fix(@angular-devkit/schematics): include the template path in errors thrown by content templates - #34621

Open
Thanatos9404 wants to merge 4 commits into
angular:mainfrom
Thanatos9404:fix/schematics-template-errors
Open

Thanatos9404 wants to merge 4 commits into
angular:mainfrom
Thanatos9404:fix/schematics-template-errors

Conversation

@Thanatos9404

@Thanatos9404 Thanatos9404 commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

PR Checklist

Please check to confirm your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

What is the current behavior?

Errors thrown while applyContentTemplate renders a file don't say which file. With a misspelled variable, ng generate prints only foobat is not defined. A syntax error inside <% %> prints Unexpected identifier 'unintended'. In both cases the stack points only into @angular-devkit/core.

There is a related bug in the same try block. Since 18.1.0 (41ee8ea), the catch returns the entry unchanged for any TypeError, so that content that isn't valid UTF-8 is left alone. Because decoding and rendering share that try, a TypeError thrown by the template itself is also ignored, for example <%= name.length %> with name undefined. The schematic then succeeds and writes the unrendered template.

Issue Number: #13192

What is the new behavior?

The PR has two commits.

  1. Decode and render separately. Content that can't be decoded is still returned unchanged. A TypeError from the template is now reported, as it was before 18.1.0.
  2. Name the template. An error from rendering is rethrown with the template path, keeping the original error as cause, like HostTree's Failed to decode "<path>" error. With a template at files/sub/__name__.ts.template applied through url('./files'), the message is:
Failed to apply content template to "/sub/__name__.ts.template": foobat is not defined

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Tests. The specs in template_spec.ts cover:

  • a TypeError thrown by a template;
  • content that isn't valid UTF-8, which is still returned unchanged;
  • a ReferenceError and a SyntaxError, checking the message and the cause;
  • an applyTemplates case that checks the path in the message.

I ran them with jasmine and tsx, outside Bazel. Each commit's new specs fail without its change and pass with it.

Other suites. All 141 specs under packages/angular_devkit/schematics pass, except two FileSystemEngineHost specs that need a Bazel-built fixture; they fail the same way on main.

Built-in templates. Because template TypeErrors used to be ignored, I also ran the 930 @schematics/angular specs on main and on this branch. I generated the schema .ts files with tools/quicktype_runner.js first. The same 18 specs fail on both in this Windows setup, so none of the built-in templates relied on the old behaviour.

Checks. tsc 6.0.3 and prettier are clean for the changed files.

Side effects. A SchematicsException thrown on purpose by a template helper is now wrapped the same way. Its message still appears after the path, and the exception itself is kept as cause.

Not covered. The message doesn't include the line inside the template, because the template is compiled with Function() and its line numbers don't match the template's.

…ontent templates

Since 18.1.0 (41ee8ea), `applyContentTemplate` returns the entry
unchanged for any `TypeError`, so that content which is not valid
UTF-8 is left alone. Decoding and applying the template shared one
`try`, so a `TypeError` thrown by the template itself, such as
`<%= name.length %>` with `name` undefined, also left the file
unrendered without an error. Decode in a separate `try` so that only
decode failures are ignored.
…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 angular#13192

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the applyContentTemplate function in Angular DevKit Schematics to separate content decoding from template execution. Template execution is now wrapped in its own try-catch block, which throws a more descriptive error containing the template path and preserves the original error as the cause. Additionally, comprehensive unit tests have been added to verify this error handling behavior, including checking that template names are included in errors and that the original error is preserved. I have no feedback to provide as the changes are well-implemented and covered by tests.

@alan-agius4 alan-agius4 added the target: patch This PR is targeted for the next patch release label Nov 2, 2026
path,
content: Buffer.from(templateImpl(decodedContent, {})(options)),
};
decodedContent = decoder.decode(content).replace(/\r?\n/g, EOL);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move .replace(/\r?\n/g, EOL) outside of the try block (or pass it directly into templateImpl) so that this try / catch block only wraps decoder.decode(content).

content: Buffer.from(templateImpl(decodedContent, {})(options)),
};
} catch (e) {
throw new Error(`Failed to apply template "${path}": ${String(e)}`, { cause: e });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using String(e) embeds the error constructor prefix (such as Error: or ReferenceError:) into the outer error message, which results in duplicate prefixes like Error: Failed to apply template "...": Error: ... when the thrown error is formatted. Please use e instanceof Error ? e.message : String(e) instead while keeping { cause: e }. Also consider using Failed to process template "${path}" (or Failed to apply content template to "${path}") since applyContentTemplate can run on any file in the tree and is distinct from applyPathTemplate.

} as SchematicContext;

await expectAsync(
callRule(applyTemplates({ a: 'foo' }), observableOf(tree), context).toPromise(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toPromise() is deprecated in RxJS. Please use lastValueFrom from rxjs instead.

@Thanatos9404

Copy link
Copy Markdown
Contributor Author

Thanks for the review. All three are addressed in two fixup commits: the decode try now only wraps decoder.decode(content) and the line endings are normalized right after it, the error uses e.message (keeping cause) and reads Failed to apply content template to "<path>": <message>, and the spec uses lastValueFrom. The body of the second commit still quotes the old message format, so I can reword it if you'd like.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: @angular-devkit/schematics target: patch This PR is targeted for the next patch release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants