mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(compiler-cli): apply debugName transform to required signal queries
Transform assumed `.required` functions always take options as the first argument.
This is true for `input` and `model`, but not for `viewChild` and `contentChild`,
which take the same arguments as non-required versions.
Change the code to put options for signal queries in the right position,
causing debugName to be correctly generated for signal queries.
(cherry picked from commit eb2a8ff63f)
This commit is contained in:
+8
-6
@@ -12,9 +12,10 @@ function insertDebugNameIntoCallExpression(
|
||||
node: ts.CallExpression,
|
||||
debugName: string,
|
||||
): ts.CallExpression {
|
||||
const isRequired = isRequiredSignalFunction(node.expression);
|
||||
const isRequiredInput = isRequiredInputFunction(node.expression);
|
||||
const hasNoArgs = node.arguments.length === 0;
|
||||
const configPosition = hasNoArgs || isSignalWithObjectOnlyDefinition(node) || isRequired ? 0 : 1;
|
||||
const configPosition =
|
||||
hasNoArgs || isSignalWithObjectOnlyDefinition(node) || isRequiredInput ? 0 : 1;
|
||||
const existingArg =
|
||||
configPosition >= node.arguments.length ? null : node.arguments[configPosition];
|
||||
|
||||
@@ -62,7 +63,7 @@ function insertDebugNameIntoCallExpression(
|
||||
|
||||
// If we're adding an argument, but the function requires a first argument (e.g. `input()`),
|
||||
// we have to add `undefined` before the debug literal.
|
||||
if (hasNoArgs && !isRequired) {
|
||||
if (hasNoArgs && !isRequiredInput) {
|
||||
spreadArgs.push(ts.factory.createIdentifier('undefined'));
|
||||
}
|
||||
|
||||
@@ -297,15 +298,16 @@ function isSignalFunction(expression: ts.Identifier): boolean {
|
||||
return signalFunctions.has(text);
|
||||
}
|
||||
|
||||
function isRequiredSignalFunction(expression: ts.Expression): boolean {
|
||||
// Check for a property access expression that uses the 'required' property
|
||||
function isRequiredInputFunction(expression: ts.Expression): boolean {
|
||||
// Check for a property access expression that uses the 'required' property on `input` or `model`
|
||||
if (
|
||||
ts.isPropertyAccessExpression(expression) &&
|
||||
ts.isIdentifier(expression.name) &&
|
||||
ts.isIdentifier(expression.expression)
|
||||
) {
|
||||
const accessName = expression.name.text;
|
||||
if (accessName === 'required') {
|
||||
const parentName = expression.expression.text;
|
||||
if (accessName === 'required' && (parentName === 'input' || parentName === 'model')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1319,6 +1319,139 @@ runInEachFileSystem(() => {
|
||||
`viewChild("foo", { debugName: "testViewChild", read: ElementRef })`,
|
||||
);
|
||||
});
|
||||
|
||||
describe('.required', () => {
|
||||
it('should insert debug info into .required', () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {viewChild, Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: '',
|
||||
})
|
||||
class MyComponent {
|
||||
testViewChild = viewChild.required('foo');
|
||||
}
|
||||
`,
|
||||
);
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(cleanNewLines(jsContents)).toContain(
|
||||
`viewChild.required('foo', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testViewChild" }] : /* istanbul ignore next */ []))`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should insert debug info into .required with existing options', () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {viewChild, Component, ElementRef} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: ''
|
||||
}) class MyComponent {
|
||||
testViewChild = viewChild.required('foo', { read: ElementRef });
|
||||
}
|
||||
`,
|
||||
);
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(cleanNewLines(jsContents)).toContain(
|
||||
`viewChild.required('foo', { ...(ngDevMode ? { debugName: "testViewChild" } : /* istanbul ignore next */ {}), read: ElementRef })`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should tree-shake away debug info if in prod mode', () => async () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {viewChild, Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: ''
|
||||
})
|
||||
class MyComponent {
|
||||
testViewChild = viewChild.required('foo');
|
||||
}
|
||||
`,
|
||||
);
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code;
|
||||
expect(builtContent).not.toContain('debugName');
|
||||
expect(cleanNewLines(builtContent)).toContain(`viewChild( "foo" )`);
|
||||
});
|
||||
|
||||
it('should not tree-shake away debug info if in dev mode', async () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {viewChild, Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: '',
|
||||
})
|
||||
class MyComponent {
|
||||
testViewChild = viewChild.required('foo');
|
||||
}
|
||||
`,
|
||||
);
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code;
|
||||
expect(cleanNewLines(builtContent)).toContain(
|
||||
`viewChild.required( "foo", { debugName: "testViewChild" } )`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should tree-shake away debug info if in prod mode with custom options', async () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {viewChild, Component, ElementRef} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: ''
|
||||
}) class MyComponent {
|
||||
testViewChild = viewChild.required('foo', { read: ElementRef });
|
||||
}
|
||||
`,
|
||||
);
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code;
|
||||
expect(builtContent).not.toContain('debugName');
|
||||
expect(builtContent).toContain('viewChild.required("foo", { read: ElementRef })');
|
||||
});
|
||||
|
||||
it('should not tree-shake away debug info if in dev mode with custom options', async () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {viewChild, Component, ElementRef} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: ''
|
||||
}) class MyComponent {
|
||||
testViewChild = viewChild.required('foo', { read: ElementRef });
|
||||
}
|
||||
`,
|
||||
);
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code;
|
||||
expect(cleanNewLines(builtContent)).toContain(
|
||||
`viewChild.required("foo", { debugName: "testViewChild", read: ElementRef })`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('viewChildren', () => {
|
||||
@@ -1575,6 +1708,139 @@ runInEachFileSystem(() => {
|
||||
`contentChild("foo", { debugName: "testContentChild", read: ElementRef })`,
|
||||
);
|
||||
});
|
||||
|
||||
describe('.required', () => {
|
||||
it('should insert debug info into .required', () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {contentChild, Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: '',
|
||||
})
|
||||
class MyComponent {
|
||||
testContentChild = contentChild.required('foo');
|
||||
}
|
||||
`,
|
||||
);
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(cleanNewLines(jsContents)).toContain(
|
||||
`contentChild.required('foo', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testContentChild" }] : /* istanbul ignore next */ []))`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should insert debug info into .required with existing options', () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {contentChild, Component, ElementRef} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: ''
|
||||
}) class MyComponent {
|
||||
testContentChild = contentChild.required('foo', { read: ElementRef });
|
||||
}
|
||||
`,
|
||||
);
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(cleanNewLines(jsContents)).toContain(
|
||||
`contentChild.required('foo', { ...(ngDevMode ? { debugName: "testContentChild" } : /* istanbul ignore next */ {}), read: ElementRef })`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should tree-shake away debug info if in prod mode', () => async () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {contentChild, Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: ''
|
||||
})
|
||||
class MyComponent {
|
||||
testContentChild = contentChild.required('foo');
|
||||
}
|
||||
`,
|
||||
);
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code;
|
||||
expect(builtContent).not.toContain('debugName');
|
||||
expect(cleanNewLines(builtContent)).toContain(`contentChild( "foo" )`);
|
||||
});
|
||||
|
||||
it('should not tree-shake away debug info if in dev mode', async () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {contentChild, Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: '',
|
||||
})
|
||||
class MyComponent {
|
||||
testContentChild = contentChild.required('foo');
|
||||
}
|
||||
`,
|
||||
);
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code;
|
||||
expect(cleanNewLines(builtContent)).toContain(
|
||||
`contentChild.required( "foo", { debugName: "testContentChild" } )`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should tree-shake away debug info if in prod mode with custom options', async () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {contentChild, Component, ElementRef} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: ''
|
||||
}) class MyComponent {
|
||||
testContentChild = contentChild.required('foo', { read: ElementRef });
|
||||
}
|
||||
`,
|
||||
);
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code;
|
||||
expect(builtContent).not.toContain('debugName');
|
||||
expect(builtContent).toContain('contentChild.required("foo", { read: ElementRef })');
|
||||
});
|
||||
|
||||
it('should not tree-shake away debug info if in dev mode with custom options', async () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {contentChild, Component, ElementRef} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: ''
|
||||
}) class MyComponent {
|
||||
testContentChild = contentChild.required('foo', { read: ElementRef });
|
||||
}
|
||||
`,
|
||||
);
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code;
|
||||
expect(cleanNewLines(builtContent)).toContain(
|
||||
`contentChild.required("foo", { debugName: "testContentChild", read: ElementRef })`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('contentChildren', () => {
|
||||
|
||||
Reference in New Issue
Block a user