fix: type the five any that slipped past the gate

Пять мест, найденных при разборе #388: они проехали мимо гейта #342, пока
диапазоны были узкими, и он их больше не покажет.

Кэш климата (houseplan-card.ts) сравнивается только по ссылке, значение не
читается ни разу — поэтому hass стал `unknown`: он и запрещает случайно
воспользоваться содержимым, и не врёт про форму объекта, которую HA нам не
обещает. Правила и маркеры типизированы по-настоящему, их типы известны.

Реестр HA (houseplan-editor-runtime.ts) описан минимальной структурной
формой: код читает ровно два поля и оба защищённо, так что форма честнее
`any` — она говорит, на что код опирается.

Ключ i18n `device_inbox.reason_excluded_integration` не нуждался в приведении
вовсе: он есть в словаре. Шаблонный ключ приведён к `I18nKey`, как уже
сделано в этом файле строкой 8013 — это утверждение о пространстве ключей, а
не отключение проверок; `as any` заодно снимал контроль и со второго
аргумента.

Поведение не меняется: аннотации типов стираются, эмит тот же. Бандл
пересобран, потому что в него вшит отпечаток исходников.

Issue: #390
User-Visible: no
This commit is contained in:
Matysh
2026-08-30 17:36:23 +03:00
parent 66db451280
commit cbf5cc1baa
20 changed files with 158 additions and 143 deletions
+9 -1
View File
@@ -12076,8 +12076,16 @@ export class HouseplanCard extends LitElement {
return key ? this._climate().get(key)?.hum ?? null : null;
}
// Ключи сравниваются только по ссылке (`===`), значение не читается ни разу.
// Поэтому `unknown` для hass точнее любого структурного типа: он и запрещает
// случайно воспользоваться содержимым, и не врёт про форму объекта, которую
// HA нам не обещает. Правила и маркеры при этом типизированы по-настоящему.
private _climateCache: {
h: any; r: any; mk: any; ex: string[] | undefined; m: Map<string, AreaClimate>;
h: unknown;
r: CompiledIconRule[] | undefined;
mk: Marker[] | undefined;
ex: string[] | undefined;
m: Map<string, AreaClimate>;
} | null = null;
/**
+13 -6
View File
@@ -11824,12 +11824,19 @@ public _discoveryFilterState(dialog: DeviceInboxDialogState): {
* identifier domains), for the exclusion search. */
private _registryIntegrations(): string[] {
const found = new Set<string>();
const full = this.host._fullRegistryHass;
for (const reg of Object.values<any>(full?.entities || {})) {
// Реестр HA нетипизирован на своей стороне, но здесь читаются ровно два
// поля и оба защищённо, поэтому хватает минимальной структурной формы —
// она честнее `any`: описывает то, на что код действительно опирается.
const full = this.host._fullRegistryHass as {
entities?: Record<string, { platform?: unknown } | undefined>;
devices?: Record<string, { identifiers?: unknown[] } | undefined>;
} | null | undefined;
for (const reg of Object.values(full?.entities || {})) {
if (reg?.platform) found.add(String(reg.platform));
}
for (const device of Object.values<any>(full?.devices || {})) {
const domain = Array.isArray(device?.identifiers?.[0]) ? device.identifiers[0][0] : null;
for (const device of Object.values(full?.devices || {})) {
const first = device?.identifiers?.[0];
const domain = Array.isArray(first) ? first[0] : null;
if (domain) found.add(String(domain));
}
return [...found].sort();
@@ -12060,9 +12067,9 @@ public _renderDeviceInbox(): TemplateResult {
</div>
<div class="device-inbox-reason">
${row.reason === 'excluded_integration' && row.integration
? this.host._t('device_inbox.reason_excluded_integration' as any,
? this.host._t('device_inbox.reason_excluded_integration',
{ integration: row.integration })
: this.host._t(`device_inbox.reason_${row.reason}` as any)}
: this.host._t(`device_inbox.reason_${row.reason}` as I18nKey)}
${status ? html`<span class="device-inbox-status">${status}</span>` : nothing}
</div>
<code>${row.binding}</code>