Skip to content
This repository was archived by the owner on Jan 19, 2019. It is now read-only.

Fix: Remove all tokens inside comments from tokens array (fixes #422) #423

Merged
merged 2 commits into from
Feb 17, 2018

Conversation

kaicataldo
Copy link
Member

@kaicataldo kaicataldo commented Dec 24, 2017

This fixes an issue where TypeScript generates tokens for {Type} (for both the brackets and the Identifier Type), causing the indent rule in core to have some false positives. I'm assuming these tokens are generated as part of TypeScript's JS file checking and type hinting from doc blocks.

@eslint/eslint-team As an aside, could we potentially leverage this behavior for our own JSDoc parsing and validation in ESLint core? Would be nice if we could rely on a well-maintained project like TypeScript instead of doctrine, since we've been struggling to maintain that.

Maybe @JamesHenry has some idea of the feasibility of this, given your experience!

This is my first time working with this codebase or really anything TypeScript related, so please do let me know if there are better ways to achieve this or if any assumptions I've made are incorrect.

To illustrate the change, here's a before and after of the tokens and comments arrays generated for the following code:

/**
 * This is a function.
 * @param {String} text
 * @returns {String}
 */
function foo(text) {}

Before this patch:

Tokens:  [ { type: 'Punctuator',
    value: '{',
    range: [ 37, 38 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Identifier',
    value: 'String',
    range: [ 38, 44 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Punctuator',
    value: '}',
    range: [ 44, 45 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Identifier',
    value: 'text',
    range: [ 46, 50 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Punctuator',
    value: '{',
    range: [ 63, 64 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Identifier',
    value: 'String',
    range: [ 64, 70 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Punctuator',
    value: '}',
    range: [ 70, 71 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Keyword',
    value: 'function',
    range: [ 76, 84 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Identifier',
    value: 'foo',
    range: [ 85, 88 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Punctuator',
    value: '(',
    range: [ 88, 89 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Identifier',
    value: 'text',
    range: [ 89, 93 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Punctuator',
    value: ')',
    range: [ 93, 94 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Punctuator',
    value: '{',
    range: [ 95, 96 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Punctuator',
    value: '}',
    range: [ 96, 97 ],
    loc: { start: [Object], end: [Object] } } ]

Comments:  [ { type: 'Block',
    value: '*\n * This is a function.\n * @param {String} text\n * @returns {String}\n ',
    range: [ 0, 75 ],
    loc: { start: [Object], end: [Object] } } ]

After this patch:

Tokens:  [ { type: 'Keyword',
    value: 'function',
    range: [ 76, 84 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Identifier',
    value: 'foo',
    range: [ 85, 88 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Punctuator',
    value: '(',
    range: [ 88, 89 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Identifier',
    value: 'text',
    range: [ 89, 93 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Punctuator',
    value: ')',
    range: [ 93, 94 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Punctuator',
    value: '{',
    range: [ 95, 96 ],
    loc: { start: [Object], end: [Object] } },
  { type: 'Punctuator',
    value: '}',
    range: [ 96, 97 ],
    loc: { start: [Object], end: [Object] } } ]

Comments:  [ { type: 'Block',
    value: '*\n * This is a function.\n * @param {String} text\n * @returns {String}\n ',
    range: [ 0, 75 ],
    loc: { start: [Object], end: [Object] } } ]

@j-f1
Copy link
Contributor

j-f1 commented Dec 24, 2017

Looks good!

Can you delete package-lock.json.1799798656?

@kaicataldo kaicataldo force-pushed the jsdoc-types-fix branch 2 times, most recently from 7f39fe4 to 5ca72f0 Compare December 24, 2017 18:35
@kaicataldo
Copy link
Member Author

@j-f1 Whoops! Not sure how that got in there. Thanks.

@kaicataldo kaicataldo force-pushed the jsdoc-types-fix branch 2 times, most recently from 572ec04 to f33bbe2 Compare December 24, 2017 19:18
@kaicataldo
Copy link
Member Author

kaicataldo commented Dec 24, 2017

I realized I was overthinking this and that it can can actually be simplified immensely by just not walking comment tokens. This way, comment tokens and their children will never be converted or added to the token list.

My only concern now is whether my comment checking algorithm is correct or not, since I'm still not very familiar with the AST or tokens generated by TypeScript.

* @returns {boolean} is commment
*/
function isComment(token) {
return token.kind === SyntaxKind.SingleLineCommentTrivia || token.kind === SyntaxKind.MultiLineCommentTrivia || (token.kind >= SyntaxKind.JSDocTypeExpression && token.kind <= SyntaxKind.JSDocTypeLiteral);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the correct check for comment tokens?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScript themselves use this within their private utilities:

function isComment(kind: SyntaxKind): boolean {
  return kind === SyntaxKind.SingleLineCommentTrivia || kind === SyntaxKind.MultiLineCommentTrivia;
}

I feel like we should split the JSDoc checks out into a separate function.

The TypeScript team has these (again not in their public API, but useful utils):

// JSDoc
    /** True if node is of some JSDoc syntax kind. */
    /* @internal */
    export function isJSDocNode(node: Node): boolean {
        return node.kind >= SyntaxKind.FirstJSDocNode && node.kind <= SyntaxKind.LastJSDocNode;
    }

    /** True if node is of a kind that may contain comment text. */
    export function isJSDocCommentContainingNode(node: Node): boolean {
        return node.kind === SyntaxKind.JSDocComment || isJSDocTag(node);
    }

    // TODO: determine what this does before making it public.
    /* @internal */
    export function isJSDocTag(node: Node): boolean {
        return node.kind >= SyntaxKind.FirstJSDocTagNode && node.kind <= SyntaxKind.LastJSDocTagNode;
    }

@kaicataldo
Copy link
Member Author

Friendly ping @JamesHenry @soda0289

Copy link
Member

@JamesHenry JamesHenry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please could you add a unit test that would be affected by these changes? Your same source from the integration test should do the trick!

* @returns {boolean} is commment
*/
function isComment(token) {
return token.kind === SyntaxKind.SingleLineCommentTrivia || token.kind === SyntaxKind.MultiLineCommentTrivia || (token.kind >= SyntaxKind.JSDocTypeExpression && token.kind <= SyntaxKind.JSDocTypeLiteral);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScript themselves use this within their private utilities:

function isComment(kind: SyntaxKind): boolean {
  return kind === SyntaxKind.SingleLineCommentTrivia || kind === SyntaxKind.MultiLineCommentTrivia;
}

I feel like we should split the JSDoc checks out into a separate function.

The TypeScript team has these (again not in their public API, but useful utils):

// JSDoc
    /** True if node is of some JSDoc syntax kind. */
    /* @internal */
    export function isJSDocNode(node: Node): boolean {
        return node.kind >= SyntaxKind.FirstJSDocNode && node.kind <= SyntaxKind.LastJSDocNode;
    }

    /** True if node is of a kind that may contain comment text. */
    export function isJSDocCommentContainingNode(node: Node): boolean {
        return node.kind === SyntaxKind.JSDocComment || isJSDocTag(node);
    }

    // TODO: determine what this does before making it public.
    /* @internal */
    export function isJSDocTag(node: Node): boolean {
        return node.kind >= SyntaxKind.FirstJSDocTagNode && node.kind <= SyntaxKind.LastJSDocTagNode;
    }

@kaicataldo
Copy link
Member Author

kaicataldo commented Jan 22, 2018

Updated! Two questions:

JSDoc Check

For the JSDoc check, it looks to me like node.kind === SyntaxKind.JSDocComment is sufficient since we stop walking as soon as we encounter the top level JSDocComment node. My inclination is to not add the other utils since the JSDocComment children node types should never be added to the token list since we're outputting ESTree.

Does that make sense or am I missing something? Do you think we should still include those utils? Or are there other downstream consumers who might want to opt into having those tokens available to them (I don't know if Prettier would ever want to get into the business of formatting JSDoc comments, for example)?

Other methods

Do convertToken and getNodeContainer in node-utils also need to be updated to correctly handle JSDoc nodes as well? I believe this change is sufficient for the current use cases for the methods, but can also see the argument for making it less likely to introduce bugs by having these not operate on JSDoc nodes.

Thanks for the review :)

@kaicataldo kaicataldo force-pushed the jsdoc-types-fix branch 2 times, most recently from a91306c to aa41652 Compare January 22, 2018 17:09
@kaicataldo
Copy link
Member Author

Friendly ping!

@JamesHenry
Copy link
Member

I think this is great for now, thanks! I'm going to change it to be a Breaking change though just in case anybody was depending on the old behaviour somehow

@JamesHenry JamesHenry merged commit adc0b1b into eslint:master Feb 17, 2018
@kaicataldo kaicataldo deleted the jsdoc-types-fix branch February 17, 2018 17:00
@kaicataldo
Copy link
Member Author

Thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants