diff --git a/config/scripts/app-store-performance-plugin.test.mjs b/config/scripts/app-store-performance-plugin.test.mjs index d8e2568165f..b07f07c26b1 100644 --- a/config/scripts/app-store-performance-plugin.test.mjs +++ b/config/scripts/app-store-performance-plugin.test.mjs @@ -1,14 +1,14 @@ import path from 'node:path' -import { describe, expect, it } from 'vitest' -import { runOxlintPluginOnSource } from './oxlint-plugin-test-runner.mjs' +import { beforeAll, describe, expect, it } from 'vitest' +import { runOxlintPluginOnSources } from './oxlint-plugin-test-runner.mjs' const pluginPath = path.resolve('config/oxlint-plugins/app-store-performance.mjs') -function lintSource(source) { - return runOxlintPluginOnSource({ +function lintSources(sources) { + return runOxlintPluginOnSources({ pluginName: 'app-store-performance', pluginPath, - source, + sources, rules: { 'app-store-performance/require-selector': 'warn', 'app-store-performance/no-identity-selector': 'warn', @@ -18,9 +18,8 @@ function lintSource(source) { }) } -describe('app store performance Oxlint plugin', () => { - it('reports whole-store and fresh-reference subscriptions', () => { - const diagnostics = lintSource(` +const sources = { + subscriptions: ` import { useAppStore as useStore } from '@/store' const WholeStore = () => useStore() const Identity = () => useStore((state) => state) @@ -30,7 +29,75 @@ describe('app store performance Oxlint plugin', () => { if (state.active) return state.items.filter(Boolean) return state.items }) - `) + `, + focusedSelectors: ` + import { useAppStore } from '@/store' + import { useShallow as shallow } from 'zustand/react/shallow' + const selectActive = (state) => state.active + const Focused = () => useAppStore(selectActive) + const Cached = () => useAppStore((state) => state.cachedProjection) + const Shallow = () => useAppStore(shallow((state) => ({ active: state.active }))) + `, + namedSelectors: ` + import { useAppStore } from '@/store' + const EarlyFresh = () => useAppStore(selectFreshRows) + const selectFreshRows = (state) => state.rows.filter(Boolean) + const Stable = () => useAppStore(selectActiveId) + const selectActiveId = (state) => state.activeId + `, + importedSelector: ` + import { useAppStore } from '@/store' + import { selectRows } from './selectors' + const Other = () => { + const selectRows = (state) => state.rows.map((row) => row.id) + return selectRows + } + const Imported = () => useAppStore(selectRows) + `, + siblingStores: ` + import { usePluginPanelsStore } from '@/store/plugin-panels' + import { useSyncExternalStore } from 'react' + const WholePanels = () => usePluginPanelsStore() + const FreshPanels = () => usePluginPanelsStore((state) => ({ open: state.open })) + const External = () => useSyncExternalStore(subscribe, () => ({ open: true })) + `, + nestedShallow: ` + import { useAppStore } from '@/store' + import { useShallow } from 'zustand/react/shallow' + const NestedObject = () => useAppStore(useShallow((state) => ({ ids: state.rows.map((row) => row.id) }))) + const NestedArray = () => useAppStore(useShallow((state) => [state.activeId, state.rows.filter(Boolean)])) + const Flat = () => useAppStore(useShallow((state) => ({ activeId: state.activeId, rows: state.rows }))) + `, + delegatingSelectors: ` + import { useAppStore } from '@/store' + import { useShallow } from 'zustand/react/shallow' + const buildRows = (state) => state.rows.map((row) => row.id) + const Delegating = () => useAppStore((state) => buildRows(state)) + const NestedDelegating = () => useAppStore(useShallow((state) => ({ ids: buildRows(state) }))) + `, + cachedHelpers: ` + import { useAppStore } from '@/store' + import { useShallow } from 'zustand/react/shallow' + // The identity-caching shape: fresh only on a miss, cached otherwise. + const selectCachedRows = (state) => cache.get(state.key) ?? state.rows.filter(Boolean) + const Cached = () => useAppStore((state) => selectCachedRows(state)) + const CachedNested = () => useAppStore(useShallow((state) => ({ rows: selectCachedRows(state) }))) + // An unknown helper cannot be resolved, so it must not be guessed at. + const External = () => useAppStore((state) => externalBuild(state)) + ` +} + +const diagnosticsBySource = new Map() +beforeAll(() => { + const fixtures = Object.values(sources) + lintSources(fixtures).forEach((diagnostics, index) => { + diagnosticsBySource.set(fixtures[index], diagnostics) + }) +}) + +describe('app store performance Oxlint plugin', () => { + it('reports whole-store and fresh-reference subscriptions', () => { + const diagnostics = diagnosticsBySource.get(sources.subscriptions) expect(diagnostics.map((diagnostic) => diagnostic.code)).toEqual([ 'app-store-performance(require-selector)', @@ -42,26 +109,13 @@ describe('app store performance Oxlint plugin', () => { }) it('allows focused, cached, and useShallow selectors', () => { - const diagnostics = lintSource(` - import { useAppStore } from '@/store' - import { useShallow as shallow } from 'zustand/react/shallow' - const selectActive = (state) => state.active - const Focused = () => useAppStore(selectActive) - const Cached = () => useAppStore((state) => state.cachedProjection) - const Shallow = () => useAppStore(shallow((state) => ({ active: state.active }))) - `) + const diagnostics = diagnosticsBySource.get(sources.focusedSelectors) expect(diagnostics).toEqual([]) }) it('resolves selectors referenced by name, including ones hoisted below the call', () => { - const diagnostics = lintSource(` - import { useAppStore } from '@/store' - const EarlyFresh = () => useAppStore(selectFreshRows) - const selectFreshRows = (state) => state.rows.filter(Boolean) - const Stable = () => useAppStore(selectActiveId) - const selectActiveId = (state) => state.activeId - `) + const diagnostics = diagnosticsBySource.get(sources.namedSelectors) expect(diagnostics.map((diagnostic) => diagnostic.code)).toEqual([ 'app-store-performance(no-fresh-selector-result)' @@ -69,27 +123,13 @@ describe('app store performance Oxlint plugin', () => { }) it('does not let a component-local helper resolve a same-named imported selector', () => { - const diagnostics = lintSource(` - import { useAppStore } from '@/store' - import { selectRows } from './selectors' - const Other = () => { - const selectRows = (state) => state.rows.map((row) => row.id) - return selectRows - } - const Imported = () => useAppStore(selectRows) - `) + const diagnostics = diagnosticsBySource.get(sources.importedSelector) expect(diagnostics).toEqual([]) }) it('covers sibling store hooks but not useSyncExternalStore', () => { - const diagnostics = lintSource(` - import { usePluginPanelsStore } from '@/store/plugin-panels' - import { useSyncExternalStore } from 'react' - const WholePanels = () => usePluginPanelsStore() - const FreshPanels = () => usePluginPanelsStore((state) => ({ open: state.open })) - const External = () => useSyncExternalStore(subscribe, () => ({ open: true })) - `) + const diagnostics = diagnosticsBySource.get(sources.siblingStores) expect(diagnostics.map((diagnostic) => diagnostic.code)).toEqual([ 'app-store-performance(require-selector)', @@ -98,13 +138,7 @@ describe('app store performance Oxlint plugin', () => { }) it('reports fresh references nested inside a useShallow projection', () => { - const diagnostics = lintSource(` - import { useAppStore } from '@/store' - import { useShallow } from 'zustand/react/shallow' - const NestedObject = () => useAppStore(useShallow((state) => ({ ids: state.rows.map((row) => row.id) }))) - const NestedArray = () => useAppStore(useShallow((state) => [state.activeId, state.rows.filter(Boolean)])) - const Flat = () => useAppStore(useShallow((state) => ({ activeId: state.activeId, rows: state.rows }))) - `) + const diagnostics = diagnosticsBySource.get(sources.nestedShallow) expect(diagnostics.map((diagnostic) => diagnostic.code)).toEqual([ 'app-store-performance(no-nested-fresh-under-shallow)', @@ -113,13 +147,7 @@ describe('app store performance Oxlint plugin', () => { }) it('follows a selector one hop into a module-scope helper', () => { - const diagnostics = lintSource(` - import { useAppStore } from '@/store' - import { useShallow } from 'zustand/react/shallow' - const buildRows = (state) => state.rows.map((row) => row.id) - const Delegating = () => useAppStore((state) => buildRows(state)) - const NestedDelegating = () => useAppStore(useShallow((state) => ({ ids: buildRows(state) }))) - `) + const diagnostics = diagnosticsBySource.get(sources.delegatingSelectors) expect(diagnostics.map((diagnostic) => diagnostic.code)).toEqual([ 'app-store-performance(no-fresh-selector-result)', @@ -128,16 +156,7 @@ describe('app store performance Oxlint plugin', () => { }) it('does not flag a helper that returns a cached reference on some branch', () => { - const diagnostics = lintSource(` - import { useAppStore } from '@/store' - import { useShallow } from 'zustand/react/shallow' - // The identity-caching shape: fresh only on a miss, cached otherwise. - const selectCachedRows = (state) => cache.get(state.key) ?? state.rows.filter(Boolean) - const Cached = () => useAppStore((state) => selectCachedRows(state)) - const CachedNested = () => useAppStore(useShallow((state) => ({ rows: selectCachedRows(state) }))) - // An unknown helper cannot be resolved, so it must not be guessed at. - const External = () => useAppStore((state) => externalBuild(state)) - `) + const diagnostics = diagnosticsBySource.get(sources.cachedHelpers) expect(diagnostics).toEqual([]) }) diff --git a/config/scripts/sort-comparator-performance-plugin.test.mjs b/config/scripts/sort-comparator-performance-plugin.test.mjs index a9319c6238d..378750a85ef 100644 --- a/config/scripts/sort-comparator-performance-plugin.test.mjs +++ b/config/scripts/sort-comparator-performance-plugin.test.mjs @@ -1,35 +1,24 @@ import path from 'node:path' -import { describe, expect, it } from 'vitest' -import { runOxlintPluginOnSource } from './oxlint-plugin-test-runner.mjs' +import { beforeAll, describe, expect, it } from 'vitest' +import { runOxlintPluginOnSources } from './oxlint-plugin-test-runner.mjs' -function lint(source) { - return runOxlintPluginOnSource({ +function lintSources(sources) { + return runOxlintPluginOnSources({ pluginName: 'sort-comparator-performance', pluginPath: path.resolve('config/oxlint-plugins/sort-comparator-performance.mjs'), rules: { 'sort-comparator-performance/no-repeated-collator': 'warn' }, - source + sources }) } -describe('sort comparator performance', () => { - it('reports repeated collation setup in inline sort and toSorted callbacks', () => { - const findings = lint(` +const sources = { + repeatedCollation: ` rows.sort((a, b) => a.name.localeCompare(b.name, locale, { sensitivity: 'base' })) rows.toSorted(function (a, b) { return new Intl.Collator('sv').compare(a, b) }) rows['sort']((a, b) => Intl.Collator('en', { numeric: true }).compare(a, b)) rows.sort((a, b) => a['localeCompare'](b, undefined, options)) - `) - expect(findings).toHaveLength(4) - expect( - findings.every( - (finding) => finding.code === 'sort-comparator-performance(no-repeated-collator)' - ) - ).toBe(true) - }) - - it('allows one collator per sort, bare comparisons, and unrelated callbacks', () => { - expect( - lint(` + `, + acceptedComparisons: ` const collator = new Intl.Collator(locale, options) rows.sort((a, b) => collator.compare(a.name, b.name) || a.id.localeCompare(b.id)) rows.toSorted(collator.compare) @@ -39,7 +28,29 @@ describe('sort comparator performance', () => { function deferred() { return new Intl.Collator(locale) } return a - b }) - `) - ).toEqual([]) + ` +} + +const diagnosticsBySource = new Map() +beforeAll(() => { + const fixtures = Object.values(sources) + lintSources(fixtures).forEach((diagnostics, index) => { + diagnosticsBySource.set(fixtures[index], diagnostics) + }) +}) + +describe('sort comparator performance', () => { + it('reports repeated collation setup in inline sort and toSorted callbacks', () => { + const findings = diagnosticsBySource.get(sources.repeatedCollation) + expect(findings).toHaveLength(4) + expect( + findings.every( + (finding) => finding.code === 'sort-comparator-performance(no-repeated-collator)' + ) + ).toBe(true) + }) + + it('allows one collator per sort, bare comparisons, and unrelated callbacks', () => { + expect(diagnosticsBySource.get(sources.acceptedComparisons)).toEqual([]) }) })