Skip to main content

Crate skif

Crate skif 

Source
Expand description

skif as a library: discover a JavaScript or TypeScript monorepo, lint its dependency graph and plan fixes.

The crate is a thin facade over the skif-* crates and behaves like a pure function of its inputs: it never prints, prompts, exits, reads the environment or the working directory, or writes files. Everything is read through a FileSystem (OsFs on disk, MemoryFs for tests, BufferFs for unsaved buffers), every path in a diagnostic or edit is relative to the workspace root, and fixes are returned as data (FixPlan) for the caller to show, merge or write. The phase timings of skif_core::timing are off unless the host installs a sink there.

use std::path::Path;
use std::sync::Arc;
use skif::{ConfigOverrides, Linter, MemoryFs, Workspace, find_workspace_root, load_config};

let fs = MemoryFs::new()
    .with_file("/repo/package.json", r#"{"name": "root", "private": true, "workspaces": ["packages/*"]}"#)
    .with_file("/repo/packages/a/package.json", r#"{"name": "a", "version": "1.0.0"}"#);

let root = find_workspace_root(Path::new("/repo/packages/a"), &fs)?;
let config = load_config(&root, &fs, &ConfigOverrides::default())?;
let workspace = Workspace::discover_with_fs(&root, &config, &fs)?;
// The linter reads source files (with `js-analysis`) through the same file system as discovery.
let report = Linter::with_fs(&config, Arc::new(fs.clone()))?.run(&workspace)?;
assert_eq!(report.summary.packages, 2);

A host that lints many projects discovers every root below a directory with discover_targets (or discover_all) and resolves the layered configuration of a single root with resolve_config and a ConfigCache (the ConfigLoader behind it is public for hosts that load packages themselves):

use std::path::{Path, PathBuf};
use std::sync::Arc;
use skif::{Ceiling, ConfigCache, ConfigOverrides, Linter, MemoryFs, RunOptions, discover_targets, resolve_config};

let fs = MemoryFs::new()
    .with_file("/r/skif.toml", "[lint]\nfail-on-warnings = true\n")
    .with_file("/r/a/package.json", r#"{"name": "a", "version": "1.0.0"}"#)
    .with_file("/r/b/skif.toml", "")
    .with_file("/r/b/package.json", r#"{"name": "b", "version": "1.0.0"}"#);

// Layered configuration: every skif.toml from the root up to the ceiling, merged farthest first. The cache is
// shared by the roots of a run so each file is read once; `origins` says which file set which key.
let cache = ConfigCache::new();
let ceiling = Ceiling::VcsOr(PathBuf::from("/r"));
let resolved = resolve_config(Path::new("/r/a"), &ceiling, &fs, &ConfigOverrides::default(), &cache)?;
assert!(resolved.config.lint.fail_on_warnings);
assert_eq!(resolved.sources.len(), 1);
assert!(resolved.origins.last("lint.fail-on-warnings").is_some());

// Multi-root: every workspace and standalone package below a directory, each with its own configuration.
let discovery = discover_targets(Path::new("/r"), &fs, &ConfigOverrides::default())?;
assert!(discovery.failures.is_empty());
assert_eq!(discovery.targets.len(), 2);
for target in &discovery.targets {
    let linter = Linter::with_fs(&target.config, Arc::new(fs.clone()))?;
    let config_path = target.config_path();
    let mut options = RunOptions::default();
    if let Some(path) = &config_path {
        options = options.with_config_path(path);
    }
    let report = linter.run_with(&target.workspace, &options)?;
    assert!(report.summary.packages >= 1);
}

A host that lints repeatedly, such as an editor, passes the same ModuleCache to every Linter::with_module_cache (feature js-analysis), so only changed files are parsed again, and lints unsaved buffers through a BufferFs over the disk. Neither changes a report: a cold, a warm and no cache report alike.

A root nested in another cannot see what the outer root imports from it. A host that wants those files counted lints the enclosing roots first and passes the files their reports list in Report::other_project_files, made relative to the nested root, to RunOptions::with_extra_entries of the nested root’s run, as skif -r does.

§Features

FeatureDefaultBrings in
manifestyesthe built-in rules: manifest rules, version and semver group policies and catalog rules
js-analysisnothe oxc based JS and TypeScript analysis and the plugin table (Linter adds it to the built-in rules)
fixnoapply_fixes and FixPlan
yaml-fixnoYAML edits for pnpm-workspace.yaml (implies fix)
reportnothe renderers (human, JSON, SARIF, GitHub, Code Climate, compact, Markdown, CODEOWNERS), as skif::render
schemanoconfig_schema
updatenoupdate: newer published versions from a registry the host supplies, planned as a FixPlan

§Third-party types in the API

No oxc or rayon type appears in a public signature; reusable thread pools are an Executor. The data model is the contract for the libraries below, and nothing else from outside skif and the standard library may appear in a public item (tests/public_api.rs fails when it does). A major release of a listed library is a breaking release of skif.

LibraryWhere it shows
serdethe Serialize and Deserialize impls of the data model
serde_jsonJsonAction and YamlAction (a fix is data a host may serialize) and the manifest values of a Package
tomla rule’s options (RuleProvider::validate_options receives the toml::Table)
indexmapthe ordered maps of Config
nodejs_semverthe parsed ranges of a dependency specifier

The renderers that take serde_json members (render_json_with) are not part of the facade; only the fixed renderers are re-exported as render.

§The configuration model

Config and every type it holds are re-exported and readable. A host builds one in code with ConfigBuilder or loads it with load_config and resolve_config. The [workspace] table is WorkspaceConfig, to tell it from the discovered Workspace.

Modules§

diagnostic
The unit of output of every rule.
render
The renderers of Reports: human, JSON, SARIF, GitHub annotations, Code Climate, compact, Markdown and CODEOWNERS groups.
update
skif update: newer published versions for the dependencies the version policies govern (ADR 0058).

Structs§

AppliedFix
A fix that is part of the plan.
Assignment
One place that set a key.
BufferFs
base with the text of some files replaced by buffers, as an editor holds them before they are saved.
CachedFs
Wraps a FileSystem and memoizes what a run asks of it repeatedly.
CachedModule
The module cache of ADR 0066, re-exported for hosts that keep one. One cached parse result. Its fields are private: a host stores and serializes it without depending on the shape of ModuleInfo.
Catalog
[catalog]
Config
The complete skif.toml model.
ConfigBuilder
Builds a Config programmatically, starting from Config::default.
ConfigCache
What a run learns about configuration files, shared by every root it loads: which directories hold a file or a .git, and each file read and parsed once, whichever roots and extends reach it.
ConfigLoader
Resolves the configuration of a root: the chain of files that govern it, merged.
ConfigOverrides
The CLI layer: typed command-line flags applied over a loaded configuration.
ConfigSource
One configuration file applied to a root.
CustomType
[[custom-types]] and the places a version can be declared. One [[custom-types]] entry: a field of package.json that holds versions. Name it in a dependency-types list to select its instances, like a dependency section (syncpack’s customTypes).
DependencyFamily
One [[dependency-families]] entry: packages that must share a version.
DependencyImport
One import of the dependency.
DependencyListing
A manifest that lists the dependency.
DependencyTrace
What JsAnalysis::trace_dependency returns.
DependencyTypes
A list of dependency types with ! negation, e.g. ["prod", "!peer"].
Diagnostic
A single finding reported by a rule.
DiagnosticBuilder
Fluent builder returned by diagnostic.
DirEntry
A directory entry returned by FileSystem::read_dir.
DiscoverOptions
Bounds for scan_candidates.
Discovery
The outcome of discover_targets.
Emitter
Collects the diagnostics of one or more rules and applies policy centrally.
EnabledRules
The effective severity of every enabled rule.
Executor
A reusable pool of worker threads that lint runs execute on.
ExecutorError
The worker threads of an Executor could not be started.
ExportKinds
The kinds of export ignore-exports-used-in-file can name, as in knip.
ExportReport
Why one export of a module counts as used or not.
ExportUser
A module that imports an export.
Fix
A titled, ordered group of operations that repairs one diagnostic.
FixPlan
The file changes selected fixes amount to. Paths are relative to the workspace root; nothing has been written.
FixSelection
Which fixes go into a FixPlan.
FormatConfig
The [format] table of the configuration: what package-json-format checks. [format]: what the package-json-format rule and skif format check, with syncpack’s defaults (ADR 0055).
GroupRef
The state of one dependency instance, from Linter::instances. The version group that owns an instance.
IgnoreEntry
One [[ignore]] suppression. An empty list does not narrow that dimension, but at least one list must be set: an entry without any filter is rejected as it would suppress everything.
Importer
One import of a module.
IncompleteEntries
Why the entry files of a package are not all known, so its files and dependencies are not judged unused.
IncompletePackage
A package whose entry files are not all known, with every reason.
InstanceFilter
Narrows the dependency instances the version-group policies see, as syncpack’s command-line filters do: an instance that does not pass is left out before it is assigned to a group, so it is neither reported nor aligned with. Empty selects everything.
InstanceStatus
The state of one dependency instance, from Linter::instances. One dependency instance and its state.
Js
[js]
JsWorkspace
[js.workspaces."<dir>"]
Lint
[lint]
LintContext
Read-only inputs shared by every rule.
Linter
Runs the registered rules over a Workspace and returns a Report.
Location
A file, relative to the workspace root, and an optional position within it.
MemoryFs
An in-memory file tree for tests and embedders. Directories exist implicitly as parents of files.
MemoryModuleCache
The module cache of ADR 0066, re-exported for hosts that keep one. An in-process ModuleCache for long-lived hosts (watch mode, the language server, the MCP server).
ModuleKey
The module cache of ADR 0066, re-exported for hosts that keep one. The key of one parse result: a 128-bit SipHash-1-3 of the schema, the crate version, the parser, the parse options, the file name and the text. The directory is not part of it, so identical files share an entry and a moved checkout keeps its cache.
Origins
The assignments behind every resolved key, oldest first.
OsFs
The real file system.
Override
One [[overrides]] entry: settings for the packages and files it selects.
OverrideScope
Where an Override was declared, with its patterns resolved to absolute ones.
Package
One package.json of the workspace.
PackageId
Identifies a package within one workspace: its index among all packages, the root first.
PackageJs
[js] keys an override can set for the packages it selects.
PathKind
What a path is, with symbolic links followed. A path that does not exist is neither.
PluginOptions
Table form of a plugin entry.
Policy
[policy]
ProviderError
A provider failed to run.
ReExport
A re-export that forwards an export, with the explanation of the forwarded export.
Related
A secondary location that explains a diagnostic.
Report
The complete result of a lint run.
ResolvedConfig
The configuration of one root with where it came from.
RootFailure
A root that could not be loaded.
RuleConfig
Configuration of one rule: an optional level plus options.
RuleId
Stable kebab-case identifier of a rule, e.g. unused-dependency.
RuleMeta
Static description of a rule, used for documentation and listings.
RulesProvider
Adapts a list of Rules into a RuleProvider.
RunOptions
Host-controlled execution settings for Linter::run_with.
Selector
A list of name patterns compiled once.
SemverGroup
One [[semver-groups]] entry; first match wins. Set range, or ignored = true to leave the matched dependencies without a range requirement (syncpack’s isIgnored and isDisabled).
SkippedFix
A diagnostic whose fix is not in the plan.
Span
Half-open UTF-8 byte range [start, end) with 1-based line and column numbers.
Summary
Aggregate counts over a report.
Target
One root to lint: a workspace or a standalone package, with the configuration that governs it.
TextEdit
Replacement of the half-open byte range [start, end); start == end inserts.
UnappliedConfig
A skif.toml below a root that no configuration of that root was built from.
UpdateConfig
The [update] table of the configuration (an update::Update is one proposed version). [update]: how skif update reads the package registry (ADR 0058). Linting never reads it.
UpdateGroup
One [[update-groups]] entry; first match wins. Set target to cap how far skif update moves the matched dependencies, or ignored = true to leave them alone (syncpack’s updateGroups).
UsageReport
The explanation JsAnalysis::explain_usage returns.
VersionGroup
One [[version-groups]] entry; first match wins. Selectors accept ! negation and $LOCAL.
Workspace
A discovered workspace.
WorkspaceConfig
The [workspace] table of the configuration (the discovered Workspace is a different type). [workspace]
WorkspaceProtocol
[workspace-protocol]

Enums§

Absence
Why a file is not in the module graph.
Alternatives
What to do when a diagnostic has several alternative fixes and choices holds no pick for it.
CatalogMode
[catalog] mode
Category
Area of the workspace a rule inspects.
Ceiling
How far above a root the search for configuration files goes.
ConfigError
Everything that can go wrong while locating, reading, parsing or validating configuration.
CustomStrategy
[[custom-types]] and the places a version can be declared. How a CustomType reads versions from its path.
DependencyType
[[custom-types]] and the places a version can be declared. A place where a dependency version is declared, as a configuration token names it.
EntryReason
Which rule makes a module an entry file.
Error
Anything that can stop discovery, configuration or a lint run.
FileChange
The net effect of fixes on one file, ready to be written.
FixError
Planning the fixes failed as a whole (as opposed to a single fix being skipped).
FixOp
A single mutation of the workspace.
FixPreference
The version a fix that is one of several alternatives aligns on, which [policy] prefer selects among them.
FixSafety
How risky applying a fix is.
Fixability
Whether, and how safely, a rule can repair its findings.
IgnoreExportsUsedInFile
ignore-exports-used-in-file: true, or a table naming the kinds of export it applies to.
IncompleteCause
What made the entry files of a package incomplete.
InsertPosition
Where a new key is placed inside an object.
JsonAction
Edit applied to a JSON node.
Level
The level a rule is configured to: off, info, warn or error.
LintError
A lint run failed.
Op
What a file did to a key.
PackageManager
Package managers that can be forced with [workspace] package-manager.
PathSeg
One step of a structured path into a JSON or YAML document.
PluginConfig
[js.plugins] <name> = true | { ... }
Prefer
Which version preferred-semver favours.
RegistryError
Two rules share an ID.
ResetKey
The inherited lists that reset can discard.
RootKind
A project root decided by claim_roots.
SemverRange
The range type a semver group enforces.
Severity
How serious a diagnostic is. Ordered Info < Warning < Error.
SkipReason
Why a diagnostic’s fix is not in the plan.
SourceKind
How a configuration file entered the chain of a root.
StatusType
The state of one dependency instance, from Linter::instances. syncpack’s classes of instance states, as statusType names them.
UnappliedReason
Why a configuration file below a root was not applied.
UpdateTarget
How far skif update may move a version: syncpack’s --target and an update group’s target.
Verdict
Whether an export counts as used.
VersionPolicy
The policy of a version group.
WorkspaceError
Everything that can stop workspace discovery. Discovery never prints and never exits.
YamlAction
Edit applied to a YAML node.

Constants§

CONFIG_FILE_NAMES
Accepted configuration file names, in lookup order.

Traits§

FileSystem
Read-only file system access used by discovery.
ModuleCache
The module cache of ADR 0066, re-exported for hosts that keep one. A store of parse results the host owns and gives the analysis.
Rule
A single lint rule. It detects and reports; policy lives in the Emitter.
RuleProvider
A source of rules, such as the manifest rules or a host’s own.

Functions§

apply_fixes
Plans the fixes selection picks from report and returns them as data; the file system is only read.
builtin_providers
The rule providers compiled into this build, in registration order.
config_file
The name of the configuration file in root (skif.toml or .skif.toml), relative to root, when there is one.
config_schema
The JSON Schema of skif.toml, pretty printed.
content_hash
A stable 64-bit hash (FNV-1a) of file contents, for the staleness guard of a FixPlan: see FixPlan::base_hash. It is not cryptographic and will not change between releases.
diagnostic
Builds a Diagnostic for meta.
discover_all
Like discover_targets, failing with the first root that cannot be loaded.
discover_targets
Finds every workspace root and standalone package at or below start and loads each as a Target, sorted by path (see skif_workspace::claim_roots for what counts as a root).
empty_tsconfig_projects
With [js] use-tsconfig-files, the TypeScript configurations, relative to the workspace root, whose files and include select nothing inside their package, so that package has no project files and no file of it can be reported unused. Empty without the setting. A host shows these as warnings; the lint itself goes on.
explain_usage
Explains why the JavaScript analysis counts file (relative to the workspace root) as used, and with export why that export of it is used or not.
find_target
Finds the workspace that start belongs to and loads it, discovering it once.
find_workspace_root
Finds the workspace that start belongs to.
join_key
Appends key to the dotted origin path prefix.
load_config
Loads the configuration of root through fs and layers overrides over it (defaults < files < overrides).
load_target
Loads the configuration for root from every config file in root or a parent up to the ceiling, merged (see resolve_config), and discovers the workspace at root.
module_cache_version
The version a persisted ModuleCache records: a store written under another version is stale as a whole. The module cache of ADR 0066, re-exported for hosts that keep one. The version a persisted cache records, <crate version>+<CACHE_SCHEMA>: a store written under another version is discarded as a whole.
preferred_alternative
The index of the alternative in fixes that carries the FixPreference prefer selects, when there is one.
relative_tsconfig
[js] tsconfig normalised to a /-separated path relative to the workspace root, or None when it is empty, absolute or leaves the root. Every reader of the setting goes through this, so they agree on which file it names.
resolve_config
Resolves the configuration of root through fs: every skif.toml or .skif.toml from root up to ceiling (stopping after a file with root = true), with what they extend, merged farthest first, then overrides on top (defaults < files < overrides). Returns the merged configuration with the files it came from and where each key was set.
slash
path as text with / separators on every platform, for messages, titles and machine-readable output.
trace_dependency
Traces the dependency name: the manifests that list it, every import of it with the importing line, and the packages that count it as used (through imports, scripts, tool configuration or its @types twin).
unapplied_config_files
The skif.toml and .skif.toml files below target.root that its configuration was not built from, sorted by path.
unapplied_config_files_all
unapplied_config_files for every target of a run at once: entry i is the answer for targets[i], with the other targets as the deeper roots.

Type Aliases§

RuleOptions
The raw options table of one rule. The rule that owns the options defines and validates their shape.