From 93743aa822c68627fd46e5fcac93576d9bdf2623 Mon Sep 17 00:00:00 2001 From: JCWasmx86 Date: Wed, 29 Jun 2022 13:43:01 +0200 Subject: [PATCH] Add more actions: - AddThrowsDeclaration to add an error to the declaration of a method/constructor - AddTryCatch around statements - AddTryCatch around variable declarations --- src/codeaction/addthrowsdeclaration.vala | 68 +++++++++++++++++++ .../addtrycatchassignmentaction.vala | 68 +++++++++++++++++++ .../addtrycatchstatementaction.vala | 61 +++++++++++++++++ src/codehelp/codeaction.vala | 53 ++++++++++++++- src/codehelp/nodesearch.vala | 4 +- src/meson.build | 3 + src/server.vala | 2 +- 7 files changed, 255 insertions(+), 4 deletions(-) create mode 100644 src/codeaction/addthrowsdeclaration.vala create mode 100644 src/codeaction/addtrycatchassignmentaction.vala create mode 100644 src/codeaction/addtrycatchstatementaction.vala diff --git a/src/codeaction/addthrowsdeclaration.vala b/src/codeaction/addthrowsdeclaration.vala new file mode 100644 index 00000000..f2a68746 --- /dev/null +++ b/src/codeaction/addthrowsdeclaration.vala @@ -0,0 +1,68 @@ +/* addthrowsdeclaration.vala + * + * Copyright 2022 JCWasmx86 + * + * This file is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +using Gee; +using Lsp; + +class Vls.AddThrowsDeclaration : CodeAction { + public AddThrowsDeclaration (VersionedTextDocumentIdentifier document, string error_name, Vala.CodeNode where) { + + var error_types = new Vala.HashSet (); + if (where is Vala.Constructor) + ((Vala.Constructor)where).body.get_error_types (error_types, null); + else + where.get_error_types (error_types, null); + + var sb = new StringBuilder (); + if (error_types.is_empty) { + sb.append (" throws "); + } else { + sb.append (", "); + } + sb.append (error_name); + sb.append (" "); + var sref = where.source_reference; + var range = new Range (); + for (var i = sref.begin.line; i <= sref.end.line; i++) { + var line = sref.file.get_source_line (i); + if (line.contains ("{")) { + var idx = line.index_of ("{"); + range.start = new Lsp.Position () { + line = i - 1, + character = idx - 1, + }; + range.end = new Lsp.Position () { + line = i - 1, + character = idx - 1, + }; + } + } + var workspace_edit = new WorkspaceEdit (); + var document_edit = new TextDocumentEdit (document); + var text_edit = new TextEdit (range); + text_edit.range.end.character++; + text_edit.newText = sb.str; + document_edit.edits.add (text_edit); + workspace_edit.documentChanges = new ArrayList (); + workspace_edit.documentChanges.add (document_edit); + this.edit = workspace_edit; + this.title = "Add to error list"; + } +} diff --git a/src/codeaction/addtrycatchassignmentaction.vala b/src/codeaction/addtrycatchassignmentaction.vala new file mode 100644 index 00000000..13007326 --- /dev/null +++ b/src/codeaction/addtrycatchassignmentaction.vala @@ -0,0 +1,68 @@ +/* AddTryCatchAssignmentAction.vala + * + * Copyright 2022 JCWasmx86 + * + * This file is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +using Gee; +using Lsp; + +class Vls.AddTryCatchAssignmentAction : CodeAction { + public AddTryCatchAssignmentAction (VersionedTextDocumentIdentifier document, Vala.ArrayList variables, string error_name, string indent, Vala.CodeNode rhs) { + var sref = rhs.source_reference; + var assignment_line = sref.file.get_source_line (sref.begin.line); + var copied_indent = assignment_line.substring (0, assignment_line.length - assignment_line.chug ().length); + var sb = new StringBuilder (); + foreach (var v in variables) { + sb.append (copied_indent).append (v.variable_type.to_string ()).append (" ").append (v.name).append (";\n"); + } + sb.append (copied_indent).append ("try {\n"); + sb.append (copied_indent).append (indent).append (variables[0].name).append (" = "); + var s1 = variables[0].initializer.source_reference; + for (var i = s1.begin.line; i <= s1.end.line; i++) { + var len = -1; + var offset = 0; + if (i == s1.begin.line && i != s1.end.line) { + offset = s1.begin.column - 1; + } else if (i == s1.end.line && i != s1.begin.line) { + len = s1.end.column; + } else if (i == s1.begin.line && i == s1.end.line) { + offset = s1.begin.column - 1; + len = s1.end.column - s1.begin.column + 1; + } + if (i != s1.begin.line) + sb.append (copied_indent).append (indent); + sb.append (s1.file.get_source_line (i).substring (offset, len).strip ()); + sb.append (i == s1.end.line ? ";" : "").append ("\n"); + } + // TODO: Deduplicate error name + sb.append (copied_indent).append ("} catch (").append (error_name).append (" e) {\n"); + sb.append (copied_indent).append (indent).append ("error (\"Caught error ").append (error_name).append (": %s\", e.message);\n"); + sb.append (copied_indent).append ("}\n"); + var workspace_edit = new WorkspaceEdit (); + var document_edit = new TextDocumentEdit (document); + var text_edit = new TextEdit (new Range.from_sourceref (sref)); + text_edit.range.start.character = 0; + text_edit.range.end.character++; + text_edit.newText = sb.str; + document_edit.edits.add (text_edit); + workspace_edit.documentChanges = new ArrayList (); + workspace_edit.documentChanges.add (document_edit); + this.edit = workspace_edit; + this.title = "Wrap with try-catch"; + } +} diff --git a/src/codeaction/addtrycatchstatementaction.vala b/src/codeaction/addtrycatchstatementaction.vala new file mode 100644 index 00000000..f94f00a4 --- /dev/null +++ b/src/codeaction/addtrycatchstatementaction.vala @@ -0,0 +1,61 @@ +/* addtrycatchstatementaction.vala + * + * Copyright 2022 JCWasmx86 + * + * This file is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +using Gee; +using Lsp; + +class Vls.AddTryCatchStatementAction : CodeAction { + public AddTryCatchStatementAction (VersionedTextDocumentIdentifier document, string error_name, string indent, Vala.CodeNode node) { + var sref = node.source_reference; + var sb = new StringBuilder (); + var line = sref.file.get_source_line (sref.begin.line); + var copied_indent = line.substring (0, line.length - line.chug ().length); + sb.append (copied_indent).append ("try {\n"); + for (var i = sref.begin.line; i <= sref.end.line; i++) { + var len = -1; + var offset = 0; + if (i == sref.begin.line && i != sref.end.line) { + offset = sref.begin.column - 1; + } else if (i == sref.end.line && i != sref.begin.line) { + len = sref.end.column; + } else if (i == sref.begin.line && i == sref.end.line) { + offset = sref.begin.column - 1; + len = sref.end.column - sref.begin.column + 1; + } + sb.append (copied_indent).append (indent); + sb.append (sref.file.get_source_line (i).substring (offset, len).strip ()); + sb.append (i == sref.end.line ? ";" : "").append ("\n"); + } + sb.append (copied_indent).append ("} catch (").append (error_name).append (" e) {\n"); + sb.append (copied_indent).append (indent).append ("error (\"Caught error ").append (error_name).append (": %s\", e.message);\n"); + sb.append (copied_indent).append ("}\n"); + var workspace_edit = new WorkspaceEdit (); + var document_edit = new TextDocumentEdit (document); + var text_edit = new TextEdit (new Range.from_sourceref (sref)); + text_edit.range.start.character = 0; + text_edit.range.end.character++; + text_edit.newText = sb.str; + document_edit.edits.add (text_edit); + workspace_edit.documentChanges = new ArrayList (); + workspace_edit.documentChanges.add (document_edit); + this.edit = workspace_edit; + this.title = "Wrap with try-catch"; + } +} diff --git a/src/codehelp/codeaction.vala b/src/codehelp/codeaction.vala index 4cb47f61..3a30713a 100644 --- a/src/codehelp/codeaction.vala +++ b/src/codehelp/codeaction.vala @@ -28,8 +28,9 @@ namespace Vls.CodeActions { * @param file the current document * @param range the range to show code actions for * @param uri the document URI + * @param diags the diagnostics to use */ - Collection extract (Compilation compilation, TextDocument file, Range range, string uri) { + Collection extract (Compilation compilation, TextDocument file, Range range, string uri, Gee.List diags) { // search for nodes containing the query range var finder = new NodeSearch (file, range.start, true, range.end); var code_actions = new ArrayList (); @@ -60,6 +61,56 @@ namespace Vls.CodeActions { } } + finder = new NodeSearch.with_filter (file, null, (a, b) => true, true); + var code_style = compilation.get_analysis_for_file (file); + foreach (var d in diags) { + if (d.message.has_prefix ("unhandled error ")) { + foreach (var node in finder.result) { + var tmp_range = new Range.from_sourceref (node.source_reference); + if ((tmp_range.contains (d.range.start) && tmp_range.contains (d.range.end)) + && !(node is Vala.Block)) { + var error_name = d.message.replace ("unhandled error `", "").replace ("'", "").strip (); + if (node is DeclarationStatement) { + var variables = new Vala.ArrayList (); + ((DeclarationStatement)node).get_defined_variables (variables); + if (variables.size == 1) + code_actions.add (new AddTryCatchAssignmentAction (document, variables, error_name, code_style.indentation, node)); + var parent = node.parent_node; + while (parent != null) { + if (parent is Vala.ForeachStatement || parent is Vala.LambdaExpression) { + parent = null; + break; + } + if (parent is Vala.Method || parent is Vala.Constructor) + break; + parent = parent.parent_node; + } + if (parent != null) { + code_actions.add (new AddThrowsDeclaration (document, error_name, parent)); + } + } else if (node is ExpressionStatement && !(node.parent_node is Vala.ForeachStatement)) { + var es = (ExpressionStatement) node; + code_actions.add (new AddTryCatchStatementAction (document, error_name, code_style.indentation, es.expression)); + } else { + var parent = node.parent_node; + while (parent != null) { + if (parent is Vala.ForeachStatement || parent is Vala.LambdaExpression) { + parent = null; + break; + } + if (parent is Vala.Method || parent is Vala.Constructor) + break; + parent = parent.parent_node; + } + if (parent != null) { + code_actions.add (new AddThrowsDeclaration (document, error_name, parent)); + } + } + } + } + } + } + return code_actions; } diff --git a/src/codehelp/nodesearch.vala b/src/codehelp/nodesearch.vala index f4eb65d5..e4c5fdd9 100644 --- a/src/codehelp/nodesearch.vala +++ b/src/codehelp/nodesearch.vala @@ -33,7 +33,7 @@ class Vls.NodeSearch : Vala.CodeVisitor { private Gee.HashSet seen = new Gee.HashSet (); [CCode (has_target = false)] - public delegate bool Filter (Vala.CodeNode needle, Vala.CodeNode hay_node); + public delegate bool Filter (Vala.CodeNode? needle, Vala.CodeNode hay_node); private Vala.CodeNode? needle; private Filter? filter; @@ -122,7 +122,7 @@ class Vls.NodeSearch : Vala.CodeVisitor { this.visit_source_file (file); } - public NodeSearch.with_filter (Vala.SourceFile file, Vala.CodeNode needle, Filter filter_func, + public NodeSearch.with_filter (Vala.SourceFile file, Vala.CodeNode? needle, Filter filter_func, bool include_declaration = true) { this.file = file; this.needle = needle; diff --git a/src/meson.build b/src/meson.build index 2ecb932b..748815d8 100644 --- a/src/meson.build +++ b/src/meson.build @@ -4,6 +4,9 @@ vls_src = files([ 'analysis/codestyleanalyzer.vala', 'analysis/inlayhintnodes.vala', 'analysis/symbolenumerator.vala', + 'codeaction/addtrycatchassignmentaction.vala', + 'codeaction/addtrycatchstatementaction.vala', + 'codeaction/addthrowsdeclaration.vala', 'codeaction/baseconverteraction.vala', 'codeaction/implementmissingprereqsaction.vala', 'codehelp/callhierarchy.vala', diff --git a/src/server.vala b/src/server.vala index 418817d6..349eacae 100644 --- a/src/server.vala +++ b/src/server.vala @@ -1664,7 +1664,7 @@ class Vls.Server : Jsonrpc.Server { var json_array = new Json.Array (); Vala.CodeContext.push (compilation.code_context); - var code_actions = CodeActions.extract (compilation, (TextDocument) source_file, p.range, Uri.unescape_string (p.textDocument.uri)); + var code_actions = CodeActions.extract (compilation, (TextDocument) source_file, p.range, Uri.unescape_string (p.textDocument.uri), p.context.diagnostics); foreach (var action in code_actions) json_array.add_element (Json.gobject_serialize (action)); Vala.CodeContext.pop ();