Repository: bids-standard/pybids

Issue:
Bug/Ambiguous parsing when extension but no suffix present
Found a bit of buggy parsing when fuzzing inputs on another project.

For the following dataset:
```
sub-0
	sub-0.ext
```

The sole file gets parsed with the following entities:


```py
{'extension': '.ext', 'subject': '0', 'suffix': '0'}
```

Clearly `0` should not be attached both to `subject` and `suffix`.

My inclination is to add a negative lookbehind to the `suffix` regex preventing alphanums preceded by a `-` from being matched: `(?<!-)[._]*([a-zA-Z0-9]*?)\.[^\/\\]$`. This should generally prevent it from snatching the values from other entities.

However, I'm not sure if bids *requires* files with extensions to also have suffixes? The regexes in the config file seem to suggest this is so (the `suffix` and `extension` regexes are identical, with only the capture group location moved).

But even if so, pybids needs to do *something* with the above file, and ignoring it as invalid seems harsh.

Also not sure how this situation will change if official bids spec files are used.

Discussion:

Patch:
diff --git a/bids/layout/config/bids.json b/bids/layout/config/bids.json
index dada4b1c..bad844ad 100644
--- a/bids/layout/config/bids.json
+++ b/bids/layout/config/bids.json
@@ -95,7 +95,7 @@
         },
         {
             "name": "suffix",
-            "pattern": "[._]*([a-zA-Z0-9]*?)\\.[^/\\\\]+$"
+            "pattern": "(?:^|[_/\\\\])([a-zA-Z0-9]+)\\.[^/\\\\]+$"
         },
         {
             "name": "scans",
@@ -111,7 +111,7 @@
         },
         {
             "name": "extension",
-            "pattern": "[._]*[a-zA-Z0-9]*?(\\.[^/\\\\]+)$"
+            "pattern": "[^./\\\\](\\.[^/\\\\]+)$"
         }
     ],
     "default_path_patterns": [


Test patch:
diff --git a/bids/layout/tests/test_utils.py b/bids/layout/tests/test_utils.py
index 1023fb76..26f1511b 100644
--- a/bids/layout/tests/test_utils.py
+++ b/bids/layout/tests/test_utils.py
@@ -48,6 +48,19 @@ def test_parse_file_entities(mock_config):
     assert target == parse_file_entities(filename, entities=entities)
 
 
+@pytest.mark.parametrize(
+    "filename, target",
+    [
+        ('/path/to/sub-01.ext', {'subject': '01', 'extension': '.ext'}),
+        ('/path/to/stub.ext', {'suffix': 'stub', 'extension': '.ext'}),
+        ('/path/to/.dotfile', {}),
+        ('/path/to/stub', {}),
+    ]
+)
+def test_parse_degenerate_files(mock_config, filename, target):
+    assert parse_file_entities(filename, config='bids') == target
+
+
 def test_add_config_paths():
     bids_dir = os.path.dirname(bids.__file__)
     bids_json = os.path.join(bids_dir, 'layout', 'config', 'bids.json')