first commit
This commit is contained in:
117
docs/devtools/tooling/linter/src/glob-resolver/glob-resolver.md
Normal file
117
docs/devtools/tooling/linter/src/glob-resolver/glob-resolver.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# `glob-resolver`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`@suaveplan/linter/glob-resolver` expands a list of patterns (glob expressions or file
|
||||
paths) into a sorted, deduplicated list of markdown targets. It owns the default ignore
|
||||
list (`node_modules`, `dist`), distinguishes between single-file paths and glob
|
||||
expressions, and produces a stable iteration order so the rest of the pipeline is
|
||||
deterministic. The resolver is the single seam where the filesystem and the glob
|
||||
implementation meet, which means every call site downstream can rely on receiving a
|
||||
plain sorted array of paths.
|
||||
|
||||
## Features
|
||||
|
||||
- Single-file fast path — a pattern that ends in `.md` AND contains no glob magic
|
||||
characters bypasses the glob adapter entirely and is added directly to the result
|
||||
after an `fs.exists` check.
|
||||
- Default ignore list — `node_modules/**`, `**/node_modules/**`, `dist/**`,
|
||||
`**/dist/**`. Callers cannot override this list at the resolver layer (the
|
||||
intent is to keep all linter walks consistent across the project).
|
||||
- Set-based deduplication — two patterns that match the same file produce one entry.
|
||||
- Sorted output — paths returned in lexicographic order so reports are reproducible
|
||||
across runs.
|
||||
- Injectable `FsAdapter` and `GlobAdapter` seams; the default `nodeGlobAdapter` wraps
|
||||
the `glob` npm package.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
nodeGlobAdapter,
|
||||
resolveMarkdownTargets,
|
||||
} from "@suaveplan/linter/glob-resolver";
|
||||
import { nodeFsAdapter } from "@suaveplan/linter/markdown-fixer";
|
||||
|
||||
const files = await resolveMarkdownTargets({
|
||||
patterns: ["docs/**/*.md", "README.md"],
|
||||
fs: nodeFsAdapter,
|
||||
glob: nodeGlobAdapter,
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Use an in-memory glob adapter to drive a hermetic test:
|
||||
|
||||
```ts
|
||||
import type { FsAdapter, GlobAdapter } from "@suaveplan/linter";
|
||||
import { resolveMarkdownTargets } from "@suaveplan/linter/glob-resolver";
|
||||
|
||||
const tree = ["a.md", "docs/b.md", "node_modules/junk.md"];
|
||||
const fs: FsAdapter = {
|
||||
async readFile() {
|
||||
return "";
|
||||
},
|
||||
async writeFile() {},
|
||||
async exists(p) {
|
||||
return tree.includes(p);
|
||||
},
|
||||
};
|
||||
const glob: GlobAdapter = {
|
||||
async find(pattern, { ignore }) {
|
||||
// ignore-aware filter so the test exercises the same contract
|
||||
return tree.filter(
|
||||
(f) =>
|
||||
!ignore.some((i) => f.startsWith(i.replace("/**", ""))) &&
|
||||
f.endsWith(".md")
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const targets = await resolveMarkdownTargets({
|
||||
patterns: ["**/*.md", "a.md"],
|
||||
fs,
|
||||
glob,
|
||||
});
|
||||
// targets === ['a.md', 'docs/b.md'] (node_modules ignored, deduped)
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `resolveMarkdownTargets(options): Promise<string[]>`
|
||||
|
||||
- `options.patterns: ReadonlyArray<string>` — required.
|
||||
- `options.cwd?: string` — defaults to `"."`.
|
||||
- `options.fs: FsAdapter` — required (used for the single-file existence check).
|
||||
- `options.glob?: GlobAdapter` — defaults to `nodeGlobAdapter`.
|
||||
|
||||
Throws `LinterError(LINTER.FILE_NOT_FOUND)` when a pattern looks like a file path
|
||||
(ends in `.md` AND has no magic chars) but does not exist.
|
||||
|
||||
### `nodeGlobAdapter: GlobAdapter`
|
||||
|
||||
Default adapter; forwards to `glob(pattern, { ignore, cwd })`.
|
||||
|
||||
### `DEFAULT_IGNORE: ReadonlyArray<string>`
|
||||
|
||||
The unmodifiable ignore list applied on every glob invocation.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The magic-character detection regex (`/[*?[\]{}]/`) matches any of the glob
|
||||
meta-characters. A pattern containing none of them is treated as a literal path —
|
||||
this avoids the `glob` library treating `README.md` as a pattern with side effects
|
||||
on a case-insensitive filesystem.
|
||||
- The `cwd` option is forwarded verbatim to the glob adapter. When omitted it falls
|
||||
through as `"."`, which is the behaviour the upstream `processMarkdownFiles` relied
|
||||
on implicitly.
|
||||
- The result is materialised into a `Set` then sorted to a regular array. Returning
|
||||
the `Set` directly would force callers to handle iteration order; the explicit
|
||||
`[...].sort()` keeps the contract simple.
|
||||
- The resolver never reads file contents — only `fs.exists` for the single-file fast
|
||||
path. This keeps the resolver O(patterns) rather than O(matches × file size).
|
||||
Reference in New Issue
Block a user