From c27de2c2caec831199d70dfb6c04c82bb12c42e6 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Fri, 22 Sep 2023 15:04:24 +0200 Subject: [PATCH] chore(core): enable eslint/strict-boolean-expressions & strictNullChecks --- packages/core/.eslintrc.json | 5 +- packages/core/src/codecs/json-codec.ts | 2 +- packages/core/src/codecs/netconf-codec.ts | 16 ++--- packages/core/src/codecs/octetstream-codec.ts | 14 ++-- packages/core/src/codecs/text-codec.ts | 2 +- packages/core/src/consumed-thing.ts | 36 +++++----- packages/core/src/content-serdes.ts | 2 +- packages/core/src/exposed-thing.ts | 67 ++++++++--------- packages/core/src/helpers.ts | 10 +-- packages/core/src/interaction-output.ts | 2 +- packages/core/src/protocol-helpers.ts | 71 +++++++++---------- .../core/src/protocol-listener-registry.ts | 2 +- packages/core/src/wot-impl.ts | 2 +- packages/core/test/ContentSerdesTest.ts | 5 +- packages/core/tsconfig.json | 3 +- 15 files changed, 123 insertions(+), 116 deletions(-) diff --git a/packages/core/.eslintrc.json b/packages/core/.eslintrc.json index c4237119e..70b9f134b 100644 --- a/packages/core/.eslintrc.json +++ b/packages/core/.eslintrc.json @@ -1,3 +1,6 @@ { - "extends": "../../.eslintrc.js" + "extends": "../../.eslintrc.js", + "rules": { + "@typescript-eslint/strict-boolean-expressions": ["error"] + } } diff --git a/packages/core/src/codecs/json-codec.ts b/packages/core/src/codecs/json-codec.ts index 39ebb042c..a414406bd 100644 --- a/packages/core/src/codecs/json-codec.ts +++ b/packages/core/src/codecs/json-codec.ts @@ -24,7 +24,7 @@ export default class JsonCodec implements ContentCodec { private subMediaType: string; constructor(subMediaType?: string) { - if (!subMediaType) { + if (subMediaType == null) { this.subMediaType = ContentSerdes.DEFAULT; // 'application/json' } else { this.subMediaType = subMediaType; diff --git a/packages/core/src/codecs/netconf-codec.ts b/packages/core/src/codecs/netconf-codec.ts index 3a638ffbd..539d76690 100644 --- a/packages/core/src/codecs/netconf-codec.ts +++ b/packages/core/src/codecs/netconf-codec.ts @@ -73,7 +73,7 @@ export default class NetconfCodec implements ContentCodec { if (hasNamespace) { // expect to have xmlns const properties = schema.properties; - if (!properties) { + if (properties == null) { throw new Error(`Missing "properties" field in TD`); } let nsFound = false; @@ -81,16 +81,16 @@ export default class NetconfCodec implements ContentCodec { let value; for (const key in properties) { const el = properties[key]; - if (!payload[key]) { + if (payload[key] == null) { throw new Error(`Payload is missing '${key}' field specified in TD`); } - if (el["nc:attribute"] === true && payload[key]) { + if (el["nc:attribute"] === true && payload[key] != null) { // if (el.format && el.format === 'urn') const ns: string = payload[key] as string; aliasNs = ns.split(":")[ns.split(":").length - 1]; NSs[aliasNs] = payload[key]; nsFound = true; - } else if (payload[key]) { + } else if (payload[key] != null) { value = payload[key]; } } @@ -102,10 +102,10 @@ export default class NetconfCodec implements ContentCodec { } } - if (schema && schema.type && schema.type === "object" && schema.properties) { + if (schema?.type === "object" && schema.properties != null) { // nested object, go down let tmpObj; - if (schema.properties && schema["nc:container"]) { + if (schema["nc:container"] != null) { // check the root level tmpObj = this.getPayloadNamespaces(schema, payload, NSs, true); // root case } else { @@ -118,10 +118,10 @@ export default class NetconfCodec implements ContentCodec { // once here schema is properties for (const key in schema) { - if ((schema[key].type && schema[key].type === "object") || hasNamespace) { + if (schema[key].type === "object" || hasNamespace) { // go down only if it is a nested object or it has a namespace let tmpHasNamespace = false; - if (schema[key].properties && schema[key]["nc:container"]) { + if (schema[key].properties != null && schema[key]["nc:container"] != null) { tmpHasNamespace = true; } const tmpObj = this.getPayloadNamespaces( diff --git a/packages/core/src/codecs/octetstream-codec.ts b/packages/core/src/codecs/octetstream-codec.ts index 4be3f69da..4ede2be79 100644 --- a/packages/core/src/codecs/octetstream-codec.ts +++ b/packages/core/src/codecs/octetstream-codec.ts @@ -59,11 +59,11 @@ export default class OctetstreamCodec implements ContentCodec { debug("OctetstreamCodec parsing", bytes); debug("Parameters", parameters); - const bigendian = !parameters.byteSeq?.includes(Endianness.LITTLE_ENDIAN); // default to big endian + const bigendian = !(parameters.byteSeq?.includes(Endianness.LITTLE_ENDIAN) === true); // default to big endian let signed = parameters.signed !== "false"; // default to signed // check length if specified - if (parameters.length && parseInt(parameters.length) !== bytes.length) { + if (parameters.length != null && parseInt(parameters.length) !== bytes.length) { throw new Error("Lengths do not match, required: " + parameters.length + " provided: " + bytes.length); } @@ -83,7 +83,7 @@ export default class OctetstreamCodec implements ContentCodec { } // Handle byte swapping - if (parameters.byteSeq?.includes("BYTE_SWAP") && dataLength > 1) { + if (parameters.byteSeq?.includes("BYTE_SWAP") === true && dataLength > 1) { bytes.swap16(); } @@ -184,13 +184,13 @@ export default class OctetstreamCodec implements ContentCodec { valueToBytes(value: unknown, schema?: DataSchema, parameters: { [key: string]: string | undefined } = {}): Buffer { debug(`OctetstreamCodec serializing '${value}'`); - if (!parameters.length) { + if (parameters.length == null) { warn("Missing 'length' parameter necessary for write. I'll do my best"); } - const bigendian = !parameters.byteSeq?.includes(Endianness.LITTLE_ENDIAN); // default to bigendian + const bigendian = !(parameters.byteSeq?.includes(Endianness.LITTLE_ENDIAN) === true); // default to bigendian let signed = parameters.signed !== "false"; // if signed is undefined -> true (default) - let length = parameters.length ? parseInt(parameters.length) : undefined; + let length = parameters.length != null ? parseInt(parameters.length) : undefined; if (value === undefined) { throw new Error("Undefined value"); @@ -212,7 +212,7 @@ export default class OctetstreamCodec implements ContentCodec { switch (dataType) { case "boolean": - return Buffer.alloc(length ?? 1, value ? 255 : 0); + return Buffer.alloc(length ?? 1, value != null ? 255 : 0); case "byte": case "short": case "int": diff --git a/packages/core/src/codecs/text-codec.ts b/packages/core/src/codecs/text-codec.ts index a24846c8c..e4f9061de 100644 --- a/packages/core/src/codecs/text-codec.ts +++ b/packages/core/src/codecs/text-codec.ts @@ -23,7 +23,7 @@ export default class TextCodec implements ContentCodec { private subMediaType: string; constructor(subMediaType?: string) { - this.subMediaType = !subMediaType ? "text/plain" : subMediaType; + this.subMediaType = subMediaType == null ? "text/plain" : subMediaType; } getMediaType(): string { diff --git a/packages/core/src/consumed-thing.ts b/packages/core/src/consumed-thing.ts index 1ee9171cb..4bf3461d6 100644 --- a/packages/core/src/consumed-thing.ts +++ b/packages/core/src/consumed-thing.ts @@ -166,10 +166,10 @@ class InternalPropertySubscription extends InternalSubscription { public async unobserveProperty(options: WoT.InteractionOptions = {}): Promise { const tp = this.thing.properties[this.name]; - if (!tp) { + if (tp == null) { throw new Error(`ConsumedThing '${this.thing.title}' does not have property ${this.name}`); } - if (!options.formIndex) { + if (options.formIndex == null) { options.formIndex = this.matchingUnsubscribeForm(); } const { form } = this.thing.getClientFor(tp.forms, "unobserveproperty", Affordance.PropertyAffordance, options); @@ -295,11 +295,11 @@ class InternalEventSubscription extends InternalSubscription { public async unsubscribeEvent(options: WoT.InteractionOptions = {}): Promise { const te = this.thing.events[this.name]; - if (!te) { + if (te == null) { throw new Error(`ConsumedThing '${this.thing.title}' does not have event ${this.name}`); } - if (!options.formIndex) { + if (options.formIndex == null) { options.formIndex = this.matchingUnsubscribeForm(); } @@ -317,7 +317,7 @@ class InternalEventSubscription extends InternalSubscription { private matchingUnsubscribeForm(): number { const refForm = this.thing.events[this.name].forms[this.formIndex]; // Here we have to keep in mind that op default is ["subscribeevent", "unsubscribeevent"] - if (!refForm.op || (Array.isArray(refForm.op) && refForm.op.includes("unsubscribeevent"))) { + if (refForm.op == null || (Array.isArray(refForm.op) && refForm.op.includes("unsubscribeevent"))) { // we can re-use the same form for unsubscribe return this.formIndex; } @@ -450,7 +450,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { for (const s of security) { const ws = this.securityDefinitions[s + ""]; // String vs. string (fix wot-typescript-definitions?) // also push nosec in case of proxy - if (ws) { + if (ws != null) { scs.push(ws); } } @@ -458,7 +458,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { } ensureClientSecurity(client: ProtocolClient, form: TD.Form | undefined): void { - if (this.securityDefinitions) { + if (this.securityDefinitions != null) { const logStatement = () => debug(`ConsumedThing '${this.title}' setting credentials for ${client} based on thing security`); @@ -471,7 +471,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { this.getSecuritySchemes(form.security), this.getServient().retrieveCredentials(this.id) ); - } else if (this.security && Array.isArray(this.security) && this.security.length > 0) { + } else if (this.security != null && Array.isArray(this.security) && this.security.length > 0) { logStatement(); client.setSecurity( this.getSecuritySchemes(this.security as string[]), @@ -551,7 +551,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { async readProperty(propertyName: string, options?: WoT.InteractionOptions): Promise { // TODO pass expected form op to getClientFor() const tp = this.properties[propertyName]; - if (!tp) { + if (tp == null) { throw new Error(`ConsumedThing '${this.title}' does not have property ${propertyName}`); } @@ -559,7 +559,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { if (!form) { throw new Error(`ConsumedThing '${this.title}' did not get suitable form`); } - if (!client) { + if (client == null) { throw new Error(`ConsumedThing '${this.title}' did not get suitable client for ${form.href}`); } debug(`ConsumedThing '${this.title}' reading ${form.href}`); @@ -618,14 +618,14 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { ): Promise { // TODO pass expected form op to getClientFor() const tp = this.properties[propertyName]; - if (!tp) { + if (tp == null) { throw new Error(`ConsumedThing '${this.title}' does not have property ${propertyName}`); } let { client, form } = this.getClientFor(tp.forms, "writeproperty", Affordance.PropertyAffordance, options); if (!form) { throw new Error(`ConsumedThing '${this.title}' did not get suitable form`); } - if (!client) { + if (client == null) { throw new Error(`ConsumedThing '${this.title}' did not get suitable client for ${form.href}`); } debug(`ConsumedThing '${this.title}' writing ${form.href} with '${value}'`); @@ -661,14 +661,14 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { options?: WoT.InteractionOptions ): Promise { const ta = this.actions[actionName]; - if (!ta) { + if (ta == null) { throw new Error(`ConsumedThing '${this.title}' does not have action ${actionName}`); } let { client, form } = this.getClientFor(ta.forms, "invokeaction", Affordance.ActionAffordance, options); if (!form) { throw new Error(`ConsumedThing '${this.title}' did not get suitable form`); } - if (!client) { + if (client == null) { throw new Error(`ConsumedThing '${this.title}' did not get suitable client for ${form.href}`); } debug( @@ -714,14 +714,14 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { options?: WoT.InteractionOptions ): Promise { const tp = this.properties[name]; - if (!tp) { + if (tp == null) { throw new Error(`ConsumedThing '${this.title}' does not have property ${name}`); } const { client, form } = this.getClientFor(tp.forms, "observeproperty", Affordance.PropertyAffordance, options); if (!form) { throw new Error(`ConsumedThing '${this.title}' did not get suitable form`); } - if (!client) { + if (client == null) { throw new Error(`ConsumedThing '${this.title}' did not get suitable client for ${form.href}`); } if (this.observedProperties.has(name)) { @@ -772,14 +772,14 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing { options?: WoT.InteractionOptions ): Promise { const te = this.events[name]; - if (!te) { + if (te == null) { throw new Error(`ConsumedThing '${this.title}' does not have event ${name}`); } const { client, form } = this.getClientFor(te.forms, "subscribeevent", Affordance.EventAffordance, options); if (!form) { throw new Error(`ConsumedThing '${this.title}' did not get suitable form`); } - if (!client) { + if (client == null) { throw new Error(`ConsumedThing '${this.title}' did not get suitable client for ${form.href}`); } if (this.subscribedEvents.has(name)) { diff --git a/packages/core/src/content-serdes.ts b/packages/core/src/content-serdes.ts index bcc929c51..799f07c82 100644 --- a/packages/core/src/content-serdes.ts +++ b/packages/core/src/content-serdes.ts @@ -57,7 +57,7 @@ export class ContentSerdes { private offered: Set = new Set(); public static get(): ContentSerdes { - if (!this.instance) { + if (this.instance == null) { this.instance = new ContentSerdes(); // JSON this.instance.addCodec(new JsonCodec(), true); diff --git a/packages/core/src/exposed-thing.ts b/packages/core/src/exposed-thing.ts index 846c82a60..fe8b58345 100644 --- a/packages/core/src/exposed-thing.ts +++ b/packages/core/src/exposed-thing.ts @@ -141,7 +141,7 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { } public emitEvent(name: string, data: WoT.InteractionInput): void { - if (this.events[name]) { + if (this.events[name] != null) { const eventAffordance = this.events[name]; this.__eventListeners.notify(eventAffordance, data, eventAffordance.data); } else { @@ -151,7 +151,7 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { } public async emitPropertyChange(name: string): Promise { - if (this.properties[name]) { + if (this.properties[name] != null) { const property = this.properties[name]; const readHandler = this.__propertyHandlers.get(name)?.readHandler; @@ -200,9 +200,9 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { setPropertyReadHandler(propertyName: string, handler: WoT.PropertyReadHandler): WoT.ExposedThing { debug(`ExposedThing '${this.title}' setting read handler for '${propertyName}'`); - if (this.properties[propertyName]) { + if (this.properties[propertyName] != null) { // setting read handler for writeOnly not allowed - if (this.properties[propertyName].writeOnly) { + if (this.properties[propertyName].writeOnly === true) { throw new Error( `ExposedThing '${this.title}' cannot set read handler for property '${propertyName}' due to writeOnly flag` ); @@ -225,9 +225,9 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { /** @inheritDoc */ setPropertyWriteHandler(propertyName: string, handler: WoT.PropertyWriteHandler): WoT.ExposedThing { debug(`ExposedThing '${this.title}' setting write handler for '${propertyName}'`); - if (this.properties[propertyName]) { + if (this.properties[propertyName] != null) { // setting write handler for readOnly not allowed - if (this.properties[propertyName].readOnly) { + if (this.properties[propertyName].readOnly === true) { throw new Error( `ExposedThing '${this.title}' cannot set write handler for property '${propertyName}' due to readOnly flag` ); @@ -251,8 +251,8 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { setPropertyObserveHandler(name: string, handler: WoT.PropertyReadHandler): WoT.ExposedThing { debug(`ExposedThing '${this.title}' setting property observe handler for '${name}'`); - if (this.properties[name]) { - if (!this.properties[name].observable) { + if (this.properties[name] != null) { + if (!(this.properties[name].observable === true)) { throw new Error( `ExposedThing '${this.title}' cannot set observe handler for property '${name}' since the observable flag is set to false` ); @@ -275,8 +275,8 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { setPropertyUnobserveHandler(name: string, handler: WoT.PropertyReadHandler): WoT.ExposedThing { debug(`ExposedThing '${this.title}' setting property unobserve handler for '${name}'`); - if (this.properties[name]) { - if (!this.properties[name].observable) { + if (this.properties[name] != null) { + if (!(this.properties[name].observable === true)) { throw new Error( `ExposedThing '${this.title}' cannot set unobserve handler for property '${name}' due to missing observable flag` ); @@ -299,7 +299,7 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { setActionHandler(actionName: string, handler: WoT.ActionHandler): WoT.ExposedThing { debug(`ExposedThing '${this.title}' setting action handler for '${actionName}'`); - if (this.actions[actionName]) { + if (this.actions[actionName] != null) { this.__actionHandlers.set(actionName, handler); } else { throw new Error(`ExposedThing '${this.title}' has no Action '${actionName}'`); @@ -311,7 +311,7 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { setEventSubscribeHandler(name: string, handler: WoT.EventSubscriptionHandler): WoT.ExposedThing { debug(`ExposedThing '${this.title}' setting event subscribe handler for '${name}'`); - if (this.events[name]) { + if (this.events[name] != null) { let eventHandler = this.__eventHandlers.get(name); if (eventHandler) { eventHandler.subscribe = handler; @@ -330,7 +330,7 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { setEventUnsubscribeHandler(name: string, handler: WoT.EventSubscriptionHandler): WoT.ExposedThing { debug(`ExposedThing '${this.title}' setting event unsubscribe handler for '${name}'`); - if (this.events[name]) { + if (this.events[name] != null) { let eventHandler = this.__eventHandlers.get(name); if (eventHandler) { eventHandler.unsubscribe = handler; @@ -355,16 +355,17 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { options: WoT.InteractionOptions & { formIndex: number } ): Promise { // TODO: handling URI variables? - if (this.actions[name]) { + if (this.actions[name] != null) { debug(`ExposedThing '${this.title}' has Action state of '${name}'`); const handler = this.__actionHandlers.get(name); if (handler != null) { debug(`ExposedThing '${this.title}' calls registered handler for Action '${name}'`); Helpers.validateInteractionOptions(this, this.actions[name], options); - const form = this.actions[name].forms - ? this.actions[name].forms[options.formIndex] - : { contentType: "application/json" }; + const form = + this.actions[name].forms != null + ? this.actions[name].forms[options.formIndex] + : { contentType: "application/json" }; const result: WoT.InteractionInput | void = await handler( new InteractionOutput(inputContent, form, this.actions[name].input), options @@ -389,7 +390,7 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { propertyName: string, options: WoT.InteractionOptions & { formIndex: number } ): Promise { - if (this.properties[propertyName]) { + if (this.properties[propertyName] != null) { debug(`ExposedThing '${this.title}' has Action state of '${propertyName}'`); const readHandler = this.__propertyHandlers.get(propertyName)?.readHandler; @@ -398,9 +399,10 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { debug(`ExposedThing '${this.title}' calls registered readHandler for Property '${propertyName}'`); Helpers.validateInteractionOptions(this, this.properties[propertyName], options); const result: WoT.InteractionInput | void = await readHandler(options); - const form = this.properties[propertyName].forms - ? this.properties[propertyName].forms[options.formIndex] - : { contentType: "application/json" }; + const form = + this.properties[propertyName].forms != null + ? this.properties[propertyName].forms[options.formIndex] + : { contentType: "application/json" }; return ContentManager.valueToContent( result, this.properties[propertyName], @@ -428,7 +430,7 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { for (const propertyName of propertyNames) { // Note: currently only JSON DataSchema properties are supported const form = this.properties[propertyName].forms.find( - (form) => form.contentType === ContentSerdes.DEFAULT || !form.contentType + (form) => form.contentType === ContentSerdes.DEFAULT || form.contentType == null ); if (!form) { continue; @@ -477,15 +479,16 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { options: WoT.InteractionOptions & { formIndex: number } ): Promise { // TODO: to be removed next api does not allow an ExposedThing to be also a ConsumeThing - if (this.properties[propertyName]) { - if (this.properties[propertyName].readOnly && this.properties[propertyName].readOnly === true) { + if (this.properties[propertyName] != null) { + if (this.properties[propertyName].readOnly === true) { throw new Error(`ExposedThing '${this.title}', property '${propertyName}' is readOnly`); } Helpers.validateInteractionOptions(this, this.properties[propertyName], options); const writeHandler = this.__propertyHandlers.get(propertyName)?.writeHandler; - const form = this.properties[propertyName].forms - ? this.properties[propertyName].forms[options.formIndex] - : {}; + const form = + this.properties[propertyName].forms != null + ? this.properties[propertyName].forms[options.formIndex] + : {}; // call write handler (if any) if (writeHandler != null) { await writeHandler(new InteractionOutput(inputContent, form, this.properties[propertyName]), options); @@ -510,7 +513,7 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { for (const propertyName in valueMap) { // Note: currently only DataSchema properties are supported const form = this.properties[propertyName].forms.find( - (form) => form.contentType === "application/json" || !form.contentType + (form) => form.contentType === "application/json" || form.contentType == null ); if (!form) { continue; @@ -536,7 +539,7 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { listener: ContentListener, options: WoT.InteractionOptions & { formIndex: number } ): Promise { - if (this.events[name]) { + if (this.events[name] != null) { Helpers.validateInteractionOptions(this, this.events[name], options); const formIndex = ProtocolHelpers.getFormIndexForOperation( @@ -574,7 +577,7 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { listener: ContentListener, options: WoT.InteractionOptions & { formIndex: number } ): void { - if (this.events[name]) { + if (this.events[name] != null) { Helpers.validateInteractionOptions(this, this.events[name], options); const formIndex = ProtocolHelpers.getFormIndexForOperation( @@ -609,7 +612,7 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { listener: ContentListener, options: WoT.InteractionOptions & { formIndex: number } ): Promise { - if (this.properties[name]) { + if (this.properties[name] != null) { Helpers.validateInteractionOptions(this, this.properties[name], options); const formIndex = ProtocolHelpers.getFormIndexForOperation( this.properties[name], @@ -641,7 +644,7 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { listener: ContentListener, options: WoT.InteractionOptions & { formIndex: number } ): void { - if (this.properties[name]) { + if (this.properties[name] != null) { Helpers.validateInteractionOptions(this, this.properties[name], options); const formIndex = ProtocolHelpers.getFormIndexForOperation( this.properties[name], diff --git a/packages/core/src/helpers.ts b/packages/core/src/helpers.ts index 48d9ca26c..fffd57154 100644 --- a/packages/core/src/helpers.ts +++ b/packages/core/src/helpers.ts @@ -120,7 +120,7 @@ export default class Helpers implements Resolver { public static toUriLiteral(address?: string): string { // Due to crash logged with: // TypeError: Cannot read property 'indexOf' of undefined at Function.Helpers.toUriLiteral - if (!address) { + if (address == null) { error(`AddressHelper received invalid address '${address}'`); return "{invalid address - undefined}"; } @@ -141,7 +141,7 @@ export default class Helpers implements Resolver { } public static toStringArray(input: string[] | string | undefined): string[] { - if (input) { + if (input != null) { if (typeof input === "string") { return [input]; } else { @@ -351,7 +351,7 @@ export default class Helpers implements Resolver { uriVariables: { [k: string]: DataSchema } = {} ): Record { const params: Record = {}; - if ((url === undefined && url == null) || (!uriVariables && !globalUriVariables)) { + if ((url === undefined && url == null) || (uriVariables == null && globalUriVariables == null)) { return params; } @@ -367,14 +367,14 @@ export default class Helpers implements Resolver { const queryKey: string = decodeURIComponent(indexPair[0]); const queryValue: string = decodeURIComponent(indexPair.length > 1 ? indexPair[1] : ""); - if (uriVariables && uriVariables[queryKey]) { + if (uriVariables != null && uriVariables[queryKey] != null) { if (uriVariables[queryKey].type === "integer" || uriVariables[queryKey].type === "number") { // *cast* it to number params[queryKey] = +queryValue; } else { params[queryKey] = queryValue; } - } else if (globalUriVariables && globalUriVariables[queryKey]) { + } else if (globalUriVariables != null && globalUriVariables[queryKey] != null) { if (globalUriVariables[queryKey].type === "integer" || globalUriVariables[queryKey].type === "number") { // *cast* it to number params[queryKey] = +queryValue; diff --git a/packages/core/src/interaction-output.ts b/packages/core/src/interaction-output.ts index b749d4fcf..7ec7b16c7 100644 --- a/packages/core/src/interaction-output.ts +++ b/packages/core/src/interaction-output.ts @@ -79,7 +79,7 @@ export class InteractionOutput implements WoT.InteractionOutput { async value(): Promise { // the value has been already read? - if (this.parsedValue) return this.parsedValue as T; + if (this.parsedValue != null) return this.parsedValue as T; if (this.dataUsed) { throw new Error("Can't read the stream once it has been already used"); diff --git a/packages/core/src/protocol-helpers.ts b/packages/core/src/protocol-helpers.ts index 85435ec2f..f757b8b01 100644 --- a/packages/core/src/protocol-helpers.ts +++ b/packages/core/src/protocol-helpers.ts @@ -69,7 +69,7 @@ export default class ProtocolHelpers { // TODO match for example http only? } // 2. Use any form - if (formTemplate.contentType) { + if (formTemplate.contentType != null) { form.contentType = formTemplate.contentType; return; // abort loop } @@ -83,7 +83,7 @@ export default class ProtocolHelpers { // TODO match for example http only? } // 2. Use any form - if (formTemplate.contentType) { + if (formTemplate.contentType != null) { form.contentType = formTemplate.contentType; return; // abort loop } @@ -97,7 +97,7 @@ export default class ProtocolHelpers { // TODO match for example http only? } // 2. Use any form - if (formTemplate.contentType) { + if (formTemplate.contentType != null) { form.contentType = formTemplate.contentType; return; // abort loop } @@ -113,16 +113,15 @@ export default class ProtocolHelpers { // Should interaction methods like readProperty() return an encapsulated value container with value&contenType // as sketched in https://github.com/w3c/wot-scripting-api/issues/201#issuecomment-573702999 if ( - td && - propertyName && - uriScheme && - td.properties && - td.properties[propertyName] && - td.properties[propertyName].forms && + propertyName != null && + uriScheme != null && + td?.properties != null && + td.properties[propertyName] != null && + td.properties[propertyName].forms != null && Array.isArray(td.properties[propertyName].forms) ) { for (const form of td.properties[propertyName].forms) { - if (form.href && form.href.startsWith(uriScheme) && form.contentType) { + if (form.href && form.href.startsWith(uriScheme) && form.contentType != null) { return form.contentType; // abort loop } } @@ -138,16 +137,15 @@ export default class ProtocolHelpers { ): string | undefined { // try to find contentType if ( - td && - actionName && - uriScheme && - td.actions && - td.actions[actionName] && - td.actions[actionName].forms && + actionName != null && + uriScheme != null && + td?.actions != null && + td.actions[actionName] != null && + td.actions[actionName].forms != null && Array.isArray(td.actions[actionName].forms) ) { for (const form of td.actions[actionName].forms) { - if (form.href && form.href.startsWith(uriScheme) && form.contentType) { + if (form.href && form.href.startsWith(uriScheme) && form.contentType != null) { return form.contentType; // abort loop } } @@ -163,16 +161,15 @@ export default class ProtocolHelpers { ): string | undefined { // try to find contentType if ( - td && - eventName && - uriScheme && - td.events && - td.events[eventName] && - td.events[eventName].forms && + eventName != null && + uriScheme != null && + td?.events != null && + td.events[eventName] != null && + td.events[eventName].forms != null && Array.isArray(td.events[eventName].forms) ) { for (const form of td.events[eventName].forms) { - if (form.href && form.href.startsWith(uriScheme) && form.contentType) { + if (form.href && form.href.startsWith(uriScheme) && form.contentType != null) { return form.contentType; // abort loop } } @@ -240,17 +237,17 @@ export default class ProtocolHelpers { static readStreamFully(stream: NodeJS.ReadableStream): Promise { return new Promise((resolve, reject) => { - if (stream) { + if (stream != null) { const chunks: Array = []; stream.on("data", (data) => chunks.push(data)); stream.on("error", reject); stream.on("end", () => { if ( - chunks[0] && + chunks[0] != null && (chunks[0] instanceof Array || chunks[0] instanceof Buffer || chunks[0] instanceof Uint8Array) ) { resolve(Buffer.concat(chunks as Array)); - } else if (chunks[0] && typeof chunks[0] === "string") { + } else if (chunks[0] != null && typeof chunks[0] === "string") { resolve(Buffer.from(chunks.join())); } else { resolve(Buffer.from(chunks as Array)); @@ -287,7 +284,7 @@ export default class ProtocolHelpers { return formUrl.protocol === uriScheme + ":" && (reqUrl === undefined || formUrl.pathname === reqUrl); }); // optionally try to match form's content type to the request's one - if (contentType) { + if (contentType != null) { const contentTypeMatchingForms: TD.Form[] = matchingForms.filter((form) => { return form.contentType === contentType; }); @@ -330,12 +327,12 @@ export default class ProtocolHelpers { switch (type) { case "property": if ( - (interaction.readOnly && operationName === "writeproperty") || - (interaction.writeOnly && operationName === "readproperty") + (interaction.readOnly === true && operationName === "writeproperty") || + (interaction.writeOnly === true && operationName === "readproperty") ) return finalFormIndex; - if (interaction.readOnly === undefined || !interaction.readOnly) defaultOps.push("writeproperty"); - if (interaction.writeOnly === undefined || !interaction.writeOnly) defaultOps.push("readproperty"); + if (interaction.readOnly !== true) defaultOps.push("writeproperty"); + if (interaction.writeOnly !== true) defaultOps.push("readproperty"); break; case "action": defaultOps = ["invokeaction"]; @@ -352,7 +349,7 @@ export default class ProtocolHelpers { // If a form index hint is gived, you it. Just check the form actually supports the op if (interaction.forms !== undefined && formIndex !== undefined && interaction.forms.length > formIndex) { const form = interaction.forms[formIndex]; - if (form && (operationName === undefined || form.op?.includes(operationName))) { + if (form != null && (operationName === undefined || form.op?.includes(operationName) === true)) { finalFormIndex = formIndex; } } @@ -362,7 +359,7 @@ export default class ProtocolHelpers { if (operationName !== undefined) { interaction.forms.every((form: TD.Form) => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- operationName !== undefined - if (form.op?.includes(operationName!)) { + if (form.op?.includes(operationName!) === true) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- interaction.forms !== undefined finalFormIndex = interaction.forms!.indexOf(form); } @@ -384,11 +381,11 @@ export default class ProtocolHelpers { public static getPropertyOpValues(property: PropertyElement): string[] { const op: string[] = []; - if (!property.readOnly) { + if (property.readOnly !== true) { op.push("writeproperty"); } - if (!property.writeOnly) { + if (property.writeOnly !== true) { op.push("readproperty"); } @@ -396,7 +393,7 @@ export default class ProtocolHelpers { warn("Property was declared both as readOnly and writeOnly."); } - if (property.observable) { + if (property.observable === true) { op.push("observeproperty"); op.push("unobserveproperty"); } diff --git a/packages/core/src/protocol-listener-registry.ts b/packages/core/src/protocol-listener-registry.ts index 3c9fe9be2..c3aed494c 100644 --- a/packages/core/src/protocol-listener-registry.ts +++ b/packages/core/src/protocol-listener-registry.ts @@ -21,7 +21,7 @@ export default class ProtocolListenerRegistry { private static EMPTY_MAP = new Map(); private listeners: Map> = new Map(); register(affordance: ThingInteraction, formIndex: number, listener: ContentListener): void { - if (!affordance.forms[formIndex]) { + if (affordance.forms[formIndex] == null) { throw new Error( "Can't register the listener for affordance with formIndex. The affordance does not contain the form" ); diff --git a/packages/core/src/wot-impl.ts b/packages/core/src/wot-impl.ts index 70afe08b4..741ca36e9 100644 --- a/packages/core/src/wot-impl.ts +++ b/packages/core/src/wot-impl.ts @@ -87,7 +87,7 @@ export default class WoTImpl { debug( `WoTImpl consuming TD ${ - newThing.id ? "'" + newThing.id + "'" : "without id" + newThing.id != null ? "'" + newThing.id + "'" : "without id" } to instantiate ConsumedThing '${newThing.title}'` ); return newThing; diff --git a/packages/core/test/ContentSerdesTest.ts b/packages/core/test/ContentSerdesTest.ts index 284bfa9d9..01a36b70e 100644 --- a/packages/core/test/ContentSerdesTest.ts +++ b/packages/core/test/ContentSerdesTest.ts @@ -60,7 +60,10 @@ const checkStreamToValue = (value: number[], match: unknown, type: string, endia const octectBuffer = Buffer.from(value); expect( ContentSerdes.contentToValue( - { type: `application/octet-stream${endianness ? `;byteSeq=${endianness}` : ""}`, body: octectBuffer }, + { + type: `application/octet-stream${endianness != null ? `;byteSeq=${endianness}` : ""}`, + body: octectBuffer, + }, { type: type ?? "integer", properties: {} } ) ).to.deep.equal(match); diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 4bc652caa..8eaa5eb0a 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "resolveJsonModule": true, "outDir": "dist", - "rootDir": "src" + "rootDir": "src", + "strictNullChecks": true }, "include": ["src/**/*"], "references": [{ "path": "../td-tools" }]