def examine_fortran(self, subject: FortranSource): issues: List[Issue] = [] candidates: List[Fortran2003.StmtBase] = [] # Component variables candidates.extend(subject.find_all( Fortran2003.Data_Component_Def_Stmt)) # Variable declaration candidates.extend(subject.find_all(Fortran2003.Type_Declaration_Stmt)) for candidate in candidates: problem = 'Use of dimension attribute for {name}.' attributes = candidate.items[1] if attributes is None: continue cannon_attr = list( str(item).lower().replace(' ', '') for item in attributes.items) argument_def = 'intent(in)' in cannon_attr \ or 'intent(out)' in cannon_attr \ or 'intent(inout)' in cannon_attr if any(x.startswith('dimension') for x in cannon_attr): for variable in candidate.items[2].items: name = str(variable.items[0]) message = problem.format(name=name) line_number = candidate.item.span[0] issues.append(Issue(message, line=line_number)) issues.sort(key=lambda x: (x.filename, x.line, x.description)) return issues
def examine_fortran(self, subject: FortranSource) -> List[Issue]: issues = [] for exit in subject.find_all(Fortran2003.Exit_Stmt): if exit.items[1] is None: issues.append( Issue( 'Usage of "exit" without label indicating ' 'which "do" construct is being exited ' 'from.', line=exit.item.span[0])) # Above doesn't catch exits in inline if statements for statement in subject.find_all(Fortran2003.If_Stmt): action = statement.items[1] if type(action ) == Fortran2003.Exit_Stmt and action.items[1] is None: issues.append( Issue( 'Usage of "exit" without label indicating ' 'which "do" construct is being exited ' 'from.', line=statement.item.span[0])) return issues
def examine_fortran(self, subject: FortranSource) -> List[Issue]: issues = [] scope_units = subject.path('Program_Unit') scope_units.extend( subject.path([ 'Main_Program', 'Internal_Subprogram_Part', 'Internal_Subprogram' ])) scope_units.extend( subject.path( ['Module', 'Module_Subprogram_Part', 'Module_Subprogram'])) for scope in scope_units: scope_statement = subject.get_first_statement(root=scope) implication = subject.path( ['Specification_Part', 'Implicit_Part', 'Implicit_Stmt'], root=scope.content[1:]) if not implication: nature = MissingImplicit._NATURE_MAP[scope_statement.__class__] name = scope_statement.items[1] description = "{thing} '{name}' is missing " \ + "an implicit statement" description = description.format(thing=nature, name=name) issues.append( Issue(description, line=scope_statement.item.span[0])) return issues
def test_path_string(self, path_case): ''' Checks that matching a path to the source works. ''' # pylint: disable=no-self-use reader = SourceStringReader(path_case[0]) unit_under_test = FortranSource(reader) result = unit_under_test.path('/'.join(path_case[1])) assert [obj.__class__.__name__ for obj in result] == path_case[2]
def test_path_string(self, path_case: Tuple[str, List[str], List[str]]) \ -> None: """ Checks that matching a path to the source works. """ reader = SourceStringReader(path_case[0]) unit_under_test = FortranSource(reader) result = unit_under_test.path('/'.join(path_case[1])) assert [obj.__class__.__name__ for obj in result] == path_case[2]
def test_find_all(self) -> None: """ Checks that finding all occurrences of a source part works. """ reader = SourceStringReader(self._MULTI_PROC_MODULE) unit_under_test = FortranSource(reader) wanted = fparser.two.Fortran2003.Module_Subprogram result = unit_under_test.find_all(wanted) assert str(next(result).content[0].items[1]) == 'one' assert str(next(result).content[0].items[1]) == 'two' with pytest.raises(StopIteration): next(result)
def examine(self, subject: FortranSource) -> List[Issue]: issues = super(FortranRule, self).examine(subject) if not isinstance(subject, FortranSource): description = 'Non-Fortran source passed to a Fortran rule' raise Exception(description) if not subject.get_tree(): description = "Unable to perform {} as source didn't parse: {}" issues.append(Issue(description.format(self.__class__.__name__, subject.get_tree_error()))) return issues issues.extend(self.examine_fortran(subject)) return issues
def test_exit_labels(self, module_name: str) -> None: """ Checks that the rule reports missing "implicit" labels correctly """ template = ''' program test contains function function1() use {module_name} end function function1 function function2() use, intrinsic :: {module_name} end function function2 end program test ''' expectation: List[str] = [] message = '{line}: Usage of intrinsic module "{module_name}" without '\ '"intrinsic" clause.' if module_name.lower() in [ "iso_c_binding", "iso_fortran_env", "ieee_exceptions", "ieee_arithmetic", "ieee_features" ]: expectation.extend([ message.format(line=5, module_name=module_name), ]) text = template.format(module_name=module_name) print(text) # Shows up in failure reports, for debugging reader = SourceStringReader(text) source = FortranSource(reader) unit_under_test = IntrinsicModule() issues = unit_under_test.examine(source) issue_descriptions = [str(issue) for issue in issues] assert issue_descriptions == expectation
def test_implicit_double( self, containing_program_unit: Tuple[str, List[str]], subprogram_implicit: Tuple[str, List[str]], second_subprogram_implicit: Tuple[str, List[str]]) -> None: """ Checks all the permutations of two contained procedures. """ procedure = '\n'.join([subprogram_implicit[0], second_subprogram_implicit[0]]).strip() text = containing_program_unit[0].format(procedure=procedure) reader = SourceStringReader(text) source = FortranSource(reader) insert_point = containing_program_unit[0].find('{procedure}') insert_line = containing_program_unit[0].count('\n', 0, insert_point) + 1 first_len = subprogram_implicit[0].count('\n') if first_len > 0: first_len += 1 expectation = [] for thing in containing_program_unit[1]: expectation.append(f"1: {thing}") for thing in subprogram_implicit[1]: expectation.append(f"{insert_line}: {thing}") for thing in second_subprogram_implicit[1]: expectation.append(f"{insert_line + first_len}: {thing}") unit_under_test = stylist.fortran.MissingImplicit('none') issues = unit_under_test.examine(source) issue_descriptions = [str(issue) for issue in issues] assert issue_descriptions == expectation
class TestKindPattern: """ Test the checker of kind patterns. """ def test_passing(self): case = dedent(""" module passing_mod implicit none integer(medium_beef) :: global_int integer(salt_beef) :: global_first, global_second real(soft_cheese) :: global_float logical(who_cares) :: global_bool type some_type integer(bloody_beef) :: member_int integer(marbled_beef) :: member_int_1, member_int_2 real(blue_cheese) :: member_float real(green_cheese) :: member_float_1, member_float_2 logical(no_difference) :: member_bool contains procedure method end type some_type contains function thing(arg_int, & arg_float, & arg_float_1, & arg_float_2, & arg_bool) result(return_int) implicit none integer(rare_beef), intent(in) :: arg_int real(hard_cheese), intent(out) :: arg_float real(stinky_cheese), intent(in) :: arg_float_1, arg_float_2 logical(no_one_cares), intent(inout) :: arg_bool integer(well_done_beef) :: return_int end function thing function method(this, & marg_int, & marg_int_1, & marg_int_2, & marg_float, & marg_bool) result(return_float) implicit none integer(cremated_beef), intent(in) :: marg_int integer(shredded_beef), intent(out) :: marg_int_1, marg_int_2 real(sheep_cheese), intent(out) :: marg_float logical(sigh), intent(inout) :: marg_bool real(goat_cheese) :: return_float end function method end module passing_mod """) reader = SourceStringReader(case) source = FortranSource(reader) unit_under_test = KindPattern(integer=r'.+_beef', real=re.compile(r'.+_cheese')) issues = unit_under_test.examine(source) assert len(issues) == 0
def test_use(self, unit_type, unit_usage, procedure_usage, ignorance): """ Checks that the rule reports missing "use" clauses correctly. """ def prepare(line_number: int, params): usage = [] expectations = [] for details in params: line = None if details[0] is not None: line = 'use {0}'.format(details[0]) if details[1]: line += ', only : {0}'.format(', '.join(details[1])) line_number += 1 elif details[0] not in ignorance: message = f'{line_number}: Usage of "{details[0]}" ' \ f'without "only" clause.' expectations.append(message) line_number += 1 if line: usage.append(line) return usage, expectations, line_number text = '''{type} test {uusage} implicit none contains subroutine foo() {pusage} implicit none end subroutine foo end {type} test ''' unit_lines, unit_expects, last_line = prepare(2, unit_usage) if len(unit_lines) == 0: last_line += 1 proc_lines, proc_expects, _ = prepare(last_line + 3, procedure_usage) reader = SourceStringReader( text.format(type=unit_type, uusage='\n'.join(unit_lines), pusage='\n'.join(proc_lines))) source = FortranSource(reader) expectation = list(unit_expects) expectation.extend(proc_expects) print( text.format(type=unit_type, uusage='\n'.join(unit_lines), pusage='\n'.join(proc_lines))) print(expectation) if ignorance: unit_under_test = stylist.fortran.MissingOnly(ignore=ignorance) else: unit_under_test = stylist.fortran.MissingOnly() issues = unit_under_test.examine(source) issue_descriptions = [str(issue) for issue in issues] assert issue_descriptions == expectation
def test_simple(self, simple_source: Tuple[str, List[str]]) -> None: """ Ensures a given input source generates the correct issue list. """ unit_under_test = stylist.fortran.FortranCharacterset() reader = SourceStringReader(simple_source[0]) source = FortranSource(reader) issues = unit_under_test.examine(source) assert [str(issue) for issue in issues] == simple_source[1]
def test_simple(self, simple_source): # pylint: disable=no-self-use ''' Ensures a given input source generates the correct issue list. ''' unit_under_test = stylist.fortran.FortranCharacterset() reader = SourceStringReader(simple_source[0]) source = FortranSource(reader) issues = unit_under_test.examine(source) assert [str(issue) for issue in issues] == simple_source[1]
def test_constructor(self) -> None: """ Checks that the source file is correctly parsed on construction. """ inject = r"""! Test program program test implicit none write(6, '("Hello ", A)') 'world' end program test """ reader = SourceStringReader(inject) unit_under_test = FortranSource(reader) assert unit_under_test.get_text() == inject expected_string = """! Test program PROGRAM test IMPLICIT NONE WRITE(6, FMT = '("Hello ", A)') 'world' END PROGRAM test""" assert str(unit_under_test.get_tree()) == expected_string
def test(self): """ Ensures the test case produces exactly the issues in expectation """ reader = SourceStringReader(TEST_CASE) source = FortranSource(reader) unit_under_test = stylist.fortran.AutoCharArrayIntent() issues = unit_under_test.examine(source) strings = [str(issue) for issue in issues] assert strings == TEST_EXPECTATION
def test_examples(self, example_source): ''' Ensures trailing whitespace is detected on the correct lines. ''' unit_under_test = stylist.rule.TrailingWhitespace() reader = SourceStringReader(example_source[0]) source = FortranSource(reader) issues = unit_under_test.examine(source) assert ([str(issue) for issue in issues] == [ str(eln) + ': Found trailing white space' for eln in example_source[1] ])
def test_examination(self): """ Checks that all the rules in a style get a look at the program. """ rule_one = TestStyle._RuleHarness() rule_two = TestStyle._RuleHarness() unit_under_test = TestStyle._StyleHarness([rule_one, rule_two]) reader = SourceStringReader('module foo\nend module foo\n') source = FortranSource(reader) unit_under_test.check(source) assert rule_one.examined == ['module foo\nend module foo\n'] assert rule_two.examined == ['module foo\nend module foo\n']
def test_constructor(self): ''' Checks that the source file is correctly parsed on construction. ''' # pylint: disable=no-self-use inject = r'''! Test program program test implicit none write(6, '("Hello ", A)') 'world' end program test ''' reader = SourceStringReader(inject) unit_under_test = FortranSource(reader) assert unit_under_test.get_text() == inject expected_string = '''! Test program PROGRAM test IMPLICIT NONE WRITE(6, FMT = '("Hello ", A)') 'world' END PROGRAM test''' assert str(unit_under_test.get_tree()) == expected_string
def examine_fortran(self, subject: FortranSource) -> List[Issue]: issues: List[Issue] = [] candidates: List[Fortran2003.Base] = [] # Component variables candidates.extend(subject.find_all( Fortran2003.Data_Component_Def_Stmt)) # Component Procedure candidates.extend(subject.find_all( Fortran2003.Proc_Component_Def_Stmt)) # Procedure declaration candidates.extend( subject.find_all(Fortran2003.Procedure_Declaration_Stmt)) # Variable declaration candidates.extend(subject.find_all(Fortran2003.Type_Declaration_Stmt)) for candidate in candidates: if isinstance( candidate, # Is variable (Fortran2003.Data_Component_Def_Stmt, Fortran2003.Type_Declaration_Stmt)): type_spec: Fortran2003.Intrinsic_Type_Spec = candidate.items[0] kind_selector: Fortran2003.Kind_Selector = type_spec.items[1] if isinstance(kind_selector, Fortran2003.Kind_Selector): data_type: str = type_spec.items[0].lower() kind: str = kind_selector.string.strip('()') match = self._patterns[data_type].match(kind) if match is None: entity_declaration = candidate.items[2] message = self._ISSUE_TEMPLATE.format( type=data_type, kind=kind, name=entity_declaration, pattern=self._patterns[data_type].pattern) issues.append( Issue(message, line=candidate.item.span[0])) issues.sort(key=lambda x: (x.filename, x.line, x.description)) return issues
def examine(self, subject: FortranSource) -> List[Issue]: """ Base for rules which scruitinise the parse tree of Fortran source. :param subject: Source file to examine. :return: Issues found with the source. """ issues = [] if not isinstance(subject, FortranSource): description = 'Non-Fortran source passed to a Fortran rule' raise Exception(description) if not subject.get_tree(): description = "Unable to perform {} as source didn't parse: {}" issues.append( Issue( description.format(self.__class__.__name__, subject.get_tree_error()))) return issues issues.extend(self.examine_fortran(subject)) return issues
def examine_fortran(self, subject: FortranSource) -> List[Issue]: issues = [] for statement in subject.find_all(Fortran2003.Use_Stmt): module = statement.items[2] onlies = statement.items[4] if str(module).lower() not in self._ignore: if onlies is None: description = 'Usage of "{module}" without "only" clause.' issues.append(Issue(description.format(module=module), line=statement.item.span[0])) return issues
def examine_fortran(self, subject: FortranSource) -> List[Issue]: issues = [] for statement in subject.find_all(Fortran2003.Use_Stmt): module = statement.items[2] if str(module).lower() in self._INTRINSICS: nature = statement.items[0] if nature is None or nature.string.lower() != 'intrinsic': description = 'Usage of intrinsic module "{module}" ' \ 'without "intrinsic" clause.' issues.append( Issue(description.format(module=module), line=statement.item.span[0])) return issues
def examine(self, subject: FortranSource) -> List[Issue]: # pylint: disable=too-many-branches """ Examines the source code for none Fortran characters. This is complicated by the fact that the source must consist of only certain characters except comments and strings. These may contain anything. """ issues = super(FortranCharacterset, self).examine(subject) text = subject.get_text() index = 0 line = 1 state = 'code' while index < len(text): character = text[index] if state == 'code': if character == self._NEWLINE: line += 1 elif character == self._EXCLAMATION: state = 'comment' elif character == self._APOSTROPHY: state = 'apostraphystring' elif character == self._QUOTE: state = 'quotestring' elif character in self._FORTRAN_CHARACTERSET: pass else: description = "Found character {char} " \ + "not in Fortran character set" description = description.format(char=repr(character)) issues.append(Issue(description, line=line)) elif state == 'comment': if character == self._NEWLINE: line += 1 state = 'code' elif state == 'apostraphystring': if character == self._APOSTROPHY: state = 'code' elif state == 'quotestring': if character == self._QUOTE: state = 'code' else: raise Exception('Parser in unknown state: ' + state) index += 1 return issues
def examine_fortran(self, subject: FortranSource) -> List[Issue]: issues = [] # Collect all variable declarations declarations: List[Fortran2003.Type_Declaration_Stmt] = list( subject.find_all(Fortran2003.Type_Declaration_Stmt)) # keep only the ones in subroutines declarations = [ declaration for declaration in declarations if isinstance( declaration.parent.parent, Fortran2003.Subroutine_Subprogram) ] for declaration in declarations: type_spec = declaration.items[0] # If not a character, no concern if not type_spec.items[0] == "CHARACTER": continue param_value = type_spec.items[1] # This might be a length selector, if so get the param value if isinstance(param_value, Fortran2003.Length_Selector): param_value = param_value.items[1] # If not an auto length, no concern if not param_value.string == "*": continue attr_spec_list = declaration.items[1] # If no attributes specified, no concern if attr_spec_list is None: continue # Get intent attr and not other attributes intent_attr = None for item in attr_spec_list.items: if isinstance(item, Fortran2003.Intent_Attr_Spec): intent_attr = item break # If no intent, no conecern # Ensuring arguments specify intent should be enforced elsewhere if intent_attr is None: continue # Intent in, no conern if intent_attr.items[1].string == "IN": continue issues.append( Issue(self._message(declaration.items[2].string, intent_attr.items[1]), line=declaration.item.span[0])) return issues
def test_implicit(self, empty_program_unit_implicit): # pylint: disable=no-self-use ''' Checks all permutations of program units. ''' reader = SourceStringReader(empty_program_unit_implicit[0]) source = FortranSource(reader) expectation = [] for thing in empty_program_unit_implicit[1]: expectation.append(f"1: {thing}") unit_under_test = stylist.fortran.MissingImplicit('none') issues = unit_under_test.examine(source) issue_descriptions = [str(issue) for issue in issues] assert issue_descriptions == expectation
def examine(self, subject: FortranSource) -> List[Issue]: # pylint: disable=too-many-branches """ Examines the source code for none Fortran characters. This is complicated by the fact that the source must consist of only certain characters except comments and strings. These may contain anything. """ issues = super(FortranOldComparisons, self).examine(subject) text = subject.get_text() index = 0 line = 1 state = 'code' while index < len(text) - 4: characters = text[index:index + 4] if state == 'code': if characters[0] == self._NEWLINE: line += 1 elif characters[0] == self._EXCLAMATION: state = 'comment' elif characters[0] == self._APOSTROPHY: state = 'apostraphystring' elif characters[0] == self._QUOTE: state = 'quotestring' elif characters == ".eq." or characters == ".ne." or characters == ".gt." or characters == ".lt." or characters == ".ge." or characters == ".le.": description = "Found " + characters issues.append(Issue(description, line=line)) elif characters[0] in self._FORTRAN_CHARACTERSET: pass elif state == 'comment': if characters[0] == self._NEWLINE: line += 1 state = 'code' elif state == 'apostraphystring': if characters[0] == self._APOSTROPHY: state = 'code' elif state == 'quotestring': if characters[0] == self._QUOTE: state = 'code' else: raise Exception('Parser in unknown state: ' + state) index += 1 return issues
def test_implicit(self, empty_program_unit_implicit: Tuple[str, List[str]]) \ -> None: """ Checks all permutations of program units. """ reader = SourceStringReader(empty_program_unit_implicit[0]) source = FortranSource(reader) expectation = [] for thing in empty_program_unit_implicit[1]: expectation.append(f"1: {thing}") unit_under_test = stylist.fortran.MissingImplicit('none') issues = unit_under_test.examine(source) issue_descriptions = [str(issue) for issue in issues] assert issue_descriptions == expectation
def examine_fortran(self, subject: FortranSource) -> List[Issue]: issues = [] text = subject.get_text() index = 0 line = 1 state = 'code' while index < len(text): character = text[index] if character == self._NEWLINE: line += 1 elif character in self._FORTRAN_CHARACTERSET: pass else: description = "Found tab" issues.append(Issue(description, line=line)) index += 1 return issues
def examine_fortran(self, subject: FortranSource) -> List[Issue]: issues = [] for statement in subject.find_all(Fortran2003.Use_Stmt): print ("statement = ", statement) for statement in subject.find_all(Fortran2003.Data_Component_Def_Stmt): print ("statement = ", statement) for statement in subject.find_all(Fortran2003.Proc_Component_Def_Stmt): print ("statement = ", statement) for statement in subject.find_all(Fortran2003.Procedure_Declaration_Stmt): print ("statement = ", statement) for statement in subject.find_all(Fortran2003.Type_Declaration_Stmt): print ("statement = ", statement) print ("text = ", subject.get_text()) return issues
def examine(self, subject: FortranSource) -> List[Issue]: issues = [] text = subject.get_text() index = 0 line = 1 state = 'code' while index < len(text): character = text[index] if state == 'code': if character == self._NEWLINE: line += 1 elif character == self._EXCLAMATION: state = 'comment' elif character == self._APOSTROPHY: state = 'apostraphystring' elif character == self._QUOTE: state = 'quotestring' elif character in self._FORTRAN_CHARACTERSET: pass else: description = "Found character {char} " \ + "not in Fortran character set" description = description.format(char=repr(character)) issues.append(Issue(description, line=line)) elif state == 'comment': if character == self._NEWLINE: line += 1 state = 'code' elif state == 'apostraphystring': if character == self._APOSTROPHY: state = 'code' elif state == 'quotestring': if character == self._QUOTE: state = 'code' else: raise Exception('Parser in unknown state: ' + state) index += 1 return issues