JavaScript: Parse Java source code, extract method - javascript

I'm trying to build a module that returns all methods of a given Java source code using nodeJs. So far I can get the AST tree built using "java-parser" module but I need to traverse it to filter out methods.
code = ' public class CallingMethodsInSameClass
{
public static void main(String[] args) {
printOne();
printOne();
printTwo();
}
public static void printOne() {
System.out.println("Hello World");
}
public static void printTwo() {
printOne();
printOne();
} }';
var javaParser = require("java-parser");
var traverse = require("babel-traverse").default;
var methodsList = [];
traverse(ast, {
enter(path){
//do extraction
}
}
I understand that babel-traverse is for Js but I wanted a way to traverse so I can filter the methods.
I'm getting this error
throw new Error(messages.get("traverseNeedsParent", parent.type));
^
Error: You must pass a scope and parentPath unless traversing a
Program/File. Instead of that you tried to traverse a undefined node
without passing scope and parentPath.
The AST if logged looks like
{ node: 'CompilationUnit',
types:
[ { node: 'TypeDeclaration',
name: [Object],
superInterfaceTypes: [],
superclassType: null,
bodyDeclarations: [Array],
typeParameters: [],
interface: false,
modifiers: [Array] } ],
package: null,
imports: [] }
where a method will be identified by "MethodDeclaration" within types. Any help is appreciated.

The AST is just another JSON object. Try jsonpath.
npm install jsonpath
To extract all methods, just filter on condition node=="MethodDeclaration":
var jp = require('jsonpath');
var methods = jp.query(ast, '$.types..[?(#.node=="MethodDeclaration")]');
console.log(methods);
See here for more JSON path syntax.

Related

Passing callbacks into JS object constructor with embind in C++

I am looking for a way to instantiate and use VideoDecoder in C++ code of webassembly app. Here is an example of the setup:
const decoder = new VideoDecoder({
output(frame)
{
renderFrame(frame);
},
error(e)
{
setStatus("decode", e);
}});
VideoDecoder class constructor has the following signature:
const videoDecoder = new VideoDecoder({
output: processVideo,
error: onEncoderError,
});
In cpp I initiate VideoDecoder like this:
static void OnVideoFrame(emscripten::val frame)
{
}
static void OnError(emscripten::val error)
{
}
struct DecodeSettings_t
{
void (*output)(emscripten::val);
void (*error)(emscripten::val);
};
void VideoManager::Init(){
emscripten::val VideoDecoder = emscripten::val::global("VideoDecoder");
DecodeSettings_t set;
set.output = &OnError;
set.error = &OnVideoFrame;
videoDecoderInst = VideoDecoder.new_(set);
}
When launching the app I am getting the following error:
{name: 'BindingError', message: 'parameter 0 has unknown type
N2sn16DecodeSettings_tE', stack: 'BindingError: parameter 0 has
unknown type ..}
Looks like DecodeSettings_t struct is unrecognized, which kind of makes sense. So I tried to simulate a dynamic JS object creation in C++ and passing it into the constructor:
emscripten::val obj = emscripten::val::object();
obj.set("output", &OnVideoFrame);
obj.set("error", &OnError);
videoDecoderInst = VideoDecoder.new_(obj);
Now I am getting this error on web app startup:
RuntimeError: Aborted(LinkError: WebAssembly.instantiate(): Import
#116 module="env" function="_emval_new_object" error: function import requires a callable)
What am I missing here?

Build a dynamic interface for Object in angular

I have an JSON Object received from an api call and the Object will have multiple values but all of type string. How to I write an Interface for such an Object in the shortest way.
I mean I can write and Interface with 100 keys whose type is string. But is there a more efficient way of doing this?
you can use Record(a utility type in typescript):
const info: Record<string, string> = {
name: "Bruce Wayne",
address: "Bat Cave"
};
you can use it in your service like this:
class MyService {
constructor(private readonly http: HttpClient) {}
callApi() {
return this.http.get<Record<string, string>>(url);
}
}

NodeJS: How to delay a module's resolution due to the dependencies on that module

Suppose the following scenario:
We have a class that handles a Mongoose connection as below:
export interface IInstance {
name: string;
instance: Mongoose.Mongoose;
}
export default class MongoHandler {
public static instances: IInstance[] = [{
name: 'default',
instance: null,
}];
// Connect to mongo instance and add it to the instances array
public static async connect(name: string, uri: string, options?: object): Promise<void> {
const instance: Mongoose.Mongoose = await Mongoose.connect(uri, options);
const newInstance: IInstance = {
name,
instance,
};
MongoHandler.instances.push(newInstance);
}
// Returns the instance based on the name of instance
public static getInstance(name: string = 'default'): Mongoose.Mongoose {
return this.instances.find(instance => instance.name === name).instance;
}
}
The other module called CarModel is using getInstance() method for creating a model:
export interface ICar {
name: string;
}
const carSchema = new Mongoose.Schema<ICar>(
{
name: {
type: String,
required: true,
},
}
);
const carModel = MongoHandler.getInstance('default').model<ICar>('Car', carSchema, 'Cars');
export default carModel;
We are using carModel in a module called CarController.
In index.ts we are calling these two modules as below:
import
const app = new App(
[
MongoHandler.connect('default', process.env.MONGO_URI),
],
[
new CarController(),
]
);
App is a class for handling express bootstrapping (can be ignored).
While running this code MongoHandler.getInstance('default') is undefined because of the order of dependency resolution (I think)! And resolving MongoHandler.getInstance('default') is followed by MongoHandler.connect() which should be reversed.
How can I solve this?
Best regards
I think there are 2 issues at play here, neither of them having to do with module resolution (which will work just fine as you don't have any circular dependencies).
array.prototype.find returns the FIRST match found, you instantiate instances array with an object that matches the name default, but has no instance. When you connect, you add another object with the name default, but this will be second in the list, thus find will return the original object, which has its instance object set as null.
I would advice removing this default empty instance object for error and code clarity.
your connect function makes use of async-await. But you never await your connect function, thus not guaranteeing that your new connection instance has been made before you are calling getInstance inside of your carController. You should catch the Promise returned by the connect function and await it. If you do not want to delay your CarController instantiation, you can use save this Promise in the MongoHandler and return it with some init function that you call inside of the CarController to make sure the connect has been resolved.

Why is TypeScript unable to detect mismatched types at compile time and how to fix it?

In our codebase we have code like below which is unable to detect if a type violation occurred due to programmer error - why does this happen and what's the best way to address this problem?
// forcing a structure on "shape" of the output
interface IPayloadOutput {
[key: string]: string
}
interface MyPayloadOutput extends IPayloadOutput {
myPayloadKey1: string
myPayloadKey2: string
extraKey: string //adding this here doesn't cause compile error if it's not returned
}
// Input data needs that to be transformed to the output
interface MyPayloadInput {
data1: string
data2: string
}
class MyPayloadOutputGenerator extends PayloadOutputGenerator {
public getPayloadKeyValues(args: MyPayloadInput): IPayloadKeyValues {
return {
myPayloadKey1: {key1: args.data1, key2: args.data2},
myPayloadKey2: { key1: args.data1 + '-senor' },
// Why does code not throw compile error if the below field is missing?
// extraKey: { key1: 'extra' }
}
}
}
function consumer(response: MyPayloadOutput): void {
console.log(response)
}
const x = new MyPayloadOutputGenerator().getPayloadOutput<MyPayloadInput, MyPayloadOutput>({
data1: 'hello',
data2: 'wrold',
})
consumer(x) // should throw compiler error if missing `extraString`
The PayloadGenerator takes in the Input and transforms it into the expected Output. But the key extraString in MyPayloadOutput is missing in the data returned but no compiler error is reported? Why is that?
Here's a fiddle showing a running example
For completeness, here's the PayloadGenerator:
// all payloads need key1 and an optional key2.
interface IPayloadDetails {
key1: string
key2?: string
}
// force structure for how we expect payload key/values to look
interface IPayloadKeyValues {
[key: string]: IPayloadDetails
}
abstract class PayloadOutputGenerator {
// source keys from implementing classes. `args` is not typed to be 'anything'
public abstract getPayloadKeyValues(args): IPayloadKeyValues
// Generic method to be used with any input/output combo
public getPayloadOutput<Input, Output>(args: Input): Output {
const payloadKeyValues = this.getPayloadKeyValues(args)
const payloadOutput = {} as Output
Object.keys(payloadKeyValues).forEach(key => {
payloadOutput[key] = JSON.stringify(payloadKeyValues[key]) //do custom encoding here
})
return payloadOutput
}
}
You typecasted (as Output) and omitted Typings at central positions (: IPayloadKeyValues) and over all worked against typescripts type system. By using index types, you made it impossible for typescript to determine what the code actually does. Instead, use Generics from the beginning:
abstract class PayloadOutputGenerator<I, O extends {}> { // already introduce the generic here ...
public abstract getPayloadKeyValues(args: I): O // then you can narrow down this correctly
public getPayloadOutput(args: I) { // and this gets typed correctly automatically
const payloadKeyValues = this.getPayloadKeyValues(args);
const payloadOutput = {} as { [K in keyof O]: string }; // its a mapped type, so lets type it as such
Object.keys(payloadKeyValues).forEach(key => {
payloadOutput[key] = JSON.stringify(payloadKeyValues[key]) //do custom encoding here
});
return payloadOutput
}
}

DeSerializing JSON string into a TS object

I am struggling over what should appear to be a very simple procedure.
In the "geolocate.ts" function "setData", the model indexes? "flightplan" and "config" are shown by Chrome debugger to be "undefined" when referenced from "model.flightplan" or "model.config". The "model" object itself seems to be fine even when expanded in the debugger.
Any thoughts or pointers would be very much appreciated ;)
geolocate.d.ts
export class FmsFlightPlan {
public status: string[];
...
}
export class Config {
public airportIcon: IconSettings;
...
}
export class InitModel {
public config: Config;
public flightplan: FmsFlightPlan;
}
geolocate.ts
import * as passedData from "./geoLocate.d";
let config: passedData.Config;
let flightPlan: passedData.FmsFlightPlan;
export function setModel( json: string): void {
console.log( json); // '{"Config": { "AirportIcon": {...} ...}, {"Flightplan": {"Status": [], ... } ...}' --- As expected (JSONlint OK)
const model: passedData.InitModel = JSON.parse( json);
console.log(model); // Chrome console: {Config: {…}, Flightplan: {…}}
flightPlan = model.flightplan; // flightPlan and config are assigned "undefined"
config = model.config; // "model" looks OK and Intellisense works.
flightplanDraw();
}
TSC generated javascript
function setModel(o) {
console.log(o);
var e = JSON.parse(o);
console.log(e), flightPlan = e.flightplan, config = e.config, flightplanDraw()
}
.NET Core View Javascript
function gmapsReady() {
initMap();
$.getJSON("/Home/GetConfig",
null,
function(data) {
setModel(data);
});
}
.NET MVC Controller
public JsonResult GetConfig()
{
// Load fplan and config objects
...
...
InitModel initModel = new InitModel
{
Flightplan = fplan,
Config = _config
};
string json = JsonConvert.SerializeObject(initModel);
return new JsonResult(json);
}
A first issue seems to be that you are accessing fields like flightplan and config, whereas in the JSON they are FlightPlan and Config. That's why you're getting undefineds.
A slightly bigger issue after that, which will mostly bite you if you plan on adding methods to your classes, is that the thing produced by JSON.parse is a simple JavaScript object, whereas Config, FlightPlan etc are classes, and instances of them would belong to that class. So if you had something like this:
let x = new Config();
x.airportIcon = 'foo';
console.log(x.constructor); // Prints 'Config'
let y = JSON.parse('{"airportIcon": "foo"}');
console.log(y.constructor); // Prints 'Object something or other'
So the two are structurally equivalent, but won't be functionally equivalent. Even by doing a TS cast you won't be able to call a function on y as you would on x. If these are simple DTOs than that's OK. But if not, you need to be explicit about this and do another step of translating from the JS object to your application ones.
Shameless plug: I wrote raynor to automate this exact process - of translating between a DTO type and a more useful JavaScript class.
You can also configure the JSON serializer on .net side to convert field names from PascalCase to 'camelCase`.

Categories