From 2df1167601c5dc5cf08b284b88ff7cb69611a1cf Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 6 Apr 2026 00:32:53 +0300 Subject: [PATCH 01/14] Add `oxlint` to `package.json` --- package-lock.json | 369 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 370 insertions(+) diff --git a/package-lock.json b/package-lock.json index f8f7e595..24b6524a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@stacks/clarinet-sdk-wasm": "^3.15.0", "@types/jest": "^30.0.0", "jest": "^30.2.0", + "oxlint": "^1.58.0", "ts-jest": "^29.4.6", "typescript": "^5.9.3" } @@ -1026,6 +1027,329 @@ ], "license": "MIT" }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.58.0.tgz", + "integrity": "sha512-1T7UN3SsWWxpWyWGn1cT3ASNJOo+pI3eUkmEl7HgtowapcV8kslYpFQcYn431VuxghXakPNlbjRwhqmR37PFOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.58.0.tgz", + "integrity": "sha512-GryzujxuiRv2YFF7bRy8mKcxlbuAN+euVUtGJt9KKbLT8JBUIosamVhcthLh+VEr6KE6cjeVMAQxKAzJcoN7dg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.58.0.tgz", + "integrity": "sha512-7/bRSJIwl4GxeZL9rPZ11anNTyUO9epZrfEJH/ZMla3+/gbQ6xZixh9nOhsZ0QwsTW7/5J2A/fHbD1udC5DQQA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.58.0.tgz", + "integrity": "sha512-EqdtJSiHweS2vfILNrpyJ6HUwpEq2g7+4Zx1FPi4hu3Hu7tC3znF6ufbXO8Ub2LD4mGgznjI7kSdku9NDD1Mkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.58.0.tgz", + "integrity": "sha512-VQt5TH4M42mY20F545G637RKxV/yjwVtKk2vfXuazfReSIiuvWBnv+FVSvIV5fKVTJNjt3GSJibh6JecbhGdBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.58.0.tgz", + "integrity": "sha512-fBYcj4ucwpAtjJT3oeBdFBYKvNyjRSK+cyuvBOTQjh0jvKp4yeA4S/D0IsCHus/VPaNG5L48qQkh+Vjy3HL2/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.58.0.tgz", + "integrity": "sha512-0BeuFfwlUHlJ1xpEdSD1YO3vByEFGPg36uLjK1JgFaxFb4W6w17F8ET8sz5cheZ4+x5f2xzdnRrrWv83E3Yd8g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.58.0.tgz", + "integrity": "sha512-TXlZgnPTlxrQzxG9ZXU7BNwx1Ilrr17P3GwZY0If2EzrinqRH3zXPc3HrRcBJgcsoZNMuNL5YivtkJYgp467UQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.58.0.tgz", + "integrity": "sha512-zSoYRo5dxHLcUx93Stl2hW3hSNjPt99O70eRVWt5A1zwJ+FPjeCCANCD2a9R4JbHsdcl11TIQOjyigcRVOH2mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.58.0.tgz", + "integrity": "sha512-NQ0U/lqxH2/VxBYeAIvMNUK1y0a1bJ3ZicqkF2c6wfakbEciP9jvIE4yNzCFpZaqeIeRYaV7AVGqEO1yrfVPjA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.58.0.tgz", + "integrity": "sha512-X9J+kr3gIC9FT8GuZt0ekzpNUtkBVzMVU4KiKDSlocyQuEgi3gBbXYN8UkQiV77FTusLDPsovjo95YedHr+3yg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.58.0.tgz", + "integrity": "sha512-CDze3pi1OO3Wvb/QsXjmLEY4XPKGM6kIo82ssNOgmcl1IdndF9VSGAE38YLhADWmOac7fjqhBw82LozuUVxD0Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.58.0.tgz", + "integrity": "sha512-b/89glbxFaEAcA6Uf1FvCNecBJEgcUTsV1quzrqXM/o4R1M4u+2KCVuyGCayN2UpsRWtGGLb+Ver0tBBpxaPog==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.58.0.tgz", + "integrity": "sha512-0/yYpkq9VJFCEcuRlrViGj8pJUFFvNS4EkEREaN7CB1EcLXJIaVSSa5eCihwBGXtOZxhnblWgxks9juRdNQI7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.58.0.tgz", + "integrity": "sha512-hr6FNvmcAXiH+JxSvaJ4SJ1HofkdqEElXICW9sm3/Rd5eC3t7kzvmLyRAB3NngKO2wzXRCAm4Z/mGWfrsS4X8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.58.0.tgz", + "integrity": "sha512-R+O368VXgRql1K6Xar+FEo7NEwfo13EibPMoTv3sesYQedRXd6m30Dh/7lZMxnrQVFfeo4EOfYIP4FpcgWQNHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.58.0.tgz", + "integrity": "sha512-Q0FZiAY/3c4YRj4z3h9K1PgaByrifrfbBoODSeX7gy97UtB7pySPUQfC2B/GbxWU6k7CzQrRy5gME10PltLAFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.58.0.tgz", + "integrity": "sha512-Y8FKBABrSPp9H0QkRLHDHOSUgM/309a3IvOVgPcVxYcX70wxJrk608CuTg7w+C6vEd724X5wJoNkBcGYfH7nNQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.58.0.tgz", + "integrity": "sha512-bCn5rbiz5My+Bj7M09sDcnqW0QJyINRVxdZ65x1/Y2tGrMwherwK/lpk+HRQCKvXa8pcaQdF5KY5j54VGZLwNg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3725,6 +4049,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/oxlint": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.58.0.tgz", + "integrity": "sha512-t4s9leczDMqlvOSjnbCQe7gtoLkWgBGZ7sBdCJ9EOj5IXFSG/X7OAzK4yuH4iW+4cAYe8kLFbC8tuYMwWZm+Cg==", + "dev": true, + "license": "MIT", + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.58.0", + "@oxlint/binding-android-arm64": "1.58.0", + "@oxlint/binding-darwin-arm64": "1.58.0", + "@oxlint/binding-darwin-x64": "1.58.0", + "@oxlint/binding-freebsd-x64": "1.58.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.58.0", + "@oxlint/binding-linux-arm-musleabihf": "1.58.0", + "@oxlint/binding-linux-arm64-gnu": "1.58.0", + "@oxlint/binding-linux-arm64-musl": "1.58.0", + "@oxlint/binding-linux-ppc64-gnu": "1.58.0", + "@oxlint/binding-linux-riscv64-gnu": "1.58.0", + "@oxlint/binding-linux-riscv64-musl": "1.58.0", + "@oxlint/binding-linux-s390x-gnu": "1.58.0", + "@oxlint/binding-linux-x64-gnu": "1.58.0", + "@oxlint/binding-linux-x64-musl": "1.58.0", + "@oxlint/binding-openharmony-arm64": "1.58.0", + "@oxlint/binding-win32-arm64-msvc": "1.58.0", + "@oxlint/binding-win32-ia32-msvc": "1.58.0", + "@oxlint/binding-win32-x64-msvc": "1.58.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=0.18.0" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + } + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", diff --git a/package.json b/package.json index fc9263a0..8b0683c9 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@stacks/clarinet-sdk-wasm": "^3.15.0", "@types/jest": "^30.0.0", "jest": "^30.2.0", + "oxlint": "^1.58.0", "ts-jest": "^29.4.6", "typescript": "^5.9.3" } From 53e42c965bca08e543ab950e9521f01e1d96c2c6 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 6 Apr 2026 00:33:58 +0300 Subject: [PATCH 02/14] Add basic `oxlintrc.json`, scripts and fix --- oxlintrc.json | 10 ++++++++++ package.json | 2 ++ persistence.ts | 2 +- shared.tests.ts | 1 - 4 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 oxlintrc.json diff --git a/oxlintrc.json b/oxlintrc.json new file mode 100644 index 00000000..8674561a --- /dev/null +++ b/oxlintrc.json @@ -0,0 +1,10 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "rules": {}, + "categories": { + "correctness": "error", + "suspicious": "warn" + }, + "ignorePatterns": ["dist", "example", "node_modules"], + "plugins": ["typescript"] +} diff --git a/package.json b/package.json index 8b0683c9..a01eecdd 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,8 @@ }, "scripts": { "build": "npx -p typescript tsc --project tsconfig.json && node -e \"if (process.platform !== 'win32') require('fs').chmodSync('./dist/app.js', 0o755);\"", + "lint": "oxlint -c oxlintrc.json", + "lint:fix": "oxlint -c oxlintrc.json --fix", "test": "npx jest", "test:coverage": "npx jest --coverage" }, diff --git a/persistence.ts b/persistence.ts index 9a6c3546..d6fd6ce5 100644 --- a/persistence.ts +++ b/persistence.ts @@ -68,7 +68,7 @@ const loadFailureStore = ( try { const content = readFileSync(filePath, "utf-8"); return JSON.parse(content); - } catch (error: any) { + } catch { return { invariant: [], test: [] }; } }; diff --git a/shared.tests.ts b/shared.tests.ts index 70798aaf..09f03d15 100644 --- a/shared.tests.ts +++ b/shared.tests.ts @@ -3,7 +3,6 @@ import { getFunctionsFromContractInterfaces, getFunctionsListForContract, getSimnetDeployerContractsInterfaces, - hexaString, getContractNameFromContractId, } from "./shared"; import { rmSync } from "fs"; From b66ae658c3cee92553ff8a860b1aa15924ae85fa Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 6 Apr 2026 00:36:16 +0300 Subject: [PATCH 03/14] Lint in CI --- .github/workflows/ci.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d659f654..9fa14cdc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,24 @@ on: - master jobs: + lint: + runs-on: ubuntu-latest + timeout-minutes: 2 + steps: + - name: Checkout code + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + + - name: Use Node.js 24 + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + with: + node-version: 24 + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + tests: runs-on: ${{ matrix.os }} timeout-minutes: 10 From 29779aafbc0c12992110abf7d8b152d560243f23 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 6 Apr 2026 01:14:54 +0300 Subject: [PATCH 04/14] Fix shadowed declarations --- heatstroke.ts | 8 ++++---- traits.ts | 48 ++++++++++++++++++++++++------------------------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/heatstroke.ts b/heatstroke.ts index 0def358c..a582b0a8 100644 --- a/heatstroke.ts +++ b/heatstroke.ts @@ -322,7 +322,7 @@ function logAsTree( node: Record, indent: string = baseIndent, isLastParent: boolean = true, - radio: EventEmitter + radioEmitter: EventEmitter ): void => { const keys = Object.keys(node); @@ -333,14 +333,14 @@ function logAsTree( const leadingChar = isLastSection ? " " : "│"; if (typeof node[key] === "object" && node[key] !== null) { - radio.emit( + radioEmitter.emit( "logMessage", `${leadingChar} ${indent}${connector} ${ARROW} ${key}` ); - printTree(node[key], nextIndent, isLast, radio); + printTree(node[key], nextIndent, isLast, radioEmitter); } else { const count = node[key] as number; - radio.emit( + radioEmitter.emit( "logMessage", `${leadingChar} ${indent}${connector} ${key}: x${count}` ); diff --git a/traits.ts b/traits.ts index f51c98f0..b45300f1 100644 --- a/traits.ts +++ b/traits.ts @@ -39,7 +39,7 @@ export const enrichInterfaceWithTraitData = ( const enrichArgs = ( args: any[], functionName: string, - traitReferenceMap: any, + traitRefMapNode: any, path: string[] = [] ): any[] => { return args.map((arg) => { @@ -49,17 +49,17 @@ export const enrichInterfaceWithTraitData = ( // looking for. It means that the current parameter does not have an // associated trait reference. if ( - !traitReferenceMap || - (!traitReferenceMap[arg.name] && - !traitReferenceMap.tuple && - !traitReferenceMap.list && - !traitReferenceMap.response && - !traitReferenceMap.optional && - !traitReferenceMap[arg.name]?.tuple && - !traitReferenceMap[arg.name]?.list && - !traitReferenceMap[arg.name]?.response && - !traitReferenceMap[arg.name]?.optional && - traitReferenceMap !== "trait_reference") + !traitRefMapNode || + (!traitRefMapNode[arg.name] && + !traitRefMapNode.tuple && + !traitRefMapNode.list && + !traitRefMapNode.response && + !traitRefMapNode.optional && + !traitRefMapNode[arg.name]?.tuple && + !traitRefMapNode[arg.name]?.list && + !traitRefMapNode[arg.name]?.response && + !traitRefMapNode[arg.name]?.optional && + traitRefMapNode !== "trait_reference") ) { return arg; } @@ -71,8 +71,8 @@ export const enrichInterfaceWithTraitData = ( arg.type.tuple, functionName, listNested - ? traitReferenceMap.tuple - : traitReferenceMap[arg.name]?.tuple, + ? traitRefMapNode.tuple + : traitRefMapNode[arg.name]?.tuple, currentPath ), }, @@ -85,8 +85,8 @@ export const enrichInterfaceWithTraitData = ( [arg.type.list], functionName, listNested - ? traitReferenceMap.list - : traitReferenceMap[arg.name]?.list, + ? traitRefMapNode.list + : traitRefMapNode[arg.name]?.list, arg.type.list.type.tuple ? [...currentPath, "tuple"] : arg.type.list.type.response @@ -106,8 +106,8 @@ export const enrichInterfaceWithTraitData = ( functionName, { ok: listNested - ? traitReferenceMap.response?.ok - : traitReferenceMap[arg.name]?.response?.ok, + ? traitRefMapNode.response?.ok + : traitRefMapNode[arg.name]?.response?.ok, }, okPath )[0]; @@ -116,8 +116,8 @@ export const enrichInterfaceWithTraitData = ( functionName, { error: listNested - ? traitReferenceMap.response?.error - : traitReferenceMap[arg.name]?.response?.error, + ? traitRefMapNode.response?.error + : traitRefMapNode[arg.name]?.response?.error, }, errorPath )[0]; @@ -137,8 +137,8 @@ export const enrichInterfaceWithTraitData = ( functionName, { optional: listNested - ? traitReferenceMap.optional - : traitReferenceMap[arg.name]?.optional, + ? traitRefMapNode.optional + : traitRefMapNode[arg.name]?.optional, }, optionalPath )[0]; @@ -148,7 +148,7 @@ export const enrichInterfaceWithTraitData = ( optional: optionalTraitReference.type, }, }; - } else if (traitReferenceMap && traitReferenceMap[arg.name]) { + } else if (traitRefMapNode && traitRefMapNode[arg.name]) { const [traitReferenceName, traitReferenceImport] = getTraitReferenceData( ast, @@ -166,7 +166,7 @@ export const enrichInterfaceWithTraitData = ( }, }; } - } else if (traitReferenceMap === "trait_reference") { + } else if (traitRefMapNode === "trait_reference") { const [traitReferenceName, traitReferenceImport] = getTraitReferenceData(ast, functionName, path); if (traitReferenceName && traitReferenceImport) { From 024d297e855685a72efbc09a8fdbb2a7f9b804c7 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 6 Apr 2026 01:30:51 +0300 Subject: [PATCH 05/14] Upgrade suspicious linter category to error, add perf and style --- dialer.tests.ts | 2 +- dialer.ts | 2 +- dialer.types.ts | 6 +++--- heatstroke.tests.ts | 2 +- heatstroke.ts | 12 ++++++------ heatstroke.types.ts | 2 +- invariant.tests.ts | 6 ++---- invariant.ts | 14 +++++++------- invariant.types.ts | 4 +--- oxlintrc.json | 25 +++++++++++++++++++++++-- persistence.tests.ts | 6 +++--- persistence.ts | 6 ++---- property.tests.ts | 2 +- property.ts | 16 ++++++++-------- shared.ts | 44 ++++++++++++++++++-------------------------- shared.types.ts | 12 +++++------- traits.tests.ts | 6 +++--- traits.ts | 42 +++++++++++++++++++----------------------- traits.types.ts | 2 +- 19 files changed, 106 insertions(+), 105 deletions(-) diff --git a/dialer.tests.ts b/dialer.tests.ts index c7eb2ff3..a1bd2922 100644 --- a/dialer.tests.ts +++ b/dialer.tests.ts @@ -1,5 +1,5 @@ import { join } from "path"; -import { DialerContext } from "./dialer.types"; +import type { DialerContext } from "./dialer.types"; import { DialerRegistry } from "./dialer"; const dialPath = join("example", "dialer.ts"); diff --git a/dialer.ts b/dialer.ts index 9acd6c07..dea5a465 100644 --- a/dialer.ts +++ b/dialer.ts @@ -1,6 +1,6 @@ import { existsSync } from "fs"; import { resolve } from "path"; -import { Dialer, DialerContext } from "./dialer.types"; +import type { Dialer, DialerContext } from "./dialer.types"; // In telephony, a registry is used for maintaining a known set of handlers, // devices, or processes. This aligns with this class's purpose. Dialers are diff --git a/dialer.types.ts b/dialer.types.ts index 1d3f662b..3bf77b8b 100644 --- a/dialer.types.ts +++ b/dialer.types.ts @@ -1,6 +1,6 @@ -import { ParsedTransactionResult } from "@stacks/clarinet-sdk"; -import { ClarityValue } from "@stacks/transactions"; -import { EnrichedContractInterfaceFunction } from "./shared.types"; +import type { ParsedTransactionResult } from "@stacks/clarinet-sdk"; +import type { ClarityValue } from "@stacks/transactions"; +import type { EnrichedContractInterfaceFunction } from "./shared.types"; export type Dialer = (context: DialerContext) => Promise | void; diff --git a/heatstroke.tests.ts b/heatstroke.tests.ts index 80cc8f6b..66113ac3 100644 --- a/heatstroke.tests.ts +++ b/heatstroke.tests.ts @@ -2,7 +2,7 @@ import { EventEmitter } from "events"; import { rmSync } from "fs"; import { join, resolve } from "path"; import { initSimnet } from "@stacks/clarinet-sdk"; -import { ContractInterfaceFunction } from "@stacks/clarinet-sdk-wasm"; +import type { ContractInterfaceFunction } from "@stacks/clarinet-sdk-wasm"; import fc from "fast-check"; import { reporter } from "./heatstroke"; import { getContractNameFromContractId } from "./shared"; diff --git a/heatstroke.ts b/heatstroke.ts index a582b0a8..d30a2ea5 100644 --- a/heatstroke.ts +++ b/heatstroke.ts @@ -1,7 +1,7 @@ -import { EventEmitter } from "events"; -import { ContractInterfaceFunction } from "@stacks/clarinet-sdk-wasm"; +import type { EventEmitter } from "events"; +import type { ContractInterfaceFunction } from "@stacks/clarinet-sdk-wasm"; import { green } from "ansicolor"; -import { +import type { InvariantCounterExample, RunDetails, Statistics, @@ -9,8 +9,8 @@ import { TestCounterExample, } from "./heatstroke.types"; import { getContractNameFromContractId } from "./shared"; -import { PropertyTestError } from "./property"; -import { FalsifiedInvariantError } from "./invariant"; +import type { PropertyTestError } from "./property"; +import type { FalsifiedInvariantError } from "./invariant"; /** * Heatstrokes Reporter @@ -321,7 +321,7 @@ function logAsTree( const printTree = ( node: Record, indent: string = baseIndent, - isLastParent: boolean = true, + isLastParent = true, radioEmitter: EventEmitter ): void => { const keys = Object.keys(node); diff --git a/heatstroke.types.ts b/heatstroke.types.ts index 1dc9cb03..5e5776f6 100644 --- a/heatstroke.types.ts +++ b/heatstroke.types.ts @@ -1,4 +1,4 @@ -import { ContractInterfaceFunction } from "@stacks/clarinet-sdk-wasm"; +import type { ContractInterfaceFunction } from "@stacks/clarinet-sdk-wasm"; export type RunDetails = { failed: boolean; diff --git a/invariant.tests.ts b/invariant.tests.ts index b2efe300..350f1821 100644 --- a/invariant.tests.ts +++ b/invariant.tests.ts @@ -94,13 +94,11 @@ describe("Simnet contracts operations", () => { // The JS representation of Clarity `(some (tuple (called uint)))`, where // `called` is initialized to 0. const expectedClarityValue = Cl.some(Cl.tuple({ called: Cl.uint(0) })); - const expectedContext = functions.map((f) => { - return { + const expectedContext = functions.map((f) => ({ contractId, functionName: f.name, called: expectedClarityValue, - }; - }); + })); expect(actualContext).toEqual(expectedContext); diff --git a/invariant.ts b/invariant.ts index 996f580c..337dc566 100644 --- a/invariant.ts +++ b/invariant.ts @@ -1,5 +1,5 @@ -import { Simnet } from "@stacks/clarinet-sdk"; -import { EventEmitter } from "events"; +import type { Simnet } from "@stacks/clarinet-sdk"; +import type { EventEmitter } from "events"; import { argsToCV, functionToArbitrary, @@ -7,12 +7,12 @@ import { getFunctionsListForContract, LOG_DIVIDER, } from "./shared"; -import { LocalContext } from "./invariant.types"; +import type { LocalContext } from "./invariant.types"; import { Cl, cvToJSON, cvToString } from "@stacks/transactions"; import { reporter } from "./heatstroke"; import fc from "fast-check"; import { dim, green, red, underline, yellow } from "ansicolor"; -import { ContractInterfaceFunction } from "@stacks/clarinet-sdk-wasm"; +import type { ContractInterfaceFunction } from "@stacks/clarinet-sdk-wasm"; import { buildTraitReferenceMap, enrichInterfaceWithTraitData, @@ -20,12 +20,12 @@ import { isTraitReferenceFunction, getNonTestableTraitFunctions, } from "./traits"; -import { EnrichedContractInterfaceFunction } from "./shared.types"; +import type { EnrichedContractInterfaceFunction } from "./shared.types"; import { DialerRegistry, PostDialerError, PreDialerError } from "./dialer"; -import { Statistics } from "./heatstroke.types"; +import type { Statistics } from "./heatstroke.types"; import { getFailureFilePath, loadFailures, persistFailure } from "./persistence"; import { resolve } from "path"; -import { ImplementedTraitType } from "./traits.types"; +import type { ImplementedTraitType } from "./traits.types"; /** * Runs invariant testing on the target contract and logs the progress. Reports diff --git a/invariant.types.ts b/invariant.types.ts index 62811fd9..5caffaa6 100644 --- a/invariant.types.ts +++ b/invariant.types.ts @@ -6,7 +6,5 @@ * - The value is the count of times the SUT function has been invoked. */ export type LocalContext = { - [contractId: string]: { - [functionName: string]: number; - }; + [contractId: string]: Record; }; diff --git a/oxlintrc.json b/oxlintrc.json index 8674561a..1e88ff12 100644 --- a/oxlintrc.json +++ b/oxlintrc.json @@ -1,9 +1,30 @@ { "$schema": "./node_modules/oxlint/configuration_schema.json", - "rules": {}, + "rules": { + "no-magic-numbers": "off", + "sort-keys": "off", + "sort-imports": "off", + "capitalized-comments": "off", + "id-length": "off", + "max-params": "off", + "max-lines-per-function": "off", + "max-statements": "off", + "consistent-type-definitions": "off", + "func-style": "off", + "prefer-destructuring": "off", + "no-ternary": "off", + "no-nested-ternary": "off", + "no-await-in-loop": "off", + "consistent-indexed-object-style": "off", + "default-param-last": "off", + "no-continue": "off", + "prefer-template": "off" + }, "categories": { "correctness": "error", - "suspicious": "warn" + "suspicious": "error", + "perf": "error", + "style": "warn" }, "ignorePatterns": ["dist", "example", "node_modules"], "plugins": ["typescript"] diff --git a/persistence.tests.ts b/persistence.tests.ts index 7ea8da78..d01a86b5 100644 --- a/persistence.tests.ts +++ b/persistence.tests.ts @@ -6,7 +6,7 @@ import { persistFailure, loadFailures, } from "./persistence"; -import { RunDetails } from "./heatstroke.types"; +import type { RunDetails } from "./heatstroke.types"; import fc from "fast-check"; const temporaryTestBaseDir = resolve(tmpdir(), "rendezvous-test-persistence"); @@ -24,8 +24,8 @@ const createTemporaryCustomTestBaseDir = (dirName: string) => { // Mock RunDetails helper const createMockRunDetails = ( seed: number, - failed: boolean = true, - numRuns: number = 100 + failed = true, + numRuns = 100 ): RunDetails => ({ failed, seed, diff --git a/persistence.ts b/persistence.ts index d6fd6ce5..ae005fb4 100644 --- a/persistence.ts +++ b/persistence.ts @@ -1,6 +1,6 @@ import { mkdirSync, readFileSync, writeFileSync } from "fs"; import { resolve } from "path"; -import { RunDetails } from "./heatstroke.types"; +import type { RunDetails } from "./heatstroke.types"; /** * Represents a persisted failure record for regression testing. @@ -49,9 +49,7 @@ const DEFAULT_CONFIG: Required = { export const getFailureFilePath = ( contractId: string, baseDir: string = DEFAULT_CONFIG.baseDir -): string => { - return resolve(baseDir, `${contractId}.json`); -}; +): string => resolve(baseDir, `${contractId}.json`); /** * Loads the failure store for a contract, or creates an empty one. diff --git a/property.tests.ts b/property.tests.ts index 8f767c33..34b263d0 100644 --- a/property.tests.ts +++ b/property.tests.ts @@ -8,7 +8,7 @@ import { rmSync } from "fs"; import { join, resolve } from "path"; import fc from "fast-check"; import { createIsolatedTestEnvironment } from "./test.utils"; -import { +import type { ContractInterfaceFunction, ContractInterfaceFunctionAccess, ContractInterfaceFunctionArg, diff --git a/property.ts b/property.ts index aefbe602..5c2b165f 100644 --- a/property.ts +++ b/property.ts @@ -1,9 +1,9 @@ -import { Simnet } from "@stacks/clarinet-sdk"; -import { EventEmitter } from "events"; +import type { Simnet } from "@stacks/clarinet-sdk"; +import type { EventEmitter } from "events"; import fc from "fast-check"; import { cvToJSON, cvToString } from "@stacks/transactions"; import { reporter } from "./heatstroke"; -import { Statistics } from "./heatstroke.types"; +import type { Statistics } from "./heatstroke.types"; import { argsToCV, functionToArbitrary, @@ -12,7 +12,7 @@ import { LOG_DIVIDER, } from "./shared"; import { dim, green, red, underline, yellow } from "ansicolor"; -import { ContractInterfaceFunction } from "@stacks/clarinet-sdk-wasm"; +import type { ContractInterfaceFunction } from "@stacks/clarinet-sdk-wasm"; import { buildTraitReferenceMap, enrichInterfaceWithTraitData, @@ -22,8 +22,8 @@ import { } from "./traits"; import { getFailureFilePath, loadFailures, persistFailure } from "./persistence"; import { resolve } from "path"; -import { ImplementedTraitType } from "./traits.types"; -import { EnrichedContractInterfaceFunction } from "./shared.types"; +import type { ImplementedTraitType } from "./traits.types"; +import type { EnrichedContractInterfaceFunction } from "./shared.types"; /** * Runs property-based tests on the target contract and logs the progress. @@ -633,7 +633,7 @@ const isTestDiscarded = ( simnet: Simnet, selectedCaller: string ) => { - if (!discardFunctionName) return false; + if (!discardFunctionName) {return false;} const { result: discardFunctionCallResult } = simnet.callReadOnlyFn( contractId, @@ -671,7 +671,7 @@ const validateDiscardFunction = ( .get(contractId) ?.find((f) => f.name === discardFunctionName); - if (!testFunction || !discardFunction) return false; + if (!testFunction || !discardFunction) {return false;} if (!isParamsMatch(testFunction, discardFunction)) { radio.emit( diff --git a/shared.ts b/shared.ts index 1021afc4..582ac995 100644 --- a/shared.ts +++ b/shared.ts @@ -1,5 +1,5 @@ import fc from "fast-check"; -import { +import type { BaseTypesToArbitrary, BaseTypesToCV, ComplexTypesToArbitrary, @@ -10,21 +10,21 @@ import { ResponseStatus, TupleData, } from "./shared.types"; -import { Simnet } from "@stacks/clarinet-sdk"; -import { +import type { Simnet } from "@stacks/clarinet-sdk"; +import type { ContractInterfaceFunction, IContractInterface, } from "@stacks/clarinet-sdk-wasm"; import { + type ClarityValue, Cl, - ClarityValue, optionalCVOf, principalCV, responseErrorCV, responseOkCV, } from "@stacks/transactions"; import { getContractIdsImplementingTrait } from "./traits"; -import { ImplementedTraitType, ImportedTraitType } from "./traits.types"; +import type { ImplementedTraitType, ImportedTraitType } from "./traits.types"; /** 79 characters long divider for logging. */ export const LOG_DIVIDER = @@ -104,11 +104,11 @@ const parameterTypeToArbitrary = ( // The type is a base type. if (type === "principal") { if (addresses.length === 0) - throw new Error( + {throw new Error( "No addresses could be retrieved from the simnet instance!" - ); + );} return baseTypesToArbitrary.principal(addresses); - } else return baseTypesToArbitrary[type]; + } else {return baseTypesToArbitrary[type];} } else { // The type is a complex type. if ("buffer" in type) { @@ -199,7 +199,7 @@ const complexTypesToArbitrary: ComplexTypesToArbitrary = { addresses: string[], projectTraitImplementations: Record ) => { - const tupleArbitraries: { [key: string]: fc.Arbitrary } = {}; + const tupleArbitraries: Record> = {}; items.forEach((item) => { tupleArbitraries[item.name] = parameterTypeToArbitrary( item.type, @@ -244,11 +244,9 @@ const complexTypesToArbitrary: ComplexTypesToArbitrary = { trait_reference: ( traitData: ImportedTraitType, projectTraitImplementations: Record - ) => { - return fc.constantFrom( + ) => fc.constantFrom( ...getContractIdsImplementingTrait(traitData, projectTraitImplementations) - ); - }, + ), }; /** @@ -330,7 +328,7 @@ const argToCV = ( ); return complexTypesToCV.list(listItems); } else if ("tuple" in type) { - const tupleData: { [key: string]: ClarityValue } = {}; + const tupleData: Record = {}; type.tuple.forEach((field) => { tupleData[field.name] = argToCV( generatedArgument[field.name], @@ -376,28 +374,22 @@ const complexTypesToCV: ComplexTypesToCV = { buffer: (arg: string) => Cl.bufferFromHex(arg), "string-ascii": (arg: string) => Cl.stringAscii(arg), "string-utf8": (arg: string) => Cl.stringUtf8(arg), - list: (items: ClarityValue[]) => { - return Cl.list(items); - }, - tuple: (tupleData: TupleData) => { - return Cl.tuple(tupleData); - }, + list: (items: ClarityValue[]) => Cl.list(items), + tuple: (tupleData: TupleData) => Cl.tuple(tupleData), optional: (arg: ClarityValue | null) => arg ? optionalCVOf(arg) : optionalCVOf(undefined), response: (status: ResponseStatus, value: ClarityValue) => { - if (status === "ok") return responseOkCV(value); - else if (status === "error") return responseErrorCV(value); - else throw new Error(`Unsupported response status: ${status}`); + if (status === "ok") {return responseOkCV(value);} + else if (status === "error") {return responseErrorCV(value);} + else {throw new Error(`Unsupported response status: ${status}`);} }, trait_reference: (traitImplementation: string) => principalCV(traitImplementation), }; -const isBaseType = (type: EnrichedParameterType): type is EnrichedBaseType => { - return ["int128", "uint128", "bool", "principal"].includes( +const isBaseType = (type: EnrichedParameterType): type is EnrichedBaseType => ["int128", "uint128", "bool", "principal"].includes( type as EnrichedBaseType ); -}; export const getContractNameFromContractId = (contractId: string): string => contractId.split(".")[1]; diff --git a/shared.types.ts b/shared.types.ts index a59edc57..9519232d 100644 --- a/shared.types.ts +++ b/shared.types.ts @@ -1,9 +1,9 @@ -import { +import type { ContractInterfaceFunctionAccess, ContractInterfaceFunctionArg, ContractInterfaceFunctionOutput, } from "@stacks/clarinet-sdk-wasm"; -import { +import type { boolCV, bufferCV, ClarityValue, @@ -18,8 +18,8 @@ import { tupleCV, uintCV, } from "@stacks/transactions"; -import fc from "fast-check"; -import { ImplementedTraitType, ImportedTraitType } from "./traits.types"; +import type fc from "fast-check"; +import type { ImplementedTraitType, ImportedTraitType } from "./traits.types"; // Types used for Clarity Value conversion. @@ -43,9 +43,7 @@ export type EnrichedContractInterfaceFunction = { export type ResponseStatus = "ok" | "error"; -export type TupleData = { - [key: string]: T; -}; +export type TupleData = Record; export type BaseTypesToCV = { int128: (arg: number) => ReturnType; diff --git a/traits.tests.ts b/traits.tests.ts index 2a5911e4..209ea142 100644 --- a/traits.tests.ts +++ b/traits.tests.ts @@ -1,4 +1,4 @@ -import { +import type { ContractInterfaceFunction, IContractAST, } from "@stacks/clarinet-sdk-wasm"; @@ -14,8 +14,8 @@ import { isTraitReferenceFunction, } from "./traits"; import { createIsolatedTestEnvironment } from "./test.utils"; -import { EnrichedContractInterfaceFunction } from "./shared.types"; -import { ImplementedTraitType } from "./traits.types"; +import type { EnrichedContractInterfaceFunction } from "./shared.types"; +import type { ImplementedTraitType } from "./traits.types"; const isolatedTestEnvPrefix = "rendezvous-test-traits-"; diff --git a/traits.ts b/traits.ts index b45300f1..c1a89158 100644 --- a/traits.ts +++ b/traits.ts @@ -1,16 +1,16 @@ -import { +import type { Atom, ContractInterfaceFunction, IContractAST, List, TraitReference, } from "@stacks/clarinet-sdk-wasm"; -import { +import type { EnrichedContractInterfaceFunction, ParameterType, } from "./shared.types"; -import { Simnet } from "@stacks/clarinet-sdk"; -import { +import type { Simnet } from "@stacks/clarinet-sdk"; +import type { DefinedTraitType, ImplementedTraitType, ImportedTraitType, @@ -41,8 +41,7 @@ export const enrichInterfaceWithTraitData = ( functionName: string, traitRefMapNode: any, path: string[] = [] - ): any[] => { - return args.map((arg) => { + ): any[] => args.map((arg) => { const listNested = !arg.name; const currentPath = listNested ? path : [...path, arg.name]; // Exit early if the traitReferenceMap does not have anything we are @@ -184,14 +183,11 @@ export const enrichInterfaceWithTraitData = ( return arg; }); - }; - const enrichedFunctions = functionInterfaceList.map((f) => { - return { + const enrichedFunctions = functionInterfaceList.map((f) => ({ ...f, args: enrichArgs(f.args, f.name, traitReferenceMap.get(f.name)), - }; - }); + })); enriched.set(targetContractId, enrichedFunctions); return enriched; @@ -296,7 +292,7 @@ const getTraitReferenceData = ( path.slice(1) ); - if (result[0] !== undefined) return result; + if (result[0] !== undefined) {return result;} } } } @@ -365,7 +361,7 @@ const getTraitReferenceData = ( ); if (traitReferenceImportData[0] !== undefined) - return traitReferenceImportData; + {return traitReferenceImportData;} } return [undefined, undefined]; }; @@ -498,22 +494,22 @@ export const isTraitReferenceFunction = ( return type === "trait_reference"; } else { // The type is a complex type. - if ("buffer" in type) return false; - if ("string-ascii" in type) return false; - if ("string-utf8" in type) return false; + if ("buffer" in type) {return false;} + if ("string-ascii" in type) {return false;} + if ("string-utf8" in type) {return false;} if ("list" in type) - return hasTraitReference(type.list.type as ParameterType); + {return hasTraitReference(type.list.type as ParameterType);} if ("tuple" in type) - return type.tuple.some((item) => + {return type.tuple.some((item) => hasTraitReference(item.type as ParameterType) - ); + );} if ("optional" in type) - return hasTraitReference(type.optional as ParameterType); + {return hasTraitReference(type.optional as ParameterType);} if ("response" in type) - return ( + {return ( hasTraitReference(type.response.ok as ParameterType) || hasTraitReference(type.response.error as ParameterType) - ); + );} // Default to false for unexpected types. return false; } @@ -573,7 +569,7 @@ export const getNonTestableTraitFunctions = ( contractId: string ): string[] => { const hasTraitReferenceWithoutImplementation = (type: any): boolean => { - if (!type) return false; + if (!type) {return false;} if (typeof type === "object" && "trait_reference" in type) { const contractIdsImplementingTrait = getContractIdsImplementingTrait( diff --git a/traits.types.ts b/traits.types.ts index d44f3296..88a21642 100644 --- a/traits.types.ts +++ b/traits.types.ts @@ -33,5 +33,5 @@ export type DefinedTraitType = { type TraitData = { name: string; - contract_identifier: { issuer: Array; name: string }; + contract_identifier: { issuer: any[]; name: string }; }; From 607db8bbafebb2edf01cea356dae342a53c08295 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 6 Apr 2026 01:42:20 +0300 Subject: [PATCH 06/14] Add `prettier` formatter --- .prettierignore | 2 ++ .prettierrc.json | 7 +++++++ package-lock.json | 17 +++++++++++++++++ package.json | 3 +++ 4 files changed, 29 insertions(+) create mode 100644 .prettierignore create mode 100644 .prettierrc.json diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..de4d1f00 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +dist +node_modules diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..de42adfe --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": false, + "tabWidth": 2, + "trailingComma": "all", + "printWidth": 80 +} diff --git a/package-lock.json b/package-lock.json index 24b6524a..19bfefee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@types/jest": "^30.0.0", "jest": "^30.2.0", "oxlint": "^1.58.0", + "prettier": "^3.8.1", "ts-jest": "^29.4.6", "typescript": "^5.9.3" } @@ -4268,6 +4269,22 @@ "node": ">=8" } }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "30.2.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", diff --git a/package.json b/package.json index a01eecdd..ca88535d 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,8 @@ }, "scripts": { "build": "npx -p typescript tsc --project tsconfig.json && node -e \"if (process.platform !== 'win32') require('fs').chmodSync('./dist/app.js', 0o755);\"", + "fmt": "prettier --write .", + "fmt:check": "prettier --check .", "lint": "oxlint -c oxlintrc.json", "lint:fix": "oxlint -c oxlintrc.json --fix", "test": "npx jest", @@ -42,6 +44,7 @@ "@types/jest": "^30.0.0", "jest": "^30.2.0", "oxlint": "^1.58.0", + "prettier": "^3.8.1", "ts-jest": "^29.4.6", "typescript": "^5.9.3" } From 395230254154bdadc2653068b9726d8484f5f650 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 6 Apr 2026 01:43:05 +0300 Subject: [PATCH 07/14] Check formatting in CI --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9fa14cdc..08e85f24 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,9 @@ jobs: - name: Install dependencies run: npm ci + - name: Format check + run: npm run fmt:check + - name: Lint run: npm run lint From c0b11e69cd469eef066ba8315a40688cbc81e24d Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 6 Apr 2026 01:46:06 +0300 Subject: [PATCH 08/14] Turn `fmt-lint` into initial CI pass other jobs need --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08e85f24..730e0b18 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: - master jobs: - lint: + fmt-lint: runs-on: ubuntu-latest timeout-minutes: 2 steps: @@ -30,6 +30,7 @@ jobs: run: npm run lint tests: + needs: [fmt-lint] runs-on: ${{ matrix.os }} timeout-minutes: 10 strategy: @@ -54,6 +55,7 @@ jobs: run: npm test examples: + needs: [fmt-lint] runs-on: ${{ matrix.os }} timeout-minutes: 10 strategy: From bd51f719c10210641063fda260be8bf163088384 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 6 Apr 2026 01:53:33 +0300 Subject: [PATCH 09/14] npm run fmt --- .prettierignore | 1 + app.tests.ts | 26 ++--- app.ts | 32 +++--- dialer.tests.ts | 2 +- docs/CONTRIBUTING.md | 8 +- docs/chapter_1.md | 5 + docs/chapter_3.md | 2 +- docs/chapter_4.md | 18 ++-- docs/chapter_5.md | 21 ++-- docs/chapter_6.md | 44 ++++---- docs/chapter_7.md | 19 ++-- docs/chapter_8.md | 40 ++++---- example/sip010.cjs | 6 +- example/tests/stx-defi.test.ts | 50 ++++----- heatstroke.tests.ts | 124 +++++++++++------------ heatstroke.ts | 74 +++++++------- invariant.tests.ts | 11 +- invariant.ts | 165 +++++++++++++++--------------- persistence.tests.ts | 70 ++++++------- persistence.ts | 10 +- property.tests.ts | 54 +++++----- property.ts | 179 ++++++++++++++++++--------------- shared.tests.ts | 24 ++--- shared.ts | 106 ++++++++++--------- shared.types.ts | 17 ++-- test.utils.ts | 2 +- traits.tests.ts | 108 ++++++++++---------- traits.ts | 112 ++++++++++++--------- 28 files changed, 703 insertions(+), 627 deletions(-) diff --git a/.prettierignore b/.prettierignore index de4d1f00..e96d0469 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,3 @@ dist node_modules +tsconfig.json \ No newline at end of file diff --git a/app.tests.ts b/app.tests.ts index 5e613be5..949bec8e 100644 --- a/app.tests.ts +++ b/app.tests.ts @@ -34,10 +34,10 @@ describe("Command-line arguments handling", () => { `; const noManifestMessage = red( - `\nNo path to Clarinet project provided. Supply it immediately or face the relentless scrutiny of your contract's vulnerabilities.` + `\nNo path to Clarinet project provided. Supply it immediately or face the relentless scrutiny of your contract's vulnerabilities.`, ); const noContractNameMessage = red( - `\nNo target contract name provided. Please provide the contract name to be fuzzed.` + `\nNo target contract name provided. Please provide the contract name to be fuzzed.`, ); const manifestDirPlaceholder = "isolated-example"; @@ -51,7 +51,7 @@ describe("Command-line arguments handling", () => { process.argv = argv; expect(await main()).toBeUndefined(); process.argv = initialArgv; - } + }, ); it("logs the help message at the end when --help is specified", async () => { @@ -107,7 +107,7 @@ describe("Command-line arguments handling", () => { process.argv = initialArgv; jest.restoreAllMocks(); - } + }, ); it.each([ @@ -126,7 +126,7 @@ describe("Command-line arguments handling", () => { ["node", "app.js", manifestDirPlaceholder, "counter"], [ red( - `\nInvalid type provided. Please provide the type of test to be executed. Possible values: test, invariant.` + `\nInvalid type provided. Please provide the type of test to be executed. Possible values: test, invariant.`, ), helpMessage, ], @@ -136,7 +136,7 @@ describe("Command-line arguments handling", () => { ["node", "app.js", manifestDirPlaceholder, "counter", "--bail"], [ red( - `\nInvalid type provided. Please provide the type of test to be executed. Possible values: test, invariant.` + `\nInvalid type provided. Please provide the type of test to be executed. Possible values: test, invariant.`, ), helpMessage, ], @@ -146,7 +146,7 @@ describe("Command-line arguments handling", () => { ["node", "app.js", manifestDirPlaceholder, "counter", "--seed=123"], [ red( - `\nInvalid type provided. Please provide the type of test to be executed. Possible values: test, invariant.` + `\nInvalid type provided. Please provide the type of test to be executed. Possible values: test, invariant.`, ), helpMessage, ], @@ -156,7 +156,7 @@ describe("Command-line arguments handling", () => { ["node", "app.js", manifestDirPlaceholder, "counter", "--runs=10"], [ red( - `\nInvalid type provided. Please provide the type of test to be executed. Possible values: test, invariant.` + `\nInvalid type provided. Please provide the type of test to be executed. Possible values: test, invariant.`, ), helpMessage, ], @@ -173,7 +173,7 @@ describe("Command-line arguments handling", () => { ], [ red( - `\nInvalid type provided. Please provide the type of test to be executed. Possible values: test, invariant.` + `\nInvalid type provided. Please provide the type of test to be executed. Possible values: test, invariant.`, ), helpMessage, ], @@ -465,12 +465,12 @@ describe("Command-line arguments handling", () => { // Setup const tempDir = createIsolatedTestEnvironment( resolve(__dirname, "example"), - isolatedTestEnvPrefix + isolatedTestEnvPrefix, ); // Update argv to use the isolated test environment. const updatedArgv = argv.map((arg) => - arg === manifestDirPlaceholder ? tempDir : arg + arg === manifestDirPlaceholder ? tempDir : arg, ); process.argv = updatedArgv; @@ -491,7 +491,7 @@ describe("Command-line arguments handling", () => { expectedLogs.forEach((expectedLog) => { // Update expected log to use the isolated test environment path. const updatedExpectedLog = expectedLog.startsWith( - "Using manifest path:" + "Using manifest path:", ) ? expectedLog.replace(manifestDirPlaceholder, tempDir) : expectedLog; @@ -503,7 +503,7 @@ describe("Command-line arguments handling", () => { process.argv = initialArgv; jest.restoreAllMocks(); rmSync(tempDir, { recursive: true, force: true }); - } + }, ); }); diff --git a/app.ts b/app.ts index 98672311..49c18c8b 100644 --- a/app.ts +++ b/app.ts @@ -29,10 +29,10 @@ const logger = (log: string, logLevel: "log" | "error" | "info" = "log") => { */ export const getManifestFileName = ( manifestDir: string, - targetContractName: string + targetContractName: string, ) => { const isCustomManifest = existsSync( - resolve(manifestDir, `Clarinet-${targetContractName}.toml`) + resolve(manifestDir, `Clarinet-${targetContractName}.toml`), ); if (isCustomManifest) { @@ -112,8 +112,8 @@ export async function main() { radio.emit( "logMessage", red( - "\nNo path to Clarinet project provided. Supply it immediately or face the relentless scrutiny of your contract's vulnerabilities." - ) + "\nNo path to Clarinet project provided. Supply it immediately or face the relentless scrutiny of your contract's vulnerabilities.", + ), ); radio.emit("logMessage", helpMessage); return; @@ -123,8 +123,8 @@ export async function main() { radio.emit( "logMessage", red( - "\nNo target contract name provided. Please provide the contract name to be fuzzed." - ) + "\nNo target contract name provided. Please provide the contract name to be fuzzed.", + ), ); radio.emit("logMessage", helpMessage); return; @@ -134,8 +134,8 @@ export async function main() { radio.emit( "logMessage", red( - "\nInvalid type provided. Please provide the type of test to be executed. Possible values: test, invariant." - ) + "\nInvalid type provided. Please provide the type of test to be executed. Possible values: test, invariant.", + ), ); radio.emit("logMessage", helpMessage); return; @@ -149,7 +149,7 @@ export async function main() { */ const manifestPath = join( runConfig.manifestDir, - getManifestFileName(runConfig.manifestDir, runConfig.sutContractName) + getManifestFileName(runConfig.manifestDir, runConfig.sutContractName), ); radio.emit("logMessage", `Using manifest path: ${manifestPath}`); radio.emit("logMessage", `Target contract: ${runConfig.sutContractName}`); @@ -192,13 +192,13 @@ export async function main() { ).filter( (deployedContract) => getContractNameFromContractId(deployedContract) === - runConfig.sutContractName + runConfig.sutContractName, ); if (rendezvousList.length === 0) { radio.emit( "logFailure", - `\nContract "${runConfig.sutContractName}" not found among project contracts.\n` + `\nContract "${runConfig.sutContractName}" not found among project contracts.\n`, ); return; } @@ -206,9 +206,9 @@ export async function main() { const rendezvousAllFunctions = getFunctionsFromContractInterfaces( new Map( Array.from(getSimnetDeployerContractsInterfaces(simnet)).filter( - ([contractId]) => rendezvousList.includes(contractId) - ) - ) + ([contractId]) => rendezvousList.includes(contractId), + ), + ), ); // Select the testing routine based on `type`. @@ -226,7 +226,7 @@ export async function main() { runConfig.dial, runConfig.bail, runConfig.regr, - radio + radio, ); break; } @@ -241,7 +241,7 @@ export async function main() { runConfig.runs, runConfig.bail, runConfig.regr, - radio + radio, ); break; } diff --git a/dialer.tests.ts b/dialer.tests.ts index a1bd2922..8dfbe8c9 100644 --- a/dialer.tests.ts +++ b/dialer.tests.ts @@ -82,7 +82,7 @@ describe("DialerRegistry interaction", () => { // Act & Assert expect(registry.registerDialers()).rejects.toThrow( - "process.exit was called" + "process.exit was called", ); }); }); diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index e275b91e..ea89c562 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -18,6 +18,7 @@ To keep things simple and to maintain quality, please follow these guidelines. 2. **Create a fork and branch:** Work in a dedicated branch. Use short, clear names: + ``` git checkout -b my-fix ``` @@ -30,6 +31,7 @@ To keep things simple and to maintain quality, please follow these guidelines. - Keep logic small and focused. Example code comment: + ```js // Good: Explains why, offering crucial context. // Bad: Focuses on how or adds unnecessary verbosity. @@ -38,6 +40,7 @@ To keep things simple and to maintain quality, please follow these guidelines. 4. **Write tests:** Test your changes. Add or update tests in `*.tests.ts` files. Run tests: + ``` npm test ``` @@ -57,6 +60,7 @@ To keep things simple and to maintain quality, please follow these guidelines. 6. **Open a pull request (PR) targeting the `master` branch:** Keep it small and focused. Explain what and why. Example PR description: + ``` This PR fixes a minor off-by-one error in the property-based tests. The fuzzing process now produces the expected results. @@ -81,11 +85,11 @@ Your contributions help keep `rv` robust, helpful, and accessible to everyone. --- -*This CONTRIBUTING guide is crafted with inspiration from the following:* +_This CONTRIBUTING guide is crafted with inspiration from the following:_ - [AutoFixture CONTRIBUTING.md](https://github.com/AutoFixture/AutoFixture/blob/master/CONTRIBUTING.md) by AutoFixture contributors - [Hedgehog STYLE_GUIDE.md](https://github.com/hedgehogqa/haskell-hedgehog/blob/master/STYLE_GUIDE.md) by Hedgehog contributors - [10 Tips for Better Pull Requests](https://blog.ploeh.dk/2015/01/15/10-tips-for-better-pull-requests/) by Mark Seemann (ploeh) - [The Importance of Comments](https://ayende.com/blog/163297/the-importance-of-comments) by Oren Eini (Ayende Rahien) -*(These references also highlight some of our roots and past influences.)* +_(These references also highlight some of our roots and past influences.)_ diff --git a/docs/chapter_1.md b/docs/chapter_1.md index bb0090e6..55da8fb5 100644 --- a/docs/chapter_1.md +++ b/docs/chapter_1.md @@ -23,8 +23,13 @@ The idea behind Rendezvous originates from several inspiring sources: We are deeply grateful to the Stacks Open Internet Foundation for supporting our work and providing crucial assistance, and to the open-source community for their continuous support and contributions. [^1]: Heterogeneous Clarinet Test-Suites: + [^2]: Hughes, J. (2004). "Testing the Hard Stuff and Staying Sane". In Proceedings of the ACM SIGPLAN Workshop on Haskell (Haskell '04). + [^3]: poxl: + [^4]: fast-check: + [^5]: hedgehog: + [^6]: echidna: diff --git a/docs/chapter_3.md b/docs/chapter_3.md index 8782dbfa..7364b10e 100644 --- a/docs/chapter_3.md +++ b/docs/chapter_3.md @@ -2,7 +2,7 @@ ## Programming In vs. Into a Language -Steve McConnell's *Code Complete* discusses the concept of "programming in vs. into a language." This distinction is particularly relevant for Clarity smart contract development: +Steve McConnell's _Code Complete_ discusses the concept of "programming in vs. into a language." This distinction is particularly relevant for Clarity smart contract development: - **Programming into a language**: Thinking in one language (like TypeScript) and then translating those thoughts into another language (like Clarity). This approach often leads to code that doesn't fully leverage the target language's strengths. diff --git a/docs/chapter_4.md b/docs/chapter_4.md index f958d262..d880e63d 100644 --- a/docs/chapter_4.md +++ b/docs/chapter_4.md @@ -4,16 +4,16 @@ Rendezvous offers two complementary testing methodologies: property-based testin ## Property-Based Testing vs. Invariant Testing -| Property-Based Testing | Invariant Testing | -|------------------------|-------------------| -| Tests individual functions | Tests system-wide properties | -| Generates random inputs for specific functions | Generates random sequences of function calls | +| Property-Based Testing | Invariant Testing | +| ---------------------------------------------------- | --------------------------------------------------------- | +| Tests individual functions | Tests system-wide properties | +| Generates random inputs for specific functions | Generates random sequences of function calls | | Verifies function behavior is correct for all inputs | Verifies state remains valid after any operation sequence | -| Helps find edge cases in specific functions | Helps find unexpected interactions between functions | +| Helps find edge cases in specific functions | Helps find unexpected interactions between functions | ## Understanding Property-Based Testing ->Property-based testing verifies that specific properties of your code hold true across a wide range of inputs. Instead of manually crafting test cases, you define a property, and Rendezvous automatically generates test inputs. +> Property-based testing verifies that specific properties of your code hold true across a wide range of inputs. Instead of manually crafting test cases, you define a property, and Rendezvous automatically generates test inputs. ### Key Concepts @@ -52,7 +52,7 @@ In this example, `seq` is automatically generated by Rendezvous with different r ## Understanding Invariant Testing ->Invariant testing ensures that certain conditions about your contract's state remain true regardless of which operations are performed in which order. +> Invariant testing ensures that certain conditions about your contract's state remain true regardless of which operations are performed in which order. ### Key Concepts @@ -99,12 +99,16 @@ In this example, the invariant verifies that if increment has been called more t ## Testing from Inside vs. Outside ### Testing from Inside + Writing tests in Clarity alongside your contract code provides: + - Direct access to internal state and private functions - Natural expression of contract properties in the same language ### Testing from Outside + Using external tools (like TypeScript libraries) enables: + - Testing integration between multiple contracts - Examining transaction-level concerns like events - Using pre- and post-execution functions (see Dialers, Chapter 6) diff --git a/docs/chapter_5.md b/docs/chapter_5.md index c0079824..0afe8b70 100644 --- a/docs/chapter_5.md +++ b/docs/chapter_5.md @@ -17,12 +17,14 @@ This chapter covers how to install Rendezvous and set up your environment for ef [Project Setup](#project-setup) [Troubleshooting Installation Issues](#troubleshooting-installation-issues) - - [Common Issues and Solutions](#common-issues-and-solutions) + +- [Common Issues and Solutions](#common-issues-and-solutions) [Uninstalling Rendezvous](#uninstalling-rendezvous) - - [Removing a Local Installation](#removing-a-local-installation) - - [Removing a Global Installation](#removing-a-global-installation) - - [Removing a Development Installation](#removing-a-development-installation) + +- [Removing a Local Installation](#removing-a-local-installation) +- [Removing a Global Installation](#removing-a-global-installation) +- [Removing a Development Installation](#removing-a-development-installation) [Next Steps](#next-steps) @@ -60,21 +62,25 @@ With a global installation, you can run the `rv` command from any directory with If you want to contribute to Rendezvous or run it from source: 1. Clone the repository: + ```bash git clone https://github.com/stacks-network/rendezvous.git ``` 2. Navigate to the project directory: + ```bash cd rendezvous ``` 3. Install dependencies: + ```bash npm install ``` 4. Build the project: + ```bash npm run build ``` @@ -113,10 +119,10 @@ my-project/ └── Devnet.toml ``` ->Key points to note: +> Key points to note: > ->1. Rendezvous functions (invariants and property-based tests) are written directly inside the contract file, annotated with `#[env(simnet)]` for conditional deployment. ->2. A valid `Clarinet.toml` file must exist at the project root. +> 1. Rendezvous functions (invariants and property-based tests) are written directly inside the contract file, annotated with `#[env(simnet)]` for conditional deployment. +> 2. A valid `Clarinet.toml` file must exist at the project root. ## Troubleshooting Installation Issues @@ -176,6 +182,7 @@ npm uninstall -g @stacks/rendezvous If you installed from source: 1. If you linked the package globally, unlink it first: + ```bash npm unlink -g @stacks/rendezvous ``` diff --git a/docs/chapter_6.md b/docs/chapter_6.md index 150856d1..2472fba1 100644 --- a/docs/chapter_6.md +++ b/docs/chapter_6.md @@ -5,32 +5,38 @@ This chapter explains how to use Rendezvous in different situations. By the end, ## What's Inside [Running Rendezvous](#running-rendezvous) - - [Positional Arguments](#positional-arguments) - - [Options](#options) - - [Summary](#summary) + +- [Positional Arguments](#positional-arguments) +- [Options](#options) +- [Summary](#summary) [Understanding Rendezvous](#understanding-rendezvous) - - [Example](#example) + +- [Example](#example) [The Rendezvous Context](#the-rendezvous-context) - - [How the Context Works](#how-the-context-works) - - [Using the context to write invariants](#using-the-context-to-write-invariants) + +- [How the Context Works](#how-the-context-works) +- [Using the context to write invariants](#using-the-context-to-write-invariants) [Discarding Property-Based Tests](#discarding-property-based-tests) - - [Discard Function](#discard-function) - - [In-Place Discarding](#in-place-discarding) - - [Discarding summary](#discarding-summary) + +- [Discard Function](#discard-function) +- [In-Place Discarding](#in-place-discarding) +- [Discarding summary](#discarding-summary) [Custom Manifest Files](#custom-manifest-files) - - [Why use a custom manifest?](#why-use-a-custom-manifest) - - [A test double for `sbtc-registry`](#a-test-double-for-sbtc-registry) - - [A Custom Manifest File](#a-custom-manifest-file) - - [How It Works](#how-it-works) + +- [Why use a custom manifest?](#why-use-a-custom-manifest) +- [A test double for `sbtc-registry`](#a-test-double-for-sbtc-registry) +- [A Custom Manifest File](#a-custom-manifest-file) +- [How It Works](#how-it-works) [Trait Reference Parameters](#trait-reference-parameters) - - [How Trait Reference Selection Works](#how-trait-reference-selection-works) - - [Example](#example-1) - - [Adding More Implementations](#adding-more-implementations) + +- [How Trait Reference Selection Works](#how-trait-reference-selection-works) +- [Example](#example-1) +- [Adding More Implementations](#adding-more-implementations) --- @@ -235,12 +241,12 @@ async function postTransferSip010PrintEvent(context) { // Find the print event in the function call events. const sip010PrintEvent = functionCallEvents.find( - (ev) => ev.event === "print_event" + (ev) => ev.event === "print_event", ); if (!sip010PrintEvent) { throw new Error( - "No print event found. The transfer function must emit the SIP-010 print event containing the memo!" + "No print event found. The transfer function must emit the SIP-010 print event containing the memo!", ); } @@ -249,7 +255,7 @@ async function postTransferSip010PrintEvent(context) { // Validate that the emitted print event matches the memo argument. if (sip010PrintEventValue !== hexMemoArgumentValue) { throw new Error( - `Print event memo value does not match the memo argument: ${hexMemoArgumentValue} !== ${sip010PrintEventValue}` + `Print event memo value does not match the memo argument: ${hexMemoArgumentValue} !== ${sip010PrintEventValue}`, ); } } diff --git a/docs/chapter_7.md b/docs/chapter_7.md index 6668007d..0e1d1a9e 100644 --- a/docs/chapter_7.md +++ b/docs/chapter_7.md @@ -145,7 +145,7 @@ describe("stx-defi unit tests", () => { "stx-defi", "deposit", [Cl.uint(amountToDeposit)], - address1 + address1, ); expect(result).toBeOk(Cl.bool(true)); @@ -159,7 +159,7 @@ describe("stx-defi unit tests", () => { "stx-defi", "deposit", [Cl.uint(amountToDeposit)], - address1 + address1, ); expect(depositResult).toBeOk(Cl.bool(true)); @@ -167,7 +167,7 @@ describe("stx-defi unit tests", () => { "stx-defi", "borrow", [Cl.uint(amountToBorrow)], - address1 + address1, ); expect(result).toBeOk(Cl.bool(true)); @@ -181,21 +181,21 @@ describe("stx-defi unit tests", () => { "stx-defi", "deposit", [Cl.uint(amountToDeposit)], - address1 + address1, ); simnet.callPublicFn( "stx-defi", "borrow", [Cl.uint(amountToBorrow)], - address1 + address1, ); const { result } = simnet.callReadOnlyFn( "stx-defi", "get-loan-amount", [], - address1 + address1, ); expect(result).toBeOk(Cl.uint(amountToBorrow)); @@ -209,14 +209,14 @@ describe("stx-defi unit tests", () => { "stx-defi", "deposit", [Cl.uint(amountToDeposit)], - address1 + address1, ); const { result } = simnet.callPublicFn( "stx-defi", "borrow", [Cl.uint(amountToBorrow)], - address1 + address1, ); // err-overborrow @@ -240,7 +240,6 @@ The main functions and state of the contract are now covered by tests. Line cove Rendezvous lets you test a broader range of inputs, not just specific examples. Let's see how to write your first property-based test and why it matters. - ### Add an Ice-Breaker Test Before writing any meaningful properties, it's a good idea to check that Rendezvous can run. Add a simple "always-true" test, annotated with `#[env(simnet)]`: @@ -598,7 +597,7 @@ it("loan amount is correct after single borrow", () => { "stx-defi", "borrow", [Cl.uint(amountToBorrow)], - address1 + address1, ); // ... }); diff --git a/docs/chapter_8.md b/docs/chapter_8.md index 3cf6d1f6..ea1faeb4 100644 --- a/docs/chapter_8.md +++ b/docs/chapter_8.md @@ -5,30 +5,34 @@ The Rendezvous repo has a Clarinet project, `example`, that shows how to test Cl ## What's Inside [The `counter` Contract](#the-counter-contract) - - [Invariants](#invariants) - - [Invariant logic](#invariant-logic) - - [Checking the invariants](#checking-the-invariants) - - [Property-Based Tests](#property-based-tests) - - [Test logic](#test-logic) - - [Checking the properties](#checking-the-properties) + +- [Invariants](#invariants) + - [Invariant logic](#invariant-logic) + - [Checking the invariants](#checking-the-invariants) +- [Property-Based Tests](#property-based-tests) + - [Test logic](#test-logic) + - [Checking the properties](#checking-the-properties) [The `cargo` Contract](#the-cargo-contract) - - [Invariants](#invariants-1) - - [Invariant logic](#invariant-logic-1) - - [Checking the invariants](#checking-the-invariants-1) - - [Property-Based Tests](#property-based-tests-1) - - [Test logic](#test-logic-1) - - [Checking the properties](#checking-the-properties-1) + +- [Invariants](#invariants-1) + - [Invariant logic](#invariant-logic-1) + - [Checking the invariants](#checking-the-invariants-1) +- [Property-Based Tests](#property-based-tests-1) + - [Test logic](#test-logic-1) + - [Checking the properties](#checking-the-properties-1) [The `reverse` Contract](#the-reverse-contract) - - [Property-Based Tests](#property-based-tests-2) - - [Test logic](#test-logic-2) - - [Checking the properties (shrinking)](#checking-the-properties-2) + +- [Property-Based Tests](#property-based-tests-2) + - [Test logic](#test-logic-2) + - [Checking the properties (shrinking)](#checking-the-properties-2) [The `slice` Contract](#the-slice-contract) - - [Property-Based Tests](#property-based-tests-3) - - [Test logic](#test-logic-3) - - [Checking the properties (discarding)](#checking-the-properties-3) + +- [Property-Based Tests](#property-based-tests-3) + - [Test logic](#test-logic-3) + - [Checking the properties (discarding)](#checking-the-properties-3) --- diff --git a/example/sip010.cjs b/example/sip010.cjs index 76be9323..c26a9583 100644 --- a/example/sip010.cjs +++ b/example/sip010.cjs @@ -44,12 +44,12 @@ async function postTransferSip010PrintEvent(context) { } const sip010PrintEvent = functionCallEvents.find( - (ev) => ev.event === "print_event" + (ev) => ev.event === "print_event", ); if (!sip010PrintEvent) { throw new Error( - "No print event found. The transfer function must emit the SIP-010 print event containing the memo!" + "No print event found. The transfer function must emit the SIP-010 print event containing the memo!", ); } @@ -58,7 +58,7 @@ async function postTransferSip010PrintEvent(context) { if (printEventValue !== memoValue) { throw new Error( - `The print event memo value is not equal to the memo parameter value: ${memoValue} !== ${printEventValue}` + `The print event memo value is not equal to the memo parameter value: ${memoValue} !== ${printEventValue}`, ); } diff --git a/example/tests/stx-defi.test.ts b/example/tests/stx-defi.test.ts index 0028b969..5970c267 100644 --- a/example/tests/stx-defi.test.ts +++ b/example/tests/stx-defi.test.ts @@ -18,7 +18,7 @@ describe("stx-defi tests", () => { "stx-defi", "deposit", [Cl.uint(amountToDeposit)], - address1 + address1, ); // Assert @@ -34,7 +34,7 @@ describe("stx-defi tests", () => { "stx-defi", "deposit", [Cl.uint(amountToDeposit)], - address1 + address1, ); // Assert @@ -50,7 +50,7 @@ describe("stx-defi tests", () => { "stx-defi", "deposit", [Cl.uint(amountToDeposit)], - address1 + address1, ); // Act @@ -58,7 +58,7 @@ describe("stx-defi tests", () => { "stx-defi", "deposit", [Cl.uint(amountToDeposit)], - address1 + address1, ); // Assert @@ -75,7 +75,7 @@ describe("stx-defi tests", () => { "stx-defi", "deposit", [Cl.uint(amountToDeposit)], - address1 + address1, ); // Act @@ -83,7 +83,7 @@ describe("stx-defi tests", () => { "stx-defi", "borrow", [Cl.uint(amountToBorrow)], - address1 + address1, ); // Assert @@ -91,7 +91,7 @@ describe("stx-defi tests", () => { "stx-defi", "get-amount-owed", [], - address1 + address1, ); expect(result).toBeOk(Cl.uint(amountToBorrow)); @@ -106,7 +106,7 @@ describe("stx-defi tests", () => { "stx-defi", "deposit", [Cl.uint(amountToDeposit)], - address1 + address1, ); // Act @@ -114,7 +114,7 @@ describe("stx-defi tests", () => { "stx-defi", "borrow", [Cl.uint(amountToBorrow)], - address1 + address1, ); // Assert @@ -122,7 +122,7 @@ describe("stx-defi tests", () => { "stx-defi", "get-amount-owed", [], - address1 + address1, ); expect(result).toBeOk(Cl.uint(0)); @@ -137,14 +137,14 @@ describe("stx-defi tests", () => { "stx-defi", "deposit", [Cl.uint(amountToDeposit)], - address1 + address1, ); simnet.callPublicFn( "stx-defi", "borrow", [Cl.uint(amountToBorrow)], - address1 + address1, ); // Act @@ -152,7 +152,7 @@ describe("stx-defi tests", () => { "stx-defi", "repay", [Cl.uint(amountToBorrow)], - address1 + address1, ); // Assert @@ -160,7 +160,7 @@ describe("stx-defi tests", () => { "stx-defi", "get-amount-owed", [], - address1 + address1, ); expect(result).toBeOk(Cl.uint(0)); }); @@ -176,7 +176,7 @@ describe("stx-defi tests", () => { "stx-defi", "deposit", [Cl.uint(amountToDeposit)], - address1 + address1, ); // Act @@ -184,7 +184,7 @@ describe("stx-defi tests", () => { "stx-defi", "borrow", [Cl.uint(amountToBorrow)], - address1 + address1, ); simnet.mineEmptyBurnBlocks(blocksToPass); @@ -194,7 +194,7 @@ describe("stx-defi tests", () => { "stx-defi", "get-amount-owed", [], - address1 + address1, ); expect(owedAmount).toBeOk(Cl.uint(amountToBorrow + accruedInterest)); @@ -210,21 +210,21 @@ describe("stx-defi tests", () => { "stx-defi", "deposit", [Cl.uint(amountToDeposit)], - address1 + address1, ); simnet.callPublicFn( "stx-defi", "borrow", [Cl.uint(amountToBorrow)], - address1 + address1, ); simnet.callPublicFn( "stx-defi", "repay", [Cl.uint(amountToBorrow)], - address1 + address1, ); // Act @@ -232,7 +232,7 @@ describe("stx-defi tests", () => { "stx-defi", "claim-yield", [], - address1 + address1, ); // Assert @@ -249,14 +249,14 @@ describe("stx-defi tests", () => { "stx-defi", "deposit", [Cl.uint(amountToDeposit)], - address1 + address1, ); simnet.callPublicFn( "stx-defi", "borrow", [Cl.uint(amountToBorrow)], - address1 + address1, ); simnet.mineEmptyBurnBlocks(blocksToPass); @@ -265,7 +265,7 @@ describe("stx-defi tests", () => { "stx-defi", "repay", [Cl.uint(amountToBorrow)], - address1 + address1, ); // Act @@ -273,7 +273,7 @@ describe("stx-defi tests", () => { "stx-defi", "claim-yield", [], - address1 + address1, ); // Assert diff --git a/heatstroke.tests.ts b/heatstroke.tests.ts index 66113ac3..63fcc31f 100644 --- a/heatstroke.tests.ts +++ b/heatstroke.tests.ts @@ -15,7 +15,7 @@ const isolatedTestEnvPrefix = "rendezvous-test-heatstroke-"; const asciiString = () => fc.string({ unit: fc.constantFrom( - ..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" + ..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", ), minLength: 1, }); @@ -25,7 +25,7 @@ describe("Custom reporter logging", () => { // Setup const tempDir = createIsolatedTestEnvironment( resolve(__dirname, "example"), - isolatedTestEnvPrefix + isolatedTestEnvPrefix, ); const manifestPath = join(tempDir, "Clarinet.toml"); const simnet = await initSimnet(manifestPath); @@ -45,10 +45,10 @@ describe("Custom reporter logging", () => { access: asciiString(), outputs: fc.array(asciiString()), args: fc.anything(), - }) + }), ), selectedFunctionsArgsList: fc.tuple( - fc.array(fc.oneof(asciiString(), fc.nat(), fc.boolean())) + fc.array(fc.oneof(asciiString(), fc.nat(), fc.boolean())), ), selectedInvariant: fc.record({ name: asciiString(), @@ -57,21 +57,21 @@ describe("Custom reporter logging", () => { args: fc.anything(), }), invariantArgs: fc.array( - fc.oneof(asciiString(), fc.nat(), fc.boolean()) + fc.oneof(asciiString(), fc.nat(), fc.boolean()), ), errorMessage: asciiString(), clarityError: asciiString(), sutCallers: fc.array( fc.constantFrom( ...new Map( - [...simnet.getAccounts()].filter(([key]) => key !== "faucet") - ).entries() - ) + [...simnet.getAccounts()].filter(([key]) => key !== "faucet"), + ).entries(), + ), ), invariantCaller: fc.constantFrom( ...new Map( - [...simnet.getAccounts()].filter(([key]) => key !== "faucet") - ).entries() + [...simnet.getAccounts()].filter(([key]) => key !== "faucet"), + ).entries(), ), }), (r: { @@ -135,7 +135,7 @@ describe("Custom reporter logging", () => { `Seed : ${r.seed}`, `\nCounterexample:`, `- Contract : ${getContractNameFromContractId( - rendezvousContractId + rendezvousContractId, )}`, `- Functions: ${r.selectedFunctions .map((selectedFunction) => selectedFunction.name) @@ -144,7 +144,7 @@ describe("Custom reporter logging", () => { .join(", ")})`, `- Arguments: ${r.selectedFunctionsArgsList .map((selectedFunctionArgs) => - JSON.stringify(selectedFunctionArgs) + JSON.stringify(selectedFunctionArgs), ) .join(", ")}`, `- Callers : ${r.sutCallers @@ -152,7 +152,7 @@ describe("Custom reporter logging", () => { .join(", ")}`, `- Outputs : ${r.selectedFunctions .map((selectedFunction) => - JSON.stringify(selectedFunction.outputs) + JSON.stringify(selectedFunction.outputs), ) .join(", ")}`, `- Invariant: ${r.selectedInvariant.name} (${r.selectedInvariant.access})`, @@ -169,9 +169,9 @@ describe("Custom reporter logging", () => { ]; expect(emittedErrorLogs).toEqual(expectedMessages); - } + }, ), - { numRuns: 10 } + { numRuns: 10 }, ); // Teardown @@ -183,7 +183,7 @@ describe("Custom reporter logging", () => { // Setup const tempDir = createIsolatedTestEnvironment( resolve(__dirname, "example"), - isolatedTestEnvPrefix + isolatedTestEnvPrefix, ); const manifestPath = join(tempDir, "Clarinet.toml"); const simnet = await initSimnet(manifestPath); @@ -204,10 +204,10 @@ describe("Custom reporter logging", () => { access: asciiString(), outputs: fc.array(asciiString()), args: fc.anything(), - }) + }), ), selectedFunctionsArgsList: fc.tuple( - fc.array(fc.oneof(asciiString(), fc.nat(), fc.boolean())) + fc.array(fc.oneof(asciiString(), fc.nat(), fc.boolean())), ), selectedInvariant: fc.record({ name: asciiString(), @@ -216,21 +216,21 @@ describe("Custom reporter logging", () => { args: fc.anything(), }), invariantArgs: fc.array( - fc.oneof(asciiString(), fc.nat(), fc.boolean()) + fc.oneof(asciiString(), fc.nat(), fc.boolean()), ), errorMessage: asciiString(), clarityError: asciiString(), sutCallers: fc.array( fc.constantFrom( ...new Map( - [...simnet.getAccounts()].filter(([key]) => key !== "faucet") - ).entries() - ) + [...simnet.getAccounts()].filter(([key]) => key !== "faucet"), + ).entries(), + ), ), invariantCaller: fc.constantFrom( ...new Map( - [...simnet.getAccounts()].filter(([key]) => key !== "faucet") - ).entries() + [...simnet.getAccounts()].filter(([key]) => key !== "faucet"), + ).entries(), ), }), (r: { @@ -297,7 +297,7 @@ describe("Custom reporter logging", () => { `Path : ${r.path}`, `\nCounterexample:`, `- Contract : ${getContractNameFromContractId( - rendezvousContractId + rendezvousContractId, )}`, `- Functions: ${r.selectedFunctions .map((selectedFunction) => selectedFunction.name) @@ -306,7 +306,7 @@ describe("Custom reporter logging", () => { .join(", ")})`, `- Arguments: ${r.selectedFunctionsArgsList .map((selectedFunctionArgs) => - JSON.stringify(selectedFunctionArgs) + JSON.stringify(selectedFunctionArgs), ) .join(", ")}`, `- Callers : ${r.sutCallers @@ -314,7 +314,7 @@ describe("Custom reporter logging", () => { .join(", ")}`, `- Outputs : ${r.selectedFunctions .map((selectedFunction) => - JSON.stringify(selectedFunction.outputs) + JSON.stringify(selectedFunction.outputs), ) .join(", ")}`, `- Invariant: ${r.selectedInvariant.name} (${r.selectedInvariant.access})`, @@ -332,9 +332,9 @@ describe("Custom reporter logging", () => { expect(emittedErrorLogs).toEqual(expectedMessages); radio.removeAllListeners(); - } + }, ), - { numRuns: 10 } + { numRuns: 10 }, ); // Teardown @@ -346,7 +346,7 @@ describe("Custom reporter logging", () => { // Setup const tempDir = createIsolatedTestEnvironment( resolve(__dirname, "example"), - isolatedTestEnvPrefix + isolatedTestEnvPrefix, ); const manifestPath = join(tempDir, "Clarinet.toml"); const simnet = await initSimnet(manifestPath); @@ -365,10 +365,10 @@ describe("Custom reporter logging", () => { access: asciiString(), outputs: fc.array(asciiString()), args: fc.anything(), - }) + }), ), selectedFunctionsArgsList: fc.tuple( - fc.array(fc.oneof(asciiString(), fc.nat(), fc.boolean())) + fc.array(fc.oneof(asciiString(), fc.nat(), fc.boolean())), ), selectedInvariant: fc.record({ name: asciiString(), @@ -377,20 +377,20 @@ describe("Custom reporter logging", () => { args: fc.anything(), }), invariantArgs: fc.array( - fc.oneof(asciiString(), fc.nat(), fc.boolean()) + fc.oneof(asciiString(), fc.nat(), fc.boolean()), ), errorMessage: asciiString(), sutCallers: fc.array( fc.constantFrom( ...new Map( - [...simnet.getAccounts()].filter(([key]) => key !== "faucet") - ).entries() - ) + [...simnet.getAccounts()].filter(([key]) => key !== "faucet"), + ).entries(), + ), ), invariantCaller: fc.constantFrom( ...new Map( - [...simnet.getAccounts()].filter(([key]) => key !== "faucet") - ).entries() + [...simnet.getAccounts()].filter(([key]) => key !== "faucet"), + ).entries(), ), }), (r: { @@ -452,9 +452,9 @@ describe("Custom reporter logging", () => { // Verify expect(emittedErrorLogs).toEqual([]); radio.removeAllListeners(); - } + }, ), - { numRuns: 10 } + { numRuns: 10 }, ); // Teardown @@ -465,7 +465,7 @@ describe("Custom reporter logging", () => { // Setup const tempDir = createIsolatedTestEnvironment( resolve(__dirname, "example"), - isolatedTestEnvPrefix + isolatedTestEnvPrefix, ); const manifestPath = join(tempDir, "Clarinet.toml"); const simnet = await initSimnet(manifestPath); @@ -486,14 +486,14 @@ describe("Custom reporter logging", () => { args: fc.anything(), }), functionArgs: fc.array( - fc.oneof(asciiString(), fc.nat(), fc.boolean()) + fc.oneof(asciiString(), fc.nat(), fc.boolean()), ), errorMessage: asciiString(), clarityError: asciiString(), testCaller: fc.constantFrom( ...new Map( - [...simnet.getAccounts()].filter(([key]) => key !== "faucet") - ).entries() + [...simnet.getAccounts()].filter(([key]) => key !== "faucet"), + ).entries(), ), }), (r: { @@ -545,13 +545,13 @@ describe("Custom reporter logging", () => { `Seed : ${r.seed}`, `\nCounterexample:`, `- Contract : ${getContractNameFromContractId( - rendezvousContractId + rendezvousContractId, )}`, `- Test Function : ${r.selectedTestFunction.name} (${r.selectedTestFunction.access})`, `- Arguments : ${JSON.stringify(r.functionArgs)}`, `- Caller : ${r.testCaller[0]}`, `- Outputs : ${JSON.stringify( - r.selectedTestFunction.outputs + r.selectedTestFunction.outputs, )}`, `\nWhat happened? Rendezvous went on a rampage and found a weak spot:\n`, `The test function "${ @@ -564,9 +564,9 @@ describe("Custom reporter logging", () => { ]; expect(emittedErrorLogs).toEqual(expectedMessages); - } + }, ), - { numRuns: 10 } + { numRuns: 10 }, ); // Teardown @@ -578,7 +578,7 @@ describe("Custom reporter logging", () => { // Setup const tempDir = createIsolatedTestEnvironment( resolve(__dirname, "example"), - isolatedTestEnvPrefix + isolatedTestEnvPrefix, ); const manifestPath = join(tempDir, "Clarinet.toml"); const simnet = await initSimnet(manifestPath); @@ -600,14 +600,14 @@ describe("Custom reporter logging", () => { args: fc.anything(), }), functionArgs: fc.array( - fc.oneof(asciiString(), fc.nat(), fc.boolean()) + fc.oneof(asciiString(), fc.nat(), fc.boolean()), ), errorMessage: asciiString(), clarityError: asciiString(), testCaller: fc.constantFrom( ...new Map( - [...simnet.getAccounts()].filter(([key]) => key !== "faucet") - ).entries() + [...simnet.getAccounts()].filter(([key]) => key !== "faucet"), + ).entries(), ), }), (r: { @@ -662,13 +662,13 @@ describe("Custom reporter logging", () => { `Path : ${r.path}`, `\nCounterexample:`, `- Contract : ${getContractNameFromContractId( - rendezvousContractId + rendezvousContractId, )}`, `- Test Function : ${r.selectedTestFunction.name} (${r.selectedTestFunction.access})`, `- Arguments : ${JSON.stringify(r.functionArgs)}`, `- Caller : ${r.testCaller[0]}`, `- Outputs : ${JSON.stringify( - r.selectedTestFunction.outputs + r.selectedTestFunction.outputs, )}`, `\nWhat happened? Rendezvous went on a rampage and found a weak spot:\n`, `The test function "${ @@ -681,9 +681,9 @@ describe("Custom reporter logging", () => { ]; expect(emittedErrorLogs).toEqual(expectedMessages); - } + }, ), - { numRuns: 10 } + { numRuns: 10 }, ); // Teardown @@ -695,7 +695,7 @@ describe("Custom reporter logging", () => { // Setup const tempDir = createIsolatedTestEnvironment( resolve(__dirname, "example"), - isolatedTestEnvPrefix + isolatedTestEnvPrefix, ); const manifestPath = join(tempDir, "Clarinet.toml"); const simnet = await initSimnet(manifestPath); @@ -714,13 +714,13 @@ describe("Custom reporter logging", () => { args: fc.anything(), }), functionArgs: fc.array( - fc.oneof(asciiString(), fc.nat(), fc.boolean()) + fc.oneof(asciiString(), fc.nat(), fc.boolean()), ), errorMessage: asciiString(), testCaller: fc.constantFrom( ...new Map( - [...simnet.getAccounts()].filter(([key]) => key !== "faucet") - ).entries() + [...simnet.getAccounts()].filter(([key]) => key !== "faucet"), + ).entries(), ), }), (r: { @@ -768,9 +768,9 @@ describe("Custom reporter logging", () => { // Verify expect(emittedErrorLogs).toEqual([]); - } + }, ), - { numRuns: 10 } + { numRuns: 10 }, ); // Teardown diff --git a/heatstroke.ts b/heatstroke.ts index d30a2ea5..311b6bad 100644 --- a/heatstroke.ts +++ b/heatstroke.ts @@ -33,7 +33,7 @@ export function reporter( runDetails: RunDetails, radio: EventEmitter, type: "invariant" | "test", - statistics: Statistics + statistics: Statistics, ) { const { counterexample, failed, numRuns, path, seed } = runDetails; @@ -50,7 +50,7 @@ export function reporter( // Report general run data. radio.emit( "logFailure", - `\nError: Property failed after ${numRuns} tests.` + `\nError: Property failed after ${numRuns} tests.`, ); radio.emit("logFailure", `Seed : ${seed}`); if (path) { @@ -64,58 +64,58 @@ export function reporter( radio.emit( "logFailure", `- Contract : ${getContractNameFromContractId( - ce.rendezvousContractId - )}` + ce.rendezvousContractId, + )}`, ); radio.emit( "logFailure", `- Functions: ${ce.selectedFunctions .map( (selectedFunction: ContractInterfaceFunction) => - selectedFunction.name + selectedFunction.name, ) .join(", ")} (${ce.selectedFunctions .map( (selectedFunction: ContractInterfaceFunction) => - selectedFunction.access + selectedFunction.access, ) - .join(", ")})` + .join(", ")})`, ); radio.emit( "logFailure", `- Arguments: ${ce.selectedFunctionsArgsList .map((selectedFunctionArgs: any[]) => - JSON.stringify(selectedFunctionArgs) + JSON.stringify(selectedFunctionArgs), ) - .join(", ")}` + .join(", ")}`, ); radio.emit( "logFailure", `- Callers : ${ce.sutCallers .map((sutCaller: [string, string]) => sutCaller[0]) - .join(", ")}` + .join(", ")}`, ); radio.emit( "logFailure", `- Outputs : ${ce.selectedFunctions .map((selectedFunction: ContractInterfaceFunction) => - JSON.stringify(selectedFunction.outputs) + JSON.stringify(selectedFunction.outputs), ) - .join(", ")}` + .join(", ")}`, ); radio.emit( "logFailure", - `- Invariant: ${ce.selectedInvariant.name} (${ce.selectedInvariant.access})` + `- Invariant: ${ce.selectedInvariant.name} (${ce.selectedInvariant.access})`, ); radio.emit( "logFailure", - `- Arguments: ${JSON.stringify(ce.invariantArgs)}` + `- Arguments: ${JSON.stringify(ce.invariantArgs)}`, ); radio.emit("logFailure", `- Caller : ${ce.invariantCaller[0]}`); radio.emit( "logFailure", - `\nWhat happened? Rendezvous went on a rampage and found a weak spot:\n` + `\nWhat happened? Rendezvous went on a rampage and found a weak spot:\n`, ); const formattedError = `The invariant "${ @@ -138,26 +138,26 @@ export function reporter( radio.emit( "logFailure", `- Contract : ${getContractNameFromContractId( - ce.rendezvousContractId - )}` + ce.rendezvousContractId, + )}`, ); radio.emit( "logFailure", - `- Test Function : ${ce.selectedTestFunction.name} (${ce.selectedTestFunction.access})` + `- Test Function : ${ce.selectedTestFunction.name} (${ce.selectedTestFunction.access})`, ); radio.emit( "logFailure", - `- Arguments : ${JSON.stringify(ce.functionArgs)}` + `- Arguments : ${JSON.stringify(ce.functionArgs)}`, ); radio.emit("logFailure", `- Caller : ${ce.testCaller[0]}`); radio.emit( "logFailure", - `- Outputs : ${JSON.stringify(ce.selectedTestFunction.outputs)}` + `- Outputs : ${JSON.stringify(ce.selectedTestFunction.outputs)}`, ); radio.emit( "logFailure", - `\nWhat happened? Rendezvous went on a rampage and found a weak spot:\n` + `\nWhat happened? Rendezvous went on a rampage and found a weak spot:\n`, ); const formattedError = `The test function "${ @@ -182,8 +182,8 @@ export function reporter( green( `\nOK, ${ type === "invariant" ? "invariants" : "properties" - } passed after ${numRuns} runs.\n` - ) + } passed after ${numRuns} runs.\n`, + ), ); } reportStatistics(statistics, type, radio); @@ -204,7 +204,7 @@ const WARN_SYMBOL = "!"; function reportStatistics( statistics: Statistics, type: "invariant" | "test", - radio: EventEmitter + radio: EventEmitter, ): void { if ( (type === "invariant" && (!statistics.invariant || !statistics.sut)) || @@ -242,24 +242,24 @@ function reportStatistics( radio.emit("logMessage", "\nLEGEND:\n"); radio.emit( "logMessage", - " SUCCESSFUL calls executed and advanced the test" + " SUCCESSFUL calls executed and advanced the test", ); radio.emit( "logMessage", - " IGNORED calls failed but did not affect the test" + " IGNORED calls failed but did not affect the test", ); radio.emit( "logMessage", - " PASSED invariants maintained system integrity" + " PASSED invariants maintained system integrity", ); radio.emit( "logMessage", - " FAILED invariants indicate contract vulnerabilities" + " FAILED invariants indicate contract vulnerabilities", ); if (computeTotalCount(statistics.invariant!.failed) > 0) { radio.emit( "logFailure", - "\n! FAILED invariants require immediate attention as they indicate that your contract can enter an invalid state under certain conditions." + "\n! FAILED invariants require immediate attention as they indicate that your contract can enter an invalid state under certain conditions.", ); } break; @@ -284,20 +284,20 @@ function reportStatistics( radio.emit("logMessage", "\nLEGEND:\n"); radio.emit( "logMessage", - " PASSED properties verified for given inputs" + " PASSED properties verified for given inputs", ); radio.emit( "logMessage", - " DISCARDED skipped due to invalid preconditions" + " DISCARDED skipped due to invalid preconditions", ); radio.emit( "logMessage", - " FAILED property violations or unexpected behavior" + " FAILED property violations or unexpected behavior", ); if (computeTotalCount(statistics.test!.failed) > 0) { radio.emit( "logFailure", - "\n! FAILED tests indicate that your function properties don't hold for all inputs. Review the counterexamples above for debugging." + "\n! FAILED tests indicate that your function properties don't hold for all inputs. Review the counterexamples above for debugging.", ); } break; @@ -314,7 +314,7 @@ function reportStatistics( function logAsTree( tree: Record, radio: EventEmitter, - options: StatisticsTreeOptions = {} + options: StatisticsTreeOptions = {}, ): void { const { isLastSection = false, baseIndent = " " } = options; @@ -322,7 +322,7 @@ function logAsTree( node: Record, indent: string = baseIndent, isLastParent = true, - radioEmitter: EventEmitter + radioEmitter: EventEmitter, ): void => { const keys = Object.keys(node); @@ -335,14 +335,14 @@ function logAsTree( if (typeof node[key] === "object" && node[key] !== null) { radioEmitter.emit( "logMessage", - `${leadingChar} ${indent}${connector} ${ARROW} ${key}` + `${leadingChar} ${indent}${connector} ${ARROW} ${key}`, ); printTree(node[key], nextIndent, isLast, radioEmitter); } else { const count = node[key] as number; radioEmitter.emit( "logMessage", - `${leadingChar} ${indent}${connector} ${key}: x${count}` + `${leadingChar} ${indent}${connector} ${key}: x${count}`, ); } }); diff --git a/invariant.tests.ts b/invariant.tests.ts index 350f1821..00fc9d7c 100644 --- a/invariant.tests.ts +++ b/invariant.tests.ts @@ -21,8 +21,7 @@ describe("Simnet contracts operations", () => { ); const manifestPath = join(tempDir, "Clarinet.toml"); const simnet = await initSimnet(manifestPath); - const sutContractsInterfaces = - getSimnetDeployerContractsInterfaces(simnet); + const sutContractsInterfaces = getSimnetDeployerContractsInterfaces(simnet); const sutContractsAllFunctions = getFunctionsFromContractInterfaces( sutContractsInterfaces, ); @@ -95,10 +94,10 @@ describe("Simnet contracts operations", () => { // `called` is initialized to 0. const expectedClarityValue = Cl.some(Cl.tuple({ called: Cl.uint(0) })); const expectedContext = functions.map((f) => ({ - contractId, - functionName: f.name, - called: expectedClarityValue, - })); + contractId, + functionName: f.name, + called: expectedClarityValue, + })); expect(actualContext).toEqual(expectedContext); diff --git a/invariant.ts b/invariant.ts index 337dc566..422663bd 100644 --- a/invariant.ts +++ b/invariant.ts @@ -23,7 +23,11 @@ import { import type { EnrichedContractInterfaceFunction } from "./shared.types"; import { DialerRegistry, PostDialerError, PreDialerError } from "./dialer"; import type { Statistics } from "./heatstroke.types"; -import { getFailureFilePath, loadFailures, persistFailure } from "./persistence"; +import { + getFailureFilePath, + loadFailures, + persistFailure, +} from "./persistence"; import { resolve } from "path"; import type { ImplementedTraitType } from "./traits.types"; @@ -54,7 +58,7 @@ export const checkInvariants = async ( dial: string | undefined, bail: boolean, regr: boolean, - radio: EventEmitter + radio: EventEmitter, ) => { // The Rendezvous identifier is the first one in the list. Only one contract // can be fuzzed at a time. @@ -69,20 +73,21 @@ export const checkInvariants = async ( // arrays of their invariant functions. This map will be used to access the // invariant functions for each Rendezvous contract afterwards. const rendezvousInvariantFunctions = filterInvariantFunctions( - rendezvousAllFunctions + rendezvousAllFunctions, ); const sutFunctions = rendezvousSutFunctions.get(rendezvousContractId)!; const traitReferenceSutFunctions = sutFunctions.filter( - isTraitReferenceFunction + isTraitReferenceFunction, ); const invariantFunctions = rendezvousInvariantFunctions.get(rendezvousContractId)!; const traitReferenceInvariantFunctions = invariantFunctions.filter( - isTraitReferenceFunction + isTraitReferenceFunction, ); - const targetContractName = getContractNameFromContractId(rendezvousContractId); + const targetContractName = + getContractNameFromContractId(rendezvousContractId); const sutTraitReferenceMap = buildTraitReferenceMap(sutFunctions); const invariantTraitReferenceMap = buildTraitReferenceMap(invariantFunctions); @@ -92,7 +97,7 @@ export const checkInvariants = async ( simnet.getContractAST(targetContractName), sutTraitReferenceMap, sutFunctions, - rendezvousContractId + rendezvousContractId, ) : rendezvousSutFunctions; @@ -102,7 +107,7 @@ export const checkInvariants = async ( simnet.getContractAST(targetContractName), invariantTraitReferenceMap, invariantFunctions, - rendezvousContractId + rendezvousContractId, ) : rendezvousInvariantFunctions; @@ -117,7 +122,7 @@ export const checkInvariants = async ( enrichedSutFunctionsInterfaces, sutTraitReferenceMap, projectTraitImplementations, - rendezvousContractId + rendezvousContractId, ); // Extract invariant functions with missing trait implementations. These @@ -127,14 +132,14 @@ export const checkInvariants = async ( enrichedInvariantFunctionsInterfaces, invariantTraitReferenceMap, projectTraitImplementations, - rendezvousContractId + rendezvousContractId, ); // Emit warnings for functions with missing trait implementations emitMissingTraitWarnings( radio, sutFunctionsWithMissingTraits, - invariantFunctionsWithMissingTraits + invariantFunctionsWithMissingTraits, ); // Filter out functions with missing trait implementations from the enriched @@ -161,20 +166,20 @@ export const checkInvariants = async ( const functions = getFunctionsListForContract( executableSutFunctions, - rendezvousContractId + rendezvousContractId, ); const invariants = getFunctionsListForContract( executableInvariantFunctions, - rendezvousContractId + rendezvousContractId, ); if (functions?.length === 0) { radio.emit( "logMessage", red( - `No public functions found for the "${targetContractName}" contract. Without public functions, no state transitions can happen inside the contract, and the invariant test is not meaningful.\n` - ) + `No public functions found for the "${targetContractName}" contract. Without public functions, no state transitions can happen inside the contract, and the invariant test is not meaningful.\n`, + ), ); return; } @@ -183,8 +188,8 @@ export const checkInvariants = async ( radio.emit( "logMessage", red( - `No invariant functions found for the "${targetContractName}" contract. Beware, for your contract may be exposed to unforeseen issues.\n` - ) + `No invariant functions found for the "${targetContractName}" contract. Beware, for your contract may be exposed to unforeseen issues.\n`, + ), ); return; } @@ -194,12 +199,12 @@ export const checkInvariants = async ( radio.emit( "logMessage", `Regressions loaded from: ${resolve( - getFailureFilePath(rendezvousContractId) - )}` + getFailureFilePath(rendezvousContractId), + )}`, ); radio.emit( "logMessage", - `Loading ${targetContractName} contract regressions...\n` + `Loading ${targetContractName} contract regressions...\n`, ); const regressions = loadFailures(rendezvousContractId, "invariant"); @@ -207,8 +212,8 @@ export const checkInvariants = async ( radio.emit( "logMessage", `Found ${underline( - `${regressions.length} regressions` - )} for the ${targetContractName} contract.\n` + `${regressions.length} regressions`, + )} for the ${targetContractName} contract.\n`, ); for (const regression of regressions) { @@ -218,7 +223,7 @@ export const checkInvariants = async ( regression.seed, regression.numRuns, regression.dial, - regression.timestamp + regression.timestamp, ); await resetSession(); @@ -241,7 +246,7 @@ export const checkInvariants = async ( // Run fresh invariant tests using user-provided configuration. radio.emit( "logMessage", - `Starting fresh round of invariant testing for the ${targetContractName} contract using user-provided configuration...\n` + `Starting fresh round of invariant testing for the ${targetContractName} contract using user-provided configuration...\n`, ); await invariantTest({ @@ -293,7 +298,7 @@ interface InvariantTestContext { * @returns A promise that resolves when the invariant test is complete. */ const invariantTest = async ( - config: InvariantTestConfig & InvariantTestContext + config: InvariantTestConfig & InvariantTestContext, ) => { const { simnet, @@ -312,7 +317,7 @@ const invariantTest = async ( // Derive accounts and addresses from simnet. const simnetAccounts = simnet.getAccounts(); const eligibleAccounts = new Map( - [...simnetAccounts].filter(([key]) => key !== "faucet") + [...simnetAccounts].filter(([key]) => key !== "faucet"), ); const simnetAddresses = Array.from(simnetAccounts.values()); @@ -384,7 +389,7 @@ const invariantTest = async ( }), selectedInvariant: fc.constantFrom(...invariants), }) - .map((selectedFunctions) => ({ ...r, ...selectedFunctions })) + .map((selectedFunctions) => ({ ...r, ...selectedFunctions })), ) .chain((r) => fc @@ -394,7 +399,7 @@ const invariantTest = async ( { minLength: r.selectedFunctions.length, maxLength: r.selectedFunctions.length, - } + }, ), selectedFunctionsArgsList: fc.tuple( ...r.selectedFunctions.map((selectedFunction) => @@ -402,20 +407,20 @@ const invariantTest = async ( ...functionToArbitrary( selectedFunction, simnetAddresses, - projectTraitImplementations - ) - ) - ) + projectTraitImplementations, + ), + ), + ), ), invariantArgs: fc.tuple( ...functionToArbitrary( r.selectedInvariant, simnetAddresses, - projectTraitImplementations - ) + projectTraitImplementations, + ), ), }) - .map((args) => ({ ...r, ...args })) + .map((args) => ({ ...r, ...args })), ) .chain((r) => fc @@ -433,16 +438,16 @@ const invariantTest = async ( }) : fc.constant(0), }) - .map((burnBlocks) => ({ ...r, ...burnBlocks })) + .map((burnBlocks) => ({ ...r, ...burnBlocks })), ), async (r) => { const selectedFunctionsArgsCV = r.selectedFunctions.map( (selectedFunction, index) => - argsToCV(selectedFunction, r.selectedFunctionsArgsList[index]) + argsToCV(selectedFunction, r.selectedFunctionsArgsList[index]), ); const selectedInvariantArgsCV = argsToCV( r.selectedInvariant, - r.invariantArgs + r.invariantArgs, ); for (const [index, selectedFunction] of r.selectedFunctions.entries()) { @@ -477,7 +482,7 @@ const invariantTest = async ( r.rendezvousContractId, selectedFunction.name, selectedFunctionsArgsCV[index], - sutCallerAddress + sutCallerAddress, ); const functionCallResultJson = cvToJSON(functionCall.result); @@ -488,13 +493,13 @@ const invariantTest = async ( // experiance by providing important information about the function // call during the run. const selectedFunctionClarityResult = cvToString( - functionCall.result + functionCall.result, ); if (functionCallResultJson.success) { statistics.sut!.successful.set( selectedFunction.name, - statistics.sut!.successful.get(selectedFunction.name)! + 1 + statistics.sut!.successful.get(selectedFunction.name)! + 1, ); localContext[r.rendezvousContractId][selectedFunction.name]++; @@ -504,10 +509,10 @@ const invariantTest = async ( [ Cl.stringAscii(selectedFunction.name), Cl.uint( - localContext[r.rendezvousContractId][selectedFunction.name] + localContext[r.rendezvousContractId][selectedFunction.name], ), ], - simnet.deployer + simnet.deployer, ); // Function call passed. @@ -519,7 +524,7 @@ const invariantTest = async ( `${targetContractName} ` + `${underline(selectedFunction.name)} ` + `${printedFunctionArgs} ` + - green(selectedFunctionClarityResult) + green(selectedFunctionClarityResult), ); try { @@ -537,7 +542,7 @@ const invariantTest = async ( // Function call failed. statistics.sut!.failed.set( selectedFunction.name, - statistics.sut!.failed.get(selectedFunction.name)! + 1 + statistics.sut!.failed.get(selectedFunction.name)! + 1, ); radio.emit( "logMessage", @@ -548,8 +553,8 @@ const invariantTest = async ( `${targetContractName} ` + `${underline(selectedFunction.name)} ` + `${printedFunctionArgs} ` + - red(selectedFunctionClarityResult) - ) + red(selectedFunctionClarityResult), + ), ); } } catch (error: any) { @@ -577,8 +582,8 @@ const invariantTest = async ( `${targetContractName} ` + `${underline(selectedFunction.name)} ` + `${printedFunctionArgs} ` + - red(displayedError) - ) + red(displayedError), + ), ); } } @@ -604,7 +609,7 @@ const invariantTest = async ( r.rendezvousContractId, r.selectedInvariant.name, selectedInvariantArgsCV, - invariantCallerAddress + invariantCallerAddress, ); const invariantCallResultJson = cvToJSON(invariantCallResult); @@ -615,7 +620,7 @@ const invariantTest = async ( statistics.invariant!.successful.set( r.selectedInvariant.name, statistics.invariant!.successful.get(r.selectedInvariant.name)! + - 1 + 1, ); radio.emit( "logMessage", @@ -626,12 +631,12 @@ const invariantTest = async ( `${targetContractName} ` + `${underline(r.selectedInvariant.name)} ` + `${printedInvariantArgs} ` + - green(invariantCallClarityResult) + green(invariantCallClarityResult), ); } else { statistics.invariant!.failed.set( r.selectedInvariant.name, - statistics.invariant!.failed.get(r.selectedInvariant.name)! + 1 + statistics.invariant!.failed.get(r.selectedInvariant.name)! + 1, ); radio.emit( "logMessage", @@ -643,8 +648,8 @@ const invariantTest = async ( `${targetContractName} ` + `${underline(r.selectedInvariant.name)} ` + `${printedInvariantArgs} ` + - red(invariantCallClarityResult) - ) + red(invariantCallClarityResult), + ), ); // Invariant call went through, but returned something other than @@ -652,7 +657,7 @@ const invariantTest = async ( // runtime errors. throw new FalsifiedInvariantError( `Invariant failed for ${targetContractName} contract: "${r.selectedInvariant.name}" returned ${invariantCallClarityResult}`, - invariantCallClarityResult + invariantCallClarityResult, ); } } catch (error: any) { @@ -668,8 +673,8 @@ const invariantTest = async ( `[FAIL] ` + `${targetContractName} ` + `${underline(r.selectedInvariant.name)} ` + - printedInvariantArgs - ) + printedInvariantArgs, + ), ); } @@ -680,7 +685,7 @@ const invariantTest = async ( if (r.canMineBlocks) { simnet.mineEmptyBurnBlocks(r.burnBlocks); } - } + }, ), { endOnFailure: bail, @@ -688,7 +693,7 @@ const invariantTest = async ( reporter: radioReporter, seed: seed, verbose: true, - } + }, ); }; @@ -699,7 +704,7 @@ const invariantTest = async ( function emitMissingTraitWarnings( radio: EventEmitter, sutFunctions: string[], - invariantFunctions: string[] + invariantFunctions: string[], ): void { if (sutFunctions.length === 0 && invariantFunctions.length === 0) { return; @@ -710,8 +715,8 @@ function emitMissingTraitWarnings( radio.emit( "logMessage", yellow( - `\nWarning: The following SUT functions reference traits without eligible implementations and will be skipped:\n\n${functionList}\n` - ) + `\nWarning: The following SUT functions reference traits without eligible implementations and will be skipped:\n\n${functionList}\n`, + ), ); } @@ -720,16 +725,16 @@ function emitMissingTraitWarnings( radio.emit( "logMessage", yellow( - `\nWarning: The following invariant functions reference traits without eligible implementations and will be skipped:\n\n${functionList}\n` - ) + `\nWarning: The following invariant functions reference traits without eligible implementations and will be skipped:\n\n${functionList}\n`, + ), ); } radio.emit( "logMessage", yellow( - `Note: You can add contracts implementing traits either as project contracts or as Clarinet requirements.\n` - ) + `Note: You can add contracts implementing traits either as project contracts or as Clarinet requirements.\n`, + ), ); } @@ -742,7 +747,7 @@ function emitMissingTraitWarnings( */ export const initializeLocalContext = ( contractId: string, - functions: EnrichedContractInterfaceFunction[] + functions: EnrichedContractInterfaceFunction[], ): LocalContext => ({ [contractId]: Object.fromEntries(functions.map((f) => [f.name, 0])), }); @@ -757,19 +762,19 @@ export const initializeLocalContext = ( export const initializeClarityContext = ( simnet: Simnet, contractId: string, - functions: EnrichedContractInterfaceFunction[] + functions: EnrichedContractInterfaceFunction[], ) => { functions.forEach((fn) => { const { result: initialize } = simnet.callPublicFn( contractId, "update-context", [Cl.stringAscii(fn.name), Cl.uint(0)], - simnet.deployer + simnet.deployer, ); const jsonResult = cvToJSON(initialize); if (!jsonResult.value || !jsonResult.success) { throw new Error( - `Failed to initialize the context for function: ${fn.name}.` + `Failed to initialize the context for function: ${fn.name}.`, ); } }); @@ -787,7 +792,7 @@ export const initializeClarityContext = ( * contract. */ const filterSutFunctions = ( - allFunctionsMap: Map + allFunctionsMap: Map, ) => new Map( Array.from(allFunctionsMap, ([contractId, functions]) => [ @@ -796,22 +801,22 @@ const filterSutFunctions = ( (f) => f.access === "public" && f.name !== "update-context" && - !f.name.startsWith("test-") + !f.name.startsWith("test-"), ), - ]) + ]), ); const filterInvariantFunctions = ( - allFunctionsMap: Map + allFunctionsMap: Map, ) => new Map( Array.from(allFunctionsMap, ([contractId, functions]) => [ contractId, functions.filter( ({ access, name }) => - access === "read_only" && name.startsWith("invariant-") + access === "read_only" && name.startsWith("invariant-"), ), - ]) + ]), ); export class FalsifiedInvariantError extends Error { @@ -828,19 +833,19 @@ const emitInvariantRegressionTestHeader = ( seed: number, numRuns: number, dial: string | undefined, - timestamp: number + timestamp: number, ) => { radio.emit("logMessage", LOG_DIVIDER); radio.emit( "logMessage", ` Running ${underline( - timestamp + timestamp, )} regression test for the ${targetContractName} contract with: - Seed: ${seed} - Runs: ${numRuns} - Dial: ${dial ?? "none (default)"} -` +`, ); }; diff --git a/persistence.tests.ts b/persistence.tests.ts index d01a86b5..0c9b94d1 100644 --- a/persistence.tests.ts +++ b/persistence.tests.ts @@ -25,7 +25,7 @@ const createTemporaryCustomTestBaseDir = (dirName: string) => { const createMockRunDetails = ( seed: number, failed = true, - numRuns = 100 + numRuns = 100, ): RunDetails => ({ failed, seed, @@ -45,7 +45,7 @@ describe("Failure Persistence", () => { // Assert expect(filePath).toBe( - resolve(".rendezvous-regressions", `${TEST_CONTRACT_ID}.json`) + resolve(".rendezvous-regressions", `${TEST_CONTRACT_ID}.json`), ); }); @@ -58,15 +58,15 @@ describe("Failure Persistence", () => { // Act const filePath = getFailureFilePath( TEST_CONTRACT_ID, - customBaseDir + customBaseDir, ); // Assert expect(filePath).toBe( - resolve(customBaseDir, `${TEST_CONTRACT_ID}.json`) + resolve(customBaseDir, `${TEST_CONTRACT_ID}.json`), ); - } - ) + }, + ), ); }); }); @@ -93,7 +93,7 @@ describe("Failure Persistence", () => { // Verify expect( - statSync(getFailureFilePath(TEST_CONTRACT_ID, customBaseDir)) + statSync(getFailureFilePath(TEST_CONTRACT_ID, customBaseDir)), ).toBeDefined(); // Teardown @@ -101,8 +101,8 @@ describe("Failure Persistence", () => { recursive: true, force: true, }); - } - ) + }, + ), ); }); @@ -129,7 +129,7 @@ describe("Failure Persistence", () => { undefined, { baseDir: customBaseDir, - } + }, ); persistFailure(runDetails2, "test", TEST_CONTRACT_ID, undefined, { baseDir: customBaseDir, @@ -141,7 +141,7 @@ describe("Failure Persistence", () => { "invariant", { baseDir: customBaseDir, - } + }, ); const testFailures = loadFailures(TEST_CONTRACT_ID, "test", { baseDir: customBaseDir, @@ -156,8 +156,8 @@ describe("Failure Persistence", () => { recursive: true, force: true, }); - } - ) + }, + ), ); }); @@ -195,8 +195,8 @@ describe("Failure Persistence", () => { recursive: true, force: true, }); - } - ) + }, + ), ); }); @@ -221,7 +221,7 @@ describe("Failure Persistence", () => { undefined, { baseDir: customBaseDir, - } + }, ); persistFailure(runDetails, "test", TEST_CONTRACT_ID, undefined, { baseDir: customBaseDir, @@ -233,7 +233,7 @@ describe("Failure Persistence", () => { "invariant", { baseDir: customBaseDir, - } + }, ); const testFailures = loadFailures(TEST_CONTRACT_ID, "test", { baseDir: customBaseDir, @@ -248,8 +248,8 @@ describe("Failure Persistence", () => { recursive: true, force: true, }); - } - ) + }, + ), ); }); @@ -277,7 +277,7 @@ describe("Failure Persistence", () => { "invariant", TEST_CONTRACT_ID, undefined, - { baseDir: customBaseDir } + { baseDir: customBaseDir }, ); }); @@ -292,8 +292,8 @@ describe("Failure Persistence", () => { // Teardown rmSync(customBaseDir, { recursive: true, force: true }); - } - ) + }, + ), ); }); @@ -319,7 +319,7 @@ describe("Failure Persistence", () => { undefined, { baseDir: customBaseDir, - } + }, ); const after = Date.now(); @@ -333,8 +333,8 @@ describe("Failure Persistence", () => { // Teardown rmSync(customBaseDir, { recursive: true, force: true }); - } - ) + }, + ), ); }); @@ -360,7 +360,7 @@ describe("Failure Persistence", () => { undefined, { baseDir: customBaseDir, - } + }, ); // Verify @@ -371,8 +371,8 @@ describe("Failure Persistence", () => { // Teardown rmSync(customBaseDir, { recursive: true, force: true }); - } - ) + }, + ), ); }); @@ -403,8 +403,8 @@ describe("Failure Persistence", () => { // Teardown rmSync(customBaseDir, { recursive: true, force: true }); - } - ) + }, + ), ); }); }); @@ -431,8 +431,8 @@ describe("Failure Persistence", () => { // Teardown rmSync(customBaseDir, { recursive: true, force: true }); - } - ) + }, + ), ); }); @@ -454,7 +454,7 @@ describe("Failure Persistence", () => { undefined, { baseDir: customBaseDir, - } + }, ); // Exercise @@ -467,8 +467,8 @@ describe("Failure Persistence", () => { // Teardown rmSync(customBaseDir, { recursive: true, force: true }); - } - ) + }, + ), ); }); }); diff --git a/persistence.ts b/persistence.ts index ae005fb4..be927cb3 100644 --- a/persistence.ts +++ b/persistence.ts @@ -48,7 +48,7 @@ const DEFAULT_CONFIG: Required = { */ export const getFailureFilePath = ( contractId: string, - baseDir: string = DEFAULT_CONFIG.baseDir + baseDir: string = DEFAULT_CONFIG.baseDir, ): string => resolve(baseDir, `${contractId}.json`); /** @@ -59,7 +59,7 @@ export const getFailureFilePath = ( */ const loadFailureStore = ( contractId: string, - baseDir: string = DEFAULT_CONFIG.baseDir + baseDir: string = DEFAULT_CONFIG.baseDir, ): FailureStore => { const filePath = getFailureFilePath(contractId, baseDir); @@ -80,7 +80,7 @@ const loadFailureStore = ( const saveFailureStore = ( contractId: string, baseDir: string, - store: FailureStore + store: FailureStore, ): void => { // Ensure the base directory exists. mkdirSync(baseDir, { recursive: true }); @@ -103,7 +103,7 @@ export const persistFailure = ( type: "invariant" | "test", contractId: string, dial: string | undefined, - config?: PersistenceConfig + config?: PersistenceConfig, ): void => { const { baseDir } = { ...DEFAULT_CONFIG, ...config }; @@ -148,7 +148,7 @@ export const persistFailure = ( export const loadFailures = ( contractId: string, type: "invariant" | "test", - config?: PersistenceConfig + config?: PersistenceConfig, ): FailureRecord[] => { const { baseDir } = { ...DEFAULT_CONFIG, ...config }; const store = loadFailureStore(contractId, baseDir); diff --git a/property.tests.ts b/property.tests.ts index 34b263d0..4594655a 100644 --- a/property.tests.ts +++ b/property.tests.ts @@ -29,7 +29,7 @@ describe("Test discarding related operations", () => { fc.record({ name: fc.string(), type: fc.constantFrom("int128", "uint128", "bool", "principal"), - }) + }), ), outputs: fc.record({ type: fc.constant("bool") }), }), @@ -47,8 +47,8 @@ describe("Test discarding related operations", () => { }; const actual = isReturnTypeBoolean(discardFunctionInterface); expect(actual).toBe(true); - } - ) + }, + ), ); }); @@ -62,7 +62,7 @@ describe("Test discarding related operations", () => { fc.record({ name: fc.string(), type: fc.constantFrom("int128", "uint128", "bool", "principal"), - }) + }), ), outputs: fc.record({ type: fc.constantFrom("int128", "uint128", "principal"), @@ -82,8 +82,8 @@ describe("Test discarding related operations", () => { }; const actual = isReturnTypeBoolean(discardFunctionInterface); expect(actual).toBe(false); - } - ) + }, + ), ); }); @@ -97,7 +97,7 @@ describe("Test discarding related operations", () => { fc.record({ name: fc.string(), type: fc.constantFrom("int128", "uint128", "bool", "principal"), - }) + }), ), outputs: fc.record({ type: fc.constant("bool") }), }), @@ -121,11 +121,11 @@ describe("Test discarding related operations", () => { }; const actual = isParamsMatch( testFunctionInterface, - discardFunctionInterface + discardFunctionInterface, ); expect(actual).toBe(true); - } - ) + }, + ), ); }); @@ -144,9 +144,9 @@ describe("Test discarding related operations", () => { "int128", "uint128", "bool", - "principal" + "principal", ), - }) + }), ), outputs: fc.record({ type: fc.constant("bool") }), }), @@ -160,9 +160,9 @@ describe("Test discarding related operations", () => { "int128", "uint128", "bool", - "principal" + "principal", ), - }) + }), ), outputs: fc.record({ type: fc.constant("bool") }), }), @@ -170,13 +170,13 @@ describe("Test discarding related operations", () => { .filter( (r) => JSON.stringify( - [...r.testFn.args].sort((a, b) => a.name.localeCompare(b.name)) + [...r.testFn.args].sort((a, b) => a.name.localeCompare(b.name)), ) !== JSON.stringify( [...r.discardFn.args].sort((a, b) => - a.name.localeCompare(b.name) - ) - ) + a.name.localeCompare(b.name), + ), + ), ), (r: { testFn: { @@ -206,11 +206,11 @@ describe("Test discarding related operations", () => { }; const actual = isParamsMatch( testFunctionInterface, - discardFunctionInterface + discardFunctionInterface, ); expect(actual).toBe(false); - } - ) + }, + ), ); }); @@ -218,7 +218,7 @@ describe("Test discarding related operations", () => { // Setup const tempDir = createIsolatedTestEnvironment( resolve(__dirname, "example"), - isolatedTestEnvPrefix + isolatedTestEnvPrefix, ); const manifestPath = join(tempDir, "Clarinet.toml"); const simnet = await initSimnet(manifestPath); @@ -227,14 +227,14 @@ describe("Test discarding related operations", () => { "contract", "(define-public (discarded-fn) (ok false))", { clarityVersion: 2 }, - simnet.deployer + simnet.deployer, ); const { result: functionCallResult } = simnet.callPublicFn( "contract", "discarded-fn", [], - simnet.deployer + simnet.deployer, ); const functionCallResultJson = cvToJSON(functionCallResult); @@ -253,7 +253,7 @@ describe("Test discarding related operations", () => { // Setup const tempDir = createIsolatedTestEnvironment( resolve(__dirname, "example"), - isolatedTestEnvPrefix + isolatedTestEnvPrefix, ); const manifestPath = join(tempDir, "Clarinet.toml"); const simnet = await initSimnet(manifestPath); @@ -262,14 +262,14 @@ describe("Test discarding related operations", () => { "contract", "(define-public (not-discarded-fn) (ok true))", { clarityVersion: 2 }, - simnet.deployer + simnet.deployer, ); const { result: functionCallResult } = simnet.callPublicFn( "contract", "not-discarded-fn", [], - simnet.deployer + simnet.deployer, ); const functionCallResultJson = cvToJSON(functionCallResult); diff --git a/property.ts b/property.ts index 5c2b165f..98ee4c56 100644 --- a/property.ts +++ b/property.ts @@ -20,7 +20,11 @@ import { isTraitReferenceFunction, getNonTestableTraitFunctions, } from "./traits"; -import { getFailureFilePath, loadFailures, persistFailure } from "./persistence"; +import { + getFailureFilePath, + loadFailures, + persistFailure, +} from "./persistence"; import { resolve } from "path"; import type { ImplementedTraitType } from "./traits.types"; import type { EnrichedContractInterfaceFunction } from "./shared.types"; @@ -50,24 +54,26 @@ export const checkProperties = async ( runs: number | undefined, bail: boolean, regr: boolean, - radio: EventEmitter + radio: EventEmitter, ) => { // A map where the keys are the test contract identifiers and the values are // arrays of their test functions. This map will be used to access the test // functions for each test contract in the property-based testing routine. const testContractsTestFunctions = filterTestFunctions( - rendezvousAllFunctions + rendezvousAllFunctions, ); const rendezvousContractId = rendezvousList[0]; - const allTestFunctions = testContractsTestFunctions.get(rendezvousContractId)!; + const allTestFunctions = + testContractsTestFunctions.get(rendezvousContractId)!; const traitReferenceFunctionsCount = allTestFunctions.filter( - isTraitReferenceFunction + isTraitReferenceFunction, ).length; - const targetContractName = getContractNameFromContractId(rendezvousContractId); + const targetContractName = + getContractNameFromContractId(rendezvousContractId); const traitReferenceMap = buildTraitReferenceMap(allTestFunctions); const enrichedTestFunctionsInterfaces = @@ -76,7 +82,7 @@ export const checkProperties = async ( simnet.getContractAST(targetContractName), traitReferenceMap, allTestFunctions, - rendezvousContractId + rendezvousContractId, ) : testContractsTestFunctions; @@ -93,7 +99,7 @@ export const checkProperties = async ( enrichedTestFunctionsInterfaces, traitReferenceMap, projectTraitImplementations, - rendezvousContractId + rendezvousContractId, ) : []; @@ -111,8 +117,8 @@ export const checkProperties = async ( .filter( (functionInterface) => !functionsMissingTraitImplementations.includes( - functionInterface.name - ) + functionInterface.name, + ), ), ], ]); @@ -124,9 +130,9 @@ export const checkProperties = async ( Array.from(rendezvousAllFunctions, ([contractId, functions]) => [ contractId, functions.filter( - ({ access, name }) => access === "read_only" && name.startsWith("can-") + ({ access, name }) => access === "read_only" && name.startsWith("can-"), ), - ]) + ]), ); // Pair each test function with its corresponding discard function. When a @@ -144,14 +150,14 @@ export const checkProperties = async ( ?.find((pf) => pf.name === `can-${f.name}`); return [f.name, discardFunction?.name]; - }) + }), ), - ] - ) + ], + ), ); const hasDiscardFunctionErrors = Array.from( - testContractsPairedFunctions + testContractsPairedFunctions, ).some(([contractId, pairedMap]) => Array.from(pairedMap).some(([testFunctionName, discardFunctionName]) => discardFunctionName @@ -161,10 +167,10 @@ export const checkProperties = async ( testFunctionName, testContractsDiscardFunctions, testContractsTestFunctions, - radio + radio, ) - : false - ) + : false, + ), ); if (hasDiscardFunctionErrors) { @@ -173,13 +179,15 @@ export const checkProperties = async ( const testFunctions = getFunctionsListForContract( executableTestContractsTestFunctions, - rendezvousContractId + rendezvousContractId, ); if (testFunctions?.length === 0) { radio.emit( "logMessage", - red(`No test functions found for the "${targetContractName}" contract.\n`) + red( + `No test functions found for the "${targetContractName}" contract.\n`, + ), ); return; } @@ -189,12 +197,12 @@ export const checkProperties = async ( radio.emit( "logMessage", `Regressions loaded from: ${resolve( - getFailureFilePath(rendezvousContractId) - )}` + getFailureFilePath(rendezvousContractId), + )}`, ); radio.emit( "logMessage", - `Loading ${targetContractName} contract regressions...\n` + `Loading ${targetContractName} contract regressions...\n`, ); const regressions = loadFailures(rendezvousContractId, "test"); @@ -202,8 +210,8 @@ export const checkProperties = async ( radio.emit( "logMessage", `Found ${underline( - `${regressions.length} regressions` - )} for the ${targetContractName} contract.\n` + `${regressions.length} regressions`, + )} for the ${targetContractName} contract.\n`, ); for (const regression of regressions) { @@ -212,7 +220,7 @@ export const checkProperties = async ( targetContractName, regression.seed, regression.numRuns, - regression.timestamp + regression.timestamp, ); await resetSession(); @@ -237,7 +245,7 @@ export const checkProperties = async ( // Run fresh tests using user-provided configuration. radio.emit( "logMessage", - `Starting fresh round of property testing for the ${targetContractName} contract using user-provided configuration...\n` + `Starting fresh round of property testing for the ${targetContractName} contract using user-provided configuration...\n`, ); await propertyTest({ @@ -287,7 +295,7 @@ interface PropertyTestContext { * @returns A promise that resolves when the property test is complete. */ const propertyTest = async ( - config: PropertyTestConfig & PropertyTestContext + config: PropertyTestConfig & PropertyTestContext, ) => { const { simnet, @@ -305,7 +313,7 @@ const propertyTest = async ( // Derive accounts and addresses from simnet. const simnetAccounts = simnet.getAccounts(); const eligibleAccounts = new Map( - [...simnetAccounts].filter(([key]) => key !== "faucet") + [...simnetAccounts].filter(([key]) => key !== "faucet"), ); const simnetAddresses = Array.from(simnetAccounts.values()); @@ -333,7 +341,7 @@ const propertyTest = async ( "test", rendezvousContractId, // No dialers in property-based testing. - undefined + undefined, ); } }; @@ -354,7 +362,7 @@ const propertyTest = async ( .map((selectedTestFunction) => ({ ...r, ...selectedTestFunction, - })) + })), ) .chain((r) => fc @@ -363,11 +371,11 @@ const propertyTest = async ( ...functionToArbitrary( r.selectedTestFunction, simnetAddresses, - projectTraitImplementations - ) + projectTraitImplementations, + ), ), }) - .map((args) => ({ ...r, ...args })) + .map((args) => ({ ...r, ...args })), ) .chain((r) => fc @@ -385,12 +393,12 @@ const propertyTest = async ( }) : fc.constant(0), }) - .map((burnBlocks) => ({ ...r, ...burnBlocks })) + .map((burnBlocks) => ({ ...r, ...burnBlocks })), ), async (r) => { const selectedTestFunctionArgs = argsToCV( r.selectedTestFunction, - r.functionArgs + r.functionArgs, ); const printedTestFunctionArgs = r.functionArgs @@ -416,13 +424,13 @@ const propertyTest = async ( selectedTestFunctionArgs, r.rendezvousContractId, simnet, - testCallerAddress + testCallerAddress, ); if (discarded) { statistics.test!.discarded.set( r.selectedTestFunction.name, - statistics.test!.discarded.get(r.selectedTestFunction.name)! + 1 + statistics.test!.discarded.get(r.selectedTestFunction.name)! + 1, ); radio.emit( "logMessage", @@ -432,7 +440,7 @@ const propertyTest = async ( `${yellow("[WARN]")} ` + `${targetContractName} ` + `${underline(r.selectedTestFunction.name)} ` + - dim(printedTestFunctionArgs) + dim(printedTestFunctionArgs), ); } else { try { @@ -442,23 +450,24 @@ const propertyTest = async ( r.rendezvousContractId, r.selectedTestFunction.name, selectedTestFunctionArgs, - testCallerAddress + testCallerAddress, ); const testFunctionCallResultJson = cvToJSON(testFunctionCallResult); const discardedInPlace = isTestDiscardedInPlace( - testFunctionCallResultJson + testFunctionCallResultJson, ); const testFunctionCallClarityResult = cvToString( - testFunctionCallResult + testFunctionCallResult, ); if (discardedInPlace) { statistics.test!.discarded.set( r.selectedTestFunction.name, - statistics.test!.discarded.get(r.selectedTestFunction.name)! + 1 + statistics.test!.discarded.get(r.selectedTestFunction.name)! + + 1, ); radio.emit( "logMessage", @@ -469,7 +478,7 @@ const propertyTest = async ( `${targetContractName} ` + `${underline(r.selectedTestFunction.name)} ` + `${dim(printedTestFunctionArgs)} ` + - yellow(testFunctionCallClarityResult) + yellow(testFunctionCallClarityResult), ); } else if ( !discardedInPlace && @@ -479,7 +488,7 @@ const propertyTest = async ( statistics.test!.successful.set( r.selectedTestFunction.name, statistics.test!.successful.get(r.selectedTestFunction.name)! + - 1 + 1, ); radio.emit( "logMessage", @@ -490,7 +499,7 @@ const propertyTest = async ( `${targetContractName} ` + `${underline(r.selectedTestFunction.name)} ` + `${printedTestFunctionArgs} ` + - green(testFunctionCallClarityResult) + green(testFunctionCallClarityResult), ); if (r.canMineBlocks) { @@ -499,14 +508,14 @@ const propertyTest = async ( } else { statistics.test!.failed.set( r.selectedTestFunction.name, - statistics.test!.failed.get(r.selectedTestFunction.name)! + 1 + statistics.test!.failed.get(r.selectedTestFunction.name)! + 1, ); // The function call did not result in (ok true) or (ok false). // Either the test failed or the test function returned an // unexpected value i.e. `(ok 1)`. throw new PropertyTestError( `Test failed for ${targetContractName} contract: "${r.selectedTestFunction.name}" returned ${testFunctionCallClarityResult}`, - testFunctionCallClarityResult + testFunctionCallClarityResult, ); } } catch (error: any) { @@ -514,10 +523,10 @@ const propertyTest = async ( error instanceof PropertyTestError ? error.clarityError : error && - typeof error === "string" && - error.toLowerCase().includes("runtime") - ? "(runtime)" - : "(unknown)"; + typeof error === "string" && + error.toLowerCase().includes("runtime") + ? "(runtime)" + : "(unknown)"; // Capture the error and log the test failure. radio.emit( @@ -530,15 +539,15 @@ const propertyTest = async ( `${targetContractName} ` + `${underline(r.selectedTestFunction.name)} ` + `${printedTestFunctionArgs} ` + - displayedError - ) + displayedError, + ), ); // Re-throw the error for fast-check to catch and process. throw error; } } - } + }, ), { endOnFailure: bail, @@ -546,7 +555,7 @@ const propertyTest = async ( reporter: radioReporter, seed: seed, verbose: true, - } + }, ); }; @@ -556,7 +565,7 @@ const propertyTest = async ( */ const emitMissingTraitWarning = ( radio: EventEmitter, - functionNames: string[] + functionNames: string[], ) => { if (functionNames.length === 0) { return; @@ -566,14 +575,14 @@ const emitMissingTraitWarning = ( radio.emit( "logMessage", yellow( - `\nWarning: The following test functions reference traits without eligible implementations and will be skipped:\n\n${functionList}\n` - ) + `\nWarning: The following test functions reference traits without eligible implementations and will be skipped:\n\n${functionList}\n`, + ), ); radio.emit( "logMessage", yellow( - `Note: You can add contracts implementing traits either as project contracts or as requirements.\n` - ) + `Note: You can add contracts implementing traits either as project contracts or as requirements.\n`, + ), ); }; @@ -585,32 +594,32 @@ const emitPropertyRegressionTestHeader = ( targetContractName: string, seed: number, numRuns: number, - timestamp: number + timestamp: number, ) => { radio.emit("logMessage", LOG_DIVIDER); radio.emit( "logMessage", ` Running ${underline( - timestamp + timestamp, )} regression test for the ${targetContractName} contract with: - Seed: ${seed} - Runs: ${numRuns} -` +`, ); }; const filterTestFunctions = ( - allFunctionsMap: Map + allFunctionsMap: Map, ) => new Map( Array.from(allFunctionsMap, ([contractId, functions]) => [ contractId, functions.filter( - (f) => f.access === "public" && f.name.startsWith("test-") + (f) => f.access === "public" && f.name.startsWith("test-"), ), - ]) + ]), ); export const isTestDiscardedInPlace = (testFunctionCallResultJson: any) => @@ -631,15 +640,17 @@ const isTestDiscarded = ( selectedTestFunctionArgs: any[], contractId: string, simnet: Simnet, - selectedCaller: string + selectedCaller: string, ) => { - if (!discardFunctionName) {return false;} + if (!discardFunctionName) { + return false; + } const { result: discardFunctionCallResult } = simnet.callReadOnlyFn( contractId, discardFunctionName, selectedTestFunctionArgs, - selectedCaller + selectedCaller, ); const jsonDiscardFunctionCallResult = cvToJSON(discardFunctionCallResult); return jsonDiscardFunctionCallResult.value === false; @@ -662,7 +673,7 @@ const validateDiscardFunction = ( testFunctionName: string, testContractsDiscardFunctions: Map, testContractsTestFunctions: Map, - radio: EventEmitter + radio: EventEmitter, ) => { const testFunction = testContractsTestFunctions .get(contractId) @@ -671,16 +682,18 @@ const validateDiscardFunction = ( .get(contractId) ?.find((f) => f.name === discardFunctionName); - if (!testFunction || !discardFunction) {return false;} + if (!testFunction || !discardFunction) { + return false; + } if (!isParamsMatch(testFunction, discardFunction)) { radio.emit( "logMessage", red( `\nError: Parameter mismatch for discard function "${discardFunctionName}" in contract "${getContractNameFromContractId( - contractId - )}".\n` - ) + contractId, + )}".\n`, + ), ); return false; } @@ -690,9 +703,9 @@ const validateDiscardFunction = ( "logMessage", red( `\nError: Return type must be boolean for discard function "${discardFunctionName}" in contract "${getContractNameFromContractId( - contractId - )}".\n` - ) + contractId, + )}".\n`, + ), ); return false; } @@ -709,13 +722,13 @@ const validateDiscardFunction = ( */ export const isParamsMatch = ( testFunctionInterface: ContractInterfaceFunction, - discardFunctionInterface: ContractInterfaceFunction + discardFunctionInterface: ContractInterfaceFunction, ) => { const sortedTestFunctionArgs = [...testFunctionInterface.args].sort((a, b) => - a.name.localeCompare(b.name) + a.name.localeCompare(b.name), ); const sortedDiscardFunctionArgs = [...discardFunctionInterface.args].sort( - (a, b) => a.name.localeCompare(b.name) + (a, b) => a.name.localeCompare(b.name), ); return ( JSON.stringify(sortedTestFunctionArgs) === @@ -729,7 +742,7 @@ export const isParamsMatch = ( * @returns A boolean indicating if the return type is boolean. */ export const isReturnTypeBoolean = ( - discardFunctionInterface: ContractInterfaceFunction + discardFunctionInterface: ContractInterfaceFunction, ) => discardFunctionInterface.outputs.type === "bool"; export class PropertyTestError extends Error { diff --git a/shared.tests.ts b/shared.tests.ts index 09f03d15..e8f03e67 100644 --- a/shared.tests.ts +++ b/shared.tests.ts @@ -17,14 +17,14 @@ describe("Simnet contracts operations", () => { // Setup const tempDir = createIsolatedTestEnvironment( resolve(__dirname, "example"), - isolatedTestEnvPrefix + isolatedTestEnvPrefix, ); const manifestPath = join(tempDir, "Clarinet.toml"); const simnet = await initSimnet(manifestPath); const expectedDeployerContracts = new Map( Array.from(simnet.getContractsInterfaces()).filter( - ([key]) => key.split(".")[0] === simnet.deployer - ) + ([key]) => key.split(".")[0] === simnet.deployer, + ), ); // Exercise @@ -42,7 +42,7 @@ describe("Simnet contracts operations", () => { // Setup const tempDir = createIsolatedTestEnvironment( resolve(__dirname, "example"), - isolatedTestEnvPrefix + isolatedTestEnvPrefix, ); const manifestPath = join(tempDir, "Clarinet.toml"); const simnet = await initSimnet(manifestPath); @@ -52,15 +52,15 @@ describe("Simnet contracts operations", () => { Array.from(sutContractsInterfaces, ([contractId, contractInterface]) => [ contractId, contractInterface.functions, - ]) + ]), ); const expectedContractFunctionsList = sutContractsList.map( - (contractId) => allFunctionsMap.get(contractId) || [] + (contractId) => allFunctionsMap.get(contractId) || [], ); // Exercise const actualContractFunctionsList = sutContractsList.map((contractId) => - getFunctionsListForContract(allFunctionsMap, contractId) + getFunctionsListForContract(allFunctionsMap, contractId), ); // Verify @@ -74,7 +74,7 @@ describe("Simnet contracts operations", () => { // Setup const tempDir = createIsolatedTestEnvironment( resolve(__dirname, "example"), - isolatedTestEnvPrefix + isolatedTestEnvPrefix, ); const manifestPath = join(tempDir, "Clarinet.toml"); const simnet = await initSimnet(manifestPath); @@ -83,12 +83,12 @@ describe("Simnet contracts operations", () => { Array.from(sutContractsInterfaces, ([contractId, contractInterface]) => [ contractId, contractInterface.functions, - ]) + ]), ); // Exercise const actualAllFunctionsMap = getFunctionsFromContractInterfaces( - sutContractsInterfaces + sutContractsInterfaces, ); // Verify @@ -117,8 +117,8 @@ describe("Contract identifier parsing", () => { // Assert expect(actual).toBe(contractName); - } - ) + }, + ), ); }); }); diff --git a/shared.ts b/shared.ts index 582ac995..c0830fc6 100644 --- a/shared.ts +++ b/shared.ts @@ -37,12 +37,12 @@ export const LOG_DIVIDER = * @returns The contract IDs mapped to their interfaces. */ export const getSimnetDeployerContractsInterfaces = ( - simnet: Simnet + simnet: Simnet, ): Map => new Map( Array.from(simnet.getContractsInterfaces()).filter( - ([key]) => key.split(".")[0] === simnet.deployer - ) + ([key]) => key.split(".")[0] === simnet.deployer, + ), ); /** @@ -52,18 +52,18 @@ export const getSimnetDeployerContractsInterfaces = ( * @returns The contract IDs mapped to their function interfaces. */ export const getFunctionsFromContractInterfaces = ( - contractInterfaces: Map + contractInterfaces: Map, ): Map => new Map( Array.from(contractInterfaces, ([contractId, contractInterface]) => [ contractId, contractInterface.functions, - ]) + ]), ); export const getFunctionsListForContract = ( functionsMap: Map, - contractId: string + contractId: string, ) => functionsMap.get(contractId) || []; /** Dynamically generates fast-check arbitraries for a given function @@ -77,14 +77,14 @@ export const getFunctionsListForContract = ( export const functionToArbitrary = ( functionInterface: EnrichedContractInterfaceFunction, addresses: string[], - projectTraitImplementations: Record + projectTraitImplementations: Record, ): fc.Arbitrary[] => functionInterface.args.map((arg) => parameterTypeToArbitrary( arg.type as EnrichedParameterType, addresses, - projectTraitImplementations - ) + projectTraitImplementations, + ), ); /** @@ -98,24 +98,27 @@ export const functionToArbitrary = ( const parameterTypeToArbitrary = ( type: EnrichedParameterType, addresses: string[], - projectTraitImplementations: Record + projectTraitImplementations: Record, ): fc.Arbitrary => { if (typeof type === "string") { // The type is a base type. if (type === "principal") { - if (addresses.length === 0) - {throw new Error( - "No addresses could be retrieved from the simnet instance!" - );} + if (addresses.length === 0) { + throw new Error( + "No addresses could be retrieved from the simnet instance!", + ); + } return baseTypesToArbitrary.principal(addresses); - } else {return baseTypesToArbitrary[type];} + } else { + return baseTypesToArbitrary[type]; + } } else { // The type is a complex type. if ("buffer" in type) { return complexTypesToArbitrary["buffer"](type.buffer.length); } else if ("string-ascii" in type) { return complexTypesToArbitrary["string-ascii"]( - type["string-ascii"].length + type["string-ascii"].length, ); } else if ("string-utf8" in type) { return complexTypesToArbitrary["string-utf8"](type["string-utf8"].length); @@ -124,31 +127,31 @@ const parameterTypeToArbitrary = ( type.list.type, type.list.length, addresses, - projectTraitImplementations + projectTraitImplementations, ); } else if ("tuple" in type) { return complexTypesToArbitrary["tuple"]( type.tuple, addresses, - projectTraitImplementations + projectTraitImplementations, ); } else if ("optional" in type) { return complexTypesToArbitrary["optional"]( type.optional, addresses, - projectTraitImplementations + projectTraitImplementations, ); } else if ("response" in type) { return complexTypesToArbitrary.response( type.response.ok, type.response.error, addresses, - projectTraitImplementations + projectTraitImplementations, ); } else if ("trait_reference" in type) { return complexTypesToArbitrary.trait_reference( type.trait_reference, - projectTraitImplementations + projectTraitImplementations, ); } else { throw new Error(`Unsupported complex type: ${JSON.stringify(type)}`); @@ -186,25 +189,25 @@ const complexTypesToArbitrary: ComplexTypesToArbitrary = { type: EnrichedParameterType, length: number, addresses: string[], - projectTraitImplementations: Record + projectTraitImplementations: Record, ) => fc.array( parameterTypeToArbitrary(type, addresses, projectTraitImplementations), { maxLength: length, - } + }, ), tuple: ( items: { name: string; type: EnrichedParameterType }[], addresses: string[], - projectTraitImplementations: Record + projectTraitImplementations: Record, ) => { const tupleArbitraries: Record> = {}; items.forEach((item) => { tupleArbitraries[item.name] = parameterTypeToArbitrary( item.type, addresses, - projectTraitImplementations + projectTraitImplementations, ); }); return fc.record(tupleArbitraries); @@ -212,16 +215,16 @@ const complexTypesToArbitrary: ComplexTypesToArbitrary = { optional: ( type: EnrichedParameterType, addresses: string[], - projectTraitImplementations: Record + projectTraitImplementations: Record, ) => fc.option( - parameterTypeToArbitrary(type, addresses, projectTraitImplementations) + parameterTypeToArbitrary(type, addresses, projectTraitImplementations), ), response: ( okType: EnrichedParameterType, errType: EnrichedParameterType, addresses: string[], - projectTraitImplementations: Record + projectTraitImplementations: Record, ) => fc.oneof( fc.record({ @@ -229,7 +232,7 @@ const complexTypesToArbitrary: ComplexTypesToArbitrary = { value: parameterTypeToArbitrary( okType, addresses, - projectTraitImplementations + projectTraitImplementations, ), }), fc.record({ @@ -237,15 +240,19 @@ const complexTypesToArbitrary: ComplexTypesToArbitrary = { value: parameterTypeToArbitrary( errType, addresses, - projectTraitImplementations + projectTraitImplementations, ), - }) + }), ), trait_reference: ( traitData: ImportedTraitType, - projectTraitImplementations: Record - ) => fc.constantFrom( - ...getContractIdsImplementingTrait(traitData, projectTraitImplementations) + projectTraitImplementations: Record, + ) => + fc.constantFrom( + ...getContractIdsImplementingTrait( + traitData, + projectTraitImplementations, + ), ), }; @@ -262,7 +269,7 @@ const complexTypesToArbitrary: ComplexTypesToArbitrary = { * https://github.com/dubzzz/fast-check/commit/3f4f1203a8863c07d22b45591bf0de1fac02b948 */ export const hexaString = ( - constraints: fc.StringConstraints = {} + constraints: fc.StringConstraints = {}, ): fc.Arbitrary => { const hexa = (): fc.Arbitrary => { const hexCharSet = "0123456789abcdef"; @@ -284,10 +291,10 @@ const charSet = */ export const argsToCV = ( functionInterface: EnrichedContractInterfaceFunction, - generatedArguments: any[] + generatedArguments: any[], ) => functionInterface.args.map((arg, i) => - argToCV(generatedArguments[i], arg.type as EnrichedParameterType) + argToCV(generatedArguments[i], arg.type as EnrichedParameterType), ); /** @@ -298,7 +305,7 @@ export const argsToCV = ( */ const argToCV = ( generatedArgument: any, - type: EnrichedParameterType + type: EnrichedParameterType, ): ClarityValue => { if (isBaseType(type)) { // Base type. @@ -324,7 +331,7 @@ const argToCV = ( return complexTypesToCV["string-utf8"](generatedArgument); } else if ("list" in type) { const listItems = generatedArgument.map((item: any) => - argToCV(item, type.list.type) + argToCV(item, type.list.type), ); return complexTypesToCV.list(listItems); } else if ("tuple" in type) { @@ -332,7 +339,7 @@ const argToCV = ( type.tuple.forEach((field) => { tupleData[field.name] = argToCV( generatedArgument[field.name], - field.type + field.type, ); }); return complexTypesToCV.tuple(tupleData); @@ -340,7 +347,7 @@ const argToCV = ( return optionalCVOf( generatedArgument ? argToCV(generatedArgument, type.optional) - : undefined + : undefined, ); } else if ("response" in type) { const status = generatedArgument.status as ResponseStatus; @@ -351,7 +358,7 @@ const argToCV = ( return complexTypesToCV.trait_reference(generatedArgument); } else { throw new Error( - `Unsupported complex parameter type: ${JSON.stringify(type)}` + `Unsupported complex parameter type: ${JSON.stringify(type)}`, ); } } @@ -379,17 +386,20 @@ const complexTypesToCV: ComplexTypesToCV = { optional: (arg: ClarityValue | null) => arg ? optionalCVOf(arg) : optionalCVOf(undefined), response: (status: ResponseStatus, value: ClarityValue) => { - if (status === "ok") {return responseOkCV(value);} - else if (status === "error") {return responseErrorCV(value);} - else {throw new Error(`Unsupported response status: ${status}`);} + if (status === "ok") { + return responseOkCV(value); + } else if (status === "error") { + return responseErrorCV(value); + } else { + throw new Error(`Unsupported response status: ${status}`); + } }, trait_reference: (traitImplementation: string) => principalCV(traitImplementation), }; -const isBaseType = (type: EnrichedParameterType): type is EnrichedBaseType => ["int128", "uint128", "bool", "principal"].includes( - type as EnrichedBaseType - ); +const isBaseType = (type: EnrichedParameterType): type is EnrichedBaseType => + ["int128", "uint128", "bool", "principal"].includes(type as EnrichedBaseType); export const getContractNameFromContractId = (contractId: string): string => contractId.split(".")[1]; diff --git a/shared.types.ts b/shared.types.ts index 9519232d..e02412e9 100644 --- a/shared.types.ts +++ b/shared.types.ts @@ -43,7 +43,10 @@ export type EnrichedContractInterfaceFunction = { export type ResponseStatus = "ok" | "error"; -export type TupleData = Record; +export type TupleData = Record< + string, + T +>; export type BaseTypesToCV = { int128: (arg: number) => ReturnType; @@ -61,7 +64,7 @@ export type ComplexTypesToCV = { optional: (arg: ClarityValue | null) => ReturnType; response: ( status: ResponseStatus, - value: ClarityValue + value: ClarityValue, ) => ReturnType; trait_reference: (trait: string) => ReturnType; }; @@ -129,26 +132,26 @@ export type ComplexTypesToArbitrary = { type: EnrichedParameterType, length: number, addresses: string[], - projectTraitImplementations: Record + projectTraitImplementations: Record, ) => fc.Arbitrary; tuple: ( items: { name: string; type: EnrichedParameterType }[], addresses: string[], - projectTraitImplementations: Record + projectTraitImplementations: Record, ) => fc.Arbitrary; optional: ( type: EnrichedParameterType, addresses: string[], - projectTraitImplementations: Record + projectTraitImplementations: Record, ) => fc.Arbitrary; response: ( okType: EnrichedParameterType, errType: EnrichedParameterType, addresses: string[], - projectTraitImplementations: Record + projectTraitImplementations: Record, ) => fc.Arbitrary; trait_reference: ( traitData: ImportedTraitType, - projectTraitImplementations: Record + projectTraitImplementations: Record, ) => fc.Arbitrary; }; diff --git a/test.utils.ts b/test.utils.ts index ed41ef5d..4bd3b707 100644 --- a/test.utils.ts +++ b/test.utils.ts @@ -14,7 +14,7 @@ import { tmpdir } from "os"; */ export function createIsolatedTestEnvironment( manifestDir: string, - testPrefix: string + testPrefix: string, ): string { const tempDir = mkdtempSync(join(tmpdir(), testPrefix)); cpSync(manifestDir, tempDir, { recursive: true }); diff --git a/traits.tests.ts b/traits.tests.ts index 209ea142..28780d63 100644 --- a/traits.tests.ts +++ b/traits.tests.ts @@ -28,7 +28,7 @@ describe("Trait reference processing", () => { const expected = new Map( Object.entries({ "test-trait": { token: "trait_reference" }, - }) + }), ); // Act @@ -46,7 +46,7 @@ describe("Trait reference processing", () => { const expected = new Map( Object.entries({ "test-trait": { token: "trait_reference" }, - }) + }), ); // Act @@ -66,7 +66,7 @@ describe("Trait reference processing", () => { Object.entries({ "test-trait": { token: "trait_reference" }, "invariant-trait": { token: "trait_reference" }, - }) + }), ); // Act @@ -84,7 +84,7 @@ describe("Trait reference processing", () => { const expected = new Map( Object.entries({ "test-trait": { token: "trait_reference" }, - }) + }), ); // Act @@ -102,7 +102,7 @@ describe("Trait reference processing", () => { const expected = new Map( Object.entries({ "test-trait": { e: "trait_reference" }, - }) + }), ); // Act @@ -122,7 +122,7 @@ describe("Trait reference processing", () => { "test-trait": { "tuple-param": { tuple: { token: "trait_reference" } }, }, - }) + }), ); // Act @@ -142,7 +142,7 @@ describe("Trait reference processing", () => { "test-trait": { "token-list": { list: "trait_reference" }, }, - }) + }), ); // Act @@ -162,7 +162,7 @@ describe("Trait reference processing", () => { "test-trait": { "resp-trait-param": { response: { ok: "trait_reference" } }, }, - }) + }), ); // Act @@ -182,7 +182,7 @@ describe("Trait reference processing", () => { "test-trait": { "resp-trait-param": { response: { error: "trait_reference" } }, }, - }) + }), ); // Act @@ -204,7 +204,7 @@ describe("Trait reference processing", () => { response: { ok: "trait_reference", error: "trait_reference" }, }, }, - }) + }), ); // Act @@ -226,7 +226,7 @@ describe("Trait reference processing", () => { optional: "trait_reference", }, }, - }) + }), ); // Act @@ -248,7 +248,7 @@ describe("Trait reference processing", () => { list: { tuple: { "mad-inner": "trait_reference" } }, }, }, - }) + }), ); // Act @@ -276,7 +276,7 @@ describe("Trait reference processing", () => { }, }, }, - }) + }), ); // Act @@ -318,7 +318,7 @@ describe("Trait reference processing", () => { }, }, }, - }) + }), ); // Act @@ -358,7 +358,7 @@ describe("Trait reference processing", () => { }, }, }, - }) + }), ); // Act @@ -449,7 +449,7 @@ describe("Trait reference processing", () => { }, }, ], - }) + }), ); // Act @@ -457,7 +457,7 @@ describe("Trait reference processing", () => { ast, traitReferenceMap, allFunctionsInterfaces, - targetContractId + targetContractId, ); // Assert @@ -545,7 +545,7 @@ describe("Trait reference processing", () => { }, }, ], - }) + }), ); // Act @@ -553,7 +553,7 @@ describe("Trait reference processing", () => { ast, traitReferenceMap, allFunctionsInterfaces, - targetContractId + targetContractId, ); // Assert @@ -673,7 +673,7 @@ describe("Trait reference processing", () => { }, }, ], - }) + }), ); // Act @@ -681,7 +681,7 @@ describe("Trait reference processing", () => { ast, traitReferenceMap, allFunctionsInterfaces, - targetContractId + targetContractId, ); // Assert @@ -769,7 +769,7 @@ describe("Trait reference processing", () => { }, }, ], - }) + }), ); // Act @@ -777,7 +777,7 @@ describe("Trait reference processing", () => { ast, traitReferenceMap, allFunctionsInterfaces, - targetContractId + targetContractId, ); // Assert @@ -875,7 +875,7 @@ describe("Trait reference processing", () => { }, }, ], - }) + }), ); // Act @@ -883,7 +883,7 @@ describe("Trait reference processing", () => { ast, traitReferenceMap, allFunctionsInterfaces, - targetContractId + targetContractId, ); // Assert @@ -977,7 +977,7 @@ describe("Trait reference processing", () => { }, }, ], - }) + }), ); // Act @@ -985,7 +985,7 @@ describe("Trait reference processing", () => { ast, traitReferenceMap, allFunctionsInterfaces, - targetContractId + targetContractId, ); // Assert @@ -1097,7 +1097,7 @@ describe("Trait reference processing", () => { }, }, ], - }) + }), ); // Act @@ -1105,7 +1105,7 @@ describe("Trait reference processing", () => { ast, traitReferenceMap, allFunctionsInterfaces, - targetContractId + targetContractId, ); // Assert @@ -1197,7 +1197,7 @@ describe("Trait reference processing", () => { }, }, ], - }) + }), ); // Act @@ -1205,7 +1205,7 @@ describe("Trait reference processing", () => { ast, traitReferenceMap, allFunctionsInterfaces, - targetContractId + targetContractId, ); // Assert @@ -1297,7 +1297,7 @@ describe("Trait reference processing", () => { }, }, ], - }) + }), ); // Act @@ -1305,7 +1305,7 @@ describe("Trait reference processing", () => { ast, traitReferenceMap, allFunctionsInterfaces, - targetContractId + targetContractId, ); // Assert @@ -1397,7 +1397,7 @@ describe("Trait reference processing", () => { }, }, ], - }) + }), ); // Act @@ -1405,7 +1405,7 @@ describe("Trait reference processing", () => { ast, traitReferenceMap, allFunctionsInterfaces, - targetContractId + targetContractId, ); // Assert @@ -1517,7 +1517,7 @@ describe("Trait reference processing", () => { }, }, ], - }) + }), ); // Act @@ -1525,7 +1525,7 @@ describe("Trait reference processing", () => { ast, traitReferenceMap, allFunctionsInterfaces, - targetContractId + targetContractId, ); // Assert @@ -1614,7 +1614,7 @@ describe("Trait reference processing", () => { }, }, ], - }) + }), ); // Act @@ -1622,7 +1622,7 @@ describe("Trait reference processing", () => { ast, traitReferenceMap, allFunctionsInterfaces, - targetContractId + targetContractId, ); // Assert @@ -1709,7 +1709,7 @@ describe("Trait reference processing", () => { }, }, ], - }) + }), ); // Act @@ -1717,7 +1717,7 @@ describe("Trait reference processing", () => { ast, traitReferenceMap, allFunctionsInterfaces, - targetContractId + targetContractId, ); // Assert @@ -1863,7 +1863,7 @@ describe("Trait reference processing", () => { }, }, ], - }) + }), ); // Act @@ -1871,7 +1871,7 @@ describe("Trait reference processing", () => { ast, traitReferenceMap, allFunctionsInterfaces, - targetContractId + targetContractId, ); // Assert @@ -2055,7 +2055,7 @@ describe("Trait reference processing", () => { }, }, ], - }) + }), ); // Act @@ -2063,7 +2063,7 @@ describe("Trait reference processing", () => { ast, traitReferenceMap, allFunctionsInterfaces, - targetContractId + targetContractId, ); // Assert @@ -2243,7 +2243,7 @@ describe("Trait reference processing", () => { }, }, ], - }) + }), ); // Act @@ -2251,7 +2251,7 @@ describe("Trait reference processing", () => { ast, traitReferenceMap, allFunctionsInterfaces, - targetContractId + targetContractId, ); // Assert @@ -2262,7 +2262,7 @@ describe("Trait reference processing", () => { // Setup const tempDir = createIsolatedTestEnvironment( resolve(__dirname, "example"), - isolatedTestEnvPrefix + isolatedTestEnvPrefix, ); const simnet = await initSimnet(join(tempDir, "Clarinet.toml")); @@ -2295,7 +2295,7 @@ describe("Trait reference processing", () => { // Exercise const actual = new Set( - getContractIdsImplementingTrait(traitData, projectTraitImplementations) + getContractIdsImplementingTrait(traitData, projectTraitImplementations), ); // Verify @@ -2309,7 +2309,7 @@ describe("Trait reference processing", () => { // Setup const tempDir = createIsolatedTestEnvironment( resolve(__dirname, "example"), - isolatedTestEnvPrefix + isolatedTestEnvPrefix, ); const simnet = await initSimnet(join(tempDir, "Clarinet.toml")); @@ -2338,7 +2338,7 @@ describe("Trait reference processing", () => { // Exercise const actual = getContractIdsImplementingTrait( traitImplementationData, - projectTraitImplementations + projectTraitImplementations, ); // Verify @@ -2352,7 +2352,7 @@ describe("Trait reference processing", () => { // Setup const tempDir = createIsolatedTestEnvironment( resolve(__dirname, "example"), - isolatedTestEnvPrefix + isolatedTestEnvPrefix, ); const simnet = await initSimnet(join(tempDir, "Clarinet.toml")); @@ -2385,7 +2385,7 @@ describe("Trait reference processing", () => { // Exercise const actual = new Set( - getContractIdsImplementingTrait(traitData, projectTraitImplementations) + getContractIdsImplementingTrait(traitData, projectTraitImplementations), ); // Verify @@ -2855,7 +2855,7 @@ describe("Non-testable trait function filtering", () => { enrichedFunctionsInterfaces, traitReferenceMap, projectTraitImplementations, - contractId + contractId, ); // Assert diff --git a/traits.ts b/traits.ts index c1a89158..de09f346 100644 --- a/traits.ts +++ b/traits.ts @@ -32,7 +32,7 @@ export const enrichInterfaceWithTraitData = ( ast: IContractAST, traitReferenceMap: Map, functionInterfaceList: ContractInterfaceFunction[], - targetContractId: string + targetContractId: string, ): Map => { const enriched = new Map(); @@ -40,8 +40,9 @@ export const enrichInterfaceWithTraitData = ( args: any[], functionName: string, traitRefMapNode: any, - path: string[] = [] - ): any[] => args.map((arg) => { + path: string[] = [], + ): any[] => + args.map((arg) => { const listNested = !arg.name; const currentPath = listNested ? path : [...path, arg.name]; // Exit early if the traitReferenceMap does not have anything we are @@ -72,7 +73,7 @@ export const enrichInterfaceWithTraitData = ( listNested ? traitRefMapNode.tuple : traitRefMapNode[arg.name]?.tuple, - currentPath + currentPath, ), }, }; @@ -89,10 +90,10 @@ export const enrichInterfaceWithTraitData = ( arg.type.list.type.tuple ? [...currentPath, "tuple"] : arg.type.list.type.response - ? [...currentPath, "response"] - : arg.type.list.type.optional - ? [...currentPath, "optional"] - : [...currentPath, "list"] + ? [...currentPath, "response"] + : arg.type.list.type.optional + ? [...currentPath, "optional"] + : [...currentPath, "list"], )[0], }, }; @@ -108,7 +109,7 @@ export const enrichInterfaceWithTraitData = ( ? traitRefMapNode.response?.ok : traitRefMapNode[arg.name]?.response?.ok, }, - okPath + okPath, )[0]; const errorTraitReference = enrichArgs( [{ name: "error", type: arg.type.response.error }], @@ -118,7 +119,7 @@ export const enrichInterfaceWithTraitData = ( ? traitRefMapNode.response?.error : traitRefMapNode[arg.name]?.response?.error, }, - errorPath + errorPath, )[0]; return { ...arg, @@ -139,7 +140,7 @@ export const enrichInterfaceWithTraitData = ( ? traitRefMapNode.optional : traitRefMapNode[arg.name]?.optional, }, - optionalPath + optionalPath, )[0]; return { ...arg, @@ -152,7 +153,7 @@ export const enrichInterfaceWithTraitData = ( getTraitReferenceData( ast, functionName, - currentPath.filter((x) => x !== undefined) + currentPath.filter((x) => x !== undefined), ); if (traitReferenceName && traitReferenceImport) { return { @@ -185,9 +186,9 @@ export const enrichInterfaceWithTraitData = ( }); const enrichedFunctions = functionInterfaceList.map((f) => ({ - ...f, - args: enrichArgs(f.args, f.name, traitReferenceMap.get(f.name)), - })); + ...f, + args: enrichArgs(f.args, f.name, traitReferenceMap.get(f.name)), + })); enriched.set(targetContractId, enrichedFunctions); return enriched; @@ -209,7 +210,7 @@ export const enrichInterfaceWithTraitData = ( const getTraitReferenceData = ( ast: IContractAST, functionName: string, - parameterPath: string[] + parameterPath: string[], ): [string, ImportedTraitType] | [undefined, undefined] => { /** * Recursively searches for a trait reference import details in the contract @@ -225,7 +226,7 @@ const getTraitReferenceData = ( */ const findTraitReference = ( functionParameterNodes: any[], - path: string[] + path: string[], ): [string, ImportedTraitType] | [undefined, undefined] => { for (const parameterNode of functionParameterNodes) { // Check if the current parameter node is a trait reference in the first @@ -289,10 +290,12 @@ const getTraitReferenceData = ( // parameter list. const result = findTraitReference( (nestedParameterList as List).List, - path.slice(1) + path.slice(1), ); - if (result[0] !== undefined) {return result;} + if (result[0] !== undefined) { + return result; + } } } } @@ -318,7 +321,7 @@ const getTraitReferenceData = ( if ( !potentialFunctionDefinitionAtom || !["define-public", "define-read-only"].includes( - (potentialFunctionDefinitionAtom.expr as Atom).Atom.toString() + (potentialFunctionDefinitionAtom.expr as Atom).Atom.toString(), ) ) { continue; @@ -357,11 +360,12 @@ const getTraitReferenceData = ( const traitReferenceImportData = findTraitReference( functionParameterNodes, - parameterPath + parameterPath, ); - if (traitReferenceImportData[0] !== undefined) - {return traitReferenceImportData;} + if (traitReferenceImportData[0] !== undefined) { + return traitReferenceImportData; + } } return [undefined, undefined]; }; @@ -374,7 +378,7 @@ const getTraitReferenceData = ( * @returns The function names mapped to their trait reference parameter paths. */ export const buildTraitReferenceMap = ( - functionInterfaces: ContractInterfaceFunction[] + functionInterfaces: ContractInterfaceFunction[], ): Map => { const traitReferenceMap = new Map(); @@ -445,7 +449,7 @@ export const buildTraitReferenceMap = ( */ export const getContractIdsImplementingTrait = ( trait: ImportedTraitType | DefinedTraitType, - projectTraitImplementations: Record + projectTraitImplementations: Record, ): string[] => { const contracts = Object.keys(projectTraitImplementations); @@ -462,16 +466,16 @@ export const getContractIdsImplementingTrait = ( JSON.stringify(implementedTrait.contract_identifier.issuer) === JSON.stringify( (trait as ImportedTraitType).import.Imported?.contract_identifier - .issuer + .issuer, ) || JSON.stringify(implementedTrait.contract_identifier.issuer) === JSON.stringify( (trait as DefinedTraitType).import.Defined?.contract_identifier - .issuer + .issuer, ); return isTraitNamesMatch && isTraitIssuersMatch; - } + }, ); return traitImplemented; }); @@ -486,7 +490,7 @@ export const getContractIdsImplementingTrait = ( * otherwise. */ export const isTraitReferenceFunction = ( - fn: ContractInterfaceFunction + fn: ContractInterfaceFunction, ): boolean => { const hasTraitReference = (type: ParameterType): boolean => { if (typeof type === "string") { @@ -494,22 +498,32 @@ export const isTraitReferenceFunction = ( return type === "trait_reference"; } else { // The type is a complex type. - if ("buffer" in type) {return false;} - if ("string-ascii" in type) {return false;} - if ("string-utf8" in type) {return false;} - if ("list" in type) - {return hasTraitReference(type.list.type as ParameterType);} - if ("tuple" in type) - {return type.tuple.some((item) => - hasTraitReference(item.type as ParameterType) - );} - if ("optional" in type) - {return hasTraitReference(type.optional as ParameterType);} - if ("response" in type) - {return ( + if ("buffer" in type) { + return false; + } + if ("string-ascii" in type) { + return false; + } + if ("string-utf8" in type) { + return false; + } + if ("list" in type) { + return hasTraitReference(type.list.type as ParameterType); + } + if ("tuple" in type) { + return type.tuple.some((item) => + hasTraitReference(item.type as ParameterType), + ); + } + if ("optional" in type) { + return hasTraitReference(type.optional as ParameterType); + } + if ("response" in type) { + return ( hasTraitReference(type.response.ok as ParameterType) || hasTraitReference(type.response.error as ParameterType) - );} + ); + } // Default to false for unexpected types. return false; } @@ -566,15 +580,17 @@ export const getNonTestableTraitFunctions = ( enrichedFunctionsInterfaces: Map, traitReferenceMap: Map, projectTraitImplementations: Record, - contractId: string + contractId: string, ): string[] => { const hasTraitReferenceWithoutImplementation = (type: any): boolean => { - if (!type) {return false;} + if (!type) { + return false; + } if (typeof type === "object" && "trait_reference" in type) { const contractIdsImplementingTrait = getContractIdsImplementingTrait( type.trait_reference as ImportedTraitType | DefinedTraitType, - projectTraitImplementations + projectTraitImplementations, ); return contractIdsImplementingTrait.length === 0; } @@ -585,7 +601,7 @@ export const getNonTestableTraitFunctions = ( hasTraitReferenceWithoutImplementation(type.list.type)) || ("tuple" in type && type.tuple.some((item: any) => - hasTraitReferenceWithoutImplementation(item.type) + hasTraitReferenceWithoutImplementation(item.type), )) || ("optional" in type && hasTraitReferenceWithoutImplementation(type.optional)) || @@ -605,7 +621,7 @@ export const getNonTestableTraitFunctions = ( return ( enrichedFunctionInterface?.args.some((param) => - hasTraitReferenceWithoutImplementation(param.type) + hasTraitReferenceWithoutImplementation(param.type), ) ?? false ); }); From eac675c4f606b1e4b2f9c710e5ec01e6ec57c30e Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 6 Apr 2026 02:10:18 +0300 Subject: [PATCH 10/14] Add import ordering formatting plugin --- .prettierrc.json | 4 ++- app.tests.ts | 10 ++++--- app.ts | 28 +++++++++++--------- dialer.tests.ts | 5 ++-- dialer.ts | 5 ++-- dialer.types.ts | 1 + example/vitest.config.js | 4 +-- heatstroke.tests.ts | 12 +++++---- heatstroke.ts | 8 +++--- invariant.tests.ts | 24 ++++++++--------- invariant.ts | 38 ++++++++++++++------------- jest.config.js | 2 +- jest.setup.js | 4 +-- package-lock.json | 49 ++++++++++++++++++++++++++++++++++ package.json | 1 + persistence.tests.ts | 14 +++++----- persistence.ts | 5 ++-- property.tests.ts | 20 +++++++------- property.ts | 57 ++++++++++++++++++++-------------------- shared.tests.ts | 14 +++++----- shared.ts | 44 +++++++++++++++++-------------- shared.types.ts | 1 + test.utils.ts | 6 ++--- traits.tests.ts | 12 +++++---- traits.ts | 5 ++-- 25 files changed, 227 insertions(+), 146 deletions(-) diff --git a/.prettierrc.json b/.prettierrc.json index de42adfe..0022644e 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -3,5 +3,7 @@ "singleQuote": false, "tabWidth": 2, "trailingComma": "all", - "printWidth": 80 + "printWidth": 80, + "plugins": ["@ianvs/prettier-plugin-sort-imports"], + "importOrder": ["", "", "", "", "^[.]"] } diff --git a/app.tests.ts b/app.tests.ts index 949bec8e..fe36149c 100644 --- a/app.tests.ts +++ b/app.tests.ts @@ -1,11 +1,13 @@ +import fs, { rmSync } from "node:fs"; +import { resolve } from "node:path"; + import { red } from "ansicolor"; + import { getManifestFileName, main } from "./app"; import { version } from "./package.json"; -import { resolve } from "path"; -import fs, { rmSync } from "fs"; -import { createIsolatedTestEnvironment } from "./test.utils"; -import { LOG_DIVIDER } from "./shared"; import { getFailureFilePath } from "./persistence"; +import { LOG_DIVIDER } from "./shared"; +import { createIsolatedTestEnvironment } from "./test.utils"; const isolatedTestEnvPrefix = "rendezvous-test-app-"; diff --git a/app.ts b/app.ts index 49c18c8b..1cbabd9e 100644 --- a/app.ts +++ b/app.ts @@ -1,19 +1,21 @@ #!/usr/bin/env node -import { join, resolve } from "path"; -import { EventEmitter } from "events"; -import { checkProperties } from "./property"; +import { EventEmitter } from "node:events"; +import { existsSync } from "node:fs"; +import { join, resolve } from "node:path"; +import { parseArgs } from "node:util"; + +import { initSimnet } from "@stacks/clarinet-sdk"; +import { red } from "ansicolor"; + import { checkInvariants } from "./invariant"; +import { version } from "./package.json"; +import { checkProperties } from "./property"; import { getContractNameFromContractId, getFunctionsFromContractInterfaces, getSimnetDeployerContractsInterfaces, LOG_DIVIDER, } from "./shared"; -import { version } from "./package.json"; -import { red } from "ansicolor"; -import { existsSync } from "fs"; -import { parseArgs } from "util"; -import { initSimnet } from "@stacks/clarinet-sdk"; const logger = (log: string, logLevel: "log" | "error" | "info" = "log") => { console[logLevel](log); @@ -187,9 +189,9 @@ export async function main() { /** * The list of contract IDs for the SUT contract names, as per the simnet. */ - const rendezvousList = Array.from( - getSimnetDeployerContractsInterfaces(simnet).keys(), - ).filter( + const rendezvousList = [ + ...getSimnetDeployerContractsInterfaces(simnet).keys(), + ].filter( (deployedContract) => getContractNameFromContractId(deployedContract) === runConfig.sutContractName, @@ -205,8 +207,8 @@ export async function main() { const rendezvousAllFunctions = getFunctionsFromContractInterfaces( new Map( - Array.from(getSimnetDeployerContractsInterfaces(simnet)).filter( - ([contractId]) => rendezvousList.includes(contractId), + [...getSimnetDeployerContractsInterfaces(simnet)].filter(([contractId]) => + rendezvousList.includes(contractId), ), ), ); diff --git a/dialer.tests.ts b/dialer.tests.ts index 8dfbe8c9..79b24396 100644 --- a/dialer.tests.ts +++ b/dialer.tests.ts @@ -1,6 +1,7 @@ -import { join } from "path"; -import type { DialerContext } from "./dialer.types"; +import { join } from "node:path"; + import { DialerRegistry } from "./dialer"; +import type { DialerContext } from "./dialer.types"; const dialPath = join("example", "dialer.ts"); diff --git a/dialer.ts b/dialer.ts index dea5a465..fec96735 100644 --- a/dialer.ts +++ b/dialer.ts @@ -1,5 +1,6 @@ -import { existsSync } from "fs"; -import { resolve } from "path"; +import { existsSync } from "node:fs"; +import { resolve } from "node:path"; + import type { Dialer, DialerContext } from "./dialer.types"; // In telephony, a registry is used for maintaining a known set of handlers, diff --git a/dialer.types.ts b/dialer.types.ts index 3bf77b8b..4219a5c2 100644 --- a/dialer.types.ts +++ b/dialer.types.ts @@ -1,5 +1,6 @@ import type { ParsedTransactionResult } from "@stacks/clarinet-sdk"; import type { ClarityValue } from "@stacks/transactions"; + import type { EnrichedContractInterfaceFunction } from "./shared.types"; export type Dialer = (context: DialerContext) => Promise | void; diff --git a/example/vitest.config.js b/example/vitest.config.js index 2a127315..beb68571 100644 --- a/example/vitest.config.js +++ b/example/vitest.config.js @@ -1,10 +1,10 @@ /// -import { defineConfig } from "vite"; import { - vitestSetupFilePath, getClarinetVitestsArgv, + vitestSetupFilePath, } from "@stacks/clarinet-sdk/vitest"; +import { defineConfig } from "vite"; export default defineConfig({ test: { diff --git a/heatstroke.tests.ts b/heatstroke.tests.ts index 63fcc31f..bfae9927 100644 --- a/heatstroke.tests.ts +++ b/heatstroke.tests.ts @@ -1,14 +1,16 @@ -import { EventEmitter } from "events"; -import { rmSync } from "fs"; -import { join, resolve } from "path"; +import { EventEmitter } from "node:events"; +import { rmSync } from "node:fs"; +import { join, resolve } from "node:path"; + import { initSimnet } from "@stacks/clarinet-sdk"; import type { ContractInterfaceFunction } from "@stacks/clarinet-sdk-wasm"; import fc from "fast-check"; + import { reporter } from "./heatstroke"; +import { FalsifiedInvariantError } from "./invariant"; +import { PropertyTestError } from "./property"; import { getContractNameFromContractId } from "./shared"; import { createIsolatedTestEnvironment } from "./test.utils"; -import { PropertyTestError } from "./property"; -import { FalsifiedInvariantError } from "./invariant"; const isolatedTestEnvPrefix = "rendezvous-test-heatstroke-"; diff --git a/heatstroke.ts b/heatstroke.ts index 311b6bad..9dd95851 100644 --- a/heatstroke.ts +++ b/heatstroke.ts @@ -1,6 +1,8 @@ -import type { EventEmitter } from "events"; +import type { EventEmitter } from "node:events"; + import type { ContractInterfaceFunction } from "@stacks/clarinet-sdk-wasm"; import { green } from "ansicolor"; + import type { InvariantCounterExample, RunDetails, @@ -8,9 +10,9 @@ import type { StatisticsTreeOptions, TestCounterExample, } from "./heatstroke.types"; -import { getContractNameFromContractId } from "./shared"; -import type { PropertyTestError } from "./property"; import type { FalsifiedInvariantError } from "./invariant"; +import type { PropertyTestError } from "./property"; +import { getContractNameFromContractId } from "./shared"; /** * Heatstrokes Reporter diff --git a/invariant.tests.ts b/invariant.tests.ts index 00fc9d7c..de6875f9 100644 --- a/invariant.tests.ts +++ b/invariant.tests.ts @@ -1,13 +1,15 @@ +import { rmSync } from "node:fs"; +import { join, resolve } from "node:path"; + import { initSimnet } from "@stacks/clarinet-sdk"; +import { Cl } from "@stacks/transactions"; + import { initializeClarityContext, initializeLocalContext } from "./invariant"; import { getContractNameFromContractId, getFunctionsFromContractInterfaces, getSimnetDeployerContractsInterfaces, } from "./shared"; -import { join, resolve } from "path"; -import { rmSync } from "fs"; -import { Cl } from "@stacks/transactions"; import { createIsolatedTestEnvironment } from "./test.utils"; const isolatedTestEnvPrefix = "rendezvous-test-invariant-"; @@ -27,9 +29,7 @@ describe("Simnet contracts operations", () => { ); // Pick the first contract for testing. - const [contractId, functions] = Array.from( - sutContractsAllFunctions.entries(), - )[0]; + const [contractId, functions] = [...sutContractsAllFunctions.entries()][0]; const expectedInitialContext = { [contractId]: Object.fromEntries(functions.map((f) => [f.name, 0])), @@ -53,24 +53,22 @@ describe("Simnet contracts operations", () => { ); const simnet = await initSimnet(join(tempDir, "Clarinet.toml")); - const rendezvousList = Array.from( - getSimnetDeployerContractsInterfaces(simnet).keys(), - ).filter((deployedContract) => + const rendezvousList = [ + ...getSimnetDeployerContractsInterfaces(simnet).keys(), + ].filter((deployedContract) => ["counter"].includes(getContractNameFromContractId(deployedContract)), ); const rendezvousAllFunctions = getFunctionsFromContractInterfaces( new Map( - Array.from(getSimnetDeployerContractsInterfaces(simnet)).filter( + [...getSimnetDeployerContractsInterfaces(simnet)].filter( ([contractId]) => rendezvousList.includes(contractId), ), ), ); // Pick the first contract for testing. - const [contractId, functions] = Array.from( - rendezvousAllFunctions.entries(), - )[0]; + const [contractId, functions] = [...rendezvousAllFunctions.entries()][0]; // Exercise initializeClarityContext(simnet, contractId, functions); diff --git a/invariant.ts b/invariant.ts index 422663bd..7da44091 100644 --- a/invariant.ts +++ b/invariant.ts @@ -1,5 +1,21 @@ +import type { EventEmitter } from "node:events"; +import { resolve } from "node:path"; + import type { Simnet } from "@stacks/clarinet-sdk"; -import type { EventEmitter } from "events"; +import type { ContractInterfaceFunction } from "@stacks/clarinet-sdk-wasm"; +import { Cl, cvToJSON, cvToString } from "@stacks/transactions"; +import { dim, green, red, underline, yellow } from "ansicolor"; +import fc from "fast-check"; + +import { DialerRegistry, PostDialerError, PreDialerError } from "./dialer"; +import { reporter } from "./heatstroke"; +import type { Statistics } from "./heatstroke.types"; +import type { LocalContext } from "./invariant.types"; +import { + getFailureFilePath, + loadFailures, + persistFailure, +} from "./persistence"; import { argsToCV, functionToArbitrary, @@ -7,28 +23,14 @@ import { getFunctionsListForContract, LOG_DIVIDER, } from "./shared"; -import type { LocalContext } from "./invariant.types"; -import { Cl, cvToJSON, cvToString } from "@stacks/transactions"; -import { reporter } from "./heatstroke"; -import fc from "fast-check"; -import { dim, green, red, underline, yellow } from "ansicolor"; -import type { ContractInterfaceFunction } from "@stacks/clarinet-sdk-wasm"; +import type { EnrichedContractInterfaceFunction } from "./shared.types"; import { buildTraitReferenceMap, enrichInterfaceWithTraitData, extractProjectTraitImplementations, - isTraitReferenceFunction, getNonTestableTraitFunctions, + isTraitReferenceFunction, } from "./traits"; -import type { EnrichedContractInterfaceFunction } from "./shared.types"; -import { DialerRegistry, PostDialerError, PreDialerError } from "./dialer"; -import type { Statistics } from "./heatstroke.types"; -import { - getFailureFilePath, - loadFailures, - persistFailure, -} from "./persistence"; -import { resolve } from "path"; import type { ImplementedTraitType } from "./traits.types"; /** @@ -319,7 +321,7 @@ const invariantTest = async ( const eligibleAccounts = new Map( [...simnetAccounts].filter(([key]) => key !== "faucet"), ); - const simnetAddresses = Array.from(simnetAccounts.values()); + const simnetAddresses = [...simnetAccounts.values()]; /** * The dialer registry, which is used to keep track of all the custom dialers diff --git a/jest.config.js b/jest.config.js index 29a2262b..8bab1329 100644 --- a/jest.config.js +++ b/jest.config.js @@ -8,7 +8,7 @@ module.exports = { silent: true, modulePathIgnorePatterns: ["/dist/"], setupFilesAfterEnv: ["/jest.setup.js"], - testTimeout: 600000, // 10 minutes + testTimeout: 600_000, // 10 minutes maxWorkers: 1, collectCoverage: false, collectCoverageFrom: [ diff --git a/jest.setup.js b/jest.setup.js index a45b041a..f3d1078c 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,9 +1,9 @@ const { initSimnet } = require("@stacks/clarinet-sdk"); -const { join, resolve } = require("path"); +const { join, resolve } = require("node:path"); // Ensure that the Clarinet project cache and deployment plan are initialized // before all the tests run. beforeAll(async () => { const manifestPath = join(resolve(__dirname, "example"), "Clarinet.toml"); await initSimnet(manifestPath); -}, 30000); +}, 30_000); diff --git a/package-lock.json b/package-lock.json index 19bfefee..987f5d36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "rv": "dist/app.js" }, "devDependencies": { + "@ianvs/prettier-plugin-sort-imports": "^4.7.1", "@stacks/clarinet-sdk-wasm": "^3.15.0", "@types/jest": "^30.0.0", "jest": "^30.2.0", @@ -557,6 +558,54 @@ "tslib": "^2.4.0" } }, + "node_modules/@ianvs/prettier-plugin-sort-imports": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/@ianvs/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.7.1.tgz", + "integrity": "sha512-jmTNYGlg95tlsoG3JLCcuC4BrFELJtLirLAkQW/71lXSyOhVt/Xj7xWbbGcuVbNq1gwWgSyMrPjJc9Z30hynVw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/generator": "^7.26.2", + "@babel/parser": "^7.26.2", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "semver": "^7.5.2" + }, + "peerDependencies": { + "@prettier/plugin-oxc": "^0.0.4 || ^0.1.0", + "@vue/compiler-sfc": "2.7.x || 3.x", + "content-tag": "^4.0.0", + "prettier": "2 || 3 || ^4.0.0-0", + "prettier-plugin-ember-template-tag": "^2.1.0" + }, + "peerDependenciesMeta": { + "@prettier/plugin-oxc": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "content-tag": { + "optional": true + }, + "prettier-plugin-ember-template-tag": { + "optional": true + } + } + }, + "node_modules/@ianvs/prettier-plugin-sort-imports/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", diff --git a/package.json b/package.json index ca88535d..0849c533 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "fast-check": "^4.5.3" }, "devDependencies": { + "@ianvs/prettier-plugin-sort-imports": "^4.7.1", "@stacks/clarinet-sdk-wasm": "^3.15.0", "@types/jest": "^30.0.0", "jest": "^30.2.0", diff --git a/persistence.tests.ts b/persistence.tests.ts index 0c9b94d1..508508f3 100644 --- a/persistence.tests.ts +++ b/persistence.tests.ts @@ -1,13 +1,15 @@ -import { mkdirSync, rmSync, statSync } from "fs"; -import { tmpdir } from "os"; -import { join, resolve } from "path"; +import { mkdirSync, rmSync, statSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; + +import fc from "fast-check"; + +import type { RunDetails } from "./heatstroke.types"; import { getFailureFilePath, - persistFailure, loadFailures, + persistFailure, } from "./persistence"; -import type { RunDetails } from "./heatstroke.types"; -import fc from "fast-check"; const temporaryTestBaseDir = resolve(tmpdir(), "rendezvous-test-persistence"); diff --git a/persistence.ts b/persistence.ts index be927cb3..a7d09566 100644 --- a/persistence.ts +++ b/persistence.ts @@ -1,5 +1,6 @@ -import { mkdirSync, readFileSync, writeFileSync } from "fs"; -import { resolve } from "path"; +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { resolve } from "node:path"; + import type { RunDetails } from "./heatstroke.types"; /** diff --git a/property.tests.ts b/property.tests.ts index 4594655a..ae7142ff 100644 --- a/property.tests.ts +++ b/property.tests.ts @@ -1,13 +1,7 @@ +import { rmSync } from "node:fs"; +import { join, resolve } from "node:path"; + import { initSimnet } from "@stacks/clarinet-sdk"; -import { - isParamsMatch, - isReturnTypeBoolean, - isTestDiscardedInPlace, -} from "./property"; -import { rmSync } from "fs"; -import { join, resolve } from "path"; -import fc from "fast-check"; -import { createIsolatedTestEnvironment } from "./test.utils"; import type { ContractInterfaceFunction, ContractInterfaceFunctionAccess, @@ -15,6 +9,14 @@ import type { ContractInterfaceFunctionOutput, } from "@stacks/clarinet-sdk-wasm"; import { cvToJSON } from "@stacks/transactions"; +import fc from "fast-check"; + +import { + isParamsMatch, + isReturnTypeBoolean, + isTestDiscardedInPlace, +} from "./property"; +import { createIsolatedTestEnvironment } from "./test.utils"; const isolatedTestEnvPrefix = "rendezvous-test-property-"; diff --git a/property.ts b/property.ts index 98ee4c56..535dbafa 100644 --- a/property.ts +++ b/property.ts @@ -1,9 +1,19 @@ +import type { EventEmitter } from "node:events"; +import { resolve } from "node:path"; + import type { Simnet } from "@stacks/clarinet-sdk"; -import type { EventEmitter } from "events"; -import fc from "fast-check"; +import type { ContractInterfaceFunction } from "@stacks/clarinet-sdk-wasm"; import { cvToJSON, cvToString } from "@stacks/transactions"; +import { dim, green, red, underline, yellow } from "ansicolor"; +import fc from "fast-check"; + import { reporter } from "./heatstroke"; import type { Statistics } from "./heatstroke.types"; +import { + getFailureFilePath, + loadFailures, + persistFailure, +} from "./persistence"; import { argsToCV, functionToArbitrary, @@ -11,23 +21,15 @@ import { getFunctionsListForContract, LOG_DIVIDER, } from "./shared"; -import { dim, green, red, underline, yellow } from "ansicolor"; -import type { ContractInterfaceFunction } from "@stacks/clarinet-sdk-wasm"; +import type { EnrichedContractInterfaceFunction } from "./shared.types"; import { buildTraitReferenceMap, enrichInterfaceWithTraitData, extractProjectTraitImplementations, - isTraitReferenceFunction, getNonTestableTraitFunctions, + isTraitReferenceFunction, } from "./traits"; -import { - getFailureFilePath, - loadFailures, - persistFailure, -} from "./persistence"; -import { resolve } from "path"; import type { ImplementedTraitType } from "./traits.types"; -import type { EnrichedContractInterfaceFunction } from "./shared.types"; /** * Runs property-based tests on the target contract and logs the progress. @@ -156,21 +158,20 @@ export const checkProperties = async ( ), ); - const hasDiscardFunctionErrors = Array.from( - testContractsPairedFunctions, - ).some(([contractId, pairedMap]) => - Array.from(pairedMap).some(([testFunctionName, discardFunctionName]) => - discardFunctionName - ? !validateDiscardFunction( - contractId, - discardFunctionName, - testFunctionName, - testContractsDiscardFunctions, - testContractsTestFunctions, - radio, - ) - : false, - ), + const hasDiscardFunctionErrors = [...testContractsPairedFunctions].some( + ([contractId, pairedMap]) => + [...pairedMap].some(([testFunctionName, discardFunctionName]) => + discardFunctionName + ? !validateDiscardFunction( + contractId, + discardFunctionName, + testFunctionName, + testContractsDiscardFunctions, + testContractsTestFunctions, + radio, + ) + : false, + ), ); if (hasDiscardFunctionErrors) { @@ -315,7 +316,7 @@ const propertyTest = async ( const eligibleAccounts = new Map( [...simnetAccounts].filter(([key]) => key !== "faucet"), ); - const simnetAddresses = Array.from(simnetAccounts.values()); + const simnetAddresses = [...simnetAccounts.values()]; const statistics: Statistics = { test: { diff --git a/shared.tests.ts b/shared.tests.ts index e8f03e67..da12b2ba 100644 --- a/shared.tests.ts +++ b/shared.tests.ts @@ -1,13 +1,15 @@ +import { rmSync } from "node:fs"; +import { join, resolve } from "node:path"; + import { initSimnet } from "@stacks/clarinet-sdk"; +import fc from "fast-check"; + import { + getContractNameFromContractId, getFunctionsFromContractInterfaces, getFunctionsListForContract, getSimnetDeployerContractsInterfaces, - getContractNameFromContractId, } from "./shared"; -import { rmSync } from "fs"; -import { join, resolve } from "path"; -import fc from "fast-check"; import { createIsolatedTestEnvironment } from "./test.utils"; const isolatedTestEnvPrefix = "rendezvous-test-shared-"; @@ -22,7 +24,7 @@ describe("Simnet contracts operations", () => { const manifestPath = join(tempDir, "Clarinet.toml"); const simnet = await initSimnet(manifestPath); const expectedDeployerContracts = new Map( - Array.from(simnet.getContractsInterfaces()).filter( + [...simnet.getContractsInterfaces()].filter( ([key]) => key.split(".")[0] === simnet.deployer, ), ); @@ -47,7 +49,7 @@ describe("Simnet contracts operations", () => { const manifestPath = join(tempDir, "Clarinet.toml"); const simnet = await initSimnet(manifestPath); const sutContractsInterfaces = getSimnetDeployerContractsInterfaces(simnet); - const sutContractsList = Array.from(sutContractsInterfaces.keys()); + const sutContractsList = [...sutContractsInterfaces.keys()]; const allFunctionsMap = new Map( Array.from(sutContractsInterfaces, ([contractId, contractInterface]) => [ contractId, diff --git a/shared.ts b/shared.ts index c0830fc6..7867fb73 100644 --- a/shared.ts +++ b/shared.ts @@ -1,28 +1,29 @@ -import fc from "fast-check"; -import type { - BaseTypesToArbitrary, - BaseTypesToCV, - ComplexTypesToArbitrary, - ComplexTypesToCV, - EnrichedBaseType, - EnrichedContractInterfaceFunction, - EnrichedParameterType, - ResponseStatus, - TupleData, -} from "./shared.types"; import type { Simnet } from "@stacks/clarinet-sdk"; import type { ContractInterfaceFunction, IContractInterface, } from "@stacks/clarinet-sdk-wasm"; import { - type ClarityValue, Cl, optionalCVOf, principalCV, responseErrorCV, responseOkCV, + type ClarityValue, } from "@stacks/transactions"; +import fc from "fast-check"; + +import type { + BaseTypesToArbitrary, + BaseTypesToCV, + ComplexTypesToArbitrary, + ComplexTypesToCV, + EnrichedBaseType, + EnrichedContractInterfaceFunction, + EnrichedParameterType, + ResponseStatus, + TupleData, +} from "./shared.types"; import { getContractIdsImplementingTrait } from "./traits"; import type { ImplementedTraitType, ImportedTraitType } from "./traits.types"; @@ -40,7 +41,7 @@ export const getSimnetDeployerContractsInterfaces = ( simnet: Simnet, ): Map => new Map( - Array.from(simnet.getContractsInterfaces()).filter( + [...simnet.getContractsInterfaces()].filter( ([key]) => key.split(".")[0] === simnet.deployer, ), ); @@ -310,16 +311,21 @@ const argToCV = ( if (isBaseType(type)) { // Base type. switch (type) { - case "int128": + case "int128": { return baseTypesToCV.int128(generatedArgument as number); - case "uint128": + } + case "uint128": { return baseTypesToCV.uint128(generatedArgument as number); - case "bool": + } + case "bool": { return baseTypesToCV.bool(generatedArgument as boolean); - case "principal": + } + case "principal": { return baseTypesToCV.principal(generatedArgument as string); - default: + } + default: { throw new Error(`Unsupported base parameter type: ${type}`); + } } } else { // Complex type. diff --git a/shared.types.ts b/shared.types.ts index e02412e9..7a9e4c12 100644 --- a/shared.types.ts +++ b/shared.types.ts @@ -19,6 +19,7 @@ import type { uintCV, } from "@stacks/transactions"; import type fc from "fast-check"; + import type { ImplementedTraitType, ImportedTraitType } from "./traits.types"; // Types used for Clarity Value conversion. diff --git a/test.utils.ts b/test.utils.ts index 4bd3b707..0e16e1a4 100644 --- a/test.utils.ts +++ b/test.utils.ts @@ -1,6 +1,6 @@ -import { join } from "path"; -import { mkdtempSync, cpSync } from "fs"; -import { tmpdir } from "os"; +import { cpSync, mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; /** * Creates an isolated test environment by copying the Clarinet project to a diff --git a/traits.tests.ts b/traits.tests.ts index 28780d63..ee2bfa59 100644 --- a/traits.tests.ts +++ b/traits.tests.ts @@ -1,10 +1,14 @@ +import { rmSync } from "node:fs"; +import { join, resolve } from "node:path"; + +import { initSimnet } from "@stacks/clarinet-sdk"; import type { ContractInterfaceFunction, IContractAST, } from "@stacks/clarinet-sdk-wasm"; -import { initSimnet } from "@stacks/clarinet-sdk"; -import { rmSync } from "fs"; -import { join, resolve } from "path"; + +import type { EnrichedContractInterfaceFunction } from "./shared.types"; +import { createIsolatedTestEnvironment } from "./test.utils"; import { buildTraitReferenceMap, enrichInterfaceWithTraitData, @@ -13,8 +17,6 @@ import { getNonTestableTraitFunctions, isTraitReferenceFunction, } from "./traits"; -import { createIsolatedTestEnvironment } from "./test.utils"; -import type { EnrichedContractInterfaceFunction } from "./shared.types"; import type { ImplementedTraitType } from "./traits.types"; const isolatedTestEnvPrefix = "rendezvous-test-traits-"; diff --git a/traits.ts b/traits.ts index de09f346..07b54eec 100644 --- a/traits.ts +++ b/traits.ts @@ -1,3 +1,4 @@ +import type { Simnet } from "@stacks/clarinet-sdk"; import type { Atom, ContractInterfaceFunction, @@ -5,11 +6,11 @@ import type { List, TraitReference, } from "@stacks/clarinet-sdk-wasm"; + import type { EnrichedContractInterfaceFunction, ParameterType, } from "./shared.types"; -import type { Simnet } from "@stacks/clarinet-sdk"; import type { DefinedTraitType, ImplementedTraitType, @@ -614,7 +615,7 @@ export const getNonTestableTraitFunctions = ( return false; }; - return Array.from(traitReferenceMap.keys()).filter((functionName) => { + return [...traitReferenceMap.keys()].filter((functionName) => { const enrichedFunctionInterface = enrichedFunctionsInterfaces .get(contractId) ?.find((f) => f.name === functionName); From fab924a79c7631034911ff3a4f06f816e8e44537 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 6 Apr 2026 16:18:32 +0300 Subject: [PATCH 11/14] Turn `consistent-type-definitions` rule on and fix --- dialer.types.ts | 4 ++-- heatstroke.types.ts | 28 ++++++++++++++-------------- invariant.types.ts | 4 ++-- oxlintrc.json | 2 -- shared.types.ts | 24 ++++++++++++------------ traits.types.ts | 16 ++++++++-------- 6 files changed, 38 insertions(+), 40 deletions(-) diff --git a/dialer.types.ts b/dialer.types.ts index 4219a5c2..28cc7272 100644 --- a/dialer.types.ts +++ b/dialer.types.ts @@ -5,8 +5,8 @@ import type { EnrichedContractInterfaceFunction } from "./shared.types"; export type Dialer = (context: DialerContext) => Promise | void; -export type DialerContext = { +export interface DialerContext { clarityValueArguments: ClarityValue[]; functionCall: ParsedTransactionResult | undefined; selectedFunction: EnrichedContractInterfaceFunction; -}; +} diff --git a/heatstroke.types.ts b/heatstroke.types.ts index 5e5776f6..6cd73c00 100644 --- a/heatstroke.types.ts +++ b/heatstroke.types.ts @@ -1,6 +1,6 @@ import type { ContractInterfaceFunction } from "@stacks/clarinet-sdk-wasm"; -export type RunDetails = { +export interface RunDetails { failed: boolean; counterexample: CounterExample[]; numRuns: number; @@ -8,18 +8,18 @@ export type RunDetails = { path?: string; error?: Error; errorInstance?: Error; -}; +} type CounterExample = TestCounterExample | InvariantCounterExample; -export type TestCounterExample = { +export interface TestCounterExample { rendezvousContractId: string; selectedTestFunction: ContractInterfaceFunction; functionArgs: any; testCaller: [string, string]; -}; +} -export type InvariantCounterExample = { +export interface InvariantCounterExample { rendezvousContractId: string; selectedFunctions: ContractInterfaceFunction[]; selectedFunctionsArgsList: any[]; @@ -27,29 +27,29 @@ export type InvariantCounterExample = { selectedInvariant: ContractInterfaceFunction; invariantArgs: any; invariantCaller: [string, string]; -}; +} -type SutFunctionStatistics = { +interface SutFunctionStatistics { successful: Map; failed: Map; -}; +} -type InvariantFunctionStatistics = { +interface InvariantFunctionStatistics { successful: Map; failed: Map; -}; +} -type TestFunctionStatistics = { +interface TestFunctionStatistics { successful: Map; discarded: Map; failed: Map; -}; +} -export type Statistics = { +export interface Statistics { sut?: SutFunctionStatistics; invariant?: InvariantFunctionStatistics; test?: TestFunctionStatistics; -}; +} /** * Options for configuring tree statistics reporting. diff --git a/invariant.types.ts b/invariant.types.ts index 5caffaa6..aef11968 100644 --- a/invariant.types.ts +++ b/invariant.types.ts @@ -5,6 +5,6 @@ * - The inner key is the SUT function name within the contract. * - The value is the count of times the SUT function has been invoked. */ -export type LocalContext = { +export interface LocalContext { [contractId: string]: Record; -}; +} diff --git a/oxlintrc.json b/oxlintrc.json index 1e88ff12..a2e81d77 100644 --- a/oxlintrc.json +++ b/oxlintrc.json @@ -7,9 +7,7 @@ "capitalized-comments": "off", "id-length": "off", "max-params": "off", - "max-lines-per-function": "off", "max-statements": "off", - "consistent-type-definitions": "off", "func-style": "off", "prefer-destructuring": "off", "no-ternary": "off", diff --git a/shared.types.ts b/shared.types.ts index 7a9e4c12..28fb992d 100644 --- a/shared.types.ts +++ b/shared.types.ts @@ -24,23 +24,23 @@ import type { ImplementedTraitType, ImportedTraitType } from "./traits.types"; // Types used for Clarity Value conversion. -type ImportedTraitReferenceFunctionArg = { +interface ImportedTraitReferenceFunctionArg { type: { trait_reference: ImportedTraitType; }; name: string; -}; +} /** * The type of the function interface, after the contract interface is * "enriched" with additional information about trait references. */ -export type EnrichedContractInterfaceFunction = { +export interface EnrichedContractInterfaceFunction { args: (ContractInterfaceFunctionArg | ImportedTraitReferenceFunctionArg)[]; name: string; access: ContractInterfaceFunctionAccess; outputs: ContractInterfaceFunctionOutput; -}; +} export type ResponseStatus = "ok" | "error"; @@ -49,14 +49,14 @@ export type TupleData = Record< T >; -export type BaseTypesToCV = { +export interface BaseTypesToCV { int128: (arg: number) => ReturnType; uint128: (arg: number) => ReturnType; bool: (arg: boolean) => ReturnType; principal: (arg: string) => ReturnType; -}; +} -export type ComplexTypesToCV = { +export interface ComplexTypesToCV { buffer: (arg: string) => ReturnType; "string-ascii": (arg: string) => ReturnType; "string-utf8": (arg: string) => ReturnType; @@ -68,7 +68,7 @@ export type ComplexTypesToCV = { value: ClarityValue, ) => ReturnType; trait_reference: (trait: string) => ReturnType; -}; +} // Types used for argument generation. @@ -118,14 +118,14 @@ export type ParameterType = BaseType | ComplexType; /** The Clarity parameter types after the contract interface is "enriched". */ export type EnrichedParameterType = EnrichedBaseType | EnrichedComplexType; -export type BaseTypesToArbitrary = { +export interface BaseTypesToArbitrary { int128: ReturnType; uint128: ReturnType; bool: ReturnType; principal: (addresses: string[]) => ReturnType; -}; +} -export type ComplexTypesToArbitrary = { +export interface ComplexTypesToArbitrary { buffer: (length: number) => fc.Arbitrary; "string-ascii": (length: number) => fc.Arbitrary; "string-utf8": (length: number) => fc.Arbitrary; @@ -155,4 +155,4 @@ export type ComplexTypesToArbitrary = { traitData: ImportedTraitType, projectTraitImplementations: Record, ) => fc.Arbitrary; -}; +} diff --git a/traits.types.ts b/traits.types.ts index 88a21642..f93b7f76 100644 --- a/traits.types.ts +++ b/traits.types.ts @@ -2,36 +2,36 @@ * The trait reference structure, as it appears in the AST under the * `implemented_traits` field. */ -export type ImplementedTraitType = { +export interface ImplementedTraitType { name: string; contract_identifier: { issuer: number[]; name: string }; -}; +} /** * The imported trait reference structure, as it appears in the AST under a * `TraitReference` node. Used to represent the `trait_reference` data for an * imported trait `(use-trait .)`. */ -export type ImportedTraitType = { +export interface ImportedTraitType { name: string; import: { Imported: TraitData; }; -}; +} /** * The defined trait reference structure, as it appears in the AST under a * `TraitReference` node. Used to represent the `trait_reference` data for a * defined trait `(define-trait ())`. */ -export type DefinedTraitType = { +export interface DefinedTraitType { name: string; import: { Defined: TraitData; }; -}; +} -type TraitData = { +interface TraitData { name: string; contract_identifier: { issuer: any[]; name: string }; -}; +} From 9591594335fd6ab445244122210e290beaed9bdd Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 6 Apr 2026 17:14:53 +0300 Subject: [PATCH 12/14] Turn `func-style` rule on and fix --- app.ts | 4 ++-- heatstroke.ts | 18 +++++++++--------- invariant.ts | 6 +++--- oxlintrc.json | 1 - test.utils.ts | 6 +++--- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/app.ts b/app.ts index 1cbabd9e..31c670aa 100644 --- a/app.ts +++ b/app.ts @@ -65,7 +65,7 @@ const helpMessage = ` Learn more: https://stacks-network.github.io/rendezvous/ `; -export async function main() { +export const main = async () => { const radio = new EventEmitter(); radio.on("logMessage", (log) => logger(log)); radio.on("logFailure", (log) => logger(red(log), "error")); @@ -248,7 +248,7 @@ export async function main() { break; } } -} +}; if (require.main === module) { main(); diff --git a/heatstroke.ts b/heatstroke.ts index 9dd95851..ce1e93c6 100644 --- a/heatstroke.ts +++ b/heatstroke.ts @@ -31,12 +31,12 @@ import { getContractNameFromContractId } from "./shared"; * @param type The type of test that failed: invariant or property. * @returns void */ -export function reporter( +export const reporter = ( runDetails: RunDetails, radio: EventEmitter, type: "invariant" | "test", statistics: Statistics, -) { +) => { const { counterexample, failed, numRuns, path, seed } = runDetails; if (failed) { @@ -190,7 +190,7 @@ export function reporter( } reportStatistics(statistics, type, radio); radio.emit("logMessage", "\n"); -} +}; const ARROW = "->"; const SUCCESS_SYMBOL = "+"; @@ -203,11 +203,11 @@ const WARN_SYMBOL = "!"; * @param type The type of test being reported. * @param radio The event emitter for logging messages. */ -function reportStatistics( +const reportStatistics = ( statistics: Statistics, type: "invariant" | "test", radio: EventEmitter, -): void { +): void => { if ( (type === "invariant" && (!statistics.invariant || !statistics.sut)) || (type === "test" && !statistics.test) @@ -305,7 +305,7 @@ function reportStatistics( break; } } -} +}; /** * Displays a tree structure of data. @@ -313,11 +313,11 @@ function reportStatistics( * @param radio The event emitter for logging messages. * @param options Configuration options for tree display. */ -function logAsTree( +const logAsTree = ( tree: Record, radio: EventEmitter, options: StatisticsTreeOptions = {}, -): void { +): void => { const { isLastSection = false, baseIndent = " " } = options; const printTree = ( @@ -351,7 +351,7 @@ function logAsTree( }; printTree(tree, baseIndent, true, radio); -} +}; /** * Computes the total number of failures from a failure map. diff --git a/invariant.ts b/invariant.ts index 7da44091..e1c10de2 100644 --- a/invariant.ts +++ b/invariant.ts @@ -703,11 +703,11 @@ const invariantTest = async ( * Emits warnings for functions that reference traits without eligible * implementations. */ -function emitMissingTraitWarnings( +const emitMissingTraitWarnings = ( radio: EventEmitter, sutFunctions: string[], invariantFunctions: string[], -): void { +): void => { if (sutFunctions.length === 0 && invariantFunctions.length === 0) { return; } @@ -738,7 +738,7 @@ function emitMissingTraitWarnings( `Note: You can add contracts implementing traits either as project contracts or as Clarinet requirements.\n`, ), ); -} +}; /** * Initializes the local context, setting the number of times each function diff --git a/oxlintrc.json b/oxlintrc.json index a2e81d77..0a4434fb 100644 --- a/oxlintrc.json +++ b/oxlintrc.json @@ -8,7 +8,6 @@ "id-length": "off", "max-params": "off", "max-statements": "off", - "func-style": "off", "prefer-destructuring": "off", "no-ternary": "off", "no-nested-ternary": "off", diff --git a/test.utils.ts b/test.utils.ts index 0e16e1a4..fcb7dffa 100644 --- a/test.utils.ts +++ b/test.utils.ts @@ -12,11 +12,11 @@ import { join } from "node:path"; * @returns The path to the temporary directory containing the isolated project * copy. */ -export function createIsolatedTestEnvironment( +export const createIsolatedTestEnvironment = ( manifestDir: string, testPrefix: string, -): string { +): string => { const tempDir = mkdtempSync(join(tmpdir(), testPrefix)); cpSync(manifestDir, tempDir, { recursive: true }); return tempDir; -} +}; From ea7afb7563939c43845746ee6fd60468133ed58e Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 6 Apr 2026 23:05:36 +0300 Subject: [PATCH 13/14] Turn `consistent-indexed-object-style` rule on and fix --- invariant.types.ts | 8 +++++--- oxlintrc.json | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/invariant.types.ts b/invariant.types.ts index aef11968..eadacfea 100644 --- a/invariant.types.ts +++ b/invariant.types.ts @@ -1,3 +1,7 @@ +type ContractId = string; + +type SutFunctionName = string; + /** * LocalContext is a data structure used to track the number of times each SUT * function is called for every contract. It is a nested map where: @@ -5,6 +9,4 @@ * - The inner key is the SUT function name within the contract. * - The value is the count of times the SUT function has been invoked. */ -export interface LocalContext { - [contractId: string]: Record; -} +export type LocalContext = Record>; diff --git a/oxlintrc.json b/oxlintrc.json index 0a4434fb..58a5e6e3 100644 --- a/oxlintrc.json +++ b/oxlintrc.json @@ -12,7 +12,6 @@ "no-ternary": "off", "no-nested-ternary": "off", "no-await-in-loop": "off", - "consistent-indexed-object-style": "off", "default-param-last": "off", "no-continue": "off", "prefer-template": "off" From 36812904af7b26996b49a2e0497017649495c925 Mon Sep 17 00:00:00 2001 From: BowTiedRadone Date: Mon, 6 Apr 2026 23:09:29 +0300 Subject: [PATCH 14/14] Turn `default-param-last` rule on and fix --- heatstroke.ts | 6 +++--- oxlintrc.json | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/heatstroke.ts b/heatstroke.ts index ce1e93c6..f683e190 100644 --- a/heatstroke.ts +++ b/heatstroke.ts @@ -322,9 +322,9 @@ const logAsTree = ( const printTree = ( node: Record, + radioEmitter: EventEmitter, indent: string = baseIndent, isLastParent = true, - radioEmitter: EventEmitter, ): void => { const keys = Object.keys(node); @@ -339,7 +339,7 @@ const logAsTree = ( "logMessage", `${leadingChar} ${indent}${connector} ${ARROW} ${key}`, ); - printTree(node[key], nextIndent, isLast, radioEmitter); + printTree(node[key], radioEmitter, nextIndent, isLast); } else { const count = node[key] as number; radioEmitter.emit( @@ -350,7 +350,7 @@ const logAsTree = ( }); }; - printTree(tree, baseIndent, true, radio); + printTree(tree, radio, baseIndent); }; /** diff --git a/oxlintrc.json b/oxlintrc.json index 58a5e6e3..00fd7648 100644 --- a/oxlintrc.json +++ b/oxlintrc.json @@ -12,7 +12,6 @@ "no-ternary": "off", "no-nested-ternary": "off", "no-await-in-loop": "off", - "default-param-last": "off", "no-continue": "off", "prefer-template": "off" },