Skip to content

Commit

Permalink
refactor: [GSW-2032] SonarQube issue when using Array.sort()
Browse files Browse the repository at this point in the history
Provide a compare function to avoid sorting elements alphabetically.
"Array.prototype.sort()" and "Array.prototype.toSorted()" should use a compare function typescript:S2871
Software qualities impacted:
Reliability
  • Loading branch information
tfrg committed Dec 17, 2024
1 parent fd1d2f2 commit b416aab
Show file tree
Hide file tree
Showing 12 changed files with 86 additions and 12 deletions.
3 changes: 2 additions & 1 deletion packages/web/src/constants/common.constant.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { GNS_TOKEN_PATH, WRAPPED_GNOT_PATH } from "./environment.constant";
import { sortTokenPaths } from "@utils/sort-utils";

export const DEFAULT_NETWORK_ID = "portal-loop";

Expand Down Expand Up @@ -309,7 +310,7 @@ export const DEFAULT_POOL_ADD_URI = `/earn/add?tokenA=gnot&tokenB=${GNS_TOKEN_PA

export const DEFAULT_TOKEN_PAIR = [WRAPPED_GNOT_PATH, GNS_TOKEN_PATH];

export const DEFAULT_POOL_PATH = [...DEFAULT_TOKEN_PAIR.sort(), "3000"].join(":");
export const DEFAULT_POOL_PATH = [...DEFAULT_TOKEN_PAIR.sort(sortTokenPaths), "3000"].join(":");

export const LANGUAGE_CODE_MAP: Record<string, string> = {
en: "en-US",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useMemo } from "react";

import useCustomRouter from "@hooks/data/common/router/use-custom-router";
import { checkGnotPath } from "@utils/common";
import { sortTokenPaths } from "@utils/sort-utils";

export const usePoolAddSearchParams = () => {
const router = useCustomRouter();
Expand All @@ -16,7 +17,7 @@ export const usePoolAddSearchParams = () => {
const poolPathSplit = poolPathParam?.split(":");
return [poolPathSplit[0], poolPathSplit[1]];
}
return [checkGnotPath(tokenAPath), checkGnotPath(tokenBPath)].sort();
return [checkGnotPath(tokenAPath), checkGnotPath(tokenBPath)].sort(sortTokenPaths);
}, [poolPathParam, tokenAPath, tokenBPath]);

const poolPath = useMemo(() => {
Expand Down
3 changes: 2 additions & 1 deletion packages/web/src/hooks/data/pool/use-pool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { PoolModel } from "@models/pool/pool-model";
import { isNativeToken, TokenModel } from "@models/token/token-model";
import { useGetPoolCreationFee, useGetRPCPoolsBy } from "@query/pools";
import { checkGnotPath } from "@utils/common";
import { sortTokenPaths } from "@utils/sort-utils";

interface Props {
compareToken: TokenModel | null;
Expand All @@ -29,7 +30,7 @@ export const usePool = ({ compareToken, tokenA, tokenB, isReverted = false }: Pr
const tokenATokenPath = checkGnotPath(tokenA.path) ? tokenA.wrappedPath : tokenA.path;
const tokenBTokenPath = checkGnotPath(tokenB.path) ? tokenB.wrappedPath : tokenB.path;

const tokenPair = [tokenATokenPath, tokenBTokenPath].sort();
const tokenPair = [tokenATokenPath, tokenBTokenPath].sort(sortTokenPaths);

return [
SwapFeeTierInfoMap.FEE_100,
Expand Down
3 changes: 2 additions & 1 deletion packages/web/src/hooks/data/pool/use-select-pool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
tickToPrice,
} from "@utils/swap-utils";
import { makeDisplayTokenAmount } from "@utils/token-utils";
import { sortTokenPaths } from "@utils/sort-utils";

type RenderState = "NONE" | "CREATE" | "LOADING" | "DONE";

Expand Down Expand Up @@ -118,7 +119,7 @@ export const useSelectPool = ({
return null;
}

return [checkGnotPath(tokenA.path), checkGnotPath(tokenB.path)].sort();
return [checkGnotPath(tokenA.path), checkGnotPath(tokenB.path)].sort(sortTokenPaths);
}, [tokenA, tokenB]);

const isReverse = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { TokenModel } from "@models/token/token-model";
import { checkGnotPath } from "@utils/common";
import { formatTokenExchangeRate } from "@utils/stake-position-utils";
import { priceToTick, tickToPrice } from "@utils/swap-utils";
import { sortTokenPaths } from "@utils/sort-utils";

import PriceSteps from "./price-steps/PriceSteps";
import StartingPrice from "./starting-price/StartingPrice";
Expand Down Expand Up @@ -91,7 +92,7 @@ const SelectPriceRangeCustom = forwardRef<SelectPriceRangeCustomHandle, SelectPr
const availSelect = Array.isArray(selectPool.liquidityOfTickPoints) && selectPool.renderState() === "DONE";

const flip = useMemo(() => {
const compareTokenPaths = [checkGnotPath(tokenA.path), checkGnotPath(tokenB.path)].sort();
const compareTokenPaths = [checkGnotPath(tokenA.path), checkGnotPath(tokenB.path)].sort(sortTokenPaths);
return compareTokenPaths[0] !== checkGnotPath(selectPool.compareToken?.path || "");
}, [selectPool.compareToken, tokenA.path, tokenB.path]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { TokenModel } from "@models/token/token-model";
import { checkGnotPath } from "@utils/common";
import { formatTokenExchangeRate } from "@utils/stake-position-utils";
import { priceToTick, tickToPrice } from "@utils/swap-utils";
import { sortTokenPaths } from "@utils/sort-utils";

import SelectPriceRangeCutomController from "./price-steps/PriceSteps";

Expand Down Expand Up @@ -78,7 +79,7 @@ const SelectPriceRangeCustomReposition: React.FC<SelectPriceRangeCustomRepositio
return false;
}

const compareTokenPaths = [checkGnotPath(tokenA.path), checkGnotPath(tokenB.path)].sort();
const compareTokenPaths = [checkGnotPath(tokenA.path), checkGnotPath(tokenB.path)].sort(sortTokenPaths);
return compareTokenPaths[0] !== checkGnotPath(selectPool.compareToken.path);
}, [selectPool.compareToken, selectPool.startPrice, tokenA.path, tokenB.path]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
priceToTick,
} from "@utils/swap-utils";
import { makeDisplayTokenAmount, makeRawTokenAmount } from "@utils/token-utils";
import { sortTokenPaths } from "@utils/sort-utils";

import PoolAddLiquidity, { PriceRangeSummary } from "../../components/pool-add-liquidity/PoolAddLiquidity";
import { usePool } from "../../../../../hooks/data/pool/use-pool";
Expand Down Expand Up @@ -548,7 +549,7 @@ const EarnAddLiquidityContainer: React.FC = () => {

useEffect(() => {
if (pools.length > 0 && tokenA && tokenB && selectPool.compareToken) {
const tokenPair = [tokenA.wrappedPath, tokenB.wrappedPath].sort();
const tokenPair = [tokenA.wrappedPath, tokenB.wrappedPath].sort(sortTokenPaths);
const compareToken = selectPool.compareToken;
const reverse =
tokenPair.findIndex(path => {
Expand All @@ -575,7 +576,7 @@ const EarnAddLiquidityContainer: React.FC = () => {
useEffect(() => {
const pair = [tokenA?.path, tokenB?.path]
.filter(item => item !== undefined)
.sort()
.sort(sortTokenPaths)
.join(":");

const isDifferentPair = pair !== lastPoolPathRef.current;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
tickToPrice,
} from "@utils/swap-utils";
import { makeDisplayTokenAmount, makeRawTokenAmount } from "@utils/token-utils";
import { sortTokenPaths } from "@utils/sort-utils";

import PoolAddLiquidity, { PriceRangeSummary } from "../../components/pool-add-liquidity/PoolAddLiquidity";
import { usePool } from "../../../../../hooks/data/pool/use-pool";
Expand Down Expand Up @@ -470,7 +471,7 @@ const PoolAddLiquidityContainer: React.FC = () => {

useEffect(() => {
if (pools.length > 0 && tokenA && tokenB && selectPool.compareToken) {
const tokenPair = [tokenA.wrappedPath, tokenB.wrappedPath].sort();
const tokenPair = [tokenA.wrappedPath, tokenB.wrappedPath].sort(sortTokenPaths);
const compareToken = selectPool.compareToken;
const reverse =
tokenPair.findIndex(path => {
Expand Down
3 changes: 2 additions & 1 deletion packages/web/src/repositories/pool/pool.message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { MAX_INT64, tickToSqrtPriceX96 } from "@utils/math.utils";
import { isOrderedTokenPaths } from "@utils/pool-utils";
import { priceToTick } from "@utils/swap-utils";
import { makeRawTokenAmount } from "@utils/token-utils";
import { sortTokenPaths } from "@utils/sort-utils";

enum PoolTransactionMessageFunctionType {
CreatePool = "CreatePool",
Expand Down Expand Up @@ -73,7 +74,7 @@ export function makeCreatePoolMessageWithApproves(
*/
const isOrdered = isOrderedTokenPaths(tokenAPath, tokenBPath);

const [orderedPoolAPath, orderedPoolBPath] = [tokenAPath, tokenBPath].sort();
const [orderedPoolAPath, orderedPoolBPath] = [tokenAPath, tokenBPath].sort(sortTokenPaths);
const orderedStartPriceNum = isOrdered || startPriceNum === 0 ? startPriceNum : 1 / startPriceNum;
const startPriceSqrt = tickToSqrtPriceX96(priceToTick(orderedStartPriceNum));

Expand Down
5 changes: 3 additions & 2 deletions packages/web/src/utils/pool-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from "@constants/option.constant";
import { TokenModel } from "@models/token/token-model";
import { tickToPriceStr } from "./swap-utils";
import { sortTokenPaths } from "./sort-utils";

const maxTicks = Object.values(SwapFeeTierMaxPriceRangeMap).map(range => range.maxTick);
const minTicks = Object.values(SwapFeeTierMaxPriceRangeMap).map(range => range.maxTick);
Expand All @@ -20,7 +21,7 @@ export function makePoolPath(
}
const tokenAPath = tokenA.wrappedPath || tokenA.path || "";
const tokenBPath = tokenB.wrappedPath || tokenB.path || "";
return [tokenAPath, tokenBPath].sort().join(":") + ":" + SwapFeeTierInfoMap[swapFeeTier].fee;
return [tokenAPath, tokenBPath].sort(sortTokenPaths).join(":") + ":" + SwapFeeTierInfoMap[swapFeeTier].fee;
}

export function isMaxTick(tick: number) {
Expand Down Expand Up @@ -50,5 +51,5 @@ export function checkPoolStakingRewards(type?: INCENTIVE_TYPE) {
}

export function isOrderedTokenPaths(tokenAPath: string, tokenBPath: string): boolean {
return [tokenAPath, tokenBPath].sort()?.[0] === tokenAPath;
return [tokenAPath, tokenBPath].sort(sortTokenPaths)?.[0] === tokenAPath;
}
34 changes: 34 additions & 0 deletions packages/web/src/utils/sort-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { sortTokenPaths } from "./sort-utils";
import { GNS_TOKEN_PATH, WRAPPED_GNOT_PATH } from "@constants/environment.constant";

describe("sortTokenPaths utility function test", () => {
test("Same result as default sort - plain string", () => {
const tokens = ["gns", "GNS", "Gns", "FOO", "BAR", "BAZ"];

expect([...tokens].sort()).toEqual([...tokens].sort(sortTokenPaths));
});

test("Same result as default sort - includes undefined", () => {
const tokens = ["gns", "GNS", undefined, "Gns", "FOO", "BAR", "BAZ"];

expect([...tokens].sort()).toEqual([...tokens].sort(sortTokenPaths));
});

test("Same result as default sort - includes number", () => {
const tokens = ["GNOT1", "GNOT2", "GNOT10", "GNS1", "GNS10", "GNS2", "wrapped.GNOT1", "wrapped.GNS1", undefined];

expect([...tokens].sort()).toEqual([...tokens].sort(sortTokenPaths));
});

test("Same result as default sort - includes special characters", () => {
const tokens = ["gns-1", "gns_1", "gns.1", "gns/1", undefined, "GNS"];

expect([...tokens].sort()).toEqual([...tokens].sort(sortTokenPaths));
});

test("Testing real-world use cases", () => {
const tokens = [WRAPPED_GNOT_PATH, GNS_TOKEN_PATH];

expect([...tokens].sort()).toEqual([...tokens].sort(sortTokenPaths));
});
});
30 changes: 30 additions & 0 deletions packages/web/src/utils/sort-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* A comparison function for token path strings
*
* to comply with SonarQube's "Provide a compare function when using 'Array.prototype.sort()'" rule.
* Used as a comparison function for Array.prototype.sort() to determine the order of token paths.
*
* Performs lexicographical sorting based on UTF-16 code unit values, identical to default .sort()
* Handles undefined values by treating them as empty strings
*
* @param tokenA - First token path (possibly undefined)
* @param tokenB - Second token path (possibly undefined)
* @returns
* -1 if tokenA < tokenB
* 1 if tokenA > tokenB
* 0 if tokenA === tokenB
*
* @example
* Same result as default .sort()
* ['gns', undefined, 'GNS'].sort() // ['GNS', 'gns', undefined]
* ['gns', undefined, 'GNS'].sort(compareTokenPaths) // ['GNS', 'gns', undefined]
*
* Actual usage examples
* const tokenPair = [tokenAPath, tokenBPath].sort(compareTokenPaths);
* const poolPath = [...[WRAPPED_GNOT_PATH, GNS_TOKEN_PATH].sort(compareTokenPaths), "3000"].join(":");
*/
export const sortTokenPaths = (tokenA: string | undefined, tokenB: string | undefined): number => {
const tokenAString = tokenA ?? "";
const tokenBString = tokenB ?? "";
return tokenAString === tokenBString ? 0 : tokenAString > tokenBString ? 1 : -1;
};

0 comments on commit b416aab

Please sign in to comment.