Add code lens for quick evaluation#1035
Conversation
e73f4fc to
d384943
Compare
adityasharad
left a comment
There was a problem hiding this comment.
A good start, and nice to see what we can achieve just from the editor.
| Range | ||
| } from 'vscode'; | ||
|
|
||
| class MyCodeLensProvider implements CodeLensProvider { |
There was a problem hiding this comment.
Give this class a meaningful name, e.g. QuickEvalCodeLensProvider. The file name can be codeLensProvider.ts to remain consistent with surrounding files.
| const startIndex = textLine.text.search(regex); | ||
| const range: Range = new Range( | ||
| textLine.range.start.line, startIndex, | ||
| textLine.range.end.line, startIndex - 1 |
There was a problem hiding this comment.
Negative moves left, positive moves right.
| textLine.range.end.line, startIndex - 1 | |
| textLine.range.end.line, startIndex + 1 |
There was a problem hiding this comment.
I was thinking of something else when I made this change, but I didn't need to. Not sure now what that thought was. Changing back now. :)
| const textLine = document.lineAt(index); | ||
| const regex = new RegExp(/(?=(\w+\(\s*.*(?:,\s*)*\)\s*\{))\1/g); | ||
| if (textLine.text.match(regex)) { | ||
| const uri = document.uri; |
There was a problem hiding this comment.
Minor cleanup: just use document.uri directly in the list of arguments.
| for (let index = 0; index < document.lineCount; index++) { | ||
| const textLine = document.lineAt(index); | ||
| const regex = new RegExp(/(?=(\w+\(\s*.*(?:,\s*)*\)\s*\{))\1/g); | ||
| if (textLine.text.match(regex)) { |
There was a problem hiding this comment.
You're regex matching twice: once with match and once with search. That can be avoided by just calling const startIndex = textLine.text.search(regex). If there is no match this will be -1, if there is a match this will be the start index of the matching substring.
|
|
||
| for (let index = 0; index < document.lineCount; index++) { | ||
| const textLine = document.lineAt(index); | ||
| const regex = new RegExp(/(?=(\w+\(\s*.*(?:,\s*)*\)\s*\{))\1/g); |
There was a problem hiding this comment.
As written, this regex performs lookahead to find a pattern without capturing it, and then uses \1 to capture that same pattern. I think you can simplify that. Try something like this.
It's also a good idea to include a human-readable comment every time you write a regex, either explaining the intent or giving a short example. Reading them is hard 😄
| const regex = new RegExp(/(?=(\w+\(\s*.*(?:,\s*)*\)\s*\{))\1/g); | |
| // Match a predicate signature, including the predicate name, parameter list, and the opening brace. | |
| const regex = new RegExp(/\w+\([^()]*\)\s*\{/); |
There was a problem hiding this comment.
Making this change. I've always struggled writing regex (although I'm getting more comfortable with it now), so I always appreciate feedback. The only reason I included the positive lookahead was I was getting a code scanning failure about potential exponential backtracking. But I have also since simplified the regex, so maybe that won't be an issue this time.
There was a problem hiding this comment.
Yup I think your new regex is free of this problem, because of the simpler handling of the (...) parameters!
If you'd like to learn more about exponential regexes, the CodeQL docs for the alert you saw are pretty good: https://un5kwke0ketx6vwhy3c87d8.julianrbryant.com/codeql-query-help/javascript/js-redos/. Exponential backtracking usually happens if you have something like (pattern1|pattern2)*, because at each step of matching the next character in the * sequence, we don't know whether we just saw a pattern1 or a pattern2, and have to backtrack to try both options if the next match fails.
|
Neat. I tried this out and it works. Though, it shows a quick eval suggestion even when the code is commented. eg- this still shows a quick eval option: |
|
And the following code does not show a quick eval option (notice the space before the This is an inherent limitation of using regexes. I think it's important to fix the first problem I mentioned, but this one is less important. |
| for (let index = 0; index < document.lineCount; index++) { | ||
| const textLine = document.lineAt(index); | ||
| // Match a predicate signature, including predicate name, parameter list, and opening brace. | ||
| const regex = new RegExp(/\w+\(\s*.*(?:,\s*)*\)\s*\{/); |
There was a problem hiding this comment.
It would be really nice to get the name of the predicate in the codelens text. You can do that by capturing the word as below:
| const regex = new RegExp(/\w+\(\s*.*(?:,\s*)*\)\s*\{/); | |
| const regex = new RegExp(/(\w+)\(\s*.*(?:,\s*)*\)\s*\{/); |
Then you can call: const matches = textLine.text.match(regex);
matches[1] contains the matched word.
There was a problem hiding this comment.
Made these changes. I also added an additional whitespace match between the word match and opening '(' to account for any predicate definition that has that space.
|
|
||
| const command: Command = { | ||
| command: 'codeQL.codeLensQuickEval', | ||
| title: 'CodeQL: Quick Evaluation', |
There was a problem hiding this comment.
| title: 'CodeQL: Quick Evaluation', | |
| title: `Quick Evaluation: ${matches[1]}`, |
adityasharad
left a comment
There was a problem hiding this comment.
Good work wiring this together. A few follow up suggestions.
| const startIndex = textLine.text.search(regex); | ||
|
|
||
| if (startIndex !== -1) { |
There was a problem hiding this comment.
Avoid calling both match and search, because you're doubling the computation cost at each line by regex matching twice. Now that we're using information from the result of match, stick with that.
matches.index will give you the start index you need, and if (matches) will give you the condition to check whether the match succeeded (because it returns null if there is no match.
There was a problem hiding this comment.
Thanks for pointing this out. I got it fixed now. :)
| async ( | ||
| progress: ProgressCallback, | ||
| token: CancellationToken, | ||
| uri: Uri | undefined, |
There was a problem hiding this comment.
I don't think this can ever be undefined, so might as well make the type strict.
| const codelensProvider = new QuickEvalCodeLensProvider(); | ||
|
|
||
| languages.registerCodeLensProvider({ scheme: 'file', language: 'ql' }, codelensProvider); |
There was a problem hiding this comment.
Please move this further down in the function: I don't think code lens creation should happen before we call languageSupport.install(), and certainly not before we've set up the extension and logging.
There was a problem hiding this comment.
Moved it to be after languageSupport.install(), but if you think it should go further down in the function I can move it again.
extensions/ql-vscode/CHANGELOG.md
Outdated
|
|
||
| ## [UNRELEASED] | ||
|
|
||
| - Add CodeLens feature to make the CodeQL quick evaluation command more accessible. [#1035](https://github.com/github/vscode-codeql/pull/1035) |
There was a problem hiding this comment.
I've attempted to word this in a way that tells the user how to use the feature. What do you think?
| - Add CodeLens feature to make the CodeQL quick evaluation command more accessible. [#1035](https://github.com/github/vscode-codeql/pull/1035) | |
| - Add a CodeLens to make the Quick Evaluation command more accessible. Click the `Quick Evaluation` prompt above a predicate definition in the editor to evaluate that predicate on its own. [#1035](https://github.com/github/vscode-codeql/pull/1035) |
There was a problem hiding this comment.
I like it. I tried to make it concise, but I should have included more information.
aeisenberg
left a comment
There was a problem hiding this comment.
Looking really good. Just a couple of more small cleanups.
| class QuickEvalCodeLensProvider implements CodeLensProvider { |
There was a problem hiding this comment.
nit: you can delete this boilerplate.
| if (matches) { | ||
| const range: Range = new Range( | ||
| textLine.range.start.line, matches.index!, | ||
| textLine.range.end.line, matches.index! + 1 |
There was a problem hiding this comment.
Type assertion no longer necessary.
| title: `Quick Evaluation: ${matches![1]}`, | |
| title: `Quick Evaluation: ${matches[1]}`, |
There was a problem hiding this comment.
Change has been made. 👍🏻
d4079cb to
b9c5e38
Compare
| const regex = new RegExp(/(\w+)\s*\(\s*.*(?:,\s*)*\)\s*\{/); | ||
| const matches = textLine.text.match(regex); | ||
|
|
||
| if (matches) { |
There was a problem hiding this comment.
Ensure that commented code removes the code lens. It's too tricky to do in a single regex, so I think doing another check is ok.
| if (matches) { | |
| if (matches && textLine.text.search(/^\s*\//)) { |
There was a problem hiding this comment.
We had an illuminating discussion offline about why this works (search returns 0 when it matches the start of the line and -1 on mismatch, which JS converts into false and true respectively). I suggested a slightly more verbose but arguably simpler use of test instead -- hopefully that's still captured the meaning you suggested here!
There was a problem hiding this comment.
A downside with the suggestion is that the regex is evaluated twice for every line, even if there is no initial match. The original suggestion only ran the second regex if the first one matched. I'm not sure if the extra overhead would be noticeable, but this code is run after every file change (in a background thread).
There was a problem hiding this comment.
That's a good point. We can keep using !test rather than search, but keep it inline instead of in its own variable.
So if (matches && /.../.test(textLine.text)
There was a problem hiding this comment.
Yep....that makes sense.
There was a problem hiding this comment.
Made this change. Sorry it took a bit. I forgot I had resolved the CHANGELOG conflict from here but I hadn't updated my local branch yet. Had to get that squared away. Does it look better?
aeisenberg
left a comment
There was a problem hiding this comment.
Looks good, but I can't approve since I was the one who created the PR.
|
@adityasharad Can you approve? I can't since I created this PR. |
Head branch was pushed to by a user without write access
…odelens-quick-eval
…e-codeql into codelens-quick-eval
aeisenberg
left a comment
There was a problem hiding this comment.
![]()
(I can't approve.)
Replace this with a description of the changes your pull request makes. Closes #446
Checklist
@github/docs-content-codeqlhas been cc'd in all issues for UI or other user-facing changes made by this pull request.