-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Semantic snippets - for loop snippet #62990
Conversation
|
||
// Creating the variable declaration based on if the user has | ||
// 'var for built in types' set in their editorconfig. | ||
var variableDeclarationSyntax = varBuiltInType |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could the conditional be used to create the varIdentifier
? Seems like everything other than the first parameter to SyntaxFactory.VariableDeclaration
here is identical.
indexVariable, | ||
argumentList: null, | ||
SyntaxFactory.EqualsValueClause((ExpressionSyntax)generator.LiteralExpression(0))))) | ||
: SyntaxFactory.VariableDeclaration(compilation.GetSpecialType(SpecialType.System_Int32).GenerateTypeSyntax(allowVar: false), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious, what does allowVar
do here? Can it just be set to varBuildInType
and does the syntax generator do magic for you then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@davidwengier To my knowledge, it adds DoNotAllowVarAnnotation.Annotation
. This annotation is used by CSharpUseImplicitTypeHelper
.
Lines 60 to 61 in a84cae0
if (!allowVar) | |
syntax = syntax.WithAdditionalAnnotations(DoNotAllowVarAnnotation.Annotation); |
Lines 38 to 41 in a84cae0
if (typeName.HasAnnotation(DoNotAllowVarAnnotation.Annotation)) | |
{ | |
return default; | |
} |
One important use of CSharpUseImplicitTypeHelper
is CSharpVarReducer
, which is run after every CodeAction
is applied.
I think no code actions are involved here, so most likely this annotation will do nothing?
I'm curious though why we will ever want to put a DoNotAllowVarAnnotation.Annotation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@davidwengier Note that if this was a CodeAction
(e.g, in a CodeFixProvider or CodeRefactoringProvider), we can always generate a non-var TypeSyntax and add Simplifier.Annotation
, and it will magically get converted to var if user prefers it.
See dotnet/roslyn-analyzers#6023 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooh snippets are calling CleanupDocumentAsync
:
roslyn/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs
Line 106 in a84cae0
var reformattedDocument = await CleanupDocumentAsync(formatAnnotatedSnippetDocument, cancellationToken).ConfigureAwait(false); |
So things I was saying specific to CodeAction
s might be applicable to snippets as well.
@akhera99 Can you try to unconditionally produce compilation.GetSpecialType(SpecialType.System_Int32).GenerateTypeSyntax(allowVar: true).WithAdditionalAnnotations(Simplifier.Annotation)
?
The CleanupDocumentAsync
call should be able to convert to var
if user prefers that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Youssef1313 yep you were correct, thanks :)
var x = 5; | ||
void LocalMethod() | ||
{ | ||
for (global::System.Int32 i = 0; (i) < (length); i++) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does it look so different when in a local function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@davidwengier Known bug #58135 😕
using Microsoft.CodeAnalysis.Snippets.SnippetProviders; | ||
using Microsoft.CodeAnalysis.Text; | ||
using Roslyn.Utilities; | ||
using static Humanizer.In; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nope
namespace Microsoft.CodeAnalysis.CSharp.Snippets | ||
{ | ||
[ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] | ||
internal class CSharpForLoopSnippetProvider : AbstractForLoopSnippetProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internal class CSharpForLoopSnippetProvider : AbstractForLoopSnippetProvider | |
internal sealed class CSharpForLoopSnippetProvider : AbstractForLoopSnippetProvider |
<data name="Insert_a_for_loop" xml:space="preserve"> | ||
<value>Insert a 'for' loop</value> | ||
</data> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will the same string be used for VB in future?
If not, consider moving to CSharpFeaturesResources
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point
for (int i = 0; i < length; i++) | ||
{$$ | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is much better if the cursor can be placed like this:
for (int i = 0; i < length; i++)
{
$$
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, but it is a question of if the LSP Snippet API should insert that indentation or if we should manually insert spaces for blocks when generating the syntax.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i feel like we shoudl be able to tell them: you should place the caret on this line at the appropriate indentation.
{ | ||
var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); | ||
var generator = SyntaxGenerator.GetGenerator(document); | ||
var options = await document.GetAnalyzerConfigOptionsAsync(cancellationToken).ConfigureAwait(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why only the analyzer config options? not just all the otpions? then you don't need the null check below either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CyrusNajmabadi I think we really don't have to look at options at all. Always generating compilation.GetSpecialType(SpecialType.System_Int32).GenerateTypeSyntax(allowVar: true).WithAdditionalAnnotations(Simplifier.Annotation)
should just work and respect the user option.
varBuiltInType = options.GetOption(CSharpCodeStyleOptions.VarForBuiltInTypes).Value; | ||
} | ||
|
||
var indexVariable = generator.Identifier("i"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we generate j/k/l etc. if we're already in a scope where that identifier is in use?
SyntaxFactory.VariableDeclarator( | ||
indexVariable, | ||
argumentList: null, | ||
SyntaxFactory.EqualsValueClause((ExpressionSyntax)generator.LiteralExpression(0))))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the declarator/list is duplicated. extract out and only do once. you only need to vary the TypeSyntax for var/int
var forLoopSyntax = SyntaxFactory.ForStatement(variableDeclarationSyntax, | ||
SyntaxFactory.SeparatedList<ExpressionSyntax>(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit.
var forLoopSyntax = SyntaxFactory.ForStatement(variableDeclarationSyntax, | |
SyntaxFactory.SeparatedList<ExpressionSyntax>(), | |
var forLoopSyntax = SyntaxFactory.ForStatement( | |
variableDeclarationSyntax, | |
SyntaxFactory.SeparatedList<ExpressionSyntax>(), |
generator.IdentifierName(indexVariable), | ||
// Using a temporary identifier name for now, could later be changed | ||
// to look for an iterable item in the scope of the insertion. | ||
generator.IdentifierName("length")), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be some sort of LSP marker that the user can then tab through?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what do you mean? It's a placeholder so the user can tab through and also rename?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess Cyrus means we should place this into the SnippetPlaceholder
?
protected override ImmutableArray<SnippetPlaceholder> GetForLoopSnippetPlaceholders(SyntaxNode node, ISyntaxFacts syntaxFacts) | ||
{ | ||
using var _ = ArrayBuilder<SnippetPlaceholder>.GetInstance(out var arrayBuilder); | ||
var placeHolderBuilder = new Dictionary<string, List<int>>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this just a MultiDictionary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess so, wasn't aware this existed
GetPartsOfForStatement(node, out var declaration, out var condition, out var incrementor, out var _1); | ||
|
||
// Retrieves the placeholder present in the variable declaration as well as its location. | ||
if (declaration != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when is teh decl, condition, or incrementor null? don't you always create all 3?
if (declaration != null) | ||
{ | ||
var variableDeclarator = ((VariableDeclarationSyntax)declaration).Variables.FirstOrDefault(); | ||
if (variableDeclarator != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when is this null? don't you always create it?
if (variableDeclarator != null) | ||
{ | ||
var declaratorIdentifier = variableDeclarator.Identifier; | ||
placeHolderBuilder.Add(declaratorIdentifier.ToString(), new List<int>() { declaratorIdentifier.SpanStart }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
placeHolderBuilder.Add(declaratorIdentifier.ToString(), new List<int>() { declaratorIdentifier.SpanStart }); | |
placeHolderBuilder.Add(declaratorIdentifier.ValueText, new List<int>() { declaratorIdentifier.SpanStart }); |
placeHolderBuilder.Add(operand.ToString(), incrementorList); | ||
} | ||
|
||
incrementorList.Add(operand.SpanStart); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MultiDictionary will make all of this a lot easier :)
foreach (var kvp in placeHolderBuilder) | ||
{ | ||
arrayBuilder.Add(new SnippetPlaceholder(kvp.Key, kvp.Value.ToImmutableArray())); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you need to sort this array in any special way? or does it not matter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't matter because when we construct the LSP string, we create a new dictionary that has a mapping of the position it should be inserted into the string to the identifier
} | ||
|
||
return arrayBuilder.ToImmutableArray(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{ | ||
GetPartsOfForStatement(caretTarget, out _, out _, out _, out var statement); | ||
return statement.SpanStart + 1; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looking great!
...SharpTest/Completion/CompletionProviders/Snippets/CSharpForSnippetCompletionProviderTests.cs
Show resolved
Hide resolved
var generator = SyntaxGenerator.GetGenerator(document); | ||
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); | ||
|
||
var iteratorName = NameGenerator.GenerateUniqueName( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to use this GenerateUniqueName to get a great name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps, but what would be the difference?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nop, I just mean if it is possible to reuse the code, if it doesn't fit your needs that's fine
// Creating the variable declaration based on if the user has | ||
// 'var for built in types' set in their editorconfig. | ||
var variableDeclarationSyntax = | ||
SyntaxFactory.VariableDeclaration(compilation.GetSpecialType(SpecialType.System_Int32).GenerateTypeSyntax(allowVar: false), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why allowVar
is false here? Is it possible to to link this to the editorconfig value here
https://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp.CodeStyle/CSharpCodeStyleOptions.cs,cb8ef73fcae9e59f,references
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why allowVar is false here?
Yeah it should be true.
Is it possible to to link this to the editorconfig value here
WithAdditionalAnnotations(Simplifier.Annotation)
is needed. That way the code cleanup will be responsible of respecting the user preference. This should happen here:
roslyn/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs
Line 108 in 35e54c1
var reformattedDocument = await CleanupDocumentAsync(formatAnnotatedSnippetDocument, cancellationToken).ConfigureAwait(false); |
(specifically the Simplifier.ReduceAsync
call)
|
||
// Retrieves the placeholder present in the variable declaration as well as its location. | ||
// The declaration is constructed so it can't be null. | ||
var variableDeclarator = ((VariableDeclarationSyntax)declaration!).Variables.FirstOrDefault(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
small nit: if it can't be null or empty because we own the code generation logic, then just call First()
or Single()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
src/Features/CSharp/Portable/Snippets/CSharpForLoopSnippetProvider.cs
Outdated
Show resolved
Hide resolved
I feel this looks good, once all the existing comments are addressed it should be good to go |
declaration = forStatement.Declaration; | ||
condition = forStatement.Condition; | ||
// We can assume there will only be one incrementor since it is only constructed with one. | ||
incrementor = forStatement.Incrementors.First(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
incrementor = forStatement.Incrementors.First(); | |
incrementor = forStatement.Incrementors.Single(); |
protected override async Task<Document> AddIndentationToDocumentAsync(Document document, int position, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) | ||
{ | ||
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); | ||
var snippet = root.GetAnnotatedNodes(_findSnippetAnnotation).FirstOrDefault(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var snippet = root.GetAnnotatedNodes(_findSnippetAnnotation).FirstOrDefault(); | |
var snippetNode = root.GetAnnotatedNodes(_findSnippetAnnotation).First(); |
|
||
// Retrieves the placeholder present in the variable declaration as well as its location. | ||
// The declaration is constructed so it can't be null. | ||
var variableDeclarator = ((VariableDeclarationSyntax)declaration!).Variables.FirstOrDefault(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@akhera99 Do you plan to continue working on this? If so, I have a little suggestion: since 'inline statement snippets' are a thing now (#67819) I think |
@DoctorKrolic feel free to implement the snippet! |
The next snippet in the long list of snippets to implement :)
This also checks for the option set for var for built in types in the editorconfig.