private async Task CreateSpellCheckCodeIssueAsync( CodeFixContext context, SyntaxToken nameToken, bool isGeneric, CancellationToken cancellationToken) { var document = context.Document; var service = CompletionService.GetService(document); // Disable snippets and unimported types from ever appearing in the completion items. // - It's very unlikely the user would ever misspell a snippet, then use spell-checking to fix it, // then try to invoke the snippet. // - We believe spell-check should only compare what you have typed to what symbol would be offered here. var originalOptions = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); var options = originalOptions .WithChangedOption(CompletionOptions.SnippetsBehavior, document.Project.Language, SnippetsRule.NeverInclude) .WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, document.Project.Language, false); var completionList = await service.GetCompletionsAsync( document, nameToken.SpanStart, options : options, cancellationToken : cancellationToken).ConfigureAwait(false); if (completionList == null) { return; } var nameText = nameToken.ValueText; var similarityChecker = WordSimilarityChecker.Allocate(nameText, substringsAreSimilar: true); try { await CheckItemsAsync( context, nameToken, isGeneric, completionList, similarityChecker).ConfigureAwait(false); } finally { similarityChecker.Free(); } }
public async Task <ImmutableArray <SymbolResult <ISymbol> > > FindDeclarationsAsync( string name, TSimpleNameSyntax nameNode, SymbolFilter filter) { if (name != null && string.IsNullOrWhiteSpace(name)) { return(ImmutableArray <SymbolResult <ISymbol> > .Empty); } using (var query = this.Exact ? SearchQuery.Create(name, ignoreCase: true) : SearchQuery.CreateFuzzy(name)) { var symbols = await FindDeclarationsAsync(name, filter, query).ConfigureAwait(false); if (Exact) { // We did an exact, case insensitive, search. Case sensitive matches should // be preferred though over insensitive ones. return(symbols.SelectAsArray(s => SymbolResult.Create(s.Name, nameNode, s, weight: s.Name == name ? 0 : 1))); } // TODO(cyrusn): It's a shame we have to compute this twice. However, there's no // great way to store the original value we compute because it happens deep in the // compiler bowels when we call FindDeclarations. var similarityChecker = WordSimilarityChecker.Allocate(name, substringsAreSimilar: false); var result = symbols.SelectAsArray(s => { var areSimilar = similarityChecker.AreSimilar(s.Name, out var matchCost); Debug.Assert(areSimilar); return(SymbolResult.Create(s.Name, nameNode, s, matchCost)); }); similarityChecker.Free(); return(result); } }
private async Task CreateSpellCheckCodeIssueAsync(CodeFixContext context, TSimpleName nameNode, string nameText, CancellationToken cancellationToken) { var document = context.Document; var service = CompletionService.GetService(document); // Disable snippets from ever appearing in the completion items. It's // very unlikely the user would ever mispell a snippet, then use spell- // checking to fix it, then try to invoke the snippet. var originalOptions = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); var options = originalOptions.WithChangedOption(CompletionOptions.SnippetsBehavior, document.Project.Language, SnippetsRule.NeverInclude); var completionList = await service.GetCompletionsAsync( document, nameNode.SpanStart, options : options, cancellationToken : cancellationToken).ConfigureAwait(false); if (completionList == null) { return; } var onlyConsiderGenerics = IsGeneric(nameNode); var results = new MultiDictionary <double, string>(); using (var similarityChecker = new WordSimilarityChecker(nameText, substringsAreSimilar: true)) { foreach (var item in completionList.Items) { if (onlyConsiderGenerics && !IsGeneric(item)) { continue; } var candidateText = item.FilterText; if (!similarityChecker.AreSimilar(candidateText, out var matchCost)) { continue; } var insertionText = await GetInsertionTextAsync(document, item, cancellationToken : cancellationToken).ConfigureAwait(false); results.Add(matchCost, insertionText); } } var codeActions = results.OrderBy(kvp => kvp.Key) .SelectMany(kvp => kvp.Value.Order()) .Where(t => t != nameText) .Take(3) .Select(n => CreateCodeAction(nameNode, nameText, n, document)) .ToImmutableArrayOrEmpty <CodeAction>(); if (codeActions.Length > 1) { // Wrap the spell checking actions into a single top level suggestion // so as to not clutter the list. context.RegisterCodeFix(new MyCodeAction( String.Format(FeaturesResources.Spell_check_0, nameText), codeActions), context.Diagnostics); } else { context.RegisterFixes(codeActions, context.Diagnostics); } }
private async Task CheckItemsAsync( CodeFixContext context, SyntaxToken nameToken, bool isGeneric, CompletionList completionList, WordSimilarityChecker similarityChecker ) { var document = context.Document; var cancellationToken = context.CancellationToken; var onlyConsiderGenerics = isGeneric; var results = new MultiDictionary <double, string>(); foreach (var item in completionList.Items) { if (onlyConsiderGenerics && !IsGeneric(item)) { continue; } var candidateText = item.FilterText; if (!similarityChecker.AreSimilar(candidateText, out var matchCost)) { continue; } var insertionText = await GetInsertionTextAsync( document, item, completionList.Span, cancellationToken : cancellationToken ) .ConfigureAwait(false); results.Add(matchCost, insertionText); } var nameText = nameToken.ValueText; var codeActions = results .OrderBy(kvp => kvp.Key) .SelectMany(kvp => kvp.Value.Order()) .Where(t => t != nameText) .Take(3) .Select(n => CreateCodeAction(nameToken, nameText, n, document)) .ToImmutableArrayOrEmpty <CodeAction>(); if (codeActions.Length > 1) { // Wrap the spell checking actions into a single top level suggestion // so as to not clutter the list. context.RegisterCodeFix( new MyCodeAction( string.Format(FeaturesResources.Fix_typo_0, nameText), codeActions ), context.Diagnostics ); } else { context.RegisterFixes(codeActions, context.Diagnostics); } }