diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 6ed812d85..71595e379 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -63,6 +63,7 @@ module.exports = {
curly: 'error',
'local-rules/prefer-object-params': 'warn',
'local-rules/no-svelte-store-in-api': 'error',
+ 'local-rules/use-nullish-checks': 'warn',
'local-rules/use-option-type-wrapper': 'warn',
'import/no-duplicates': ['error', { 'prefer-inline': true }],
'no-console': ['error', { allow: ['error', 'warn'] }],
diff --git a/eslint-local-rules.cjs b/eslint-local-rules.cjs
index b437e2671..2281080b5 100644
--- a/eslint-local-rules.cjs
+++ b/eslint-local-rules.cjs
@@ -172,5 +172,54 @@ module.exports = {
}
};
}
+ },
+ 'use-nullish-checks': {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'Enforce the use of isNullish functions for nullish checks',
+ category: 'Best Practices',
+ recommended: true
+ },
+ fixable: 'code',
+ schema: []
+ },
+ create(context) {
+ const isNullishMessage =
+ 'Use isNullish() instead of direct variable check for nullish checks.';
+ const nonNullishMessage =
+ 'Use nonNullish() instead of direct variable check for nullish checks.';
+
+ const binaryCheck = (node) => {
+ if (node.type === 'BinaryExpression') {
+ return (
+ (node.operator === '===' || node.operator === '!==') &&
+ ((node.right.type === 'Identifier' && node.right.name === 'undefined') ||
+ (node.right.type === 'Literal' && node.right.value === null))
+ );
+ }
+ };
+
+ const binaryReportCheck = (node) => {
+ context.report({
+ node,
+ message: node.operator === '===' ? isNullishMessage : nonNullishMessage,
+ fix(fixer) {
+ return fixer.replaceText(
+ node,
+ `${node.operator === '===' ? 'isNullish' : 'nonNullish'}(${context.getSourceCode().getText(node.left)})`
+ );
+ }
+ });
+ };
+
+ return {
+ BinaryExpression(node) {
+ if (binaryCheck(node)) {
+ binaryReportCheck(node);
+ }
+ }
+ };
+ }
}
};
diff --git a/scripts/build.copy-workers.mjs b/scripts/build.copy-workers.mjs
index 22be97875..51bd3cd7c 100644
--- a/scripts/build.copy-workers.mjs
+++ b/scripts/build.copy-workers.mjs
@@ -9,6 +9,8 @@ await cp(
filter: (source) => extname(source) !== '.map'
},
(err) => {
+ // TODO: Remove ESLint exception and use nullish checks
+ // eslint-disable-next-line local-rules/use-nullish-checks
if (err === null) {
return;
}
diff --git a/scripts/build.tokens.ckerc20.mjs b/scripts/build.tokens.ckerc20.mjs
index 6cc73f8c0..1e55e96d6 100755
--- a/scripts/build.tokens.ckerc20.mjs
+++ b/scripts/build.tokens.ckerc20.mjs
@@ -74,6 +74,8 @@ const buildOrchestratorInfo = async (orchestratorId) => {
const assertUniqueTokenSymbol = Object.values(tokens).find((value) => value.length > 1);
+ // TODO: Remove ESLint exception and use nullish checks
+ // eslint-disable-next-line local-rules/use-nullish-checks
if (assertUniqueTokenSymbol !== undefined) {
throw new Error(
`More than one pair of ledger and index canisters were used for the token symbol ${assertUniqueTokenSymbol}.`
diff --git a/src/frontend/src/eth/components/tokens/AddTokenReview.svelte b/src/frontend/src/eth/components/tokens/AddTokenReview.svelte
index 656804434..09054a986 100644
--- a/src/frontend/src/eth/components/tokens/AddTokenReview.svelte
+++ b/src/frontend/src/eth/components/tokens/AddTokenReview.svelte
@@ -1,5 +1,5 @@
+
{#if token.balance === undefined}
{:else}
diff --git a/src/frontend/src/lib/components/tokens/TokenExchangeValueSkeleton.svelte b/src/frontend/src/lib/components/tokens/TokenExchangeValueSkeleton.svelte
index 369f64178..86dc87e23 100644
--- a/src/frontend/src/lib/components/tokens/TokenExchangeValueSkeleton.svelte
+++ b/src/frontend/src/lib/components/tokens/TokenExchangeValueSkeleton.svelte
@@ -6,6 +6,7 @@
export let token: TokenUi;
+
{#if token.balance === undefined || !$exchangeInitialized}
{:else}
diff --git a/src/frontend/src/lib/components/ui/Json.svelte b/src/frontend/src/lib/components/ui/Json.svelte
index 7fd2946d1..679707a01 100644
--- a/src/frontend/src/lib/components/ui/Json.svelte
+++ b/src/frontend/src/lib/components/ui/Json.svelte
@@ -21,6 +21,7 @@
| 'undefined';
const getValueType = (value: unknown): ValueType => {
+ // eslint-disable-next-line local-rules/use-nullish-checks -- We want to check for null and not undefined
if (value === null) {
return 'null';
}
diff --git a/src/frontend/src/lib/derived/auth.derived.ts b/src/frontend/src/lib/derived/auth.derived.ts
index e0d04ce06..4d4f6e24c 100644
--- a/src/frontend/src/lib/derived/auth.derived.ts
+++ b/src/frontend/src/lib/derived/auth.derived.ts
@@ -4,6 +4,8 @@ import { derived, type Readable } from 'svelte/store';
export const authSignedIn: Readable = derived(
authStore,
+ // TODO: Remove ESLint exception and use nullish checks
+ // eslint-disable-next-line local-rules/use-nullish-checks
({ identity }) => identity !== null && identity !== undefined
);
diff --git a/src/frontend/src/lib/utils/certified-store.utils.ts b/src/frontend/src/lib/utils/certified-store.utils.ts
index 0675e5490..9db20f27a 100644
--- a/src/frontend/src/lib/utils/certified-store.utils.ts
+++ b/src/frontend/src/lib/utils/certified-store.utils.ts
@@ -2,4 +2,5 @@ import type { CertifiedData } from '$lib/types/store';
import type { Option } from '$lib/types/utils';
export const mapCertifiedData = (certifiedData: Option>): Option =>
+ // eslint-disable-next-line local-rules/use-nullish-checks -- We need to check for null explicitly, since it is the scope of this function.
certifiedData === null ? null : certifiedData?.data;
diff --git a/src/frontend/src/lib/utils/json.utils.ts b/src/frontend/src/lib/utils/json.utils.ts
index f0c89ecd1..c2be6de37 100644
--- a/src/frontend/src/lib/utils/json.utils.ts
+++ b/src/frontend/src/lib/utils/json.utils.ts
@@ -1,9 +1,10 @@
import type { Principal } from '@dfinity/principal';
+import { isNullish } from '@dfinity/utils';
// e.g. payloads.did/state_hash
export const isHash = (bytes: number[]): boolean =>
bytes.length === 32 &&
- bytes.find((value) => !Number.isInteger(value) || value < 0 || value > 255) === undefined;
+ isNullish(bytes.find((value) => !Number.isInteger(value) || value < 0 || value > 255));
// Convert a byte array to a hex string
const bytesToHexString = (bytes: number[]): string =>
@@ -58,6 +59,8 @@ export const stringifyJson = ({
break;
}
case 'bigint': {
+ // TODO: Remove ESLint exception and use nullish checks
+ // eslint-disable-next-line local-rules/use-nullish-checks
if (options?.devMode !== undefined && options.devMode) {
return `BigInt('${value.toString()}')`;
}
diff --git a/src/frontend/src/lib/utils/nav.utils.ts b/src/frontend/src/lib/utils/nav.utils.ts
index 4e1b0ab8f..05f82aa47 100644
--- a/src/frontend/src/lib/utils/nav.utils.ts
+++ b/src/frontend/src/lib/utils/nav.utils.ts
@@ -73,6 +73,8 @@ export const loadRouteParams = ($event: LoadEvent): RouteParams => {
const token = searchParams?.get('token');
const replaceEmoji = (input: string | null): string | null => {
+ // TODO: Remove ESLint exception and use nullish checks
+ // eslint-disable-next-line local-rules/use-nullish-checks
if (input === null) {
return null;
}
diff --git a/src/frontend/src/lib/utils/route.utils.ts b/src/frontend/src/lib/utils/route.utils.ts
index d52b3ae57..3f1bf5b29 100644
--- a/src/frontend/src/lib/utils/route.utils.ts
+++ b/src/frontend/src/lib/utils/route.utils.ts
@@ -15,6 +15,8 @@ export const replaceHistory = (url: URL) => {
* Source: https://stackoverflow.com/a/6825002/5404186
*/
const supportsHistory = (): boolean =>
+ // TODO: Remove ESLint exception and use nullish checks
+ // eslint-disable-next-line local-rules/use-nullish-checks
window.history !== undefined &&
'pushState' in window.history &&
typeof window.history.pushState !== 'undefined';
diff --git a/src/frontend/src/lib/workers/auth.worker.ts b/src/frontend/src/lib/workers/auth.worker.ts
index 52ce30b17..91129f274 100644
--- a/src/frontend/src/lib/workers/auth.worker.ts
+++ b/src/frontend/src/lib/workers/auth.worker.ts
@@ -38,6 +38,8 @@ const onIdleSignOut = async () => {
const [auth, chain] = await Promise.all([checkAuthentication(), checkDelegationChain()]);
// Both identity and delegation are alright, so all good
+ // TODO: Remove ESLint exception and use nullish checks
+ // eslint-disable-next-line local-rules/use-nullish-checks
if (auth && chain.valid && chain.delegation !== null) {
emitExpirationTime(chain.delegation);
return;
@@ -68,9 +70,13 @@ const checkDelegationChain = async (): Promise<{
const idbStorage: IdbStorage = new IdbStorage();
const delegationChain: string | null = await idbStorage.get(KEY_STORAGE_DELEGATION);
+ // TODO: Remove ESLint exception and use nullish checks
+ // eslint-disable-next-line local-rules/use-nullish-checks
const delegation = delegationChain !== null ? DelegationChain.fromJSON(delegationChain) : null;
return {
+ // TODO: Remove ESLint exception and use nullish checks
+ // eslint-disable-next-line local-rules/use-nullish-checks
valid: delegation !== null && isDelegationValid(delegation),
delegation
};
@@ -88,6 +94,8 @@ const emitExpirationTime = (delegation: DelegationChain) => {
const expirationTime: bigint | undefined = delegation.delegations[0]?.delegation.expiration;
// That would be unexpected here because the delegation has just been tested and is valid
+ // TODO: Remove ESLint exception and use nullish checks
+ // eslint-disable-next-line local-rules/use-nullish-checks
if (expirationTime === undefined) {
return;
}
diff --git a/src/frontend/src/routes/(app)/transactions/+page.svelte b/src/frontend/src/routes/(app)/transactions/+page.svelte
index 33439c892..904efcb0a 100644
--- a/src/frontend/src/routes/(app)/transactions/+page.svelte
+++ b/src/frontend/src/routes/(app)/transactions/+page.svelte
@@ -15,8 +15,7 @@
await goto('/');
}
- const unknownNetwork =
- $networks.find(({ id }) => id.description === $routeNetwork) === undefined;
+ const unknownNetwork = isNullish($networks.find(({ id }) => id.description === $routeNetwork));
if (unknownNetwork) {
await goto('/');
diff --git a/src/frontend/src/routes/+layout.svelte b/src/frontend/src/routes/+layout.svelte
index 8fd970f47..0128b80b8 100644
--- a/src/frontend/src/routes/+layout.svelte
+++ b/src/frontend/src/routes/+layout.svelte
@@ -1,6 +1,6 @@