-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Add refactor convertToOptionalChainExpression #39135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jessetrinity
merged 49 commits into
microsoft:master
from
jessetrinity:refactorOptionalChain
Jul 14, 2020
Merged
Changes from 20 commits
Commits
Show all changes
49 commits
Select commit
Hold shift + click to select a range
77a49c3
add convertOptionalChain
9cf07cf
cover more cases
80bf5d1
expose containsMatchingReference
4e64236
clean up performing edits
9a59e13
bound start position
6c1bccf
add tests
2a355e4
refactor and handle edge cases
3dac9c6
update tests
b403037
consider explicit requests for empty spans
1ae5500
update fourslash to use trigger reason
bf28673
add tests cases for trigger reason
fa3f9c8
fix errors
8794154
remove type assertion
3d8fed1
fix non ampersand chains
b5c833c
clean up some logic
796b2bf
add ternary case
37004b7
add diagnostic message
3584bae
resolve merge conflict
1b2e86b
Merge remote-tracking branch 'upstream/master' into refactorOptionalC…
afb0e44
add nullish check for ternary expressions
65ca81e
Update src/services/refactors/convertToOptionalChainExpression.ts
jessetrinity d9c34ff
Update src/services/refactors/convertToOptionalChainExpression.ts
jessetrinity c2b9924
Update tests/cases/fourslash/refactorConvertToOptionalChainExpression…
jessetrinity 1d51dab
Update tests/cases/fourslash/refactorConvertToOptionalChainExpression…
jessetrinity adbd586
reformat and remove unused checks
fb6b831
allow any for ternary refactor
8184ecf
add tests
5ac29a0
add tests
a0708be
check return and variable statements
6810cea
use isMatchingReference instead of containsMatchingReference
5634a4c
allow partial selections
e6e54cb
fine tune selection ranges
77f47e7
recurse for call expressions
1ba5fd0
fix spellings
6095e95
add recursive cases
89c8c9d
remove isOrContainsMatchingReference
b20cd95
cleanup
15176b2
more refactoring
2de5e28
cleanup
4126a46
rename tests
8f65c02
address PR comments
d4f6f52
Merge remote-tracking branch 'upstream/master' into refactorOptionalC…
8aed315
check match syntactically
602075f
handle another call expression case
a4cc060
some renames
e89ae08
inline some checks
2cdb5e1
add test
fd64b14
address comments
01854bb
add refactorNotAvailableReason
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
130 changes: 130 additions & 0 deletions
130
src/services/refactors/convertToOptionalChainExpression.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/* @internal */ | ||
namespace ts.refactor.convertToOptionalChainExpression { | ||
const refactorName = "Convert to optional chain expression"; | ||
const convertToOptionalChainExpressionMessage = getLocaleSpecificMessage(Diagnostics.Convert_to_optional_chain_expression); | ||
|
||
registerRefactor(refactorName, { getAvailableActions, getEditsForAction }); | ||
|
||
function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { | ||
const info = getInfo(context, context.triggerReason === "invoked"); | ||
if (!info) return emptyArray; | ||
return [{ | ||
name: refactorName, | ||
description: convertToOptionalChainExpressionMessage, | ||
actions: [{ | ||
name: refactorName, | ||
description: convertToOptionalChainExpressionMessage | ||
}] | ||
}]; | ||
} | ||
|
||
function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { | ||
const info = getInfo(context); | ||
if (!info) return undefined; | ||
const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program.getTypeChecker(), t, info, actionName)); | ||
return { edits, renameFilename: undefined, renameLocation: undefined }; | ||
} | ||
|
||
interface Info { | ||
fullPropertyAccess: PropertyAccessExpression, | ||
firstOccurrence: Node, | ||
expression: BinaryExpression | ConditionalExpression | ||
} | ||
|
||
function getInfo(context: RefactorContext, considerEmptySpans = true): Info | undefined { | ||
const { file, program } = context; | ||
const span = getRefactorContextSpan(context); | ||
|
||
if (span.length === 0 && !considerEmptySpans) return undefined; | ||
const forEmptySpan = span.length === 0 && considerEmptySpans; | ||
|
||
const startToken = getTokenAtPosition(file, span.start); | ||
|
||
const containingNode = forEmptySpan ? findAncestor(startToken, (node) => { return isExpressionStatement(node) && (isBinaryExpression(node.expression) || isConditionalExpression(node.expression)); }) : getParentNodeInSpan(startToken, file, span); | ||
jessetrinity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const expression = containingNode && isExpressionStatement(containingNode) ? containingNode.expression : containingNode; | ||
if (!expression) return undefined; | ||
|
||
const checker = program.getTypeChecker(); | ||
|
||
|
||
if (isBinaryExpression(expression)) { | ||
sandersn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const start = forEmptySpan ? expression.pos : startToken.pos; | ||
|
||
const fullPropertyAccess = getFullPropertyAccessChain(expression); | ||
if (!fullPropertyAccess) return undefined; | ||
if (expression.operatorToken.kind !== SyntaxKind.AmpersandAmpersandToken && expression.operatorToken.pos <= fullPropertyAccess.pos) return undefined; | ||
|
||
// ensure that each sequential operand in range matches the longest acceess chain | ||
let checkNode = expression.left; | ||
let firstOccurrence: PropertyAccessExpression | Identifier = fullPropertyAccess; | ||
while (isBinaryExpression(checkNode) && checkNode.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && (isPropertyAccessExpression(checkNode.right) || isIdentifier(checkNode.right)) && checkNode.right.pos >= start) { | ||
jessetrinity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!checker.containsMatchingReference(fullPropertyAccess, checkNode.right)) { | ||
jessetrinity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return undefined; | ||
} | ||
firstOccurrence = checkNode.right; | ||
checkNode = checkNode.left; | ||
} | ||
// check final identifier | ||
if ((isIdentifier(checkNode) || isPropertyAccessExpression(checkNode)) && checker.containsMatchingReference(fullPropertyAccess, checkNode) && checkNode.pos >= start) { | ||
firstOccurrence = checkNode; | ||
} | ||
return firstOccurrence ? { fullPropertyAccess, firstOccurrence, expression } : undefined; | ||
} | ||
|
||
if (isConditionalExpression(expression)) { | ||
jessetrinity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const whenTrue = expression.whenTrue; | ||
const condition = expression.condition; | ||
if ((isIdentifier(condition) || isPropertyAccessExpression(condition)) && isPropertyAccessExpression(whenTrue) && checker.containsMatchingReference(whenTrue, condition)) { | ||
// The ternary expression and nullish coalescing would result in different return values if c is nullish so do not offer a refactor | ||
const type = checker.getTypeAtLocation(whenTrue.name); | ||
if (checker.isNullableType(type) || type.flags & TypeFlags.Any) { | ||
return undefined; | ||
} | ||
return { fullPropertyAccess: whenTrue, firstOccurrence: condition, expression }; | ||
} | ||
} | ||
return undefined; | ||
} | ||
|
||
function getRightHandSidePropertyAccess(node: BinaryExpression | CallExpression): PropertyAccessExpression | undefined { | ||
if (isCallExpression(node) && isPropertyAccessExpression(node.expression)) { | ||
// a && |a.b|(); | ||
return node.expression; | ||
} | ||
else if (isBinaryExpression(node)) { | ||
if (isPropertyAccessExpression(node.left)) { | ||
// a && |a.b| == 1; | ||
return node.left; | ||
} | ||
else if (isCallExpression(node.left) && isPropertyAccessExpression(node.left.expression)) { | ||
jessetrinity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// a && |a.b|() == 1; | ||
return node.left.expression; | ||
} | ||
} | ||
return undefined; | ||
} | ||
|
||
function getFullPropertyAccessChain(node: BinaryExpression): PropertyAccessExpression | undefined { | ||
return isBinaryExpression(node.right) || isCallExpression(node.right) | ||
jessetrinity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
? getRightHandSidePropertyAccess(node.right) : node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && isPropertyAccessExpression(node.right) && !isOptionalChain(node.right) | ||
? node.right : undefined; | ||
} | ||
|
||
function convertPropertyAccessToOptionalChain(checker: TypeChecker, toConvert: PropertyAccessExpression, until: Identifier | PrivateIdentifier): PropertyAccessExpression { | ||
if (isPropertyAccessExpression(toConvert.expression) && checker.getSymbolAtLocation(toConvert.expression.name) !== checker.getSymbolAtLocation(until)) { | ||
return factory.createPropertyAccessChain(convertPropertyAccessToOptionalChain(checker, toConvert.expression, until), factory.createToken(SyntaxKind.QuestionDotToken), toConvert.name); | ||
} | ||
return factory.createPropertyAccessChain(toConvert.expression, factory.createToken(SyntaxKind.QuestionDotToken), toConvert.name); | ||
} | ||
|
||
function doChange(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, info: Info, _actionName: string): void { | ||
const { fullPropertyAccess, firstOccurrence, expression } = info; | ||
const until = isPropertyAccessExpression(firstOccurrence) ? firstOccurrence.name : isIdentifier(firstOccurrence) ? firstOccurrence : fullPropertyAccess.name; | ||
if (isBinaryExpression(expression)) { | ||
changes.replaceNodeRange(sourceFile, firstOccurrence, fullPropertyAccess, convertPropertyAccessToOptionalChain(checker, fullPropertyAccess, until)); | ||
} | ||
else if (isConditionalExpression(expression)) { | ||
changes.replaceNode(sourceFile, expression, factory.createBinaryExpression(convertPropertyAccessToOptionalChain(checker, fullPropertyAccess, until), factory.createToken(SyntaxKind.QuestionQuestionToken), expression.whenFalse)); | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
tests/cases/fourslash/refactorConvertToOptionalChainExpression1.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
////let a = { b: { c: 0 } }; | ||
/////*a*/a && a.b && a.b.c;/*b*/ | ||
|
||
goTo.select("a", "b"); | ||
edit.applyRefactor({ | ||
refactorName: "Convert to optional chain expression", | ||
actionName: "Convert to optional chain expression", | ||
actionDescription: "Convert to optional chain expression", | ||
newContent: | ||
`let a = { b: { c: 0 } }; | ||
a?.b?.c;` | ||
}); |
7 changes: 7 additions & 0 deletions
7
tests/cases/fourslash/refactorConvertToOptionalChainExpression10.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
////let a = { b: { c: 0 } }; | ||
/////*a*/a && a.b && a?.b?.c;/*b*/ | ||
|
||
goTo.select("a", "b"); | ||
verify.not.refactorAvailable("Convert to optional chain expression"); |
14 changes: 14 additions & 0 deletions
14
tests/cases/fourslash/refactorConvertToOptionalChainExpression11.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
////let a = { b: 0 }; | ||
/////*a*/a ? a.b : "whenFalse";/*b*/ | ||
|
||
goTo.select("a", "b"); | ||
edit.applyRefactor({ | ||
refactorName: "Convert to optional chain expression", | ||
actionName: "Convert to optional chain expression", | ||
actionDescription: "Convert to optional chain expression", | ||
newContent: | ||
`let a = { b: 0 }; | ||
a?.b ?? "whenFalse";` | ||
}); |
14 changes: 14 additions & 0 deletions
14
tests/cases/fourslash/refactorConvertToOptionalChainExpression12.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
////let a = { b: { c: 0 } }; | ||
/////*a*/a.b ? a.b.c : "whenFalse";/*b*/ | ||
jessetrinity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
goTo.select("a", "b"); | ||
edit.applyRefactor({ | ||
refactorName: "Convert to optional chain expression", | ||
actionName: "Convert to optional chain expression", | ||
actionDescription: "Convert to optional chain expression", | ||
newContent: | ||
`let a = { b: { c: 0 } }; | ||
a.b?.c ?? "whenFalse";` | ||
}); |
15 changes: 15 additions & 0 deletions
15
tests/cases/fourslash/refactorConvertToOptionalChainExpression13.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
// @strict: true | ||
|
||
////interface Foo { | ||
//// bar?:{ | ||
//// baz: string; | ||
//// } | ||
////} | ||
////declare let foo: Foo; | ||
/////*a*/foo.bar ? foo.bar.baz : "whenFalse";/*b*/ | ||
|
||
// Offer the refactor for ternary expressions if type of baz is not any, null, unknown, or undefined | ||
jessetrinity marked this conversation as resolved.
Show resolved
Hide resolved
|
||
goTo.select("a", "b"); | ||
verify.refactorAvailable("Convert to optional chain expression"); |
15 changes: 15 additions & 0 deletions
15
tests/cases/fourslash/refactorConvertToOptionalChainExpression14.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
// @strict: true | ||
|
||
////interface Foo { | ||
//// bar?:{ | ||
//// baz?: any; | ||
//// } | ||
////} | ||
////declare let foo: Foo; | ||
/////*a*/foo.bar ? foo.bar.baz : "whenFalse";/*b*/ | ||
|
||
// do not offer a refactor for ternary expression if type of baz is any | ||
goTo.select("a", "b"); | ||
verify.not.refactorAvailable("Convert to optional chain expression"); |
15 changes: 15 additions & 0 deletions
15
tests/cases/fourslash/refactorConvertToOptionalChainExpression15.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
// @strict: true | ||
|
||
////interface Foo { | ||
//// bar?:{ | ||
//// baz?: string | null; | ||
//// } | ||
////} | ||
////declare let foo: Foo; | ||
/////*a*/foo.bar ? foo.bar.baz : "whenFalse";/*b*/ | ||
|
||
// do not offer a refactor for ternary expression if type of baz is nullish | ||
goTo.select("a", "b"); | ||
verify.not.refactorAvailable("Convert to optional chain expression"); |
14 changes: 14 additions & 0 deletions
14
tests/cases/fourslash/refactorConvertToOptionalChainExpression2.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
////let a = { b: { c: () => { } } }; | ||
/////*a*/a && a.b && a.b.c();/*b*/ | ||
|
||
goTo.select("a", "b"); | ||
edit.applyRefactor({ | ||
refactorName: "Convert to optional chain expression", | ||
actionName: "Convert to optional chain expression", | ||
actionDescription: "Convert to optional chain expression", | ||
newContent: | ||
`let a = { b: { c: () => { } } }; | ||
a?.b?.c();` | ||
}); |
14 changes: 14 additions & 0 deletions
14
tests/cases/fourslash/refactorConvertToOptionalChainExpression3.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
////let a = { b: { c: 0 } }; | ||
/////*a*/a && a.b && a.b.c === 1;/*b*/ | ||
|
||
goTo.select("a", "b"); | ||
edit.applyRefactor({ | ||
refactorName: "Convert to optional chain expression", | ||
actionName: "Convert to optional chain expression", | ||
actionDescription: "Convert to optional chain expression", | ||
newContent: | ||
`let a = { b: { c: 0 } }; | ||
a?.b?.c === 1;` | ||
}); |
14 changes: 14 additions & 0 deletions
14
tests/cases/fourslash/refactorConvertToOptionalChainExpression4.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
////let a = { b: { c: () => { } } }; | ||
/////*a*/a && a.b && a.b.c() === 1;/*b*/ | ||
|
||
goTo.select("a", "b"); | ||
edit.applyRefactor({ | ||
refactorName: "Convert to optional chain expression", | ||
actionName: "Convert to optional chain expression", | ||
actionDescription: "Convert to optional chain expression", | ||
newContent: | ||
`let a = { b: { c: () => { } } }; | ||
a?.b?.c() === 1;` | ||
}); |
14 changes: 14 additions & 0 deletions
14
tests/cases/fourslash/refactorConvertToOptionalChainExpression5.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
////let a = { b: { c: 0 } }; | ||
////if(/*a*/a && a.b && a.b.c/*b*/){}; | ||
|
||
goTo.select("a", "b"); | ||
edit.applyRefactor({ | ||
refactorName: "Convert to optional chain expression", | ||
actionName: "Convert to optional chain expression", | ||
actionDescription: "Convert to optional chain expression", | ||
newContent: | ||
`let a = { b: { c: 0 } }; | ||
if(a?.b?.c){};` | ||
}); |
24 changes: 24 additions & 0 deletions
24
tests/cases/fourslash/refactorConvertToOptionalChainExpression6.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
////interface Foo { | ||
//// bar?:{ | ||
//// baz?: string; | ||
//// } | ||
////} | ||
////declare let foo: Foo | undefined; | ||
/////*a*/foo && foo.bar && foo.bar.baz;/*b*/ | ||
|
||
goTo.select("a", "b"); | ||
edit.applyRefactor({ | ||
refactorName: "Convert to optional chain expression", | ||
actionName: "Convert to optional chain expression", | ||
actionDescription: "Convert to optional chain expression", | ||
newContent: | ||
`interface Foo { | ||
bar?:{ | ||
baz?: string; | ||
} | ||
} | ||
declare let foo: Foo | undefined; | ||
foo?.bar?.baz;` | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.