Skip to content

Commit b5d8bc2

Browse files
authored
Merge pull request #306 from fortran-lang/feat/generic-sources
fix: rewrote the source file extension REGEX
2 parents 351ae01 + f5b45d2 commit b5d8bc2

File tree

8 files changed

+113
-26
lines changed

8 files changed

+113
-26
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818

1919
### Changed
2020

21+
- Changed `--incl_suffixes` option to faithfully match the suffixes that are
22+
provided in the option, without performing any type of modification.
23+
([#300](https://github.com/fortran-lang/fortls/issues/300))
2124
- Changed the completion signature to include the full Markdown documentation
2225
for the completion item.
2326
([#219](https://github.com/fortran-lang/fortls/issues/219))

docs/options.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,15 @@ incl_suffixes
102102
.. code-block:: json
103103
104104
{
105-
"incl_suffixes": [".h", ".FYP"]
105+
"incl_suffixes": [".h", ".FYP", "inc"]
106106
}
107107
108108
``fortls`` will parse only files with ``incl_suffixes`` extensions found in
109-
``source_dirs``. By default ``incl_suffixes`` are defined as
109+
``source_dirs``. Using the above example, ``fortls`` will match files by the
110+
``file.h`` and ``file.FYP``, but not ``file.fyp`` or ``filefyp``.
111+
It will also match ``file.inc`` and ``fileinc`` but not ``file.inc2``.
112+
113+
By default, ``incl_suffixes`` are defined as
110114
.F .f .F03 .f03 .F05 .f05 .F08 .f08 .F18 .f18 .F77 .f77 .F90 .f90 .F95 .f95 .FOR .for .FPP .fpp.
111115
Additional source file extensions can be defined in ``incl_suffixes``.
112116

fortls/fortls.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
},
6262
"incl_suffixes": {
6363
"title": "Incl Suffixes",
64-
"description": "Consider additional file extensions to the default (default: F,F77,F90,F95,F03,F08,FOR,FPP (lower & upper casing))",
64+
"description": "Consider additional file extensions to the default (default: .F, .F77, .F90, .F95, .F03, .F08, .FOR, .FPP (lower & upper casing))",
6565
"default": [],
6666
"type": "array",
6767
"items": {},

fortls/interface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def cli(name: str = "fortls") -> argparse.ArgumentParser:
126126
metavar="SUFFIXES",
127127
help=(
128128
"Consider additional file extensions to the default (default: "
129-
"F,F77,F90,F95,F03,F08,FOR,FPP (lower & upper casing))"
129+
".F, .F77, .F90, .F95, .F03, .F08, .FOR, .FPP (lower & upper casing))"
130130
),
131131
)
132132
group.add_argument(

fortls/langserver.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
get_use_tree,
6262
)
6363
from fortls.parse_fortran import FortranFile, get_line_context
64-
from fortls.regex_patterns import src_file_exts
64+
from fortls.regex_patterns import create_src_file_exts_str
6565
from fortls.version import __version__
6666

6767
# Global regexes
@@ -89,7 +89,9 @@ def __init__(self, conn, settings: dict):
8989

9090
self.sync_type: int = 2 if self.incremental_sync else 1
9191
self.post_messages = []
92-
self.FORTRAN_SRC_EXT_REGEX: Pattern[str] = src_file_exts(self.incl_suffixes)
92+
self.FORTRAN_SRC_EXT_REGEX: Pattern[str] = create_src_file_exts_str(
93+
self.incl_suffixes
94+
)
9395
# Intrinsic (re-loaded during initialize)
9496
(
9597
self.statements,
@@ -1569,7 +1571,7 @@ def _load_config_file_dirs(self, config_dict: dict) -> None:
15691571
self.source_dirs = set(config_dict.get("source_dirs", self.source_dirs))
15701572
self.incl_suffixes = set(config_dict.get("incl_suffixes", self.incl_suffixes))
15711573
# Update the source file REGEX
1572-
self.FORTRAN_SRC_EXT_REGEX = src_file_exts(self.incl_suffixes)
1574+
self.FORTRAN_SRC_EXT_REGEX = create_src_file_exts_str(self.incl_suffixes)
15731575
self.excl_suffixes = set(config_dict.get("excl_suffixes", self.excl_suffixes))
15741576

15751577
def _load_config_file_general(self, config_dict: dict) -> None:

fortls/regex_patterns.py

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -149,30 +149,63 @@ class FortranRegularExpressions:
149149
OBJBREAK: Pattern = compile(r"[\/\-(.,+*<>=$: ]", I)
150150

151151

152-
def src_file_exts(input_exts: list[str] = []) -> Pattern[str]:
153-
"""Create a REGEX for which file extensions the Language Server should parse
154-
Default extensions are
155-
F F03 F05 F08 F18 F77 F90 F95 FOR FPP f f03 f05 f08 f18 f77 f90 f95 for fpp
152+
# TODO: use this in the main code
153+
def create_src_file_exts_regex(input_exts: list[str] = []) -> Pattern[str]:
154+
r"""Create a REGEX for which sources the Language Server should parse.
155+
156+
Default extensions are (case insensitive):
157+
F F03 F05 F08 F18 F77 F90 F95 FOR FPP
156158
157159
Parameters
158160
----------
159161
input_exts : list[str], optional
160-
Additional Fortran, by default []
162+
Additional list of file extensions to parse, in Python REGEX format
163+
that means special characters must be escaped
164+
, by default []
165+
166+
Examples
167+
--------
168+
>>> regex = create_src_file_exts_regex([r"\.fypp", r"\.inc"])
169+
>>> regex.search("test.fypp")
170+
<re.Match object; span=(4, 9), match='.fypp'>
171+
>>> regex.search("test.inc")
172+
<re.Match object; span=(4, 8), match='.inc'>
173+
174+
>>> regex = create_src_file_exts_regex([r"\.inc.*"])
175+
>>> regex.search("test.inc.1")
176+
<re.Match object; span=(4, 10), match='.inc.1'>
177+
178+
Invalid regex expressions will cause the function to revert to the default
179+
extensions
180+
181+
>>> regex = create_src_file_exts_regex(["*.inc"])
182+
>>> regex.search("test.inc") is None
183+
True
161184
162185
Returns
163186
-------
164187
Pattern[str]
165-
A compiled regular expression, by default
166-
'.(F|F03|F05|F08|F18|F77|F90|F95|FOR|FPP|f|f03|f05|f08|f18|f77|f90|f95|for|fpp)?'
188+
A compiled regular expression for matching file extensions
167189
"""
168-
EXTS = ["", "77", "90", "95", "03", "05", "08", "18", "OR", "PP"]
169-
FORTRAN_FILE_EXTS = []
170-
for e in EXTS:
171-
FORTRAN_FILE_EXTS.extend([f"F{e}".upper(), f"f{e}".lower()])
172-
# Add the custom extensions for the server to parse
173-
for e in input_exts:
174-
if e.startswith("."):
175-
FORTRAN_FILE_EXTS.append(e.replace(".", ""))
176-
# Cast into a set to ensure uniqueness of extensions & sort for consistency
177-
# Create a regular expression from this
178-
return compile(rf"\.({'|'.join(sorted(set(FORTRAN_FILE_EXTS)))})?$")
190+
import re
191+
192+
DEFAULT = r"\.[fF](77|90|95|03|05|08|18|[oO][rR]|[pP]{2})?"
193+
EXPRESSIONS = [DEFAULT]
194+
try:
195+
EXPRESSIONS.extend(input_exts)
196+
# Add its expression as an OR and force they match the end of the string
197+
return re.compile(rf"(({'$)|('.join(EXPRESSIONS)}$))")
198+
except re.error:
199+
# TODO: Add a warning to the logger
200+
return re.compile(rf"({DEFAULT}$)")
201+
202+
203+
def create_src_file_exts_str(input_exts: list[str] = []) -> Pattern[str]:
204+
"""This is a version of create_src_file_exts_regex that takes a list
205+
sanitises the list of input_exts before compiling the regex.
206+
For more info see create_src_file_exts_regex
207+
"""
208+
import re
209+
210+
input_exts = [re.escape(ext) for ext in input_exts]
211+
return create_src_file_exts_regex(input_exts)

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ dev =
6262
black
6363
isort
6464
pre-commit
65-
pydantic
65+
pydantic==1.9.1
6666
docs =
6767
sphinx >= 4.0.0
6868
sphinx-argparse

test/test_regex_patterns.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from fortls.regex_patterns import create_src_file_exts_regex
6+
7+
8+
@pytest.mark.parametrize(
9+
"input_exts, input_files, matches",
10+
[
11+
(
12+
[],
13+
[
14+
"test.f",
15+
"test.F",
16+
"test.f90",
17+
"test.F90",
18+
"test.f03",
19+
"test.F03",
20+
"test.f18",
21+
"test.F18",
22+
"test.f77",
23+
"test.F77",
24+
"test.f95",
25+
"test.F95",
26+
"test.for",
27+
"test.FOR",
28+
"test.fpp",
29+
"test.FPP",
30+
],
31+
[True] * 16,
32+
),
33+
([], ["test.ff", "test.f901", "test.f90.ff"], [False, False, False]),
34+
([r"\.inc"], ["test.inc", "testinc", "test.inc2"], [True, False, False]),
35+
(["inc.*"], ["test.inc", "testinc", "test.inc2"], [True, True, True]),
36+
],
37+
)
38+
def test_src_file_exts(
39+
input_exts: list[str],
40+
input_files: list[str],
41+
matches: list[bool],
42+
):
43+
regex = create_src_file_exts_regex(input_exts)
44+
results = [bool(regex.search(file)) for file in input_files]
45+
assert results == matches

0 commit comments

Comments
 (0)