Skip to content

Commit

Permalink
Kind pattern rule to support absent kind (#121)
Browse files Browse the repository at this point in the history
* Replace `setup.py` script with `stylist.toml` configuration file.

* Turned out the `.toml` file wasn't being used.

* Discovered mistake in pull request workflow.

* Since `setup.py` has been removed we can't check it with `mypy`

* Added Conda Forge badge to ReadMe.

* Constrain fParser version.

* Remove reference to setup.py from documentation.

* Allow pattern to be optional.

* Style and typing
  • Loading branch information
MatthewHambley committed Nov 27, 2023
1 parent 9cac327 commit 0ffccb3
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 17 deletions.
39 changes: 28 additions & 11 deletions source/stylist/fortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@
"""
import re
from abc import ABC, abstractmethod
from collections import defaultdict
from typing import (Container, Dict, List, Optional, Pattern, Sequence, Type,
Union)
Union, Any, cast)

import fparser.two.Fortran2003 as Fortran2003 # type: ignore
import fparser.two.Fortran2008 as Fortran2008 # type: ignore
Expand Down Expand Up @@ -539,8 +538,8 @@ class KindPattern(FortranRule):
" not fit the pattern /{pattern}/."

def __init__(self, *, # There are no positional arguments.
integer: Union[str, Pattern],
real: Union[str, Pattern]):
integer: Optional[Union[str, Pattern]] = None,
real: Optional[Union[str, Pattern]] = None):
"""
Patterns are set only for integer and real data types however Fortran
supports many more. Logical and Complex for example. For those cases a
Expand All @@ -549,13 +548,16 @@ def __init__(self, *, # There are no positional arguments.
:param integer: Regular expression which integer kinds must match.
:param real: Regular expression which real kinds must match.
"""
self._patterns: Dict[str, Pattern] \
= defaultdict(lambda: re.compile(r'.*'))
if isinstance(integer, str):
self._patterns: Dict[str, Optional[Pattern]] = {'logical': None}
if integer is None:
pass
elif isinstance(integer, str):
self._patterns['integer'] = re.compile(integer)
else:
self._patterns['integer'] = integer
if isinstance(real, str):
if real is None:
pass
elif isinstance(real, str):
self._patterns['real'] = re.compile(real)
else:
self._patterns['real'] = real
Expand All @@ -582,19 +584,34 @@ def examine_fortran(self, subject: FortranSource) -> List[Issue]:
(Fortran2003.Data_Component_Def_Stmt,
Fortran2003.Type_Declaration_Stmt)):
type_spec: Fortran2003.Intrinsic_Type_Spec = candidate.items[0]
data_type: str = type_spec.items[0].lower()

if self._patterns.get(data_type) is None:
continue
pattern = cast(Pattern[Any], self._patterns[data_type])

kind_selector: Fortran2003.Kind_Selector = type_spec.items[1]
if kind_selector is None:
entity_declaration = candidate.items[2]
message = self._ISSUE_TEMPLATE.format(
type=data_type,
kind='',
name=entity_declaration,
pattern=pattern.pattern)
issues.append(Issue(message,
line=_line(candidate)))
continue

if isinstance(kind_selector, Fortran2003.Kind_Selector):
data_type: str = type_spec.items[0].lower()
kind: str = str(kind_selector.children[1])
match = self._patterns[data_type].match(kind)
match = pattern.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)
pattern=pattern.pattern)
issues.append(Issue(message,
line=_line(candidate)))

Expand Down
49 changes: 43 additions & 6 deletions unit-tests/fortran_kind_pattern_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import re
from textwrap import dedent

import pytest

from stylist.fortran import KindPattern
from stylist.source import (FortranPreProcessor,
FortranSource,
Expand Down Expand Up @@ -238,8 +240,9 @@ def test_failing(self):

assert len(issues) == 28

def test_missing_kind(self):
case = dedent("""
@pytest.fixture
def missing_kind_text(self) -> str:
return dedent("""
module passing_mod
implicit none
integer :: global_int
Expand Down Expand Up @@ -273,9 +276,43 @@ def test_missing_kind(self):
end module passing_mod
""")

reader = SourceStringReader(case)
def test_missing_kind_expected(self, missing_kind_text):
reader = SourceStringReader(missing_kind_text)
source = FortranSource(reader)
unit_under_test = KindPattern(integer=r'.+_beef',
real=re.compile(r'.+_cheese'))
unit_under_test = KindPattern()
issues = unit_under_test.examine(source)
assert len(issues) == 0
assert [str(issue) for issue in issues] == []

def test_missing_kind_integer(self, missing_kind_text):
reader = SourceStringReader(missing_kind_text)
source = FortranSource(reader)
unit_under_test = KindPattern(integer=re.compile('i_.*_var'))
issues = unit_under_test.examine(source)
assert [str(issue) for issue in issues] \
== ["4: Kind '' found for integer variable 'global_int' "
"does not fit the pattern /i_.*_var/.",
"8: Kind '' found for integer variable 'member_int' "
"does not fit the pattern /i_.*_var/.",
"17: Kind '' found for integer variable 'arg_int' "
"does not fit the pattern /i_.*_var/.",
"20: Kind '' found for integer variable 'return_int' "
"does not fit the pattern /i_.*_var/.",
"27: Kind '' found for integer variable 'marg_int' "
"does not fit the pattern /i_.*_var/."]

def test_missing_kind_float(self, missing_kind_text):
reader = SourceStringReader(missing_kind_text)
source = FortranSource(reader)
unit_under_test = KindPattern(real=re.compile('r_.*_var'))
issues = unit_under_test.examine(source)
assert [str(issue) for issue in issues] \
== ["5: Kind '' found for real variable 'global_float' "
"does not fit the pattern /r_.*_var/.",
"9: Kind '' found for real variable 'member_float' "
"does not fit the pattern /r_.*_var/.",
"18: Kind '' found for real variable 'arg_float' "
"does not fit the pattern /r_.*_var/.",
"28: Kind '' found for real variable 'marg_float' "
"does not fit the pattern /r_.*_var/.",
"30: Kind '' found for real variable 'return_float' "
"does not fit the pattern /r_.*_var/."]

0 comments on commit 0ffccb3

Please sign in to comment.