Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Conversation

Alhadis
Copy link
Contributor

@Alhadis Alhadis commented Mar 3, 2017

Fixes #495.

Description of the Change

This PR separates the patterns for highlighting JSDoc tags from the main JavaScript grammar. The lookahead patterns currently responsible for this are inflicting severe performance issues in certain cases, which is to say nothing of the maintainability issues they've always imposed.

Additionally, I've also included inline JS highlighting for @example tags:

Figure 1

Alternate Designs / Thoughts

  • Initially, I wrote each block tag to span multiple lines, but realised highlighting something like @access \n private really didn't justify the added complexity.

Benefits

Other than being much more readable and maintainable, I imagine this will boost the tokeniser's performance drastically. It might be interesting to benchmark.

Possible Drawbacks

In light of the countless intricacies of Closure Compiler, I've opted to match {types} and @param tags with an inclusive approach, rather than exclusive. Users will no longer be able to rely on what this grammar matches to indicate what syntax is valid. To be honest, I feel it's a fair trade-off... grammars shouldn't be responsible for validating input (at least beyond the scope of unclosed strings, etc).

@Alhadis
Copy link
Contributor Author

Alhadis commented Mar 3, 2017

/cc @50Wliu @bgriffith

@winstliu
Copy link
Contributor

winstliu commented Mar 6, 2017

Oh look, another huge PR to review from @Alhadis 😁. I may need to punt this for a while to get a language-c rewrite merged first, as that grammar is in pretty bad shape right now.

'3':
'name': 'entity.name.type.instance.jsdoc'
'4':
'name': 'punctuation.definition.begin.jsdoc'
Copy link
Contributor

Choose a reason for hiding this comment

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

.bracket.angle

'match': '''(?x)
((@)borrows) \\s+
((?:[^@\\s*/]|\\*[^/])++) # <that namepath>
\\s++ (as) \\s++ # as
Copy link
Contributor

Choose a reason for hiding this comment

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

Why the possessive match?

# URL
(
(?=https?://)
(?:[^\\s*]|\\*[/])+
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is the forwards slash in a character class? Did you perhaps forget a ^? I noticed it also looks different from all the other patterns similar to this one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, fixed.

# JSDoc namepath
(
(?!https?://)
(?:[^@\\s*/]|\\*[^/])++
Copy link
Contributor

Choose a reason for hiding this comment

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

Why possessive?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because I'm a possessive prick.

(removed)*+

[A-Za-z_$] # First character: non-numeric word character
[\\w$.\\[\\]]* # Rest of identifier
(?: # Possible list of additional identifiers
\\s* , \\s*
Copy link
Contributor

Choose a reason for hiding this comment

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

the comma should be tokenized

]
}
]

Copy link
Contributor

Choose a reason for hiding this comment

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

🔥 empty line

runs ->
grammar = atom.grammars.grammarForScopeName("source.js")


Copy link
Contributor

Choose a reason for hiding this comment

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

🔥 empty line

expect(tokens[1]).toEqual value: ' Text ', scopes: ['source.js', 'comment.block.documentation.js']
expect(tokens[2]).toEqual value: '{@', scopes: ['source.js', 'comment.block.documentation.js', 'entity.name.type.instance.jsdoc', 'storage.type.class.jsdoc', 'punctuation.definition.inline.tag.begin.jsdoc']
expect(tokens[3]).toEqual value: 'link', scopes: ['source.js', 'comment.block.documentation.js', 'entity.name.type.instance.jsdoc', 'storage.type.class.jsdoc']
expect(tokens[4]).toEqual value: 'plain', scopes: ['source.js', 'comment.block.documentation.js', 'entity.name.type.instance.jsdoc', 'variable.other.description.jsdoc']
Copy link
Contributor

Choose a reason for hiding this comment

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

This is interesting...shouldn't it be tokenized as linkplain and not separately?

expect(tokens[1]).toEqual value: ' Text ', scopes: ['source.js', 'comment.block.documentation.js']
expect(tokens[2]).toEqual value: '{@', scopes: ['source.js', 'comment.block.documentation.js', 'entity.name.type.instance.jsdoc', 'storage.type.class.jsdoc', 'punctuation.definition.inline.tag.begin.jsdoc']
expect(tokens[3]).toEqual value: 'link', scopes: ['source.js', 'comment.block.documentation.js', 'entity.name.type.instance.jsdoc', 'storage.type.class.jsdoc']
expect(tokens[4]).toEqual value: 'code', scopes: ['source.js', 'comment.block.documentation.js', 'entity.name.type.instance.jsdoc', 'variable.other.description.jsdoc']
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto

expect(tokens[5]).toEqual value: '{', scopes: ['source.js', 'comment.block.documentation.js', 'entity.name.type.instance.jsdoc', 'punctuation.definition.bracket.curly.begin.jsdoc']
expect(tokens[6]).toEqual value: '*', scopes: ['source.js', 'comment.block.documentation.js', 'entity.name.type.instance.jsdoc']
expect(tokens[7]).toEqual value: '}', scopes: ['source.js', 'comment.block.documentation.js', 'entity.name.type.instance.jsdoc', 'punctuation.definition.bracket.curly.end.jsdoc']
expect(tokens[9]).toEqual value: 'variable', scopes: ['source.js', 'comment.block.documentation.js', 'variab
Copy link
Contributor

Choose a reason for hiding this comment

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

🔥 empty line

@winstliu
Copy link
Contributor

winstliu commented Mar 8, 2017

Would it be worthwhile to tokenize default values as actual JS?

@Alhadis
Copy link
Contributor Author

Alhadis commented Mar 9, 2017

Possibly, but that would likely leave us at the mercy of runaway begin/end pairs if the terminating ] was matched by something else.

@Alhadis
Copy link
Contributor Author

Alhadis commented Mar 9, 2017

... never mind, I said nothing.

I shouldn't be allowed to communicate with humans until I've finished waking up.

Copy link
Contributor

@winstliu winstliu left a comment

Choose a reason for hiding this comment

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

Note: All I did was highlight instances of possessive matching. I'm unsure if any of those are intentional, if so, just ignore the comment.

'match': '''(?x)
((@)borrows) \\s+
((?:[^@\\s*/]|\\*[^/])+) # <that namepath>
\\s++ (as) \\s+ # as
Copy link
Contributor

Choose a reason for hiding this comment

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

You forgot to remove the first possessive match on this line.

}
{
'name': 'entity.name.type.instance.jsdoc'
'match': '(?:[^@\\s*/]|\\*[^/])++'
Copy link
Contributor

Choose a reason for hiding this comment

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

Possessive

\\s+
(
[^@\\s<>*/]
(?:[^@<>*/]|\\*[^/])*+
Copy link
Contributor

Choose a reason for hiding this comment

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

Possessive

}
{
# Highlighted JavaScript example
'match': '[^\\s@*](?:[^*]|\\*[^/])*+'
Copy link
Contributor

Choose a reason for hiding this comment

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

Possessive

(?:
\\s*
(<)
([^>\\s]++)
Copy link
Contributor

Choose a reason for hiding this comment

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

Possessive

@Alhadis
Copy link
Contributor Author

Alhadis commented Mar 9, 2017

Fixed.

@winstliu winstliu merged commit 2ba9a98 into atom:master Mar 9, 2017
@Alhadis
Copy link
Contributor Author

Alhadis commented Mar 10, 2017

@50Wliu Urgh, I forgot to accommodate arrays when highlighting default values as JavaScript. Something like this will only be partially highlighted:

/** @param {Object} [thing={a: [], b: [0, 2], c: "String"}] [Not Highlighted] [] [] [] */
                                ^ Terminates here

Currently working on a fix, although this is surprisingly tricky to get right...

@winstliu
Copy link
Contributor

If it's too difficult/tricky to achieve just revert the embedded highlighting all together.

@Alhadis
Copy link
Contributor Author

Alhadis commented Mar 10, 2017

No no, so far, so good... it's just that if users get too carried away with really convoluted complex values, they might get flaky-looking highlighting.

Then again, if they're stuffing that much into a default value, what else do you expect? 😀 Heck, this might even discourage an anti-pattern, haha.

@Alhadis
Copy link
Contributor Author

Alhadis commented Mar 10, 2017

Okay, fixed. Pleeeease don't chide me over the odd-looking way I've implemented this... :D Some repetition was necessary...

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Catastrophic backtracking in JSDoc param types

2 participants