node-tree-sitter

repository·master·Indexed 21 days ago

https://github.com/tree-sitter/node-tree-sitter

Node.js bindings to the Tree-sitter parsing library (version 0.25.1), enabling high-performance incremental parsing of source code into syntax trees. It provides the Parser class for generating trees, the Query class for pattern matching, and the TreeCursor for efficient tree traversal. The library supports custom data structures for input, fine-grained timeout and range configuration, and detailed syntax node inspection.

Tokens
3.7K
Snippets
9
Records
18
Agent score
74%

What's inside tree-sitter

  1. Install a language grammar

    master

    Tree-sitter requires a specific grammar for the language you intend to parse. You can install existing grammars (like tree-sitter-javascript) via npm. If a grammar is not available, you can develop one using the Tree-sitter CLI.

    npm install tree-sitter-javascript
  2. Parse source code with tree-sitter

    master

    To parse source code, initialize a Parser instance, set its language using a grammar module, and call .parse() with the source string. The resulting tree allows you to inspect the syntax tree via its nodes.

    const Parser = require('tree-sitter');
    const JavaScript = require('tree-sitter-javascript');
    
    const parser = new Parser();
    parser.setLanguage(JavaScript);
    
    const sourceCode = 'let x = 1; console.log(x);';
    const tree = parser.parse(sourceCode);
    
    // Inspect the root node
    console.log(tree.rootNode.toString());
    
    // Access specific nodes
    const callExpression = tree.rootNode.child(1).firstChild;
    console.log(callExpression);
  3. Parse text from a custom data structure

    master

    If your source text is not a single string (e.g., it is stored in a rope or an array of lines), you can provide a callback function to parser.parse() instead of a string. The callback receives (index, position) and should return the text slice starting from the requested position.

    const sourceLines = [
      'let x = 1;',
      'console.log(x);'
    ];
    
    const tree = parser.parse((index, position) => {
      let line = sourceLines[position.row];
      if (line) {
        return line.slice(position.column);
      }
    });
  4. Update an existing syntax tree using tree.edit()

    master

    When source code changes, you can update an existing syntax tree instead of performing a full re-parse. This is significantly faster. Use tree.edit() to describe the change by providing the start/end indices and positions for both the old and new source segments. Note that end indices are exclusive.

    // Example: replacing 'let' (index 0-3) with 'const' (index 0-5)
    const newSourceCode = 'const x = 1; console.log(x);';
    
    tree.edit({
      startIndex: 0,
      oldEndIndex: 3,
      newEndIndex: 5,
      startPosition: {row: 0, column: 0},
      oldEndPosition: {row: 0, column: 3},
      newEndPosition: {row: 0, column: 5},
    });
    
    // Re-parse using the existing tree as a base
    const newTree = parser.parse(newSourceCode, tree);
  5. Navigate the syntax tree using TreeCursor

    master

    The TreeCursor class provides a way to traverse the syntax tree efficiently without the overhead of creating full Node objects for every element. It allows you to move through the tree structure using methods for navigating to parents, children, and siblings, and to inspect the properties of the node currently being pointed to by the cursor.

    • gotoFirstChild(): Moves the cursor to the first child of the current node.
    • gotoLastChild(): Moves the cursor to the last child of the current node.
    • gotoParent(): Moves the cursor to the parent of the current node.
    • gotoNextSibling(): Moves the cursor to the next sibling of the current node.
    • gotoPreviousSibling(): Moves the cursor to the previous sibling of the current node.
    • gotoDescendant(): Moves the cursor to the first descendant of the current node.
    • gotoFirstChildForIndex(index): Moves the cursor to the child at a specific index.
    • gotoFirstChildForPosition(position): Moves the cursor to the first child at a specific position.
    • reset(): Resets the cursor to the root of the tree.
    • resetTo(node): Resets the cursor to a specific node.

    Inspection Methods

    Once the cursor is positioned, you can query the current node's properties:

    • currentNode(): Returns the Node object at the current cursor position.
    • nodeType(): Returns the type of the current node.
    • nodeTypeId(): Returns the ID of the current node type.
    • nodeStateId(): Returns the ID of the current node state.
    • nodeIsNamed(): Returns a boolean indicating if the node is a named node.
    • nodeIsMissing(): Returns a boolean indicating if the node is a missing node.
    • currentFieldId(): Returns the ID of the field associated with the current node.
    • currentFieldName(): Returns the name of the field associated with the current node.
    • currentDepth(): Returns the depth of the current node in the tree.
    • currentDescendantIndex(): Returns the index of the current node among its siblings.
    • startIndex(): Returns the start index of the current node in the source text.
    • endIndex(): Returns the end index of the current node in the source text.
    • startPosition(): Returns the start position (row and column) of the current node.
    • endPosition(): Returns the end position (row and column) of the current node.
  6. Perform tree edits for incremental parsing

    master

    To perform incremental parsing, you must notify the Tree about changes made to the source text using the edit method. This method takes an object describing the change.

    tree.edit({
      startPosition: { row: 1, column: 0 },
      oldEndPosition: { row: 1, column: 5 },
      newEndPosition: { row: 1, column: 10 },
      startIndex: 10,
      oldEndIndex: 15,
      newEndIndex: 20
    });
  7. Configure Parser timeouts and included ranges

    master

    The Parser class allows fine-grained control over the parsing process to handle edge cases or performance constraints:

    • Timeouts: To prevent the parser from hanging on malicious or overly complex input, use setTimeoutMicros(micros) to set a limit in microseconds. You can retrieve the current limit using timeoutMicros().
    • Included Ranges: If you only want to parse specific segments of a file, use setIncludedRanges(ranges) to provide a list of ranges. You can check the currently active ranges with includedRanges().
    • Resetting: Use reset() to clear the parser state.
  8. Traverse the tree with a TreeCursor

    master

    A TreeCursor provides a high-performance way to traverse the tree without the overhead of creating many SyntaxNode objects. It allows you to move through the tree structure step-by-step.

    Key Properties:

    • currentNode: Returns the SyntaxNode at the current cursor position.
    • startPosition / endPosition: The coordinates of the current node.
    • nodeText: The text content of the current node.

    Usage Pattern: You can obtain a cursor from a node using node.walk() or from a tree using tree.walk().

  9. Use the Parser class

    master

    The Parser class is the main entry point for generating syntax trees.

    • setLanguage(language): Sets the language for the parser. This also initializes language-specific SyntaxNode subclasses.
    • getLanguage(): Returns the currently set language.
    • parse(input, oldTree, options): Parses the provided input.
      • input: Can be a string or a function (offset, position) => string.
      • oldTree: (Optional) An existing Tree instance used for incremental parsing.
      • options: An object containing bufferSize, includedRanges, and progressCallback.
  10. Navigate and inspect SyntaxNodes

    master

    A SyntaxNode represents a node in the syntax tree. You can navigate the tree structure using various properties and methods.

    Navigation Properties:

    • parent: The parent node.
    • children: An array of all child nodes.
    • namedChildren: An array of only named child nodes.
    • firstChild / lastChild: The first or last child node.
    • firstNamedChild / lastNamedChild: The first or last named child node.
    • nextSibling / previousSibling: The adjacent sibling nodes.
    • nextNamedSibling / previousNamedSibling: The adjacent named sibling nodes.

    Node Metadata:

    • type: The string name of the node type.
    • text: The raw text content of the node.
    • startPosition / endPosition: The {row, column} coordinates of the node.
    • startIndex / endIndex: The byte offsets of the node.
    • isNamed: Boolean indicating if the node is a named node.
    • isExtra: Boolean indicating if the node is an extra node.
    • isMissing: Boolean indicating if the node is a missing node.
    • hasError / isError: Boolean indicating if the node represents a syntax error.
    • childCount / namedChildCount: The number of children or named children.

    Field-based Navigation: Nodes can have specific fields defined by the grammar. You can access them via:

    • childForFieldName(fieldName): Returns a single node by field name.
    • childrenForFieldName(fieldName): Returns an array of nodes by field name.
    • childForFieldId(fieldId): Returns a single node by field ID.
    • childrenForFieldId(fieldId): Returns an array of nodes by field ID.
  11. Run queries using the Query class

    master

    The Query class allows you to search for patterns within a syntax tree. You can use .matches() to find patterns or .captures() to retrieve specific named captures.

    Query Methods:

    • matches(node, options): Returns an array of match results. Each result contains the pattern index and the captures associated with it.
    • captures(node, options): Returns the specific capture for a pattern match.

    Common Query Options:

    • startPosition / endPosition: The range within the tree to search.
    • startIndex / endIndex: The byte range to search.
    • matchLimit: Maximum number of matches to return.
    • timeoutMicros: Timeout for the query execution in microseconds.