From 810b0a7e5cef7742fa88494ed8c107d717417c55 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Tue, 13 May 2025 15:13:14 +0000 Subject: [PATCH] refactor: add explicit types for exports relying on inferred call return type (#61312) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As part of the Bazel toolchain migration we noticed that implicit types generated by the TypeScript compiler sometimes end up referencing types from other packages (i.e. cross-package imports). These imports currently work just because the Bazel `ts_library` and `ng_module` rules automatically inserted a `` into `.d.ts` of packages. This helped TS figure out how to import a given file. Notably this is custom logic that is not occuring in vanilla TS or Angular compilations—so we will drop this magic as part of the toolchain cleanup! To improve code quality and keep the existing behavior working, we are doing the following: - adding a lint rule that reduces the risk of such imports breaking. The failure scenario without the rule is that API goldens show unexpected diffs, and types might be duplicated in a different package! - keeping the ` !route.path?.startsWith(PagePrefix.TUTORIALS) && route.path !== PagePrefix.PLAYGROUND, diff --git a/goldens/public-api/common/index.api.md b/goldens/public-api/common/index.api.md index 3e703e81b78..fa399abb00e 100644 --- a/goldens/public-api/common/index.api.md +++ b/goldens/public-api/common/index.api.md @@ -874,16 +874,16 @@ export { PopStateEvent_2 as PopStateEvent } export const PRECONNECT_CHECK_BLOCKLIST: InjectionToken<(string | string[])[]>; // @public -export const provideCloudflareLoader: (path: string) => i0.Provider[]; +export const provideCloudflareLoader: (path: string) => Provider[]; // @public -export const provideCloudinaryLoader: (path: string) => i0.Provider[]; +export const provideCloudinaryLoader: (path: string) => Provider[]; // @public -export const provideImageKitLoader: (path: string) => i0.Provider[]; +export const provideImageKitLoader: (path: string) => Provider[]; // @public -export const provideImgixLoader: (path: string) => i0.Provider[]; +export const provideImgixLoader: (path: string) => Provider[]; // @public export function provideNetlifyLoader(path?: string): Provider[]; diff --git a/goldens/public-api/platform-browser-dynamic/index.api.md b/goldens/public-api/platform-browser-dynamic/index.api.md index cc5f45d1fad..5129e504dff 100644 --- a/goldens/public-api/platform-browser-dynamic/index.api.md +++ b/goldens/public-api/platform-browser-dynamic/index.api.md @@ -7,7 +7,7 @@ import { Compiler } from '@angular/core'; import { CompilerFactory } from '@angular/core'; import { CompilerOptions } from '@angular/core'; -import * as i0 from '@angular/core'; +import { PlatformRef } from '@angular/core'; import { StaticProvider } from '@angular/core'; import { Version } from '@angular/core'; @@ -18,7 +18,7 @@ export class JitCompilerFactory implements CompilerFactory { } // @public (undocumented) -export const platformBrowserDynamic: (extraProviders?: StaticProvider[]) => i0.PlatformRef; +export const platformBrowserDynamic: (extraProviders?: StaticProvider[]) => PlatformRef; // @public (undocumented) export const VERSION: Version; diff --git a/goldens/public-api/platform-browser/testing/index.api.md b/goldens/public-api/platform-browser/testing/index.api.md index aef5cb15664..b4c362b05a2 100644 --- a/goldens/public-api/platform-browser/testing/index.api.md +++ b/goldens/public-api/platform-browser/testing/index.api.md @@ -6,6 +6,7 @@ import * as i0 from '@angular/core'; import * as i1 from '@angular/common'; +import { PlatformRef } from '@angular/core'; import { StaticProvider } from '@angular/core'; // @public @@ -19,7 +20,7 @@ export class BrowserTestingModule { } // @public -export const platformBrowserTesting: (extraProviders?: StaticProvider[]) => i0.PlatformRef; +export const platformBrowserTesting: (extraProviders?: StaticProvider[]) => PlatformRef; // (No @packageDocumentation comment for this package) diff --git a/goldens/public-api/platform-server/testing/index.api.md b/goldens/public-api/platform-server/testing/index.api.md index d6364590445..74b226cdcba 100644 --- a/goldens/public-api/platform-server/testing/index.api.md +++ b/goldens/public-api/platform-server/testing/index.api.md @@ -6,10 +6,11 @@ import * as i0 from '@angular/core'; import * as i1 from '@angular/platform-browser-dynamic/testing'; +import { PlatformRef } from '@angular/core'; import { StaticProvider } from '@angular/core'; // @public @deprecated -export const platformServerTesting: (extraProviders?: StaticProvider[]) => i0.PlatformRef; +export const platformServerTesting: (extraProviders?: StaticProvider[]) => PlatformRef; // @public @deprecated export class ServerTestingModule { diff --git a/modules/benchmarks/src/change_detection/util.ts b/modules/benchmarks/src/change_detection/util.ts index 4d6cb8dc475..73dca914778 100644 --- a/modules/benchmarks/src/change_detection/util.ts +++ b/modules/benchmarks/src/change_detection/util.ts @@ -8,7 +8,7 @@ import {getIntParameter} from '../util'; -export const numViews = getIntParameter('viewCount'); +export const numViews: number = getIntParameter('viewCount'); export function newArray(size: number): T[]; export function newArray(size: number, value: T): T[]; diff --git a/modules/ssr-benchmarks/src/app/app.config.server.ts b/modules/ssr-benchmarks/src/app/app.config.server.ts index 5fd01686705..4f60c86bfd5 100644 --- a/modules/ssr-benchmarks/src/app/app.config.server.ts +++ b/modules/ssr-benchmarks/src/app/app.config.server.ts @@ -14,4 +14,4 @@ const serverConfig: ApplicationConfig = { providers: [provideServerRendering()], }; -export const config = mergeApplicationConfig(appConfig, serverConfig); +export const config: ApplicationConfig = mergeApplicationConfig(appConfig, serverConfig); diff --git a/packages/common/src/directives/ng_optimized_image/image_loaders/cloudflare_loader.ts b/packages/common/src/directives/ng_optimized_image/image_loaders/cloudflare_loader.ts index 8508a5349a2..5e5a25939ea 100644 --- a/packages/common/src/directives/ng_optimized_image/image_loaders/cloudflare_loader.ts +++ b/packages/common/src/directives/ng_optimized_image/image_loaders/cloudflare_loader.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ +import {Provider} from '@angular/core'; import {PLACEHOLDER_QUALITY} from './constants'; import {createImageLoader, ImageLoaderConfig} from './image_loader'; @@ -20,7 +21,7 @@ import {createImageLoader, ImageLoaderConfig} from './image_loader'; * * @publicApi */ -export const provideCloudflareLoader = createImageLoader( +export const provideCloudflareLoader: (path: string) => Provider[] = createImageLoader( createCloudflareUrl, ngDevMode ? ['https:///cdn-cgi/image//'] : undefined, ); diff --git a/packages/common/src/directives/ng_optimized_image/image_loaders/cloudinary_loader.ts b/packages/common/src/directives/ng_optimized_image/image_loaders/cloudinary_loader.ts index f3a6b852804..cfee85bf9fa 100644 --- a/packages/common/src/directives/ng_optimized_image/image_loaders/cloudinary_loader.ts +++ b/packages/common/src/directives/ng_optimized_image/image_loaders/cloudinary_loader.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ +import {Provider} from '@angular/core'; import {createImageLoader, ImageLoaderConfig, ImageLoaderInfo} from './image_loader'; /** @@ -36,7 +37,7 @@ function isCloudinaryUrl(url: string): boolean { * * @publicApi */ -export const provideCloudinaryLoader = createImageLoader( +export const provideCloudinaryLoader: (path: string) => Provider[] = createImageLoader( createCloudinaryUrl, ngDevMode ? [ diff --git a/packages/common/src/directives/ng_optimized_image/image_loaders/imagekit_loader.ts b/packages/common/src/directives/ng_optimized_image/image_loaders/imagekit_loader.ts index af50ff5223c..341cf4cf8f4 100644 --- a/packages/common/src/directives/ng_optimized_image/image_loaders/imagekit_loader.ts +++ b/packages/common/src/directives/ng_optimized_image/image_loaders/imagekit_loader.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ +import {Provider} from '@angular/core'; import {PLACEHOLDER_QUALITY} from './constants'; import {createImageLoader, ImageLoaderConfig, ImageLoaderInfo} from './image_loader'; @@ -36,7 +37,7 @@ function isImageKitUrl(url: string): boolean { * * @publicApi */ -export const provideImageKitLoader = createImageLoader( +export const provideImageKitLoader: (path: string) => Provider[] = createImageLoader( createImagekitUrl, ngDevMode ? ['https://ik.imagekit.io/mysite', 'https://subdomain.mysite.com'] : undefined, ); diff --git a/packages/common/src/directives/ng_optimized_image/image_loaders/imgix_loader.ts b/packages/common/src/directives/ng_optimized_image/image_loaders/imgix_loader.ts index 6830404441e..561437cb903 100644 --- a/packages/common/src/directives/ng_optimized_image/image_loaders/imgix_loader.ts +++ b/packages/common/src/directives/ng_optimized_image/image_loaders/imgix_loader.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ +import {Provider} from '@angular/core'; import {PLACEHOLDER_QUALITY} from './constants'; import {createImageLoader, ImageLoaderConfig, ImageLoaderInfo} from './image_loader'; @@ -34,7 +35,7 @@ function isImgixUrl(url: string): boolean { * * @publicApi */ -export const provideImgixLoader = createImageLoader( +export const provideImgixLoader: (path: string) => Provider[] = createImageLoader( createImgixUrl, ngDevMode ? ['https://somepath.imgix.net/'] : undefined, ); diff --git a/packages/compiler-cli/src/ngtsc/annotations/directive/src/query_functions.ts b/packages/compiler-cli/src/ngtsc/annotations/directive/src/query_functions.ts index 28520770c86..e3e3b723681 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/directive/src/query_functions.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/directive/src/query_functions.ts @@ -26,7 +26,7 @@ import { import {tryUnwrapForwardRef} from '../../common'; import {validateAccessOfInitializerApiMember} from './initializer_function_access'; -import {tryParseInitializerApi} from './initializer_functions'; +import {InitializerApiFunction, tryParseInitializerApi} from './initializer_functions'; /** Possible query initializer API functions. */ export type QueryFunctionName = 'viewChild' | 'contentChild' | 'viewChildren' | 'contentChildren'; @@ -40,20 +40,21 @@ const queryFunctionNames: QueryFunctionName[] = [ ]; /** Possible query initializer API functions. */ -export const QUERY_INITIALIZER_FNS = queryFunctionNames.map((fnName) => ({ - functionName: fnName, - owningModule: '@angular/core' as const, - // Queries are accessed from within static blocks, via the query definition functions. - // Conceptually, the fields could access private members— even ES private fields. - // Support for ES private fields requires special caution and complexity when partial - // output is linked— hence not supported. TS private members are allowed in static blocks. - allowedAccessLevels: [ - ClassMemberAccessLevel.PublicWritable, - ClassMemberAccessLevel.PublicReadonly, - ClassMemberAccessLevel.Protected, - ClassMemberAccessLevel.Private, - ], -})); +export const QUERY_INITIALIZER_FNS: (InitializerApiFunction & {functionName: QueryFunctionName})[] = + queryFunctionNames.map((fnName) => ({ + functionName: fnName, + owningModule: '@angular/core' as const, + // Queries are accessed from within static blocks, via the query definition functions. + // Conceptually, the fields could access private members— even ES private fields. + // Support for ES private fields requires special caution and complexity when partial + // output is linked— hence not supported. TS private members are allowed in static blocks. + allowedAccessLevels: [ + ClassMemberAccessLevel.PublicWritable, + ClassMemberAccessLevel.PublicReadonly, + ClassMemberAccessLevel.Protected, + ClassMemberAccessLevel.Private, + ], + })); // The `descendants` option is enabled by default, except for content children. const defaultDescendantsValue = (type: QueryFunctionName) => type !== 'contentChildren'; diff --git a/packages/compiler-cli/src/ngtsc/program_driver/src/api.ts b/packages/compiler-cli/src/ngtsc/program_driver/src/api.ts index 5e4f9b9dfbe..8a388df6aa4 100644 --- a/packages/compiler-cli/src/ngtsc/program_driver/src/api.ts +++ b/packages/compiler-cli/src/ngtsc/program_driver/src/api.ts @@ -22,7 +22,7 @@ export interface FileUpdate { originalFile: ts.SourceFile | null; } -export const NgOriginalFile = Symbol('NgOriginalFile'); +export const NgOriginalFile: unique symbol = Symbol('NgOriginalFile'); /** * If an updated file has an associated original source file, then the original source file diff --git a/packages/compiler-cli/src/ngtsc/shims/src/expando.ts b/packages/compiler-cli/src/ngtsc/shims/src/expando.ts index 2a58bcd8b67..91a1fac1608 100644 --- a/packages/compiler-cli/src/ngtsc/shims/src/expando.ts +++ b/packages/compiler-cli/src/ngtsc/shims/src/expando.ts @@ -13,7 +13,7 @@ import {AbsoluteFsPath} from '../../file_system'; /** * A `Symbol` which is used to patch extension data onto `ts.SourceFile`s. */ -export const NgExtension = Symbol('NgExtension'); +export const NgExtension: unique symbol = Symbol('NgExtension'); /** * Contents of the `NgExtension` property of a `ts.SourceFile`. diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/expression.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/expression.ts index 3f8ae8ce0f9..3ea7b6d06bb 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/expression.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/expression.ts @@ -54,7 +54,7 @@ import {tsCastToAny, tsNumericExpression} from './ts_util'; * - Some flavor of function call, like `isNan(0) as any` - requires even more characters than the * NaN option and has the same issue with `noLib`. */ -export const ANY_EXPRESSION = ts.factory.createAsExpression( +export const ANY_EXPRESSION: ts.AsExpression = ts.factory.createAsExpression( ts.factory.createNumericLiteral('0'), ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), ); diff --git a/packages/compiler/src/template/pipeline/ir/src/traits.ts b/packages/compiler/src/template/pipeline/ir/src/traits.ts index a5783de7434..8da07824794 100644 --- a/packages/compiler/src/template/pipeline/ir/src/traits.ts +++ b/packages/compiler/src/template/pipeline/ir/src/traits.ts @@ -15,22 +15,22 @@ import {SlotHandle} from './handle'; /** * Marker symbol for `ConsumesSlotOpTrait`. */ -export const ConsumesSlot = Symbol('ConsumesSlot'); +export const ConsumesSlot: unique symbol = Symbol('ConsumesSlot'); /** * Marker symbol for `DependsOnSlotContextOpTrait`. */ -export const DependsOnSlotContext = Symbol('DependsOnSlotContext'); +export const DependsOnSlotContext: unique symbol = Symbol('DependsOnSlotContext'); /** * Marker symbol for `ConsumesVars` trait. */ -export const ConsumesVarsTrait = Symbol('ConsumesVars'); +export const ConsumesVarsTrait: unique symbol = Symbol('ConsumesVars'); /** * Marker symbol for `UsesVarOffset` trait. */ -export const UsesVarOffset = Symbol('UsesVarOffset'); +export const UsesVarOffset: unique symbol = Symbol('UsesVarOffset'); /** * Marks an operation as requiring allocation of one or more data slots for storage. diff --git a/packages/core/primitives/event-dispatch/src/event_dispatcher.ts b/packages/core/primitives/event-dispatch/src/event_dispatcher.ts index 839d2d8a111..a859bfd1e23 100644 --- a/packages/core/primitives/event-dispatch/src/event_dispatcher.ts +++ b/packages/core/primitives/event-dispatch/src/event_dispatcher.ts @@ -19,7 +19,8 @@ import {Restriction} from './restriction'; export type Replayer = (eventInfoWrappers: Event[]) => void; /** An internal symbol used to indicate whether propagation should be stopped or not. */ -export const PROPAGATION_STOPPED_SYMBOL = /* @__PURE__ */ Symbol.for('propagationStopped'); +export const PROPAGATION_STOPPED_SYMBOL: unique symbol = + /* @__PURE__ */ Symbol.for('propagationStopped'); /** Extra event phases beyond what the browser provides. */ export const EventPhase = { diff --git a/packages/core/primitives/signals/src/graph.ts b/packages/core/primitives/signals/src/graph.ts index 52667b34116..40e1bc4a58c 100644 --- a/packages/core/primitives/signals/src/graph.ts +++ b/packages/core/primitives/signals/src/graph.ts @@ -37,7 +37,7 @@ let postProducerCreatedFn: ReactiveHookFn | null = null; * * This can be used to auto-unwrap signals in various cases, or to auto-wrap non-signal values. */ -export const SIGNAL = /* @__PURE__ */ Symbol('SIGNAL'); +export const SIGNAL: unique symbol = /* @__PURE__ */ Symbol('SIGNAL'); export function setActiveConsumer(consumer: ReactiveNode | null): ReactiveNode | null { const prev = activeConsumer; diff --git a/packages/core/primitives/signals/src/linked_signal.ts b/packages/core/primitives/signals/src/linked_signal.ts index 9f940992690..230c1b85ddb 100644 --- a/packages/core/primitives/signals/src/linked_signal.ts +++ b/packages/core/primitives/signals/src/linked_signal.ts @@ -119,7 +119,7 @@ export function linkedSignalUpdateFn( // Note: Using an IIFE here to ensure that the spread assignment is not considered // a side-effect, ending up preserving `LINKED_SIGNAL_NODE` and `REACTIVE_NODE`. // TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved. -export const LINKED_SIGNAL_NODE = /* @__PURE__ */ (() => { +export const LINKED_SIGNAL_NODE: object = /* @__PURE__ */ (() => { return { ...REACTIVE_NODE, value: UNSET, diff --git a/packages/core/schematics/migrations/signal-migration/src/utils/is_identifier_free_in_scope.ts b/packages/core/schematics/migrations/signal-migration/src/utils/is_identifier_free_in_scope.ts index ea5ed640c5a..d483e21ec31 100644 --- a/packages/core/schematics/migrations/signal-migration/src/utils/is_identifier_free_in_scope.ts +++ b/packages/core/schematics/migrations/signal-migration/src/utils/is_identifier_free_in_scope.ts @@ -11,7 +11,7 @@ import ts from 'typescript'; import {isNodeDescendantOf} from './is_descendant_of'; /** Symbol that can be used to mark a variable as reserved, synthetically. */ -export const ReservedMarker = Symbol(); +export const ReservedMarker: unique symbol = Symbol(); // typescript/stable/src/compiler/types.ts;l=967;rcl=651008033 export interface LocalsContainer extends ts.Node { diff --git a/packages/core/src/authoring/input/input_signal.ts b/packages/core/src/authoring/input/input_signal.ts index 70cc3ebbbcc..42e70225487 100644 --- a/packages/core/src/authoring/input/input_signal.ts +++ b/packages/core/src/authoring/input/input_signal.ts @@ -57,8 +57,8 @@ export type InputOptionsWithTransform = Required< > & InputOptions; -export const ɵINPUT_SIGNAL_BRAND_READ_TYPE = /* @__PURE__ */ Symbol(); -export const ɵINPUT_SIGNAL_BRAND_WRITE_TYPE = /* @__PURE__ */ Symbol(); +export const ɵINPUT_SIGNAL_BRAND_READ_TYPE: unique symbol = /* @__PURE__ */ Symbol(); +export const ɵINPUT_SIGNAL_BRAND_WRITE_TYPE: unique symbol = /* @__PURE__ */ Symbol(); /** * `InputSignalWithTransform` represents a special `Signal` for a diff --git a/packages/core/src/authoring/input/input_signal_node.ts b/packages/core/src/authoring/input/input_signal_node.ts index ed1bbdc20e0..b521545b1a3 100644 --- a/packages/core/src/authoring/input/input_signal_node.ts +++ b/packages/core/src/authoring/input/input_signal_node.ts @@ -8,7 +8,7 @@ import {SIGNAL_NODE, SignalNode, signalSetFn} from '../../../primitives/signals'; -export const REQUIRED_UNSET_VALUE = /* @__PURE__ */ Symbol('InputSignalNode#UNSET'); +export const REQUIRED_UNSET_VALUE: unique symbol = /* @__PURE__ */ Symbol('InputSignalNode#UNSET'); /** * Reactive node type for an input signal. An input signal extends a signal. diff --git a/packages/core/src/di/interface/defs.ts b/packages/core/src/di/interface/defs.ts index f45c6c7224e..f90ac24249b 100644 --- a/packages/core/src/di/interface/defs.ts +++ b/packages/core/src/di/interface/defs.ts @@ -261,5 +261,5 @@ export function getInjectorDef(type: any): ɵɵInjectorDef | null { return type && type.hasOwnProperty(NG_INJ_DEF) ? (type as any)[NG_INJ_DEF] : null; } -export const NG_PROV_DEF = getClosureSafeProperty({ɵprov: getClosureSafeProperty}); -export const NG_INJ_DEF = getClosureSafeProperty({ɵinj: getClosureSafeProperty}); +export const NG_PROV_DEF: string = getClosureSafeProperty({ɵprov: getClosureSafeProperty}); +export const NG_INJ_DEF: string = getClosureSafeProperty({ɵinj: getClosureSafeProperty}); diff --git a/packages/core/src/di/provider_collection.ts b/packages/core/src/di/provider_collection.ts index 4a81ae0e6c2..9e13eb2aa45 100644 --- a/packages/core/src/di/provider_collection.ts +++ b/packages/core/src/di/provider_collection.ts @@ -403,7 +403,7 @@ function deepForEachProvider( } } -export const USE_VALUE = getClosureSafeProperty({ +export const USE_VALUE: string = getClosureSafeProperty({ provide: String, useValue: getClosureSafeProperty, }); diff --git a/packages/core/src/hydration/utils.ts b/packages/core/src/hydration/utils.ts index 672b5e9e48c..c0ec8f33d04 100644 --- a/packages/core/src/hydration/utils.ts +++ b/packages/core/src/hydration/utils.ts @@ -14,7 +14,7 @@ import {getDocument} from '../render3/interfaces/document'; import {RElement, RNode} from '../render3/interfaces/renderer_dom'; import {isRootView} from '../render3/interfaces/type_checks'; import {HEADER_OFFSET, LView, TVIEW, TViewType} from '../render3/interfaces/view'; -import {makeStateKey, TransferState} from '../transfer_state'; +import {makeStateKey, StateKey, TransferState} from '../transfer_state'; import {assertDefined, assertEqual} from '../util/assert'; import type {HydrationContext} from './annotate'; @@ -49,7 +49,8 @@ const TRANSFER_STATE_TOKEN_ID = '__nghData__'; /** * Lookup key used to reference DOM hydration data (ngh) in `TransferState`. */ -export const NGH_DATA_KEY = makeStateKey>(TRANSFER_STATE_TOKEN_ID); +export const NGH_DATA_KEY: StateKey = + makeStateKey>(TRANSFER_STATE_TOKEN_ID); /** * The name of the key used in the TransferState collection, @@ -60,9 +61,9 @@ export const TRANSFER_STATE_DEFER_BLOCKS_INFO = '__nghDeferData__'; /** * Lookup key used to retrieve defer block datain `TransferState`. */ -export const NGH_DEFER_BLOCKS_KEY = makeStateKey<{[key: string]: SerializedDeferBlock}>( - TRANSFER_STATE_DEFER_BLOCKS_INFO, -); +export const NGH_DEFER_BLOCKS_KEY: StateKey<{[key: string]: SerializedDeferBlock}> = makeStateKey<{ + [key: string]: SerializedDeferBlock; +}>(TRANSFER_STATE_DEFER_BLOCKS_INFO); /** * The name of the attribute that would be added to host component diff --git a/packages/core/src/render3/after_render/manager.ts b/packages/core/src/render3/after_render/manager.ts index 79c5264eec9..9a4e4e56395 100644 --- a/packages/core/src/render3/after_render/manager.ts +++ b/packages/core/src/render3/after_render/manager.ts @@ -37,7 +37,7 @@ export class AfterRenderManager { }); } -export const AFTER_RENDER_PHASES = /* @__PURE__ **/ (() => +export const AFTER_RENDER_PHASES: AfterRenderPhase[] = /* @__PURE__ **/ (() => [ AfterRenderPhase.EarlyRead, AfterRenderPhase.Write, diff --git a/packages/core/src/render3/dynamic_bindings.ts b/packages/core/src/render3/dynamic_bindings.ts index ba925c18b33..6f887a97fe5 100644 --- a/packages/core/src/render3/dynamic_bindings.ts +++ b/packages/core/src/render3/dynamic_bindings.ts @@ -18,7 +18,7 @@ import {stringifyForError} from './util/stringify_utils'; import {createOutputListener} from './view/directive_outputs'; /** Symbol used to store and retrieve metadata about a binding. */ -export const BINDING = /* @__PURE__ */ Symbol('BINDING'); +export const BINDING: unique symbol = /* @__PURE__ */ Symbol('BINDING'); /** * A dynamically-defined binding targeting. diff --git a/packages/core/src/render3/fields.ts b/packages/core/src/render3/fields.ts index a6792d4d927..9e840ebb48f 100644 --- a/packages/core/src/render3/fields.ts +++ b/packages/core/src/render3/fields.ts @@ -8,11 +8,11 @@ import {getClosureSafeProperty} from '../util/property'; -export const NG_COMP_DEF = getClosureSafeProperty({ɵcmp: getClosureSafeProperty}); -export const NG_DIR_DEF = getClosureSafeProperty({ɵdir: getClosureSafeProperty}); -export const NG_PIPE_DEF = getClosureSafeProperty({ɵpipe: getClosureSafeProperty}); -export const NG_MOD_DEF = getClosureSafeProperty({ɵmod: getClosureSafeProperty}); -export const NG_FACTORY_DEF = getClosureSafeProperty({ɵfac: getClosureSafeProperty}); +export const NG_COMP_DEF: string = getClosureSafeProperty({ɵcmp: getClosureSafeProperty}); +export const NG_DIR_DEF: string = getClosureSafeProperty({ɵdir: getClosureSafeProperty}); +export const NG_PIPE_DEF: string = getClosureSafeProperty({ɵpipe: getClosureSafeProperty}); +export const NG_MOD_DEF: string = getClosureSafeProperty({ɵmod: getClosureSafeProperty}); +export const NG_FACTORY_DEF: string = getClosureSafeProperty({ɵfac: getClosureSafeProperty}); /** * If a directive is diPublic, bloomAdd sets a property on the type with this constant as @@ -20,7 +20,9 @@ export const NG_FACTORY_DEF = getClosureSafeProperty({ɵfac: getClosureSafePrope * bloom filter bit for DI. */ // TODO(misko): This is wrong. The NG_ELEMENT_ID should never be minified. -export const NG_ELEMENT_ID = getClosureSafeProperty({__NG_ELEMENT_ID__: getClosureSafeProperty}); +export const NG_ELEMENT_ID: string = getClosureSafeProperty({ + __NG_ELEMENT_ID__: getClosureSafeProperty, +}); /** * The `NG_ENV_ID` field on a DI token indicates special processing in the `EnvironmentInjector`: @@ -30,4 +32,4 @@ export const NG_ELEMENT_ID = getClosureSafeProperty({__NG_ELEMENT_ID__: getClosu * This particular retrieval of DI tokens is mostly done to eliminate circular dependencies and * improve tree-shaking. */ -export const NG_ENV_ID = getClosureSafeProperty({__NG_ENV_ID__: getClosureSafeProperty}); +export const NG_ENV_ID: string = getClosureSafeProperty({__NG_ENV_ID__: getClosureSafeProperty}); diff --git a/packages/core/src/render3/reactivity/signal.ts b/packages/core/src/render3/reactivity/signal.ts index b62f89ecce7..10fad915b20 100644 --- a/packages/core/src/render3/reactivity/signal.ts +++ b/packages/core/src/render3/reactivity/signal.ts @@ -18,7 +18,7 @@ import { import {isSignal, Signal, ValueEqualityFn} from './api'; /** Symbol used distinguish `WritableSignal` from other non-writable signals and functions. */ -export const ɵWRITABLE_SIGNAL = /* @__PURE__ */ Symbol('WRITABLE_SIGNAL'); +export const ɵWRITABLE_SIGNAL: unique symbol = /* @__PURE__ */ Symbol('WRITABLE_SIGNAL'); /** * A `Signal` with a value that can be mutated via a setter interface. diff --git a/packages/core/src/sanitization/html_sanitizer.ts b/packages/core/src/sanitization/html_sanitizer.ts index ab0b1377c12..e2f5a72bedb 100644 --- a/packages/core/src/sanitization/html_sanitizer.ts +++ b/packages/core/src/sanitization/html_sanitizer.ts @@ -66,7 +66,7 @@ const INLINE_ELEMENTS = merge( ), ); -export const VALID_ELEMENTS = merge( +export const VALID_ELEMENTS: {[k: string]: boolean} = merge( VOID_ELEMENTS, BLOCK_ELEMENTS, INLINE_ELEMENTS, @@ -74,7 +74,9 @@ export const VALID_ELEMENTS = merge( ); // Attributes that have href and hence need to be sanitized -export const URI_ATTRS = tagSet('background,cite,href,itemtype,longdesc,poster,src,xlink:href'); +export const URI_ATTRS: {[k: string]: boolean} = tagSet( + 'background,cite,href,itemtype,longdesc,poster,src,xlink:href', +); const HTML_ATTRS = tagSet( 'abbr,accesskey,align,alt,autoplay,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,' + @@ -103,7 +105,7 @@ const ARIA_ATTRS = tagSet( // can be sanitized, but they increase security surface area without a legitimate use case, so they // are left out here. -export const VALID_ATTRS = merge(URI_ATTRS, HTML_ATTRS, ARIA_ATTRS); +export const VALID_ATTRS: {[k: string]: boolean} = merge(URI_ATTRS, HTML_ATTRS, ARIA_ATTRS); // Elements whose content should not be traversed/preserved, if the elements themselves are invalid. // diff --git a/packages/language-service/test/legacy/mock_host.ts b/packages/language-service/test/legacy/mock_host.ts index 25d1fbaddd5..857c3058fbf 100644 --- a/packages/language-service/test/legacy/mock_host.ts +++ b/packages/language-service/test/legacy/mock_host.ts @@ -31,7 +31,7 @@ const logger: ts.server.Logger = { }; export const TEST_SRCDIR = process.env['TEST_SRCDIR']!; -export const PROJECT_DIR = join( +export const PROJECT_DIR: string = join( TEST_SRCDIR, 'angular', 'packages', @@ -40,11 +40,11 @@ export const PROJECT_DIR = join( 'legacy', 'project', ); -export const TSCONFIG = join(PROJECT_DIR, 'tsconfig.json'); -export const APP_COMPONENT = join(PROJECT_DIR, 'app', 'app.component.ts'); -export const APP_MAIN = join(PROJECT_DIR, 'app', 'main.ts'); -export const PARSING_CASES = join(PROJECT_DIR, 'app', 'parsing-cases.ts'); -export const TEST_TEMPLATE = join(PROJECT_DIR, 'app', 'test.ng'); +export const TSCONFIG: string = join(PROJECT_DIR, 'tsconfig.json'); +export const APP_COMPONENT: string = join(PROJECT_DIR, 'app', 'app.component.ts'); +export const APP_MAIN: string = join(PROJECT_DIR, 'app', 'main.ts'); +export const PARSING_CASES: string = join(PROJECT_DIR, 'app', 'parsing-cases.ts'); +export const TEST_TEMPLATE: string = join(PROJECT_DIR, 'app', 'test.ng'); const NOOP_FILE_WATCHER: ts.FileWatcher = { close() {}, diff --git a/packages/platform-browser-dynamic/src/platform_providers.ts b/packages/platform-browser-dynamic/src/platform_providers.ts index 8eee1ce7b22..d854cc77a45 100644 --- a/packages/platform-browser-dynamic/src/platform_providers.ts +++ b/packages/platform-browser-dynamic/src/platform_providers.ts @@ -10,6 +10,7 @@ import { COMPILER_OPTIONS, CompilerFactory, createPlatformFactory, + PlatformRef, StaticProvider, } from '@angular/core'; import {platformBrowser} from '@angular/platform-browser'; @@ -29,8 +30,9 @@ export const INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS: StaticProvider[] = [ /** * @publicApi */ -export const platformBrowserDynamic = createPlatformFactory( - platformBrowser, - 'browserDynamic', - INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, -); +export const platformBrowserDynamic: (extraProviders?: StaticProvider[]) => PlatformRef = + createPlatformFactory( + platformBrowser, + 'browserDynamic', + INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, + ); diff --git a/packages/platform-browser-dynamic/testing/src/testing.ts b/packages/platform-browser-dynamic/testing/src/testing.ts index 48ce0a2d45b..a7cd8cba7c1 100644 --- a/packages/platform-browser-dynamic/testing/src/testing.ts +++ b/packages/platform-browser-dynamic/testing/src/testing.ts @@ -13,10 +13,8 @@ import {BrowserTestingModule} from '@angular/platform-browser/testing'; /** * @publicApi */ -export const platformBrowserDynamicTesting = createPlatformFactory( - platformBrowserDynamic, - 'browserDynamicTesting', -); +export const platformBrowserDynamicTesting: (extraProviders?: StaticProvider[]) => PlatformRef = + createPlatformFactory(platformBrowserDynamic, 'browserDynamicTesting'); /** * NgModule for testing. diff --git a/packages/platform-browser/testing/src/browser.ts b/packages/platform-browser/testing/src/browser.ts index 243f29a691d..6fa967ff965 100644 --- a/packages/platform-browser/testing/src/browser.ts +++ b/packages/platform-browser/testing/src/browser.ts @@ -15,6 +15,7 @@ import { ɵinternalProvideZoneChangeDetection as internalProvideZoneChangeDetection, ɵChangeDetectionScheduler as ChangeDetectionScheduler, ɵChangeDetectionSchedulerImpl as ChangeDetectionSchedulerImpl, + PlatformRef, } from '@angular/core'; import {TestComponentRenderer} from '@angular/core/testing'; import {BrowserModule, platformBrowser} from '../../index'; @@ -25,7 +26,8 @@ import {DOMTestComponentRenderer} from './dom_test_component_renderer'; * * @publicApi */ -export const platformBrowserTesting = createPlatformFactory(platformBrowser, 'browserTesting'); +export const platformBrowserTesting: (extraProviders?: StaticProvider[]) => PlatformRef = + createPlatformFactory(platformBrowser, 'browserTesting'); /** * NgModule for testing. diff --git a/packages/platform-server/test/hydration_utils.ts b/packages/platform-server/test/hydration_utils.ts index 91cac882982..f9f8a71c8bf 100644 --- a/packages/platform-server/test/hydration_utils.ts +++ b/packages/platform-server/test/hydration_utils.ts @@ -42,7 +42,7 @@ export const EMPTY_TEXT_NODE_COMMENT = 'ngetn'; export const TEXT_NODE_SEPARATOR_COMMENT = 'ngtns'; export const SKIP_HYDRATION_ATTR_NAME = 'ngSkipHydration'; -export const SKIP_HYDRATION_ATTR_NAME_LOWER_CASE = SKIP_HYDRATION_ATTR_NAME.toLowerCase(); +export const SKIP_HYDRATION_ATTR_NAME_LOWER_CASE: string = SKIP_HYDRATION_ATTR_NAME.toLowerCase(); export const TRANSFER_STATE_TOKEN_ID = '__nghData__'; diff --git a/packages/platform-server/testing/src/server.ts b/packages/platform-server/testing/src/server.ts index 3845ef48728..ac397d32080 100644 --- a/packages/platform-server/testing/src/server.ts +++ b/packages/platform-server/testing/src/server.ts @@ -6,7 +6,13 @@ * found in the LICENSE file at https://angular.dev/license */ -import {createPlatformFactory, NgModule, platformCore, StaticProvider} from '@angular/core'; +import { + createPlatformFactory, + NgModule, + platformCore, + PlatformRef, + StaticProvider, +} from '@angular/core'; import {ɵINTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS as INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS} from '@angular/platform-browser-dynamic'; import {BrowserDynamicTestingModule} from '@angular/platform-browser-dynamic/testing'; import { @@ -25,11 +31,12 @@ const INTERNAL_SERVER_DYNAMIC_PLATFORM_TESTING_PROVIDERS: StaticProvider[] = [ * @publicApi * @deprecated from v20.0.0, use e2e testing to verify SSR behavior. */ -export const platformServerTesting = createPlatformFactory( - platformCore, - 'serverTesting', - INTERNAL_SERVER_DYNAMIC_PLATFORM_TESTING_PROVIDERS, -); +export const platformServerTesting: (extraProviders?: StaticProvider[]) => PlatformRef = + createPlatformFactory( + platformCore, + 'serverTesting', + INTERNAL_SERVER_DYNAMIC_PLATFORM_TESTING_PROVIDERS, + ); /** * NgModule for testing. diff --git a/packages/router/src/shared.ts b/packages/router/src/shared.ts index 0cfc5924ecf..23ec9aa8c5e 100644 --- a/packages/router/src/shared.ts +++ b/packages/router/src/shared.ts @@ -21,7 +21,7 @@ export const PRIMARY_OUTLET = 'primary'; * static string or `Route.resolve` if anything else. This allows us to reuse the existing route * data/resolvers to support the title feature without new instrumentation in the `Router` pipeline. */ -export const RouteTitleKey = /* @__PURE__ */ Symbol('RouteTitle'); +export const RouteTitleKey: unique symbol = /* @__PURE__ */ Symbol('RouteTitle'); /** * A collection of matrix and query URL parameters. diff --git a/packages/upgrade/src/common/test/helpers/common_test_helpers.ts b/packages/upgrade/src/common/test/helpers/common_test_helpers.ts index 4737d55ee89..8534040dcf0 100644 --- a/packages/upgrade/src/common/test/helpers/common_test_helpers.ts +++ b/packages/upgrade/src/common/test/helpers/common_test_helpers.ts @@ -176,4 +176,5 @@ export function nodes(html: string) { return Array.prototype.slice.call(div.childNodes); } -export const withEachNg1Version = createWithEachNg1VersionFn(setAngularJSGlobal); +export const withEachNg1Version: (specSuite: () => void) => void = + createWithEachNg1VersionFn(setAngularJSGlobal); diff --git a/scripts/benchmarks/utils.mts b/scripts/benchmarks/utils.mts index 4ee95fcfa3d..52710a5d6e9 100644 --- a/scripts/benchmarks/utils.mts +++ b/scripts/benchmarks/utils.mts @@ -14,7 +14,7 @@ import url from 'url'; const scriptDir = path.dirname(url.fileURLToPath(import.meta.url)); /** Absolute disk path to the project directory. */ -export const projectDir = path.join(scriptDir, '../..'); +export const projectDir: string = path.join(scriptDir, '../..'); /** * Executes the given command, forwarding stdin, stdout and stderr while diff --git a/scripts/build/package-builder.mts b/scripts/build/package-builder.mts index 2af378825fa..440e3e6e3a5 100644 --- a/scripts/build/package-builder.mts +++ b/scripts/build/package-builder.mts @@ -16,7 +16,7 @@ import sh from 'shelljs'; sh.set('-e'); /** Path to the project directory. */ -export const projectDir = join(dirname(fileURLToPath(import.meta.url)), '../..'); +export const projectDir: string = join(dirname(fileURLToPath(import.meta.url)), '../..'); /** Command that runs Bazel. */ export const bazelCmd = process.env.BAZEL || `yarn -s bazel`; @@ -56,7 +56,7 @@ export function performDefaultSnapshotBuild(): BuiltPackage[] { function buildReleasePackages( distPath: string, isSnapshotBuild: boolean, - additionalTargets: string[] = [] + additionalTargets: string[] = [], ): BuiltPackage[] { console.info('######################################'); console.info(' Building release packages...'); @@ -120,7 +120,7 @@ function getPackageNamesOfTargets(targets: string[]): string[] { if (matches === null) { throw Error( `Found Bazel target with "${releaseTargetTag}" tag, but could not ` + - `determine release output name: ${targetName}` + `determine release output name: ${targetName}`, ); } return matches[1]; diff --git a/tools/tslint/noExportedInferredCallTypeRule.ts b/tools/tslint/noExportedInferredCallTypeRule.ts new file mode 100644 index 00000000000..8057124ec09 --- /dev/null +++ b/tools/tslint/noExportedInferredCallTypeRule.ts @@ -0,0 +1,57 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Replacement, RuleFailure} from 'tslint/lib'; +import {AbstractRule, TypedRule} from 'tslint/lib/rules'; +import {Rule as TypedefRule} from 'tslint/lib/rules/typedefRule'; +import ts from 'typescript'; + +/** + * Rule that ensures ("best effort") that exported constants are having + * an explicit type if they would otherwise rely on a synthetically inserted + * type that is inferred from a function call. E.g. + * + * ```ts + * export const myLoader = createImageLoader(); // Wrong! + * export const myLoader: ImageLoader = createImageLoader(); // Correct! + * ``` + */ +export class Rule extends TypedRule { + override applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] { + const checker = program.getTypeChecker(); + + return this.applyWithFunction(sourceFile, (ctx) => { + for (const st of sourceFile.statements) { + if ( + !ts.isVariableStatement(st) || + !(st.modifiers ?? []).some((s) => s.kind === ts.SyntaxKind.ExportKeyword) + ) { + continue; + } + + for (const decl of st.declarationList.declarations) { + if ( + decl.initializer !== undefined && + ts.isCallExpression(decl.initializer) && + decl.type === undefined + ) { + const inferredType = checker.getTypeAtLocation(decl.name); + const typeStr = checker.typeToString(inferredType, decl); + + ctx.addFailureAtNode( + decl, + 'No explicit type. Inferred types can cause unexpected issues. ' + + 'Please add an explicit type.', + Replacement.appendText(decl.name.end, `: ${typeStr}`), + ); + } + } + } + }); + } +} diff --git a/tslint.json b/tslint.json index e498369e72e..ec8c22c202e 100644 --- a/tslint.json +++ b/tslint.json @@ -11,6 +11,7 @@ // that rules written in TypeScript can be loaded without needing to be transpiled. "ts-node-loader": true, // Custom rules written in TypeScript. + "no-exported-inferred-call-type": true, "require-internal-with-underscore": true, "no-implicit-override-abstract": true, "validate-import-for-esm-cjs-interop": [ @@ -77,7 +78,10 @@ "name": ["*", "getMutableClone"], "message": "Use a ts.factory.update* or ts.factory.create* method instead." }, - {"name": ["performance", "mark"], "message": "`performance` methods aren't not fully supported in all environments like JSDOM and Cloudflare workers. Use 'performanceMark' from '@angular/core' instead."} + { + "name": ["performance", "mark"], + "message": "`performance` methods aren't not fully supported in all environments like JSDOM and Cloudflare workers. Use 'performanceMark' from '@angular/core' instead." + } ] }, "jsRules": {