Files
orca/config/oxlint-plugins/app-store-performance.mjs
T
Neil 12ef12c55b chore(quality): ratchet Oxlint, React Doctor, and Zustand performance (#11034)
* chore(quality): ratchet lint and Zustand performance

* fix(ci): stabilize React peer lock snapshot

* fix(ci): isolate PR diff and React Doctor CLI
2026-07-27 18:58:36 -07:00

245 lines
6.3 KiB
JavaScript

const ALLOCATING_METHODS = new Set([
'filter',
'flat',
'flatMap',
'map',
'toReversed',
'toSorted',
'toSpliced',
'with'
])
function identifierName(node) {
return node?.type === 'Identifier' ? node.name : null
}
function propertyName(node) {
if (node?.type !== 'MemberExpression') {
return null
}
if (!node.computed) {
return identifierName(node.property)
}
return node.property?.type === 'Literal' && typeof node.property.value === 'string'
? node.property.value
: null
}
function returnedExpressions(selector) {
if (selector?.type !== 'ArrowFunctionExpression' && selector?.type !== 'FunctionExpression') {
return []
}
if (selector.body.type !== 'BlockStatement') {
return [selector.body]
}
const expressions = []
const visit = (node) => {
if (!node || typeof node !== 'object') {
return
}
if (
node !== selector.body &&
['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'].includes(node.type)
) {
return
}
if (node.type === 'ReturnStatement') {
if (node.argument) {
expressions.push(node.argument)
}
return
}
for (const [key, child] of Object.entries(node)) {
if (key === 'parent') {
continue
}
if (Array.isArray(child)) {
child.forEach(visit)
} else {
visit(child)
}
}
}
visit(selector.body)
return expressions
}
function unwrapShallowSelector(selector, shallowHooks) {
if (
selector?.type === 'CallExpression' &&
selector.callee.type === 'Identifier' &&
shallowHooks.has(selector.callee.name)
) {
return { selector: selector.arguments[0], shallow: true }
}
return { selector, shallow: false }
}
function isIdentitySelector(selector) {
if (selector?.type !== 'ArrowFunctionExpression' && selector?.type !== 'FunctionExpression') {
return false
}
const parameter = selector.params[0]
if (parameter?.type !== 'Identifier') {
return false
}
return returnedExpressions(selector).some(
(expression) => expression.type === 'Identifier' && expression.name === parameter.name
)
}
function isAllocatingExpression(expression) {
if (expression?.type === 'ConditionalExpression') {
return (
isAllocatingExpression(expression.consequent) || isAllocatingExpression(expression.alternate)
)
}
if (expression?.type === 'LogicalExpression') {
return isAllocatingExpression(expression.left) || isAllocatingExpression(expression.right)
}
if (
expression?.type === 'ArrayExpression' ||
expression?.type === 'ObjectExpression' ||
expression?.type === 'NewExpression'
) {
return true
}
if (expression?.type !== 'CallExpression') {
return false
}
const method = propertyName(expression.callee)
if (method && ALLOCATING_METHODS.has(method)) {
return true
}
const callee = expression.callee
return (
callee.type === 'MemberExpression' &&
identifierName(callee.object) === 'Object' &&
['assign', 'create', 'entries', 'fromEntries', 'keys', 'values'].includes(propertyName(callee))
)
}
function importedLocalName(specifier, importedName) {
if (specifier.type !== 'ImportSpecifier' || identifierName(specifier.imported) !== importedName) {
return null
}
return identifierName(specifier.local)
}
function createRuleState() {
return {
appStoreHooks: new Set(),
shallowHooks: new Set()
}
}
function recordImports(node, state) {
if (node.source?.value === 'zustand/react/shallow') {
for (const specifier of node.specifiers) {
const localName = importedLocalName(specifier, 'useShallow')
if (localName) {
state.shallowHooks.add(localName)
}
}
}
for (const specifier of node.specifiers) {
const localName = importedLocalName(specifier, 'useAppStore')
if (localName) {
state.appStoreHooks.add(localName)
}
}
}
function isAppStoreCall(node, state) {
return (
node.callee.type === 'Identifier' &&
state.appStoreHooks.has(node.callee.name) &&
node.optional !== true
)
}
function requireSelectorRule() {
const state = createRuleState()
return {
ImportDeclaration(node) {
recordImports(node, state)
},
CallExpression(node) {
if (isAppStoreCall(node, state) && node.arguments.length === 0) {
this.report({
node,
message:
'Pass a selector to useAppStore so the component does not rerender for every store write.'
})
}
}
}
}
function noIdentitySelectorRule() {
const state = createRuleState()
return {
ImportDeclaration(node) {
recordImports(node, state)
},
CallExpression(node) {
if (!isAppStoreCall(node, state)) {
return
}
const { selector } = unwrapShallowSelector(node.arguments[0], state.shallowHooks)
if (isIdentitySelector(selector)) {
this.report({
node: selector,
message:
'Select the smallest required fields instead of subscribing to the entire app store.'
})
}
}
}
}
function noFreshSelectorResultRule() {
const state = createRuleState()
return {
ImportDeclaration(node) {
recordImports(node, state)
},
CallExpression(node) {
if (!isAppStoreCall(node, state)) {
return
}
const { selector, shallow } = unwrapShallowSelector(node.arguments[0], state.shallowHooks)
if (shallow) {
return
}
const freshResult = returnedExpressions(selector).find(isAllocatingExpression)
if (freshResult) {
this.report({
node: freshResult,
message:
'This selector returns a fresh reference on every store write; select a stable field, cache the result, or use useShallow.'
})
}
}
}
}
function bindContext(createVisitors) {
return (context) => {
const visitors = createVisitors()
for (const [nodeType, visit] of Object.entries(visitors)) {
visitors[nodeType] = visit.bind(context)
}
return visitors
}
}
export default {
meta: { name: 'app-store-performance' },
rules: {
'require-selector': { create: bindContext(requireSelectorRule) },
'no-identity-selector': { create: bindContext(noIdentitySelectorRule) },
'no-fresh-selector-result': { create: bindContext(noFreshSelectorResultRule) }
}
}