This monorepo uses a comprehensive linting setup with multiple tools to ensure code quality and consistency across all packages.
- ESLint: JavaScript/TypeScript code quality and style enforcement
- Prettier: Code formatting for JavaScript, TypeScript, JSON, Markdown, YAML, and Solidity
- Solhint: Solidity-specific linting for smart contracts
- Forge Lint: Foundry's Solidity linter (for packages using Forge)
- TODO Check: Reports TODO/FIXME/XXX/HACK comments in Solidity files (informational)
- Markdownlint: Markdown formatting and style consistency
- YAML Lint: YAML file validation and formatting
The linting configuration follows a hierarchical structure where packages inherit from root-level configurations.
- Root Configuration:
eslint.config.mjs- Modern flat config format - Direct Command:
npx eslint '**/*.{js,ts,cjs,mjs,jsx,tsx}' --fix - Behavior: ESLint automatically searches up parent directories to find configuration files
- Package Inheritance: Packages automatically inherit the root ESLint configuration without needing local config files
- Global Ignores: Configured to exclude autogenerated files (
.graphclient-extracted/,lib/) and build outputs
-
Root Configuration:
prettier.config.cjs- Base formatting rules for all file types -
Direct Command:
npx prettier -w --cache '**/*.{js,ts,cjs,mjs,jsx,tsx,json,md,sol,yml,yaml}' -
Package Inheritance: Packages that need Prettier must have a
prettier.config.cjsfile that inherits from the shared config -
Example Package Config:
const baseConfig = require('../../prettier.config.cjs') module.exports = { ...baseConfig }
-
Ignore Files:
.prettierignoreexcludes lock files, build outputs, and third-party dependencies
-
Root Configuration:
.solhint.json- Base Solidity linting rules extendingsolhint:recommended -
Direct Command:
npx solhint 'contracts/**/*.sol'(add--fixfor auto-fixing) -
List Applied Rules:
npx solhint list-rules -
Package Inheritance: Packages can extend the root config with package-specific rules
-
Configuration Inheritance Limitation: Solhint has a limitation where nested
extendsdon't work properly. When a local config extends a parent config that itself extendssolhint:recommended, the built-in ruleset is ignored. -
Recommended Package Extension Pattern:
{ "extends": ["solhint:recommended", "./../../.solhint.json"], "rules": { "no-console": "off", "import-path-check": "off" } }
Forge lint is Foundry's built-in Solidity linter. Packages using Foundry can add lint:forge to their lint scripts.
- Package Configuration:
foundry.tomlwith[lint]section - Direct Command:
forge lintorforge lint contracts/ - Available in: Packages with
lint:forgescript defined (horizon, subgraph-service, issuance)
-
Root Configuration:
.markdownlint.json- Markdown formatting and style rules -
Direct Command:
npx markdownlint '**/*.md' --fix -
Ignore Files:
.markdownlintignoreautomatically picked up by markdownlint CLI -
Package Inheritance: Packages that need Markdownlint must have a
.markdownlint.jsonfile that extends the root config -
Example Package Config:
{ "extends": "../../.markdownlint.json" }
When you need to suppress a lint warning for a specific line or item, use the appropriate comment directive.
// Disable for next line (can have intervening comments before target)
// solhint-disable-next-line func-name-mixedcase
// Disable for previous line
function example() {
// solhint-disable-previous-line no-empty-blocks
}
// Block disable/enable
// solhint-disable no-console
console.log("debug");
// solhint-enable no-console // Disable for next item (function, struct, etc. - AST-aware)
// forge-lint: disable-next-item(mixed-case-function)
// Note: forge-lint uses "next-item" not "next-line"
// It applies to the entire syntactic construct, not just the next lineFor functions that need both Solhint and Forge lint suppression (e.g., OpenZeppelin-style initializers):
// solhint-disable-next-line func-name-mixedcase
// forge-lint: disable-next-item(mixed-case-function)
/**
* @notice Internal function to initialize the contract
*/
function __ContractName_init(address param) internal {
// initialization code
}Note: Place suppression comments before natspec to avoid warnings about comments not directly preceding the function.
# Run all linting tools
pnpm lint
# Individual linting commands
pnpm lint:ts # ESLint + Prettier for TypeScript/JavaScript
pnpm lint:sol # TODO check + Solhint + Prettier for Solidity (runs recursively)
pnpm lint:forge # Forge lint for packages that support it
pnpm lint:md # Markdownlint + Prettier for Markdown
pnpm lint:json # Prettier for JSON files
pnpm lint:yaml # YAML linting + Prettier
# Lint only staged files (useful for manual pre-commit checks)
pnpm lint:staged # Run linting on git-staged files onlyEach package can define its own linting scripts that work with the inherited configurations:
# Example from packages/contracts
pnpm lint:sol # Solhint for contracts in this package only
pnpm lint:ts # ESLint for TypeScript files in this packageThe repository uses lint-staged with Husky to run linting on staged files before commits:
- Automatic: Runs automatically on
git commitvia Husky pre-commit hook - Manual: Run
pnpm lint:stagedto manually check staged files before committing - Configuration: Root
package.jsoncontains lint-staged configuration - Custom Script:
scripts/lint-staged-run.shfilters out generated files that shouldn't be linted - File Type Handling:
.{js,ts,cjs,mjs,jsx,tsx}: ESLint + Prettier.sol: TODO check + Solhint + Prettier.md: Markdownlint + Prettier.json: Prettier only.{yml,yaml}: YAML lint + Prettier
Usage: pnpm lint:staged is particularly useful when you want to check what linting changes will be applied to your staged files before actually committing.
The repository reports TODO comments in Solidity files to help track technical debt:
- Scope: Applies only to Solidity (
.sol) files - Detection: Finds TODO, FIXME, XXX, and HACK comments (case-insensitive)
- Behavior: Informational only - does not block commits or fail linting
- Included in:
lint:solandlint:stagedscripts - Script:
scripts/check-todos.sh(must be run from repository root)
| Tool | Root Config | Package Config | Ignore Files |
|---|---|---|---|
| ESLint | eslint.config.mjs |
Auto-inherited | Built into config |
| Prettier | prettier.config.cjs |
prettier.config.cjs (inherits) |
.prettierignore |
| Solhint | .solhint.json |
.solhint.json (array extends) |
N/A |
| Forge Lint | N/A | foundry.toml [lint] section |
N/A |
| TODO Check | scripts/check-todos.sh |
N/A | N/A |
| Markdownlint | .markdownlint.json |
.markdownlint.json (extends) |
.markdownlintignore |
| Lint-staged | package.json |
N/A | scripts/lint-staged-run.sh |
- ESLint not finding config: ESLint searches up parent directories automatically - no local config needed
- Prettier not working: Packages need a
prettier.config.cjsthat inherits from root config - Markdownlint not working: Packages need a
.markdownlint.jsonthat extends root config - Solhint missing rules: If extending a parent config, use array format:
["solhint:recommended", "./../../.solhint.json"]to ensure all rules are loaded - Solhint inheritance not working: Nested extends don't work - parent config's
solhint:recommendedwon't be inherited with simple string extends - Solhint rule reference: Use
npx solhint list-rulesto see all available rules and their descriptions - Generated files being linted: Check ignore patterns in
.prettierignore,.markdownlintignore, and ESLint config - Preview lint changes before commit: Use
pnpm lint:stagedto see what changes will be applied to staged files - Commit blocked by linting: Fix the linting issues or use
git commit --no-verifyto bypass (not recommended) - Forge lint symlink errors: Forge follows symlinks when scanning for files, which can cause "Too many levels of symbolic links" errors in packages with nested workspace dependencies. If a package has a
test/subproject with workspace symlinks that create loops, rename the directory (e.g., totesting/) so forge doesn't scan it by default.