From 70f3c456d9ddc55303bb981c3c13f416bf898d12 Mon Sep 17 00:00:00 2001
From: Lautaro Petaccio <1120791+LautaroPetaccio@users.noreply.github.com>
Date: Thu, 14 Mar 2024 15:32:25 -0300
Subject: [PATCH] fix: Linting rules (no-non-null-assertion and
no-unsafe-enum-comparisson) (#2186)
* fix: no-non-null-assertion
* fix: no-unsafe-enum-comparison
* fix: Comparisson
---
webapp/.eslintrc.cjs | 4 +-
.../AccountSidebar/AccountSidebar.types.ts | 3 +-
.../OtherAccountSidebar.types.tsx | 3 +-
.../AssetFilters/PriceFilter/utils.ts | 29 +++++----
.../StatusFilter/StatusFilter.tsx | 4 +-
.../components/AssetImage/AssetImage.types.ts | 4 +-
.../AssetImage/AvailableForMintPopup.tsx | 2 +-
.../SelectedFilters/SelectedFilters.tsx | 2 +-
.../BuyWithCryptoModal/BuyWithCryptoModal.tsx | 4 +-
.../components/SuccessPage/SuccessPage.tsx | 3 +-
webapp/src/modules/nft/utils.ts | 4 +-
webapp/src/modules/routing/sagas.ts | 15 +++--
webapp/src/modules/routing/search.ts | 19 +++++-
webapp/src/modules/routing/selectors.ts | 9 ++-
webapp/src/modules/store/utils.ts | 3 +-
webapp/src/modules/ui/sagas.ts | 2 +-
webapp/src/modules/vendor/utils.ts | 4 +-
webapp/src/utils/enums.spec.ts | 65 +++++++++++++++++++
webapp/src/utils/enums.ts | 7 ++
19 files changed, 141 insertions(+), 45 deletions(-)
create mode 100644 webapp/src/utils/enums.spec.ts
create mode 100644 webapp/src/utils/enums.ts
diff --git a/webapp/.eslintrc.cjs b/webapp/.eslintrc.cjs
index 209dda6162..e27542a517 100644
--- a/webapp/.eslintrc.cjs
+++ b/webapp/.eslintrc.cjs
@@ -21,9 +21,7 @@ module.exports = {
'@typescript-eslint/no-unsafe-member-access': 'off', // TODO: migrate code progressively to remove this line. https://typescript-eslint.io/rules/no-unsafe-member-access/
'@typescript-eslint/no-unsafe-argument': 'off', // TODO: migrate code progressively to remove this line. https://typescript-eslint.io/rules/no-unsafe-argument/
'@typescript-eslint/no-explicit-any': 'off', // TODO: migrate code progressively to remove this line. https://typescript-eslint.io/rules/no-explicit-any
- '@typescript-eslint/no-non-null-assertion': 'off', // TODO: migrate code progressively to remove this line. https://typescript-eslint.io/rules/no-non-null-assertion
- 'import/order': 'off', // TODO: migrate code progressively to remove this line.
- '@typescript-eslint/no-unsafe-enum-comparison': 'off'
+ 'import/order': 'off' // TODO: migrate code progressively to remove this line.
},
parserOptions: {
project: ['./tsconfig.json']
diff --git a/webapp/src/components/AccountSidebar/AccountSidebar.types.ts b/webapp/src/components/AccountSidebar/AccountSidebar.types.ts
index f4f7887cd3..eec329b018 100644
--- a/webapp/src/components/AccountSidebar/AccountSidebar.types.ts
+++ b/webapp/src/components/AccountSidebar/AccountSidebar.types.ts
@@ -1,8 +1,9 @@
import { Dispatch } from 'redux'
import { browse, BrowseAction } from '../../modules/routing/actions'
+import { Section } from '../../modules/vendor/decentraland'
export type Props = {
- section: string
+ section: Section
address: string
isCurrentAccount?: boolean
onBrowse: typeof browse
diff --git a/webapp/src/components/AccountSidebar/OtherAccountSidebar/OtherAccountSidebar.types.tsx b/webapp/src/components/AccountSidebar/OtherAccountSidebar/OtherAccountSidebar.types.tsx
index e64143abbf..99ddc625b5 100644
--- a/webapp/src/components/AccountSidebar/OtherAccountSidebar/OtherAccountSidebar.types.tsx
+++ b/webapp/src/components/AccountSidebar/OtherAccountSidebar/OtherAccountSidebar.types.tsx
@@ -1,8 +1,9 @@
import { AssetType } from '../../../modules/asset/types'
import { BrowseOptions } from '../../../modules/routing/types'
+import { Section } from '../../../modules/vendor/decentraland'
export type Props = {
- section: string
+ section: Section
address: string
assetType: AssetType
onBrowse: (options: BrowseOptions) => void
diff --git a/webapp/src/components/AssetFilters/PriceFilter/utils.ts b/webapp/src/components/AssetFilters/PriceFilter/utils.ts
index b82bdb4af1..9dc5afe4c3 100644
--- a/webapp/src/components/AssetFilters/PriceFilter/utils.ts
+++ b/webapp/src/components/AssetFilters/PriceFilter/utils.ts
@@ -2,6 +2,7 @@ import { NFTCategory } from '@dcl/schemas'
import { ethers } from 'ethers'
import { getCategoryFromSection, getSearchEmoteCategory, getSearchWearableCategory } from '../../../modules/routing/search'
import { PriceFilterExtraOption, PriceFilters, Section } from '../../../modules/vendor/decentraland'
+import { isOfEnumType } from '../../../utils/enums'
const LAND_MAX_PRICE_ALLOWED = ethers.BigNumber.from('1000000000000000000000000000') // 1B
@@ -11,21 +12,21 @@ const WEARABLES_MAX_PRICE_ALLOWED = ethers.BigNumber.from('100000000000000000000
export const getChartUpperBound = (section: string) => {
let upperBound = WEARABLES_MAX_PRICE_ALLOWED
- switch (section) {
- case Section.LAND:
- case Section.ESTATES:
- case Section.PARCELS:
- upperBound = LAND_MAX_PRICE_ALLOWED
- break
- case Section.ENS:
- upperBound = ENS_MAX_PRICE_ALLOWED
- break
-
- default:
- upperBound = WEARABLES_MAX_PRICE_ALLOWED
- break
+ if (isOfEnumType(section, Section)) {
+ switch (section) {
+ case Section.LAND:
+ case Section.ESTATES:
+ case Section.PARCELS:
+ upperBound = LAND_MAX_PRICE_ALLOWED
+ break
+ case Section.ENS:
+ upperBound = ENS_MAX_PRICE_ALLOWED
+ break
+ default:
+ upperBound = WEARABLES_MAX_PRICE_ALLOWED
+ break
+ }
}
-
return upperBound
}
diff --git a/webapp/src/components/AssetFilters/StatusFilter/StatusFilter.tsx b/webapp/src/components/AssetFilters/StatusFilter/StatusFilter.tsx
index 0fd9ad4e72..84b35689ce 100644
--- a/webapp/src/components/AssetFilters/StatusFilter/StatusFilter.tsx
+++ b/webapp/src/components/AssetFilters/StatusFilter/StatusFilter.tsx
@@ -16,8 +16,8 @@ export const StatusFilter = ({ status, onChange, defaultCollapsed = false }: Sta
const isMobileOrTablet = useTabletAndBelowMediaQuery()
const statusOptions = useMemo(
() =>
- Object.keys(AssetStatusFilter).map(opt => ({
- value: opt.toLocaleLowerCase(),
+ Object.values(AssetStatusFilter).map(opt => ({
+ value: opt,
text: t(`nft_filters.status.${opt.toLocaleLowerCase()}`)
})),
[]
diff --git a/webapp/src/components/AssetImage/AssetImage.types.ts b/webapp/src/components/AssetImage/AssetImage.types.ts
index e0a84db7e3..9d4530eed6 100644
--- a/webapp/src/components/AssetImage/AssetImage.types.ts
+++ b/webapp/src/components/AssetImage/AssetImage.types.ts
@@ -1,6 +1,6 @@
import React from 'react'
import { Dispatch } from 'redux'
-import { Avatar, IPreviewController, Item, Order, Rarity } from '@dcl/schemas'
+import { Avatar, IPreviewController, Item, Order, Rarity, Network } from '@dcl/schemas'
import { Wallet } from 'decentraland-dapps/dist/modules/wallet/types'
import {
setIsTryingOn,
@@ -89,5 +89,5 @@ export type AvailableForMintPopupType = {
rarity: Rarity
contractAddress: string
itemId: string
- network: string
+ network: Network
}
diff --git a/webapp/src/components/AssetImage/AvailableForMintPopup.tsx b/webapp/src/components/AssetImage/AvailableForMintPopup.tsx
index ca9ae29f85..a5bf3173ff 100644
--- a/webapp/src/components/AssetImage/AvailableForMintPopup.tsx
+++ b/webapp/src/components/AssetImage/AvailableForMintPopup.tsx
@@ -41,7 +41,7 @@ const AvailableForMintPopup = ({ price, stock, rarity, contractAddress, itemId,
-
+
{formatWeiToAssetCard(price)}
diff --git a/webapp/src/components/AssetTopbar/SelectedFilters/SelectedFilters.tsx b/webapp/src/components/AssetTopbar/SelectedFilters/SelectedFilters.tsx
index 749722a68e..41357671e2 100644
--- a/webapp/src/components/AssetTopbar/SelectedFilters/SelectedFilters.tsx
+++ b/webapp/src/components/AssetTopbar/SelectedFilters/SelectedFilters.tsx
@@ -93,7 +93,7 @@ export const SelectedFilters = ({ browseOptions, isLandSection, category, onBrow
const handleDeleteRarity = useCallback(
(rarity: string) => {
- onBrowse({ rarities: rarities?.filter((r: Rarity) => r !== rarity) })
+ onBrowse({ rarities: rarities?.filter((r: Rarity) => r !== (rarity as Rarity)) })
},
[onBrowse, rarities]
)
diff --git a/webapp/src/components/Modals/BuyWithCryptoModal/BuyWithCryptoModal.tsx b/webapp/src/components/Modals/BuyWithCryptoModal/BuyWithCryptoModal.tsx
index c01718e714..a125372da1 100644
--- a/webapp/src/components/Modals/BuyWithCryptoModal/BuyWithCryptoModal.tsx
+++ b/webapp/src/components/Modals/BuyWithCryptoModal/BuyWithCryptoModal.tsx
@@ -88,7 +88,9 @@ export const BuyWithCryptoModal = (props: Props) => {
}, [providerChains, selectedChain])
const chainNativeToken = useMemo(() => {
- return providerTokens.find(t => +t.chainId === selectedChain && t.symbol === selectedProviderChain?.nativeCurrency.symbol)
+ return providerTokens.find(
+ t => t.chainId.toString() === selectedChain.toString() && t.symbol === selectedProviderChain?.nativeCurrency.symbol
+ )
}, [selectedChain, selectedProviderChain, providerTokens])
const { gasCost, isFetchingGasCost } = onGetGasCost(selectedToken, chainNativeToken, wallet)
diff --git a/webapp/src/components/SuccessPage/SuccessPage.tsx b/webapp/src/components/SuccessPage/SuccessPage.tsx
index 76ba2a492e..9f51c43d24 100644
--- a/webapp/src/components/SuccessPage/SuccessPage.tsx
+++ b/webapp/src/components/SuccessPage/SuccessPage.tsx
@@ -6,6 +6,7 @@ import { NFTCategory } from '@dcl/schemas'
import { t } from 'decentraland-dapps/dist/modules/translation/utils'
import { locations } from '../../modules/routing/locations'
import { config } from '../../config'
+import { isOfEnumType } from '../../utils/enums'
import { Footer } from '../Footer'
import { Asset, AssetType } from '../../modules/asset/types'
import { AssetImage } from '../AssetImage'
@@ -38,7 +39,7 @@ export function SuccessPage(props: Props) {
const search = new URLSearchParams(useLocation().search)
const contractAddress = search.get('contractAddress')
const tokenId = search.get('tokenId')
- const assetType = search.get('assetType')
+ const assetType = isOfEnumType(search.get('assetType'), AssetType) ? (search.get('assetType') as AssetType) : null
const subdomain = search.get('subdomain')
// this is a workaround to show the NAME while the transaction is being mined or the tokenId getting retrieved.
diff --git a/webapp/src/modules/nft/utils.ts b/webapp/src/modules/nft/utils.ts
index 18b67b82e9..6f8bd1ae8c 100644
--- a/webapp/src/modules/nft/utils.ts
+++ b/webapp/src/modules/nft/utils.ts
@@ -20,11 +20,11 @@ export function getNFT(contractAddress: string | null, tokenId: string | null, n
export const getBodyShapeUrn = (bodyShape: string) => `urn:decentraland:off-chain:base-avatars:${bodyShape}`
-export function isGender(bodyShapes: BodyShape[], gender: BodyShape) {
+export function isGender(bodyShapes: (BodyShape | string)[], gender: BodyShape): boolean {
if (bodyShapes.length !== 1) {
return false
}
- return bodyShapes[0] === gender || getBodyShapeUrn(bodyShapes[0]) === gender
+ return bodyShapes[0] === gender.toString() || getBodyShapeUrn(bodyShapes[0]) === gender.toString()
}
export function isUnisex(bodyShapes: BodyShape[]) {
diff --git a/webapp/src/modules/routing/sagas.ts b/webapp/src/modules/routing/sagas.ts
index 0be2f62531..331f3d4f80 100644
--- a/webapp/src/modules/routing/sagas.ts
+++ b/webapp/src/modules/routing/sagas.ts
@@ -62,7 +62,7 @@ import {
GO_BACK,
GoBackAction
} from './actions'
-import { BrowseOptions, Sections } from './types'
+import { BrowseOptions } from './types'
import { Section } from '../vendor/decentraland'
import { getClearedBrowseOptions, isCatalogView, rentalFilters, SALES_PER_PAGE, sellFilters, buildBrowseURL } from './utils'
import { FetchSalesFailureAction, fetchSalesRequest, FETCH_SALES_FAILURE, FETCH_SALES_SUCCESS } from '../sale/actions'
@@ -83,6 +83,7 @@ import {
ClaimNameSuccessAction,
ClaimNameTransactionSubmittedAction
} from '../ens/actions'
+import { isOfEnumType } from '../../utils/enums'
import { DCLRegistrar__factory } from '../../contracts/factories/DCLRegistrar__factory'
import { REGISTRAR_ADDRESS } from '../ens/sagas'
@@ -168,7 +169,7 @@ export function* fetchAssetsFromRoute(options: BrowseOptions) {
const view = options.view!
const vendor = options.vendor!
const page = options.page!
- const section = options.section!
+ const section = options.section && isOfEnumType(options.section, Section) ? options.section : undefined
const sortBy = options.sortBy!
const {
search,
@@ -193,7 +194,7 @@ export function* fetchAssetsFromRoute(options: BrowseOptions) {
yield put(setView(view))
}
- const category = getCategoryFromSection(section)
+ const category = section ? getCategoryFromSection(section) : undefined
const currentPageInState: number = yield select(getPage)
const offset = currentPageInState && currentPageInState < page ? page - 1 : 0
@@ -240,12 +241,12 @@ export function* fetchAssetsFromRoute(options: BrowseOptions) {
)
break
default: {
- const isWearableHead = section === Sections[VendorName.DECENTRALAND].WEARABLES_HEAD
- const isWearableAccessory = section === Sections[VendorName.DECENTRALAND].WEARABLES_ACCESSORIES
+ const isWearableHead = section === Section.WEARABLES_HEAD
+ const isWearableAccessory = section === Section.WEARABLES_ACCESSORIES
- const wearableCategory = !isWearableAccessory ? getSearchWearableCategory(section) : undefined
+ const wearableCategory = !isWearableAccessory && section ? getSearchWearableCategory(section) : undefined
- const emoteCategory = category === NFTCategory.EMOTE ? getSearchEmoteCategory(section) : undefined
+ const emoteCategory = category === NFTCategory.EMOTE && section ? getSearchEmoteCategory(section) : undefined
const { rarities, wearableGenders, emotePlayMode } = options
diff --git a/webapp/src/modules/routing/search.ts b/webapp/src/modules/routing/search.ts
index 8e0b87b465..bd72fde3d8 100644
--- a/webapp/src/modules/routing/search.ts
+++ b/webapp/src/modules/routing/search.ts
@@ -5,6 +5,7 @@ import { Section } from '../vendor/decentraland'
import { NFTSortBy } from '../nft/types'
import { isAccountView, isLandSection } from '../ui/utils'
import { AssetStatusFilter } from '../../utils/filters'
+import { isOfEnumType } from '../../utils/enums'
import { AssetType } from '../asset/types'
import { isCatalogView, isCatalogViewWithStatusFilter } from './utils'
@@ -161,7 +162,11 @@ export function getSearchParams(options?: BrowseOptions) {
return params
}
-export function getCategoryFromSection(section: string) {
+export function getCategoryFromSection(section: string): NFTCategory | undefined {
+ if (!isOfEnumType(section, Section)) {
+ return undefined
+ }
+
switch (section) {
case Section.PARCELS:
return NFTCategory.PARCEL
@@ -246,7 +251,11 @@ export function getSearchSection(category: WearableCategory | EmoteCategory) {
}
}
-export function getSearchWearableCategory(section: string) {
+export function getSearchWearableCategory(section: string): WearableCategory | undefined {
+ if (!isOfEnumType(section, Section)) {
+ return undefined
+ }
+
switch (section) {
case Section.WEARABLES_EYEBROWS:
return WearableCategory.EYEBROWS
@@ -285,7 +294,11 @@ export function getSearchWearableCategory(section: string) {
}
}
-export function getSearchEmoteCategory(section: string) {
+export function getSearchEmoteCategory(section: string): EmoteCategory | undefined {
+ if (!isOfEnumType(section, Section)) {
+ return undefined
+ }
+
switch (section) {
case Section.EMOTES_DANCE:
return EmoteCategory.DANCE
diff --git a/webapp/src/modules/routing/selectors.ts b/webapp/src/modules/routing/selectors.ts
index 826a452cd8..e64ea44a8b 100644
--- a/webapp/src/modules/routing/selectors.ts
+++ b/webapp/src/modules/routing/selectors.ts
@@ -4,6 +4,7 @@ import { getSearch as getRouterSearch, getLocation } from 'connected-react-route
import { EmotePlayMode, GenderFilterOption, Network, Rarity } from '@dcl/schemas'
import { t } from 'decentraland-dapps/dist/modules/translation/utils'
import { AssetStatusFilter } from '../../utils/filters'
+import { isOfEnumType } from '../../utils/enums'
import { getView } from '../ui/browse/selectors'
import { View } from '../ui/types'
import { VendorName } from '../vendor/types'
@@ -53,7 +54,11 @@ export const getSection = createSelector
{
const SORT_BY_MAP = getAllSortByOptions()
let orderByDropdownOptions: SortByOption[] = []
- if (status) {
+ if (status && isOfEnumType(status, AssetStatusFilter)) {
const baseFilters = [
SORT_BY_MAP[SortBy.NEWEST],
SORT_BY_MAP[SortBy.RECENTLY_LISTED],
diff --git a/webapp/src/modules/store/utils.ts b/webapp/src/modules/store/utils.ts
index 7f300b5e73..e88885c6f1 100644
--- a/webapp/src/modules/store/utils.ts
+++ b/webapp/src/modules/store/utils.ts
@@ -6,6 +6,7 @@ import { BuildEntityWithoutFilesOptions } from 'dcl-catalyst-client/dist/client/
import { EntityContentItemReference } from 'dcl-catalyst-commons'
import { peerUrl } from '../../lib/environment'
import { convertToOutputString } from '../../utils/output'
+import { isOfEnumType } from '../../utils/enums'
import { LinkType, Store, StoreEntityMetadata } from './types'
export const getPeerCoverUrl = (hash: string) => `${peerUrl}/content/contents/${hash}`
@@ -48,7 +49,7 @@ export const getStoreFromEntity = (entity: Entity): Store => {
coverName = reference.file
}
- const getLink = (type: LinkType) => metadata.links.find(link => link.name === type)?.url || ''
+ const getLink = (type: LinkType) => metadata.links.find(link => isOfEnumType(link.name, LinkType) && link.name === type)?.url || ''
return {
cover,
diff --git a/webapp/src/modules/ui/sagas.ts b/webapp/src/modules/ui/sagas.ts
index 7e01c858bc..21139aa655 100644
--- a/webapp/src/modules/ui/sagas.ts
+++ b/webapp/src/modules/ui/sagas.ts
@@ -59,7 +59,7 @@ function* handleSetWearablePreviewController(action: SetWearablePreviewControlle
try {
while (true) {
try {
- const event: string = yield take(emotesChannel)
+ const event: PreviewEmoteEventType = yield take(emotesChannel)
switch (event) {
case PreviewEmoteEventType.ANIMATION_PLAY:
yield put(setEmotePlaying(true))
diff --git a/webapp/src/modules/vendor/utils.ts b/webapp/src/modules/vendor/utils.ts
index 6473e22840..327ecb8a67 100644
--- a/webapp/src/modules/vendor/utils.ts
+++ b/webapp/src/modules/vendor/utils.ts
@@ -18,7 +18,7 @@ export function getFilters(vendor: VendorName, options: BrowseOptions): NFTsFetc
const isWearableHead = section === currentSection.WEARABLES_HEAD
const isWearableAccessory = section === currentSection.WEARABLES_ACCESSORIES
- const category = getCategoryFromSection(section!)
+ const category = section ? getCategoryFromSection(section) : undefined
const wearableCategory = !isWearableAccessory && category === NFTCategory.WEARABLE ? getSearchWearableCategory(section!) : undefined
const emoteCategory = category === NFTCategory.EMOTE ? getSearchEmoteCategory(section!) : undefined
@@ -79,7 +79,7 @@ export function getOriginURL(vendor: VendorName) {
}
}
-export function isVendor(vendor: string) {
+export function isVendor(vendor: string): vendor is VendorName {
return Object.values(VendorName).includes(vendor as VendorName)
}
diff --git a/webapp/src/utils/enums.spec.ts b/webapp/src/utils/enums.spec.ts
new file mode 100644
index 0000000000..7d14341300
--- /dev/null
+++ b/webapp/src/utils/enums.spec.ts
@@ -0,0 +1,65 @@
+import { isOfEnumType } from './enums'
+
+enum ANumericEnum {
+ A = 1,
+ B = 2,
+ C = 3
+}
+
+enum AnotherNumericEnum {
+ A = 4,
+ B = 5,
+ C = 6
+}
+
+enum StringEnum {
+ A = '1',
+ B = '2',
+ C = '3'
+}
+
+enum AnotherStringEnum {
+ A = '4',
+ B = '5',
+ C = '6'
+}
+
+describe('when checking if a value is of an enum type', () => {
+ let value: string | number
+
+ describe('and the value is numeric', () => {
+ beforeEach(() => {
+ value = 1
+ })
+
+ describe('and the value belongs to the enum', () => {
+ it('should return true', () => {
+ expect(isOfEnumType(value, ANumericEnum)).toBe(true)
+ })
+ })
+
+ describe('and the value does not belong to the enum', () => {
+ it('should return false', () => {
+ expect(isOfEnumType(value, AnotherNumericEnum)).toBe(false)
+ })
+ })
+ })
+
+ describe('and the value is a string', () => {
+ beforeEach(() => {
+ value = '1'
+ })
+
+ describe('and the value belongs to the enum', () => {
+ it('should return true', () => {
+ expect(isOfEnumType(value, StringEnum)).toBe(true)
+ })
+ })
+
+ describe('and the value does not belong to the enum', () => {
+ it('should return false', () => {
+ expect(isOfEnumType(value, AnotherStringEnum)).toBe(false)
+ })
+ })
+ })
+})
diff --git a/webapp/src/utils/enums.ts b/webapp/src/utils/enums.ts
new file mode 100644
index 0000000000..b6629adfc3
--- /dev/null
+++ b/webapp/src/utils/enums.ts
@@ -0,0 +1,7 @@
+type Enum = Record
+type Keys = keyof Enum
+type Values = E[Keys]
+
+export const isOfEnumType = >(value: unknown, enumObject: T): value is Values => {
+ return Object.values(enumObject).includes(value)
+}