A permissive Rust Parser for Rust Tooling written in Typescript
jinx-rust ·

jinx-rust is a Rust parser written in Typescript; it enables the development of Rust Tooling in Typescript.
Example project using jinx-rust: Prettier Rust formatter
Get Started
npm install jinx-rust
import { rs } from "jinx-rust";
const file = rs.parseFile("let leet: u32 = 1337;");
console.log(JSON.stringify(file));
{ "type": "SourceFile", "program": { "type": "Program", "ast": [ { "type": "LetVariableDeclaration", "pattern": { "type": "Identifier", "name": "leet" }, "typeAnnotation": { "type": "Identifier", "name": "u32" }, "expression": { "type": "Literal", "kind": 11, "value": "1337" } } ], "danglingAttributes": [], "comments": [] } }
A tolerant parser for better Rust Tooling
jinx-rust is unstrict by default and tolerates bad syntax
- Tokens:
=== and !==)
- Unsyntactic parentheses (e.g. RangePattern bounds)
- Closures with a returnType and a non-block expression
- Nodes:
unsafe pub fn ...)
- Forbidden node types (e.g. patterns in const variables, expressions in top level)
- Missing typeAnnotations
import { rs } from "jinx-rust";
const arg0 = rs.parseFile("fn foo(arg0) {}").program.ast[0].parameters[0];
assert(arg_0.typeAnnotation.type === "MissingNode");
In the future, jinx-rust should eventually get a strict option.
Conversion between nodes and tokens
Attributes and Macro invocation arguments are returned as tokens in rs.parseFile. rs exposes other methods to re-read tokens in arbitrary contexts, or to re-read nodes as tokens.
import { rs, MacroInvocation } from "jinx-rust";
const node = rs.parseFile("foo!(123);").program.ast[0].expression as MacroInvocation;
const args = rs.toCallExpressionArguments(node.segments).ast; // ExpressionNode[] const block = rs.toBlockBody(node.segments).ast; // StatementNode[]
const tokens = rs.toTokens(node).ast; // TokenNode[]
Nightly features
jinx-rust supports 23 nightly features. The full list can be found in src/parser/nodes.ts under enum Feature. (link)
jinx-rust/utils
jinx-rust/utils is automatically included on install. It is a library of (mostly) auto-generated helpers from the parser's type declarations. E.g. tree traversing helpers, is_{Type}(node) functions for every type declared exported by the parser.
import { eachnode, isStatementNode } from "jinx-rust/utils";
declare const target: Node;
each_node(target, (child, parent) => { if (is_StatementNode(child)) { // gets called for every statement in target } });
Gotchas
- When a node has outer attributes, its start location expands to include them,
node.loc.ownStart
- Prefer
jinx-rust/utilshelpers to access locations, e.g.start(node)instead ofnode.loc[0]
- Exported
.d.tsnodes each document their syntax, hitGo to Definitionor hover over their class to see it.
import { TraitDeclaration } from "jinx-rust";
TraitDeclaration; // ^? import TraitDeclaration // trait id<...generics>?: ...typeBounds? where ...whereBounds? { ...body }
filepathis not required but useful in debugging:rs.parseFile(code, { filepath })
node.loc.url() is an easy, quick way to get a clickable link to nodes causing problems.
- To facilitate debugging in NodeJS, class Loc implements nodejs.util.inspect.custom Hence in console.log(node), loc is logged as node.loc.url() e.g. TraiDeclaration { ..., loc: "path/to/file.rs:51:18", ... }
Why jinx-rust? And why in Typescript?
- ### The case for Rust Tooling to split from rustc
- The spec TC39 ("legislative") - The core V8/JC/SM ("judicial") - The tooling Typescript ("executive")
Through that lens, Rust is in a rather bleek shape:
- No spec, the 1 implementation is the law - The core is rustc - The tooling is done on top of core, sometimes even arbitrarily within core's algorithms
Building Rust Tooling on top of rustc is a mistake akin to building Typescript's editor integration on top of v8's Turbofan.
- ### The case for Core and Tooling to use different Parsers and ASTs
- Core needs a minimalistic, clear and no bs parser & AST that follows the syntax allowed in the spec by the dot and to the dot. Arbitrarily injecting lints in the core parser or bloating its data structure with CST is frankly out of scope and corrupts the entire language foundation.
- Tooling needs a parser that is much more tolerant and flexible, it should be able to overlook or bend the spec syntax rules to best infer user's intent.
- ### The case for writing Rust Tooling in Typescript