From 1a2eb0b040e72488f44a71209f4d2e6421abd538 Mon Sep 17 00:00:00 2001 From: Arjun Mehta Date: Wed, 22 Jan 2025 22:06:26 +0530 Subject: [PATCH] feat: Add KeywordCase rule for Fortran keyword casing Adds new rule to enforce consistent casing of Fortran keywords using regex patterns. Default enforces lowercase but supports custom patterns. --- source/stylist/fortran.py | 55 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/source/stylist/fortran.py b/source/stylist/fortran.py index 4982032..ca2cec7 100644 --- a/source/stylist/fortran.py +++ b/source/stylist/fortran.py @@ -783,3 +783,58 @@ def examine_fortran(self, subject: FortranSource) -> List[Issue]: message = f"Attempt to use forbidden module '{use_module}'" issues.append(Issue(message, line=use.item.span[0])) return issues + + +class KeywordCase(FortranRule): + """ + Ensures Fortran keywords match a specified case pattern. + + By default, enforces lowercase keywords if no pattern is provided. + """ + _ISSUE_TEMPLATE = "Keyword '{keyword}' does not match the pattern /{pattern}/." + + def __init__(self, pattern: Optional[Union[str, Pattern]] = None): + """ + :param pattern: Regular expression which keywords must match. + Defaults to lowercase if not specified. + """ + if pattern is None: + self._pattern = re.compile(r'^[a-z]+$') + elif isinstance(pattern, str): + self._pattern = re.compile(pattern) + else: + self._pattern = pattern + + def examine_fortran(self, subject: FortranSource) -> List[Issue]: + issues: List[Issue] = [] + + # Get all statements from the parse tree + tree = subject.get_tree() + if tree is None: + return [] + + # Walk through all nodes looking for keywords + for node in fp_walk(tree, Fortran2003.StmtBase): + # Get the raw text of the statement + stmt_text = str(node) + + # Extract keywords - this is a simplified approach + # Would need to be enhanced for more complex keyword detection + words = stmt_text.split() + for word in words: + # Basic check for Fortran keywords + if word.upper() in {'PROGRAM', 'SUBROUTINE', 'FUNCTION', + 'MODULE', 'CONTAINS', 'USE', 'IMPLICIT', + 'INTEGER', 'REAL', 'CHARACTER', 'LOGICAL', + 'INTENT', 'ALLOCATABLE', 'POINTER', + 'DIMENSION', 'TYPE', 'END'}: + if not self._pattern.match(word): + issues.append(Issue( + self._ISSUE_TEMPLATE.format( + keyword=word, + pattern=self._pattern.pattern + ), + line=_line(node) + )) + + return issues