def update_location(self, location): """ Updates both the fields and location attribute. TODO: Handle less information intelligently. """ if isinstance(location, int): location = Location(location) self.location = location self.fields['location'] = location
def condition(self): """ Use find_matches to check number of occurrences. """ name = self.fields['name'] calls = find_function_calls(name, root=self.fields['root']) if calls: self.update_location(Location.from_ast(calls[-1])) return self._check_usage('call_count', calls)
def __init__(self, exception, context, traceback, location, **kwargs): report = kwargs.get('report', MAIN_REPORT) exception_name = get_exception_name(exception) exception_name_proper = add_indefinite_article(exception_name) exception_message = str(exception).capitalize() if type(exception) not in EXCEPTION_FF_MAP: title = exception_name else: title = EXCEPTION_FF_MAP[type(exception)].title location = Location(location) traceback_stack = traceback.build_traceback() traceback_message = traceback.format_traceback(traceback_stack, report.format) context_message = format_contexts(context, report.format) fields = {'exception': exception, 'exception_name': exception_name_proper, 'exception_message': report.format.exception(exception_message), 'location': location, 'traceback': traceback, 'traceback_stack': traceback_stack, 'traceback_message': traceback_message, 'context': context, 'context_message': context_message} super().__init__(fields=fields, title=title, location=location, **kwargs)
def __init__(self, line, filename, code, col_offset, exception, exc_info, **kwargs): report = kwargs.get('report', MAIN_REPORT) files = report.submission.get_files_lines() if filename not in files: files[filename] = code.split("\n") if report.submission is not None: lines = report.submission.get_lines() line_offsets = report.submission.line_offsets else: lines = code.split("\n") line_offsets = {} line_offset = line_offsets.get(filename, 0) traceback = ExpandedTraceback(exception, exc_info, False, [report.submission.instructor_file], line_offsets, [filename], lines, files) traceback_stack = traceback.build_traceback() traceback_message = traceback.format_traceback(traceback_stack, report.format) line_offset = line_offsets.get(filename, 0) fields = { 'lineno': line + line_offset, 'filename': filename, 'offset': col_offset, 'exception': exception, 'traceback': traceback, 'traceback_stack': traceback_stack, 'traceback_message': traceback_message } location = Location(line=line + line_offset, col=col_offset, filename=filename) super().__init__(fields=fields, location=location, **kwargs)
def condition(self): """ Use find_all to check number of occurrences. """ name = self.fields['name'] self.fields['name_message'] = AST_NODE_NAMES.get(name, name) uses = list(self.fields['root'].find_all(name)) if uses: self.update_location(Location.from_ast(uses[-1])) return self._check_usage('use_count', uses)
def condition(self): """ Use find_matches to check number of occurrences. """ name = self.fields['name'] self.fields['name_message'] = self.report.format.python_expression( name) uses = list(find_operation(name, root=self.fields['root'])) if uses: self.update_location(Location.from_ast(uses[-1])) return self._check_usage('use_count', uses)
def match_location(self, first=True): """ Determines the first (or last) location of this match. Args: first (bool): Whether to use the first found match's location, or the last one. Returns: pedal.core.Location: The the first location of this match. """ finder = min if first else max values = [ v.lineno for v in self.mappings.values() if v.lineno is not None ] if not values: return Location(None) else: return Location(finder(values))
def locate(self, node: ast.AST = None): """ Return a dictionary representing the current location within the AST. Returns: Location: The line and column of the current or given node. """ if node is None: if self.node_chain: node = self.node_chain[-1] else: node = self.final_node return Location(node.lineno+self.line_offset, col=node.col_offset)
def ensure_function(name, arity=None, parameters=None, returns=None, root=None, compliment=False, **kwargs): """ Checks that the function exists and has the right signature. """ report = kwargs.get('report', MAIN_REPORT) root = root or parse_program(report=report) defs = root.find_all('FunctionDef') defs = [a_def for a_def in defs if a_def._name == name] if not defs: return missing_function(name, **kwargs) if len(defs) > 1: lines = [Location.from_ast(a_def) for a_def in defs] return duplicate_function_definition(name, lines, **kwargs) definition = defs[0] # Actual logic # 1.2.1 'arity' style - simply checks number of parameters if arity is not None or parameters is not None: expected_arity = arity if arity is not None else len(parameters) actual_arity = len(definition.args.args) if actual_arity < expected_arity: return too_few_parameters(name, actual_arity, expected_arity, **kwargs) elif actual_arity > expected_arity: return too_many_parameters(name, actual_arity, expected_arity, **kwargs) # 1.2.2 'parameters' style - checks each parameter's name and type if parameters is not None: actual_parameters = definition.args.args for expected_parameter, actual_parameter in zip( parameters, actual_parameters): expected_parameter_type = normalize_type(expected_parameter) actual_parameter_name = (actual_parameter.id if actual_parameter.id is not None else actual_parameter.arg) if actual_parameter.annotation is None: return missing_parameter_type(name, actual_parameter_name, expected_parameter_type, **kwargs) try: actual_parameter_type = normalize_type( actual_parameter.annotation.ast_node) except ValueError as e: return invalid_parameter_type(name, actual_parameter_name, actual_parameter.annotation, expected_parameter_type, **kwargs) if not are_types_equal(actual_parameter_type, expected_parameter_type): return wrong_parameter_type(name, actual_parameter_name, actual_parameter_type, expected_parameter_type, **kwargs) # 1.2.3. 'returns' style - checks the return type explicitly if returns is not None: expected_returns = normalize_type(returns) if definition.returns is None: return missing_return_type(name, expected_returns, **kwargs) try: actual_returns = normalize_type(definition.returns.ast_node) except ValueError as e: return invalid_return_type(name, definition.returns, expected_returns, **kwargs) if not are_types_equal(actual_returns, expected_returns): return wrong_return_type(name, actual_returns, expected_returns, **kwargs) # Alternatively, returns positive FF? if compliment: if isinstance(compliment, str): core_compliment(compliment, label="function_defined", **kwargs) elif compliment is True: core_compliment(f"Defined {name}", label="function_defined", **kwargs) elif kwargs.get("score"): give_partial(kwargs.pop("score"), label="function_defined", **kwargs) return None
def __init__(self, *args, label=None, category=None, justification=None, fields=None, field_names=None, kind=None, title=None, message=None, message_template=None, else_message=None, priority=None, valence=None, location=None, score=None, correct=None, muted=None, unscored=None, tool=None, version=None, author=None, tags=None, parent=None, report=MAIN_REPORT, delay_condition=False, activate=True, **kwargs): # Internal data self.report = report # Metadata if label is not None: self.label = label else: self.label = self.__class__.__name__ # Condition if category is not None: self.category = category if justification is not None: self.justification = justification self.activate = activate # Response if kind is not None: self.kind = kind if priority is not None: self.priority = priority if valence is not None: self.valence = valence # Presentation if fields is not None: self.fields = fields else: self.fields = {} if self.constant_fields is not None: self.fields.update(self.constant_fields) if field_names is not None: self.field_names = field_names if title is not None: self.title = title elif self.title is None: self.title = label if message is not None: self.message = message if message_template is not None: self.message_template = message_template if else_message is not None: self.else_message = else_message # Locations if isinstance(location, int): location = Location(location) # TODO: Handle tuples (Line, Col) and (Filename, Line, Col), and # possibly lists thereof if location is not None: self.location = location # Result if score is not None: self.score = score if correct is not None: self.correct = correct if muted is not None: self.muted = muted if unscored is not None: self.unscored = unscored # Metadata if tool is not None: self.tool = tool if version is not None: self.version = version if author is not None: self.author = author if tags is not None: self.tags = tags # Organizational if parent is not None: self.parent = parent if self.parent is None: # Might inherit from Report's current group self.parent = self.report.get_current_group() if self.field_names is not None: for field_name in self.field_names: self.fields[field_name] = kwargs.get(field_name) for key, value in kwargs.items(): self.fields[key] = value if 'location' not in self.fields and self.location is not None: self.fields['location'] = self.location # Potentially delay the condition check self._stored_args = args self._stored_kwargs = kwargs if delay_condition: self._met_condition = False self._status = FeedbackStatus.DELAYED else: self._handle_condition()
def locate(self): """ Returns: pedal.core.Location: The location of this node. """ return Location(self.astNode.lineno, col=self.astNode.col_offset)