Static flow analysis for TypeScript
anytypes. Finds the few sources responsible for most of your type erosion.
TypeScript’s any is a silent type-safety killer. Existing tools (type-coverage, ESLint’s no-explicit-any, tsc --noImplicitAny) tell you where any exists — few tell you where it originates, how far it spreads, or which few fixes would do the most good.
A single any in a utility can propagate through assignments, destructuring, and function returns, turning many downstream symbols into untyped code. any-map models the project as a directed graph of type-flow, traces any from sources to infected graph nodes, and ranks fix order with blast radius and a greedy set-cover pass.
Published on npm: any-map@2. Algorithm details: PLAN.md. Migrating from 1.x: docs/v2-migration.md.
v2 keeps the same analyzer core as 1.4 (graph, propagation, blast, set-cover, diff) and adds team workflow features:
| Feature | What you get |
|---|---|
| Scan health | After each scan, see median source overlap + whether greedy “fix order” is meaningful on your repo (not a fake “fix 3 = 80%” story). |
any-map diff gates |
--fail-on-new-sources, --fail-on-infected-increase, --fail-on-blast-increase, --max-new-sources for PR checks. |
| JSON report v2 | Structured output (reportVersion: 2) with summary, rankings, and health. Legacy flat JSON via --report-version 1. |
| SARIF | --format sarif on scan and diff for GitHub Code Scanning and other SARIF consumers. |
| Clearer scan CI flags | --fail-top3-greedy-pct replaces the confusing --fail-coverage name (old flag still works with a deprecation warning). |
| GitHub Action | Pin version: "2"; command: diff writes a PR summary and up to 10 workflow annotations on new sources. |
| Diff scan cache | Reuses summaries in .any-map-cache/ keyed by commit (disable with --no-cache). |
--max-files |
Cap TypeScript program size on huge trees. |
Install: npm i -D any-map@2 or npx any-map@2. Migration: docs/v2-migration.md.
| Command | Purpose |
|---|---|
any-map diff <base> <head> [path] |
Branch any deltas (merge-base); table / JSON / SARIF; PR fail gates. |
any-map scan [path] |
Full-project analysis; table / JSON / DOT / SARIF; health footer. |
any-map trace <loc> [path] |
Print type-flow paths from each any source to the symbol at loc (file:line:col). |
any-map graph [path] |
Emit the project type-flow graph as Graphviz DOT (-o out.dot or stdout). |
any-map scan: --format table|json|dot|sarif, --report-version 2 (default) or 1 (legacy), --dump-graph, --top N, --source-kinds, --ignore, --fail-above N, --fail-top3-greedy-pct P (see scan health before using this on large repos), --max-files N.
any-map diff: --fail-on-new-sources, --fail-on-infected-increase, --fail-on-blast-increase, --max-new-sources N, --no-cache.
Pull request check (recommended):
npx any-map@2 diff origin/main HEAD --fail-on-new-sourcesCI: .github/actions/any-map-scan (version: "2", command: diff) or .github/workflows/any-map-pr.yml.
SARIF (GitHub Code Scanning): any-map scan . --format sarif > any-map.sarif then upload with upload-sarif.
Pair with type-coverage: docs/integrations/type-coverage.md.
Direct intra-project flow currently includes import bindings, re-export chains, property/index reads, plain assignments, and resolved cross-file call edges, so any can propagate through chains like export default value -> export { value as renamed } -> import x -> box.payload -> consume(x).
Inference-only kinds (implicit-param, untyped-return, untyped-import, catch-binding) are not reported for plain .js/.jsx/.mjs/.cjs where TypeScript often infers any without the developer “choosing” it — so source counts and set-cover stay meaningful on mixed TS/JS repos. Written any and other non–inference-only classifiers still apply where applicable.
| Repo | Project files | any sources |
Infected nodes | Top blast* | Top-3 greedy cum. % |
|---|---|---|---|---|---|
| colinhacks/zod | 357 | 834 | 457 | 13 | 5% |
| pmndrs/zustand | 30 | 131 | 52 | 2 | 12% |
| immerjs/immer | 16 | 138 | 121 | 17 | 21% |
| sindresorhus/ky | 29 | 18 | 5 | 2 | 80% |
*Highest blast-radius among ranked sources (tie broken by sort order). These are snapshot runs against the default repo roots on April 26, 2026. Repos that extend shared tsconfig packages may need pnpm install before scanning so TypeScript can resolve the config chain.
Takeaway: Even among TS-native repos, top-3 greedy coverage ranges from 5% to 80%. Raw any count alone does not tell you whether a codebase “rewards” a few fixes; overlap and graph shape dominate.
Implication: “Fixing these 3 any sources would restore type safety for most of the repo” is sometimes true and often false. any-map is most useful when it shows you that the long tail is real, not when it flatters you with a single magic percentage. For a larger, more entangled stress-test, the TypeORM sample below remains a good illustration of the same point.
any-map trace src/foo.ts:12:5 prints forward hops (reason per edge) from each source to the traced binding; use --json for machine-readable TraceReport.
any-map diff main HEAD --format table computes the merge-base of main and HEAD, runs full scans for both revisions, and reports only the any deltas for touched files by default. If the diff touches tsconfig*.json, package.json, lockfiles, or .d.ts files inside the selected scan root, it automatically falls back to a full-project delta.
$ any-map scan ./typeorm --top 10
Scanning 3336 project files...
Found 1460 any sources, 908 infected graph nodes.
Fix order (greedy set-cover)
Pick Cum.% +Nodes Blast Bl# File Line:Col Kind Name
1 9 80 80 2 src/util/TreeRepositoryUtils.ts 70:40 explicit-any childEntity
2 13 38 38 25 src/util/ApplyValueTransformers.ts 5:12 untyped-return transformFrom
3 16 24 28 41 src/metadata/EntityMetadata.ts 574:13 explicit-any ret
4 17 12 12 42 src/metadata/EntityListenerMetadata.ts 81:5 untyped-return execute
5 18 9 9 44 src/driver/postgres/PostgresDriver.ts 486:38 explicit-any extensionsMetadata
6 19 5 5 46 src/query-builder/RelationLoader.ts 517:28 explicit-any value
7 19 4 4 47 src/driver/cockroachdb/CockroachQueryRunner.ts 3294:23 untyped-return getSchemaFromKey
8 19 4 4 49 src/driver/sap/SapQueryRunner.ts 2846:19 untyped-return getSchemaFromKey
9 20 4 4 50 src/driver/sqlserver/SqlServerQueryRunner.ts 3273:23 untyped-return getSchemaFromKey
10 20 4 4 51 test/github-issues/4219/shim.ts 1:5 explicit-any _Shim
By blast radius
Rank Blast File Line:Col Kind Name
1 80 src/query-builder/SelectQueryBuilder.ts 1764:15 as-any result
2 80 src/util/TreeRepositoryUtils.ts 70:40 explicit-any childEntity
3 79 src/entity-manager/MongoEntityManager.ts 1271:9 explicit-any idMap
4 79 src/metadata/ColumnMetadata.ts 917:24 explicit-any entity
5 79 src/persistence/tree/NestedSetSubjectExecutor.ts 339:9 explicit-any parent
6 79 src/util/TreeRepositoryUtils.ts 47:9 explicit-any entity
7 79 src/util/TreeRepositoryUtils.ts 86:9 explicit-any entity
8 78 src/driver/aurora-mysql/AuroraMysqlDriver.ts 544:28 explicit-any value
9 78 src/driver/aurora-postgres/AuroraPostgresDriver.ts 139:28 explicit-any value
10 78 src/driver/cockroachdb/CockroachDriver.ts 407:28 explicit-any value
(Tables match a real run. Install dependencies in the TypeORM clone before scanning.)
- Classify every
anysource (explicit: any,as any, untyped imports, untyped returns,catch (e), implicit params — see PLAN.md). - Build a directed graph where each edge represents type flow (
const a = b→b→a, plus direct intra-project call/import edges). - Propagate from each source with forward BFS; nodes track
infectedBysource ids. - Rank by blast radius; run greedy set-cover over infected nodes for fix order and cumulative %.
- Emit table, JSON, DOT, or SARIF; optional health interpretation.
Details: PLAN.md §5.
| Tool | What it tells you | What any-map adds |
|---|---|---|
tsc --noImplicitAny |
Where any is inferred |
— |
@typescript-eslint/no-explicit-any |
Where any is written |
— |
type-coverage |
% of typed identifiers | — |
| any-map | Source → infection graph, blast, rank | Propagation + greedy order + trace + PR diff gates + SARIF |
Use type-coverage for a single “% typed” score; use any-map to prioritize which any origins matter and block new ones in PRs. See docs/integrations/type-coverage.md.
CHANGELOG.md · PLAN.md (algorithms, scope, test strategy).
- No full inference through generics / conditionals / distributive types (surface at usage only).
- No full callsite-sensitive interprocedural analysis across complex re-export chains, dynamic dispatch, or overload/generic specialization; direct resolved callees plus import/re-export hops are followed across project files.
- No built-in LSP extension (see docs/editor-vscode.md for a task snippet); no
historycommand yet.
Planned for 2.x minors: deeper graph edges, bitset performance, monorepo --project flag. See PLAN.md.
- NPM on CI:
.github/workflows/release.ymlpublishes through npm trusted publishing (OIDC) from GitHub Actions; no long-livedNPM_TOKENis required. Keep the npm trusted publisher config aligned with this repo and workflow filename. - Reusable action:
.github/actions/any-map-scan— pinversion: "2", usecommand: diffwithfail-on-new-sourcesandpost-summary: truefor PR reviews. - Releases: tag and GitHub Release should match the version published to npm (see the release workflow).
pnpm install
pnpm build
pnpm test
pnpm lint