Exemplo n.º 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
Exemplo n.º 2
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
Exemplo n.º 3
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
Exemplo n.º 4
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
Exemplo n.º 5
0
def print_cursor_recursive(cur, depth=0):

    token_text = full_text_for_cursor(cur)
    token_text = cur.displayname

    print('{0} {1} | {2}'.format('->' * depth, cur.kind, token_text,
        cur.location.file.name if cur.location.file else '???'))

    for child in cur.get_children():
        print_cursor_recursive(child, depth + 1)
Exemplo n.º 6
0
def print_cursor_recursive(cur, depth=0):

    token_text = full_text_for_cursor(cur)
    # token_text = cur.displayname
    # token_text = cur.spelling

    if '--all' not in sys.argv\
            and cur.location.file\
            and cur.location.file.name == sys.argv[1]:
        print('{0} {1} | {2}'.format('->' * depth, cur.kind, token_text))

    for child in cur.get_children():
        print_cursor_recursive(child, depth + 1)
Exemplo n.º 7
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
Exemplo n.º 8
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
Exemplo n.º 9
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
Exemplo n.º 10
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
Exemplo n.º 11
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
Exemplo n.º 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
Exemplo n.º 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
Exemplo n.º 14
0
            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)
Exemplo n.º 15
0
            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)
    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
Exemplo n.º 17
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
Exemplo n.º 18
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
    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
Exemplo n.º 20
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
Exemplo n.º 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
Exemplo n.º 22
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
Exemplo n.º 23
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
Exemplo n.º 24
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
Exemplo n.º 25
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