benjamn / recast

JavaScript syntax tree transformer, nondestructive pretty-printer, and automatic source map generator
MIT License
5.01k stars 350 forks source link

recast does not print leadingComments on FunctionDeclaration #572

Open a-x- opened 5 years ago

a-x- commented 5 years ago
ast example ```json { "type": "FunctionDeclaration", "start": 5, "end": 25, "loc": { "start": { "line": 2, "column": 0 }, "end": { "line": 3, "column": 1 } }, "id": { "type": "Identifier", "start": 14, "end": 18, "loc": { "start": { "line": 2, "column": 9 }, "end": { "line": 2, "column": 13 }, "identifierName": "name" }, "name": "name" }, "generator": false, "async": false, "params": [], "body": { "type": "BlockStatement", "start": 22, "end": 25, "loc": { "start": { "line": 2, "column": 17 }, "end": { "line": 3, "column": 1 } }, "body": [], "directives": [] }, "leadingComments": [ { "type": "CommentLine", "value": " 1", "start": 0, "end": 4, "loc": { "start": { "line": 1, "column": 0 }, "end": { "line": 1, "column": 4 } } } ] } ```

`print(ast).code` result: ```js function name () { } ``` expected: ``` // 1 function name () { } ``` leadingComments is working for VariableDeclarations for example
a-x- commented 5 years ago

I got: FunctionDeclaration has comments field instead of VariableDeclarations's leadingComments

but ast explorer shows leadingComments for functions too.

where is my bad?

benjamn commented 5 years ago

Recast uses a different representation for comments than you may be used to. Instead of leadingComments and trailingComments properties, there's just a comments array attached to some nodes, with each comment object in the array having optional boolean leading and/or trailing properties. This representation is created by this attachComments call in lib/parser.ts based on the recast.parse(...).comments array, if your parser provides that information.

This representation has proven itself more flexible and effective than having separate {leading,trailing}Comments arrays, at least for tools that need to handle comments in a reasonable way (Babel doesn't really need to do a good job with comments, for example, because comment placement doesn't matter much in generated code).

One reason for this flexibility is that not all comments are leading or trailing: some are neither, such as if (x) { /*empty*/ }, and Recast represents this weird but common case by setting both node.leading and node.trailing to false. The Recast representation of comments was inherited by Prettier (Prettier is a fork of Recast's pretty-printer).

Long story short, my advice would be to stop letting your parser attach comments to the nodes it thinks the comments pertain to, and instead let Recast do the attachment. If you can tell me what parser you're using and how it's configured, I'm happy to help you get this working.

a-x- commented 5 years ago

I use pre-configured babelv7: 'recast/parsers/babel'

a-x- commented 5 years ago

and my parser config:

const babel7 = require('recast/parsers/babel')
const recast = require('recast');

module.exports.parse = function parse (code) {
  return recast.parse(code, {
    parser: {
      parse(source) {
        return babel7.parse(source, {
          sourceType: "module",
          plugins: ["jsx", 'optionalChaining', 'classProperties'],
        });
      }
    }
  });
};
benjamn commented 5 years ago

Ok, I agree this should work with that setup. I suspect @babel/parser isn't returning the comments in the expected format, which is definitely something we can work around in recast/parsers/babel, one way or another.

conartist6 commented 4 years ago

This seems to be fixed, no?

wenq1 commented 1 year ago

not fixed. Issue seems abandoned. See #1272

b0tm1nd commented 3 months ago

I got: FunctionDeclaration has comments field instead of VariableDeclarations's leadingComments

but ast explorer shows leadingComments for functions too.

where is my bad?

With recast 0.23.9 I am also having FunctionDeclaration having comments block instead of leadingComments or trailingComments. The problem is when I try to extract function AST node, comments are getting missed.

Let I have this code:

/**
 * Comments
 */
function testFunction() { return 1;
}

I can access function block like this:

  recast.types.visit(ast, {
    // Handle function declarations (e.g., function foo() { ... })
    visitFunctionDeclaration(path) {
      console.log('Comments:', path.node.comments);
      functionCallback({path: path.node, type: 'FunctionDeclaration', name: path.node.id.name, 
      ...(path.node.comments?.length ? { comments: safeStringify(path.node.comments, ) } : {}) });
      this.traverse(path);
    },
  });

This prints comments block into console. But when trying to access the same path.node.comments inside functionCallback it becomes an empty array. So I am using stringify()/JSON.parse() workaround to preserve comments:

         const funcNode = func.path;
          if (func.comments) { // missing comments fix
            funcNode.comments = JSON.parse(func.comments);
          }
          functionDefinitions[func.name] = generateCode(funcNode);

The same behaviour when parsing code to AST tree with default recast.parse(code); or with babel

   recast.parse(code, {
    parser: require("recast/parsers/babel"),
  });

The only difference is that with recast.parse(code); I can use JSON.stringify(path.node.comments) when with babel I have to use a special function to handle circular structures.