qg_java_17137
qg_java_17137

Reputation: 3579

How to get the tree structure data of class/property/method in tree-sitter?

How to get the tree structure data of class/property/method in tree-sitter:

I can only match the class like below:

const Parser = require("tree-sitter")
const JavaScript = require("tree-sitter-javascript")
const { Query } = Parser 


const parser = new Parser()
parser.setLanguage(JavaScript)

const query = new Query(
  JavaScript,
  `
    (class_declaration name: (identifier) @class-name)
  `
);


const tree = parser.parse(`
class Person {}
 
const TestPerson = class {}

class Person2 {}
 
const TestPerson2 = class {}
  `);
const matches = query.matches(tree.rootNode);

matches.forEach(item => {
  console.log(item.captures[0])
})

there I can get the classnames of a file.

but you see the GitHub page: I want to get the tree structure data of AST:

enter image description here

1、please help with how to match the AST tree data?
2、you see the snapshot display a 3-layer depth tree data, how to control the matched tree data depth?

Upvotes: 4

Views: 537

Answers (2)

el cato
el cato

Reputation: 81

Normally, this is usally achieved by programmatically iterate through the syntax tree instead of direct matching. If you really want go matching, then I think the answer by @Prabhat is the one for you.

If you want alternative way by iterating the AST, here's the solution:

const Parser = require('tree-sitter');
const JavaScript = require('tree-sitter-javascript');

const parser = new Parser();
parser.setLanguage(JavaScript);

const code = `INSERT YOUR CODE HERE`;

// Parse the JavaScript code
const tree = parser.parse(code);

// AST type to readable name map
const treeTypeNameMap = {
    "class_declaration": "class",
    "method_definition": "method",
    "function_declaration": "function"
}

// Nodes that we consider to increment the depth level.
// For example, when we enter a new class body, or new statement block.
const depthNodes = [
    "class_body", "statement_block"
];

// Recursively traverse the syntax tree up to a certain depth
function findClassesAndMethods(node, currentDepth = 0, maxDepth = 3) {
    // If we reached the maximum depth, return
    if (currentDepth >= maxDepth) {
        return;
    }

    let nextDepth = currentDepth;
    // Only increment depth level if we encounter a new class body or statement block
    if (depthNodes.includes(node.type)) {
        nextDepth += 1;
    } else if (node.type in treeTypeNameMap) {
        const name = node.childForFieldName("name").text;
        console.log("-".repeat(currentDepth), treeTypeNameMap[node.type], name);
    }

    node.children.forEach(child => findClassesAndMethods(child, nextDepth, maxDepth))
}

// Start traversing from the root node
findClassesAndMethods(tree.rootNode);

Example input:

class C {
    A() {
        {
            class AInner {}
        }
        class B {
            
        }
    }
}

class A {
    constructor() {}
    B() {
        class C {
            
        }
    }
}

And its output:

 class C
- method A
-- class B
 class A
- method constructor
- method B
-- class C

Upvotes: 2

Prabhat
Prabhat

Reputation: 841

Whereas I can understand that you want to extract and display the tree structure of classes, methods, and properties using "tree-sitter".

In order to match the ATS tree data, you are supposed to first create a 'Query' that will focus on specific nodes in the AST, based on grammar of the language being parsed.

After this you can control the matched tree data depth, by restricting your 'Query' to target only specific levels of ATS. Once this much is done then you can process the ATS manually (if you want) and control the depth, this ATS can be traverse recursively but should be stopped after reaching the expected depth.

I think it would be difficult to understand simply by defining the way to solve the problem. So I am attaching the code snippet below you can have a reference from here:

const Parser = require("tree-sitter");
const JavaScript = require("tree-sitter-javascript");
const { Query } = Parser;

const parser = new Parser();
parser.setLanguage(JavaScript);

const query = new Query(
  JavaScript,
  `
    (class_declaration
      name: (identifier) @class-name
      body: (class_body
        (method_definition
          name: (property_identifier) @method-name)
        (public_field_definition
          name: (property_identifier) @property-name)
      )
    )
  `
);

const code = `
class Person {
  constructor() {}
  getName() {}
  age = 30;
}

const TestPerson = class {
  constructor() {}
  setAge() {}
  height = 180;
}

class Person2 {
  sayHello() {}
}
`;

const tree = parser.parse(code);
const matches = query.matches(tree.rootNode);

const classTree = {};

matches.forEach((match) => {
  const className = match.captures.find(capture => capture.name === "class-name").node.text;
  const methodName = match.captures.find(capture => capture.name === "method-name");
  const propertyName = match.captures.find(capture => capture.name === "property-name");

  if (!classTree[className]) {
    classTree[className] = {
      methods: [],
      properties: []
    };
  }

  if (methodName) {
    classTree[className].methods.push(methodName.node.text);
  }

  if (propertyName) {
    classTree[className].properties.push(propertyName.node.text);
  }
});

console.log(JSON.stringify(classTree, null, 2));

In case if you have any further doubt then ask again.

Upvotes: 1

Related Questions