Skip to content

Commit

Permalink
fix(eslint-plugin-query): relax property order rule to ignore relativ…
Browse files Browse the repository at this point in the history
…e order of getPreviousPageParam and getNextPageParam
  • Loading branch information
schiller-manuel committed Sep 20, 2024
1 parent 1d851dd commit 074dc30
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,60 @@ const validTestMatrix = combinate({
properties: generatePartialCombinations(checkedProperties, 2),
})

export function generateInvalidPermutations<T>(
arr: ReadonlyArray<T>,
): Array<{ invalid: Array<T>; valid: Array<T> }> {
export function generateInvalidPermutations(
arr: ReadonlyArray<CheckedProperties>,
): Array<{
invalid: Array<CheckedProperties>
valid: Array<CheckedProperties>
}> {
const combinations = generatePartialCombinations(arr, 2)
const allPermutations: Array<{ invalid: Array<T>; valid: Array<T> }> = []
const allPermutations: Array<{
invalid: Array<CheckedProperties>
valid: Array<CheckedProperties>
}> = []

for (const combination of combinations) {
const permutations = generatePermutations(combination)
// skip the first permutation as it matches the original combination
const invalidPermutations = permutations.slice(1)

if (
combination.includes('getNextPageParam') &&
combination.includes('getPreviousPageParam')
) {
if (
combination.indexOf('getNextPageParam') <
combination.indexOf('getPreviousPageParam')
) {
// since we ignore the relative order of 'getPreviousPageParam' and 'getNextPageParam', we skip this combination (but keep the other one where `getPreviousPageParam` is before `getNextPageParam`)

continue
}
}

allPermutations.push(
...invalidPermutations.map((p) => ({ invalid: p, valid: combination })),
...invalidPermutations
.map((p) => {
// ignore the relative order of 'getPreviousPageParam' and 'getNextPageParam'
const correctedValid = [...combination].sort((a, b) => {
if (
(a === 'getNextPageParam' && b === 'getPreviousPageParam') ||
(a === 'getPreviousPageParam' && b === 'getNextPageParam')
) {
return p.indexOf(a) - p.indexOf(b)
}
return checkedProperties.indexOf(a) - checkedProperties.indexOf(b)
})
return { invalid: p, valid: correctedValid }
})
.filter(
({ invalid }) =>
// if `getPreviousPageParam` and `getNextPageParam` are next to each other and `queryFn` is not present, we skip this invalid permutation
Math.abs(
invalid.indexOf('getNextPageParam') -
invalid.indexOf('getPreviousPageParam'),
) !== 1 && !invalid.includes('queryFn'),
),
)
}

Expand Down Expand Up @@ -121,7 +163,7 @@ const validTestCases = validTestMatrix.map(

const invalidTestCases = invalidTestMatrix.map(
({ infiniteQueryFunction, properties }) => ({
name: `incorrect property order is detected for ${infiniteQueryFunction} with order: ${properties.invalid.join(', ')}`,
name: `incorrect property order is detected for ${infiniteQueryFunction} with invalid order: ${properties.invalid.join(', ')}, valid order: ${properties.valid.join(', ')}`,
code: getCode({
infiniteQueryFunction: infiniteQueryFunction,
properties: properties.invalid,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,64 @@ describe('create-route-property-order utils', () => {
const testCases = [
{
data: [{ key: 'a' }, { key: 'c' }, { key: 'b' }],
orderArray: ['a', 'b', 'c'],
orderArray: [
[['a'], ['b']],
[['b'], ['c']],
],
key: 'key',
expected: [{ key: 'a' }, { key: 'b' }, { key: 'c' }],
},
{
data: [{ key: 'b' }, { key: 'a' }, { key: 'c' }],
orderArray: ['a', 'b', 'c'],
orderArray: [
[['a'], ['b']],
[['b'], ['c']],
],
key: 'key',
expected: [{ key: 'a' }, { key: 'b' }, { key: 'c' }],
},
{
data: [{ key: 'a' }, { key: 'b' }, { key: 'c' }],
orderArray: ['a', 'b', 'c'],
orderArray: [
[['a'], ['b']],
[['b'], ['c']],
],
key: 'key',
expected: null,
},
{
data: [{ key: 'a' }, { key: 'b' }, { key: 'c' }, { key: 'd' }],
orderArray: ['a', 'b', 'c'],
orderArray: [
[['a'], ['b']],
[['b'], ['c']],
],
key: 'key',
expected: null,
},
{
data: [{ key: 'a' }, { key: 'b' }, { key: 'd' }, { key: 'c' }],
orderArray: ['a', 'b', 'c'],
orderArray: [
[['a'], ['b']],
[['b'], ['c']],
],
key: 'key',
expected: null,
},
{
data: [{ key: 'd' }, { key: 'a' }, { key: 'b' }, { key: 'c' }],
orderArray: ['a', 'b', 'c'],
orderArray: [
[['a'], ['b']],
[['b'], ['c']],
],
key: 'key',
expected: null,
},
{
data: [{ key: 'd' }, { key: 'b' }, { key: 'a' }, { key: 'c' }],
orderArray: ['a', 'b', 'c'],
orderArray: [
[['a'], ['b']],
[['b'], ['c']],
],
key: 'key',
expected: [{ key: 'd' }, { key: 'a' }, { key: 'b' }, { key: 'c' }],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ export const checkedProperties = [
'getPreviousPageParam',
'getNextPageParam',
] as const

export const sortRules = [
[['queryFn'], ['getPreviousPageParam', 'getNextPageParam']],
] as const
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'
import { getDocsUrl } from '../../utils/get-docs-url'
import { detectTanstackQueryImports } from '../../utils/detect-react-query-imports'
import { sortDataByOrder } from './infinite-query-property-order.utils'
import { checkedProperties, infiniteQueryFunctions } from './constants'
import {
checkedProperties,
infiniteQueryFunctions,
sortRules,
} from './constants'
import type { InfiniteQueryFunctions } from './constants'
import type { ExtraRuleDocs } from '../../types'

Expand Down Expand Up @@ -50,6 +54,8 @@ export const rule = createRule({
}

const allProperties = argument.properties

// no need to sort if there is at max 1 property
if (allProperties.length < 2) {
return
}
Expand All @@ -70,11 +76,7 @@ export const rule = createRule({
return []
})

const sortedProperties = sortDataByOrder(
properties,
checkedProperties,
'name',
)
const sortedProperties = sortDataByOrder(properties, sortRules, 'name')
if (sortedProperties === null) {
return
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,56 @@
export function sortDataByOrder<T, TKey extends keyof T>(
data: Array<T> | ReadonlyArray<T>,
orderArray: Array<T[TKey]> | ReadonlyArray<T[TKey]>,
orderRules: ReadonlyArray<
Readonly<[ReadonlyArray<T[TKey]>, ReadonlyArray<T[TKey]>]>
>,
key: TKey,
): Array<T> | null {
const orderMap = new Map(orderArray.map((item, index) => [item, index]))

// Separate items that are in orderArray from those that are not
const inOrderArray = data
.filter((item) => orderMap.has(item[key]))
.sort((a, b) => {
const indexA = orderMap.get(a[key])!
const indexB = orderMap.get(b[key])!
const getSubsetIndex = (
item: T[TKey],
subsets: ReadonlyArray<ReadonlyArray<T[TKey]> | Array<T[TKey]>>,
): number | null => {
for (let i = 0; i < subsets.length; i++) {
if (subsets[i]?.includes(item)) {
return i
}
}
return null
}

return indexA - indexB
})
const orderSets = orderRules.reduce(
(sets, [A, B]) => [...sets, A, B],
[] as Array<ReadonlyArray<T[TKey]> | Array<T[TKey]>>,
)

const inOrderIterator = inOrderArray.values()
const inOrderArray = data.filter(
(item) => getSubsetIndex(item[key], orderSets) !== null,
)

// `as boolean` is needed to avoid TS incorrectly inferring that wasResorted is always `true`
let wasResorted = false as boolean

// Sort by the relative order defined by the rules
const sortedArray = inOrderArray.sort((a, b) => {
const aKey = a[key],
bKey = b[key]
const aSubsetIndex = getSubsetIndex(aKey, orderSets)
const bSubsetIndex = getSubsetIndex(bKey, orderSets)

// If both items belong to different subsets, sort by their subset order
if (
aSubsetIndex !== null &&
bSubsetIndex !== null &&
aSubsetIndex !== bSubsetIndex
) {
return aSubsetIndex - bSubsetIndex
}

// If both items belong to the same subset or neither is in the subset, keep their relative order
return 0
})

const inOrderIterator = sortedArray.values()
const result = data.map((item) => {
if (orderMap.has(item[key])) {
if (getSubsetIndex(item[key], orderSets) !== null) {
const sortedItem = inOrderIterator.next().value!
if (sortedItem[key] !== item[key]) {
wasResorted = true
Expand Down

0 comments on commit 074dc30

Please sign in to comment.