Skip to content

Commit 8fe73e2

Browse files
authored
Merge pull request #25 from veillette/claude/stricter-format-linter-lC2kq
chore: enforce stricter Biome lint/format rules and TypeScript checks
2 parents 96a72bc + d26d50f commit 8fe73e2

37 files changed

Lines changed: 822 additions & 1426 deletions

biome.json

Lines changed: 119 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "https://biomejs.dev/schemas/2.4.2/schema.json",
2+
"$schema": "https://biomejs.dev/schemas/2.4.4/schema.json",
33
"assist": {
44
"actions": {
55
"source": {
@@ -10,21 +10,136 @@
1010
"linter": {
1111
"enabled": true,
1212
"rules": {
13-
"recommended": true
13+
"recommended": true,
14+
"nursery": {
15+
"recommended": true,
16+
"noShadow": "error",
17+
"useExplicitType": "warn",
18+
"noEqualsToNull": "error",
19+
"noReturnAssign": "error",
20+
"useArraySortCompare": "error",
21+
"useFind": "warn",
22+
"useSpread": "warn"
23+
},
24+
"correctness": {
25+
"recommended": true,
26+
"noUnusedImports": "error",
27+
"noUnusedVariables": "error",
28+
"noUnusedPrivateClassMembers": "error",
29+
"noUndeclaredVariables": "error",
30+
"noUnreachable": "error",
31+
"noInvalidUseBeforeDeclaration": "error"
32+
},
33+
"style": {
34+
"recommended": true,
35+
"noDefaultExport": "off",
36+
"noParameterAssign": "error",
37+
"useBlockStatements": "error",
38+
"useCollapsedElseIf": "warn",
39+
"useConsistentBuiltinInstantiation": "error",
40+
"useDefaultParameterLast": "error",
41+
"useExplicitLengthCheck": "warn",
42+
"useForOf": "warn",
43+
"useFragmentSyntax": "warn",
44+
"useImportType": "error",
45+
"useShorthandAssign": "warn",
46+
"useShorthandFunctionType": "warn",
47+
"useThrowNewError": "error",
48+
"useThrowOnlyError": "error",
49+
"noCommonJs": "error",
50+
"noExportedImports": "off",
51+
"useNamingConvention": {
52+
"level": "warn",
53+
"options": {
54+
"conventions": [
55+
{
56+
"selector": { "kind": "variable" },
57+
"formats": ["camelCase", "CONSTANT_CASE", "PascalCase"]
58+
},
59+
{
60+
"selector": { "kind": "function" },
61+
"formats": ["camelCase", "PascalCase"]
62+
},
63+
{
64+
"selector": { "kind": "typeLike" },
65+
"formats": ["PascalCase"]
66+
},
67+
{
68+
"selector": { "kind": "enumMember" },
69+
"formats": ["CONSTANT_CASE", "PascalCase"]
70+
}
71+
]
72+
}
73+
},
74+
"useFilenamingConvention": {
75+
"level": "warn",
76+
"options": {
77+
"requireAscii": true,
78+
"filenameCases": ["PascalCase", "camelCase"]
79+
}
80+
}
81+
},
82+
"suspicious": {
83+
"recommended": true,
84+
"noConsole": "warn",
85+
"noEmptyBlockStatements": "error",
86+
"noExplicitAny": "warn",
87+
"useAwait": "error",
88+
"useErrorMessage": "error",
89+
"useGuardForIn": "error",
90+
"useIsArray": "error",
91+
"noIrregularWhitespace": "off"
92+
},
93+
"complexity": {
94+
"recommended": true,
95+
"noExcessiveCognitiveComplexity": {
96+
"level": "warn",
97+
"options": {
98+
"maxAllowedComplexity": 25
99+
}
100+
},
101+
"noForEach": "warn",
102+
"noStaticOnlyClass": "error",
103+
"noUselessStringConcat": "error",
104+
"noUselessUndefinedInitialization": "error",
105+
"noVoid": "error",
106+
"useArrowFunction": "warn",
107+
"useDateNow": "error",
108+
"useFlatMap": "warn",
109+
"useSimplifiedLogicExpression": "warn"
110+
},
111+
"performance": {
112+
"recommended": true,
113+
"noAccumulatingSpread": "warn",
114+
"noBarrelFile": "warn",
115+
"noDelete": "warn",
116+
"noReExportAll": "warn"
117+
},
118+
"a11y": {
119+
"recommended": true
120+
},
121+
"security": {
122+
"recommended": true,
123+
"noGlobalEval": "error"
124+
}
14125
}
15126
},
16127
"formatter": {
17128
"enabled": true,
18129
"indentStyle": "space",
19-
"indentWidth": 2
130+
"indentWidth": 2,
131+
"lineWidth": 120
20132
},
21133
"files": {
22134
"includes": ["**/*.ts", "**/*.js", "**/*.json", "**/*.html", "!dist"]
23135
},
24136
"javascript": {
25137
"formatter": {
26138
"quoteStyle": "double",
27-
"semicolons": "always"
139+
"semicolons": "always",
140+
"trailingCommas": "all",
141+
"arrowParentheses": "always",
142+
"bracketSameLine": false
28143
}
29144
}
30145
}

scripts/bouncingBallToSVG.ts

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,7 @@ const FLOOR_WIDTH = 0.8; // stroke-width
2929
* T_n ∝ sqrt(h_n). We normalise so the total "time" = 1.
3030
*/
3131
function buildBounces(): Array<{ tStart: number; tEnd: number; h: number }> {
32-
const heights = Array.from(
33-
{ length: NUM_BOUNCES },
34-
(_, n) => INITIAL_HEIGHT * RESTITUTION ** n,
35-
);
32+
const heights = Array.from({ length: NUM_BOUNCES }, (_, n) => INITIAL_HEIGHT * RESTITUTION ** n);
3633

3734
// flight time ∝ sqrt(h)
3835
const durations = heights.map((h) => Math.sqrt(h));
@@ -59,9 +56,7 @@ function computeSnapshots(): Point[] {
5956

6057
// x positions of bounce endpoints (floor contacts) spaced proportionally
6158
// to flight time (= proportional to normalised duration)
62-
const xContacts: number[] = bounces.map(
63-
(b) => X_START + b.tStart * (X_END - X_START),
64-
);
59+
const xContacts: number[] = bounces.map((b) => X_START + b.tStart * (X_END - X_START));
6560
xContacts.push(X_END); // final landing
6661

6762
for (let i = 0; i < TOTAL_SNAPSHOTS; i++) {
@@ -70,7 +65,9 @@ function computeSnapshots(): Point[] {
7065

7166
// find which bounce this snapshot falls in
7267
const bounce = bounces.find((b) => tGlobal >= b.tStart && tGlobal < b.tEnd);
73-
if (!bounce) continue; // shouldn't happen
68+
if (!bounce) {
69+
continue; // shouldn't happen
70+
}
7471

7572
const bounceIdx = bounces.indexOf(bounce);
7673
// local time within this bounce, in [0, 1]
@@ -93,13 +90,9 @@ function computeSnapshots(): Point[] {
9390
return points;
9491
}
9592

96-
function buildSVG(points: Point[]): string {
97-
const circles = points
98-
.map(
99-
(p) =>
100-
` <circle cx="${p.x}" cy="${p.y}" r="${BALL_RADIUS}" ` +
101-
`fill="${BALL_COLOR}" opacity="${BALL_OPACITY}"/>`,
102-
)
93+
function buildSvg(snapshots: Point[]): string {
94+
const circles = snapshots
95+
.map((p) => ` <circle cx="${p.x}" cy="${p.y}" r="${BALL_RADIUS}" fill="${BALL_COLOR}" opacity="${BALL_OPACITY}"/>`)
10396
.join("\n");
10497

10598
return `<svg xmlns="http://www.w3.org/2000/svg" \
@@ -113,12 +106,9 @@ ${circles}
113106
}
114107

115108
const points = computeSnapshots();
116-
const svg = buildSVG(points);
109+
const svg = buildSvg(points);
117110

118111
// Write to public/icons/icon.svg (source for PNG icon generation)
119112
const outputPath = "public/icons/icon.svg";
120113
fs.mkdirSync("public/icons", { recursive: true });
121114
fs.writeFileSync(outputPath, svg, "utf8");
122-
console.log(
123-
`✓ Generated ${outputPath} with ${points.length} bouncing ball snapshots`,
124-
);

scripts/generate-icons.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { fileURLToPath } from "node:url";
44
import pngToIco from "png-to-ico";
55
import sharp from "sharp";
66

7-
const __dirname = dirname(fileURLToPath(import.meta.url));
8-
const root = resolve(__dirname, "..");
7+
const Dirname = dirname(fileURLToPath(import.meta.url));
8+
const root = resolve(Dirname, "..");
99

1010
const svgBuffer = readFileSync(resolve(root, "public/icons/icon.svg"));
1111

@@ -19,7 +19,6 @@ const pngIcons = [
1919
for (const { name, size } of pngIcons) {
2020
const dest = resolve(root, "public", name);
2121
await sharp(svgBuffer).resize(size, size).png().toFile(dest);
22-
console.log(`Generated ${name} (${size}x${size})`);
2322
}
2423

2524
// Generate multi-size favicon.ico (16, 32, 48, 64)
@@ -34,4 +33,3 @@ for (const size of faviconSizes) {
3433
const icoBuffer = await pngToIco(faviconPngs);
3534
const faviconDest = resolve(root, "public", "favicon.ico");
3635
writeFileSync(faviconDest, icoBuffer);
37-
console.log(`Generated favicon.ico (16x16, 32x32, 48x48, 64x64)`);

0 commit comments

Comments
 (0)