unist-util-stringify-position
Serializes a unist node, position, or point into a human-readable location string for error and warning messages.
Repository Health
Technical Analysis
unist-util-stringify-position is a small unist ecosystem utility that turns positional information — a node’s position, a standalone position object, or a single point — into a compact, human-readable string such as 2:5-2:6 (a range) or 5:2 (a single point). It is one of the foundational utilities used across the unified/remark/rehype/mdast toolchain wherever a tool needs to tell a user where in the original source document something happened.
The package exports a single function, stringifyPosition, which accepts any of the three positional shapes (or null/undefined) and always returns a string, falling back to '' for unrecognized input and to 1:1 style defaults for missing line/column indices rather than throwing. This defensive, never-throw design is what makes it safe to call from deep inside linter and compiler error-reporting paths without adding new failure modes to already-failing code.
What You Get
- A single
stringifyPositionfunction - takes a node, position, or point and returns a formatted location string - Range formatting - renders
position-shaped input (or a node’s.position) asls:cs-le:ce, e.g.2:5-2:6 - Point formatting - renders a single
point-shaped input asl:c, e.g.5:2 - Graceful fallback behavior - returns an empty string for unrecognized input and defaults missing line/column values to
1, never throwing - Full TypeScript types - ships with complete type coverage (enforced at 100% via
type-coverage) and JSDoc-based type annotations - Zero runtime dependencies beyond
@types/unist- the single production dependency is a types-only package, keeping the runtime footprint minimal
Common Use Cases
- Linter/compiler error messages - a remark/rehype/eslint-style plugin reports “unexpected token at 3:14” by calling
stringifyPositionon the offending node - Custom lint rule authoring - plugin authors building unified/remark ecosystem lint rules use it to format the location prefix of warning messages
- AST debugging tooling - developer tools that print syntax trees use it to annotate each node with a readable source location
- Building higher-level unist utilities - other unist-util-* packages (and unified itself) call it internally whenever they need to render a position as text rather than work with the raw object
Under The Hood
Architecture
The entire implementation lives in lib/index.js (re-exported from the package root via index.js) as three small, purely functional helpers: stringifyPosition (the public entry point), and two private helpers position() and point() that recursively reduce a node/position down to its two constituent points and then to their line:column pairs. There is no internal state, no classes, and no branching complexity beyond a small cascade of 'position' in value / 'start' in value / 'line' in value duck-type checks that let the same function transparently accept a full AST node, a bare position object, or a bare point object. Because the function is a pure transform with no I/O or side effects, the entire module could be inlined anywhere without changing behavior — its only “architecture” is the deliberate flatness that keeps it embeddable in any code path, including hot error-handling paths.
Tech Stack
The package is ESM-only (Node.js 16+), written in plain JavaScript with JSDoc type annotations rather than a .ts source file, and compiled to declaration files only via tsc --build with emitDeclarationOnly. Its sole runtime dependency is @types/unist (a types-only package, so it adds no runtime code). Dev tooling is the standard syntax-tree/unified project template: xo (ESLint preset) plus prettier for formatting, remark-cli with remark-preset-wooorm for linting the README itself, c8 for coverage, and type-coverage to enforce 100% type coverage as a build gate.
Code Quality
Testing uses Node’s built-in node:test and node:assert/strict (no external test framework) in a single test.js file that exhaustively covers every input shape: undefined, null, string, number, empty object, node without position, node with several kinds of malformed position, node with valid position, a position without points, a position with invalid points, and every point-only variant — effectively enumerating the function’s full branch space. c8 --100 enforces 100% coverage as part of npm test. There are no explicit error/exception paths in the source at all (the function is designed to never throw), so “error handling” quality here is really about the exhaustive defaulting logic being fully tested, which it is. Naming is short and consistent with the rest of the syntax-tree org’s utilities.
What Makes It Unique The package makes no novel technical claims — it is deliberately the smallest possible implementation of one very narrow formatting concern, and its value is in being the single shared, exhaustively-tested implementation that the entire unified/remark/rehype ecosystem relies on instead of every downstream plugin reinventing its own position-to-string logic (and inevitably handling the missing/partial-position edge cases differently).