Skip to content

Commit 3374424

Browse files
leonsenftatscott
authored andcommitted
refactor(compiler-cli): disallow @content (children) in favor of implicit children
Defining a `@content (children)` block explicitly is unnecessary because children should always be passed implicitly as direct nested content of the foreign component. Using an explicit block could also lead to conflicts and silent template rendering issues where implicit content (like whitespace) accidentally overwrote the explicit block in the compiler's template representation. This change introduces a compilation error (`FOREIGN_COMPONENT_CONTENT_UNNECESSARY_FOR_CHILDREN`) when an explicit `@content (children)` block is detected, guiding developers to pass children implicitly instead.
1 parent 1f6e843 commit 3374424

4 files changed

Lines changed: 47 additions & 1 deletion

File tree

goldens/public-api/compiler-cli/error_code.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export enum ErrorCode {
5555
DUPLICATE_DECORATED_PROPERTIES = 1012,
5656
DUPLICATE_VARIABLE_DECLARATION = 8006,
5757
FORBIDDEN_REQUIRED_INITIALIZER_INVOCATION = 8118,
58+
FOREIGN_COMPONENT_CONTENT_UNNECESSARY_FOR_CHILDREN = 8027,
5859
FOREIGN_COMPONENT_UNSUPPORTED_BINDING = 8025,
5960
FORM_FIELD_UNSUPPORTED_BINDING = 8022,
6061
HOST_BINDING_PARSE_ERROR = 5001,

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,21 @@ class ForeignComponentFeatureAnalyzer extends TmplAstRecursiveVisitor {
206206
}
207207

208208
override visitContentBlock(block: TmplAstContentBlock): void {
209-
if (!this.parentNodeIsForeignComponent()) {
209+
if (this.parentNodeIsForeignComponent()) {
210+
if (block.name === 'children') {
211+
this.diagnostics.push(
212+
makeTemplateDiagnostic(
213+
'' as TypeCheckId,
214+
this.sourceMapping,
215+
block.sourceSpan,
216+
ts.DiagnosticCategory.Error,
217+
ngErrorCode(ErrorCode.FOREIGN_COMPONENT_CONTENT_UNNECESSARY_FOR_CHILDREN),
218+
'Defining a @content (children) block is unnecessary. ' +
219+
'Pass children as direct nested content of the foreign component instead.',
220+
),
221+
);
222+
}
223+
} else {
210224
this.diagnostics.push(
211225
makeTemplateDiagnostic(
212226
'' as TypeCheckId,

packages/compiler-cli/src/ngtsc/diagnostics/src/error_code.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,12 @@ export enum ErrorCode {
469469
*/
470470
INVALID_CONTENT_PLACEMENT = 8026,
471471

472+
/**
473+
* Raised when a `@content` block is named 'children', which is unnecessary because children should be passed
474+
* implicitly.
475+
*/
476+
FOREIGN_COMPONENT_CONTENT_UNNECESSARY_FOR_CHILDREN = 8027,
477+
472478
/**
473479
* A two way binding in a template has an incorrect syntax,
474480
* parentheses outside brackets. For example:

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2404,6 +2404,31 @@ runInEachFileSystem(() => {
24042404
'@content blocks are only valid as direct children of foreign components.',
24052405
);
24062406
});
2407+
2408+
it('should detect unnecessary @content (children) block on a foreign component', () => {
2409+
env.write(
2410+
'test.ts',
2411+
`
2412+
${foreignSetupCode}
2413+
2414+
@Component({
2415+
selector: 'test',
2416+
template: '<FancyButton> @content (children) {} </FancyButton>',
2417+
foreignImports: [frameworkImport(FancyButton)],
2418+
})
2419+
export class TestCmp {}
2420+
`,
2421+
);
2422+
const diags = env.driveDiagnostics();
2423+
expect(diags.length).toEqual(1);
2424+
expect(diags[0].code).toEqual(
2425+
ngErrorCode(ErrorCode.FOREIGN_COMPONENT_CONTENT_UNNECESSARY_FOR_CHILDREN),
2426+
);
2427+
expect(diags[0].messageText).toEqual(
2428+
'Defining a @content (children) block is unnecessary. ' +
2429+
'Pass children as direct nested content of the foreign component instead.',
2430+
);
2431+
});
24072432
});
24082433

24092434
it('should detect a duplicate variable declaration', () => {

0 commit comments

Comments
 (0)