5

There is a test.js:

const add = (x,y) => {
    return x+y;
}

const multiply = (x,y,z) => {
    return x*y*z;
}

I want to read this test.js from an index.js and print all its function name and arguments;

const fs = require("fs");

let file = fs.readFileSync("./test.js", "utf8");

let functionArg = "Do some operations"

console.log(functionArg)

//Result:
//  add : [x,y]
//  multiply : [x,y,z]

Without module.exports.
Is it possible to read js file and return all its function and their arguments.

4
  • Since you don't want to import the functions, you can read every line of the file and check if the line is a function definition or declaration. Commented Mar 13, 2019 at 9:38
  • How to do that? For functions I can read each line and split it by a space and then can check if it is of type function or not. What about arguments? Can you please explain your approach? thanks Commented Mar 13, 2019 at 9:54
  • You don't have to read each line for functions. see my answer below Commented Mar 13, 2019 at 9:58
  • I can confirm that remix23's answer works. Just tried it. Commented Mar 13, 2019 at 11:06

4 Answers 4

3

You can get functions and their arguments with the help of a JavaScript parser like esprima.

const fs = require("fs");
const esprima = require('esprima');

let file = fs.readFileSync("./test.js", "utf8");

let functionArg = esprima.parseScript(file);

functionArg.body.forEach(el => {
  let variableDeclarator = el.declarations[0]
  let params = []
  variableDeclarator.init.params.forEach(arg => {
    params.push(arg.name)
  })
  console.log(variableDeclarator.id.name, ' : ', [params.join()])
})

//Result:
// add  :  [ 'x,y' ]
// multiply  :  [ 'x,y,z' ]
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for sharing the answer. I had heard about ESPrima before, but never used it. After your answer I read more about it, went through its code, and it helped improve my JS knowledge.
@NirajKumar good reflex. Note that the project is 68% in TypeScript so it will help to improve your TS too ;)
note that although esprima has a parseModule function, it just doesn't support modern ES modules, like with class field declarations. It is recommended to use @babel/parser
3

When you need to read a code file, the better is to directly use a compiler when available.

It turns out acorn is a well known parser and you're probably already using it without knowing because you're probably using babel.

With acorn you can parse a source file into an abstract source tree which in turn can be walked with acorn-walk to find what you need.

example:

testfiletoparse.js


    export function factorialR(n) {
        if (n <= 1) {
            return 1;
        }
        return factorialR(n - 1) * n;
    }

    export function bound(v, min, max) {
        return Math.max(Math.min(v, min), max);
    }

    export function isNullOrEmpty(str) {
        return !str || str.length === 0;
    }

    export const arrowFunction = v => v;

basicparser.js


    import fs from 'fs';

    const acorn = require('acorn');
    const walk = require('acorn-walk');

    require('acorn-object-rest-spread/inject')(acorn);
    require('acorn-static-class-property-initializer/inject')(acorn);
    require('acorn-class-fields/inject')(acorn);

    const filePath = 'src/testfiletoparse.js';

    const sourceCode = String(fs.readFileSync(filePath));
    const program = acorn.parse(
        sourceCode,
        {
            ranges: true,
            locations: true,
            sourceType: 'module',
            plugins: {
                objectRestSpread: true,
                // es7: true,
                staticClassPropertyInitializer: true,
                classFields: true,
            }
        }
    );

    walk.full(
        program,
        /**
         * @param {}
         */
        (node) => {
            if (node.type === 'FunctionDeclaration') {
                console.log(`There's a FunctionDeclaration node at ${JSON.stringify(node.loc.start)}`);
                console.log(`Function name is ${node.id.name}`);
                const params = node.params.map((param) => {
                    return param.name;
                }).join();
                console.log(`Function params are ${params}`);
            }
            // it is a little tricky to catch arrow functions but trial and error will get you through it
            if (node.type === 'VariableDeclarator' && node.init.type === 'ArrowFunctionExpression') {
                console.log(`There's an arrow function expression declaration node at ${JSON.stringify(node.loc.start)}`);
                console.log(`Its name is ${node.id.name}`);
                const params = node.init.params.map((param) => {
                    return param.name;
                }).join();
                console.log(`Function params are ${params}`);
            }
        }
    );

output


    There's a FunctionDeclaration node at {"line":1,"column":7}
    Function name is factorialR
    Function params are n
    There's a FunctionDeclaration node at {"line":8,"column":7}
    Function name is bound
    Function params are v,min,max
    There's a FunctionDeclaration node at {"line":12,"column":7}
    Function name is isNullOrEmpty
    Function params are str
    There's an arrow function expression declaration node at {"line":16,"column":13}
    Its name is arrowFunction
    Function params are v

Starting with this it should be quite straightforward to find a solution to your problem.

Comments

1
  • read a js file
  • replace const to this.
  • wrap it inside constructor function and evaluate it.
  • create a instance of it
  • since you replaced const to this., all variables in test.js became the member of the instance. now you can interate this instance to get the members.
  • to get the function signature, you will have to convert a function object to string and get the arguments manually.

Here is the code:

const fs = require("fs");

let file = fs.readFileSync("./test.js", "utf8");

const getFuncInfo = function(file) {
  file = file.replace(new RegExp('const ', 'g'), 'this.');
  eval(`function Container(){
    ${file}}
  `)
  const instance = new Container()
  const names = Object.keys(instance)
  return names.reduce((res, cur) => {
    if(typeof instance[cur] == 'function') {
      let args = instance[cur].toString()
      res[cur] = args.split('(')[1].split(')')[0].split(',')
    }
    return res;
  }, {})
}

let functionArg = getFuncInfo(file)

console.log(functionArg)

The result is:

{ add: [ 'x', 'y' ], multiply: [ 'x', 'y', 'z' ] }

Edit: Regarding the question about what eval does, it is same as below:

const getFuncInfo = function(file) {
  file = file.replace(new RegExp('const ', 'g'), 'this.');
  // think like eval replace the content with below one
  function Container(){
    // content of test.js except `const ` is replaced with `this.`
    this.add = (x,y) => {
      return x+y;
    }

    this.multiply = (x,y,z) => {
      return x*y*z;
    }
  }
  // end of replacement
  const instance = new Container()
  const names = Object.keys(instance)
  return names.reduce((res, cur) => {
    if(typeof instance[cur] == 'function') {
      let args = instance[cur].toString()
      res[cur] = args.split('(')[1].split(')')[0].split(',')
    }
    return res;
  }, {})
}

11 Comments

Can you please explain what actually eval is doing here? code is working. thanks
eval is executing the code. look at the parameter of it. it's not just the content of test.js. it's wrapped by another function. think of it as compiler.
const instance = new Function(file); this is what running inside the eval. no it's not :). please check my edited answer.
Okay now I understood, when we put function Container() {file}; it takes file as a variable. but when we use eval it first write the complete function with $file and then it runs, thanks for the explanation it helped
For a lot of reasons, using eval is a bad option. stackoverflow.com/questions/86513/…
|
-3

I think you are on the right path already but you never used the file you read, it should actually be(I hope I got your question right):

let file = fs.readFileSync("./test.js", "utf8");
console.log(file);

// Result:
//const add = (x,y) => {
//   return x+y;
//}

//const multiply = (x,y,z) => {
//  return x*y*z;
// }

1 Comment

thanks for the answer, actually i don't want to print the content of js file, I want only the function name and their arguments inside the js file.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.