npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pugneum-linker

v1.2.0

Published

Link together multiple pugneum abstract syntax trees

Readme

pugneum-linker

Link multiple pugneum ASTs together using include/extends

Installation

npm install pugneum-linker

Node.js 22 or newer is required.

Usage

var link = require('pugneum-linker');

link(ast, options)

Assemble inclusion and inheritance, then resolve document-level references, footnotes, table-of-contents nodes, and structured-AST lints. The returned tree is a new owned copy; the input AST and dependency ASTs are not consumed and may be reused after success or failure.

The linker doesn't read the file system to resolve and parse included and extended files. Thus, the main AST must already have the ASTs of the included and extended files embedded in the FileReference nodes. pugneum-loader is designed to do that.

If the tree contains filters, use the staged API so nodes generated by a filter participate in document-global resolution:

var filter = require('pugneum-filterer');

var assembled = link.assemble(loaded, options);
var filtered = filter(assembled, options);
var resolved = link.resolve(filtered, options);

link(ast, options) is the convenient equivalent for already-filtered trees or trees with no filters. link.assemble(ast, options) performs only inheritance and inclusion. link.resolve(ast, options) performs only document-global resolution and linting. Before resolving those document facts, link() and link.resolve() lower mixin declarations and calls into independent call-site AST instances. The resolved result therefore contains the nodes that will actually render rather than reusable declarations or ignored caller blocks. All three return owned copies without mutating their input tree.

options can contain the following properties:

  • sources (object): a map from filename to that file's Pugneum source string, used to attach source context (±3 lines and a caret) to diagnostics. This is normally populated by pugneum-loader. For the entry file, scalar source and filename options are also used as a fallback.
  • warnings (array): an extensible array that receives non-fatal diagnostics. If omitted, the linker establishes options.warnings; therefore a frozen options object must supply its own warnings array.
  • maxLinkDepth (number): a safe integer from 0 through 256, defaulting to 256. It counts followed include and extends edges in one combined chain: 0 allows a plain root but no dependency edge, and a failure is reported at the edge that would exceed the limit. Caller properties that resemble private recursion state are ignored.
  • compilationLimits (object) or compilationContext (created by pugneum-error): a local budget or a shared build-wide budget. Linking charges AST validation, owned/inherited/yielded materialization, cloned binary bytes, and mixin invocations. Every yield receives a separately charged owned copy, so wide acyclic fan-out cannot bypass maxLinkDepth.

Inheritance blocks

A replace-mode block name in document structure declares an inheritance slot. block append name and block prepend name modify an existing declared slot; an append/prepend occurrence alone does not create a slot that descendants can override. Validation and composition use this same effective-slot model.

Named blocks inside mixin declarations or calls belong to that mixin and are never template-inheritance targets. Put the inheritance block outside the mixin call, then invoke or fill the mixin inside the overriding block when both features are needed. An extending root may contain inheritance blocks, mixin declarations, and document-global references declarations.

Diagnostics

The linker throws PUGNEUM:-coded errors (e.g. UNDEFINED_REFERENCE, DUPLICATE_REFERENCE, UNDEFINED_FOOTNOTE, DUPLICATE_FOOTNOTE, UNEXPECTED_BLOCK, LINK_DEPTH_EXCEEDED, MISSING_YIELD) for fatal problems, and pushes the following non-fatal warnings into options.warnings:

  • DUPLICATE_ID — two elements share the same id.
  • IMG_WITHOUT_ALT — an img has no alt attribute.
  • UNUSED_REFERENCE — a references entry is defined but never used.
  • UNUSED_FOOTNOTE — a footnotes entry is defined but never referenced.
  • EMPTY_TOC — a toc produced nothing (no headings with an explicit id).

HTML tag and attribute names used by these lints and by table-of-contents discovery have ASCII-case-insensitive identity. Their authored spelling in the AST is preserved.

These are structured-AST lints, not an HTML parser or validator. Markup hidden inside raw includes, raw HTML, verbatim text, or filter output represented as opaque text is not inspected for duplicate IDs or missing image alternatives.

The linker also resolves:

  • Reference links/images — @[name] and ![name] nodes are resolved against a references block. Definitions can include optional default display text: name url Default Text.
  • Footnotes — ^[name] nodes are resolved against a footnotes block. Names may contain only ASCII letters, digits, -, and _. Reachable nested references are numbered in first-discovery order, then the linker generates a <section role="doc-endnotes"> with DPUB-ARIA roles. Generated IDs are currently derived directly from names and can collide with adversarial names or author IDs; DUPLICATE_ID reports the collision but cannot repair it.
  • Table of contents — toc nodes are replaced with a <nav role="doc-toc"> containing nested <ol> lists. Any structured heading tag with an explicit non-empty string id containing no ASCII whitespace can participate; the ID need not use shorthand syntax.

Scope of references and footnotes

Reference (@[name], ![name]), footnote (^[name]), and toc resolution runs as a single pass over the fully assembled document — after extends and include are spliced in and filters have run. Resolution is therefore document-global: a @[name]/^[name] use matches any references/footnotes block in the assembled tree, regardless of which physical file the use or the definition came from (a use in an included partial resolves against a block in the including file, and vice versa). A use with no matching definition anywhere in the document raises UNDEFINED_REFERENCE / UNDEFINED_FOOTNOTE; because the namespace is document-wide, duplicate names collide across files (DUPLICATE_REFERENCE / DUPLICATE_FOOTNOTE), and only one footnotes block is allowed per document (DUPLICATE_FOOTNOTES_BLOCK). Running after the filterer is also what lets references, footnotes, and toc emitted by a pugneum-type filter (e.g. a :table cell) resolve against the document's blocks.

BlockComment descendants are outside that document-global scope because the renderer serializes a buffered body into one HTML comment string (and discards an unbuffered body). Headings, IDs, images, references, footnotes, and toc inside either form therefore do not create live navigation, endnotes, usage, or lint facts. A buffered reference keeps only its local label/alt text, a footnote reference remains readable literal ^[name] text, and declarations/toc emit nothing inside the comment.

An unused footnote definition is discarded content. Its reference links and reference declarations do not participate in resolution or mark global definitions as used. When a footnote is reached from the document—or transitively from another reached footnote—its definition body joins the same document-global reference namespace and is resolved normally.

Mixin calls are instantiated before this document-global pass. An unused declaration contributes no IDs, headings, references, footnotes, images, or lints; each rendered call contributes an independent instance; and only caller content consumed by a named or unnamed slot participates. Parameter values are resolved while lowering, including values used by heading IDs, visible TOC text, attributes, reachable footnote bodies, and deferred reference URLs.

License

MIT