Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
ParsedHostBindings,
ParseError,
parseHostBindings,
ParserError,
R3DirectiveMetadata,
R3HostDirectiveMetadata,
R3InputMetadata,
Expand Down Expand Up @@ -1629,17 +1630,40 @@ function evaluateHostExpressionBindings(
const errors = verifyHostBindings(bindings, createSourceSpan(hostExpr));
if (errors.length > 0) {
throw new FatalDiagnosticError(
// TODO: provide more granular diagnostic and output specific host expression that
// triggered an error instead of the whole host object.
ErrorCode.HOST_BINDING_PARSE_ERROR,
hostExpr,
getHostBindingErrorNode(errors[0], hostExpr),
errors.map((error: ParseError) => error.msg).join('\n'),
);
}

return bindings;
}

/**
* Attempts to match a parser error to the host binding expression that caused it.
* @param error Error to match.
* @param hostExpr Expression declaring the host bindings.
*/
function getHostBindingErrorNode(error: ParseError, hostExpr: ts.Expression): ts.Node {
// In the most common case the `host` object is an object literal with string values. We can
// confidently match the error to its expression by looking at the string value that the parser
// failed to parse and the initializers for each of the properties. If we fail to match, we fall
// back to the old behavior where the error is reported on the entire `host` object.
if (ts.isObjectLiteralExpression(hostExpr) && error.relatedError instanceof ParserError) {
for (const prop of hostExpr.properties) {
if (
ts.isPropertyAssignment(prop) &&
ts.isStringLiteralLike(prop.initializer) &&
prop.initializer.text === error.relatedError.input
) {
return prop.initializer;
}
}
}

return hostExpr;
}

/**
* Extracts and prepares the host directives metadata from an array literal expression.
* @param rawHostDirectives Expression that defined the `hostDirectives`.
Expand Down
10 changes: 5 additions & 5 deletions packages/compiler-cli/test/ngtsc/ngtsc_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5114,9 +5114,7 @@ runInEachFileSystem((os: string) => {
);

const errors = env.driveDiagnostics();
expect(getDiagnosticSourceCode(errors[0])).toBe(`{
'(click)': 'act() | pipe',
}`);
expect(getDiagnosticSourceCode(errors[0])).toBe(`'act() | pipe'`);
expect(errors[0].messageText).toContain('/test.ts@7:17');
});

Expand Down Expand Up @@ -5158,10 +5156,12 @@ runInEachFileSystem((os: string) => {
class FooCmp {}
`,
);
const errors = env.driveDiagnostics();
expect(trim(errors[0].messageText as string)).toContain(
const diags = env.driveDiagnostics();
expect(diags.length).toBe(1);
expect(trim(diags[0].messageText as string)).toContain(
'Host binding expression cannot contain pipes',
);
expect(getDiagnosticSourceCode(diags[0])).toBe(`'id | myPipe'`);
});

it('should generate host bindings for directives', () => {
Expand Down
14 changes: 11 additions & 3 deletions packages/compiler/src/parse_util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,17 @@ export enum ParseErrorLevel {

export class ParseError {
constructor(
public span: ParseSourceSpan,
public msg: string,
public level: ParseErrorLevel = ParseErrorLevel.ERROR,
/** Location of the error. */
readonly span: ParseSourceSpan,
/** Error message. */
readonly msg: string,
/** Severity level of the error. */
readonly level: ParseErrorLevel = ParseErrorLevel.ERROR,
/**
* Error that caused the error to be surfaced. For example, an error in a sub-expression that
* couldn't be parsed. Not guaranteed to be defined, but can be used to provide more context.
*/
readonly relatedError?: unknown,
) {}

contextualMessage(): string {
Expand Down
5 changes: 3 additions & 2 deletions packages/compiler/src/template_parser/binding_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -770,13 +770,14 @@ export class BindingParser {
message: string,
sourceSpan: ParseSourceSpan,
level: ParseErrorLevel = ParseErrorLevel.ERROR,
relatedError?: ParserError,
) {
this.errors.push(new ParseError(sourceSpan, message, level));
this.errors.push(new ParseError(sourceSpan, message, level, relatedError));
}

private _reportExpressionParserErrors(errors: ParserError[], sourceSpan: ParseSourceSpan) {
for (const error of errors) {
this._reportError(error.message, sourceSpan);
this._reportError(error.message, sourceSpan, undefined, error);
}
}

Expand Down