From 8e326054250867791bd18915523dde240ea2262e Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:20:59 -0500 Subject: [PATCH] refactor(@angular-devkit/schematics): support `isolatedDeclarations` for tree classes This commit refactors several Tree implementations (`DelegateTree`, `HostTree`, `NullTree`, `ScopedTree`) to support the TypeScript `isolatedDeclarations` compiler option. It uses declaration merging to define the `[TreeSymbol]` method on an interface and assigns it in the constructor to avoid TS9038 errors regarding computed property names inference. --- .../angular_devkit/schematics/index.api.md | 16 ++++++++++++++-- .../schematics/src/tree/delegate.ts | 16 +++++++++++----- .../schematics/src/tree/host-tree.ts | 13 +++++++++---- .../angular_devkit/schematics/src/tree/null.ts | 12 ++++++++++-- .../angular_devkit/schematics/src/tree/scoped.ts | 13 +++++++++---- 5 files changed, 53 insertions(+), 17 deletions(-) diff --git a/goldens/public-api/angular_devkit/schematics/index.api.md b/goldens/public-api/angular_devkit/schematics/index.api.md index 505bd2c39920..6df49bbb1584 100644 --- a/goldens/public-api/angular_devkit/schematics/index.api.md +++ b/goldens/public-api/angular_devkit/schematics/index.api.md @@ -202,10 +202,16 @@ export interface CreateFileAction extends ActionBase { readonly kind: 'c'; } +// @public (undocumented) +export interface DelegateTree { + // (undocumented) + [TreeSymbol](): DelegateTree; +} + // @public (undocumented) export class DelegateTree implements Tree_2 { // (undocumented) - [TreeSymbol]: () => this; + [TreeSymbol]: () => DelegateTree; constructor(_other: Tree_2); // (undocumented) get actions(): Action[]; @@ -516,10 +522,16 @@ export class HostSink extends SimpleSinkBase { protected _validateFileExists(p: Path): Observable; } +// @public (undocumented) +export interface HostTree { + // (undocumented) + [TreeSymbol](): HostTree; +} + // @public (undocumented) export class HostTree implements Tree_2 { // (undocumented) - [TreeSymbol]: () => this; + [TreeSymbol]: () => HostTree; constructor(_backend?: virtualFs.ReadonlyHost<{}>); // (undocumented) get actions(): Action[]; diff --git a/packages/angular_devkit/schematics/src/tree/delegate.ts b/packages/angular_devkit/schematics/src/tree/delegate.ts index 5162511f4b45..9c9ef3fc7dc0 100644 --- a/packages/angular_devkit/schematics/src/tree/delegate.ts +++ b/packages/angular_devkit/schematics/src/tree/delegate.ts @@ -18,8 +18,18 @@ import { UpdateRecorder, } from './interface'; +// Workaround for "error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations." +// When this is fixed within TypeScript, the method can be added back directly to the class. +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +export interface DelegateTree { + [TreeSymbol](): DelegateTree; +} + +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export class DelegateTree implements Tree { - constructor(protected _other: Tree) {} + constructor(protected _other: Tree) { + this[TreeSymbol] = () => this; + } branch(): Tree { return this._other.branch(); @@ -83,8 +93,4 @@ export class DelegateTree implements Tree { get actions(): Action[] { return this._other.actions; } - - [TreeSymbol]() { - return this; - } } diff --git a/packages/angular_devkit/schematics/src/tree/host-tree.ts b/packages/angular_devkit/schematics/src/tree/host-tree.ts index c2437556be16..ecfba25e8276 100644 --- a/packages/angular_devkit/schematics/src/tree/host-tree.ts +++ b/packages/angular_devkit/schematics/src/tree/host-tree.ts @@ -98,6 +98,14 @@ export class HostDirEntry implements DirEntry { } } +// Workaround for "error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations." +// When this is fixed within TypeScript, the method can be added back directly to the class. +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +export interface HostTree { + [TreeSymbol](): HostTree; +} + +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export class HostTree implements Tree { private readonly _id = --_uniqueId; private _record: virtualFs.CordHost; @@ -106,10 +114,6 @@ export class HostTree implements Tree { private _dirCache = new Map(); - [TreeSymbol](): this { - return this; - } - static isHostTree(tree: Tree): tree is HostTree { if (tree instanceof HostTree) { return true; @@ -123,6 +127,7 @@ export class HostTree implements Tree { } constructor(protected _backend: virtualFs.ReadonlyHost<{}> = new virtualFs.Empty()) { + this[TreeSymbol] = () => this; this._record = new virtualFs.CordHost(new virtualFs.SafeReadonlyHost(_backend)); this._recordSync = new virtualFs.SyncDelegateHost(this._record); } diff --git a/packages/angular_devkit/schematics/src/tree/null.ts b/packages/angular_devkit/schematics/src/tree/null.ts index 2a772a8fbceb..1d2f02161e23 100644 --- a/packages/angular_devkit/schematics/src/tree/null.ts +++ b/packages/angular_devkit/schematics/src/tree/null.ts @@ -46,9 +46,17 @@ export class NullTreeDirEntry implements DirEntry { visit(): void {} } +// Workaround for "error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations." +// When this is fixed within TypeScript, the method can be added back directly to the class. +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +export interface NullTree { + [TreeSymbol](): NullTree; +} + +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export class NullTree implements Tree { - [TreeSymbol](): this { - return this; + constructor() { + this[TreeSymbol] = () => this; } branch(): Tree { diff --git a/packages/angular_devkit/schematics/src/tree/scoped.ts b/packages/angular_devkit/schematics/src/tree/scoped.ts index b8cdbc09d23a..a64ad709243f 100644 --- a/packages/angular_devkit/schematics/src/tree/scoped.ts +++ b/packages/angular_devkit/schematics/src/tree/scoped.ts @@ -89,6 +89,14 @@ class ScopedDirEntry implements DirEntry { } } +// Workaround for "error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations." +// When this is fixed within TypeScript, the method can be added back directly to the class. +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +export interface ScopedTree { + [TreeSymbol](): ScopedTree; +} + +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export class ScopedTree implements Tree { readonly _root: ScopedDirEntry; @@ -96,6 +104,7 @@ export class ScopedTree implements Tree { private _base: Tree, scope: string, ) { + this[TreeSymbol] = () => this; const normalizedScope = normalize('/' + scope); this._root = new ScopedDirEntry(this._base.getDir(normalizedScope), normalizedScope); } @@ -197,10 +206,6 @@ export class ScopedTree implements Tree { return scopedActions; } - [TreeSymbol](): this { - return this; - } - private _fullPath(path: string): Path { return join(this._root.scope, normalize('/' + path)); }