SkillAgentSearch skills...

Tenko

An 100% spec compliant ES2021 JavaScript parser written in JS

Install / Use

/learn @pvdz/Tenko
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Tenko

A "pixel perfect" 100% spec compliant JavaScript parser written in JavaScript, parsing ES6/ES2015 - ES2025.

REPL: https://pvdz.github.io/tenko/repl

  • Supports:
    • Anything stage 4 up to and including ES2025
    • Explicit resource ("using") stage 3 (under flag)
    • Regex syntax (deep)
    • Parsing modes:
      • Sloppy / non-strict
      • Web compat / AnnexB
      • Strict
      • Module
  • AST
    • Is optional, enabled by default
    • Estree (default)
    • Acorn
    • Babel (anything stage 4, except comments, didn't check after ES2021 tbh)
    • Supports location data (matching Acorn/Babel for reference)
  • Tests
    • 29k input syntax tests
    • Passes test262 suite (at least as per March 2026), without exception (./t t)

Name

The name is short for "The Parser Formerly Known As ZeParser3".

It's also an anagram for "Token", perfectly fitting this project.

In Japanese it's a divine beast ("heavenly fox" or "celestial fox"), playing into my nicknames.

REPL

You can find the REPL in repl/index.html, github link: https://pvdz.github.io/tenko/repl

The REPL runs on dev master branch and needs a very new browser due to es module syntax.

Usage

import {Tenko, GOAL_MODULE, COLLECT_TOKENS_ALL} from 'src/index.mjs';
const {
  ast,                 // estree compatible AST
  tokens,              // array of numbers (see Lexer)
  tokenCountSolid,     // number of non-whitespace tokens
  tokenCountAny,       // number of tokens of any kind
} = Tenko(
  inputCode,           // string
  {
    // Parse with script or module goal (module allows import/export)
    goalMode = GOAL_MODULE, // GOAL_MODULE | GOAL_SCRIPT | "module" | "script"
    // Do you want to collect generated tokens at all?
    collectTokens = COLLECT_TOKENS_ALL, // COLLECT_TOKENS_ALL | COLLECT_TOKENS_SOLID | COLLECT_TOKENS_NONE | COLLECT_TOKENS_TYPES | "all" | "solid" | "none" | "types"
    // Apply Annex B rules? (Only works in sloppy mode)
    webCompat = true,
    // Start parsing as if in strict mode? (Works with script goal)
    strictMode = false,
    // Output a Babel compatible AST? Note: comment nodes are not properly mirrored
    babelCompat = false,
    // Add a loc (with `{start: {line, column}, stop: {line, column}}`) to each token?
    babelTokenCompat = false,
    // Pass on a reference that will be used as the AST root
    astRoot = null,
    // Should it normalize \r and \r\n to \n in the .raw of template nodes?
    // Estree spec but makes it hard to serialize template nodes losslessly
    templateNewlineNormalization = true,
    // Pass on a reference to store the tokens
    tokenStorage = [],
    // Callback to receive the lexer instance once its created
    getLexer = null, // getLexer(lexer)
    // You use this to parse `eval` code
    allowGlobalReturn = false,
    // Target a very specific ecmascript version (like, reject async). Number; 6 - 16, or 2015 - 2025, or Infinity.
    targetEsVersion = lastVersion, // (Last supported version is currently ES2025)
    // Top-level await in Module: undefined = allow when target ES2022+; true = force on; false = force off
    toplevelAwait = undefined,
    // Explicit opt-in for `using` and `await using` declarations (not yet stage 4 at time of writing)
    allowUsingDeclaration = false,
    // Leave built up scope information in the ASTs (good luck)
    exposeScopes = false,
    // Assign each node a unique incremental id
    astUids = false,
    // Do you want to print a code frame with error messages? (Part of the input around the point of error)
    errorCodeFrame = true,
    // For the code frame, do you want to always show the entire input, regardless of size? Or just a small context
    truncCodeFrame = true,
    // You can override the logging functions to catch or squash all output
    $log = console.log,
    $warn = console.warn,
    $error = console.error,
    // Value ot use for the `source` field of each `loc` object
    sourceField = '',
    // Generate a `range: {start: number, end: number}` property on all loc objects (does not require `locationTracking`)
    ranges = false,
    // Generate a `range: [start: number, end: number]` property on all nodes. `input.slice(range[0], range[1])` should get you the text for a node.
    nodeRange = false,
    // Do not populate loc properties on AST nodes (property will be undefined). Since v<unpublished>
    locationTracking = true,
    // Prevent syntax error for octal escapes regardless of strict mode or anything else
    alwaysAllowOctalEscapes = false,
  }
);

Development

There is a single entry point in the root project called t which calls tests/t.sh which calls out to various development related scripts.

ES modules

Note that the files use import and export declarations and import(), which requires node 10+ or a cutting edge browser.

At the time of writing node requires the experimental --experimental-modules flag.

It's a burden in some ways and nice in others. A prod build would not have any modules.

Test cases

All test cases are in "special" plain-text .md files. See tests/testcases/README.md for details on formatting those.

Entry point

Some interesting usages of ./t:

# Show help
./t --help

# Run and update (inline) all tests. 
# Use git diff to see changes. Will bail fast on unexpected or assertion errors.
# This tests four modes (sloppy, strict, module, and sloppy-web-compat)
# This also tests the printer on the first successful parse
./t u
# Run all tests step-by-step (same as above) and ask what to do for any changes
./t m

# Same as `./t u` but compare it against Babel or Acorn. Recorded changes should be discarded afterwards.
# Use this to test against AST differences. If there are any they will be printed explicitly.
# Acorn:
./t a
# Babel:
./t b

# Test a particular input from cli
./t i "some.input()"
# Test a particular test file
./t f "tests/testcases/regexes/foo.md"
# Test a particular test file or folder and force-write the result(s)
./t ff "tests/testcases/regexes"
# Use entire contents of given file as input
./t F "test262/test/annexB/built-ins/foo.js"

# Generate prod builds
# Generate a build. Strips ASSERT*, inline many constants
./t z
# Same as above but explicitly set `acornCompat` and `babelCompat` to `false`.
./t z --no-compat
# Generate pretty builds for debugging without asserts:
./t --pretty
# Minified build with Terser (will lower performance due to inlining)
./t --min

# Run test262 tests (requires cloning https://github.com/tc39/test262 into tenko/ignore/test262)
./t t

# Fuzz the parser
./t fuzz

# Regenerate all autogen test files. Regenerates files still need to be updated (`./t u`).
# All files, regardless:
./t g
# Only create new files:
./t G

# Find out which tests execute a particular code branch in the parser
# Add `HIT()` to any part of the code in src
# Reports (only) all inputs that trigger a `HIT()` call in Tenko
./t s

Some tooling that requires additional setup;

# Benchmarks (requires benchmark files in projroot/ignore/perf);
# Simply spawn new node process and run test:
./t p
# Run benchmarks repeatedly and report results
./t p6
# Configure machine to be as stable as possible (DANGEROUS, read the script before using it, requires root). All 
# changes should be reset after reboot. Then run the benchmarks in the shielded cpus at RT prio (also requires root).
./t stable
./t p6 --stabled
# Same as above but without running `./t stable` previously, and tries to undo certain (but not all) things afterwards
./t p6 --stable

# Investigate v8 perf regressions with deoptigate:
./t deoptigate

# Profile the parser in Chrome devtools (open the tab through `about://inspect`)
./t devtools

# Run a visual heatmap profiler for counts based investigation (private)
./t hf

There are many flags. Some are specific to an action, others are generic. Some examples:

--sloppy             Run in non-strict mode (but non-web compat!)
--strict             Run with script goal but consider the code strict
--module             Run with module goal (enabling strict mode by default)
--web                Run with script goal, non-strict, and enable web compat (AnnexB rules)
--annexb             Force enable AnnexB rules, regardless of mode

6                    Run as close to the rules as of ES6  / ES2015 as possible
7                    Run as close to the rules as of ES7  / ES2016 as possible
8                    Run as close to the rules as of ES8  / ES2017 as possible
9                    Run as close to the rules as of ES9  / ES2018 as possible
10                   Run as close to the rules as of ES10 / ES2019 as possible
11                   Run as close to the rules as of ES11 / ES2020 as possible
12                   Run as close to the rules as of ES12 / ES2021 as possible
13                   Run as close to the rules as of ES13 / ES2022 as possible (ie. top-level await)
14                   Run as close to the rules as of ES14 / ES2023 as possible (ie. hashbang)
15                   Run as close to the rules as of ES15 / ES2024 as possible (ie. RegExp v flag / unicode sets)
16                   Run as close to the rules as of ES16 / ES2025 as possible (ie. import with, RegExp modifiers)
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025

--min                Given a broken input, brute force minify the input while maintaining the same error message
--acorn              Output a Acorn compatible AST
--babel              Output a Babel compatible AST
--test-acorn         Compare the `--acorn` output to the actual output of Acorn on same input (./t a)
--test-babel         Compare the `--babel` output to the actual output of Babel on same input (./t b)
--test-node          Compile input in a `Function()` and report whether that throws when Tenko throws, for fuzzer
--build              Use a prod build (from standard output location), inst
View on GitHub
GitHub Stars509
CategoryProduct
Updated23d ago
Forks13

Languages

JavaScript

Security Score

85/100

Audited on Mar 9, 2026

No findings