def represents_applied_decorator(line_index, source_lines):
    """Check whether line has a decorator declaration."""
    ### grab line text from its index
    line_text = source_lines[line_index]

    ### if the quantity of '@' characters in the line
    ### isn't 1, we assume it isn't a decorator being
    ### applied, so we return an empty tuple
    if line_text.count("@") != 1:
        return ()

    ### XXX note that, in the 'if block' above, even though
    ### we assume that a line which doesn't have only one
    ### '@' character is't a decorator, there is still a
    ### possibility, however rare, in which we could still
    ### have a decorator being applied with more than one
    ### '@' character in the same line:
    ###
    ### the other(s) character(s) could be inside strings
    ### in arguments of a call, in case the decorator is
    ### returned by a callable;
    ###
    ### this kind of edge case should be dealt with in
    ### future redesigns, probably by using the tokenize
    ### module so we analize the substrings in the line
    ### by their meanings as Python source code units,
    ### not as raw strings (we'd probably eliminate NL
    ### tokens to simplify the analysys; we'd probably
    ### also use OP to found the quantity of actual
    ### '@' operators in the line, instead of using the
    ### character as a string);
    ###
    ### for now, the string analysis we use here isn't
    ### perfect, but covers common cases satisfactorily;

    ### define the index of the '@' character and the
    ### slice of the text before it, which we'll call
    ### "the indent of 'at'"

    index_of_at = line_text.index("@")
    indent_of_at = line_text[0:index_of_at]

    ### calculate the interval of the word after '@'

    ## grab index after '@'
    index_after_at = index_of_at + 1

    ## grab word after '@'

    word = (
        ## grab slice of line after the '@'
        line_text[index_after_at:]
        ## next replace any '(' character for a ' ' character
        ## so it doesn't get attached to the word before it
        ## when we split the string into words
        .replace("(", " ")
        ## then split the string into words
        .split()
        ## and finally grab the first word
        [0]
    )

    ## having found the word, now calculate its interval

    including_start = line_text.index(word, index_after_at)
    excluding_end = including_start + len(word)

    next_word_interval = including_start, excluding_end

    ### now let's check whether the next lines comply with
    ### the decorator declaration by iterating over which
    ### subsequent line;
    ###
    ### we iterate until we find a definition statement
    ### (a 'def' or 'class' statement), which confirms that
    ### we have a decorator being applied (since they are
    ### applied before definition statements);
    ###
    ### or until we hit a condition which means that our
    ### line containing '@' doesn't actually represents
    ### a decorator being applied (since the character is
    ### used for other purposes in Python as well, like
    ### matrix multiplication)

    ## grab index of next line
    next_line_index = line_index + 1

    ## iterate over each subsequent line

    for index in count(start=next_line_index):

        ## grab the text of the line
        line_text = source_lines[index]

        ## check whether same indent as the one in
        ## our first line is used in this line;
        ##
        ## if indents are different, we abort the
        ## highlighting by returning an empty tuple,
        ## since we assume it isn't the application
        ## of a decorator

        current_indent = line_text[0:index_of_at]

        if current_indent != indent_of_at:
            return ()

        ## XXX the "if block" above is another instance
        ## where what we assume could not actually be
        ## True, since different indents could be caused,
        ## for instance, by the situation shown below:
        ##
        ## @deco_func(
        ##    arg1, arg2)
        ## @deco2
        ## def func():
        ##
        ## that is, by a nested decorator with arguments
        ## being nested irregularly;
        ##
        ## once again this justifies a future redesign of
        ## this function using the tokenize module as
        ## discussed previously

        ## if current line has an '@' character at the
        ## same position that there's an '@' character in
        ## the first line, we must perform specific checks...

        if line_text[index_of_at] == "@":

            ## if quantity of '@' characters in the line
            ## isn't 1, we abort highlighting by returning
            ## an empty tuple
            if line_text.count("@") != 1:
                return ()

            ## otherwise we assume this is ok and go on to
            ## check the next line (it may be that the
            ## decorators are nested, and this is why we
            ## found a '@' before hitting a 'def' or 'class'
            ## statement)
            else:
                continue

        ## if at the beginning of the substring from the
        ## index of '@' you find a def or class statement,
        ## then we consider that we truly have a decorator
        ## declaration in our hands, so we return the
        ## interval of the word after the '@' in the
        ## first line

        startswith = line_text[index_of_at:].startswith

        if any(startswith(substring) for substring in ("def ", "class ")):
            return next_word_interval

        ## otherwise we abort highlighting by returning
        ## an empty tuple
        else:
            return ()
Check whether line has a decorator declaration.
nodezator/syntaxman/syntaxes/python/utils.py