enforce-export-coverage
A static-analysis command line that checks every exported TypeScript function has a corresponding test, without running the test suite.
Parses a project's TypeScript with ts-morph, extracts the names of the exported functions, resolves the candidate test files for each source file, and records a violation when no candidate test file contains a call to the function. It runs on the file paths it is given, which is the pre-commit mode on staged files, or with --all over the whole source root, which is the CI mode. Test locations follow configured conventions (co-located, a __tests__ subdirectory, or a mirrored test root), set in enforce-export-coverage.config.json or an enforceExportCoverage key in package.json. The README contrasts it with Jest's coverageThreshold: { functions: 100 }, which it calls overlapping but not equivalent. It checks that a call exists in a test file and does not run the tests, so it says nothing about whether the call passes or what it asserts.
Facts
Where it sits
Who is behind it
In the atlas: maintains 6, contributes to 3@qualituremaintainer
Before choosing this
Conditions
PracticalFunctional boundary
It reads TypeScript exports only; a project written in plain JavaScript has no exported functions for it to extract. A call to the function name in a test file counts as tested, and a call through property access such as .foo( does not.
What it declares
Every artifact
- Type
- CLI
- Origin
- Independently maintained
- Runtime
- Node.js
As a CLI
- Distributed through
- does not say
Connections
Proving a service still does what it did, without a deployment
Download
Install
npm add enforce-export-coverageDerived from the package name. No recipe recorded; the README has the configuration.
README
quoted from enforce-export-coverage@1.0.0 under MIT, readenforce-export-coverage
A static analysis tool that enforces every exported TypeScript function has a corresponding test — without running your test suite.
Designed to run as a fast pre-commit hook on staged files, or as a full project scan in CI.
Why not just set Jest's coverageThreshold: { functions: 100 }?
It's a fair question. The two approaches overlap but are not equivalent:
enforce-export-coverage | Jest functions: 100 | |
|---|---|---|
| Requires a dedicated test | Yes — the function name must appear as a call in a test file | No — executing the function as a side effect of any test counts |
| Scope | Exported functions only (your public API) | All functions, including private helpers and anonymous callbacks |
| Speed | Sub-second static analysis, no test execution | Requires running the full test suite |
| Pre-commit (staged files only) | Yes | Impractical — too slow for every commit |
| Actionable output | Names the exact function and file missing a test | Reports a percentage; you must open the HTML report to find what's missing |
The honest take: if you have collectCoverageFrom configured and coverageThreshold: { functions: 100 } in CI, you catch most of the same problems at the pipeline level. The two tools are complementary: this one gives you fast, intent-based, export-focused enforcement before you commit; Jest's threshold gives you a hard runtime gate afterwards. Running both is not redundant — one is proactive, one is a safety net.
Installation
npm install --save-dev enforce-export-coverage
Or install globally to use the CLI anywhere:
npm install -g enforce-export-coverage
CLI usage
Check specific files (pre-commit hook pattern)
Pass file paths as arguments. Non-.ts files and test files are automatically skipped.
enforce-export-coverage src/handlers/foo.ts src/utils/bar.ts
Scan the entire source root
enforce-export-coverage --all
All options
enforce-export-coverage [files...]
Options:
--all Scan all .ts files under srcRoot instead of reading from arguments
--project-root <path> Project root directory (default: cwd)
--src-root <path> Source directory, relative to project root (overrides config)
--test-root <path> Test mirror directory, relative to project root (overrides config)
--config <path> Path to a JSON config file
--help Show this message
Exit codes:
0 All exported functions have tests
1 One or more exported functions are untested
Configuration
Configuration can be placed in a dedicated file or inside package.json. All fields are optional and fall back to the defaults shown below.
enforce-export-coverage.config.json
{
"srcRoot": "src",
"testRoot": "test",
"testSuffixes": [".test.ts", ".spec.ts"],
"testConventions": ["co-located", "__tests__", "mirror"],
"ignore": []
}
package.json (alternative)
{
"enforceExportCoverage": {
"srcRoot": "srv",
"testRoot": "test",
"testConventions": ["mirror"]
}
}
Config options
| Option | Type | Default | Description |
|---|---|---|---|
srcRoot | string | "src" | Source directory to scan, relative to the project root |
testRoot | string | "test" | Root for mirror-style test files, relative to the project root |
testSuffixes | string[] | [".test.ts", ".spec.ts"] | File suffixes that identify test files |
testConventions | string[] | ["co-located", "__tests__", "mirror"] | Which test file location patterns to check (see below) |
ignore | string[] | [] | Substrings matched against project-relative paths — matching files are skipped |
Test conventions
The tool checks three conventions and considers a function tested if any of them finds a matching test file:
-
co-located— test file lives next to the source file
src/handlers/foo.ts→src/handlers/foo.test.ts -
__tests__— test file lives in a__tests__subdirectory
src/handlers/foo.ts→src/handlers/__tests__/foo.test.ts -
mirror— test file mirrors the source tree undertestRoot
src/handlers/foo.ts→test/handlers/foo.test.ts
Integrations
Pre-commit hook with lint-staged
lint-staged passes staged file paths as arguments automatically:
{
"lint-staged": {
"src/**/*.ts": "enforce-export-coverage"
}
}
Pre-commit hook with Husky
# .husky/pre-commit
npx lint-staged
Or directly, using git diff to get staged .ts files:
# .husky/pre-commit
STAGED=$(git diff --cached --name-only --diff-filter=ACM | grep '\.ts$' | grep -v '\.test\.ts$')
[ -z "$STAGED" ] && exit 0
enforce-export-coverage $STAGED
CI — full project scan
# GitHub Actions example
- name: Enforce export coverage
run: enforce-export-coverage --all --src-root srv --test-root test
Programmatic API
The package exports all its internals for use in custom scripts or other tools.
import { validateFunctionCoverage } from 'enforce-export-coverage';
const result = validateFunctionCoverage(['./src/handlers/foo.ts'], {
projectRoot: '/path/to/project',
srcRoot: 'src',
testRoot: 'test',
testConventions: ['mirror'],
});
if (!result.success) {
for (const { functionName, filePath } of result.violations) {
console.error(`'${functionName}' in ${filePath} has no test`);
}
}
validateFunctionCoverage(sourceFiles, options?)
| Parameter | Type | Description |
|---|---|---|
sourceFiles | string[] | Absolute paths to source files to check |
options.projectRoot | string | Absolute project root (default: process.cwd()) |
options.configPath | string | Path to a JSON config file |
options.srcRoot | string | Overrides srcRoot from config |
options.testRoot | string | Overrides testRoot from config |
options.testSuffixes | string[] | Overrides testSuffixes from config |
options.testConventions | string[] | Overrides testConventions from config |
options.ignore | string[] | Overrides ignore from config |
Returns { success: boolean, violations: Array<{ functionName: string, filePath: string }> }.
Other exports
import {
loadConfig, // load + merge config from project files
resolveTestFilePaths, // resolve candidate test paths for a source file
getExportedFunctionNames, // extract exported function names via ts-morph
isTestedIn, // check if a function name is called in a test file
findFiles, // recursively find .ts files under a directory
} from 'enforce-export-coverage';
How it works
- For each source file,
ts-morphparses the AST and extracts the names of all exported functions (bothexport function foo()andexport const foo = () => {}). - Candidate test file paths are resolved according to the configured conventions.
- Each candidate test file is read, comments are stripped, and a word-boundary regex checks for a call expression matching the function name. A property access (
.foo() does not count. - If no candidate file contains a call to the function, a violation is recorded.
License
MIT
History
Others providing
Compare all 8 on the Testing page →- com.sap.cds:cds-services-bomcom.sap.cds:cds-services-bomThe Maven bill of materials for CAP Java, pinning every com.sap.cds module in a release, from the API and core runtime to the Spring Boot starters and the CQN engine, to the same version.sap official, capMaven bill of materials pinning CAP Java module versionsfrom CAP
- @cap-js/cds-test@cap-js/cds-testTest support for CAP Node.js, installed as a dev dependency: it starts the project's server for the test, hands the test an HTTP client bound to it, and carries the assertions.sap official, capTest support for CAP Node.js: server start, HTTP client, assertionsfrom CAP
- cds-cucumbercds-cucumberBehaviour-driven tests for CAP applications: Gherkin scenarios that drive a running service rather than assertions written against handlers.community org, capGherkin scenarios driving a running CAP servicefrom CAP
- sapbrunosapbrunoA terminal UI that runs Bruno collections against a CAP service, fetching an OAuth2 client_credentials token before each run so collections that fail with 401 under bru run work in CI.independent, capTerminal UI running Bruno collections against a CAP servicefrom CAP
- cds-launchpad-plugincds-launchpad-pluginIndexes every SAPUI5 application in a workspace into one sandbox launchpad served by the CDS server, so all of them can be opened from a single page while developing.independent, capSandbox launchpad for all SAPUI5 apps in a CAP workspacefrom CAP
- @sap-ux/fe-mockserver-plugin-cds@sap-ux/fe-mockserver-plugin-cdsLets the Fiori Elements mock server take a CDS file as its metadata source, so a UI is developed against the real model before a service exists.sap official, capFiori Elements mock server plugin taking a CDS file as metadatafrom CAP
- @saptools/bruno@saptools/brunoA command line that runs Bruno collections against XSUAA-protected apps on Cloud Foundry, fetching the app's token and writing it into the selected environment file before each run.independent, btpRunner for Bruno collections against XSUAA-protected CF appsfrom BTP/BAIP
Details
Sources
Sources are graph entities: the same page may back several entries, and an unreachable source flags every row that rests on it.
Compatibility and provenance
History in the atlas
- First seen.
For your agent
- Ref
artifact:qualiture-enforce-export-coverage- JSON
- /artifacts/qualiture-enforce-export-coverage.json
- MCP
- Connecting an agent