def only_use_own_priv_vars(ast): """Check that all private variables used are defined here.""" used_privs = [] global_set_vars = find_variables_in_scopes.set_in_tree(ast) global_used_vars = find_variables_in_scopes.used_in_tree(ast) _, global_definitions = find_all.private_calls_and_definitions(ast) # The big assumption here is that the "scopes" structure in both # trees are the same def _scope_visitor(set_vars_scope, used_vars_scope): """Visit scope's set vars and used vars. If a var was private and used, but not set in this scope or any parents, then report an error """ assert len(set_vars_scope.scopes) == len(used_vars_scope.scopes) for index in range(0, len(set_vars_scope.scopes)): _scope_visitor(set_vars_scope.scopes[index], used_vars_scope.scopes[index]) for variable in used_vars_scope.used_vars: used_privs.extend(list(_find_violating_priv_uses(variable, set_vars_scope))) _scope_visitor(global_set_vars, global_used_vars) # Filter out definitions of private functions of the same name # as functions can be used as variables. used_privs = [up for up in used_privs if up[0] not in global_definitions] err_msg = "Referenced external private variable {0}" return [LinterFailure(err_msg.format(u[0]), u[1]) for u in used_privs]
def _call_handler(name, node): """Handle function calls.""" assert name == "FunctionCall" if node.name.lower() != node.name: msg = "{0} is not lowercase".format(node.name) replacement = util.replace_word(contents[node.line - 1], node.col - 1, node.name, node.name.lower()) errors.append(LinterFailure(msg, node.line, replacement))
def _generate_error(node): """Generate an error and replacement for node in violation.""" msg = "Path {0} must be quoted".format(node.contents) line_index = node.line - 1 col_index = node.col - 1 quoted = "\"{0}\"" replacement = util.replace_word(contents[line_index], col_index, node.contents, quoted.format(node.contents)) return LinterFailure(msg, node.line, replacement)
def _word_visitor(name, node): """Visit all arguments.""" assert name == "Word" if node.type == WordType.String: if not _RE_DOUBLE_OUTER_QUOTES.match(node.contents): msg = "{0} must use double quotes".format(node.contents) replacement_word = "\"{0}\"".format(node.contents[1:-1]) replacement = util.replace_word(contents[node.line - 1], node.col - 1, node.contents, replacement_word) errors.append(LinterFailure(msg, node.line, replacement))
def _visit_function_call(node, depth): """Handle function calls.""" col = node.col expected = 1 + (depth * indent) if node.col != expected: delta = expected - node.col msg = "Expected {0} to be on column {1}".format( node.name, expected) replacement = util.replace_word(contents[node.line - 1], col - 1 + min(0, delta), " " * max(0, delta * -1), " " * max(0, delta)) errors.append(LinterFailure(msg, node.line, replacement))
def only_use_own_privates(abstract_syntax_tree): """Check that all private definitions used are defined here.""" calls, defs = find_all.private_calls_and_definitions(abstract_syntax_tree) errors = [] for call, info in calls.items(): if call not in defs.keys(): for line in info: msg = "Used external private definition {0}".format(call) errors.append(LinterFailure(msg, line)) return errors
def _definition_handler(name, node): """Handle function calls and macro definitions.""" assert name == "FunctionDefinition" or name == "MacroDefinition" definition_name_word = node.header.arguments[0] definition_name = definition_name_word.contents if definition_name.lower() != definition_name: msg = "{0} name {1} is not lowercase".format(name, definition_name) line_index = definition_name_word.line - 1 col_index = definition_name_word.col - 1 replacement = util.replace_word(contents[line_index], col_index, definition_name, definition_name.lower()) errors.append( LinterFailure(msg, definition_name_word.line, replacement))
def _definition_visitor(name, node): """Visit all definitions.""" assert name == "FunctionDefinition" or name == "MacroDefinition" if len(node.header.arguments) < 2: return for arg in node.header.arguments[1:]: if arg.contents.upper() != arg.contents: msg = "{0} must be uppercase".format(arg.contents) line_index = arg.line - 1 col_index = arg.col - 1 replacement = util.replace_word(contents[line_index], col_index, arg.contents, arg.contents.upper()) errors.append(LinterFailure(msg, arg.line, replacement))
def _scope_visitor(set_scope, used_scope): """Visit both scopes at the same level.""" assert len(set_scope.scopes) == len(used_scope.scopes) # Ignore the global scope if id(set_scopes) != id(set_scope): for var in set_scope.set_vars: if var.node.type != WordType.Variable: return if not _variable_used_in_scope(var.node.contents, var.node, used_scope): msg = "Unused local variable {0}".format(var.node.contents) errors.append(LinterFailure(msg, var.node.line)) for index in range(0, len(set_scope.scopes)): _scope_visitor(set_scope.scopes[index], used_scope.scopes[index])
def private_definitions_used(abstract_syntax_tree): """Check that all private definitions are used by this module.""" calls, defs = find_all.private_calls_and_definitions(abstract_syntax_tree) errors = [] # There's no scoping of functions defined within other functions, so # we search from the root of the tree. global_scope = find_variables_in_scopes.used_in_tree(abstract_syntax_tree) for definition, info in defs.items(): not_in_function_calls = definition not in calls.keys() not_used_as_variable = not _variable_used_in_scope( definition, None, global_scope) if not_in_function_calls and not_used_as_variable: for line in info: msg = "Unused private definition {0}".format(definition) errors.append(LinterFailure(msg, line)) return errors
def _check_horizontal_space(node, index, contents): """Check horizontal space between arguments on same line.""" if index > 0: current_column = node.arguments[index].col - 1 previous_column = node.arguments[index - 1].col - 1 previous_len = len(node.arguments[index - 1].contents) spaces_start = previous_column + previous_len num_spaces = current_column - (previous_column + previous_len) line_number = node.arguments[index].line - 1 if num_spaces != 1: cur = node.arguments[index].contents prev = node.arguments[index - 1].contents msg = "Must be a single space between {0} and {1}".format( cur, prev) replacement = util.replace_word(contents[line_number], spaces_start, " " * num_spaces, " ") return LinterFailure(msg, node.line, replacement)
def set_variables_capitalized(contents, abstract_syntax_tree): """Check that each variable mutated is capitalized.""" errors = [] variables = find_set_variables.in_tree(abstract_syntax_tree) for evaluate in variables: evaluate_upper = evaluate.contents.upper() # Argument should be either String or Variable and a transformation # to uppercase should have no effect if (util.is_word_sink_variable(evaluate.type) and evaluate_upper != evaluate.contents): desc = "{0} must be uppercase".format(evaluate.contents) line = evaluate.line - 1 replacement = util.replace_word(contents[line], evaluate.col - 1, evaluate.contents, evaluate_upper) errors.append(LinterFailure(desc, evaluate.line, replacement)) break return errors
def _definition_handler(name, node, depth): """Visit all definitions.""" assert name == "FunctionDefinition" or name == "MacroDefinition" assert len(node.header.arguments) > 0 del depth definition = node.header.arguments[0] def_name = definition.contents if not def_name.startswith((namespace, "_" + namespace)): msg = "Definition {0} does not start with {1}".format(def_name, namespace) replacement_name = "{0}_{1}".format(namespace, def_name) if def_name.startswith("_"): replacement_name = "_{0}".format(replacement_name) replacement = util.replace_word(contents[node.line - 1], definition.col - 1, def_name, replacement_name) errors.append(LinterFailure(msg, node.line, replacement))
def _check_alignment(arg, last_align, baseline_col, baseline_line, contents): """Check alignment of arg.""" align = _expected_alignment(arg, baseline_col, baseline_line, last_align) misaligned_col = align.col and align.col != arg.col misaligned_line = align.line and align.line != arg.line def _offset_with_space(line, align_col, arg_col): """Insert spaces as required to place arg at align_col.""" offset = max(0, arg_col - align_col) spaces = max(0, align_col - arg_col) return util.replace_word(line, arg_col - 1 - offset, " " * offset, " " * spaces) if ((misaligned_col or not align.col) and (misaligned_line or not align.line)): msg_parts_append_table = [ (lambda: misaligned_line is not None, "line {0}".format(align.line)), (lambda: misaligned_col is not None, "col {0}".format(align.col)), (lambda: align.col != baseline_col, "col {0}".format(baseline_col)) ] replacement = None msg_parts = [msg for c, msg in msg_parts_append_table if c()] if misaligned_col and align.col == baseline_col: replacement = _offset_with_space(contents[arg.line - 1], align.col, arg.col) msg = ("Argument {0} must fall on any of: " "{1}".format(arg.contents, ", ".join(msg_parts))) return align, LinterFailure(msg, arg.line, replacement) return align, None