Skip to content

Commit 06fa029

Browse files
crisbetothePunderWoman
authored andcommitted
refactor(compiler-cli): add jit transform for model inputs (angular#54252)
Adds a JIT transform that marks `model` fields as `@Input` and `@Output`. PR Close angular#54252
1 parent ab0c0cb commit 06fa029

5 files changed

Lines changed: 475 additions & 244 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import ts from 'typescript';
10+
11+
import {isAngularDecorator, tryParseSignalModelMapping} from '../../../ngtsc/annotations';
12+
import {ImportManager} from '../../../ngtsc/translator';
13+
14+
import {PropertyTransform} from './transform_api';
15+
16+
/**
17+
* Transform that automatically adds `@Input` and `@Output` to members initialized as `model()`.
18+
* It is useful for JIT environments where models can't be recognized based on the initializer.
19+
*/
20+
export const signalModelTransform: PropertyTransform = (
21+
member,
22+
host,
23+
factory,
24+
importManager,
25+
decorator,
26+
isCore,
27+
) => {
28+
if (host.getDecoratorsOfDeclaration(member)?.some(d => {
29+
return isAngularDecorator(d, 'Input', isCore) || isAngularDecorator(d, 'Output', isCore);
30+
})) {
31+
return member;
32+
}
33+
34+
const modelMapping = tryParseSignalModelMapping(
35+
{name: member.name.text, value: member.initializer ?? null},
36+
host,
37+
isCore,
38+
);
39+
40+
if (modelMapping === null) {
41+
return member;
42+
}
43+
44+
const classDecoratorIdentifier = ts.isIdentifier(decorator.identifier) ?
45+
decorator.identifier :
46+
decorator.identifier.expression;
47+
48+
const inputConfig = factory.createObjectLiteralExpression([
49+
factory.createPropertyAssignment(
50+
'isSignal', modelMapping.input.isSignal ? factory.createTrue() : factory.createFalse()),
51+
factory.createPropertyAssignment(
52+
'alias', factory.createStringLiteral(modelMapping.input.bindingPropertyName)),
53+
factory.createPropertyAssignment(
54+
'required', modelMapping.input.required ? factory.createTrue() : factory.createFalse()),
55+
]);
56+
57+
const inputDecorator = createDecorator(
58+
'Input',
59+
// Config is cast to `any` because `isSignal` will be private, and in case this
60+
// transform is used directly as a pre-compilation step, the decorator should
61+
// not fail. It is already validated now due to us parsing the input metadata.
62+
factory.createAsExpression(
63+
inputConfig, factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)),
64+
classDecoratorIdentifier, factory, importManager);
65+
66+
const outputDecorator = createDecorator(
67+
'Output', factory.createStringLiteral(modelMapping.output.bindingPropertyName),
68+
classDecoratorIdentifier, factory, importManager);
69+
70+
return factory.updatePropertyDeclaration(
71+
member,
72+
[inputDecorator, outputDecorator, ...(member.modifiers ?? [])],
73+
member.name,
74+
member.questionToken,
75+
member.type,
76+
member.initializer,
77+
);
78+
};
79+
80+
function createDecorator(
81+
name: string, config: ts.Expression, classDecoratorIdentifier: ts.Identifier,
82+
factory: ts.NodeFactory, importManager: ImportManager): ts.Decorator {
83+
const callTarget = factory.createPropertyAccessExpression(
84+
importManager.generateNamespaceImport('@angular/core'),
85+
// The synthetic identifier may be checked later by the downlevel decorators
86+
// transform to resolve to an Angular import using `getSymbolAtLocation`. We trick
87+
// the transform to think it's not synthetic and comes from Angular core.
88+
ts.setOriginalNode(factory.createIdentifier(name), classDecoratorIdentifier));
89+
90+
return factory.createDecorator(factory.createCallExpression(callTarget, undefined, [config]));
91+
}

packages/compiler-cli/src/transformers/jit_transforms/initializer_api_transforms/transform.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {addImports} from '../../../ngtsc/transform';
1414
import {ImportManager} from '../../../ngtsc/translator';
1515

1616
import {signalInputsTransform} from './input_function';
17+
import {signalModelTransform} from './model_function';
1718
import {initializerApiOutputTransform} from './output_function';
1819
import {queryFunctionsTransforms} from './query_functions';
1920
import {PropertyTransform} from './transform_api';
@@ -29,6 +30,7 @@ const propertyTransforms: PropertyTransform[] = [
2930
signalInputsTransform,
3031
initializerApiOutputTransform,
3132
queryFunctionsTransforms,
33+
signalModelTransform,
3234
];
3335

3436
/**

packages/compiler-cli/test/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ ts_library(
5757
testonly = True,
5858
srcs = [
5959
"downlevel_decorators_transform_spec.ts",
60-
"signal_inputs_metadata_transform_spec.ts",
60+
"initializer_api_transforms_spec.ts",
6161
"signal_queries_metadata_transform_spec.ts",
6262
],
6363
deps = [

0 commit comments

Comments
 (0)