[gobject-introspection] giscanner: refactor annotation validation



commit 35278b955304eb62565b5578b6848494721847bd
Author: Dieter Verfaillie <dieterv optionexplicit be>
Date:   Wed Aug 21 12:16:30 2013 +0200

    giscanner: refactor annotation validation
    
    - annotations on the identifier (formerly g-i specific tags) have
      never been validated before, so fix this
    - removes duplicate validation code from GtkDocTag and GtkDocParameter
    - remove repeated validation code doing the same thing as
      annotationparser from maintransformer...

 giscanner/annotationparser.py                     |  649 ++++++++++++---------
 giscanner/maintransformer.py                      |   17 +-
 tests/scanner/annotationparser/gi/tag_returns.xml |   32 +
 tests/warn/callback-invalid-scope.h               |    7 +-
 tests/warn/invalid-array.h                        |   12 +-
 tests/warn/invalid-closure.h                      |    2 +-
 tests/warn/invalid-element-type.h                 |   47 +-
 tests/warn/invalid-out.h                          |    2 +-
 tests/warn/invalid-transfer.h                     |    6 +-
 9 files changed, 462 insertions(+), 312 deletions(-)
---
diff --git a/giscanner/annotationparser.py b/giscanner/annotationparser.py
index ce1f723..3c377c7 100644
--- a/giscanner/annotationparser.py
+++ b/giscanner/annotationparser.py
@@ -109,6 +109,8 @@ Refer to the `GTK-Doc manual`_ for more detailed usage information.
 import os
 import re
 
+from operator import ne, gt, lt
+
 from .collections import OrderedDict
 from .message import Position, warn, error
 
@@ -248,21 +250,37 @@ OPT_ARRAY_FIXED_SIZE = 'fixed-size'
 OPT_ARRAY_LENGTH = 'length'
 OPT_ARRAY_ZERO_TERMINATED = 'zero-terminated'
 
+ARRAY_OPTIONS = [OPT_ARRAY_FIXED_SIZE,
+                 OPT_ARRAY_LENGTH,
+                 OPT_ARRAY_ZERO_TERMINATED]
+
 # (out) annotation options
 OPT_OUT_CALLEE_ALLOCATES = 'callee-allocates'
 OPT_OUT_CALLER_ALLOCATES = 'caller-allocates'
 
+OUT_OPTIONS = [OPT_OUT_CALLEE_ALLOCATES,
+               OPT_OUT_CALLER_ALLOCATES]
+
 # (scope) annotation options
 OPT_SCOPE_ASYNC = 'async'
 OPT_SCOPE_CALL = 'call'
 OPT_SCOPE_NOTIFIED = 'notified'
 
+SCOPE_OPTIONS = [OPT_SCOPE_ASYNC,
+                 OPT_SCOPE_CALL,
+                 OPT_SCOPE_NOTIFIED]
+
 # (transfer) annotation options
 OPT_TRANSFER_CONTAINER = 'container'
 OPT_TRANSFER_FLOATING = 'floating'
 OPT_TRANSFER_FULL = 'full'
 OPT_TRANSFER_NONE = 'none'
 
+TRANSFER_OPTIONS = [OPT_TRANSFER_CONTAINER,
+                    OPT_TRANSFER_FLOATING,
+                    OPT_TRANSFER_FULL,
+                    OPT_TRANSFER_NONE]
+
 
 # Program matching the start of a comment block.
 #
@@ -490,53 +508,125 @@ class GtkDocAnnotations(OrderedDict):
         self.position = position
 
 
-class GtkDocParameter(object):
+class GtkDocAnnotatable(object):
     '''
-    Represents a GTK-Doc parameter part.
+    Base class for GTK-Doc comment block parts that can be annotated.
     '''
 
-    __slots__ = ('name', 'annotations', 'description', 'position')
+    __slots__ = ('position', 'annotations')
 
-    def __init__(self, name, position=None):
-        #: Parameter name.
-        self.name = name
+    #: A :class:`tuple` of annotation name constants that are valid for this object. Annotation
+    #: names not in this :class:`tuple` will be reported as *unknown* by :func:`validate`. The
+    #: :attr:`valid_annotations` class attribute should be overridden by subclasses.
+    valid_annotations = ()
 
-        #: Parameter description or :const:`None`.
-        self.description = None
+    def __init__(self, position=None):
+        #: A :class:`giscanner.message.Position` instance specifying the location of the
+        #: annotatable comment block part in the source file or :const:`None`.
+        self.position = position
 
+        #: A :class:`GtkDocAnnotations` instance representing the annotations
+        #: applied to this :class:`GtkDocAnnotatable` instance.
         self.annotations = GtkDocAnnotations()
-        self.position = position
 
     def __repr__(self):
-        return '<GtkDocParameter %r %r>' % (self.name, self.annotations)
+        return '<GtkDocAnnotatable %r %r>' % (self.annotations, )
 
-    def _validate_annotation(self, ann_name, options, required=False,
-                             n_params=None, choices=None):
-        if required and len(options) == 0:
-            warn('%s annotation needs a value' % (ann_name, ), self.position)
-            return
+    def validate(self):
+        '''
+        Validate annotations stored by the :class:`GtkDocAnnotatable` instance, if any.
+        '''
 
-        if n_params is not None:
-            if n_params == 0:
-                s = 'no value'
-            elif n_params == 1:
-                s = 'one value'
-            else:
-                s = '%d values' % (n_params, )
-            if len(options) != n_params:
-                length = len(options)
-                warn('%s annotation needs %s, not %d' % (ann_name, s, length),
-                     self.position)
-                return
+        if self.annotations:
+            for ann_name, options in self.annotations.items():
+                if ann_name in self.valid_annotations:
+                    validate = getattr(self, '_do_validate_' + ann_name.replace('-', '_'))
+                    validate(ann_name, options)
+                elif ann_name in ALL_ANNOTATIONS:
+                    # Not error() as ann_name might be valid in some newer
+                    # GObject-Instrospection version.
+                    warn('unexpected annotation: %s' % (ann_name, ), self.position)
+                else:
+                    # Not error() as ann_name might be valid in some newer
+                    # GObject-Instrospection version.
+                    warn('unknown annotation: %s' % (ann_name, ), self.position)
+
+    def _validate_options(self, ann_name, n_options, expected_n_options, operator, message):
+        '''
+        Validate the number of options held by an annotation according to the test
+        ``operator(n_options, expected_n_options)``.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param n_options: number of options held by the annotation
+        :param expected_n_options: number of expected options
+        :param operator: an operator function from python's :mod:`operator` module, for example
+                         :func:`operator.ne` or :func:`operator.lt`
+        :param message: warning message used when the test
+                        ``operator(n_options, expected_n_options)`` fails.
+        '''
+
+        if n_options == 0:
+            t = 'none'
+        else:
+            t = '%d' % (n_options, )
+
+        if expected_n_options == 0:
+            s = 'no options'
+        elif expected_n_options == 1:
+            s = 'one option'
+        else:
+            s = '%d options' % (expected_n_options, )
+
+        if operator(n_options, expected_n_options):
+            warn('"%s" annotation %s %s, %s given' % (ann_name, message, s, t), self.position)
+
+    def _validate_annotation(self, ann_name, options, choices=None,
+                             exact_n_options=None, min_n_options=None, max_n_options=None):
+        '''
+        Validate an annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to be validated
+        :param choices: an iterable of allowed option names or :const:`None` to skip this test
+        :param exact_n_options: exact number of expected options or :const:`None` to skip this test
+        :param min_n_options: minimum number of expected options or :const:`None` to skip this test
+        :param max_n_options: maximum number of expected options or :const:`None` to skip this test
+        '''
+
+        n_options = len(options)
+
+        if exact_n_options is not None:
+            self._validate_options(ann_name, n_options, exact_n_options, ne, 'needs')
+
+        if min_n_options is not None:
+            self._validate_options(ann_name, n_options, min_n_options, lt, 'takes at least')
 
-        if choices is not None:
+        if max_n_options is not None:
+            self._validate_options(ann_name, n_options, max_n_options, gt, 'takes at most')
+
+        if options and choices is not None:
             option = options[0]
             if option not in choices:
-                warn('invalid %s annotation value: %r' % (ann_name, option, ),
-                     self.position)
-                return
+                warn('invalid "%s" annotation option: "%s"' % (ann_name, option), self.position)
+
+    def _do_validate_allow_none(self, ann_name, options):
+        '''
+        Validate the ``(allow-none)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options held by the annotation
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=0)
+
+    def _do_validate_array(self, ann_name, options):
+        '''
+        Validate the ``(array)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options held by the annotation
+        '''
 
-    def _validate_array(self, ann_name, options):
         if len(options) == 0:
             return
 
@@ -546,49 +636,254 @@ class GtkDocParameter(object):
                     int(value)
                 except (TypeError, ValueError):
                     if value is None:
-                        warn('array option %s needs a value' % (option, ),
-                             positions=self.position)
+                        warn('"%s" annotation option "%s" needs a value' % (ann_name, option),
+                             self.position)
                     else:
-                        warn('invalid array %s option value %r, '
-                             'must be an integer' % (option, value, ),
-                             positions=self.position)
+                        warn('invalid "%s" annotation option "%s" value "%s", must be an integer' %
+                             (ann_name, option, value),
+                             self.position)
             elif option == OPT_ARRAY_LENGTH:
                 if value is None:
-                    warn('array option length needs a value',
-                         positions=self.position)
+                    warn('"%s" annotation option "length" needs a value' % (ann_name, ),
+                         self.position)
             else:
-                warn('invalid array annotation value: %r' % (option, ),
+                warn('invalid "%s" annotation option: "%s"' % (ann_name, option),
                      self.position)
 
-    def _validate_closure(self, ann_name, options):
-        if len(options) != 0 and len(options) > 1:
-            warn('closure takes at most 1 value, %d given' % (len(options), ),
-                 self.position)
+    def _do_validate_attributes(self, ann_name, options):
+        '''
+        Validate the ``(attributes)`` annotation.
 
-    def _validate_element_type(self, ann_name, options):
-        self._validate_annotation(ann_name, options, required=True)
-        if len(options) == 0:
-            warn('element-type takes at least one value, none given',
-                 self.position)
-            return
-        if len(options) > 2:
-            warn('element-type takes at most 2 values, %d given' % (len(options), ),
-                 self.position)
-            return
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
 
-    def _validate_out(self, ann_name, options):
-        if len(options) == 0:
-            return
-        if len(options) > 1:
-            warn('out annotation takes at most 1 value, %d given' % (len(options), ),
-                 self.position)
-            return
-        option = options[0]
-        if option not in [OPT_OUT_CALLEE_ALLOCATES,
-                          OPT_OUT_CALLER_ALLOCATES]:
-            warn("out annotation value is invalid: %r" % (option, ),
-                 self.position)
-            return
+        # The 'attributes' annotation allows free form annotations.
+        pass
+
+    def _do_validate_closure(self, ann_name, options):
+        '''
+        Validate the ``(closure)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, max_n_options=1)
+
+    def _do_validate_constructor(self, ann_name, options):
+        '''
+        Validate the ``(constructor)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=0)
+
+    def _do_validate_destroy(self, ann_name, options):
+        '''
+        Validate the ``(destroy)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=1)
+
+    def _do_validate_element_type(self, ann_name, options):
+        '''
+        Validate the ``(element)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, min_n_options=1, max_n_options=2)
+
+    def _do_validate_foreign(self, ann_name, options):
+        '''
+        Validate the ``(foreign)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=0)
+
+    def _do_validate_get_value_func(self, ann_name, options):
+        '''
+        Validate the ``(value-func)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=1)
+
+    def _do_validate_in(self, ann_name, options):
+        '''
+        Validate the ``(in)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=0)
+
+    def _do_validate_inout(self, ann_name, options):
+        '''
+        Validate the ``(in-out)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=0)
+
+    def _do_validate_method(self, ann_name, options):
+        '''
+        Validate the ``(method)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=0)
+
+    def _do_validate_out(self, ann_name, options):
+        '''
+        Validate the ``(out)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, max_n_options=1, choices=OUT_OPTIONS)
+
+    def _do_validate_ref_func(self, ann_name, options):
+        '''
+        Validate the ``(ref-func)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=1)
+
+    def _do_validate_rename_to(self, ann_name, options):
+        '''
+        Validate the ``(rename-to)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=1)
+
+    def _do_validate_scope(self, ann_name, options):
+        '''
+        Validate the ``(scope)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=1, choices=SCOPE_OPTIONS)
+
+    def _do_validate_set_value_func(self, ann_name, options):
+        '''
+        Validate the ``(value-func)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=1)
+
+    def _do_validate_skip(self, ann_name, options):
+        '''
+        Validate the ``(skip)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=0)
+
+    def _do_validate_transfer(self, ann_name, options):
+        '''
+        Validate the ``(transfer)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=1, choices=TRANSFER_OPTIONS)
+
+    def _do_validate_type(self, ann_name, options):
+        '''
+        Validate the ``(type)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=1)
+
+    def _do_validate_unref_func(self, ann_name, options):
+        '''
+        Validate the ``(unref-func)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=1)
+
+    def _do_validate_value(self, ann_name, options):
+        '''
+        Validate the ``(value)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=1)
+
+    def _do_validate_virtual(self, ann_name, options):
+        '''
+        Validate the ``(virtual)`` annotation.
+
+        :param ann_name: name of the annotation holding the options to validate
+        :param options: annotation options to validate
+        '''
+
+        self._validate_annotation(ann_name, options, exact_n_options=1)
+
+
+class GtkDocParameter(GtkDocAnnotatable):
+    '''
+    Represents a GTK-Doc parameter part.
+    '''
+
+    __slots__ = ('name', 'description')
+
+    valid_annotations = (ANN_ALLOW_NONE, ANN_ARRAY, ANN_ATTRIBUTES, ANN_CLOSURE, ANN_DESTROY,
+                         ANN_ELEMENT_TYPE, ANN_IN, ANN_INOUT, ANN_OUT, ANN_SCOPE, ANN_SKIP,
+                         ANN_TRANSFER, ANN_TYPE)
+
+    def __init__(self, name, position=None):
+        GtkDocAnnotatable.__init__(self, position)
+
+        #: Parameter name.
+        self.name = name
+
+        #: Parameter description or :const:`None`.
+        self.description = None
+
+    def __repr__(self):
+        return '<GtkDocParameter %r %r>' % (self.name, self.annotations)
 
     def _get_gtk_doc_value(self):
         def serialize_one(option, value, fmt, fmt2):
@@ -615,66 +910,20 @@ class GtkDocParameter(object):
     def to_gtk_doc(self):
         return '@%s: %s%s' % (self.name, self._get_gtk_doc_value(), self.description)
 
-    def validate(self):
-        for ann_name, value in self.annotations.items():
-            if ann_name == ANN_ALLOW_NONE:
-                self._validate_annotation(ann_name, value, n_params=0)
-            elif ann_name == ANN_ARRAY:
-                self._validate_array(ann_name, value)
-            elif ann_name == ANN_ATTRIBUTES:
-                # The 'attributes' annotation allows free form annotations.
-                pass
-            elif ann_name == ANN_CLOSURE:
-                self._validate_closure(ann_name, value)
-            elif ann_name == ANN_DESTROY:
-                self._validate_annotation(ann_name, value, n_params=1)
-            elif ann_name == ANN_ELEMENT_TYPE:
-                self._validate_element_type(ann_name, value)
-            elif ann_name == ANN_FOREIGN:
-                self._validate_annotation(ann_name, value, n_params=0)
-            elif ann_name == ANN_IN:
-                self._validate_annotation(ann_name, value, n_params=0)
-            elif ann_name in [ANN_INOUT, ANN_INOUT_ALT]:
-                self._validate_annotation(ann_name, value, n_params=0)
-            elif ann_name == ANN_OUT:
-                self._validate_out(ann_name, value)
-            elif ann_name == ANN_SCOPE:
-                self._validate_annotation(
-                    ann_name, value, required=True,
-                    n_params=1,
-                    choices=[OPT_SCOPE_ASYNC,
-                             OPT_SCOPE_CALL,
-                             OPT_SCOPE_NOTIFIED])
-            elif ann_name == ANN_SKIP:
-                self._validate_annotation(ann_name, value, n_params=0)
-            elif ann_name == ANN_TRANSFER:
-                self._validate_annotation(
-                    ann_name, value, required=True,
-                    n_params=1,
-                    choices=[OPT_TRANSFER_FULL,
-                             OPT_TRANSFER_CONTAINER,
-                             OPT_TRANSFER_NONE,
-                             OPT_TRANSFER_FLOATING])
-            elif ann_name == ANN_TYPE:
-                self._validate_annotation(ann_name, value, required=True,
-                                          n_params=1)
-            elif ann_name == ANN_CONSTRUCTOR:
-                self._validate_annotation(ann_name, value, n_params=0)
-            elif ann_name == ANN_METHOD:
-                self._validate_annotation(ann_name, value, n_params=0)
-            else:
-                warn('unknown annotation: %s' % (ann_name, ),
-                     self.position)
-
 
-class GtkDocTag(object):
+class GtkDocTag(GtkDocAnnotatable):
     '''
     Represents a GTK-Doc tag part.
     '''
 
-    __slots__ = ('name', 'annotations', 'value', 'description', 'position')
+    __slots__ = ('name', 'value', 'description')
+
+    valid_annotations = (ANN_ALLOW_NONE, ANN_ARRAY, ANN_ATTRIBUTES, ANN_ELEMENT_TYPE, ANN_SKIP,
+                         ANN_TRANSFER, ANN_TYPE)
 
     def __init__(self, name, position=None):
+        GtkDocAnnotatable.__init__(self, position)
+
         #: Tag name.
         self.name = name
 
@@ -684,92 +933,9 @@ class GtkDocTag(object):
         #: Tag description or :const:`None`.
         self.description = None
 
-        self.annotations = GtkDocAnnotations()
-        self.position = position
-
     def __repr__(self):
         return '<GtkDocTag %r %r>' % (self.name, self.annotations)
 
-    def _validate_annotation(self, ann_name, options, required=False,
-                             n_params=None, choices=None):
-        if required and len(options) == 0:
-            warn('%s annotation needs a value' % (ann_name, ), self.position)
-            return
-
-        if n_params is not None:
-            if n_params == 0:
-                s = 'no value'
-            elif n_params == 1:
-                s = 'one value'
-            else:
-                s = '%d values' % (n_params, )
-            if len(options) != n_params:
-                length = len(options)
-                warn('%s annotation needs %s, not %d' % (ann_name, s, length),
-                     self.position)
-                return
-
-        if choices is not None:
-            option = options[0]
-            if option not in choices:
-                warn('invalid %s annotation value: %r' % (ann_name, option, ),
-                     self.position)
-                return
-
-    def _validate_array(self, ann_name, options):
-        if len(options) == 0:
-            return
-
-        for option, value in options.items():
-            if option in [OPT_ARRAY_ZERO_TERMINATED, OPT_ARRAY_FIXED_SIZE]:
-                try:
-                    int(value)
-                except (TypeError, ValueError):
-                    if value is None:
-                        warn('array option %s needs a value' % (option, ),
-                             positions=self.position)
-                    else:
-                        warn('invalid array %s option value %r, '
-                             'must be an integer' % (option, value, ),
-                             positions=self.position)
-            elif option == OPT_ARRAY_LENGTH:
-                if value is None:
-                    warn('array option length needs a value',
-                         positions=self.position)
-            else:
-                warn('invalid array annotation value: %r' % (option, ),
-                     self.position)
-
-    def _validate_closure(self, ann_name, options):
-        if len(options) != 0 and len(options) > 1:
-            warn('closure takes at most 1 value, %d given' % (len(options), ),
-                 self.position)
-
-    def _validate_element_type(self, ann_name, options):
-        self._validate_annotation(ann_name, options, required=True)
-        if len(options) == 0:
-            warn('element-type takes at least one value, none given',
-                 self.position)
-            return
-        if len(options) > 2:
-            warn('element-type takes at most 2 values, %d given' % (len(options), ),
-                 self.position)
-            return
-
-    def _validate_out(self, ann_name, options):
-        if len(options) == 0:
-            return
-        if len(options) > 1:
-            warn('out annotation takes at most 1 value, %d given' % (len(options), ),
-                 self.position)
-            return
-        option = options[0]
-        if option not in [OPT_OUT_CALLEE_ALLOCATES,
-                          OPT_OUT_CALLER_ALLOCATES]:
-            warn("out annotation value is invalid: %r" % (option, ),
-                 self.position)
-            return
-
     def _get_gtk_doc_value(self):
         def serialize_one(option, value, fmt, fmt2):
             if value:
@@ -801,66 +967,22 @@ class GtkDocTag(object):
                              self._get_gtk_doc_value(),
                              self.description or '')
 
-    def validate(self):
-        for ann_name, value in self.annotations.items():
-            if ann_name == ANN_ALLOW_NONE:
-                self._validate_annotation(ann_name, value, n_params=0)
-            elif ann_name == ANN_ARRAY:
-                self._validate_array(ann_name, value)
-            elif ann_name == ANN_ATTRIBUTES:
-                # The 'attributes' annotation allows free form annotations.
-                pass
-            elif ann_name == ANN_CLOSURE:
-                self._validate_closure(ann_name, value)
-            elif ann_name == ANN_DESTROY:
-                self._validate_annotation(ann_name, value, n_params=1)
-            elif ann_name == ANN_ELEMENT_TYPE:
-                self._validate_element_type(ann_name, value)
-            elif ann_name == ANN_FOREIGN:
-                self._validate_annotation(ann_name, value, n_params=0)
-            elif ann_name == ANN_IN:
-                self._validate_annotation(ann_name, value, n_params=0)
-            elif ann_name in [ANN_INOUT, ANN_INOUT_ALT]:
-                self._validate_annotation(ann_name, value, n_params=0)
-            elif ann_name == ANN_OUT:
-                self._validate_out(ann_name, value)
-            elif ann_name == ANN_SCOPE:
-                self._validate_annotation(
-                    ann_name, value, required=True,
-                    n_params=1,
-                    choices=[OPT_SCOPE_ASYNC,
-                             OPT_SCOPE_CALL,
-                             OPT_SCOPE_NOTIFIED])
-            elif ann_name == ANN_SKIP:
-                self._validate_annotation(ann_name, value, n_params=0)
-            elif ann_name == ANN_TRANSFER:
-                self._validate_annotation(
-                    ann_name, value, required=True,
-                    n_params=1,
-                    choices=[OPT_TRANSFER_FULL,
-                             OPT_TRANSFER_CONTAINER,
-                             OPT_TRANSFER_NONE,
-                             OPT_TRANSFER_FLOATING])
-            elif ann_name == ANN_TYPE:
-                self._validate_annotation(ann_name, value, required=True,
-                                          n_params=1)
-            elif ann_name == ANN_CONSTRUCTOR:
-                self._validate_annotation(ann_name, value, n_params=0)
-            elif ann_name == ANN_METHOD:
-                self._validate_annotation(ann_name, value, n_params=0)
-            else:
-                warn('unknown annotation: %s' % (ann_name, ),
-                     self.position)
-
 
-class GtkDocCommentBlock(object):
+class GtkDocCommentBlock(GtkDocAnnotatable):
     '''
     Represents a GTK-Doc comment block.
     '''
 
-    __slots__ = ('name', 'annotations', 'tags', 'description', 'params', 'position')
+    __slots__ = ('name', 'params', 'description', 'tags')
+
+    #: Valid annotation names for the GTK-Doc comment block identifier part.
+    valid_annotations = (ANN_ATTRIBUTES, ANN_CONSTRUCTOR, ANN_FOREIGN, ANN_GET_VALUE_FUNC,
+                         ANN_METHOD, ANN_REF_FUNC, ANN_RENAME_TO, ANN_SET_VALUE_FUNC,
+                         ANN_SKIP, ANN_TRANSFER, ANN_TYPE, ANN_UNREF_FUNC, ANN_VALUE, ANN_VFUNC)
+
+    def __init__(self, name, position=None):
+        GtkDocAnnotatable.__init__(self, position)
 
-    def __init__(self, name):
         #: Identifier name.
         self.name = name
 
@@ -875,9 +997,6 @@ class GtkDocCommentBlock(object):
         #: applied to this :class:`GtkDocCommentBlock`.
         self.tags = OrderedDict()
 
-        self.annotations = GtkDocAnnotations()
-        self.position = None
-
     def __cmp__(self, other):
         # Note: This is used by g-ir-annotation-tool, which does a ``sorted(blocks.values())``,
         #       meaning that keeping this around makes update-glib-annotations.py patches
@@ -934,6 +1053,12 @@ class GtkDocCommentBlock(object):
         return comment
 
     def validate(self):
+        '''
+        Validate annotations applied to the :class:`GtkDocCommentBlock` identifier, parameters
+        and tags.
+        '''
+        GtkDocAnnotatable.validate(self)
+
         for param in self.params.values():
             param.validate()
 
diff --git a/giscanner/maintransformer.py b/giscanner/maintransformer.py
index b4418b6..ebb2b88 100644
--- a/giscanner/maintransformer.py
+++ b/giscanner/maintransformer.py
@@ -140,7 +140,7 @@ class MainTransformer(object):
         target = self._namespace.get_by_symbol(rename_to)
         if not target:
             message.warn_node(node,
-                "Can't find symbol %r referenced by Rename annotation" % (rename_to, ))
+                "Can't find symbol %r referenced by \"rename-to\" annotation" % (rename_to, ))
         elif target.shadowed_by:
             message.warn_node(node,
                 "Function %r already shadowed by %r, can't overwrite "
@@ -394,16 +394,12 @@ class MainTransformer(object):
     def _apply_annotations_element_type(self, parent, node, annotations):
         element_type_opt = annotations.get(ANN_ELEMENT_TYPE)
         if element_type_opt is None:
-            message.warn(
-                'element-type annotation takes at least one option, '
-                'none given',
-                annotations.position)
             return
 
         if isinstance(node.type, ast.List):
             if len(element_type_opt) != 1:
                 message.warn(
-                    'element-type annotation for a list must have exactly '
+                    '"element-type" annotation for a list must have exactly '
                     'one option, not %d options' % (len(element_type_opt), ),
                     annotations.position)
                 return
@@ -412,7 +408,7 @@ class MainTransformer(object):
         elif isinstance(node.type, ast.Map):
             if len(element_type_opt) != 2:
                 message.warn(
-                    'element-type annotation for a hash table must have exactly '
+                    '"element-type" annotation for a hash table must have exactly '
                     'two options, not %d option(s)' % (len(element_type_opt), ),
                     annotations.position)
                 return
@@ -423,15 +419,16 @@ class MainTransformer(object):
         elif isinstance(node.type, ast.Array):
             if len(element_type_opt) != 1:
                 message.warn(
-                    'element-type annotation for an array must have exactly '
+                    '"element-type" annotation for an array must have exactly '
                     'one option, not %d options' % (len(element_type_opt), ),
                     annotations.position)
                 return
             node.type.element_type = self._resolve(element_type_opt[0],
                                                    node.type, node, parent)
         else:
-            message.warn_node(parent,
-                "Unknown container %r for element-type annotation" % (node.type, ))
+            message.warn(
+                "Unknown container %r for element-type annotation" % (node.type, ),
+                annotations.position)
 
     def _get_transfer_default_param(self, parent, node):
         if node.direction in [ast.PARAM_DIRECTION_INOUT,
diff --git a/tests/scanner/annotationparser/gi/tag_returns.xml 
b/tests/scanner/annotationparser/gi/tag_returns.xml
index b5643b3..96ee804 100644
--- a/tests/scanner/annotationparser/gi/tag_returns.xml
+++ b/tests/scanner/annotationparser/gi/tag_returns.xml
@@ -333,4 +333,36 @@ parameter is encountered.</description>
   </parser>
 </test>
 
+<test>
+  <input>/**
+ * annotation_object_string_out:
+ *
+ * Test returning a string as an out parameter
+ *
+ * Returns: (out): some boolean
+ **/</input>
+  <parser>
+    <docblock>
+      <identifier>
+        <name>annotation_object_string_out</name>
+      </identifier>
+      <description>Test returning a string as an out parameter</description>
+      <tags>
+        <tag>
+          <name>returns</name>
+          <annotations>
+            <annotation>
+              <name>out</name>
+            </annotation>
+          </annotations>
+          <description>some boolean</description>
+        </tag>
+      </tags>
+    </docblock>
+    <messages>
+      <message>6: Warning: Test: unexpected annotation: out</message>
+    </messages>
+  </parser>
+</test>
+
 </tests>
diff --git a/tests/warn/callback-invalid-scope.h b/tests/warn/callback-invalid-scope.h
index 583dc0c..d07f172 100644
--- a/tests/warn/callback-invalid-scope.h
+++ b/tests/warn/callback-invalid-scope.h
@@ -7,7 +7,7 @@
  */
 void test_callback_invalid(GCallback *callback, gpointer user_data);
 
-// EXPECT:5: Warning: Test: invalid scope annotation value: 'invalid'
+// EXPECT:5: Warning: Test: invalid "scope" annotation option: "invalid"
 
 /**
  * test_callback_invalid2:
@@ -16,7 +16,7 @@ void test_callback_invalid(GCallback *callback, gpointer user_data);
  */
 void test_callback_invalid2(GCallback *callback, gpointer user_data);
 
-// EXPECT:14: Warning: Test: scope annotation needs a value
+// EXPECT:14: Warning: Test: "scope" annotation needs one option, none given
 
 /**
  * test_callback_invalid3:
@@ -25,7 +25,8 @@ void test_callback_invalid2(GCallback *callback, gpointer user_data);
  */
 void test_callback_invalid3(GCallback *callback, gpointer user_data);
 
-// EXPECT:23: Warning: Test: scope annotation needs one value, not 2
+// EXPECT:23: Warning: Test: "scope" annotation needs one option, 2 given
+// EXPECT:23: Warning: Test: invalid "scope" annotation option: "invalid"
 
 // EXPECT:13: Warning: Test: test_callback_invalid2: argument callback: Missing (scope) annotation for 
callback without GDestroyNotify (valid: call, async)
 // EXPECT:22: Warning: Test: test_callback_invalid3: argument callback: Missing (scope) annotation for 
callback without GDestroyNotify (valid: call, async)
diff --git a/tests/warn/invalid-array.h b/tests/warn/invalid-array.h
index a4a4e47..b9b828c 100644
--- a/tests/warn/invalid-array.h
+++ b/tests/warn/invalid-array.h
@@ -7,7 +7,7 @@
 void
 test_invalid_array (char ***out1);
 
-// EXPECT:5: Warning: Test: invalid array annotation value: 'foobar'
+// EXPECT:5: Warning: Test: invalid "array" annotation option: "foobar"
 
 /**
  * test_invalid_array_zero_terminated:
@@ -18,8 +18,8 @@ void
 test_invalid_array_zero_terminated (char ***out1,
                                     char ***out2);
 
-// EXPECT:14: Warning: Test: array option zero-terminated needs a value
-// EXPECT:15: Warning: Test: invalid array zero-terminated option value 'foobar', must be an integer
+// EXPECT:14: Warning: Test: "array" annotation option "zero-terminated" needs a value
+// EXPECT:15: Warning: Test: invalid "array" annotation option "zero-terminated" value "foobar", must be an 
integer
 
 /**
  * test_invalid_array_fixed_size:
@@ -30,8 +30,8 @@ void
 test_invalid_array_fixed_size (char ***out1,
                                char ***out2);
 
-// EXPECT:26: Warning: Test: array option fixed-size needs a value
-// EXPECT:27: Warning: Test: invalid array fixed-size option value 'foobar', must be an integer
+// EXPECT:26: Warning: Test: "array" annotation option "fixed-size" needs a value
+// EXPECT:27: Warning: Test: invalid "array" annotation option "fixed-size" value "foobar", must be an 
integer
 
 /**
  * test_invalid_array_length:
@@ -41,4 +41,4 @@ void
 test_invalid_array_length (char ***out1,
                            char ***out2);
 
-// EXPECT:38: Warning: Test: array option length needs a value
+// EXPECT:38: Warning: Test: "array" annotation option "length" needs a value
diff --git a/tests/warn/invalid-closure.h b/tests/warn/invalid-closure.h
index 50ba086..9769804 100644
--- a/tests/warn/invalid-closure.h
+++ b/tests/warn/invalid-closure.h
@@ -5,4 +5,4 @@
  */
 void test_invalid_closure(int param);
 
-// EXPECT:4: Warning: Test: closure takes at most 1 value, 2 given
+// EXPECT:4: Warning: Test: "closure" annotation takes at most one option, 2 given
diff --git a/tests/warn/invalid-element-type.h b/tests/warn/invalid-element-type.h
index 97b8281..8028fb7 100644
--- a/tests/warn/invalid-element-type.h
+++ b/tests/warn/invalid-element-type.h
@@ -8,8 +8,11 @@
 
 void test_invalid_list_element_type(GList *l1, GList *l2);
 
-// EXPECT:5: Warning: Test: element-type annotation needs a value
-// EXPECT:5: Warning: Test: element-type takes at least one value, none given
+// EXPECT:4: Warning: Test: test_invalid_list_element_type: argument l1: Missing (element-type) annotation
+// EXPECT:4: Warning: Test: test_invalid_list_element_type: argument l2: Missing (element-type) annotation
+// EXPECT:6: Warning: Test: "element-type" annotation for a list must have exactly one option, not 2 options
+// EXPECT:5: Warning: Test: "element-type" annotation takes at least one option, none given
+// EXPECT:5: Warning: Test: "element-type" annotation for a list must have exactly one option, not 0 options
 
 /**
  * test_invalid_array_element_type:
@@ -19,8 +22,7 @@ void test_invalid_list_element_type(GList *l1, GList *l2);
 
 void test_invalid_array_element_type(const char *a1, const char *a2);
 
-// EXPECT:16: Warning: Test: element-type annotation needs a value
-// EXPECT:16: Warning: Test: element-type takes at least one value, none given
+// EXPECT:19: Warning: Test: "element-type" annotation takes at least one option, none given
 
 /**
  * test_invalid_hash_element_type:
@@ -31,9 +33,9 @@ void test_invalid_array_element_type(const char *a1, const char *a2);
 
 void test_invalid_hash_element_type(GHashTable *h1, GHashTable *h2, GHashTable *h3);
 
-// EXPECT:27: Warning: Test: element-type annotation needs a value
-// EXPECT:27: Warning: Test: element-type takes at least one value, none given
-// EXPECT:29: Warning: Test: element-type takes at most 2 values, 3 given
+// EXPECT:29: Warning: Test: "element-type" annotation takes at least one option, none given
+// EXPECT:29: Warning: Test: "element-type" annotation for a hash table must have exactly two options, not 0 
option(s)
+// EXPECT:31: Warning: Test: "element-type" annotation takes at most 2 options, 3 given
 
 /**
  * test_invalid_bytearray_element_type:
@@ -43,8 +45,8 @@ void test_invalid_hash_element_type(GHashTable *h1, GHashTable *h2, GHashTable *
 
 void test_invalid_bytearray_element_type(GByteArray *b1, GByteArray *b2);
 
-// EXPECT:40: Warning: Test: element-type annotation needs a value
-// EXPECT:40: Warning: Test: element-type takes at least one value, none given
+// EXPECT:42: Warning: Test: "element-type" annotation takes at least one option, none given
+// EXPECT:42: Warning: Test: "element-type" annotation for an array must have exactly one option, not 0 
options
 
 /**
  * test_invalid_ptrarray_element_type:
@@ -54,8 +56,8 @@ void test_invalid_bytearray_element_type(GByteArray *b1, GByteArray *b2);
 
 void test_invalid_ptrarray_element_type(GPtrArray *p1, GPtrArray *p2);
 
-// EXPECT:51: Warning: Test: element-type annotation needs a value
-// EXPECT:51: Warning: Test: element-type takes at least one value, none given
+// EXPECT:53: Warning: Test: "element-type" annotation takes at least one option, none given
+// EXPECT:53: Warning: Test: "element-type" annotation for an array must have exactly one option, not 0 
options
 
 /**
  * test_unresolved_element_type:
@@ -74,19 +76,12 @@ GList* test_unresolved_element_type(void);
 GPtrArray* test_unresolved_value_element_type(void);
 
 
-// EXPECT:5: Warning: Test: element-type annotation for a list must have exactly one option, not 0 options
-// EXPECT:6: Warning: Test: element-type annotation for a list must have exactly one option, not 2 options
-// EXPECT:20: Warning: Test: Unknown container Type(target_fundamental=utf8, ctype=char*) for element-type 
annotation
+// EXPECT:19: Warning: Test: Unknown container Type(target_fundamental=utf8, ctype=char*) for element-type 
annotation
 // EXPECT:20: Warning: Test: Unknown container Type(target_fundamental=utf8, ctype=char*) for element-type 
annotation
-// EXPECT:27: Warning: Test: element-type annotation for a hash table must have exactly two options, not 0 
option(s)
-// EXPECT:28: Warning: Test: element-type annotation for a hash table must have exactly two options, not 1 
option(s)
-// EXPECT:29: Warning: Test: element-type annotation for a hash table must have exactly two options, not 3 
option(s)
-// EXPECT:40: Warning: Test: element-type annotation for an array must have exactly one option, not 0 options
-// EXPECT:41: Warning: Test: invalid (element-type) for a GByteArray, must be one of guint8, gint8 or gchar
-// EXPECT:51: Warning: Test: element-type annotation for an array must have exactly one option, not 0 options
-// EXPECT:52: Warning: Test: invalid (element-type) for a GPtrArray, must be a pointer
-// EXPECT:63: Warning: Test: test_unresolved_element_type: Unknown type: 'Unresolved'
-// EXPECT:71: Warning: Test: test_unresolved_value_element_type: Unknown type: 'GLib.Value'
-// EXPECT:4: Warning: Test: test_invalid_list_element_type: argument l1: Missing (element-type) annotation
-// EXPECT:4: Warning: Test: test_invalid_list_element_type: argument l2: Missing (element-type) annotation
-// EXPECT:50: Warning: Test: test_invalid_ptrarray_element_type: argument p1: Missing (element-type) 
annotation
+// EXPECT:30: Warning: Test: "element-type" annotation for a hash table must have exactly two options, not 1 
option(s)
+// EXPECT:31: Warning: Test: "element-type" annotation for a hash table must have exactly two options, not 3 
option(s)
+// EXPECT:43: Warning: Test: invalid (element-type) for a GByteArray, must be one of guint8, gint8 or gchar
+// EXPECT:52: Warning: Test: test_invalid_ptrarray_element_type: argument p1: Missing (element-type) 
annotation
+// EXPECT:54: Warning: Test: invalid (element-type) for a GPtrArray, must be a pointer
+// EXPECT:65: Warning: Test: test_unresolved_element_type: Unknown type: 'Unresolved'
+// EXPECT:73: Warning: Test: test_unresolved_value_element_type: Unknown type: 'GLib.Value'
diff --git a/tests/warn/invalid-out.h b/tests/warn/invalid-out.h
index fcb4f70..7e6ec34 100644
--- a/tests/warn/invalid-out.h
+++ b/tests/warn/invalid-out.h
@@ -5,4 +5,4 @@
 
 void test_invalid_out(int *out);
 
-// EXPECT:3: Warning: Test: out annotation value is invalid: 'invalid'
+// EXPECT:3: Warning: Test: invalid "out" annotation option: "invalid"
diff --git a/tests/warn/invalid-transfer.h b/tests/warn/invalid-transfer.h
index 3579ad1..ec43f2a 100644
--- a/tests/warn/invalid-transfer.h
+++ b/tests/warn/invalid-transfer.h
@@ -7,6 +7,6 @@
  */
 void test_transfer_invalid(int param, int param2, int param3);
 
-// EXPECT:4: Warning: Test: transfer annotation needs a value
-// EXPECT:5: Warning: Test: invalid transfer annotation value: 'invalid'
-// EXPECT:6: Warning: Test: transfer annotation needs one value, not 2
+// EXPECT:4: Warning: Test: "transfer" annotation needs one option, none given
+// EXPECT:5: Warning: Test: invalid "transfer" annotation option: "invalid"
+// EXPECT:6: Warning: Test: "transfer" annotation needs one option, 2 given



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]