예제 #1
0
    def maybe_diagnostic(self, cur):
        syn_statement = full_text_for_cursor(cur)
        syn_statement = syn_statement.replace('\n', '')
        syn_statement = syn_statement.replace('@synthesize ', '')
        if ',' in syn_statement:
            syn_statement = syn_statement.split(',')[-1]
        syn_statement = syn_statement.strip()

        d = LintDiagnostic()
        d.line_number = cur.location.line
        d.filename = cur.location.file.name
        d.context = '@synthesize ' + syn_statement
        d.category = self.category

        if '=' in syn_statement:
            if not self.re_long_form.match(syn_statement):
                d.message = "synthesize statement doesn't match "\
                        "template '@synthesize fooBar = fooBar_'"
                return d
        else:
            if not self.re_short_form.match(syn_statement) and\
               not self.re_macro_form.match(syn_statement):
                d.message = "synthesize statement doesn't match "\
                        "template '@synthesize fooBar'"
                return d

        return None
예제 #2
0
    def maybe_diagnostic(self, cur):

        # depth -1 -- OBJC_INSTANCE_METHOD_DECL (method signature)
        # depth  0 -- COMPOUND_STATMENT (curly braces)
        # depth  1 -- actual method code
        def calculate_complexity(c, depth=-1):
            result = 0
            if c.kind == ci.CursorKind.COMPOUND_STMT and depth > 0:
                result = 1 if (depth < 3) else 2

            result += sum(calculate_complexity(child, depth + 1) for child in c.get_children())

            return result

        complexity = calculate_complexity(cur)
        line_count = full_text_for_cursor(cur).count("\n")

        # TODO configurable thresholds
        if complexity > 15 or complexity > 3 and line_count > 50:

            d = LintDiagnostic()
            d.line_number = cur.location.line
            d.message = "method {0} looks complex ({1} lines, {2} complexity)".format(
                cur.displayname, line_count, complexity
            )
            d.filename = cur.location.file.name
            d.context = cur.spelling
            d.category = self.category

            return d

        return None
예제 #3
0
    def maybe_diagnostic(self, cur):
        syn_statement = full_text_for_cursor(cur)
        syn_statement = syn_statement.replace('\n', '')
        syn_statement = syn_statement.replace('@synthesize ', '')
        if ',' in syn_statement:
            syn_statement = syn_statement.split(',')[-1]
        syn_statement = syn_statement.strip()

        d = LintDiagnostic()
        d.line_number = cur.location.line
        d.filename = cur.location.file.name
        d.context = '@synthesize ' + syn_statement
        d.category = self.category

        if '=' in syn_statement:
            if not self.re_long_form.match(syn_statement):
                d.message = "synthesize statement doesn't match "\
                        "template '@synthesize fooBar = fooBar_'"
                return d
        else:
            if not self.re_short_form.match(syn_statement) and\
               not self.re_macro_form.match(syn_statement):
                d.message = "synthesize statement doesn't match "\
                        "template '@synthesize fooBar'"
                return d

        return None
예제 #4
0
    def get_diagnostics(self):

        def filter_cursors(cur):

            if cur.kind == self.cursor_kind:
                result = [cur]
            else:
                result = []

            for child in cur.get_children():
                if child.location.file and not isabs(child.location.file.name):
                    result += filter_cursors(child)

            return result

        curs = chain(*map(filter_cursors, self.cursors))

        regex = re.compile(self.regex_string)

        diags = []

        for c in curs:

            if not regex.search(c.displayname) and c.location.file:

                d = LintDiagnostic()
                d.line_number = c.location.line
                d.message = self.message.format(cur=c)
                d.filename = c.location.file.name
                d.context = c.displayname
                d.category = self.category
                diags.append(d)

        return diags
예제 #5
0
    def maybe_diagnostic(self, cur):

        # depth -1 -- OBJC_INSTANCE_METHOD_DECL (method signature)
        # depth  0 -- COMPOUND_STATMENT (curly braces)
        # depth  1 -- actual method code
        def calculate_complexity(c, depth=-1):
            result = 0
            if c.kind == ci.CursorKind.COMPOUND_STMT and depth > 0:
                result = 1 if (depth < 3) else 2

            result += sum(calculate_complexity(child, depth + 1)
                          for child in c.get_children())

            return result

        complexity = calculate_complexity(cur)
        line_count = full_text_for_cursor(cur).count('\n')

        # TODO configurable thresholds
        if complexity > 15 or\
           complexity > 3 and line_count > 50:

            d = LintDiagnostic()
            d.line_number = cur.location.line
            d.message = "method {0} looks complex ({1} lines, {2} complexity)"\
                    .format(cur.displayname, line_count, complexity)
            d.filename = cur.location.file.name
            d.context = cur.spelling
            d.category = self.category

            return d

        return None
예제 #6
0
    def maybe_diagnostic(self, cur):

        compound = None
        for c in cur.get_children():
            if c.kind == ci.CursorKind.COMPOUND_STMT:
                compound = c

        if not compound:
            return None

        children = list(compound.get_children())
        if not children:

            d = LintDiagnostic()
            d.line_number = cur.location.line
            d.message = "method {0} has empty implementation"\
                    .format(cur.displayname)
            d.filename = cur.location.file.name
            d.context = cur.displayname
            d.category = self.category

            return d

        elif len(children) == 1:

            only_statement = children[0]
            if only_statement.kind == ci.CursorKind.OBJC_MESSAGE_EXPR\
                    and only_statement.displayname == cur.displayname\
                    and '[super ' == full_text_for_cursor(only_statement)[0:7]:

                # TODO: verify that all parameters are passed through unchanged

                d = LintDiagnostic()
                d.line_number = cur.location.line
                d.message = "method {0} only calls super implementation"\
                        .format(cur.displayname)
                d.filename = cur.location.file.name
                d.context = cur.displayname
                d.category = self.category

                return d

        return None
예제 #7
0
    def maybe_diagnostic(self, cur):

        compound = None
        for c in cur.get_children():
            if c.kind == ci.CursorKind.COMPOUND_STMT:
                compound = c

        if not compound:
            return None

        children = list(compound.get_children())
        if not children:

            d = LintDiagnostic()
            d.line_number = cur.location.line
            d.message = "method {0} has empty implementation"\
                    .format(cur.displayname)
            d.filename = cur.location.file.name
            d.context = cur.displayname
            d.category = self.category

            return d

        elif len(children) == 1:

            only_statement = children[0]
            if only_statement.kind == ci.CursorKind.OBJC_MESSAGE_EXPR\
                    and only_statement.displayname == cur.displayname\
                    and '[super ' == full_text_for_cursor(only_statement)[0:7]:

                # TODO: verify that all parameters are passed through unchanged

                d = LintDiagnostic()
                d.line_number = cur.location.line
                d.message = "method {0} only calls super implementation"\
                        .format(cur.displayname)
                d.filename = cur.location.file.name
                d.context = cur.displayname
                d.category = self.category

                return d

        return None
예제 #8
0
 def get_diagnostics(self):
     result = []
     lines = enumerate(self.text.split('\n'))
     for i, l in lines:
         if len(l) > self.config.max_line_length(default=100):
             d = LintDiagnostic()
             d.line_number = i + 1
             d.message = 'Line too long ({0})'.format(len(l))
             d.filename = self.filename
             d.context = l.strip()
             d.category = self.category
             result.append(d)
     return result
예제 #9
0
    def maybe_diagnostic(self, cur):
        if cur.type.kind == ci.TypeKind.CONSTANTARRAY:

            d = LintDiagnostic()
            d.line_number = cur.location.line
            d.message = "ivar {0} is C array, do you really want to do this?"\
                    .format(cur.displayname)
            d.filename = cur.location.file.name
            d.context = full_text_for_cursor(cur)
            d.category = self.category

            return d

        return None
예제 #10
0
    def maybe_diagnostic(self, cur):
        if cur.type.kind == ci.TypeKind.CONSTANTARRAY:

            d = LintDiagnostic()
            d.line_number = cur.location.line
            d.message = "ivar {0} is C array, do you really want to do this?"\
                    .format(cur.displayname)
            d.filename = cur.location.file.name
            d.context = full_text_for_cursor(cur)
            d.category = self.category

            return d

        return None
예제 #11
0
    def maybe_diagnostic(self, cur):
        first_line = full_text_for_cursor(cur).split('\n')[0]

        if first_line[0] == self.first_char and\
                not self.regex.match(first_line):

            d = LintDiagnostic()
            d.line_number = cur.location.line
            d.message = self.message
            d.filename = cur.location.file.name
            d.context = first_line
            d.category = self.category

            return d

        return None
예제 #12
0
    def maybe_diagnostic(self, cur):
        first_line = full_text_for_cursor(cur).split('\n')[0]

        if first_line[0] == '-' and not re_method_decl.match(first_line):

            d = LintDiagnostic()
            d.line_number = cur.location.line
            d.message = "method declaration whitespace doesn't match "\
                        "template '- (Foo)barBaz:(Baz)baz'"
            d.filename = cur.location.file.name
            d.context = first_line
            d.category = self.category

            return d

        return None
예제 #13
0
    def maybe_diagnostic(self, cur):
        first_line = full_text_for_cursor(cur).split('\n')[0]

        if first_line[0] == self.first_char and\
                not self.regex.match(first_line):

            d = LintDiagnostic()
            d.line_number = cur.location.line
            d.message = self.message
            d.filename = cur.location.file.name
            d.context = first_line
            d.category = self.category

            return d

        return None
예제 #14
0
    def maybe_diagnostic(self, cur):

        try:
            cur.get_children().next()
            return None
        except StopIteration:
            pass

        d = LintDiagnostic()
        d.line_number = cur.location.line
        d.message = "empty compound statement"
        d.filename = cur.location.file.name
        d.context = full_text_for_cursor(cur)
        d.category = self.category

        return d
예제 #15
0
    def maybe_diagnostic(self, cur):

        try:
            cur.get_children().next()
            return None
        except StopIteration:
            pass

        d = LintDiagnostic()
        d.line_number = cur.location.line
        d.message = "empty compound statement"
        d.filename = cur.location.file.name
        d.context = full_text_for_cursor(cur)
        d.category = self.category

        return d
예제 #16
0
    def maybe_diagnostic(self, cur):
        ivar_name = cur.displayname

        if cur.parent.kind == ci.CursorKind.OBJC_INTERFACE_DECL and\
                not self.re_ivar_name.match(ivar_name):

            d = LintDiagnostic()
            d.line_number = cur.location.line
            d.message = "ivar {0} is not named likeThis_"\
                    .format(cur.displayname)
            d.filename = cur.location.file.name
            d.context = cur.displayname
            d.category = self.category

            return d

        return None
예제 #17
0
    def maybe_diagnostic(self, cur):
        ivar_name = cur.displayname

        if cur.parent.kind == ci.CursorKind.OBJC_INTERFACE_DECL and\
                not self.re_ivar_name.match(ivar_name):

            d = LintDiagnostic()
            d.line_number = cur.location.line
            d.message = "ivar {0} is not named likeThis_"\
                    .format(cur.displayname)
            d.filename = cur.location.file.name
            d.context = cur.displayname
            d.category = self.category

            return d

        return None
예제 #18
0
    def get_diagnostics(self):
        result = []
        lines = enumerate(self.text.split('\n'))

        regex = re.compile(self.regex_string)

        for i, l in lines:
            if regex.search(l):
                d = LintDiagnostic()
                d.line_number = i
                d.message = self.message
                d.filename = self.filename
                d.context = l
                d.category = self.category
                result.append(d)

        return result
    def maybe_diagnostic(self, cur):

        if not cur.location.file:
            return None

        children = list(cur.get_children())

        if 2 != len(children):
            print 'WTF'

        lhs, rhs = children[0], children[1]

        start = lhs.extent.end.offset
        end = rhs.extent.start.offset

        if end-start <= 1:
            return None

        with open(cur.location.file.name) as fi:
            op_and_whitespace = fi.read()[start:end]

        if '\n' in op_and_whitespace:
            return None

        for i in range(len(op_and_whitespace)):
            left, right = op_and_whitespace[i], op_and_whitespace[-(i+1)]

            if left != ' ' and right != ' ':
                # no more whitespace at either side
                break
            elif left == ' ' and right == ' ':
                # whitespace is symmetric so far
                continue
            else:
                d = LintDiagnostic()
                d.line_number = cur.location.line
                d.message = "Whitespace around binary operator isn't symmetric"
                d.filename = cur.location.file.name
                d.context = full_text_for_cursor(cur)
                d.category = self.category
                return d

        return None
    def maybe_diagnostic(self, cur):

        if not cur.location.file:
            return None

        children = list(cur.get_children())

        if 2 != len(children):
            print 'WTF'

        lhs, rhs = children[0], children[1]

        start = lhs.extent.end.offset
        end = rhs.extent.start.offset

        if end - start <= 1:
            return None

        with open(cur.location.file.name) as fi:
            op_and_whitespace = fi.read()[start:end]

        if '\n' in op_and_whitespace:
            return None

        for i in range(len(op_and_whitespace)):
            left, right = op_and_whitespace[i], op_and_whitespace[-(i + 1)]

            if left != ' ' and right != ' ':
                # no more whitespace at either side
                break
            elif left == ' ' and right == ' ':
                # whitespace is symmetric so far
                continue
            else:
                d = LintDiagnostic()
                d.line_number = cur.location.line
                d.message = "Whitespace around binary operator isn't symmetric"
                d.filename = cur.location.file.name
                d.context = full_text_for_cursor(cur)
                d.category = self.category
                return d

        return None
예제 #21
0
    def maybe_diagnostic(self, cur):

        def get_objc_superclass(c):
            for child in c.get_children():
                if child.kind == ci.CursorKind.OBJC_SUPER_CLASS_REF:
                    return child.get_definition()

            return None

        if cur.type.kind != ci.TypeKind.OBJCOBJECTPOINTER:
            return None

        class_cursor = cur.type.get_pointee().get_declaration()
        var_name = cur.displayname

        for regex, expected_class_name in self.rules:
            if not regex.search(var_name):
                continue

            # walk objc class hierarchy trying to find
            # ancestor with name |expected_class_name|
            while class_cursor:
                class_name = class_cursor.displayname
                if class_name == expected_class_name:
                    return None
                class_cursor = get_objc_superclass(class_cursor)
            else:

                actual_class = cur.type.get_pointee().get_declaration()

                d = LintDiagnostic()
                d.line_number = cur.location.line
                d.message = self.message_template.format(cur.displayname,
                                                    actual_class.displayname,
                                                    expected_class_name)
                d.filename = cur.location.file.name
                d.context = full_text_for_cursor(cur)
                d.category = self.category

                return d

        return None
예제 #22
0
    def maybe_diagnostic(self, cur):
        def get_objc_superclass(c):
            for child in c.get_children():
                if child.kind == ci.CursorKind.OBJC_SUPER_CLASS_REF:
                    return child.get_definition()

            return None

        if cur.type.kind != ci.TypeKind.OBJCOBJECTPOINTER:
            return None

        class_cursor = cur.type.get_pointee().get_declaration()
        var_name = cur.displayname

        for regex, expected_class_name in self.rules:
            if not regex.search(var_name):
                continue

            # walk objc class hierarchy trying to find
            # ancestor with name |expected_class_name|
            while class_cursor:
                class_name = class_cursor.displayname
                if class_name == expected_class_name:
                    return None
                class_cursor = get_objc_superclass(class_cursor)
            else:

                actual_class = cur.type.get_pointee().get_declaration()

                d = LintDiagnostic()
                d.line_number = cur.location.line
                d.message = self.message_template.format(
                    cur.displayname, actual_class.displayname,
                    expected_class_name)
                d.filename = cur.location.file.name
                d.context = full_text_for_cursor(cur)
                d.category = self.category

                return d

        return None
예제 #23
0
def get_clang_analyzer_diagnostics(filename, clang_args):

    plist_filename = '.sa' + os.path.basename(filename)

    invocation = ['clang', '--analyze']
    invocation += clang_args
    invocation += ['-o', plist_filename]
    invocation += [
        '-Xclang', '-analyzer-checker=core,deadcode,unix,osx,experimental'
    ]

    if '-fobjc-arc' in clang_args:
        invocation += [
            '-Xclang',
            '-analyzer-disable-checker=experimental.osx.cocoa.Dealloc'
        ]

    invocation.append(filename)

    p = sp.Popen(invocation, stdout=sp.PIPE, stderr=sp.STDOUT)
    p.communicate()

    try:
        with open(plist_filename) as fi:
            root = plist.readPlist(fi)
    except IOError:
        return []

    os.remove(plist_filename)

    result = []
    for diag_dict in root['diagnostics']:
        d = LintDiagnostic()
        d.message = diag_dict['description']
        d.line_number = diag_dict['location']['line']
        d.filename = root['files'][diag_dict['location']['file']]
        with open(d.filename) as fi:
            d.context = fi.readlines()[d.line_number - 1].rstrip()
        d.category = 'ClangSA'
        result.append(d)
    return result
        def maybe_diagnostic(cur):

            class_name = cur.displayname

            instance_methods = [c for c in cur.get_children() if\
                    c.kind == cindex.CursorKind.OBJC_INSTANCE_METHOD_DECL]

            def is_init(c):
                return self.re_init_decl.search(c.displayname)

            def is_dealloc(c):
                return self.re_dealloc_decl.search(c.displayname)

            init_count = len([m for m in instance_methods if is_init(m)])

            dealloc_present = any(map(is_dealloc, instance_methods))

            these_should_be_inits = instance_methods[:init_count]

            d = LintDiagnostic()
            d.line_number = cur.location.line
            d.filename = cur.location.file.name
            d.context = ''
            d.category = self.category

            if not all(map(is_init, these_should_be_inits)):
                d.message = INIT_TEMPLATE.format(len(these_should_be_inits),
                                                 class_name)

                return d

            if dealloc_present:
                this_should_be_dealloc = instance_methods[init_count]
                if not is_dealloc(this_should_be_dealloc):
                    d.message = DEALLOC_TEMPLATE.format(class_name)
                    return d

            return None
예제 #25
0
파일: utils.py 프로젝트: ethercrow/trollint
def get_clang_analyzer_diagnostics(filename, clang_args):

    plist_filename = '.sa' + os.path.basename(filename)

    invocation = ['clang', '--analyze']
    invocation += clang_args
    invocation += ['-o', plist_filename]
    invocation += ['-Xclang',
            '-analyzer-checker=core,deadcode,unix,osx,experimental']

    if '-fobjc-arc' in clang_args:
        invocation += ['-Xclang',
                '-analyzer-disable-checker=experimental.osx.cocoa.Dealloc']

    invocation.append(filename)

    p = sp.Popen(invocation, stdout=sp.PIPE, stderr=sp.STDOUT)
    p.communicate()

    try:
        with open(plist_filename) as fi:
            root = plist.readPlist(fi)
    except IOError:
        return []

    os.remove(plist_filename)

    result = []
    for diag_dict in root['diagnostics']:
        d = LintDiagnostic()
        d.message = diag_dict['description']
        d.line_number = diag_dict['location']['line']
        d.filename = root['files'][diag_dict['location']['file']]
        with open(d.filename) as fi:
            d.context = fi.readlines()[d.line_number - 1].rstrip()
        d.category = 'ClangSA'
        result.append(d)
    return result
        def maybe_diagnostic(cur):

            class_name = cur.displayname

            instance_methods = [c for c in cur.get_children() if c.kind == cindex.CursorKind.OBJC_INSTANCE_METHOD_DECL]

            def is_init(c):
                return self.re_init_decl.search(c.displayname)

            def is_dealloc(c):
                return self.re_dealloc_decl.search(c.displayname)

            init_count = len([m for m in instance_methods if is_init(m)])

            dealloc_present = any(map(is_dealloc, instance_methods))

            these_should_be_inits = instance_methods[:init_count]

            d = LintDiagnostic()
            d.line_number = cur.location.line
            d.filename = cur.location.file.name
            d.context = ""
            d.category = self.category

            if not all(map(is_init, these_should_be_inits)):
                d.message = INIT_TEMPLATE.format(len(these_should_be_inits), class_name)

                return d

            if dealloc_present:
                this_should_be_dealloc = instance_methods[init_count]
                if not is_dealloc(this_should_be_dealloc):
                    d.message = DEALLOC_TEMPLATE.format(class_name)
                    return d

            return None
예제 #27
0
    def get_diagnostics(self):

        local_cursors = [c for c in self.cursors\
                if c.location.file.name == self.filename]

        def is_local_category(cur):
            return cur.kind == ci.CursorKind.OBJC_CATEGORY_DECL\
               and cur.displayname == ''
        local_categories = [c for c in local_cursors if is_local_category(c)]

        class Extension(object):
            def __init__(self, classname, ivars):
                self.classname = classname
                self.ivars = ivars

        exts = []
        for lc in local_categories:
            class_cursor = list(lc.get_children())[0]
            ivars = [i for i in lc.get_children()\
                    if i.kind == ci.CursorKind.OBJC_IVAR_DECL]

            exts.append(Extension(class_cursor.displayname, ivars))

        result = []
        for ext in exts:
            class_impl = [c for c in local_cursors\
                    if c.kind == ci.CursorKind.OBJC_IMPLEMENTATION_DECL and\
                    c.displayname == ext.classname][0]

            ivar_usages = dict([(i.displayname, 0) for i in ext.ivars])

            method_impls = [c for c in class_impl.get_children()\
                    if c.kind == ci.CursorKind.OBJC_INSTANCE_METHOD_DECL]

            def collect_ivar_usages(cur):
                ivar_set = set()

                def go(cur):
                    if cur.kind == ci.CursorKind.MEMBER_REF_EXPR:
                        ivar_set.add(cur.displayname)

                    for child in cur.get_children():
                        go(child)

                go(cur)

                return ivar_set

            for mi in method_impls:
                ivar_usages_in_method = collect_ivar_usages(mi)
                for usage in ivar_usages_in_method:
                    if usage in ivar_usages:
                        ivar_usages[usage] += 1

            for ivar in ext.ivars:
                if ivar_usages[ivar.displayname] == 0:
                    d = LintDiagnostic()
                    d.category = self.category
                    d.filename = self.filename
                    d.line = ivar.location.line
                    d.context = full_text_for_cursor(ivar)
                    d.message = 'unused ivar ' + ivar.displayname
                    result.append(d)
                elif ivar_usages[ivar.displayname] == 1 and self.has_arc:

                    # FIXME: false positive if ivar is used as a storage
                    # to fix this, we need to check if read access is possible
                    # before write access in this method

                    d = LintDiagnostic()
                    d.category = self.category
                    d.filename = self.filename
                    d.line = ivar.location.line
                    d.context = full_text_for_cursor(ivar)
                    d.message = 'ivar {0} is used in only one method'.format(
                            ivar.displayname)
                    result.append(d)

        return result
예제 #28
0
    def get_diagnostics(self):

        local_cursors = [c for c in self.cursors\
                if c.location.file.name == self.filename]

        def is_local_category(cur):
            return cur.kind == ci.CursorKind.OBJC_CATEGORY_DECL\
               and cur.displayname == ''
        local_categories = [c for c in local_cursors if is_local_category(c)]

        class Extension(object):
            def __init__(self, filename, methods):
                self.filename = filename
                self.methods = methods

        exts = []
        for lc in local_categories:
            class_cursor = list(lc.get_children())[0]
            ivars = [i for i in lc.get_children()\
                    if i.kind == ci.CursorKind.OBJC_INSTANCE_METHOD_DECL]

            exts.append(Extension(class_cursor.displayname, ivars))

        result = []
        for ext in exts:
            class_impls = [c for c in local_cursors\
                    if c.kind == ci.CursorKind.OBJC_IMPLEMENTATION_DECL and\
                       c.displayname == ext.filename]

            yet_unused_method_names = [i.displayname for i in ext.methods]

            def traverse(cur):
                if cur.kind == ci.CursorKind.OBJC_MESSAGE_EXPR\
                        and cur.displayname in yet_unused_method_names:
                    yet_unused_method_names.remove(cur.displayname)

                if cur.kind == ci.CursorKind.OBJC_SELECTOR_EXPR:
                    # '@selector(foo)' -> 'foo'
                    selector_name = full_text_for_cursor(cur)[10:-1]

                    if selector_name in yet_unused_method_names:
                        yet_unused_method_names.remove(selector_name)

                if not yet_unused_method_names:
                    return

                for child in cur.get_children():
                    traverse(child)

            for class_impl in class_impls:
                traverse(class_impl)

            for method in ext.methods:
                if method.displayname in yet_unused_method_names:
                    d = LintDiagnostic()
                    d.category = self.category
                    d.filename = self.filename
                    d.line = method.location.line
                    d.context = full_text_for_cursor(method)
                    d.message = 'unused method ' + method.displayname
                    result.append(d)

        return result
예제 #29
0
    def get_diagnostics(self):

        local_cursors = [c for c in self.cursors\
                if c.location.file.name == self.filename]

        def is_local_category(cur):
            return cur.kind == ci.CursorKind.OBJC_CATEGORY_DECL\
               and cur.displayname == ''

        local_categories = [c for c in local_cursors if is_local_category(c)]

        class Extension(object):
            def __init__(self, classname, ivars):
                self.classname = classname
                self.ivars = ivars

        exts = []
        for lc in local_categories:
            class_cursor = list(lc.get_children())[0]
            ivars = [i for i in lc.get_children()\
                    if i.kind == ci.CursorKind.OBJC_IVAR_DECL]

            exts.append(Extension(class_cursor.displayname, ivars))

        result = []
        for ext in exts:
            class_impl = [c for c in local_cursors\
                    if c.kind == ci.CursorKind.OBJC_IMPLEMENTATION_DECL and\
                    c.displayname == ext.classname][0]

            ivar_usages = dict([(i.displayname, 0) for i in ext.ivars])

            method_impls = [c for c in class_impl.get_children()\
                    if c.kind == ci.CursorKind.OBJC_INSTANCE_METHOD_DECL]

            def collect_ivar_usages(cur):
                ivar_set = set()

                def go(cur):
                    if cur.kind == ci.CursorKind.MEMBER_REF_EXPR:
                        ivar_set.add(cur.displayname)

                    for child in cur.get_children():
                        go(child)

                go(cur)

                return ivar_set

            for mi in method_impls:
                ivar_usages_in_method = collect_ivar_usages(mi)
                for usage in ivar_usages_in_method:
                    if usage in ivar_usages:
                        ivar_usages[usage] += 1

            for ivar in ext.ivars:
                if ivar_usages[ivar.displayname] == 0:
                    d = LintDiagnostic()
                    d.category = self.category
                    d.filename = self.filename
                    d.line = ivar.location.line
                    d.context = full_text_for_cursor(ivar)
                    d.message = 'unused ivar ' + ivar.displayname
                    result.append(d)
                elif ivar_usages[ivar.displayname] == 1 and self.has_arc:

                    # FIXME: false positive if ivar is used as a storage
                    # to fix this, we need to check if read access is possible
                    # before write access in this method

                    d = LintDiagnostic()
                    d.category = self.category
                    d.filename = self.filename
                    d.line = ivar.location.line
                    d.context = full_text_for_cursor(ivar)
                    d.message = 'ivar {0} is used in only one method'.format(
                        ivar.displayname)
                    result.append(d)

        return result
예제 #30
0
    def get_diagnostics(self):

        local_cursors = [c for c in self.cursors\
                if c.location.file.name == self.filename]

        def is_local_category(cur):
            return cur.kind == ci.CursorKind.OBJC_CATEGORY_DECL\
               and cur.displayname == ''

        local_categories = [c for c in local_cursors if is_local_category(c)]

        class Extension(object):
            def __init__(self, filename, methods):
                self.filename = filename
                self.methods = methods

        exts = []
        for lc in local_categories:
            class_cursor = list(lc.get_children())[0]
            ivars = [i for i in lc.get_children()\
                    if i.kind == ci.CursorKind.OBJC_INSTANCE_METHOD_DECL]

            exts.append(Extension(class_cursor.displayname, ivars))

        result = []
        for ext in exts:
            class_impls = [c for c in local_cursors\
                    if c.kind == ci.CursorKind.OBJC_IMPLEMENTATION_DECL and\
                       c.displayname == ext.filename]

            yet_unused_method_names = [i.displayname for i in ext.methods]

            def traverse(cur):
                if cur.kind == ci.CursorKind.OBJC_MESSAGE_EXPR\
                        and cur.displayname in yet_unused_method_names:
                    yet_unused_method_names.remove(cur.displayname)

                if cur.kind == ci.CursorKind.OBJC_SELECTOR_EXPR:
                    # '@selector(foo)' -> 'foo'
                    selector_name = full_text_for_cursor(cur)[10:-1]

                    if selector_name in yet_unused_method_names:
                        yet_unused_method_names.remove(selector_name)

                if not yet_unused_method_names:
                    return

                for child in cur.get_children():
                    traverse(child)

            for class_impl in class_impls:
                traverse(class_impl)

            for method in ext.methods:
                if method.displayname in yet_unused_method_names:
                    d = LintDiagnostic()
                    d.category = self.category
                    d.filename = self.filename
                    d.line = method.location.line
                    d.context = full_text_for_cursor(method)
                    d.message = 'unused method ' + method.displayname
                    result.append(d)

        return result