/// <summary> /// Returns a sequence of suggestions on how errors for ambiguous types and callable in the given diagnostics can be fixed, /// given the file for which those diagnostics were generated and the corresponding compilation. /// Returns an empty enumerable if any of the given arguments is null. /// </summary> internal static IEnumerable <(string, WorkspaceEdit)> SuggestionsForAmbiguousIdentifiers (this FileContentManager file, CompilationUnit compilation, IEnumerable <Diagnostic> diagnostics) { if (file == null || diagnostics == null) { return(Enumerable.Empty <(string, WorkspaceEdit)>()); } var ambiguousCallables = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.AmbiguousCallable)); var ambiguousTypes = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.AmbiguousType)); if (!ambiguousCallables.Any() && !ambiguousTypes.Any()) { return(Enumerable.Empty <(string, WorkspaceEdit)>()); } (string, WorkspaceEdit) SuggestedNameQualification(NonNullable <string> suggestedNS, string id, Position pos) { var edit = new TextEdit { Range = new LSP.Range { Start = pos, End = pos }, NewText = $"{suggestedNS.Value}." }; return($"{suggestedNS.Value}.{id}", file.GetWorkspaceEdit(edit)); } var suggestedIdQualifications = ambiguousCallables.Select(d => d.Range.Start) .SelectMany(pos => file.NamespaceSuggestionsForIdAtPosition(pos, compilation, out var id) .Select(ns => SuggestedNameQualification(ns, id, pos))); var suggestedTypeQualifications = ambiguousTypes.Select(d => d.Range.Start) .SelectMany(pos => file.NamespaceSuggestionsForTypeAtPosition(pos, compilation, out var id) .Select(ns => SuggestedNameQualification(ns, id, pos))); return(suggestedIdQualifications.Concat(suggestedTypeQualifications)); }
/// <summary> /// Returns workspace edits for correcting the casing or capitalization of unknown identifiers. /// </summary> private static IEnumerable <(string, WorkspaceEdit)> UnknownIdCaseSuggestions( this FileContentManager file, CompilationUnit compilation, IReadOnlyCollection <Diagnostic> diagnostics) { (string, WorkspaceEdit) SuggestedIdEdit(string suggestedId, Lsp.Range range) { var edit = new TextEdit { Range = range, NewText = suggestedId }; return($"Replace with \"{suggestedId}\".", file.GetWorkspaceEdit(edit)); } var idSuggestions = from diagnostic in diagnostics where DiagnosticTools.ErrorType(ErrorCode.UnknownIdentifier)(diagnostic) from id in file.IdCaseSuggestions(diagnostic.Range.Start.ToQSharp(), compilation) select SuggestedIdEdit(id, diagnostic.Range); var typeSuggestions = from diagnostic in diagnostics where DiagnosticTools.ErrorType(ErrorCode.UnknownType)(diagnostic) from type in file.TypeCaseSuggestions(diagnostic.Range.Start.ToQSharp(), compilation) select SuggestedIdEdit(type, diagnostic.Range); return(idSuggestions.Concat(typeSuggestions)); }
/// <summary> /// Returns workspace edits for opening namespaces of unknown identifiers. /// </summary> private static IEnumerable <(string, WorkspaceEdit)> UnknownIdNamespaceSuggestions( this FileContentManager file, CompilationUnit compilation, int lineNr, IReadOnlyCollection <Diagnostic> diagnostics) { var idSuggestions = diagnostics .Where(DiagnosticTools.ErrorType(ErrorCode.UnknownIdentifier)) .SelectMany(d => file.IdNamespaceSuggestions(d.Range.Start.ToQSharp(), compilation, out _)); var typeSuggestions = diagnostics .Where(DiagnosticTools.ErrorType(ErrorCode.UnknownType)) .SelectMany(d => file.TypeNamespaceSuggestions(d.Range.Start.ToQSharp(), compilation, out _)); return(file .OpenDirectiveSuggestions(lineNr, idSuggestions.Concat(typeSuggestions)) .Select(edit => (edit.NewText.Trim().Trim(';'), file.GetWorkspaceEdit(edit)))); }
/// <summary> /// Returns a sequence of suggestions on how errors for ambiguous types and callable in the given diagnostics can be fixed, /// given the file for which those diagnostics were generated and the corresponding compilation. /// Returns an empty enumerable if any of the given arguments is null. /// </summary> internal static IEnumerable <(string, WorkspaceEdit)> AmbiguousIdSuggestions( this FileContentManager file, CompilationUnit compilation, IEnumerable <Diagnostic> diagnostics) { if (file == null || diagnostics == null) { return(Enumerable.Empty <(string, WorkspaceEdit)>()); } var ambiguousCallables = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.AmbiguousCallable)); var ambiguousTypes = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.AmbiguousType)); if (!ambiguousCallables.Any() && !ambiguousTypes.Any()) { return(Enumerable.Empty <(string, WorkspaceEdit)>()); } (string, WorkspaceEdit)? SuggestedNameQualification(string suggestedNS, string?id, Position pos) { var edit = new TextEdit { Range = new Lsp.Range { Start = pos.ToLsp(), End = pos.ToLsp() }, NewText = $"{suggestedNS}." }; return(id is null ? null as (string, WorkspaceEdit)? : ($"{suggestedNS}.{id}", file.GetWorkspaceEdit(edit))); } var suggestedIdQualifications = ambiguousCallables .Select(d => d.Range.Start.ToQSharp()) .SelectMany(pos => file .IdNamespaceSuggestions(pos, compilation, out var id) .SelectNotNull(ns => SuggestedNameQualification(ns, id, pos))); var suggestedTypeQualifications = ambiguousTypes .Select(d => d.Range.Start.ToQSharp()) .SelectMany(pos => file .TypeNamespaceSuggestions(pos, compilation, out var id) .SelectNotNull(ns => SuggestedNameQualification(ns, id, pos))); return(suggestedIdQualifications.Concat(suggestedTypeQualifications)); }
/// <summary> /// Returns a sequence of suggestions on how errors for unknown types and callable in the given diagnostics can be fixed, /// given the file for which those diagnostics were generated and the corresponding compilation. /// The given line number is used to determine the containing namespace. /// Returns an empty enumerable if any of the given arguments is null. /// </summary> internal static IEnumerable <(string, WorkspaceEdit)> SuggestionsForUnknownIdentifiers (this FileContentManager file, CompilationUnit compilation, int lineNr, IEnumerable <Diagnostic> diagnostics) { if (file == null || diagnostics == null) { return(Enumerable.Empty <(string, WorkspaceEdit)>()); } var unknownCallables = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.UnknownIdentifier)); var unknownTypes = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.UnknownType)); if (!unknownCallables.Any() && !unknownTypes.Any()) { return(Enumerable.Empty <(string, WorkspaceEdit)>()); } var suggestionsForIds = unknownCallables.Select(d => d.Range.Start) .SelectMany(pos => file.NamespaceSuggestionsForIdAtPosition(pos, compilation, out var _)); var suggestionsForTypes = unknownTypes.Select(d => d.Range.Start) .SelectMany(pos => file.NamespaceSuggestionsForTypeAtPosition(pos, compilation, out var _)); return(file.OpenDirectiveSuggestions(lineNr, suggestionsForIds.Concat(suggestionsForTypes).ToArray()) .Select(edit => (edit.NewText.Trim().Trim(';'), file.GetWorkspaceEdit(edit)))); }
/// <summary> /// Returns a sequence of suggestions for update-and-reassign statements based on the generated diagnostics, /// and given the file for which those diagnostics were generated. /// Returns an empty enumerable if any of the given arguments is null. /// </summary> internal static IEnumerable <(string, WorkspaceEdit)> SuggestionsForUpdateAndReassignStatements (this FileContentManager file, IEnumerable <Diagnostic> diagnostics) { if (file == null || diagnostics == null) { return(Enumerable.Empty <(string, WorkspaceEdit)>()); } var updateOfArrayItemExprs = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.UpdateOfArrayItemExpr)); (string, WorkspaceEdit) SuggestedCopyAndUpdateExpr(CodeFragment fragment) { var exprInfo = Parsing.ProcessUpdateOfArrayItemExpr.Invoke(fragment.Text); // Skip if the statement did not match a pattern for which we can give a code action if (exprInfo == null || (exprInfo.Item1.Line == 1 && exprInfo.Item1.Column == 1)) { return("", null); } // Convert set <identifier>[<index>] = <rhs> to set <identifier> w/= <index> <- <rhs> var rhs = $"{exprInfo.Item3} {Keywords.qsCopyAndUpdateOp.cont} {exprInfo.Item4}"; var outputStr = $"{Keywords.qsValueUpdate.id} {exprInfo.Item2} {Keywords.qsCopyAndUpdateOp.op}= {rhs}"; var fragmentRange = fragment.GetRange(); var edit = new TextEdit { Range = fragmentRange.Copy(), NewText = outputStr }; return("Replace with an update-and-reassign statement.", file.GetWorkspaceEdit(edit)); } return(updateOfArrayItemExprs .Select(d => file?.TryGetFragmentAt(d.Range.Start, out var _, includeEnd: true)) .Where(frag => frag != null) .Select(frag => SuggestedCopyAndUpdateExpr(frag)) .Where(s => s.Item2 != null)); }
/// <summary> /// Returns a sequence of suggestions on how deprecated syntax can be updated based on the generated diagnostics, /// and given the file for which those diagnostics were generated. /// Returns an empty enumerable if any of the given arguments is null. /// </summary> internal static IEnumerable <(string, WorkspaceEdit)> SuggestionsForDeprecatedSyntax (this FileContentManager file, IEnumerable <Diagnostic> diagnostics) { if (file == null || diagnostics == null) { return(Enumerable.Empty <(string, WorkspaceEdit)>()); } var deprecatedUnitTypes = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedUnitType)); var deprecatedNOToperators = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedNOToperator)); var deprecatedANDoperators = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedANDoperator)); var deprecatedORoperators = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedORoperator)); var deprecatedOpCharacteristics = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedOpCharacteristics)); (string, WorkspaceEdit) ReplaceWith(string text, LSP.Range range) { bool NeedsWs(Char ch) => Char.IsLetterOrDigit(ch) || ch == '_'; if (range?.Start != null && range.End != null) { var beforeEdit = file.GetLine(range.Start.Line).Text.Substring(0, range.Start.Character); var afterEdit = file.GetLine(range.End.Line).Text.Substring(range.End.Character); if (beforeEdit.Any() && NeedsWs(beforeEdit.Last())) { text = $" {text}"; } if (afterEdit.Any() && NeedsWs(afterEdit.First())) { text = $"{text} "; } } var edit = new TextEdit { Range = range?.Copy(), NewText = text }; return($"Replace with \"{text.Trim()}\".", file.GetWorkspaceEdit(edit)); } // update deprecated keywords and operators var suggestionsForUnitType = deprecatedUnitTypes.Select(d => ReplaceWith(Keywords.qsUnit.id, d.Range)); var suggestionsForNOT = deprecatedNOToperators.Select(d => ReplaceWith(Keywords.qsNOTop.op, d.Range)); var suggestionsForAND = deprecatedANDoperators.Select(d => ReplaceWith(Keywords.qsANDop.op, d.Range)); var suggestionsForOR = deprecatedORoperators.Select(d => ReplaceWith(Keywords.qsORop.op, d.Range)); // update deprecated operation characteristics syntax var typeToQs = new ExpressionTypeToQs(new ExpressionToQs()); string CharacteristicsAnnotation(Characteristics c) { typeToQs.onCharacteristicsExpression(SymbolResolution.ResolveCharacteristics(c)); return($"{Keywords.qsCharacteristics.id} {typeToQs.Output}"); } var suggestionsForOpCharacteristics = deprecatedOpCharacteristics.SelectMany(d => { // TODO: TryGetQsSymbolInfo currently only returns information about the inner most leafs rather than all types etc. // Once it returns indeed all types in the fragment, the following code block should be replaced by the commented out code below. var fragment = file.TryGetFragmentAt(d.Range.Start, out var _); IEnumerable <Characteristics> GetCharacteristics(QsTuple <Tuple <QsSymbol, QsType> > argTuple) => SyntaxGenerator.ExtractItems(argTuple).SelectMany(item => item.Item2.ExtractCharacteristics()).Distinct(); var characteristicsInFragment = fragment?.Kind is QsFragmentKind.FunctionDeclaration function ? GetCharacteristics(function.Item2.Argument) : fragment?.Kind is QsFragmentKind.OperationDeclaration operation ? GetCharacteristics(operation.Item2.Argument) : fragment?.Kind is QsFragmentKind.TypeDefinition type ? GetCharacteristics(type.Item2) : Enumerable.Empty <Characteristics>(); //var symbolInfo = file.TryGetQsSymbolInfo(d.Range.Start, false, out var fragment); //var characteristicsInFragment = (symbolInfo?.UsedTypes ?? Enumerable.Empty<QsType>()) // .SelectMany(t => t.ExtractCharacteristics()).Distinct(); var fragmentStart = fragment?.GetRange()?.Start; return(characteristicsInFragment .Where(c => c.Range.IsValue && DiagnosticTools.GetAbsoluteRange(fragmentStart, c.Range.Item).Overlaps(d.Range)) .Select(c => ReplaceWith(CharacteristicsAnnotation(c), d.Range))); }); return(suggestionsForOpCharacteristics.ToArray() .Concat(suggestionsForUnitType) .Concat(suggestionsForNOT) .Concat(suggestionsForAND) .Concat(suggestionsForOR)); }