function-size
a function, hook or component over its line cap
The argument
Function Size Limits
| Lines | Assessment |
|---|---|
| <=50 | Fine |
| 50-80 | Review — look for extractable logic |
| 80+ | Needs splitting |
The one exemption, in one sentence: a function is exempt when every
statement is a call to a named step (or the assignment of its result) and the
control flow is linear — any inline loop, branch, or transformation
disqualifies it. A 150-line start() calling 8 step functions is fine; a
150-line function with an inline loop is not. Such a function has no logic to
extract, and splitting it would only scatter the sequence over more files than
the reader has to hold.
Reach for the exemption last, not first. It is the verdict a function earns once the logic is out of it, not a reason to leave the logic in — and a function that is genuinely just sequencing calls will be short enough that the cap never asks. Extract the work into named pieces, then look again: what remains is either under the cap or visibly a sequence, and either way the question has answered itself.
Extraction moves cost rather than removing it. Each piece pulled out is a new name to read and, if it leaves the file, a new file in the folder. Split because the piece deserves a name, not to buy back lines.
The proof
interface Row {
region: string;
product: string;
currency: string;
amount: number;
refunded: boolean;
}
interface Params {
rows: Row[];
rates: Record<string, number>;
}
// Every step of the summary inlined into one body — not a linear flow of step
// calls, just a long function, which is the shape the table calls "needs
// splitting".
export const buildReportSummary = ({ rows, rates }: Params): Record<string, number> => {
const regionTotals: Record<string, number> = {};
const regionRefunds: Record<string, number> = {};
const regionCounts: Record<string, number> = {};
for (const row of rows) {
const converted = row.amount * (rates[row.currency] ?? 1);
if (row.refunded) {
regionRefunds[row.region] = (regionRefunds[row.region] ?? 0) + converted;
} else {
regionTotals[row.region] = (regionTotals[row.region] ?? 0) + converted;
}
regionCounts[row.region] = (regionCounts[row.region] ?? 0) + 1;
}
const productTotals: Record<string, number> = {};
const productRefunds: Record<string, number> = {};
const productCounts: Record<string, number> = {};
for (const row of rows) {
const converted = row.amount * (rates[row.currency] ?? 1);
if (row.refunded) {
productRefunds[row.product] = (productRefunds[row.product] ?? 0) + converted;
} else {
productTotals[row.product] = (productTotals[row.product] ?? 0) + converted;
}
productCounts[row.product] = (productCounts[row.product] ?? 0) + 1;
}
const currencyTotals: Record<string, number> = {};
const currencyRefunds: Record<string, number> = {};
const currencyCounts: Record<string, number> = {};
for (const row of rows) {
const converted = row.amount * (rates[row.currency] ?? 1);
if (row.refunded) {
currencyRefunds[row.currency] = (currencyRefunds[row.currency] ?? 0) + converted;
} else {
currencyTotals[row.currency] = (currencyTotals[row.currency] ?? 0) + converted;
}
currencyCounts[row.currency] = (currencyCounts[row.currency] ?? 0) + 1;
}
const summary: Record<string, number> = {};
for (const [region, total] of Object.entries(regionTotals)) {
summary[`region.${region}.total`] = total;
summary[`region.${region}.refunded`] = regionRefunds[region] ?? 0;
summary[`region.${region}.count`] = regionCounts[region] ?? 0;
summary[`region.${region}.average`] = total / (regionCounts[region] ?? 1);
}
for (const [product, total] of Object.entries(productTotals)) {
summary[`product.${product}.total`] = total;
summary[`product.${product}.refunded`] = productRefunds[product] ?? 0;
summary[`product.${product}.count`] = productCounts[product] ?? 0;
summary[`product.${product}.average`] = total / (productCounts[product] ?? 1);
}
for (const [currency, total] of Object.entries(currencyTotals)) {
summary[`currency.${currency}.total`] = total;
summary[`currency.${currency}.refunded`] = currencyRefunds[currency] ?? 0;
summary[`currency.${currency}.count`] = currencyCounts[currency] ?? 0;
summary[`currency.${currency}.average`] = total / (currencyCounts[currency] ?? 1);
}
summary['rows.counted'] = rows.length;
summary['rows.refunded'] = rows.filter((row) => row.refunded).length;
summary['rows.settled'] = rows.filter((row) => !row.refunded).length;
summary['regions.counted'] = Object.keys(regionTotals).length;
summary['products.counted'] = Object.keys(productTotals).length;
summary['currencies.counted'] = Object.keys(currencyTotals).length;
for (const [region, count] of Object.entries(regionCounts)) {
summary[`region.${region}.share`] = count / rows.length;
}
for (const [product, count] of Object.entries(productCounts)) {
summary[`product.${product}.share`] = count / rows.length;
}
for (const [currency, count] of Object.entries(currencyCounts)) {
summary[`currency.${currency}.share`] = count / rows.length;
}
return summary;
};
interface Row {
region: string;
product: string;
currency: string;
amount: number;
refunded: boolean;
}
interface Params {
rows: Row[];
rates: Record<string, number>;
}
// One grouping helper the export reuses three times — the same report, with the
// body under the cap because the repeated work was named once.
const sumBy = ({ rows, rates, key }: { rows: Row[]; rates: Record<string, number>; key: keyof Row }) => {
const totals: Record<string, number> = {};
for (const row of rows) {
const group = String(row[key]);
totals[group] = (totals[group] ?? 0) + row.amount * (rates[row.currency] ?? 1);
}
return totals;
};
export const buildReportSummary = ({ rows, rates }: Params): Record<string, number> => {
const summary: Record<string, number> = {};
for (const key of ['region', 'product', 'currency'] as const) {
for (const [group, total] of Object.entries(sumBy({ rows, rates, key }))) {
summary[`${key}.${group}.total`] = total;
}
}
summary['rows.counted'] = rows.length;
return summary;
};
Its numbers
The defaults the pack ships. A repo may set its own.
- function
- 80
- hook
- 160
- component
- 200
{
"standards-checks": {
"function-size": {
"settings": {
"function": 80,
"hook": 160,
"component": 200
}
}
}
}Turn it down
Both lines go in your lightsout.config.json.
"standards-checks": { "function-size": "advisory" }"standards-checks": { "function-size": "off" }