Skip to content

Commit 9e21582

Browse files
pmvaldthePunderWoman
authored andcommitted
fix(compiler-cli): Show template syntax errors in local compilation modified (#55855)
Currently the template syntax errors are extracted in the template type check phase. But in local compilation mode we skip the type check phase. As a result template syntax errors are not displayed. With this change we show the template syntax diagnostics in local mode. PR Close #55855
1 parent 9d034f6 commit 9e21582

5 files changed

Lines changed: 111 additions & 34 deletions

File tree

packages/compiler-cli/src/ngtsc/annotations/component/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ ts_library(
2525
"//packages/compiler-cli/src/ngtsc/reflection",
2626
"//packages/compiler-cli/src/ngtsc/scope",
2727
"//packages/compiler-cli/src/ngtsc/transform",
28+
"//packages/compiler-cli/src/ngtsc/typecheck",
2829
"//packages/compiler-cli/src/ngtsc/typecheck/api",
2930
"//packages/compiler-cli/src/ngtsc/typecheck/extended/api",
3031
"//packages/compiler-cli/src/ngtsc/typecheck/template_semantics/api",

packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ import {
114114
HandlerPrecedence,
115115
ResolveResult,
116116
} from '../../../transform';
117-
import {TypeCheckableDirectiveMeta, TypeCheckContext} from '../../../typecheck/api';
117+
import {TemplateId, TypeCheckableDirectiveMeta, TypeCheckContext} from '../../../typecheck/api';
118118
import {ExtendedTemplateChecker} from '../../../typecheck/extended/api';
119119
import {TemplateSemanticsChecker} from '../../../typecheck/template_semantics/api/api';
120120
import {getSourceFile} from '../../../util/src/typescript';
@@ -176,6 +176,7 @@ import {
176176
collectAnimationNames,
177177
validateAndFlattenComponentImports,
178178
} from './util';
179+
import {getTemplateDiagnostics} from '../../../typecheck';
179180

180181
const EMPTY_ARRAY: any[] = [];
181182

@@ -624,6 +625,25 @@ export class ComponentDecoratorHandler
624625
},
625626
this.compilationMode,
626627
);
628+
629+
if (
630+
this.compilationMode === CompilationMode.LOCAL &&
631+
template.errors &&
632+
template.errors.length > 0
633+
) {
634+
// Template errors are handled at the type check phase. But we skip this phase in local compilation mode. As a result we need to handle the errors now and add them to the diagnostics.
635+
if (diagnostics === undefined) {
636+
diagnostics = [];
637+
}
638+
639+
diagnostics.push(
640+
...getTemplateDiagnostics(
641+
template.errors,
642+
'' as TemplateId, // Template ID is required as part of the template type check, mainly for mapping the template to its component class. But here we are generating the diagnostic outside of the type check context, and so we skip the template ID.
643+
template.sourceMapping,
644+
),
645+
);
646+
}
627647
}
628648
const templateResource = template.declaration.isInline
629649
? {path: null, expression: component.get('template')!}
@@ -1578,10 +1598,6 @@ export class ComponentDecoratorHandler
15781598
resolution: Readonly<Partial<ComponentResolutionData>>,
15791599
pool: ConstantPool,
15801600
): CompileResult[] {
1581-
if (analysis.template.errors !== null && analysis.template.errors.length > 0) {
1582-
return [];
1583-
}
1584-
15851601
// In the local compilation mode we can only rely on the information available
15861602
// within the `@Component.deferredImports` array, because in this mode compiler
15871603
// doesn't have information on which dependencies belong to which defer blocks.

packages/compiler-cli/src/ngtsc/typecheck/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
*/
88

99
export {FileTypeCheckingData, TemplateTypeCheckerImpl} from './src/checker';
10-
export {TypeCheckContextImpl} from './src/context';
10+
export {TypeCheckContextImpl, getTemplateDiagnostics} from './src/context';
1111
export {TypeCheckShimGenerator} from './src/shim';
1212
export {typeCheckFilePath} from './src/type_check_file';

packages/compiler-cli/src/ngtsc/typecheck/src/context.ts

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
250250
const templateDiagnostics: TemplateDiagnostic[] = [];
251251

252252
if (parseErrors !== null) {
253-
templateDiagnostics.push(
254-
...this.getTemplateDiagnostics(parseErrors, templateId, sourceMapping),
255-
);
253+
templateDiagnostics.push(...getTemplateDiagnostics(parseErrors, templateId, sourceMapping));
256254
}
257255

258256
const boundTarget = binder.bind({template});
@@ -559,33 +557,33 @@ export class TypeCheckContextImpl implements TypeCheckContext {
559557

560558
return this.fileMap.get(sfPath)!;
561559
}
560+
}
562561

563-
private getTemplateDiagnostics(
564-
parseErrors: ParseError[],
565-
templateId: TemplateId,
566-
sourceMapping: TemplateSourceMapping,
567-
): TemplateDiagnostic[] {
568-
return parseErrors.map((error) => {
569-
const span = error.span;
570-
571-
if (span.start.offset === span.end.offset) {
572-
// Template errors can contain zero-length spans, if the error occurs at a single point.
573-
// However, TypeScript does not handle displaying a zero-length diagnostic very well, so
574-
// increase the ending offset by 1 for such errors, to ensure the position is shown in the
575-
// diagnostic.
576-
span.end.offset++;
577-
}
562+
export function getTemplateDiagnostics(
563+
parseErrors: ParseError[],
564+
templateId: TemplateId,
565+
sourceMapping: TemplateSourceMapping,
566+
): TemplateDiagnostic[] {
567+
return parseErrors.map((error) => {
568+
const span = error.span;
569+
570+
if (span.start.offset === span.end.offset) {
571+
// Template errors can contain zero-length spans, if the error occurs at a single point.
572+
// However, TypeScript does not handle displaying a zero-length diagnostic very well, so
573+
// increase the ending offset by 1 for such errors, to ensure the position is shown in the
574+
// diagnostic.
575+
span.end.offset++;
576+
}
578577

579-
return makeTemplateDiagnostic(
580-
templateId,
581-
sourceMapping,
582-
span,
583-
ts.DiagnosticCategory.Error,
584-
ngErrorCode(ErrorCode.TEMPLATE_PARSE_ERROR),
585-
error.msg,
586-
);
587-
});
588-
}
578+
return makeTemplateDiagnostic(
579+
templateId,
580+
sourceMapping,
581+
span,
582+
ts.DiagnosticCategory.Error,
583+
ngErrorCode(ErrorCode.TEMPLATE_PARSE_ERROR),
584+
error.msg,
585+
);
586+
});
589587
}
590588

591589
/**

packages/compiler-cli/test/ngtsc/local_compilation_spec.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2623,5 +2623,67 @@ runInEachFileSystem(() => {
26232623
);
26242624
});
26252625
});
2626+
2627+
describe('template diagnostics', () => {
2628+
it('should show correct error message for syntatic template errors - case of inline template', () => {
2629+
env.write(
2630+
'test.ts',
2631+
`
2632+
import {Component} from '@angular/core';
2633+
2634+
@Component({
2635+
template: '<span Hello! </span>',
2636+
})
2637+
export class Main {
2638+
}
2639+
`,
2640+
);
2641+
2642+
const errors = env.driveDiagnostics();
2643+
2644+
expect(errors.length).toBeGreaterThanOrEqual(1);
2645+
2646+
const {code, messageText} = errors[0];
2647+
2648+
expect(code).toBe(ngErrorCode(ErrorCode.TEMPLATE_PARSE_ERROR));
2649+
2650+
const text = ts.flattenDiagnosticMessageText(messageText, '\n');
2651+
2652+
expect(text).toContain('Opening tag "span" not terminated');
2653+
});
2654+
2655+
it('should show correct error message for syntatic template errors - case of external template', () => {
2656+
env.write(
2657+
'test.ts',
2658+
`
2659+
import {Component} from '@angular/core';
2660+
2661+
@Component({
2662+
templateUrl: 'test.ng.html',
2663+
})
2664+
export class Main {
2665+
}
2666+
`,
2667+
);
2668+
env.write(
2669+
'test.ng.html',
2670+
`
2671+
<span Hello! </span>
2672+
`,
2673+
);
2674+
2675+
const errors = env.driveDiagnostics();
2676+
2677+
expect(errors.length).toBeGreaterThanOrEqual(1);
2678+
2679+
const {code, messageText} = errors[0];
2680+
2681+
expect(code).toBe(ngErrorCode(ErrorCode.TEMPLATE_PARSE_ERROR));
2682+
2683+
const text = ts.flattenDiagnosticMessageText(messageText, '\n');
2684+
2685+
expect(text).toContain('Opening tag "span" not terminated');
2686+
});
2687+
});
26262688
});
26272689
});

0 commit comments

Comments
 (0)