Coding Week 3&4: GSoC’ 22 with SCoRe Lab

Pranjal Walia
Leopards Lab
Published in
4 min readAug 12, 2022

In this post, I’ll be talking about how the consequent weeks of coding went. You can read my previous post about week 2 here as well.

Now, previously we discussed a bit on the need of type-definition files and the traversal process of class ASTs, this time onward, we’ll be focusing more on consuming the obtained ASTs from the TypeScript compiler.

More Specifically, as promised the last time, we’ll be focusing on how to extract the data from an AST as per dummy class specifications. Remember the dummy class? It was a blueprint of the functions we want to generate automatically.

class ClassName {
/**
*
* @param {module} alisdk ali SDK
* @param {object} options SDK options
*/
constructor(alisdk, accessKeyId, accessKeySecret) {
this._ali = alisdk;
this._instance = new this._ali(accessKeyId, accessKeySecret);
this._sdkClassName = this._instance.SDKClassName;
}function() {
return new Promise((resolve, reject) => {
this._sdkClassName
.SDKFunctionName()
.then(data => resolve(data))
.catch(err => reject(err));
});
}
}module.exports = ClassName;

Now what exactly is the extraction process? As you are aware, during parsing, we previously copied a class node, which is essentially a class you would build in regular code but stored as an AST as far as the compiler is concerned.

Remember the compiler workflow for code interpretation

The task at hand is to obtain functions, their parameters, and other crucial information about how classes are constructed so that we can work with it and modify it as per out specific requirements.

For all our needs and purposes, I’ve narrowed down the required compiler entities to the following three parts:

  1. Method Declarations: JavaScript methods are actions that can be performed on objects. It is a property containing a function definition.
  2. Method Parameters: The parameters, in a function call, are the function’s arguments. JavaScript arguments are passed by value: The function only gets to know the values, not the argument’s locations.
  3. JSDoc Annotations: JSDoc is a markup language used to annotate JavaScript source code files. Using comments containing JSDoc, programmers can add documentation describing the application programming interface of the code they’re creating.
JSDoc Annotations are pretty useful especially when writing JavaScript!

The members of a class can be functions, constructors, and return statements, but we are primarily concerned with the method declaration node. Now, How exactly is the data extraction done? As soon as we have the class at hand, we’ll begin exploring all of its child nodes. If we run into a syntax like a method declaration, we’ll push it, and after that, we’ll travel through the extracted function nodes once again to extract their child parameter nodes.

let methods: FunctionData[] = [];sdkClassAst.members.map(method => { 
if
(method.name && functions.includes(method.name.text)) {
const parameters = [];
method.parameters.map(param => {
if (param.name.text !== "callback") {
const parameter = {
name: param.name.text,
optional: param.questionToken ? true : false,
type: SyntaxKind[param.type.kind],
typeName: null
};
if (parameter.type==="TypeReference"&& param.type.typeName) {
parameter.typeName = param.type.typeName.text;
} parameters.push(parameter);
}
});
methods.push({
functionName: name.toString(),
SDKFunctionName: method.name.text.toString(),
params: parameters
});
});

As a result, we were able to extract all the necessary data from a class in this manner. We will now do certain transformations on a dummy Class and merge the extracted data into that class.

Now, let’s focus on running transformations!

Helpful functions exposed by the compiler when working with nodes:

  1. check for variables
ts.isVariableDeclaration(node) //To check is it a variable

2. check for method declarations

ts.isMethodDeclaration(node) //To check is it a method/function

3. create a new node with desired behaviour (variable/method)

ts.createIdentifier('variable')
ts.createFunctionDeclaration(.....)

Now, let’s run a basic transformation:

import * as ts from 'typescript';

export const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
return sourceFile => {
const visitor = (node: ts.Node): ts.Node => {
if (ts.isIdentifier(node)) {

if(node.escapedText === 'babel') {
return ts.createIdentifier('typesript');
}
else if(node.escapedText === 'plugins') {
return ts.createIdentifier('transformers')
}
return ts.visitEachChild(node, visitor, context);
};

return ts.visitNode(sourceFile, visitor);
};
};

We pass context and inside the function we are returning the source file, which is again a function. Inside it we are defining the visitor, which will visit each node. As we are traversing the ast, we are checking if we have encountered a identifier node, if yes change its name according to the defined cases.

Summarising the above post, the entire workflow of the transformer is mentioned below.

Credits: my mentor Mohit Bhat for comming up with an amazing illustration for the transformer module

Epilogue 🔚

Over the course of this week, I worked on getting the transformation logic to work correctly, and going forward into the next week, I aim to get back to development of the SDK to complete the implementation to be extracted and additionally I’ll be working to extract the functionality of the extracted classes into a package for NodeCloud.

Well, this is the end of the line for now, till next time! Follow me on my socials in case of any questions, tips, and guidance pertaining to GSoC or anything in general.

Connect with me: Linkedin, Github, Twitter 😀

--

--