diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index d06c02d..688605f 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -96,7 +96,7 @@ jobs: # carries, or a change to them is not exercised until after it merges. # Consumers use the action; this repository owns the rules. - name: Refuse a skip, and a suite that shrank - run: tools/check-test-outcome.py "$RUNNER_TEMP/pytest.log" --min-tests 317 + run: tools/check-test-outcome.py "$RUNNER_TEMP/pytest.log" --min-tests 318 # The rules earn their place by refusing a log that carries what they # name. Both fixtures are written here rather than tracked, and the diff --git a/parser/nullable.py b/parser/nullable.py index 41c411c..4a64dd5 100644 --- a/parser/nullable.py +++ b/parser/nullable.py @@ -27,8 +27,13 @@ _PARAM = re.compile( r'@param\[[^\]]*\]\s+(?P\w+(?:\s*,\s*\w+)*)\s+(?P.*?)' r'(?=\n\s*\*\s*@|\*/|\Z)', re.S) -_NULLISH = re.compile(r'may be\s+`?NULL`?|can be\s+`?NULL`?|`?NULL`?\s+is allowed' - r'|or\s+`?NULL`?', re.I) +# Doxygen marks up a parameter or literal it names — ``NULL``, `NULL`, @p NULL, +# \p NULL — and every one of those spellings is the same note. Reading only the +# bare and backticked forms drops the ones an author marked up, and a parameter +# whose note is unread reaches every binding as one that must be supplied. +_MARKED_NULL = r'(?:[@\\]p\s+)?`?NULL`?' +_NULLISH = re.compile(rf'may be\s+{_MARKED_NULL}|can be\s+{_MARKED_NULL}' + rf'|{_MARKED_NULL}\s+is allowed|or\s+{_MARKED_NULL}', re.I) def extract_nullable(meos_root: str | Path) -> dict[str, list[str]]: diff --git a/tests/test_nullable.py b/tests/test_nullable.py index 0d37bc4..ee158b0 100644 --- a/tests/test_nullable.py +++ b/tests/test_nullable.py @@ -36,6 +36,19 @@ { return NULL; } + +/** + * @brief Interpolate between two geography points + * @param[in] p1 First point + * @param[in] s Spheroid, may be @p NULL when the sphere is meant + * @param[in] tol Tolerance, or \\p NULL for the default + */ +int +interpolate_point4d_spheroid(const POINT4D *p1, const SPHEROID *s, + const double *tol) +{ + return 0; +} ''' @@ -57,6 +70,14 @@ def test_extracts_only_may_be_null_params(self): # `temp`, `inst`, `interp` carry no NULL note -> not nullable self.assertNotIn("temp", nul["temporal_as_mfjson"]) + def test_a_null_note_reads_through_the_markup_around_it(self): + # Doxygen marks up the literal it names, and `NULL`, @p NULL and \p NULL + # are one note. A parameter whose note goes unread reaches every binding + # as one that must be supplied. + nul = extract_nullable(self.tmp.name) + self.assertEqual(nul["interpolate_point4d_spheroid"], ["s", "tol"]) + self.assertNotIn("p1", nul["interpolate_point4d_spheroid"]) + def test_merge_only_existing_params(self): idl = {"functions": [ {"name": "temporal_as_mfjson",