Skip to content

Commit 48d77e4

Browse files
NerivecKoenkk
andauthored
fix: Enforce TS strict type checking (#23601)
* Enforce TS `strict` type checking. * updates * updates * updates * Updates * Updates * pretty * u * u * u * Updates * updates * Updates * Updates * `ReadonlyArray` * scenesChanged * objectID * Improve coverage * u * u * process feedback --------- Co-authored-by: Koen Kanters <[email protected]>
1 parent 63c3bb4 commit 48d77e4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1866
-1447
lines changed

lib/controller.ts

+29-13
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,28 @@ export class Controller {
119119
new ExtensionReport(...this.extensionArgs),
120120
new ExtensionExternalExtension(...this.extensionArgs),
121121
new ExtensionAvailability(...this.extensionArgs),
122-
settings.get().frontend && new ExtensionFrontend(...this.extensionArgs),
123-
settings.get().advanced.legacy_api && new ExtensionBridgeLegacy(...this.extensionArgs),
124-
settings.get().external_converters.length && new ExtensionExternalConverters(...this.extensionArgs),
125-
settings.get().homeassistant && new ExtensionHomeAssistant(...this.extensionArgs),
126-
/* istanbul ignore next */
127-
settings.get().advanced.soft_reset_timeout !== 0 && new ExtensionSoftReset(...this.extensionArgs),
128-
].filter((n) => n);
122+
];
123+
124+
if (settings.get().frontend) {
125+
this.extensions.push(new ExtensionFrontend(...this.extensionArgs));
126+
}
127+
128+
if (settings.get().advanced.legacy_api) {
129+
this.extensions.push(new ExtensionBridgeLegacy(...this.extensionArgs));
130+
}
131+
132+
if (settings.get().external_converters.length) {
133+
this.extensions.push(new ExtensionExternalConverters(...this.extensionArgs));
134+
}
135+
136+
if (settings.get().homeassistant) {
137+
this.extensions.push(new ExtensionHomeAssistant(...this.extensionArgs));
138+
}
139+
140+
/* istanbul ignore next */
141+
if (settings.get().advanced.soft_reset_timeout !== 0) {
142+
this.extensions.push(new ExtensionSoftReset(...this.extensionArgs));
143+
}
129144
}
130145

131146
async start(): Promise<void> {
@@ -143,7 +158,7 @@ export class Controller {
143158
logger.error('Failed to start zigbee');
144159
logger.error('Check https://www.zigbee2mqtt.io/guide/installation/20_zigbee2mqtt-fails-to-start.html for possible solutions');
145160
logger.error('Exiting...');
146-
logger.error(error.stack);
161+
logger.error((error as Error).stack!);
147162
return this.exit(1);
148163
}
149164

@@ -160,8 +175,9 @@ export class Controller {
160175
let deviceCount = 0;
161176

162177
for (const device of this.zigbee.devicesIterator(utils.deviceNotCoordinator)) {
178+
// `definition` validated by `isSupported`
163179
const model = device.isSupported
164-
? `${device.definition.model} - ${device.definition.vendor} ${device.definition.description}`
180+
? `${device.definition!.model} - ${device.definition!.vendor} ${device.definition!.description}`
165181
: 'Not supported';
166182
logger.info(`${device.name} (${device.ieeeAddr}): ${model} (${device.zh.type})`);
167183

@@ -180,14 +196,14 @@ export class Controller {
180196

181197
await this.zigbee.permitJoin(settings.get().permit_join);
182198
} catch (error) {
183-
logger.error(`Failed to set permit join to ${settings.get().permit_join} (${error.message})`);
199+
logger.error(`Failed to set permit join to ${settings.get().permit_join} (${(error as Error).message})`);
184200
}
185201

186202
// MQTT
187203
try {
188204
await this.mqtt.connect();
189205
} catch (error) {
190-
logger.error(`MQTT failed to connect, exiting... (${error.message})`);
206+
logger.error(`MQTT failed to connect, exiting... (${(error as Error).message})`);
191207
await this.zigbee.stop();
192208
return this.exit(1);
193209
}
@@ -252,7 +268,7 @@ export class Controller {
252268
await this.zigbee.stop();
253269
logger.info('Stopped Zigbee2MQTT');
254270
} catch (error) {
255-
logger.error(`Failed to stop Zigbee2MQTT (${error.message})`);
271+
logger.error(`Failed to stop Zigbee2MQTT (${(error as Error).message})`);
256272
code = 1;
257273
}
258274

@@ -377,7 +393,7 @@ export class Controller {
377393
await extension[method]?.();
378394
} catch (error) {
379395
/* istanbul ignore next */
380-
logger.error(`Failed to call '${extension.constructor.name}' '${method}' (${error.stack})`);
396+
logger.error(`Failed to call '${extension.constructor.name}' '${method}' (${(error as Error).stack})`);
381397
}
382398
}
383399
}

lib/eventBus.ts

+42-8
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,39 @@ import logger from './util/logger';
55
// eslint-disable-next-line
66
type ListenerKey = object;
77

8+
interface EventBusMap {
9+
adapterDisconnected: [];
10+
permitJoinChanged: [data: eventdata.PermitJoinChanged];
11+
publishAvailability: [];
12+
deviceRenamed: [data: eventdata.EntityRenamed];
13+
deviceRemoved: [data: eventdata.EntityRemoved];
14+
lastSeenChanged: [data: eventdata.LastSeenChanged];
15+
deviceNetworkAddressChanged: [data: eventdata.DeviceNetworkAddressChanged];
16+
deviceAnnounce: [data: eventdata.DeviceAnnounce];
17+
deviceInterview: [data: eventdata.DeviceInterview];
18+
deviceJoined: [data: eventdata.DeviceJoined];
19+
entityOptionsChanged: [data: eventdata.EntityOptionsChanged];
20+
exposesChanged: [data: eventdata.ExposesChanged];
21+
deviceLeave: [data: eventdata.DeviceLeave];
22+
deviceMessage: [data: eventdata.DeviceMessage];
23+
mqttMessage: [data: eventdata.MQTTMessage];
24+
mqttMessagePublished: [data: eventdata.MQTTMessagePublished];
25+
publishEntityState: [data: eventdata.PublishEntityState];
26+
groupMembersChanged: [data: eventdata.GroupMembersChanged];
27+
devicesChanged: [];
28+
scenesChanged: [data: eventdata.ScenesChanged];
29+
reconfigure: [data: eventdata.Reconfigure];
30+
stateChange: [data: eventdata.StateChange];
31+
}
32+
type EventBusListener<K> = K extends keyof EventBusMap
33+
? EventBusMap[K] extends unknown[]
34+
? (...args: EventBusMap[K]) => Promise<void> | void
35+
: never
36+
: never;
37+
838
export default class EventBus {
9-
private callbacksByExtension: {[s: string]: {event: string; callback: (...args: unknown[]) => void}[]} = {};
10-
private emitter = new events.EventEmitter();
39+
private callbacksByExtension: {[s: string]: {event: keyof EventBusMap; callback: EventBusListener<keyof EventBusMap>}[]} = {};
40+
private emitter = new events.EventEmitter<EventBusMap>();
1141

1242
constructor() {
1343
this.emitter.setMaxListeners(100);
@@ -167,18 +197,22 @@ export default class EventBus {
167197
this.on('stateChange', callback, key);
168198
}
169199

170-
private on(event: string, callback: (...args: unknown[]) => Promise<void> | void, key: ListenerKey): void {
171-
if (!this.callbacksByExtension[key.constructor.name]) this.callbacksByExtension[key.constructor.name] = [];
172-
const wrappedCallback = async (...args: unknown[]): Promise<void> => {
200+
private on<K extends keyof EventBusMap>(event: K, callback: EventBusListener<K>, key: ListenerKey): void {
201+
if (!this.callbacksByExtension[key.constructor.name]) {
202+
this.callbacksByExtension[key.constructor.name] = [];
203+
}
204+
205+
const wrappedCallback = async (...args: never[]): Promise<void> => {
173206
try {
174207
await callback(...args);
175208
} catch (error) {
176-
logger.error(`EventBus error '${key.constructor.name}/${event}': ${error.message}`);
177-
logger.debug(error.stack);
209+
logger.error(`EventBus error '${key.constructor.name}/${event}': ${(error as Error).message}`);
210+
logger.debug((error as Error).stack!);
178211
}
179212
};
213+
180214
this.callbacksByExtension[key.constructor.name].push({event, callback: wrappedCallback});
181-
this.emitter.on(event, wrappedCallback);
215+
this.emitter.on(event, wrappedCallback as EventBusListener<K>);
182216
}
183217

184218
public removeListeners(key: ListenerKey): void {

lib/extension/availability.ts

+18-11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import assert from 'assert';
12
import bind from 'bind-decorator';
23
import debounce from 'debounce';
34
import * as zhc from 'zigbee-herdsman-converters';
@@ -41,9 +42,12 @@ export default class Availability extends Extension {
4142
}
4243

4344
private isAvailable(entity: Device | Group): boolean {
44-
return entity.isDevice()
45-
? Date.now() - entity.zh.lastSeen < this.getTimeout(entity)
46-
: entity.membersDevices().length === 0 || entity.membersDevices().some((d) => this.availabilityCache[d.ieeeAddr]);
45+
if (entity.isDevice()) {
46+
return Date.now() - (entity.zh.lastSeen ?? /* istanbul ignore next */ 0) < this.getTimeout(entity);
47+
} else {
48+
const membersDevices = entity.membersDevices();
49+
return membersDevices.length === 0 || membersDevices.some((d) => this.availabilityCache[d.ieeeAddr]);
50+
}
4751
}
4852

4953
private resetTimer(device: Device): void {
@@ -92,7 +96,7 @@ export default class Availability extends Extension {
9296
logger.debug(`Successfully pinged '${device.name}' (attempt ${i}/${attempts})`);
9397
break;
9498
} catch (error) {
95-
logger.warning(`Failed to ping '${device.name}' (attempt ${i}/${attempts}, ${error.message})`);
99+
logger.warning(`Failed to ping '${device.name}' (attempt ${i}/${attempts}, ${(error as Error).message})`);
96100

97101
// Try again in 3 seconds.
98102
if (i !== attempts) {
@@ -125,7 +129,7 @@ export default class Availability extends Extension {
125129

126130
this.eventBus.onEntityRenamed(this, async (data) => {
127131
if (utils.isAvailabilityEnabledForEntity(data.entity, settings.get())) {
128-
await this.mqtt.publish(`${data.from}/availability`, null, {retain: true, qos: 1});
132+
await this.mqtt.publish(`${data.from}/availability`, '', {retain: true, qos: 1});
129133
await this.publishAvailability(data.entity, false, true);
130134
}
131135
});
@@ -162,7 +166,8 @@ export default class Availability extends Extension {
162166

163167
private async publishAvailability(entity: Device | Group, logLastSeen: boolean, forcePublish = false, skipGroups = false): Promise<void> {
164168
if (logLastSeen && entity.isDevice()) {
165-
const ago = Date.now() - entity.zh.lastSeen;
169+
const ago = Date.now() - (entity.zh.lastSeen ?? /* istanbul ignore next */ 0);
170+
166171
if (this.isActiveDevice(entity)) {
167172
logger.debug(`Active device '${entity.name}' was last seen '${(ago / utils.minutes(1)).toFixed(2)}' minutes ago.`);
168173
} else {
@@ -230,22 +235,24 @@ export default class Availability extends Extension {
230235
continue;
231236
}
232237

233-
const converter = device.definition.toZigbee.find((c) => !c.key || c.key.find((k) => item.keys.includes(k)));
238+
const converter = device.definition!.toZigbee.find((c) => !c.key || c.key.find((k) => item.keys.includes(k)));
234239
const options: KeyValue = device.options;
235240
const state = this.state.get(device);
236241
const meta: zhc.Tz.Meta = {
237242
message: this.state.get(device),
238-
mapped: device.definition,
239-
endpoint_name: null,
243+
mapped: device.definition!,
244+
endpoint_name: undefined,
240245
options,
241246
state,
242247
device: device.zh,
243248
};
244249

245250
try {
246-
await converter?.convertGet?.(device.endpoint(), item.keys[0], meta);
251+
const endpoint = device.endpoint();
252+
assert(endpoint);
253+
await converter?.convertGet?.(endpoint, item.keys[0], meta);
247254
} catch (error) {
248-
logger.error(`Failed to read state of '${device.name}' after reconnect (${error.message})`);
255+
logger.error(`Failed to read state of '${device.name}' after reconnect (${(error as Error).message})`);
249256
}
250257

251258
await utils.sleep(500);

0 commit comments

Comments
 (0)