From 6228d987f01c4562e571c8eee759238d5b83718c Mon Sep 17 00:00:00 2001 From: Antonio Ventilii Date: Fri, 27 Sep 2024 09:45:34 +0200 Subject: [PATCH] feat(lint): created local rule to use isNullish and nonNullish --- .eslintrc.cjs | 1 + eslint-local-rules.cjs | 49 +++++++++++++++++++ scripts/build.copy-workers.mjs | 2 + scripts/build.tokens.ckerc20.mjs | 2 + .../components/tokens/AddTokenReview.svelte | 22 +++++---- src/frontend/src/eth/derived/erc20.derived.ts | 1 + .../src/icp-eth/services/cketh.services.ts | 1 + .../services/ic-add-custom-tokens.service.ts | 16 +++--- .../src/icp/utils/cketh-transactions.utils.ts | 4 ++ .../tokens/TokenBalanceSkeleton.svelte | 1 + .../tokens/TokenExchangeValueSkeleton.svelte | 1 + .../src/lib/components/ui/Json.svelte | 1 + src/frontend/src/lib/derived/auth.derived.ts | 2 + .../src/lib/utils/certified-store.utils.ts | 1 + src/frontend/src/lib/utils/json.utils.ts | 5 +- src/frontend/src/lib/utils/nav.utils.ts | 2 + src/frontend/src/lib/utils/route.utils.ts | 2 + src/frontend/src/lib/workers/auth.worker.ts | 8 +++ .../routes/(app)/transactions/+page.svelte | 3 +- src/frontend/src/routes/+layout.svelte | 4 +- .../src/tests/mocks/qr-generator.mock.ts | 1 + vite.config.ts | 11 +++-- vite.utils.ts | 2 + 23 files changed, 117 insertions(+), 25 deletions(-) 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 @@