Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
047fb30
chore: security updates
Gabrielfernandes7 Oct 22, 2026
bf20408
fix: update GitHub Actions to v4 for Node 22.x support
Gabrielfernandes7 Oct 22, 2026
5c41eac
ci: downgrade actions to v3 (stable with Node 24)
Gabrielfernandes7 Oct 22, 2026
f8face1
test: fix error message regex for Node 14+ compatibility
Gabrielfernandes7 Oct 22, 2026
c23794c
Merge pull request #1 from Gabrielfernandes7/chore/security-updates
Gabrielfernandes7 Oct 22, 2026
4b810b7
chore: update dev dependencies (Jest, TypeScript, Prettier)
Gabrielfernandes7 Oct 22, 2026
1c25a3e
chore: update dev dependencies (Jest, TypeScript, Prettier)
Gabrielfernandes7 Oct 22, 2026
6e5ccc8
Merge pull request #2 from Gabrielfernandes7/chore/dependency-updates
Gabrielfernandes7 Oct 22, 2026
731af2b
chore: add ESLint, coverage thresholds, and pre-commit hooks
Gabrielfernandes7 Oct 22, 2026
590e2aa
chore: add ESLint, coverage thresholds, and pre-commit hooks (Priorit…
Gabrielfernandes7 Oct 22, 2026
3726a7d
fix: downgrade lint-staged to support Node 18/20 and update test fixt…
Gabrielfernandes7 Oct 22, 2026
6d54476
Merge pull request #3 from Gabrielfernandes7/chore/test-coverage-and-…
Gabrielfernandes7 Oct 22, 2026
56ffcae
feat: convert pug-lexer to TypeScript
Gabrielfernandes7 Oct 22, 2026
ca6876d
fix: pug-lexer test references and recursive call
Gabrielfernandes7 Oct 22, 2026
eee1988
fix: add type declaration for pug-error module
Gabrielfernandes7 Oct 22, 2026
9306cb9
Merge pull request #4 from Gabrielfernandes7/chore/typescript-migrati…
Gabrielfernandes7 Oct 22, 2026
6cee4f6
docs: update documentation and add CLAUDE.md
Gabrielfernandes7 Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash -c 'f=$(jq -r \".tool_input.file_path // empty\"); case \"$f\" in *.js) npx --no-install prettier --write \"$f\" ;; esac' 2>/dev/null || true"
}
]
}
]
}
}
19 changes: 19 additions & 0 deletions .claude/skills/verify/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
name: verify
description: Run the same checks CI runs (Prettier check, ESLint, Jest) and report the results. Use when the user asks to verify, check, or validate the repo before a commit or pull request.
disable-model-invocation: true
---

Run the repository's CI checks in this order and report the result of each. Do not fix anything automatically.

1. `yarn prettier:check`. Checks formatting of all `*.js` files.
2. `yarn lint`. Runs ESLint on the repository.
3. `yarn test`. Runs `yarn build` first (through `pretest`), then the full Jest suite.

Rules:

- Run the steps in order, but keep going after a failure so the user sees every failing check.
- If `$ARGUMENTS` is given, treat it as a Jest path or pattern and run `npx jest $ARGUMENTS` in step 3 instead of the full `yarn test`. Still run steps 1 and 2.
- Report each step as PASS or FAIL. For a failure, include the relevant error output, not a paraphrase.
- Do not say a check passed unless its command exited with status 0.
- End with a one-line summary: all checks passed, or which checks failed.
8 changes: 8 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules/
dist/
build/
**/__snapshots__/
*.snap
.git/
.github/
coverage/
28 changes: 28 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"env": {
"node": true,
"es6": true,
"jest": true
},
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "script"
},
"rules": {
"no-console": "warn",
"no-unused-vars": "warn",
"semi": ["warn", "always"],
"quotes": ["warn", "single"],
"comma-dangle": "off",
"no-var": "off",
"prefer-const": "warn"
},
"overrides": [
{
"files": ["**/test/**"],
"rules": {
"no-unused-expressions": "off"
}
}
]
}
2 changes: 1 addition & 1 deletion .github/workflows/rollingversions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:

strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
node-version: [18.x, 20.x, 22.x]

steps:
- uses: actions/checkout@v2
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ jobs:

strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
node-version: [18.x, 20.x, 22.x]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: yarn install --frozen-lockfile
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ cov-pt*
package-lock.json

scripts/tsconfig.json
# generated by build (tsc / prepublish); hand-written lib/ dirs are source
packages/pug-error/lib
packages/pug-lexer/lib
packages/pug-runtime/lib
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npx lint-staged
3 changes: 3 additions & 0 deletions .lintstagedrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"*.js": ["eslint --fix", "prettier --write"]
}
118 changes: 118 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Architecture

This document describes how Pug turns a template string into a rendered HTML string, which package owns each step, and where to extend the pipeline. It is written for contributors. For setup and the development workflow, see [CONTRIBUTING.md](./CONTRIBUTING.md).

## Overview

Pug is split into small packages that each perform one stage of compilation. The `pug` package is the public entry point: it wires the stages together, handles options and caching, and exposes `compile`, `render`, `compileClient` and the file-based variants.

A template goes through this pipeline:

```mermaid
flowchart LR
src[Template string] --> lex[pug-lexer]
lex --> strip[pug-strip-comments]
strip --> parse[pug-parser]
parse --> load[pug-load]
load --> filters[pug-filters]
filters --> link[pug-linker]
link --> gen[pug-code-gen]
gen --> fn[Template function]
rt[pug-runtime] -. helpers used at render time .-> fn
```

The stages run in this order inside `compileBody` in `packages/pug/lib/index.js`. The plugin hooks listed in the next section run between them.

## Stages

| # | Package | Input | Output | Responsibility |
| --- | --- | --- | --- | --- |
| 1 | `pug-lexer` | Source string | Token array | Splits the source into tokens: tags, classes, ids, attributes, text, code, mixins, includes and so on. Written in TypeScript (`src/`), built to `lib/`. |
| 2 | `pug-strip-comments` | Token array | Token array | Removes comment tokens so later stages never see them. |
| 3 | `pug-parser` | Token array | AST | Builds a tree of nodes (`Tag`, `Text`, `Code`, `Mixin`, `Filter`, `Include`, `Extends`, `Block`, and so on). |
| 4 | `pug-load` | AST | AST with dependencies | Finds `include` and `extends` targets, reads them from disk (or the `read` hook), and runs the lexer and parser on each one. Uses `pug-walk` to find the nodes. |
| 5 | `pug-filters` | AST | AST with filter output | Runs every `:filter` block (for example `:markdown-it` or `:cdata`) at compile time and replaces the filter node with the resulting text. |
| 6 | `pug-linker` | AST with dependencies | Single AST | Resolves `extends` and `block` relationships so the result is one tree with no unresolved dependencies. |
| 7 | `pug-code-gen` | Linked AST | JavaScript source | Generates the body of a template function. Uses `pug-attrs` for attribute code. |
| 8 | Wrapping (in `pug`) | Function body | Template function | Turns the generated body into a function. By default it uses `pug-runtime/wrap`, which imports `pug-runtime`. With `inlineRuntimeFunctions` the helpers are inlined into the function via `new Function`. |

Each stage throws errors built by `pug-error`, which attach the filename and line of the offending source so users see a useful location.

## Runtime

`pug-runtime` is a small set of helpers that generated templates call while they run: `escape` for HTML escaping, `attr` / `attrs` / `classes` / `style` for attribute output, `merge` for combining attributes, and `rethrow`, which adds source context to runtime errors. It has no dependencies. Because it is shipped with every compiled template, changes to it affect the size and speed of all output and must stay backward compatible.

`pug-attrs` generates the code for a tag's attributes. It depends on `pug-runtime` because the generated code calls its helpers.

## Packages at a glance

| Package | Depends on (internal) | Role |
| --- | --- | --- |
| `pug` | all stage packages and `pug-runtime` | Public API and pipeline orchestration |
| `pug-lexer` | `pug-error` | Tokenizer |
| `pug-strip-comments` | `pug-error` | Comment removal |
| `pug-parser` | `pug-error` | Tokens to AST |
| `pug-load` | `pug-walk` | Include / extends loading |
| `pug-filters` | `pug-error`, `pug-walk` | Compile-time filters |
| `pug-linker` | `pug-error`, `pug-walk` | Extends / block resolution |
| `pug-code-gen` | `pug-attrs`, `pug-error`, `pug-runtime` | Code generation |
| `pug-attrs` | `pug-runtime` | Attribute code generation |
| `pug-runtime` | none | Helpers used by generated code |
| `pug-walk` | none | AST traversal |
| `pug-error` | none | Error objects with source locations |

Keep dependencies flowing in this direction. `pug-runtime`, `pug-walk` and `pug-error` are leaves and must not depend on any other Pug package.

## Public API

Defined in `packages/pug/lib/index.js`:

- `compile(str, options)` returns a template function.
- `render(str, options)` compiles and runs a template in one step.
- `compileFile(path, options)` and `renderFile(path, options)` read the template from disk. These use `pug.cache` when `cache` is enabled.
- `compileClient(str, options)` and `compileFileClient(path, options)` produce a function source string for use in the browser.
- `compileClientWithDependenciesTracked` returns the source along with the list of files it depends on, which bundlers use to rebuild.
- `__express(path, options, fn)` is the Express integration.
- `runtime` exposes `pug-runtime`.
- `filters` is the global filter registry. Filters passed in `options.filters` override entries with the same name.

## Extension points

### Plugins

Plugins are objects passed in `options.plugins`. Most hooks transform the output of a stage and run in pipeline order: `preLex`, `postLex`, `preParse`, `postParse`, `preLoad`, `postLoad`, `preFilters`, `postFilters`, `preLink`, `postLink`, `preCodeGen`, `postCodeGen`.

Three hooks replace a stage instead: `resolve` (locating included files), `read` (reading their contents) and `generateCode`. Only one plugin may implement each of these, and Pug throws if two do. The `lex` and `parse` entries add hooks inside the lexer and parser themselves.

### Filters

Filters are the `:name` blocks in a template. They are looked up in this order:

1. `options.filters`, then the global `pug.filters`.
2. A `jstransformer-<name>` package, resolved relative to the template file.

`options.filterOptions` supplies default options per filter name, and `options.filterAliases` maps one filter name to another. The filter guide in [packages/pug-filters/README.md](./packages/pug-filters/README.md) has examples.

## Where to make a change

| If you want to... | Start in |
| --- | --- |
| Add or change syntax (a new token) | `pug-lexer` (`src/`), then `pug-parser` |
| Change how a node compiles to JavaScript | `pug-code-gen` |
| Change attribute output | `pug-attrs` |
| Change include or extends behavior | `pug-load` and `pug-linker` |
| Change filter behavior | `pug-filters` |
| Change what generated templates call at runtime | `pug-runtime` (keep it backward compatible) |
| Change options, caching or the public API | `packages/pug/lib/index.js` |

Most syntax changes touch more than one stage. Add a test in each package that changes, and an end-to-end test in `packages/pug`.

## TypeScript status

`pug-lexer` and `pug-error` are written in TypeScript. Their TypeScript source lives in `src/`, and the build writes JavaScript and `.d.ts` files to `lib/`. The other packages are still JavaScript, and [TYPESCRIPT_MIGRATION_ROADMAP.md](./TYPESCRIPT_MIGRATION_ROADMAP.md) gives the order in which they will be converted. [TYPESCRIPT_CONVERSION_GUIDE.md](./TYPESCRIPT_CONVERSION_GUIDE.md) describes the steps for converting one package.

## Further reading

- [CONTRIBUTING.md](./CONTRIBUTING.md): setup, commands and pull request process
- [SECURITY.md](./SECURITY.md): threat model and vulnerability reporting
- [packages/pug-filters/README.md](./packages/pug-filters/README.md): filter authoring guide
26 changes: 26 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Setup, workflow and pipeline details live in CONTRIBUTING.md and ARCHITECTURE.md. Read them before large changes.

## Commands

- Yarn 1 (classic) with `yarn.lock` v1. Use `yarn`, not npm.
- `yarn test` runs `yarn build` first (`pretest`). Tests run against compiled `lib/` output, so run `yarn build` after changing TypeScript sources in `src/`.
- Run one test file without the full build: `npx jest packages/pug-filters/test/filter-aliases.test.js`
- `yarn lint` and `yarn lint:fix` run ESLint. `yarn format` and `yarn prettier:check` cover only `*.js` files. Markdown is not formatted by Prettier.
- CI runs `yarn prettier:check` and then `yarn test` on Node 18, 20 and 22.

## Generated files

- `yarn build` runs `scripts/prebuild.js`, which writes a `LICENSE` into any package that lacks one, adds a `name` to `package.json` when it is missing, and regenerates each TypeScript package's `tsconfig.json`. Do not hand-edit those generated files.
- `pug-lexer` and `pug-error` are TypeScript. Edit `src/`, never their `lib/` output.

## Conventions

- Commit messages use Conventional Commits prefixes (`feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`, `ci:`), as in the existing history.
- Branch names use the same prefixes, for example `chore/documentation`. Pull requests target `master`.
- Do not edit `version` fields or the `VERSION_TABLE` block in the root README. Releases are published by the `rollingversions` workflow.
- Snapshot tests live in `__snapshots__/`. Update a snapshot with `-u` only when the output change is intended, and review the diff.
- Every package has its own tests in `packages/<name>/test/`. Other packages depend on compiled output, so run the whole `yarn test` for changes to shared code such as `pug-runtime` or `pug-code-gen`.
118 changes: 118 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Contributing to Pug

Thanks for helping improve Pug! This guide covers how to set up the monorepo, run the checks CI runs, and open a pull request. For how the code is organized internally, read [ARCHITECTURE.md](./ARCHITECTURE.md) first.

## Before you start

- For bug reports, feature requests and questions, [open an issue](https://github.com/pugjs/pug/issues/new) first if the change is large or changes behavior. Small fixes and documentation changes can go straight to a pull request.
- Security issues must **not** go in public issues. See [SECURITY.md](./SECURITY.md).

## Requirements

- **Node.js 18 or newer.** CI runs the test suite on Node 18.x, 20.x and 22.x, so make sure your change works on all three.
- **Yarn 1 (classic).** The repository uses `yarn.lock` in the v1 format, and CI installs with `yarn install --frozen-lockfile`. Do not use npm or pnpm to install dependencies, because they would create lockfiles that the repository does not track.

## Setting up

```bash
git clone https://github.com/pugjs/pug.git
cd pug
yarn install
yarn build
yarn test
```

`yarn test` runs `yarn build` first (via the `pretest` script), so you only need to run `yarn build` by hand when you want to build without running tests.

## Repository layout

Pug is a Yarn workspaces monorepo. Every directory under `packages/` is a separately published npm package with its own `package.json`, README, and tests.

```
packages/
pug/ # Public entry point: compile, render, and express integration
pug-lexer/ # String -> tokens (TypeScript; source in src/, output in lib/)
pug-parser/ # Tokens -> AST
pug-load/ # Resolves include / extends and loads dependency files
pug-linker/ # Links extends / blocks into a single AST
pug-filters/ # Runs :filter blocks at compile time
pug-code-gen/ # AST -> JavaScript template function source
pug-attrs/ # Generates code for attributes
pug-runtime/ # Helpers used by generated templates at render time
pug-strip-comments/ # Removes comment tokens
pug-walk/ # AST walker used by other packages
pug-error/ # Error objects with source location (TypeScript)
scripts/ # Build and test helpers (prebuild, Jest serializers)
```

For what each package does and how they connect, see [ARCHITECTURE.md](./ARCHITECTURE.md). The TypeScript migration status is tracked in [TYPESCRIPT_MIGRATION_ROADMAP.md](./TYPESCRIPT_MIGRATION_ROADMAP.md).

## Common commands

| Command | What it does |
| --- | --- |
| `yarn build` | Runs `scripts/prebuild`, builds every package that has a `build` script, and compiles the `scripts` project. |
| `yarn test` | Runs `yarn build` and then the Jest test suite. |
| `yarn watch` | Runs Jest in watch mode. |
| `yarn coverage` | Runs Jest with coverage. |
| `yarn lint` | Runs ESLint on the whole repository. |
| `yarn lint:fix` | Runs ESLint and applies automatic fixes. |
| `yarn format` | Formats all `.js` files with Prettier. |
| `yarn prettier:check` | Fails if any `.js` file is not formatted. This is what CI runs. |

### Generated files

`scripts/prebuild.js` generates files as part of `yarn build`. It creates a `LICENSE` file in each package that does not have one, and writes the TypeScript composite configuration for packages that use TypeScript. Do not edit generated `lib/` output by hand. For `pug-lexer` and `pug-error`, edit the TypeScript source in `src/` and rebuild.

## Coding standards

- **Formatting:** Prettier, configured in `.prettierrc.js` (single quotes, no bracket spacing, trailing commas). `.editorconfig` sets two-space indentation, LF line endings and a final newline.
- **Linting:** ESLint, configured in `.eslintrc.json`. The rules there are explicit (single quotes, semicolons, `prefer-const`, and so on). `eslint-config-airbnb-base` is installed but not extended, so it does not apply.
- **Pre-commit hook:** Husky runs `lint-staged` on commit. Staged `*.js` files are passed through `eslint --fix` and `prettier --write`. If the hook changes files, stage them again before committing.
- **Coverage:** `package.json` enforces global Jest coverage thresholds of 80% statements, 70% branches, 80% functions and 80% lines. A change that drops coverage below these thresholds will fail `yarn coverage`.
- **Match the surrounding code.** Keep the naming, comment density and style of the file you are editing.

## Writing tests

- Tests live in `packages/<name>/test/`. Snapshot tests keep their snapshots in `__snapshots__/`.
- Add a test for every bug fix. Name the test after the behavior it checks, not the implementation.
- Only update a snapshot when the output change is intended. Review the snapshot diff in the pull request.
- If you change compiled output, run `yarn test` on the whole repository, not only the package you edited, because other packages depend on the output.

## Commits and pull requests

### Commit messages

The history follows [Conventional Commits](https://www.conventionalcommits.org/) prefixes, for example:

- `feat:` a new feature
- `fix:` a bug fix
- `refactor:` a change that does not change behavior
- `test:` tests only
- `docs:` documentation only
- `chore:` tooling, dependencies and maintenance
- `ci:` CI configuration

Write the subject in the imperative mood and keep it short. Use the body to explain *why* the change is needed when that is not obvious from the diff.

### Branches

Use a descriptive branch name with the same prefix, for example `fix/doctype-validation` or `docs/filter-guide`.

### Pull requests

- Target `master`. CI runs on every pull request to `master`.
- Keep each pull request focused on one change. Separate refactors from behavior changes.
- Before requesting review, make sure these pass locally:
1. `yarn lint`
2. `yarn prettier:check`
3. `yarn test`
- Describe what changed and how you tested it. Link the issue it addresses, if there is one.

### Versioning and changelogs

Releases are published by the GitHub Actions workflow in `.github/workflows/rollingversions.yml`, which uses `rollingversions`. Do not edit `version` fields or the `VERSION_TABLE` block in the root `README.md` by hand. Ask a maintainer if your change needs a changelog entry.

## Getting help

If you are unsure about the approach, open a draft pull request or an issue before writing a large change. The [chat room](https://gitter.im/pugjs/pug) is linked from the root README for discussion.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ pug-strip-comments | [![NPM version](https://img.shields.io/npm/v/pug-strip-comm
pug-walk | [![NPM version](https://img.shields.io/npm/v/pug-walk?style=for-the-badge)](https://www.npmjs.com/package/pug-walk)
<!-- VERSION_TABLE -->

## Contributing and Documentation for Developers

- [CONTRIBUTING.md](./CONTRIBUTING.md): setting up the monorepo, running tests and lint, and opening pull requests
- [ARCHITECTURE.md](./ARCHITECTURE.md): how a template moves through the compiler pipeline and what each package does
- [SECURITY.md](./SECURITY.md): threat model, security assumptions and how to report a vulnerability
- [Filter guide](./packages/pug-filters/README.md#filter-guide): writing and using filters, with examples

## Rename from "Jade"

This project was formerly known as "Jade". However, it was revealed to us that "Jade" is a registered trademark; as a result, a rename was needed. After some discussion among the maintainers, **"Pug"** was chosen as the new name for this project. As of version 2, "pug" is the official package name.
Expand Down
Loading