jashkenas / coffeescript

Unfancy JavaScript
https://coffeescript.org/
MIT License
16.52k stars 1.98k forks source link

Bug: Block comments should be placed above variable assignment, not declaration (Fix JSDoc etc) #5366

Open phil294 opened 3 years ago

phil294 commented 3 years ago

Input Code

For example, if you want to output usable JSDoc to integrate with TypeScript:

###*
# @param a {string}
###
method1 = (a) ->

###*
# @param b {string}
###
method2 = (b) ->

Expected Behavior

var method1, method2;

/**
 * @param a {string}
 */
method1 = function(a) {};

/**
 * @param b {string}
 */
method2 = function(b) {};

Current Behavior

/**
 * @param a {string}
 */
/**
 * @param b {string}
 */
var method1, method2;

method1 = function(a) {};

method2 = function(b) {};

Possible Solution

Possible current workaround (pretty ugly):

method1 = method2 = null

#
###*
# @param a {string}
###
method1 = (a) ->

#
###*
# @param b {string}
###
method2 = (b) ->

Alternatively, you can of course do inline typed params

method1 = (###* @type string ### b) ->

but this is not always a feasible solution of course

Context

Besides, should we maybe update the docs to state the possiblity of type-checking via JSDoc+TS? I know there is a TypeScript discussion issue going on in #5307 but this jsdoc thing is already possible.

GeoffreyBooth commented 3 years ago

Yes, the block comments shouldn’t be hoisted with the var declarations. That’s a useless place for the comments to be output, and as you point out, it breaks JSDoc.

The place to look to understand comments is https://github.com/jashkenas/coffeescript/pull/4572, in particular https://github.com/jashkenas/coffeescript/pull/4572#issue-235812849, https://github.com/jashkenas/coffeescript/pull/4572#issuecomment-315287772, https://github.com/jashkenas/coffeescript/pull/4572#issuecomment-316612725, https://github.com/jashkenas/coffeescript/pull/4572#issuecomment-317285440. It’s a long thread, but those comments should give you a general idea of how comments work in the compiler. It’s a bit of a hack because comments aren’t part of JavaScript syntax itself (they’re not nodes in the abstract syntax tree); but this is the same approach that Babel takes, so it’s pretty solid.

It’s probably either in scope.litcoffee or in nodes.coffee Assign that we create the special var line and move it to the top of the function scope. To fix this, you need to find where an Assign node gets split into two nodes, the declaration var line and the assignment line, and make sure any comments attached to the original node get moved onto the assignment node (and not the declaration node, which is where they must be going now hence this issue).

phil294 commented 2 years ago

@robert-boulanger found a similar issue with comment blocks above fat arrow class methods: https://github.com/phil294/coffeesense/issues/1#issuecomment-1019522762

edemaine commented 2 years ago

I think #5395 could help with the original issue here. Currently, that PR doesn't push the var declaration down to first assignment if there's an attached comment (so that the comment doesn't get lost). But it could instead move the comment to the pushed declaration.

I assume it's better to leave #5395 as is, so it's easier to review, and I can work on this after it gets merged. If you'd rather me work on it first, as a proof of concept that pushing var down is useful, let me know.

phil294 commented 2 years ago

Possible current workaround (pretty ugly):


#
###*
# @param a {string}
###
method1 = (a) ->

Side note: I found another, slightly shorter workaround that does not require an extra line:

``###*
# @param a {string}
###
method1 = (a) ->

Edit: I stand corrected, it does not always work. Example:

a =
  b: 1
  ``### abc ###
  c: 2

-> error: unexpected newline

phil294 commented 2 years ago

Yes, the block comments shouldn’t be hoisted with the var declarations. That’s a useless place for the comments to be output, and as you point out, it breaks JSDoc.

I'm not convinced anymore this is the case, because sometimes, you need to declare and type a var before assigning it inside some callback:

###* @type {number} ###
var x
cb = =>
  x = 2
  null
cb()
console.log(x)

not sure how this would be solved. Sure, instead of var x you could write x = 1 but that is different logic, and not feasible when your type is not number but perhaps a complicated third party interface.

So there's two problems here:

It would be questionable to re-introduce the var keyword just for this, but it's a noteworthy limitation on the usage of JSDoc inside CoffeeScript. A workaround with

`var x`

wouldn't work because cb ships its own var x which overrules the parent one.

The proper solution for the programmer is to make cb return the value of x and assigning it outside of cb.

TL;DR: This is not a showstopper and the logic should still be changed as planned

GeoffreyBooth commented 2 years ago

sometimes, you need to declare and type a var before assigning it inside some callback

CoffeeScript doesn't allow variable declarations, only assignments. In your code where you have var x you would instead write x = undefined, which is syntactically identical and is an assignment. The complier generates the declaration farther up, at the top of the function scope. We want the comment output at this assignment, not at the computer's generated declaration.

phil294 commented 2 years ago

x = undefined

Sure! But then you'll have to mark x as @type {number | undefined} and the effective type changes.

In contrast, consider this JS code, perhaps a better example:

/** @type {number} */
var x
var cb = () => {
    x.toFixed()
};
x = 3
cb();

no type error, and runs successfully. Not possible with var x = undefined.

Not great code though, I understand that. You'd risk exposing yourself to some used x before being defined at some point if you mess up assignment order.

GeoffreyBooth commented 2 years ago

As I understand it, you don’t need the | undefined part for JSDoc:

/** @type {number} */
var x;
x = undefined;
x = 3;

This doesn’t error in the TypeScript playground: https://www.typescriptlang.org/play?#code/PQKhAIAEBcE8AcCm4DeA7ArgWwEaIE4C+4IwAUAG4CG+4AHgNxl3gC84GaAJogGYCWaRFyYt2AZiZA

This does show that we need the JSDoc output above the var declaration, though, not above the assignment, in order for TypeScript to detect it. So I guess that’s something to consider.

phil294 commented 2 years ago

ah cool. Not with strictNullChecks: true, though. playground link

(and also, you need to set language to JavaScript as JSDoc has no effect inside .ts files)