From b10021c79bcadb95d1f2147e28d18d4741cd6a27 Mon Sep 17 00:00:00 2001 From: splincode Date: Tue, 31 Mar 2026 15:00:34 +0300 Subject: [PATCH] refactor(localize): replace `any` with `unknown` in messages and translations utils MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace unsafe `any` type annotations with `unknown` across the localize utility layer to improve type safety and catch potential type errors at compile time rather than at runtime. Changes in `messages.ts`: - `ParsedMessage.substitutions`: `Record` → `Record` - `parseMessage` parameter `expressions`: `readonly any[]` → `readonly unknown[]` - Local `substitutions` variable: `{[key: string]: any}` → `Record` Changes in `translations.ts`: - `isMissingTranslationError` parameter: `any` → `unknown`, with proper narrowing (`typeof e === 'object' && e !== null`) before property access - `MissingTranslationError.type` visibility: `private` → `readonly` to allow access through the narrowed `unknown` type in the type guard - `translate` parameter and return type: `readonly any[]` → `readonly unknown[]` - `makeTemplateObject` cast: `cooked as any` → `cooked as unknown as TemplateStringsArray` Fix in `mock_message.ts` (test helper): - `substitutions: []` → `substitutions: {}` — the array literal was only assignable because the field was typed as `any`; the correct empty value for a `Record` is an object literal --- packages/localize/src/utils/src/messages.ts | 6 +++--- packages/localize/src/utils/src/translations.ts | 16 ++++++++++------ .../extract/translation_files/mock_message.ts | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/localize/src/utils/src/messages.ts b/packages/localize/src/utils/src/messages.ts index 61f6ffb6db1..0b7e0a43c50 100644 --- a/packages/localize/src/utils/src/messages.ts +++ b/packages/localize/src/utils/src/messages.ts @@ -138,7 +138,7 @@ export interface ParsedMessage extends MessageMetadata { /** * A mapping of placeholder names to substitution values. */ - substitutions: Record; + substitutions: Record; /** * An optional mapping of placeholder names to associated MessageIds. * This can be used to match ICU placeholders to the message that contains the ICU. @@ -170,12 +170,12 @@ export interface ParsedMessage extends MessageMetadata { */ export function parseMessage( messageParts: TemplateStringsArray, - expressions?: readonly any[], + expressions?: readonly unknown[], location?: SourceLocation, messagePartLocations?: (SourceLocation | undefined)[], expressionLocations: (SourceLocation | undefined)[] = [], ): ParsedMessage { - const substitutions: {[placeholderName: string]: any} = {}; + const substitutions: Record = {}; const substitutionLocations: {[placeholderName: string]: SourceLocation | undefined} = {}; const associatedMessageIds: {[placeholderName: string]: MessageId} = {}; const metadata = parseMetadata(messageParts[0], messageParts.raw[0]); diff --git a/packages/localize/src/utils/src/translations.ts b/packages/localize/src/utils/src/translations.ts index 00a3751d38b..d947f61a19b 100644 --- a/packages/localize/src/utils/src/translations.ts +++ b/packages/localize/src/utils/src/translations.ts @@ -22,14 +22,18 @@ export interface ParsedTranslation extends MessageMetadata { export type ParsedTranslations = Record; export class MissingTranslationError extends Error { - private readonly type = 'MissingTranslationError'; + readonly type = 'MissingTranslationError'; constructor(readonly parsedMessage: ParsedMessage) { super(`No translation found for ${describeMessage(parsedMessage)}.`); } } -export function isMissingTranslationError(e: any): e is MissingTranslationError { - return e.type === 'MissingTranslationError'; +export function isMissingTranslationError(e: unknown): e is MissingTranslationError { + return ( + typeof e === 'object' && + e !== null && + (e as MissingTranslationError).type === 'MissingTranslationError' + ); } /** @@ -51,8 +55,8 @@ export function isMissingTranslationError(e: any): e is MissingTranslationError export function translate( translations: Record, messageParts: TemplateStringsArray, - substitutions: readonly any[], -): [TemplateStringsArray, readonly any[]] { + substitutions: readonly unknown[], +): [TemplateStringsArray, readonly unknown[]] { const message = parseMessage(messageParts, substitutions); // Look up the translation using the messageId, and then the legacyId if available. let translation = translations[message.id]; @@ -137,7 +141,7 @@ export function makeParsedTranslation( */ export function makeTemplateObject(cooked: string[], raw: string[]): TemplateStringsArray { Object.defineProperty(cooked, 'raw', {value: raw}); - return cooked as any; + return cooked as unknown as TemplateStringsArray; } function describeMessage(message: ParsedMessage): string { diff --git a/packages/localize/tools/test/extract/translation_files/mock_message.ts b/packages/localize/tools/test/extract/translation_files/mock_message.ts index c16261d96e4..8905153cd7e 100644 --- a/packages/localize/tools/test/extract/translation_files/mock_message.ts +++ b/packages/localize/tools/test/extract/translation_files/mock_message.ts @@ -35,7 +35,7 @@ export function mockMessage( text += `{$${placeholderNames[i - 1]}}${messageParts[i]}`; } return { - substitutions: [], + substitutions: {}, ...options, id: options.customId || id, // customId trumps id text,