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

td-antlers-linter

v0.2.2

Published

`antlers-linter` is a small Antlers template linting library built on top of [`td-antlers-parser`](https://www.npmjs.com/package/td-antlers-parser). It parses template text into an AST, walks the node tree, and runs rule listeners against each node to pro

Readme

antlers-linter

antlers-linter is a small Antlers template linting library built on top of td-antlers-parser. It parses template text into an AST, walks the node tree, and runs rule listeners against each node to produce diagnostics and fix suggestions.

The package exposes both a programmatic API and a CLI (antlers-lint) suitable for CI use. The source is authored in TypeScript and compiled to dist/ for consumption.

Project Structure

.
├── package.json
├── tsconfig.json
├── bin
│   └── antlers-lint.mjs
├── src
│   ├── cli.ts
│   ├── fix.ts
│   ├── formatters.ts
│   ├── index.ts
│   ├── linter.ts
│   ├── types.ts
│   └── rules
│       └── *.ts
├── type-tests
│   └── public-api.ts
└── test
    ├── cli.test.ts
    ├── config.test.ts
    ├── custom-rules.test.ts
    ├── lint-text.test.ts
    └── rules
        └── *.test.ts

What each file does

  • bin/antlers-lint.mjs is the thin shebang entry that npm/npx resolves to when invoking the CLI.
  • src/cli.ts parses CLI arguments, discovers files via globs, loads config, runs the linter, applies --fix, and writes formatter output.
  • src/fix.ts applies non-overlapping auto-fixes from diagnostics to a source string.
  • src/formatters.ts implements the stylish, json, and github-actions reporters.
  • src/index.ts re-exports the public API and public TypeScript types.
  • src/types.ts declares the linter, rule, config, and diagnostic contracts.
  • src/linter.ts contains the core linter engine:
    • built-in rule registration
    • the recommended default config
    • config loading from disk
    • AST traversal
    • rule context creation
    • disable-directive handling
    • diagnostic and suggestion generation
  • src/rules/ contains the built-in Antlers rules shipped by the package.
  • type-tests/public-api.ts is a compile-time smoke test for the exported API and types.
  • test/cli.test.ts covers argument parsing, file discovery, formatter output, --fix, --quiet, --max-warnings, --config, and exit codes.
  • test/config.test.ts covers config discovery and merge behavior for .antlerslintrc.json, antlerslint.config.js, and package.json.
  • test/custom-rules.test.ts verifies custom rule registration, node listeners, and rule-context state.
  • test/lint-text.test.ts covers the default recommended config, suggestions, inline disables, severity overrides, emitted declarations, and starter-rule coverage.
  • test/rules/ contains focused behavioral tests for individual built-in rules.

Linter Flow

The linter currently works in this order:

  1. Parse template text with Document.fromText(...).
  2. Load the effective config by merging the recommended defaults with any provided config.
  3. Collect inline antlers-lint-disable ... directives and attach them to the next lintable node.
  4. Instantiate enabled rule listeners with a shared traversal state.
  5. Walk the Antlers AST depth-first and invoke matching listeners such as enter, exit, or VariableNode.
  6. Return sorted diagnostics with severity, message, source range, source snippet, and optional suggestions/fixes.

CLI

The package ships an antlers-lint binary that works with npx in CI:

npx antlers-lint [options] [files/globs]

Options:

| Flag | Description | | --------------------- | -------------------------------------------------------------------- | | --config <path> | Path to a .json or ESM .js config file | | --fix | Rewrite files by applying every non-overlapping auto-fix | | --format <reporter> | Output reporter: stylish (default), json, or github-actions | | --quiet | Suppress warnings and only surface errors | | --max-warnings <n> | Exit 1 when warnings exceed <n> (default: no threshold) | | --no-config | Ignore any discovered config file and use recommendedConfig | | --ignore-pattern <p> | Glob pattern to exclude from linting (repeatable) | | --no-default-ignore | Disable built-in ignore patterns (vendor, node_modules) | | --version | Print the installed antlers-linter version | | -h, --help | Print CLI usage |

Behaviour:

  • File discovery uses globs; the default pattern is resources/views/**/*.antlers.html.
  • Positional arguments override the default and accept any mix of globs or explicit paths.
  • Discovered files matching a built-in ignore (**/vendor/**, **/node_modules/**), a config ignorePatterns entry, or a --ignore-pattern value are skipped. Pass --no-default-ignore to lint vendored templates.
  • When --fix resolves every problem the file is rewritten in place; otherwise the remaining diagnostics are reported.
  • --format json emits an ESLint-compatible array of { filePath, messages, errorCount, warningCount, ... } records.
  • --format github-actions emits ::error, ::warning, and ::notice workflow commands that GitHub annotates inline on pull requests.

Exit codes:

| Code | Meaning | | ---- | --------------------------------------------------------------- | | 0 | Clean (warnings allowed unless --max-warnings is exceeded) | | 1 | Lint errors found, or warnings exceeded --max-warnings | | 2 | Configuration or runtime problem (bad flags, unreadable config) |

Example GitHub Actions step:

- name: Lint Antlers templates
  run: npx antlers-lint --format github-actions --max-warnings 0

Public API

The package exports:

  • builtinRules
  • recommendedConfig
  • DEFAULT_IGNORE_PATTERNS — built-in ignore globs applied by the CLI
  • createLinter()
  • lintText()
  • loadConfig()
  • applyFixes() — apply diagnostic fixes to a source string
  • formatResults(), FORMATTER_NAMES, isFormatterName() — reporter helpers
  • run(), parseCliArgs(), DEFAULT_PATTERN — CLI helpers for custom integrations

Example:

import { lintText } from 'antlers-linter';

const diagnostics = lintText('{{ get:name }} {{ old:email }}');

Configuration

loadConfig() looks for configuration in this order:

  1. antlerslint.config.js
  2. .antlerslintrc.json
  3. package.json under antlersLint or antlers-lint

Supported severities are:

  • off
  • error
  • warning
  • info
  • hint

The current recommended config is:

{
  rules: {
    'no-debug-tags': 'error',
    'no-duplicate-params': 'warning',
    'no-empty-conditionals': 'warning',
    'no-nested-loops-depth': 'warning',
    'no-unclosed-tags': 'warning',
    'no-undefined-partial': 'warning',
    'no-unmatched-conditionals': 'warning',
    'no-unsanitized-request': 'warning',
    'no-raw-user-input': 'warning',
    'prefer-ternary': 'warning',
    'tag-spacing': 'warning',
    'valid-modifier-chain': 'warning'
  }
}

Rule settings can be provided as:

  • a severity string, such as 'warning'
  • a tuple, such as ['warning', { ...options }]
  • an object with severity plus rule-specific options

Ignoring files

Configs can declare additional ignorePatterns to skip vendored or generated templates alongside the built-in defaults (**/vendor/**, **/node_modules/**):

{
  "ignorePatterns": ["storage/**", "public/build/**"]
}

Built-in defaults are always applied unless --no-default-ignore is passed on the CLI. Config-level and CLI patterns are additive.

Inline Disables

The linter supports inline disable directives inside Antlers comments:

{{# antlers-lint-disable no-raw-user-input #}}
{{ old:email }}

The directive applies to the next lintable node. It also supports all as a rule id.

Built-in Rules

The package currently ships 13 built-in rules. recommendedConfig enables 12 of them by default; use-disambiguation is available but opt-in.

no-debug-tags

Flags debug helpers that should not ship in committed template source.

Current behavior:

  • Recommended severity: error
  • Default blocked tags: dump, dd, debug
  • Ignores non-debug tags and closing tags

Example:

{{ dump }}
{{ dd }}
{{ debug }}

no-duplicate-params

Flags duplicate parameters that appear more than once on the same tag.

Current behavior:

  • Recommended severity: warning
  • Reports each repeated parameter after its first occurrence on the same tag
  • Only compares parameters within a single tag

Example:

{{ partial:card src="hero" src="thumb" }}

no-empty-conditionals

Flags if and unless chains whose branches contain no rendered content.

Current behavior:

  • Recommended severity: warning
  • Checks opening if and unless nodes that are part of a matched conditional chain
  • Treats else, elseif, and elseunless as part of the same chain
  • Reports only when every branch in the chain is empty

Example:

{{ if featured }}{{ /if }}

no-nested-loops-depth

Flags inline expressions whose nested ternary depth exceeds the configured maximum.

Current behavior:

  • Recommended severity: warning
  • Default maxDepth: 2
  • Only checks inline runtime expressions, not Antlers tag nodes or conditional tag nodes
  • Reports when nested ternaries are deep enough that the expression should be extracted

Example:

{{ (a ? (b ? (c ? d : e) : f) : g) }}

no-raw-user-input

Flags rendered flashed input values that start with old: when they are output without an escaping modifier.

Current behavior:

  • Recommended severity: warning
  • Default target: old:
  • Default accepted modifiers: entities, sanitize, escape, esc, e
  • Default suggested modifier: entities
  • Only checks rendered variables, not tag expressions or condition expressions
  • Suggests appending the preferred escape modifier

Example:

{{ old:email }}

no-unclosed-tags

Flags paired Antlers tags that are opened but never closed.

Current behavior:

  • Recommended severity: warning
  • Only checks tag nodes that are not self-closing and are not conditionals
  • Ignores tags already matched with a closing pair
  • Leaves unmatched conditionals to no-unmatched-conditionals

Example:

{{ collection:blog }}
  {{ title }}

no-undefined-partial

Flags {{ partial:name }} tags that reference a view missing from resources/views.

Current behavior:

  • Recommended severity: warning
  • Resolves partial names from the current lint cwd
  • Supports both slash and dot notation when resolving view paths
  • Checks common view extensions such as .antlers.html, .blade.php, .html, and .php

Example:

{{ partial:hero }}

no-unmatched-conditionals

Flags if, elseif, and else conditionals that never resolve to a closing {{ /if }}.

Current behavior:

  • Recommended severity: warning
  • Reports unmatched opening if nodes
  • Reports orphan elseif and else branches
  • Walks conditional chains via the parser's matched-tag links

Example:

{{ if featured }}
  {{ title }}

no-unsanitized-request

Flags rendered request variables that start with get: or post: when they are output without a sanitizing modifier.

Current behavior:

  • Recommended severity: warning
  • Default targets: get:, post:
  • Default accepted modifier: sanitize
  • Only checks rendered variables, not tag expressions or condition expressions
  • Suggests appending | sanitize

Example:

{{ get:name }}
{{ post:email }}

prefer-ternary

Suggests inline ternaries for simple if/else blocks.

Current behavior:

  • Recommended severity: warning
  • Only matches if ... else ... /if chains
  • Requires both branches to be standalone Antlers expressions
  • Provides a suggestion that rewrites the whole block into {{ condition ? truthy : falsey }}

Example:

{{ if featured }}{{ title }}{{ else }}{{ fallback_title }}{{ /if }}

tag-spacing

Enforces consistent whitespace just inside {{ and }}.

Current behavior:

  • Recommended severity: warning
  • Default style: always
  • Supports always and never
  • Only normalizes the padding adjacent to the delimiters
  • Ignores multiline Antlers nodes so layout-heavy tags are not collapsed into one line

Example:

{{ title }}
{{ collection:blog }}
{{ /collection:blog }}

use-disambiguation

Requires explicit disambiguation prefixes for rendered variables, conditional variables, and non-conditional tags.

Current behavior:

  • Not enabled by recommendedConfig; choose a severity explicitly to opt in
  • Requires $ for rendered variables and variables used in condition expressions
  • Requires % for non-conditional tags and paired closing tags
  • Ignores condition tag names such as if and /if
  • Provides insertion fixes for both variable and tag prefixes

Example:

{{ $title }}
{{ if $songs }}{{ /if }}
{{ %collection:blog }}{{ /%collection:blog }}

valid-modifier-chain

Flags unknown modifier names in a chain so misspellings and unconfigured custom modifiers do not silently pass through linting.

Current behavior:

  • Recommended severity: warning
  • Checks modifier chains attached to parsed Antlers runtime nodes
  • Ignores modifiers resolved by the parser and any names listed in additionalModifiers
  • Reports each unknown modifier once

Example:

{{ title | upper | uppre }}

Rule Options

These built-in rules currently accept options:

no-debug-tags

{
  rules: {
    'no-debug-tags': ['error', {
      tagNames: ['dump', 'dd', 'debug', 'clockwork']
    }]
  }
}

no-nested-loops-depth

{
  rules: {
    'no-nested-loops-depth': ['warning', {
      maxDepth: 3
    }]
  }
}

no-unsanitized-request

{
  rules: {
    'no-unsanitized-request': ['warning', {
      targets: ['get:', 'post:'],
      sanitizeModifiers: ['sanitize']
    }]
  }
}

no-raw-user-input

{
  rules: {
    'no-raw-user-input': ['warning', {
      targets: ['old:'],
      escapeModifiers: ['entities', 'sanitize', 'escape', 'esc', 'e'],
      preferredEscapeModifier: 'entities'
    }]
  }
}

tag-spacing

{
  rules: {
    'tag-spacing': ['warning', {
      style: 'always' // or 'never'
    }]
  }
}

valid-modifier-chain

{
  rules: {
    'valid-modifier-chain': ['warning', {
      additionalModifiers: ['markdownify']
    }]
  }
}

Test Suite

The repository currently has:

The runtime suite covers:

  • config loading from .antlerslintrc.json, antlerslint.config.js, and package.json
  • custom rule registration and listener execution
  • default lintText() behavior, inline disables, severity overrides, and fix suggestions
  • emitted declaration files in dist/
  • focused rule behavior for no-debug-tags, no-duplicate-params, no-empty-conditionals, no-nested-loops-depth, no-unclosed-tags, no-undefined-partial, no-unmatched-conditionals, prefer-ternary, tag-spacing, use-disambiguation, and valid-modifier-chain

Coverage for no-unsanitized-request and no-raw-user-input currently lives in test/lint-text.test.ts, which exercises the recommended defaults and suggestion behavior for those rules.

Development

Run the linter with:

npm run lint

Run the full test suite with:

npm test

npm test runs:

  • npm run build to emit dist/
  • npm run test:types to compile the public API smoke test in type-tests/public-api.ts
  • node --test to execute the runtime suite in test/

Build the package output manually with:

npm run build