From 7e15ecc63092f3f3925eb2be3d7283165f7de46b Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Tue, 27 Oct 2026 09:48:01 +0000 Subject: [PATCH] fix(@angular-devkit/core): only invoke smart default provider when value is undefined Previously, `CoreSchemaRegistry._applySmartDefaults` invoked the smart default provider unconditionally before `CoreSchemaRegistry._set` checked whether the target property already had a defined value. This caused smart default providers to be evaluated even when an option was explicitly provided. --- .../core/src/json/schema/registry.ts | 22 +++++++++--------- .../core/src/json/schema/registry_spec.ts | 23 +++++++++++++++++++ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/packages/angular_devkit/core/src/json/schema/registry.ts b/packages/angular_devkit/core/src/json/schema/registry.ts index 928771337bb5..bd862a0e5f17 100644 --- a/packages/angular_devkit/core/src/json/schema/registry.ts +++ b/packages/angular_devkit/core/src/json/schema/registry.ts @@ -585,11 +585,11 @@ export class CoreSchemaRegistry implements SchemaRegistry { for (const path in answers) { const pathFragments = path.split('/').slice(1); - CoreSchemaRegistry._set(data, pathFragments, answers[path], null, undefined, true); + await CoreSchemaRegistry._set(data, pathFragments, answers[path], null, undefined, true); } } - private static _set( + private static async _set( // eslint-disable-next-line @typescript-eslint/no-explicit-any data: any, fragments: string[], @@ -598,7 +598,7 @@ export class CoreSchemaRegistry implements SchemaRegistry { parent: any = null, parentProperty?: string, force?: boolean, - ): void { + ): Promise { for (let index = 0; index < fragments.length; index++) { const fragment = fragments[index]; if (/^i\d+$/.test(fragment)) { @@ -607,7 +607,7 @@ export class CoreSchemaRegistry implements SchemaRegistry { } for (let dataIndex = 0; dataIndex < data.length; dataIndex++) { - CoreSchemaRegistry._set( + await CoreSchemaRegistry._set( data[dataIndex], fragments.slice(index + 1), value, @@ -629,7 +629,12 @@ export class CoreSchemaRegistry implements SchemaRegistry { } if (parent && parentProperty && (force || parent[parentProperty] === undefined)) { - parent[parentProperty] = value; + let resolvedValue = typeof value === 'function' ? value() : value; + if (isObservable(resolvedValue)) { + resolvedValue = await lastValueFrom(resolvedValue); + } + + parent[parentProperty] = resolvedValue; } } @@ -644,12 +649,7 @@ export class CoreSchemaRegistry implements SchemaRegistry { continue; } - let value = source(schema); - if (isObservable(value)) { - value = (await lastValueFrom(value)) as {}; - } - - CoreSchemaRegistry._set(data, fragments, value); + await CoreSchemaRegistry._set(data, fragments, () => source(schema)); } } diff --git a/packages/angular_devkit/core/src/json/schema/registry_spec.ts b/packages/angular_devkit/core/src/json/schema/registry_spec.ts index 32c601154d16..022588036d3b 100644 --- a/packages/angular_devkit/core/src/json/schema/registry_spec.ts +++ b/packages/angular_devkit/core/src/json/schema/registry_spec.ts @@ -305,6 +305,29 @@ describe('CoreSchemaRegistry', () => { expect(data.obj.deep.arr).toEqual([1, 2, 3]); }); + it('does not call smart default provider when value is already defined', async () => { + const registry = new CoreSchemaRegistry(); + const provider = jasmine.createSpy('provider').and.returnValue('default-val'); + registry.addSmartDefaultProvider('test', provider); + + const validator = await registry.compile({ + properties: { + prop: { + type: 'string', + $default: { + $source: 'test', + }, + }, + }, + }); + + const data = { prop: 'explicit-val' }; + const result = await validator(data); + expect(result.success).toBe(true); + expect(data.prop).toBe('explicit-val'); + expect(provider).not.toHaveBeenCalled(); + }); + it('works with true as a schema and post-transforms', async () => { const registry = new CoreSchemaRegistry(); registry.addPostTransform(addUndefinedDefaults);