fix: lint: Ban waitForTimeout, setTimeout-as-delay, and fixed sleep patterns (#442)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c9e84b2c34
commit
748557bc83
24 changed files with 139 additions and 3 deletions
4
.lintstagedrc.json
Normal file
4
.lintstagedrc.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"tests/**/*.ts": ["eslint"],
|
||||
"scripts/**/*.ts": ["eslint"]
|
||||
}
|
||||
58
eslint.config.js
Normal file
58
eslint.config.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import tseslint from '@typescript-eslint/eslint-plugin';
|
||||
import tsparser from '@typescript-eslint/parser';
|
||||
|
||||
export default [
|
||||
{
|
||||
name: 'tests/files-to-lint',
|
||||
files: ['tests/**/*.ts', 'scripts/harb-evaluator/**/*.ts'],
|
||||
languageOptions: {
|
||||
parser: tsparser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: 'module',
|
||||
},
|
||||
globals: {
|
||||
process: 'readonly',
|
||||
console: 'readonly',
|
||||
fetch: 'readonly',
|
||||
setTimeout: 'readonly',
|
||||
Date: 'readonly',
|
||||
Promise: 'readonly',
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
'@typescript-eslint': tseslint,
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'arch/no-fixed-delays',
|
||||
files: ['tests/**/*.ts', 'scripts/harb-evaluator/**/*.ts'],
|
||||
rules: {
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector: "CallExpression[callee.property.name='waitForTimeout']",
|
||||
message:
|
||||
'[BANNED] waitForTimeout is a fixed delay. → Subscribe to events instead (eth_newFilter for on-chain, waitForSelector/waitForURL for DOM). → Polling with timeout is acceptable only if no event source exists. → See AGENTS.md #Engineering Principles.',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"NewExpression[callee.name='Promise'] > ArrowFunctionExpression CallExpression[callee.name='setTimeout']",
|
||||
message:
|
||||
'[BANNED] Promise+setTimeout sleep pattern. → Use event subscription or polling with timeout instead. → See AGENTS.md #Engineering Principles.',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
@ -60,6 +60,17 @@ export default [
|
|||
message:
|
||||
'String interpolation used in a GraphQL query or mutation string. GraphQL queries must not use string interpolation — it bypasses type-checking and is unsafe. Use the `variables` parameter in the fetch body instead. Example: `variables: { holder: address }`. See PR #191 for the pattern.',
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.property.name='waitForTimeout']",
|
||||
message:
|
||||
'[BANNED] waitForTimeout is a fixed delay. → Subscribe to events instead (eth_newFilter for on-chain, waitForSelector/waitForURL for DOM). → Polling with timeout is acceptable only if no event source exists. → See AGENTS.md #Engineering Principles.',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"NewExpression[callee.name='Promise'] > ArrowFunctionExpression CallExpression[callee.name='setTimeout']",
|
||||
message:
|
||||
'[BANNED] Promise+setTimeout sleep pattern. → Use event subscription or polling with timeout instead. → See AGENTS.md #Engineering Principles.',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
|||
11
package.json
11
package.json
|
|
@ -2,13 +2,18 @@
|
|||
"name": "harb",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.55.1",
|
||||
"playwright-mcp": "^0.0.12",
|
||||
"@typescript-eslint/eslint-plugin": "^8.45.0",
|
||||
"@typescript-eslint/parser": "^8.45.0",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"ethers": "^6.11.1",
|
||||
"husky": "^9.0.11"
|
||||
"husky": "^9.0.11",
|
||||
"playwright-mcp": "^0.0.12"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "husky",
|
||||
"test:e2e": "playwright test"
|
||||
"test:e2e": "playwright test",
|
||||
"lint:tests": "eslint tests/ scripts/harb-evaluator/"
|
||||
},
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ async function waitForReceipt(rpcUrl: string, txHash: string, maxAttempts = 20):
|
|||
}
|
||||
return; // status === '0x1' — success
|
||||
}
|
||||
// eslint-disable-next-line no-restricted-syntax -- Polling with timeout: no event source for transaction receipt over HTTP RPC (eth_subscribe not available). See AGENTS.md #Engineering Principles.
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
}
|
||||
throw new Error(`Transaction ${txHash} not mined after ${maxAttempts * 500}ms`);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-restricted-syntax -- waitForTimeout: no event source exists for Vue component animation settling and wagmi wallet connector state transitions. See AGENTS.md #Engineering Principles. */
|
||||
/**
|
||||
* Shared wallet helpers for holdout scenarios.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -82,6 +82,17 @@ export default [
|
|||
message:
|
||||
"Ponder findMany() called without a limit parameter. Unbounded queries will grow without limit as the chain is indexed — never use findMany() without a limit. Always specify `limit` in the options object. Use the ring buffer pattern from docs/ARCHITECTURE.md.",
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.property.name='waitForTimeout']",
|
||||
message:
|
||||
'[BANNED] waitForTimeout is a fixed delay. → Subscribe to events instead (eth_newFilter for on-chain, waitForSelector/waitForURL for DOM). → Polling with timeout is acceptable only if no event source exists. → See AGENTS.md #Engineering Principles.',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"NewExpression[callee.name='Promise'] > ArrowFunctionExpression CallExpression[callee.name='setTimeout']",
|
||||
message:
|
||||
'[BANNED] Promise+setTimeout sleep pattern. → Use event subscription or polling with timeout instead. → See AGENTS.md #Engineering Principles.',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -62,5 +62,24 @@ export default [
|
|||
'max-statements': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'arch/no-fixed-delays',
|
||||
rules: {
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector: "CallExpression[callee.property.name='waitForTimeout']",
|
||||
message:
|
||||
'[BANNED] waitForTimeout is a fixed delay. → Subscribe to events instead (eth_newFilter for on-chain, waitForSelector/waitForURL for DOM). → Polling with timeout is acceptable only if no event source exists. → See AGENTS.md #Engineering Principles.',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"NewExpression[callee.name='Promise'] > ArrowFunctionExpression CallExpression[callee.name='setTimeout']",
|
||||
message:
|
||||
'[BANNED] Promise+setTimeout sleep pattern. → Use event subscription or polling with timeout instead. → See AGENTS.md #Engineering Principles.',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
eslintConfigPrettier,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-restricted-syntax -- waitForTimeout: no event source exists for Ponder indexing lag and UI re-renders after on-chain transactions in E2E tests. See AGENTS.md #Engineering Principles. */
|
||||
import { expect, test, type APIRequestContext } from '@playwright/test';
|
||||
import { Wallet } from 'ethers';
|
||||
import { createWalletContext } from '../setup/wallet-provider';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-restricted-syntax -- waitForTimeout: no event source exists for Ponder indexing lag and UI re-renders after on-chain transactions in E2E tests. See AGENTS.md #Engineering Principles. */
|
||||
import { expect, test, type APIRequestContext } from '@playwright/test';
|
||||
import { Wallet } from 'ethers';
|
||||
import { createWalletContext } from '../setup/wallet-provider';
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ test.describe('GraphQL URL Verification', () => {
|
|||
|
||||
// Give more time for Vue components to mount and composables to initialize
|
||||
console.log('[TEST] Waiting for composables to initialize...');
|
||||
// eslint-disable-next-line no-restricted-syntax -- waitForTimeout: no event source for Ponder/chain indexing delay in CI cold-start. See AGENTS.md #Engineering Principles.
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
// Log findings
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-restricted-syntax -- waitForTimeout/Promise+setTimeout: no event source exists for Ponder indexing lag and UI re-renders; Promise+setTimeout is used for polling Ponder over HTTP where no push subscription is available. See AGENTS.md #Engineering Principles. */
|
||||
import { test, expect, type APIRequestContext } from '@playwright/test';
|
||||
import { Wallet } from 'ethers';
|
||||
import { createWalletContext } from '../setup/wallet-provider';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-restricted-syntax -- waitForTimeout: no event source exists for animation settling, wallet connector UI transitions, and debounced state updates in user-simulation tests. See AGENTS.md #Engineering Principles. */
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { Wallet } from 'ethers';
|
||||
import { createWalletContext } from '../../setup/wallet-provider';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-restricted-syntax -- waitForTimeout: no event source exists for animation settling, wallet connector UI transitions, and debounced state updates in user-simulation tests. See AGENTS.md #Engineering Principles. */
|
||||
import { test } from '@playwright/test';
|
||||
import { Wallet } from 'ethers';
|
||||
import { createWalletContext } from '../../setup/wallet-provider';
|
||||
|
|
|
|||
|
|
@ -246,6 +246,7 @@ async function waitForReceipt(rpcUrl: string, txHash: string, timeoutMs = 15000)
|
|||
});
|
||||
const data = await resp.json();
|
||||
if (data.result) return data.result;
|
||||
// eslint-disable-next-line no-restricted-syntax -- Polling with timeout: no event source for transaction receipt over HTTP RPC (eth_subscribe not available). See AGENTS.md #Engineering Principles.
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
}
|
||||
throw new Error(`Transaction ${txHash} not mined within ${timeoutMs}ms`);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-restricted-syntax -- waitForTimeout: no event source exists for animation settling, wallet connector UI transitions, and debounced state updates in user-simulation tests. See AGENTS.md #Engineering Principles. */
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { Wallet } from 'ethers';
|
||||
import { createWalletContext } from '../../setup/wallet-provider';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-restricted-syntax -- waitForTimeout: no event source exists for animation settling, wallet connector UI transitions, and debounced state updates in user-simulation tests. See AGENTS.md #Engineering Principles. */
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { Wallet } from 'ethers';
|
||||
import { createWalletContext } from '../../setup/wallet-provider';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-restricted-syntax -- waitForTimeout: no event source exists for animation settling, wallet connector UI transitions, and debounced state updates in user-simulation tests. See AGENTS.md #Engineering Principles. */
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { Wallet } from 'ethers';
|
||||
import { createWalletContext } from '../../setup/wallet-provider';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-restricted-syntax -- waitForTimeout: no event source exists for animation settling, wallet connector UI transitions, and debounced state updates in user-simulation tests. See AGENTS.md #Engineering Principles. */
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { Wallet } from 'ethers';
|
||||
import { createWalletContext } from '../../setup/wallet-provider';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-restricted-syntax -- waitForTimeout: no event source exists for animation settling, wallet connector UI transitions, and debounced state updates in user-simulation tests. See AGENTS.md #Engineering Principles. */
|
||||
/**
|
||||
* Test B: Comprehensive Staker Journey (v2)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-restricted-syntax -- waitForTimeout: no event source exists for animation settling, wallet connector UI transitions, and debounced state updates in user-simulation tests. See AGENTS.md #Engineering Principles. */
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { Wallet } from 'ethers';
|
||||
import { createWalletContext } from '../../setup/wallet-provider';
|
||||
|
|
|
|||
|
|
@ -277,6 +277,7 @@ for (const persona of personas) {
|
|||
// Navigate to variant
|
||||
await page.goto(variant.url);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
// eslint-disable-next-line no-restricted-syntax -- waitForTimeout: no event source for CSS animation completion. See AGENTS.md #Engineering Principles.
|
||||
await page.waitForTimeout(1000); // Let animations settle
|
||||
|
||||
// Take screenshot
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-restricted-syntax -- waitForTimeout: no event source exists for animation settling, wallet connector UI transitions, and debounced state updates in user-simulation tests. See AGENTS.md #Engineering Principles. */
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { Wallet } from 'ethers';
|
||||
import { createWalletContext } from '../../setup/wallet-provider';
|
||||
|
|
|
|||
|
|
@ -92,6 +92,17 @@ export default [
|
|||
message:
|
||||
'String interpolation used in a GraphQL query or mutation string. GraphQL queries must not use string interpolation — it bypasses type-checking and is unsafe. Use the `variables` parameter in the fetch body instead. Example: `variables: { holder: address }`. See PR #191 for the pattern.',
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.property.name='waitForTimeout']",
|
||||
message:
|
||||
'[BANNED] waitForTimeout is a fixed delay. → Subscribe to events instead (eth_newFilter for on-chain, waitForSelector/waitForURL for DOM). → Polling with timeout is acceptable only if no event source exists. → See AGENTS.md #Engineering Principles.',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"NewExpression[callee.name='Promise'] > ArrowFunctionExpression CallExpression[callee.name='setTimeout']",
|
||||
message:
|
||||
'[BANNED] Promise+setTimeout sleep pattern. → Use event subscription or polling with timeout instead. → See AGENTS.md #Engineering Principles.',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue