1 /* ide-diagnostic.c
2  *
3  * Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
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-diagnostic"
22 
23 #include "config.h"
24 
25 #include "ide-code-enums.h"
26 #include "ide-diagnostic.h"
27 #include "ide-location.h"
28 #include "ide-range.h"
29 #include "ide-text-edit.h"
30 
31 typedef struct
32 {
33   IdeDiagnosticSeverity  severity;
34   guint                  hash;
35   gchar                 *text;
36   IdeLocation           *location;
37   GPtrArray             *ranges;
38   GPtrArray             *fixits;
39 } IdeDiagnosticPrivate;
40 
41 enum {
42   PROP_0,
43   PROP_LOCATION,
44   PROP_SEVERITY,
45   PROP_TEXT,
46   PROP_DISPLAY_TEXT,
47   N_PROPS
48 };
49 
G_DEFINE_TYPE_WITH_PRIVATE(IdeDiagnostic,ide_diagnostic,IDE_TYPE_OBJECT)50 G_DEFINE_TYPE_WITH_PRIVATE (IdeDiagnostic, ide_diagnostic, IDE_TYPE_OBJECT)
51 
52 static GParamSpec *properties [N_PROPS];
53 
54 static void
55 ide_diagnostic_set_location (IdeDiagnostic *self,
56                              IdeLocation   *location)
57 {
58   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
59 
60   g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
61 
62   g_set_object (&priv->location, location);
63 }
64 
65 static void
ide_diagnostic_set_text(IdeDiagnostic * self,const gchar * text)66 ide_diagnostic_set_text (IdeDiagnostic *self,
67                          const gchar   *text)
68 {
69   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
70 
71   g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
72 
73   priv->text = g_strdup (text);
74 }
75 
76 static void
ide_diagnostic_set_severity(IdeDiagnostic * self,IdeDiagnosticSeverity severity)77 ide_diagnostic_set_severity (IdeDiagnostic         *self,
78                              IdeDiagnosticSeverity  severity)
79 {
80   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
81 
82   g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
83 
84   priv->severity = severity;
85 }
86 
87 static void
ide_diagnostic_finalize(GObject * object)88 ide_diagnostic_finalize (GObject *object)
89 {
90   IdeDiagnostic *self = (IdeDiagnostic *)object;
91   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
92 
93   g_clear_pointer (&priv->text, g_free);
94   g_clear_pointer (&priv->ranges, g_ptr_array_unref);
95   g_clear_pointer (&priv->fixits, g_ptr_array_unref);
96   g_clear_object (&priv->location);
97 
98   G_OBJECT_CLASS (ide_diagnostic_parent_class)->finalize (object);
99 }
100 
101 static void
ide_diagnostic_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)102 ide_diagnostic_get_property (GObject    *object,
103                              guint       prop_id,
104                              GValue     *value,
105                              GParamSpec *pspec)
106 {
107   IdeDiagnostic *self = IDE_DIAGNOSTIC (object);
108 
109   switch (prop_id)
110     {
111     case PROP_LOCATION:
112       g_value_set_object (value, ide_diagnostic_get_location (self));
113       break;
114 
115     case PROP_SEVERITY:
116       g_value_set_enum (value, ide_diagnostic_get_severity (self));
117       break;
118 
119     case PROP_DISPLAY_TEXT:
120       g_value_take_string (value, ide_diagnostic_get_text_for_display (self));
121       break;
122 
123     case PROP_TEXT:
124       g_value_set_string (value, ide_diagnostic_get_text (self));
125       break;
126 
127     default:
128       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
129     }
130 }
131 
132 static void
ide_diagnostic_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)133 ide_diagnostic_set_property (GObject      *object,
134                              guint         prop_id,
135                              const GValue *value,
136                              GParamSpec   *pspec)
137 {
138   IdeDiagnostic *self = IDE_DIAGNOSTIC (object);
139 
140   switch (prop_id)
141     {
142     case PROP_LOCATION:
143       ide_diagnostic_set_location (self, g_value_get_object (value));
144       break;
145 
146     case PROP_SEVERITY:
147       ide_diagnostic_set_severity (self, g_value_get_enum (value));
148       break;
149 
150     case PROP_TEXT:
151       ide_diagnostic_set_text (self, g_value_get_string (value));
152       break;
153 
154     default:
155       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
156     }
157 }
158 
159 static void
ide_diagnostic_class_init(IdeDiagnosticClass * klass)160 ide_diagnostic_class_init (IdeDiagnosticClass *klass)
161 {
162   GObjectClass *object_class = G_OBJECT_CLASS (klass);
163 
164   object_class->finalize = ide_diagnostic_finalize;
165   object_class->get_property = ide_diagnostic_get_property;
166   object_class->set_property = ide_diagnostic_set_property;
167 
168   properties [PROP_LOCATION] =
169     g_param_spec_object ("location",
170                          "Location",
171                          "The location of the diagnostic",
172                          IDE_TYPE_LOCATION,
173                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
174 
175   properties [PROP_SEVERITY] =
176     g_param_spec_enum ("severity",
177                        "Severity",
178                        "The severity of the diagnostic",
179                        IDE_TYPE_DIAGNOSTIC_SEVERITY,
180                        IDE_DIAGNOSTIC_IGNORED,
181                        (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
182 
183   properties [PROP_TEXT] =
184     g_param_spec_string ("text",
185                          "Text",
186                          "The text of the diagnostic",
187                          NULL,
188                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
189 
190   properties [PROP_DISPLAY_TEXT] =
191     g_param_spec_string ("display-text",
192                          "Display Text",
193                          "The text formatted for display",
194                          NULL,
195                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
196 
197   g_object_class_install_properties (object_class, N_PROPS, properties);
198 }
199 
200 static void
ide_diagnostic_init(IdeDiagnostic * self)201 ide_diagnostic_init (IdeDiagnostic *self)
202 {
203 }
204 
205 /**
206  * ide_diagnostic_get_location:
207  * @self: a #IdeDiagnostic
208  *
209  * Gets the location of the diagnostic.
210  *
211  * See also: ide_diagnostic_get_range().
212  *
213  * Returns: (transfer none) (nullable): an #IdeLocation or %NULL
214  *
215  * Since: 3.32
216  */
217 IdeLocation *
ide_diagnostic_get_location(IdeDiagnostic * self)218 ide_diagnostic_get_location (IdeDiagnostic *self)
219 {
220   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
221 
222   g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
223 
224   if (priv->location != NULL)
225     return priv->location;
226 
227   if (priv->ranges != NULL && priv->ranges->len > 0)
228     {
229       IdeRange *range = g_ptr_array_index (priv->ranges, 0);
230       return ide_range_get_begin (range);
231     }
232 
233   return NULL;
234 }
235 
236 /**
237  * ide_diagnostic_get_file:
238  * @self: a #IdeDiagnostic
239  *
240  * Gets the file containing the diagnostic, if any.
241  *
242  * Returns: (transfer none) (nullable): an #IdeLocation or %NULL
243  *
244  * Since: 3.32
245  */
246 GFile *
ide_diagnostic_get_file(IdeDiagnostic * self)247 ide_diagnostic_get_file (IdeDiagnostic *self)
248 {
249   IdeLocation *location;
250 
251   g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
252 
253   if ((location = ide_diagnostic_get_location (self)))
254     return ide_location_get_file (location);
255 
256   return NULL;
257 }
258 
259 /**
260  * ide_diagnostic_get_text_for_display:
261  * @self: an #IdeDiagnostic
262  *
263  * This creates a new string that is formatted using the diagnostics
264  * line number, column, severity, and message text in the format
265  * "line:column: severity: message".
266  *
267  * This can be convenient when wanting to quickly display a
268  * diagnostic such as in a tooltip.
269  *
270  * Returns: (transfer full): string containing the text formatted for
271  *   display.
272  *
273  * Since: 3.32
274  */
275 gchar *
ide_diagnostic_get_text_for_display(IdeDiagnostic * self)276 ide_diagnostic_get_text_for_display (IdeDiagnostic *self)
277 {
278   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
279   IdeLocation *location;
280   const gchar *severity;
281   guint line = 0;
282   guint column = 0;
283 
284   g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
285 
286   severity = ide_diagnostic_severity_to_string (priv->severity);
287   location = ide_diagnostic_get_location (self);
288 
289   if (location != NULL)
290     {
291       line = ide_location_get_line (location) + 1;
292       column = ide_location_get_line_offset (location) + 1;
293     }
294 
295   return g_strdup_printf ("%u:%u: %s: %s", line, column, severity, priv->text);
296 }
297 
298 /**
299  * ide_diagnostic_severity_to_string:
300  * @severity: a #IdeDiagnosticSeverity
301  *
302  * Returns a string suitable to represent the diagnsotic severity.
303  *
304  * Returns: a string
305  *
306  * Since: 3.32
307  */
308 const gchar *
ide_diagnostic_severity_to_string(IdeDiagnosticSeverity severity)309 ide_diagnostic_severity_to_string (IdeDiagnosticSeverity severity)
310 {
311   switch (severity)
312     {
313     case IDE_DIAGNOSTIC_IGNORED:
314       return "ignored";
315 
316     case IDE_DIAGNOSTIC_NOTE:
317       return "note";
318 
319     case IDE_DIAGNOSTIC_UNUSED:
320       return "unused";
321 
322     case IDE_DIAGNOSTIC_DEPRECATED:
323       return "deprecated";
324 
325     case IDE_DIAGNOSTIC_WARNING:
326       return "warning";
327 
328     case IDE_DIAGNOSTIC_ERROR:
329       return "error";
330 
331     case IDE_DIAGNOSTIC_FATAL:
332       return "fatal";
333 
334     default:
335       return "unknown";
336     }
337 }
338 
339 guint
ide_diagnostic_get_n_ranges(IdeDiagnostic * self)340 ide_diagnostic_get_n_ranges (IdeDiagnostic *self)
341 {
342   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
343 
344   g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), 0);
345 
346   return priv->ranges ? priv->ranges->len : 0;
347 }
348 
349 /**
350  * ide_diagnostic_get_range:
351  *
352  * Retrieves the range found at @index. It is a programming error to call this
353  * function with a value greater or equal to ide_diagnostic_get_n_ranges().
354  *
355  * Returns: (transfer none) (nullable): An #IdeRange
356  *
357  * Since: 3.32
358  */
359 IdeRange *
ide_diagnostic_get_range(IdeDiagnostic * self,guint index)360 ide_diagnostic_get_range (IdeDiagnostic *self,
361                           guint          index)
362 {
363   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
364 
365   g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
366 
367   if (priv->ranges)
368     {
369       if (index < priv->ranges->len)
370         return g_ptr_array_index (priv->ranges, index);
371     }
372 
373   return NULL;
374 }
375 
376 guint
ide_diagnostic_get_n_fixits(IdeDiagnostic * self)377 ide_diagnostic_get_n_fixits (IdeDiagnostic *self)
378 {
379   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
380 
381   g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), 0);
382 
383   return (priv->fixits != NULL) ? priv->fixits->len : 0;
384 }
385 
386 /**
387  * ide_diagnostic_get_fixit:
388  * @self: an #IdeDiagnostic.
389  * @index: The index of the fixit.
390  *
391  * Gets the fixit denoted by @index. This value should be less than the value
392  * returned from ide_diagnostic_get_n_fixits().
393  *
394  * Returns: (transfer none) (nullable): An #IdeTextEdit
395  *
396  * Since: 3.32
397  */
398 IdeTextEdit *
ide_diagnostic_get_fixit(IdeDiagnostic * self,guint index)399 ide_diagnostic_get_fixit (IdeDiagnostic *self,
400                           guint          index)
401 {
402   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
403 
404   g_return_val_if_fail (self, NULL);
405   g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
406   g_return_val_if_fail (priv->fixits, NULL);
407 
408   if (priv->fixits != NULL)
409     {
410       if (index < priv->fixits->len)
411         return g_ptr_array_index (priv->fixits, index);
412     }
413 
414   return NULL;
415 }
416 
417 gint
ide_diagnostic_compare(IdeDiagnostic * a,IdeDiagnostic * b)418 ide_diagnostic_compare (IdeDiagnostic *a,
419                         IdeDiagnostic *b)
420 {
421   IdeDiagnosticPrivate *priv_a = ide_diagnostic_get_instance_private (a);
422   IdeDiagnosticPrivate *priv_b = ide_diagnostic_get_instance_private (b);
423   gint ret;
424 
425   g_assert (IDE_IS_DIAGNOSTIC (a));
426   g_assert (IDE_IS_DIAGNOSTIC (b));
427 
428   /* Severity is 0..N where N is more important. So reverse comparator. */
429   if (0 != (ret = (gint)priv_b->severity - (gint)priv_a->severity))
430     return ret;
431 
432   if (priv_a->location && priv_b->location)
433     {
434       if (0 != (ret = ide_location_compare (priv_a->location, priv_b->location)))
435         return ret;
436     }
437 
438   return g_strcmp0 (priv_a->text, priv_b->text);
439 }
440 
441 const gchar *
ide_diagnostic_get_text(IdeDiagnostic * self)442 ide_diagnostic_get_text (IdeDiagnostic *self)
443 {
444   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
445 
446   g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
447 
448   return priv->text;
449 }
450 
451 IdeDiagnosticSeverity
ide_diagnostic_get_severity(IdeDiagnostic * self)452 ide_diagnostic_get_severity (IdeDiagnostic *self)
453 {
454   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
455 
456   g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), 0);
457 
458   return priv->severity;
459 }
460 
461 /**
462  * ide_diagnostic_add_range:
463  * @self: a #IdeDiagnostic
464  * @range: an #IdeRange
465  *
466  * Adds a source range to the diagnostic.
467  *
468  * Since: 3.32
469  */
470 void
ide_diagnostic_add_range(IdeDiagnostic * self,IdeRange * range)471 ide_diagnostic_add_range (IdeDiagnostic *self,
472                           IdeRange      *range)
473 {
474   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
475 
476   g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
477   g_return_if_fail (IDE_IS_RANGE (range));
478 
479   if (priv->ranges == NULL)
480     priv->ranges = g_ptr_array_new_with_free_func (g_object_unref);
481   g_ptr_array_add (priv->ranges, g_object_ref (range));
482 }
483 
484 /**
485  * ide_diagnostic_take_range:
486  * @self: a #IdeDiagnostic
487  * @range: (transfer full): an #IdeRange
488  *
489  * Adds a source range to the diagnostic, but does not increment the
490  * reference count of @range.
491  *
492  * Since: 3.32
493  */
494 void
ide_diagnostic_take_range(IdeDiagnostic * self,IdeRange * range)495 ide_diagnostic_take_range (IdeDiagnostic *self,
496                            IdeRange      *range)
497 {
498   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
499 
500   g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
501   g_return_if_fail (IDE_IS_RANGE (range));
502 
503   if (priv->ranges == NULL)
504     priv->ranges = g_ptr_array_new_with_free_func (g_object_unref);
505   g_ptr_array_add (priv->ranges, g_steal_pointer (&range));
506 }
507 
508 /**
509  * ide_diagnostic_add_fixit:
510  * @self: a #IdeDiagnostic
511  * @fixit: an #IdeTextEdit
512  *
513  * Adds a source fixit to the diagnostic.
514  *
515  * Since: 3.32
516  */
517 void
ide_diagnostic_add_fixit(IdeDiagnostic * self,IdeTextEdit * fixit)518 ide_diagnostic_add_fixit (IdeDiagnostic *self,
519                           IdeTextEdit   *fixit)
520 {
521   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
522 
523   g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
524   g_return_if_fail (IDE_IS_TEXT_EDIT (fixit));
525 
526   if (priv->fixits == NULL)
527     priv->fixits = g_ptr_array_new_with_free_func (g_object_unref);
528   g_ptr_array_add (priv->fixits, g_object_ref (fixit));
529 }
530 
531 /**
532  * ide_diagnostic_take_fixit:
533  * @self: a #IdeDiagnostic
534  * @fixit: (transfer full): an #IdeTextEdit
535  *
536  * Adds a source fixit to the diagnostic, but does not increment the
537  * reference count of @fixit.
538  *
539  * Since: 3.32
540  */
541 void
ide_diagnostic_take_fixit(IdeDiagnostic * self,IdeTextEdit * fixit)542 ide_diagnostic_take_fixit (IdeDiagnostic *self,
543                            IdeTextEdit   *fixit)
544 {
545   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
546 
547   g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
548   g_return_if_fail (IDE_IS_TEXT_EDIT (fixit));
549 
550   if (priv->fixits == NULL)
551     priv->fixits = g_ptr_array_new_with_free_func (g_object_unref);
552   g_ptr_array_add (priv->fixits, g_steal_pointer (&fixit));
553 }
554 
555 IdeDiagnostic *
ide_diagnostic_new(IdeDiagnosticSeverity severity,const gchar * message,IdeLocation * location)556 ide_diagnostic_new (IdeDiagnosticSeverity  severity,
557                     const gchar           *message,
558                     IdeLocation           *location)
559 {
560   return g_object_new (IDE_TYPE_DIAGNOSTIC,
561                        "severity", severity,
562                        "location", location,
563                        "text", message,
564                        NULL);
565 }
566 
567 guint
ide_diagnostic_hash(IdeDiagnostic * self)568 ide_diagnostic_hash (IdeDiagnostic *self)
569 {
570   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
571 
572   g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), 0);
573 
574   if (priv->hash == 0)
575     {
576       guint hash = g_str_hash (priv->text ?: "");
577       if (priv->location)
578         hash ^= ide_location_hash (priv->location);
579       if (priv->fixits)
580         hash ^= g_int_hash (&priv->fixits->len);
581       if (priv->ranges)
582         hash ^= g_int_hash (&priv->ranges->len);
583       priv->hash = hash;
584     }
585 
586   return priv->hash;
587 }
588 
589 /**
590  * ide_diagnostic_to_variant:
591  * @self: a #IdeDiagnostic
592  *
593  * Creates a #GVariant to represent the diagnostic. This can be useful when
594  * working in subprocesses to serialize the diagnostic.
595  *
596  * This function will never return a floating variant.
597  *
598  * Returns: (transfer full): a #GVariant
599  *
600  * Since: 3.32
601  */
602 GVariant *
ide_diagnostic_to_variant(IdeDiagnostic * self)603 ide_diagnostic_to_variant (IdeDiagnostic *self)
604 {
605   IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
606   GVariantDict dict;
607 
608   g_return_val_if_fail (self != NULL, NULL);
609   g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
610 
611   g_variant_dict_init (&dict, NULL);
612 
613   g_variant_dict_insert (&dict, "text", "s", priv->text ?: "");
614   g_variant_dict_insert (&dict, "severity", "u", priv->severity);
615 
616   if (priv->location != NULL)
617     {
618       g_autoptr(GVariant) vloc = ide_location_to_variant (priv->location);
619 
620       if (vloc != NULL)
621         g_variant_dict_insert_value (&dict, "location", vloc);
622     }
623 
624   if (priv->ranges != NULL && priv->ranges->len > 0)
625     {
626       GVariantBuilder builder;
627 
628       g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
629 
630       for (guint i = 0; i < priv->ranges->len; i++)
631         {
632           IdeRange *range = g_ptr_array_index (priv->ranges, i);
633           g_autoptr(GVariant) vrange = ide_range_to_variant (range);
634 
635           g_variant_builder_add_value (&builder, vrange);
636         }
637 
638       g_variant_dict_insert_value (&dict, "ranges", g_variant_builder_end (&builder));
639     }
640 
641   if (priv->fixits != NULL && priv->fixits->len > 0)
642     {
643       GVariantBuilder builder;
644 
645       g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
646 
647       for (guint i = 0; i < priv->fixits->len; i++)
648         {
649           IdeTextEdit *fixit = g_ptr_array_index (priv->fixits, i);
650           g_autoptr(GVariant) vfixit = ide_text_edit_to_variant (fixit);
651 
652           g_variant_builder_add_value (&builder, vfixit);
653         }
654 
655       g_variant_dict_insert_value (&dict, "fixits", g_variant_builder_end (&builder));
656     }
657 
658   return g_variant_take_ref (g_variant_dict_end (&dict));
659 }
660 
661 /**
662  * ide_diagnostic_new_from_variant:
663  * @variant: (nullable): a #GVariant or %NULL
664  *
665  * Creates a new #GVariant using the data contained in @variant.
666  *
667  * If @variant is %NULL or Upon failure, %NULL is returned.
668  *
669  * Returns: (nullable) (transfer full): a #IdeDiagnostic or %NULL
670  *
671  * Since: 3.32
672  */
673 IdeDiagnostic *
ide_diagnostic_new_from_variant(GVariant * variant)674 ide_diagnostic_new_from_variant (GVariant *variant)
675 {
676   g_autoptr(IdeLocation) loc = NULL;
677   g_autoptr(GVariant) vloc = NULL;
678   g_autoptr(GVariant) unboxed = NULL;
679   g_autoptr(GVariant) ranges = NULL;
680   g_autoptr(GVariant) fixits = NULL;
681   IdeDiagnostic *self;
682   GVariantDict dict;
683   GVariantIter iter;
684   const gchar *text;
685   guint32 severity;
686 
687   if (variant == NULL)
688     return NULL;
689 
690   if (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARIANT))
691     variant = unboxed = g_variant_get_variant (variant);
692 
693   if (!g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT))
694     return NULL;
695 
696   g_variant_dict_init (&dict, variant);
697 
698   if (!g_variant_dict_lookup (&dict, "text", "&s", &text))
699     text = NULL;
700 
701   if (!g_variant_dict_lookup (&dict, "severity", "u", &severity))
702     severity = 0;
703 
704   if ((vloc = g_variant_dict_lookup_value (&dict, "location", NULL)))
705     loc = ide_location_new_from_variant (vloc);
706 
707   if (!(self = ide_diagnostic_new (severity, text, loc)))
708     goto failure;
709 
710   /* Ranges */
711   if ((ranges = g_variant_dict_lookup_value (&dict, "ranges", NULL)))
712     {
713       GVariant *vrange;
714 
715       g_variant_iter_init (&iter, ranges);
716 
717       while ((vrange = g_variant_iter_next_value (&iter)))
718         {
719           IdeRange *range;
720 
721           if ((range = ide_range_new_from_variant (vrange)))
722             ide_diagnostic_take_range (self, g_steal_pointer (&range));
723 
724           g_variant_unref (vrange);
725         }
726     }
727 
728   /* Fixits */
729   if ((fixits = g_variant_dict_lookup_value (&dict, "fixits", NULL)))
730     {
731       GVariant *vfixit;
732 
733       g_variant_iter_init (&iter, fixits);
734 
735       while ((vfixit = g_variant_iter_next_value (&iter)))
736         {
737           IdeTextEdit *fixit;
738 
739           if ((fixit = ide_text_edit_new_from_variant (vfixit)))
740             ide_diagnostic_take_fixit (self, g_steal_pointer (&fixit));
741 
742           g_variant_unref (vfixit);
743         }
744     }
745 
746 failure:
747 
748   g_variant_dict_clear (&dict);
749 
750   return self;
751 }
752