1 /* ide-gca-diagnostic-provider.c
2  *
3  * Copyright 2015-2019 Christian Hergert <christian@hergert.me>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * SPDX-License-Identifier: GPL-3.0-or-later
19  */
20 
21 #define G_LOG_DOMAIN "ide-gca-diagnostic-provider"
22 
23 #include <glib/gi18n.h>
24 
25 #include "gca-diagnostics.h"
26 #include "ide-gca-diagnostic-provider.h"
27 #include "ide-gca-service.h"
28 
29 #include "gca-structs.h"
30 
31 struct _IdeGcaDiagnosticProvider
32 {
33   IdeObject   parent_instance;
34   GHashTable *document_cache;
35 };
36 
37 typedef struct
38 {
39   IdeTask        *task; /* Integrity check backpointer */
40   IdeUnsavedFile *unsaved_file;
41   GFile          *file;
42   gchar          *language_id;
43 } DiagnoseState;
44 
45 static void diagnostic_provider_iface_init (IdeDiagnosticProviderInterface *iface);
46 
47 G_DEFINE_TYPE_EXTENDED (IdeGcaDiagnosticProvider, ide_gca_diagnostic_provider, IDE_TYPE_OBJECT, G_TYPE_FLAG_FINAL,
48                         G_IMPLEMENT_INTERFACE (IDE_TYPE_DIAGNOSTIC_PROVIDER,
49                                                diagnostic_provider_iface_init))
50 
51 static GSettings *gca_settings;
52 
53 static void
diagnose_state_free(gpointer data)54 diagnose_state_free (gpointer data)
55 {
56   DiagnoseState *state = data;
57 
58   if (state)
59     {
60       g_clear_object (&state->file);
61       g_free (state->language_id);
62       g_clear_pointer (&state->unsaved_file, ide_unsaved_file_unref);
63       g_slice_free (DiagnoseState, state);
64     }
65 }
66 
67 static IdeDiagnosticSeverity
get_severity(guint val)68 get_severity (guint val)
69 {
70   switch (val)
71     {
72     case GCA_SEVERITY_INFO:
73       return IDE_DIAGNOSTIC_NOTE;
74 
75     case GCA_SEVERITY_WARNING:
76       return IDE_DIAGNOSTIC_WARNING;
77 
78     case GCA_SEVERITY_DEPRECATED:
79       return IDE_DIAGNOSTIC_DEPRECATED;
80 
81     case GCA_SEVERITY_ERROR:
82       return IDE_DIAGNOSTIC_ERROR;
83 
84     case GCA_SEVERITY_FATAL:
85       return IDE_DIAGNOSTIC_FATAL;
86 
87     case GCA_SEVERITY_NONE:
88     default:
89       return IDE_DIAGNOSTIC_IGNORED;
90       break;
91     }
92 }
93 
94 static IdeDiagnostics *
variant_to_diagnostics(DiagnoseState * state,GVariant * variant)95 variant_to_diagnostics (DiagnoseState *state,
96                         GVariant *variant)
97 {
98 
99   g_autoptr(GPtrArray) ar = NULL;
100   GVariantIter iter;
101   GVariantIter *b;
102   GVariantIter *c;
103   gchar *d = NULL;
104   guint a;
105 
106   IDE_PROBE;
107 
108   g_assert (variant);
109 
110   ar = g_ptr_array_new_with_free_func (g_object_unref);
111 
112   g_variant_iter_init (&iter, variant);
113 
114   while (g_variant_iter_loop (&iter, "(ua((x(xx)(xx))s)a(x(xx)(xx))s)",
115                               &a, &b, &c, &d))
116     {
117       IdeDiagnosticSeverity severity;
118       IdeDiagnostic *diag;
119       gint64 x1, x2, x3, x4, x5;
120       gchar *e;
121 
122       severity = get_severity (a);
123 
124       while (g_variant_iter_next (b, "((x(xx)(xx))s)",
125                                   &x1, &x2, &x3, &x4, &x5, &e))
126         {
127           /*
128            * TODO: Add fixits back after we plumb them into IdeDiagnostic.
129            */
130 #if 0
131           GcaFixit fixit = {{ 0 }};
132 
133           fixit.range.file = x1;
134           fixit.range.begin.line = x2 - 1;
135           fixit.range.begin.column = x3 - 1;
136           fixit.range.end.line = x4 - 1;
137           fixit.range.end.column = x5 - 1;
138           fixit.value = g_strdup (e);
139 
140           g_array_append_val (diag.fixits, fixit);
141 #endif
142         }
143 
144       diag = ide_diagnostic_new (severity, d, NULL);
145 
146       while (g_variant_iter_next (c, "(x(xx)(xx))", &x1, &x2, &x3, &x4, &x5))
147         {
148           g_autoptr(IdeRange) range = NULL;
149           g_autoptr(IdeLocation) begin = NULL;
150           g_autoptr(IdeLocation) end = NULL;
151 
152           /*
153            * FIXME:
154            *
155            * Not always true, but we can cheat for now and claim it is within
156            * the file we just parsed.
157            */
158 
159           begin = ide_location_new (state->file, x2 - 1, x3 - 1);
160           end = ide_location_new (state->file, x4 - 1, x5 - 1);
161 
162           range = ide_range_new (begin, end);
163           ide_diagnostic_take_range (diag, g_steal_pointer (&range));
164         }
165 
166       g_ptr_array_add (ar, g_steal_pointer (&diag));
167     }
168 
169   return ide_diagnostics_new_from_array (ar);
170 }
171 
172 static void
diagnostics_cb(GObject * object,GAsyncResult * result,gpointer user_data)173 diagnostics_cb (GObject      *object,
174                 GAsyncResult *result,
175                 gpointer      user_data)
176 {
177   GcaDiagnostics *proxy = (GcaDiagnostics *)object;
178   g_autoptr(IdeTask) task = user_data;
179   g_autoptr(GVariant) var = NULL;
180   g_autoptr(GError) error = NULL;
181   IdeDiagnostics *diagnostics;
182   DiagnoseState *state;
183 
184   IDE_ENTRY;
185 
186   g_assert (IDE_IS_TASK (task));
187   g_assert (G_IS_ASYNC_RESULT (result));
188 
189   if (!gca_diagnostics_call_diagnostics_finish (proxy, &var, result, &error))
190     {
191       IDE_TRACE_MSG ("%s", error->message);
192       ide_task_return_error (task, g_steal_pointer (&error));
193       IDE_EXIT;
194     }
195 
196   state = ide_task_get_task_data (task);
197   g_assert (state->task == task);
198 
199   diagnostics = variant_to_diagnostics (state, var);
200 
201   ide_task_return_pointer (task, diagnostics, g_object_unref);
202 
203   IDE_EXIT;
204 }
205 
206 static void
get_diag_proxy_cb(GObject * object,GAsyncResult * result,gpointer user_data)207 get_diag_proxy_cb (GObject      *object,
208                    GAsyncResult *result,
209                    gpointer      user_data)
210 {
211   g_autoptr(IdeTask) task = user_data;
212   g_autoptr(GError) error = NULL;
213   IdeGcaDiagnosticProvider *self;
214   GcaDiagnostics *proxy;
215   const gchar *path;
216 
217   IDE_ENTRY;
218 
219   g_assert (IDE_IS_TASK (task));
220   g_assert (G_IS_ASYNC_RESULT (result));
221 
222   self = ide_task_get_source_object (task);
223 
224   proxy = gca_diagnostics_proxy_new_finish (result, &error);
225 
226   if (!proxy)
227     {
228       ide_task_return_error (task, g_steal_pointer (&error));
229       IDE_EXIT;
230     }
231 
232   path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (proxy));
233   g_hash_table_replace (self->document_cache, g_strdup (path), proxy);
234 
235   gca_diagnostics_call_diagnostics (proxy,
236                                     ide_task_get_cancellable (task),
237                                     diagnostics_cb,
238                                     g_object_ref (task));
239 
240   IDE_EXIT;
241 }
242 
243 static void
parse_cb(GObject * object,GAsyncResult * result,gpointer user_data)244 parse_cb (GObject      *object,
245           GAsyncResult *result,
246           gpointer      user_data)
247 {
248   GcaService *proxy = (GcaService *)object;
249   IdeGcaDiagnosticProvider *self;
250   g_autoptr(IdeTask) task = user_data;
251   g_autoptr(GError) error = NULL;
252   g_autofree gchar *document_path = NULL;
253   GcaDiagnostics *doc_proxy;
254   DiagnoseState *state;
255   gboolean ret;
256 
257   IDE_ENTRY;
258 
259   g_assert (GCA_IS_SERVICE (proxy));
260   g_assert (IDE_IS_TASK (task));
261 
262   self = ide_task_get_source_object (task);
263   state = ide_task_get_task_data (task);
264 
265   ret = gca_service_call_parse_finish (proxy, &document_path, result, &error);
266 
267   if (!ret)
268     {
269       if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))
270         {
271           ide_task_return_pointer (task,
272                                    ide_diagnostics_new (),
273                                    g_object_unref);
274         }
275       else
276         {
277           IDE_TRACE_MSG ("%s", error->message);
278           ide_task_return_error (task, g_steal_pointer (&error));
279         }
280 
281       IDE_EXIT;
282     }
283 
284   doc_proxy = g_hash_table_lookup (self->document_cache, document_path);
285 
286   if (!doc_proxy)
287     {
288       g_autofree gchar *well_known_name = NULL;
289       GDBusConnection *conn;
290 
291       well_known_name = g_strdup_printf ("org.gnome.CodeAssist.v1.%s",
292                                          state->language_id);
293       conn = g_dbus_proxy_get_connection (G_DBUS_PROXY (proxy));
294 
295       gca_diagnostics_proxy_new (conn,
296                                  G_DBUS_PROXY_FLAGS_NONE,
297                                  well_known_name,
298                                  document_path,
299                                  ide_task_get_cancellable (task),
300                                  get_diag_proxy_cb,
301                                  g_object_ref (task));
302       IDE_EXIT;
303     }
304 
305   gca_diagnostics_call_diagnostics (doc_proxy,
306                                     ide_task_get_cancellable (task),
307                                     diagnostics_cb,
308                                     g_object_ref (task));
309 
310   IDE_EXIT;
311 }
312 
313 static GVariant *
get_parse_options(void)314 get_parse_options (void)
315 {
316   if (G_UNLIKELY (gca_settings == NULL))
317     gca_settings = g_settings_new ("org.gnome.builder.gnome-code-assistance");
318 
319   if (g_settings_get_boolean (gca_settings, "enable-pylint"))
320     {
321       GVariantBuilder builder;
322 
323       g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
324       g_variant_builder_add (&builder, "{sv}", "pylint", g_variant_new_boolean (TRUE));
325       return g_variant_builder_end (&builder);
326     }
327 
328   return g_variant_new ("a{sv}", 0);
329 }
330 
331 static void
get_proxy_cb(GObject * object,GAsyncResult * result,gpointer user_data)332 get_proxy_cb (GObject      *object,
333               GAsyncResult *result,
334               gpointer      user_data)
335 {
336   g_autoptr(IdeTask) task = user_data;
337   g_autoptr(GVariant) options = NULL;
338   IdeGcaService *service = (IdeGcaService *)object;
339   g_autofree gchar *path = NULL;
340   g_autoptr(GError) error = NULL;
341   DiagnoseState *state;
342   const gchar *temp_path;
343   GcaService *proxy;
344   GVariant *cursor = NULL;
345 
346   IDE_ENTRY;
347 
348   g_assert (IDE_IS_TASK (task));
349   g_assert (IDE_IS_GCA_SERVICE (service));
350 
351   state = ide_task_get_task_data (task);
352   g_assert (state->task == task);
353 
354   proxy = ide_gca_service_get_proxy_finish (service, result, &error);
355 
356   if (!proxy)
357     {
358       ide_task_return_error (task, g_steal_pointer (&error));
359       IDE_GOTO (cleanup);
360     }
361 
362   temp_path = path = g_file_get_path (state->file);
363 
364   if (!path)
365     {
366       ide_task_return_new_error (task,
367                                  G_IO_ERROR,
368                                  G_IO_ERROR_NOT_SUPPORTED,
369                                  _("Code assistance requires a local file."));
370       IDE_GOTO (cleanup);
371     }
372 
373   if (state->unsaved_file)
374     {
375       if (!ide_unsaved_file_persist (state->unsaved_file,
376                                      ide_task_get_cancellable (task),
377                                      &error))
378         {
379           ide_task_return_error (task, g_steal_pointer (&error));
380           IDE_GOTO (cleanup);
381         }
382 
383       temp_path = ide_unsaved_file_get_temp_path (state->unsaved_file);
384     }
385 
386   /* TODO: Plumb support for cursors down to this level? */
387   cursor = g_variant_new ("(xx)", (gint64)0, (gint64)0);
388   options = g_variant_ref_sink (get_parse_options ());
389 
390   gca_service_call_parse (proxy,
391                           path,
392                           temp_path,
393                           cursor,
394                           options,
395                           ide_task_get_cancellable (task),
396                           parse_cb,
397                           g_object_ref (task));
398 
399 cleanup:
400   g_clear_object (&proxy);
401 
402   IDE_EXIT;
403 }
404 
405 static void
ide_gca_diagnostic_provider_diagnose_async(IdeDiagnosticProvider * provider,GFile * file,GBytes * contents,const gchar * language_id,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)406 ide_gca_diagnostic_provider_diagnose_async (IdeDiagnosticProvider *provider,
407                                             GFile                 *file,
408                                             GBytes                *contents,
409                                             const gchar           *language_id,
410                                             GCancellable          *cancellable,
411                                             GAsyncReadyCallback    callback,
412                                             gpointer               user_data)
413 {
414   IdeGcaDiagnosticProvider *self = (IdeGcaDiagnosticProvider *)provider;
415   g_autoptr(IdeTask) task = NULL;
416   IdeGcaService *service;
417   DiagnoseState *state;
418   IdeUnsavedFiles *files;
419   IdeContext *context;
420 
421   IDE_ENTRY;
422 
423   g_return_if_fail (IDE_IS_GCA_DIAGNOSTIC_PROVIDER (self));
424 
425   task = ide_task_new (self, cancellable, callback, user_data);
426   ide_task_set_source_tag (task, ide_gca_diagnostic_provider_diagnose_async);
427 
428   if (language_id == NULL)
429     {
430       ide_task_return_new_error (task,
431                                  G_IO_ERROR,
432                                  G_IO_ERROR_NOT_SUPPORTED,
433                                  "No language specified, code assistance not supported.");
434       IDE_EXIT;
435     }
436 
437   context = ide_object_get_context (IDE_OBJECT (provider));
438   service = ide_gca_service_from_context (context);
439   files = ide_unsaved_files_from_context (context);
440 
441   state = g_slice_new0 (DiagnoseState);
442   state->task = task;
443   state->language_id = g_strdup (language_id);
444   state->file = g_object_ref (file);
445   state->unsaved_file = ide_unsaved_files_get_unsaved_file (files, file);
446 
447   ide_task_set_task_data (task, state, diagnose_state_free);
448 
449   ide_gca_service_get_proxy_async (service,
450                                    language_id,
451                                    cancellable,
452                                    get_proxy_cb,
453                                    g_steal_pointer (&task));
454 
455   IDE_EXIT;
456 }
457 
458 static IdeDiagnostics *
ide_gca_diagnostic_provider_diagnose_finish(IdeDiagnosticProvider * self,GAsyncResult * result,GError ** error)459 ide_gca_diagnostic_provider_diagnose_finish (IdeDiagnosticProvider  *self,
460                                              GAsyncResult           *result,
461                                              GError                **error)
462 {
463   IdeTask *task = (IdeTask *)result;
464   IdeDiagnostics *ret;
465 
466   IDE_ENTRY;
467 
468   g_return_val_if_fail (IDE_IS_GCA_DIAGNOSTIC_PROVIDER (self), NULL);
469   g_return_val_if_fail (IDE_IS_TASK (task), NULL);
470 
471   ret = ide_task_propagate_pointer (task, error);
472 
473   IDE_RETURN (ret);
474 }
475 
476 static void
ide_gca_diagnostic_provider_finalize(GObject * object)477 ide_gca_diagnostic_provider_finalize (GObject *object)
478 {
479   IdeGcaDiagnosticProvider *self = (IdeGcaDiagnosticProvider *)object;
480 
481   g_clear_pointer (&self->document_cache, g_hash_table_unref);
482 
483   G_OBJECT_CLASS (ide_gca_diagnostic_provider_parent_class)->finalize (object);
484 }
485 
486 static void
diagnostic_provider_iface_init(IdeDiagnosticProviderInterface * iface)487 diagnostic_provider_iface_init (IdeDiagnosticProviderInterface *iface)
488 {
489   iface->diagnose_async = ide_gca_diagnostic_provider_diagnose_async;
490   iface->diagnose_finish = ide_gca_diagnostic_provider_diagnose_finish;
491 }
492 
493 static void
ide_gca_diagnostic_provider_class_init(IdeGcaDiagnosticProviderClass * klass)494 ide_gca_diagnostic_provider_class_init (IdeGcaDiagnosticProviderClass *klass)
495 {
496   GObjectClass *object_class = G_OBJECT_CLASS (klass);
497 
498   object_class->finalize = ide_gca_diagnostic_provider_finalize;
499 }
500 
501 static void
ide_gca_diagnostic_provider_init(IdeGcaDiagnosticProvider * self)502 ide_gca_diagnostic_provider_init (IdeGcaDiagnosticProvider *self)
503 {
504   self->document_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
505                                                 g_free, g_object_unref);
506 }
507