mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
ci(client): source files may not grow past their size baseline
lint:size counts the lines of every source file under client/src, tests aside. A file may hold 1000 lines; the 24 files that are longer today are listed in scripts/size-baseline.json with their current length and fail the check when they grow. --update lowers an entry once a file shrinks and never raises one, so a long file is split by concern instead of growing. A baseline that cannot be read or parsed stops the check with its reason, and --update leaves it as it is. It runs in the client job next to the reading direction check.
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"api/client.ts": 1685,
|
||||
"components/Admin/AdminPluginsPanel.tsx": 2274,
|
||||
"components/Budget/CostsPanel.tsx": 2107,
|
||||
"components/Map/MapView.tsx": 1482,
|
||||
"components/Map/MapViewGL.tsx": 2572,
|
||||
"components/Map/RouteCalculator.ts": 1190,
|
||||
"components/PDF/TripPDF.tsx": 1025,
|
||||
"components/Planner/DayPlanSidebar.tsx": 3058,
|
||||
"components/Planner/PlaceFormModal.tsx": 1633,
|
||||
"components/Planner/PlaceInspector.tsx": 1273,
|
||||
"components/Planner/TransportModal.tsx": 1199,
|
||||
"components/Roadtrip/RoadtripSidebar.tsx": 2287,
|
||||
"components/Studio/autoLayout.ts": 1676,
|
||||
"components/Studio/bookTemplates.data.ts": 1279,
|
||||
"components/Studio/TravelElements.tsx": 1745,
|
||||
"mobile/screens/admin/MAdminPluginsPanel.tsx": 2105,
|
||||
"mobile/screens/journey/MJourneyEntrySheet.tsx": 1060,
|
||||
"mobile/screens/trip/sheets/MTransportFormSheet.tsx": 1127,
|
||||
"mobile/screens/trip/tabs/MCostsTab.tsx": 1011,
|
||||
"mobile/screens/trip/tabs/MPackingListTab.tsx": 1017,
|
||||
"pages/atlas/useAtlas.ts": 1246,
|
||||
"pages/LoginPage.tsx": 1340,
|
||||
"pages/tripPlanner/useTripPlanner.ts": 3095,
|
||||
"pages/TripPlannerPage.tsx": 1427
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* lint:size: keeps source files from growing into the next god file.
|
||||
*
|
||||
* A file past a thousand lines holds several concerns that no longer fit in
|
||||
* one reading, and every change to one of them risks the others. Each source
|
||||
* file under src/ (tests aside) may hold LIMIT lines; the files that were
|
||||
* already longer are listed in scripts/size-baseline.json with the length
|
||||
* they had, and the check fails when one of them grows past its entry. A
|
||||
* file that needs to grow is split by concern instead (the trip planner hook,
|
||||
* page and road trip sidebar are the precedent).
|
||||
*
|
||||
* npm run lint:size check against the baseline (CI)
|
||||
* npm run lint:size -- --update lower the baseline to what the files hold now;
|
||||
* it never raises an entry, and drops a file
|
||||
* that is back under the limit
|
||||
*/
|
||||
import { existsSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs'
|
||||
import { join, relative } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const SRC = fileURLToPath(new URL('../src', import.meta.url))
|
||||
const BASELINE = fileURLToPath(new URL('./size-baseline.json', import.meta.url))
|
||||
|
||||
/** The most lines a file without a baseline entry may hold. */
|
||||
export const LIMIT = 1000
|
||||
|
||||
function walk(dir, files = []) {
|
||||
for (const name of readdirSync(dir)) {
|
||||
const path = join(dir, name)
|
||||
if (statSync(path).isDirectory()) walk(path, files)
|
||||
else if (/\.tsx?$/.test(name) && !/\.(?:test|spec)\./.test(name)) files.push(path)
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
/** Lines as an editor numbers them: a final newline does not start another. */
|
||||
export function lineCount(text) {
|
||||
if (text === '') return 0
|
||||
const breaks = text.split('\n').length - 1
|
||||
return text.endsWith('\n') ? breaks : breaks + 1
|
||||
}
|
||||
|
||||
function scan() {
|
||||
const counts = {}
|
||||
for (const path of walk(SRC)) counts[relative(SRC, path).split('\\').join('/')] = lineCount(readFileSync(path, 'utf8'))
|
||||
return counts
|
||||
}
|
||||
|
||||
/**
|
||||
* The baseline as committed. A missing file is an empty baseline. One that cannot be
|
||||
* read or parsed stops the run with its reason: read as empty, every long file would
|
||||
* fail with no hint at the cause, and --update would replace it with an empty one.
|
||||
*/
|
||||
function readBaseline() {
|
||||
try {
|
||||
return JSON.parse(readFileSync(BASELINE, 'utf8'))
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') return {}
|
||||
console.error(`FAIL scripts/size-baseline.json cannot be read: ${err.message}`)
|
||||
console.error('Restore the file from git before running the check or --update again.')
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Compared by real path, so the check still runs when the script is started through a symlink.
|
||||
const isCli =
|
||||
Boolean(process.argv[1]) && existsSync(process.argv[1]) && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)
|
||||
if (isCli) {
|
||||
const counts = scan()
|
||||
let baseline = readBaseline()
|
||||
|
||||
if (process.argv.includes('--update')) {
|
||||
const lowered = {}
|
||||
for (const [file, allowed] of Object.entries(baseline)) {
|
||||
const now = counts[file] ?? 0
|
||||
if (now > LIMIT) lowered[file] = Math.min(allowed, now)
|
||||
}
|
||||
const sorted = Object.fromEntries(Object.entries(lowered).sort(([a], [b]) => a.localeCompare(b)))
|
||||
writeFileSync(BASELINE, JSON.stringify(sorted, null, 2) + '\n')
|
||||
baseline = sorted
|
||||
}
|
||||
|
||||
const grown = Object.entries(counts).filter(([file, n]) => n > Math.max(baseline[file] ?? 0, LIMIT))
|
||||
const lowerable = Object.entries(baseline).filter(([file, n]) => (counts[file] ?? 0) < n)
|
||||
|
||||
for (const [file, n] of grown) {
|
||||
const entry = baseline[file]
|
||||
console.error(
|
||||
`FAIL ${file}: ${n} lines, ` +
|
||||
(entry ? `its baseline is ${entry}. ` : `the limit is ${LIMIT}. `) +
|
||||
'Move a concern into a hook or component of its own instead of growing the file.',
|
||||
)
|
||||
}
|
||||
if (lowerable.length && !process.argv.includes('--update')) {
|
||||
console.log(`${lowerable.length} file(s) are shorter than their baseline now: run with --update to lower it.`)
|
||||
}
|
||||
const listed = Object.keys(baseline).length
|
||||
console.log(`size: ${Object.keys(counts).length} file(s), ${listed} over ${LIMIT} lines held at their baseline`)
|
||||
if (grown.length) process.exit(1)
|
||||
}
|
||||
Reference in New Issue
Block a user