Skip to content

graphops/subgraph-linter

Repository files navigation

Subgraph Linter

Static-analysis utility for subgraph mappings (entity overwrite, null safety, division guards, and more).

License: MIT

Why

Handlers often load an entity (for example GraphNetwork.load('1')), call helpers that reload the same entity and persist counter updates, then save the original instance without reloading. The last save() wipes whatever the helper stored. This detector walks every handler referenced by your manifest(s), inlines helper call graphs, and currently runs multiple checks:

  • [entity-overwrite] Flags “load → helper mutates/save → handler saves stale copy” patterns.
  • [unexpected-null] Ensures newly constructed entities initialize every non-null, non-@derivedFrom field before calling .save() and warns when code writes to derived fields.
  • [unchecked-load] Warns when Entity.load(...) results are forced non-null with ! instead of handling the absent entity case explicitly.
  • [unchecked-nonnull] Fails when graphNetwork.currentL1BlockNumber! is used instead of checking that the optional field is set on L2 code paths.
  • [division-guard] Errors when code divides by a value that has not been proven non-zero (e.g., x / shares without checking shares !== 0). The check understands common Graph helpers such as if (!shares.isZero()) { ... }, if (shares.gt(BigInt.fromI32(0))) { ... }, and ternaries that short-circuit on BigDecimal.fromString(value.toString()), so legitimate guards don't raise noise.
  • [derived-field-guard] Warns when handlers mutate metrics that feed helper routines (e.g., updateDelegationExchangeRate) but skip calling the helper before saving, so you know to recompute derived state before persisting.
  • [helper-return-contract] Warns at helper call sites when the helper may return an entity with required fields still unset, nudging the caller to initialize those fields before the next .save().

Usage

cd subgraph-linter
npm install
npm run build

# Analyze the handlers referenced by a manifest (tsconfig inferred)
npm run check -- --manifest ../graph-network-analytics-horizon/subgraph.yaml

# Provide an explicit tsconfig when the default isn't correct
npm run check -- --manifest ../graph-network-analytics-horizon/subgraph.yaml --tsconfig ../graph-network-analytics-horizon/tsconfig.json

# Override the analyzer config (defaults to subgraph-linter.config.json when present)
npm run check -- --manifest ../graph-network-analytics-horizon/subgraph.yaml --config ./detector.config.json
  • At least one --manifest <path> flag is required. You can repeat the flag to merge multiple manifests.
  • The detector inspects each manifest’s mapping.file entries to know which TypeScript files contain the handlers, and only analyzes the functions referenced by the manifest event/call/block/full handler lists.
  • --tsconfig <path> is optional; when omitted we default to the repository’s tsconfig.json.
  • --config <path> is optional; when omitted we look for subgraph-linter.config.json in the current directory. The config file currently supports severityOverrides, which remaps check ids to 'warning' or 'error':
{
  "severityOverrides": {
    "helper-return-contract": "warning",
    "division-guard": "error"
  }
}

Results are grouped into Errors and Warnings; only issues whose effective severity is error (after overrides) cause a non‑zero exit code. Each line follows file:line - handler [checkId]: message so you can diff outputs or feed them into other tools.

Fixtures

A lightweight mock subgraph lives under test-fixtures/ with deterministic positives/negatives so we can regression-test the analyzer. To run it:

npm run build
npm run check:fixtures
# Enforce that fixture positives match the expected list
npm run verify:fixtures

The script wires in test-fixtures/subgraph.yaml, so only the handlers enumerated there (including the manifest-only processAllocation) are analyzed. You should see the expected positives in test-fixtures/src/mappings/positives.ts (both the overwrite cases and the unexpected-null cases, including the derived-field violation); negatives demonstrate safe patterns such as explicit reloads or distinct entity ids. npm run verify:fixtures runs the analyzer and compares the diagnostic list against test-fixtures/expected-issues.json, failing fast if anything changes.

Development Notes

  • Implementation is in src/index.ts using ts-morph.
  • The detector tracks entity identity (type + id expression), assignment aliases, helper return values, and parameter mutations.
  • When adding new heuristics, update the fixtures and re-run both npm run check:fixtures and the real subgraph check to ensure we’re not reintroducing noise.
  • Need to add another static-analysis rule? See ADDING_CHECKS.md for the step-by-step guide (implementation, fixtures, expected issues, and documentation).

Feel free to extend the fixtures with additional corner cases as we encounter new patterns in production subgraphs.

About

Static-analysis tool for subgraphs

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published