Skip to content

Commit a9a9de4

Browse files
committed
chore: adopt eslint-plugin-unicorn v68 and resolve fallout
Bump eslint-plugin-unicorn 65 -> 68 (and viem 2.52 -> 2.53), then work through the new rule violations. Rules disabled (with rationale in eslint.config.mjs): - name-replacements (preference) - no-useless-recursion (conflicts with functional/no-loop-statements) - prefer-else-if (conflicts with the no-else / early-return style) - prefer-await (conflicts with no-try/catch and deliberate promise combinators) - no-top-level-assignment-in-function, no-global-object-property-assignment, max-nested-calls (test files only) - no-top-level-side-effects (cli command modules only) Rules fixed in code: - prefer-iterator-to-array, require-array-sort-compare, prefer-url-href, prefer-minimal-ternary, no-unsafe-string-replacement, no-computed-property-existence-check, no-declarations-before-early-exit, no-break-in-nested-loop, no-non-function-verb-prefix - consistent-boolean-name renames: isWithinRateLimit, isConstantTimeEqual, hasAddressInRecords, isTruthy, shouldInterpolate, shouldTrustForwardedFor Style: - curly: 'multi-line' -> 'all' (braces required on all control flow) Tests: - Backfill coverage for branches the brace change exposed (server, process, json-path, pipeline, auth, plugins, async, proof). 509 -> 573 tests.
1 parent 803d1e6 commit a9a9de4

52 files changed

Lines changed: 1531 additions & 368 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bun.lock

Lines changed: 36 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

eslint.config.mjs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export default defineConfig([
5656
'no-else-return': ['error', { allowElseIf: false }],
5757

5858
// --- General strict rules ---
59-
curly: ['error', 'multi-line'],
59+
curly: ['error', 'all'],
6060
eqeqeq: ['error', 'always'],
6161
'no-console': ['warn', { allow: ['info', 'warn', 'error'] }],
6262
'no-eval': 'error',
@@ -74,6 +74,14 @@ export default defineConfig([
7474

7575
// --- Unicorn overrides ---
7676
'unicorn/prevent-abbreviations': 'off',
77+
'unicorn/name-replacements': 'off',
78+
// Conflicts with functional/no-loop-statements (recursion replaces loops).
79+
'unicorn/no-useless-recursion': 'off',
80+
// Conflicts with the no-else / early-return style (wants `else if`).
81+
'unicorn/prefer-else-if': 'off',
82+
// Conflicts with the no-try/catch convention and deliberate promise
83+
// combinators used to cache and race promises.
84+
'unicorn/prefer-await': 'off',
7785

7886
// --- Import ordering (alphabetical) ---
7987
'import-x/order': [
@@ -93,6 +101,19 @@ export default defineConfig([
93101

94102
{
95103
files: ['**/*.test.ts', '**/*.test.tsx'],
96-
rules: Object.fromEntries(Object.keys(functionalPlugin.rules).map((rule) => [`functional/${rule}`, 'off'])),
104+
rules: {
105+
...Object.fromEntries(Object.keys(functionalPlugin.rules).map((rule) => [`functional/${rule}`, 'off'])),
106+
// Standard async fixture pattern: `let ctx; beforeAll(() => { ctx = ... })`.
107+
'unicorn/no-top-level-assignment-in-function': 'off',
108+
// Tests mock process globals (e.g. fetch) and nest calls in assertions.
109+
'unicorn/no-global-object-property-assignment': 'off',
110+
'unicorn/max-nested-calls': 'off',
111+
},
112+
},
113+
114+
{
115+
// CLI command modules build commander.js commands at module load.
116+
files: ['src/cli/**/*.ts'],
117+
rules: { 'unicorn/no-top-level-side-effects': 'off' },
97118
},
98119
]);

examples/plugins/encrypted-channel.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,9 @@ export default function encryptedChannel(config: Config): AirnodePlugin {
201201
// =======================================================================
202202
onBeforeApiCall: (ctx: BeforeApiCallContext): BeforeApiCallResult => {
203203
const encrypted = ctx.parameters['_encrypted'];
204-
if (!encrypted) return;
204+
if (!encrypted) {
205+
return;
206+
}
205207

206208
const ciphertext = hexToBytes(encrypted);
207209
if (ciphertext.length < PUBKEY_LENGTH + NONCE_LENGTH + TAG_LENGTH) {
@@ -241,7 +243,9 @@ export default function encryptedChannel(config: Config): AirnodePlugin {
241243
// =======================================================================
242244
onBeforeSign: (ctx: BeforeSignContext): BeforeSignResult => {
243245
const responsePubKey = responseKeys.get(ctx.requestId);
244-
if (!responsePubKey) return;
246+
if (!responsePubKey) {
247+
return;
248+
}
245249

246250
// Clean up stored key
247251
responseKeys.delete(ctx.requestId); // eslint-disable-line functional/immutable-data

examples/plugins/heartbeat.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ export default function heartbeat(config: Config): AirnodePlugin {
6868
name: 'heartbeat',
6969
hooks: {
7070
onResponseSent: async (context: ResponseSentContext) => {
71-
if (!config.url) return;
71+
if (!config.url) {
72+
return;
73+
}
7274

7375
const payload: HeartbeatPayload = {
7476
timestamp: new Date().toISOString(),
@@ -80,7 +82,7 @@ export default function heartbeat(config: Config): AirnodePlugin {
8082

8183
const headers: Record<string, string> = {
8284
'Content-Type': 'application/json',
83-
...(config.apiKey ? { 'x-api-key': config.apiKey } : {}),
85+
...(config.apiKey && { 'x-api-key': config.apiKey }),
8486
};
8587

8688
await fetch(config.url, {

integration/helpers.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,12 @@ async function createTestServer(options: TestServerOptions = {}): Promise<TestCo
111111
}
112112

113113
function findEndpointId(endpointMap: ReadonlyMap<Hex, ResolvedEndpoint>, apiName: string, endpointName: string): Hex {
114-
const entry = [...endpointMap.entries()].find(
114+
const entry = [...endpointMap].find(
115115
([, resolved]) => resolved.api.name === apiName && resolved.endpoint.name === endpointName
116116
);
117-
if (!entry) throw new Error(`Endpoint ${apiName}/${endpointName} not found`);
117+
if (!entry) {
118+
throw new Error(`Endpoint ${apiName}/${endpointName} not found`);
119+
}
118120
return entry[0];
119121
}
120122

@@ -137,7 +139,9 @@ async function setMockResponse(path: string, response: unknown, status = 200): P
137139
headers: { 'Content-Type': 'application/json' },
138140
body: JSON.stringify({ path, response, status }),
139141
});
140-
if (!result.ok) throw new Error(`Failed to set mock response: ${String(result.status)}`);
142+
if (!result.ok) {
143+
throw new Error(`Failed to set mock response: ${String(result.status)}`);
144+
}
141145
}
142146

143147
interface RecordedCall {
@@ -149,13 +153,17 @@ interface RecordedCall {
149153

150154
async function getMockCalls(): Promise<readonly RecordedCall[]> {
151155
const response = await fetch(`${MOCK_API_URL}/mock/calls`);
152-
if (!response.ok) throw new Error(`Failed to get mock calls: ${String(response.status)}`);
156+
if (!response.ok) {
157+
throw new Error(`Failed to get mock calls: ${String(response.status)}`);
158+
}
153159
return (await response.json()) as RecordedCall[];
154160
}
155161

156162
async function resetMock(): Promise<void> {
157163
const result = await fetch(`${MOCK_API_URL}/mock/reset`, { method: 'POST' });
158-
if (!result.ok) throw new Error(`Failed to reset mock: ${String(result.status)}`);
164+
if (!result.ok) {
165+
throw new Error(`Failed to reset mock: ${String(result.status)}`);
166+
}
159167
}
160168

161169
export {

integration/mock-api.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ const DEFAULT_RESPONSES: Record<string, unknown> = {
3232
function findDefaultResponse(pathname: string): unknown {
3333
// eslint-disable-next-line functional/no-loop-statements
3434
for (const [prefix, response] of Object.entries(DEFAULT_RESPONSES)) {
35-
if (pathname.startsWith(prefix)) return response;
35+
if (pathname.startsWith(prefix)) {
36+
return response;
37+
}
3638
}
3739
return { mock: true };
3840
}

integration/run-sequential.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ console.info(`Mock API server at http://127.0.0.1:${String(mock.port)}`);
1515
// Run each test file in its own process
1616
// =============================================================================
1717
const glob = new Glob('integration/scenarios/s*.test.ts');
18-
const files = [...glob.scanSync('.')].toSorted();
18+
const files = [...glob.scanSync('.')].toSorted((a, b) => a.localeCompare(b));
1919

2020
// eslint-disable-next-line functional/no-let
2121
let failed = 0;

integration/scenarios/s31-sse-streaming.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ beforeAll(async () => {
1010
ctx = await createTestServer({
1111
apiOverrides: (apis) =>
1212
apis.map((api) => {
13-
if (api.name !== 'WeatherAPI') return api;
13+
if (api.name !== 'WeatherAPI') {
14+
return api;
15+
}
1416
return {
1517
...api,
1618
endpoints: api.endpoints.map((ep) => (ep.name === 'currentTemp' ? { ...ep, mode: 'stream' as const } : ep)),

0 commit comments

Comments
 (0)