def grade_student_file(self, filename): extension = filename.split('.')[-1] if extension not in ['h', 'cpp']: sys.stderr.write('Failed to parse {}: incorrect file type.\n'.format(filename)) return data = safely_open(filename) if data: self.reset_for_new_file(filename) raw_data = deepcopy(data) RemoveMultiLineComments(filename, data, '') clean_lines = CleansedLines(data) clean_code = clean_lines.lines for self.current_line_num, code in enumerate(clean_code): for function in self.single_line_checks: function(self, code) for function in self.multi_line_checks: function(self, clean_lines) # COMMENT CHECKS #TODO for self.current_line_num, text in enumerate(raw_data): if self.config.get('COMMENT_CHECKS', 'line_width').lower() == 'yes': getattr(comment_checks, 'check_line_width')(self, text) if check_if_function(text): if self.config.get('COMMENT_CHECKS', 'missing_rme').lower() == 'yes': getattr(comment_checks, 'check_missing_rme')(self, raw_data) if self.config.get('COMMENT_CHECKS', 'min_comments').lower() == 'yes': getattr(comment_checks, 'check_min_comments')(self, raw_data, clean_code) for function in self.misc_checks: function(self) self.error_tracker[filename].sort() self.file_has_a_main[filename] = not self.outside_main
def check_brace_consistency(self, clean_lines): code = clean_lines.lines[self.current_line_num] stripped_code = code.strip() function = check_if_function(code) if_statement = re.search(r'^if\s*\(\s*', stripped_code) else_if_statement = re.search(r'^else\s*\(', code) else_statement = re.search(r'^else\s+', code) switch_statement = re.search(r'^switch\s*\(', stripped_code) #TODO: Clean this line up if function or if_statement or else_statement or switch_statement: if function and clean_lines.lines[self.current_line_num + 1].find('{') != -1 or\ else_if_statement and clean_lines.lines[self.current_line_num + 1].find('{') != -1 or\ else_statement and clean_lines.lines[self.current_line_num + 1].find('{') != -1 or\ switch_statement and clean_lines.lines[self.current_line_num + 1].find('{') != -1 or\ if_statement and clean_lines.lines[self.current_line_num + 1].find('{') != -1: self.set_egyptian_style(False) elif function and code.find('{') != -1 or \ else_if_statement and code.find('{') != -1 or\ else_statement and code.find('{') != -1 or\ switch_statement and code.find('{') != -1 or\ if_statement and code.find('{') != -1: self.set_egyptian_style(True) elif not self.is_outside_main(): self.add_error("BRACES_ERROR") #if both of these are true, they are not consistent, therefore error. if self.notEgyptian: if self.egyptian: self.add_error("BRACES_ERROR")
def check_non_const_global(self, code): if re.search(r'int main', code): self.set_inside_main() if self.is_outside_main(): function = check_if_function(code) variable = re.search(r'^\s*(int|string|char|bool)\s+', code) if not function and variable: self.add_error("GLOBAL_VARIABLE")
def check_function_def_above_main(self, code): prototype = check_if_function_prototype(code) function = check_if_function(code) inside = Literal("int main") if len(inside.searchString(code)): return elif function and not prototype and self.outside_main: function_regex = re.compile("^\s*(\w+)\s+(\w+)") match = function_regex.search(code) function_name = match.group(2) if match else "NOT_FOUND" self.add_error(label="DEFINITION_ABOVE_MAIN", data={'function': function_name})
def check_int_for_bool(self, code): if check_if_function(code): function_regex = re.compile("^\s*(\w+)\s+(\w+)") match = function_regex.search(code) if match: self.current_function = (match.group(1), match.group(2)) current_function = getattr(self, "current_function", ("", "")) return_regex = re.compile("\s*return\s+(\w+)") match = return_regex.search(code) if match and match.group(1).isdigit() and current_function[0] == "bool": self.add_error(label="INT_FOR_BOOL")
def check_int_for_bool(self, code): if check_if_function(code): function_regex = re.compile(r"^\s*(\w+)\s+(\w+)") match = function_regex.search(code) if match: self.current_function = (match.group(1), match.group(2)) current_function = getattr(self, "current_function", ("", "")) return_regex = re.compile(r"\s*return\s+(\w+)") match = return_regex.search(code) if match and match.group(1).isdigit() and current_function[0] == "bool": self.add_error(label="INT_FOR_BOOL")
def check_non_const_global(self, code): inside = Literal("int main") if len(inside.searchString(code)): self.outside_main = False if self.outside_main: function = check_if_function(code) variables = variables = re.compile("^(?:\w|_)+\s+(?:\w|_|\[|\])+\s*=\s*.+;") keywords = re.compile("^\s*(?:using|class|struct)") constants = re.compile("^\s*(?:static\s+)?const") if not function and variables.search(code) and \ not keywords.search(code) and \ not constants.search(code): self.add_error(label="NON_CONST_GLOBAL")
def check_non_const_global(self, code): inside = Literal("int main") if len(inside.searchString(code)): self.outside_main = False elif self.outside_main: function = check_if_function(code) variables = variables = re.compile( r"^(?:\w|_)+\s+(?:\w|_|\[|\])+\s*=\s*.+;") keywords = re.compile(r"^\s*(?:using|class|struct)") constants = re.compile(r"^\s*(?:static\s+)?const") if not function and variables.search(code) and \ not keywords.search(code) and \ not constants.search(code): self.add_error(label="NON_CONST_GLOBAL")
def check_function_block_indentation(self, clean_lines, operator_space_tracker): tab_size = 4 code = clean_lines.lines[self.current_line_num] stripped_code = code.strip() function = check_if_function(code) if_statement = re.search(r'^if\s*\(\s*', stripped_code) else_if_statement = re.search(r'^else\s*\(', code) else_statement = re.search(r'^else\s+', code) switch_statement = re.search(r'^switch\s*\(', stripped_code) indentation = re.search(r'^( *)\S', code) if indentation: indentation = indentation.group() indentation_size = len(indentation) - len(indentation.strip()) else: return if function or self.is_outside_main(): if indentation_size != 0: self.add_error("INDENTATION_ERROR") if self.is_outside_main(): return #TODO: Need to check indentation ON the same line as the function still if function: #if not egyptian style if code.find('{') == -1: second_line = clean_lines.lines[self.current_line_num + 1] if code.find('{'): temp_line_num = self.current_line_num + 1 data_structure_tracker = DataStructureTracker() data_structure_tracker.brace_stack.append('{') self.process_current_blocks_indentation(indentation, tab_size, code, clean_lines, data_structure_tracker, temp_line_num) else: #TODO Figure out what it means to not have braces in the right place pass else: temp_line_num = self.current_line_num data_structure_tracker = DataStructureTracker() data_structure_tracker.brace_stack.append('{') self.process_current_blocks_indentation(indentation, tab_size, code, clean_lines, data_structure_tracker, temp_line_num) else: return
def check_brace_consistency(self, clean_lines): code = clean_lines.lines[self.current_line_num] stripped_code = code.strip() function = check_if_function(code) if_statement = re.search(r'^if\s*\(\s*', stripped_code) else_if_statement = re.search(r'^else\s*\(', code) else_statement = re.search(r'^else\s+', code) switch_statement = re.search(r'^switch\s*\(', stripped_code) indentation = re.search(r'^( *)\S', code) if indentation: indentation = indentation.group() indentation_size = len(indentation) - len(indentation.strip()) else: indentation_size = 0 current = self.current_line_num if function or if_statement or else_statement or switch_statement: try: if function or \ else_if_statement or\ else_statement or\ switch_statement or\ if_statement: if deep_egyptian_check(clean_lines.lines, indentation_size, current): self.egyptian = True else: self.not_egyptian = True elif not self.outside_main: if not self.braces_error: self.add_error(label="BRACE_CONSISTENCY") self.braces_error = True if self.not_egyptian and self.egyptian and not self.braces_error: self.add_error(label="BRACE_CONSISTENCY") self.braces_error = True #if both of these are true, they are not consistent, therefore error. if self.not_egyptian: if self.egyptian and not self.braces_error: self.add_error(label="BRACE_CONSISTENCY") self.braces_error = True except IndexError: # cannot access next line of end of file, rubric properties don't matter return
def check_operator_spacing(self, code): # TODO: Temporary fix to ignore & and * operators in function params if check_if_function(code) or check_if_function_prototype(code) or\ '#include' in code: return # Check normal operators # account for *=, %=, /=, +=, -= indexes = [] indexes = findOccurences(code, '+') + \ findOccurences(code, '-') + \ findOccurences(code, '%') + \ findOccurences(code, '*') + \ findOccurences(code, '/') + \ findOccurences(code, '!') + \ findOccurences(code, '>') + \ findOccurences(code, '<') + \ findOccurences(code, '=') + \ findOccurences(code, '&') + \ findOccurences(code, '|') indexes.sort() # Force compound operator indexes to be correctly ordered skip_next = False for operator_index in indexes: if skip_next: # skip second operator in compound/increment/decrement operator skip_next = False continue if skip_operator(code, operator_index): skip_next = True elif is_compound_operator(code, operator_index): # Always use front char in compound operators, therefore need to skip second char skip_next = True if not operator_helper(True, code, operator_index): self.add_error(label='OPERATOR_SPACING', column=operator_index, data={'operator': code[operator_index:operator_index + 2]}) else: # TODO: Add code checking for unary + and - operators if code[operator_index] == '!': index = operator_index - 1 # Only check for spacing in front of ! (NOT) operator if code[index]: if code[index] not in [' ', '\r', '\n', '(']: self.add_error(label='OPERATOR_SPACING', column=operator_index, data={'operator': code[operator_index]}) elif not operator_helper(False, code, operator_index): self.add_error(label='OPERATOR_SPACING', column=operator_index, data={'operator': code[operator_index]})
def check_brace_consistency(self, clean_lines): code = clean_lines.lines[self.current_line_num] stripped_code = code.strip() function = check_if_function(code) if_statement = re.search(r'^if\s*\(\s*', stripped_code) else_if_statement = re.search(r'^else\s*\(', code) else_statement = re.search(r'^else\s+', code) switch_statement = re.search(r'^switch\s*\(', stripped_code) #TODO: Clean this line up if function or if_statement or else_statement or switch_statement: try: if function and code.find('{') != -1 or \ else_if_statement and code.find('{') != -1 or\ else_statement and code.find('{') != -1 or\ switch_statement and code.find('{') != -1 or\ if_statement and code.find('{') != -1: self.egyptian = True elif function and clean_lines.lines[self.current_line_num + 1].find('{') != -1 or\ else_if_statement and clean_lines.lines[self.current_line_num + 1].find('{') != -1 or\ else_statement and clean_lines.lines[self.current_line_num + 1].find('{') != -1 or\ switch_statement and clean_lines.lines[self.current_line_num + 1].find('{') != -1 or\ if_statement and clean_lines.lines[self.current_line_num + 1].find('{') != -1: self.not_egyptian = True elif not self.outside_main: if not self.braces_error: self.add_error(label="BRACE_CONSISTENCY") self.braces_error = True #if both of these are true, they are not consistent, therefore error. if self.not_egyptian: if self.egyptian and not self.braces_error: self.add_error(label="BRACE_CONSISTENCY") self.braces_error = True except IndexError: # cannot access next line of end of file, rubric properties don't matter return
def grade_student_file(self, filename): extension = filename.split('.')[-1] if extension not in ['h', 'cpp']: sys.stderr.write('Failed to parse {}: incorrect file type.\n'.format(filename)) return data = safely_open(filename) if data: self.reset_for_new_file(filename) raw_data = deepcopy(data) RemoveMultiLineComments(filename, data, '') clean_lines = CleansedLines(data) clean_code = clean_lines.lines for self.current_line_num, code in enumerate(clean_code): code = erase_string(code) # TODO: Improve the check for 'Is this a #include line?' if '#include' in code: continue if code.find('\t') != -1: self.add_error(label='USING_TABS') break if self.current_line_num == 114: print "stop" for function in self.single_line_checks: function(self, code) for function in self.multi_line_checks: function(self, clean_lines) # COMMENT CHECKS #TODO for self.current_line_num, text in enumerate(raw_data): if self.config.get('COMMENT_CHECKS', 'line_width').lower() == 'yes': getattr(comment_checks, 'check_line_width')(self, text) if check_if_function(text): if self.config.get('COMMENT_CHECKS', 'missing_rme').lower() == 'yes': getattr(comment_checks, 'check_missing_rme')(self, raw_data) if self.config.get('COMMENT_CHECKS', 'min_comments').lower() == 'yes': getattr(comment_checks, 'check_min_comments')(self, raw_data, clean_code) for function in self.misc_checks: function(self) self.error_tracker[filename].sort() self.file_has_a_main[filename] = not self.outside_main if not self.file_has_a_main[filename]: self.remove_def_above_main_errors(filename)
def grade_student_file(self, filename, original_filename): extension = filename.split('.')[-1] if extension not in ['h', 'cpp']: sys.stderr.write( 'Failed to parse {}: incorrect file type.\n'.format(filename)) return data = safely_open(filename) if data: self.reset_for_new_file(filename) raw_data = deepcopy(data) RemoveMultiLineComments(filename, data, '') clean_lines = CleansedLines(data) clean_code = clean_lines.elided for function in self.filename_checks: function(self, original_filename) for self.current_line_num, code in enumerate(clean_code): code = erase_string(code) if self.config.get('SINGLE_LINE_CHECKS', 'tab_type').lower( ) == 'soft' and code.find('\t') != -1: self.add_error(label='USING_TABS') break for function in self.single_line_checks: function(self, code) for function in self.multi_line_checks: function(self, clean_lines) # COMMENT CHECKS #TODO for self.current_line_num, text in enumerate(raw_data): if self.config.get('COMMENT_CHECKS', 'line_width').lower() == 'yes': getattr(comment_checks, 'check_line_width')(self, text) if check_if_function(text): if self.config.get('COMMENT_CHECKS', 'missing_rme').lower() == 'yes': getattr(comment_checks, 'check_missing_rme')(self, raw_data) if check_if_function_prototype(text): if self.config.get( 'COMMENT_CHECKS', 'missing_prototype_comments').lower() == 'yes': getattr(comment_checks, 'check_missing_prototype_comments')( self, clean_lines.lines_without_raw_strings) if self.config.get('COMMENT_CHECKS', 'min_comments').lower() == 'yes': getattr(comment_checks, 'check_min_comments')(self, raw_data, clean_code) if self.config.get('COMMENT_CHECKS', 'missing_type_comments').lower() == 'yes': getattr(comment_checks, 'check_missing_type_comments')( self, clean_lines.lines_without_raw_strings) if self.config.get('COMMENT_CHECKS', 'comment_spacing').lower() == 'yes': getattr(comment_checks, 'check_comment_spacing')( self, clean_lines.lines_without_raw_strings) for function in self.misc_checks: function(self) # Run checks directly from cpplint # Filters are configurable from the CPPLINT.cfg file self.cpplint_tests(filename) # Organize the error list for this file self.error_tracker[filename].sort() self.file_has_a_main[filename] = not self.outside_main if not self.file_has_a_main[filename]: self.remove_error_by_type(filename, 'DEFINITION_ABOVE_MAIN') # This is a bit of a hack, to make the setting work. # TODO: make a multi-line check for unnecessary breaks. if not self.detect_unnecessary_break: self.remove_error_by_type(filename, 'UNNECESSARY_BREAK')
def check_block_indentation(self, clean_lines): #TODO: Load from config file? tab_size = 4 code = clean_lines.lines[self.current_line_num] print code if check_if_struct_or_class(code): self.global_in_object = True if self.global_in_object and code.find('{') != -1: self.add_global_brace('{') elif self.global_in_object and code.find('}') != -1: self.pop_global_brace() function = check_if_function(code) struct_or_class = check_if_struct_or_class(code) indentation = re.search(r'^( *)\S', code) if indentation: indentation = indentation.group() indentation_size = len(indentation) - len(indentation.strip()) else: return if function and indentation_size != 0 and not self.global_in_object and code.find('else if') == -1: data = {'expected': 0, 'found': indentation_size} self.add_error(label="BLOCK_INDENTATION", data=data) if function: self.current_line_num = find_function_end(clean_lines.lines, self.current_line_num) if (function and not self.outside_main) or struct_or_class: #if not egyptian style if code.find('{') == -1: if code.find('{'): temp_line_num = self.current_line_num + 1 data_structure_tracker = DataStructureTracker() data_structure_tracker.brace_stack.append('{') if check_if_struct_or_class(code): data_structure_tracker.in_class_or_struct = True if self.global_in_object: self.add_global_brace('{') data_structure_tracker.add_object_brace('{') results = indent_helper(indentation, tab_size, clean_lines, data_structure_tracker, temp_line_num) for error in results: self.add_error(**error) else: #TODO Figure out what it means to not have braces in the right place pass else: temp_line_num = self.current_line_num data_structure_tracker = DataStructureTracker() if check_if_struct_or_class(code): data_structure_tracker.add_object_brace("{") data_structure_tracker.in_class_or_struct = True data_structure_tracker.brace_stack.append('{') results = indent_helper(indentation, tab_size, clean_lines, data_structure_tracker, temp_line_num) for error in results: self.add_error(**error) else: return
def check_identifier_case(self, code): if code.isspace(): return # check if the first char is lower-case alpha or '_' lowercase = re.compile(r"(?:^|\s+)(?:class|struct|enum)\s+(?:[a-z]|_)\w*") bad_naming = lowercase.search(code) if bad_naming: result = bad_naming.group(0).split() expected = str(result[1]) if len(expected) == 1: expected = 'A Descriptive Name' if (expected and expected[0] == '_'): # Remove leading _ from expected input expected = expected[1:] self.add_error(label="IDENTIFIER_CASE", data={ "style": "camel case (starting with a capital letter)", "type": result[0], "expected": expected[0].capitalize() + (expected[1:] if len(expected) > 1 else ''), "found": str(result[1]) }) return # Make sure the first letter of non-const variable names are lowercase. uppercase = re.compile( r'(?:^|\s+)(?<!const\s)\s*(?:void|bool|char|short|long|int|float|double|string|std::string|auto|ifstream|ofstream)[\*\&\s]+(?:[\w_]+\:\:)*((?:[A-Z]|_)\w+)\s*[,\[\(\)\{;=]' ) bad_naming = uppercase.search(code) uppercase_unsigned = re.compile( r'(?:^|\s+)const\s+(?:signed|unsigned)\s+(?:bool|char|short|long|int|float|double)[\*\&\s]+(?:[\w_]+\:\:)*((?:[A-Z]|_)\w+)\s*[,\(\)\{;=]' ) bad_underscore = re.search( r'(?:^|\s+)(?<!const\s)\s*(?:void|bool|char|short|long|int|float|double|string|std::string|auto|ifstream|ofstream)[\*\&\s]+(?:[\w_]+\:\:)*((?:[\w_])\w*_[\w_]*)\s*[,\[\(\)\{;=]', code) if (bad_naming and not uppercase_unsigned.search(code)) or bad_underscore: result = bad_naming.group(1) if bad_naming else bad_underscore.group(1) # Create an expected constant name where underscores are converted to camel case try: expected = '' var_length = len(result) cap_next = False for i, ch in enumerate(result): if ch == '_': cap_next = True elif cap_next: expected += ch.upper() cap_next = False else: expected += ch if (expected and expected[0] == '_'): # Remove leading _ from expected input expected = expected[1:] self.add_error(label="IDENTIFIER_CASE", data={ "type": 'non-constant variables or function', "style": "camel case (starting with a lowercase letter)", "expected": ((expected[:1].lower() + expected[1:]) if expected else '') if len(expected) > 1 else "a descriptive name", "found": str(result) }) except IndexError: # probably means that this is an std:: parameter, they don't need to be capitalized. print("Something weird happened in check_identifier_case with '", code, "'.") return return # Make sure const variables are all caps if not check_if_function_prototype(code) and not check_if_function(code): const_var = re.compile( r"(?:^|\s+)const\s+(?:void|bool|char|short|long|int|float|double|string|std::string|auto)\s*[\*\&\s]*\s*(?:\w|_)\w+" ) const_var = const_var.search(code) unsigned_const_var = re.search( r'(?:^|\s+)const\s+(?:signed|unsigned)\s+(?:char|short|long|long\s+long|int)\s*[\*\&\s]*\s*(?:\w|_)\w+', code) if const_var or unsigned_const_var: if const_var: const_var = str(const_var.group(0).split()[-1]) else: const_var = str(unsigned_const_var.group(0).split()[-1]) # Create an expected constant name where camel case is converted to all caps with underscores expected = '' var_length = len(const_var) for i, ch in enumerate(reversed(const_var)): if ch.isupper( ) and ch != '_' and i < var_length - 1 and i > 0 and const_var[ var_length - i - 2] != '_': expected = '_' + ch + expected else: expected = ch.upper() + expected if (expected and expected[0] == '_'): # Remove leading _ from expected input expected = expected[1:] if not const_var.isupper(): self.add_error( label="IDENTIFIER_CASE", data={ "type": 'constant variable', "style": "all caps (separating words with an underscore)", "expected": expected if len(expected) > 1 else "a descriptive name", "found": const_var })