Skip to content

Commit e941e6b

Browse files
hawkgsAndrewKushnir
authored andcommitted
refactor(compiler-cli): add a resource debugName transform (angular#64172)
Add a TS transform for `resource` (and `httpResource`) `debugName`. Test the transformations. PR Close angular#64172
1 parent 6318006 commit e941e6b

File tree

2 files changed

+440
-34
lines changed

2 files changed

+440
-34
lines changed

packages/compiler-cli/src/ngtsc/transform/src/implicit_signal_debug_name_transform.ts

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,31 +15,27 @@ function insertDebugNameIntoCallExpression(
1515
const signalExpressionIsRequired = isRequiredSignalFunction(callExpression.expression);
1616
let configPosition = signalExpressionIsRequired ? 0 : 1;
1717

18-
const nodeArgs = Array.from(callExpression.arguments);
19-
2018
// 1. If the call expression has no arguments, we pretend that the config object is at position 0.
2119
// We do this so that we can insert a spread element at the start of the args list in a way where
2220
// undefined can be the first argument but still get tree-shaken out in production builds.
2321
// or
24-
// 2. Since `linkedSignal` with computation uses a single object for both computation logic
25-
// and options (unlike other signal-based primitives), we set the argument position to 0, i.e.
26-
// reusing the computation logic object.
22+
// 2. If the signal has an object-only definition (e.g. `linkedSignal` or `resource`), we set
23+
// the argument position to 0, i.e. reusing the existing object.
2724
const signalExpressionHasNoArguments = callExpression.arguments.length === 0;
28-
const isLinkedSignal = callExpression.expression.getText() === 'linkedSignal';
29-
const isComputationLinkedSignal =
30-
isLinkedSignal && nodeArgs[0].kind === ts.SyntaxKind.ObjectLiteralExpression;
31-
if (signalExpressionHasNoArguments || isComputationLinkedSignal) {
25+
const signalWithObjectOnlyDefinition = isSignalWithObjectOnlyDefinition(callExpression);
26+
if (signalExpressionHasNoArguments || signalWithObjectOnlyDefinition) {
3227
configPosition = 0;
3328
}
3429

30+
const nodeArgs = Array.from(callExpression.arguments);
3531
let existingArgument = nodeArgs[configPosition];
3632

3733
if (existingArgument === undefined) {
3834
existingArgument = ts.factory.createObjectLiteralExpression([]);
3935
}
4036

4137
// Do nothing if an identifier is used as the config object
42-
// Ex -
38+
// Ex:
4339
// const defaultObject = { equals: () => false };
4440
// signal(123, defaultObject)
4541
if (ts.isIdentifier(existingArgument)) {
@@ -104,15 +100,18 @@ function insertDebugNameIntoCallExpression(
104100

105101
let transformedSignalArgs: ts.NodeArray<ts.Expression>;
106102

107-
if (signalExpressionIsRequired || signalExpressionHasNoArguments || isComputationLinkedSignal) {
103+
if (
104+
signalExpressionIsRequired ||
105+
signalExpressionHasNoArguments ||
106+
signalWithObjectOnlyDefinition
107+
) {
108108
// 1. If the call expression is a required signal function, there is no args other than the config object.
109109
// So we just use the spread element as the only argument.
110110
// or
111111
// 2. If the call expression has no arguments (ex. input(), model(), etc), we already added the undefined
112112
// identifier in the spread element above. So we use that spread Element as is.
113113
// or
114-
// 3. We are transforming a `linkedSignal` with computation (i.e. we have a single object for both
115-
// logic and options).
114+
// 3. We are transforming a signal with object-only definition.
116115
transformedSignalArgs = ts.factory.createNodeArray([spreadElementContainingUpdatedOptions]);
117116
} else {
118117
// 3. Signal expression is not required and has arguments.
@@ -234,6 +233,23 @@ function isPropertyDeclarationCase(
234233
return ts.isIdentifier(expression) && isSignalFunction(expression);
235234
}
236235

236+
type PackageName = 'core' | 'common';
237+
238+
const signalFunctions: ReadonlyMap<string, PackageName> = new Map([
239+
['signal', 'core'],
240+
['computed', 'core'],
241+
['linkedSignal', 'core'],
242+
['input', 'core'],
243+
['model', 'core'],
244+
['viewChild', 'core'],
245+
['viewChildren', 'core'],
246+
['contentChild', 'core'],
247+
['contentChildren', 'core'],
248+
['effect', 'core'],
249+
['resource', 'core'],
250+
['httpResource', 'common'],
251+
]);
252+
237253
/**
238254
*
239255
* Determines if a node is an expression that references an @angular/core imported symbol.
@@ -243,7 +259,7 @@ function isPropertyDeclarationCase(
243259
* const mySignal = signal(123); // expressionIsUsingAngularImportedSymbol === true
244260
* ```
245261
*/
246-
function expressionIsUsingAngularCoreImportedSymbol(
262+
function expressionIsUsingAngularImportedSymbol(
247263
program: ts.Program,
248264
expression: ts.Expression,
249265
): boolean {
@@ -282,25 +298,14 @@ function expressionIsUsingAngularCoreImportedSymbol(
282298
}
283299

284300
const specifier = importDeclaration.moduleSpecifier.text;
301+
const packageName = signalFunctions.get(expression.getText());
285302
return (
286303
specifier !== undefined &&
287-
(specifier === '@angular/core' || specifier.startsWith('@angular/core/'))
304+
packageName !== undefined &&
305+
(specifier === `@angular/${packageName}` || specifier.startsWith(`@angular/${packageName}/`))
288306
);
289307
}
290308

291-
const signalFunctions: ReadonlySet<string> = new Set([
292-
'signal',
293-
'computed',
294-
'linkedSignal',
295-
'input',
296-
'model',
297-
'viewChild',
298-
'viewChildren',
299-
'contentChild',
300-
'contentChildren',
301-
'effect',
302-
]);
303-
304309
function isSignalFunction(expression: ts.Identifier): boolean {
305310
const text = expression.text;
306311

@@ -331,10 +336,10 @@ function transformVariableDeclaration(
331336

332337
const expression = node.initializer.expression;
333338
if (ts.isPropertyAccessExpression(expression)) {
334-
if (!expressionIsUsingAngularCoreImportedSymbol(program, expression.expression)) {
339+
if (!expressionIsUsingAngularImportedSymbol(program, expression.expression)) {
335340
return node;
336341
}
337-
} else if (!expressionIsUsingAngularCoreImportedSymbol(program, expression)) {
342+
} else if (!expressionIsUsingAngularImportedSymbol(program, expression)) {
338343
return node;
339344
}
340345

@@ -362,10 +367,10 @@ function transformPropertyAssignment(
362367
): ts.ExpressionStatement {
363368
const expression = node.expression.right.expression;
364369
if (ts.isPropertyAccessExpression(expression)) {
365-
if (!expressionIsUsingAngularCoreImportedSymbol(program, expression.expression)) {
370+
if (!expressionIsUsingAngularImportedSymbol(program, expression.expression)) {
366371
return node;
367372
}
368-
} else if (!expressionIsUsingAngularCoreImportedSymbol(program, expression)) {
373+
} else if (!expressionIsUsingAngularImportedSymbol(program, expression)) {
369374
return node;
370375
}
371376

@@ -387,10 +392,10 @@ function transformPropertyDeclaration(
387392

388393
const expression = node.initializer.expression;
389394
if (ts.isPropertyAccessExpression(expression)) {
390-
if (!expressionIsUsingAngularCoreImportedSymbol(program, expression.expression)) {
395+
if (!expressionIsUsingAngularImportedSymbol(program, expression.expression)) {
391396
return node;
392397
}
393-
} else if (!expressionIsUsingAngularCoreImportedSymbol(program, expression)) {
398+
} else if (!expressionIsUsingAngularImportedSymbol(program, expression)) {
394399
return node;
395400
}
396401

@@ -410,6 +415,24 @@ function transformPropertyDeclaration(
410415
}
411416
}
412417

418+
/**
419+
* The function determines whether the target signal has an object-only definition, that includes
420+
* both the computation logic and the options (unlike other signal-based primitives), or not.
421+
* Ex: `linkedSignal` with computation, `resource`
422+
*/
423+
function isSignalWithObjectOnlyDefinition(callExpression: ts.CallExpression): boolean {
424+
const callExpressionText = callExpression.expression.getText();
425+
const nodeArgs = Array.from(callExpression.arguments);
426+
427+
const isLinkedSignal = callExpressionText === 'linkedSignal';
428+
const isComputationLinkedSignal =
429+
isLinkedSignal && nodeArgs[0].kind === ts.SyntaxKind.ObjectLiteralExpression;
430+
431+
const isResource = callExpressionText === 'resource';
432+
433+
return isComputationLinkedSignal || isResource;
434+
}
435+
413436
/**
414437
*
415438
* This transformer adds a debugName property to the config object of signal functions like

0 commit comments

Comments
 (0)