Skip to content

Commit 8d3dd54

Browse files
feat: doxygen supports
1 parent e805379 commit 8d3dd54

23 files changed

Lines changed: 1858 additions & 268 deletions

base/include/base/macro/plain_property.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
/**
2+
* @file base/include/base/macro/plain_property.h
3+
* @brief Macro for generating simple property getters and setters.
4+
*
5+
* Defines the CF_PLAIN_PROPERTY macro which generates getter and setter
6+
* methods for a class member variable with a default value.
7+
*
8+
* @author N/A
9+
* @date N/A
10+
* @version N/A
11+
* @since N/A
12+
* @ingroup none
13+
*/
114
#pragma once
215

316
#ifndef CF_PLAIN_PROPERTY

base/include/base/weak_ptr/weak_ptr.h

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ template <typename T> class WeakPtr {
335335
* otherwise returns an invalid WeakPtr.
336336
*
337337
* @tparam Source Source type (base class).
338-
* @param other WeakPtr to the source object.
338+
* @param[in] other WeakPtr to the source object.
339339
*
340340
* @return WeakPtr<Derived> pointing to the same object if
341341
* the object is of type Derived, otherwise invalid.
@@ -384,12 +384,30 @@ template <typename T> class WeakPtr {
384384
explicit WeakPtr(T* ptr, internal::WeakReferenceFlagPtr flag) noexcept
385385
: ptr_(ptr), flag_(std::move(flag)) {}
386386

387+
/// Raw pointer to the owned object. Ownership: observer; may be nullptr.
387388
T* ptr_ = nullptr;
389+
390+
/// Weak reference flag for lifetime tracking. Ownership: shared; may be nullptr.
388391
internal::WeakReferenceFlagPtr flag_ = nullptr;
389392

390-
// Allows WeakPtr with different T to access private members (covariance)
393+
/**
394+
* @brief Friend declaration for covariance support.
395+
*
396+
* @tparam U Type parameter for the befriended WeakPtr.
397+
*
398+
* @since N/A
399+
* @ingroup none
400+
*/
391401
template <typename U> friend class WeakPtr;
392402

403+
/**
404+
* @brief Friend declaration for factory access.
405+
*
406+
* @tparam U Type parameter for the befriended WeakPtrFactory.
407+
*
408+
* @since N/A
409+
* @ingroup none
410+
*/
393411
template <typename U> friend class WeakPtrFactory;
394412
};
395413

scripts/doxygen/lint.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,17 @@
5151
# ====================== REGEX RULES =========================
5252
# ============================================================
5353

54-
FIRST_PERSON_PATTERN = re.compile(r"\b(we|our|my)\b|\bI\b(?![/oO])", re.IGNORECASE)
54+
# Note: 'I' must be case-sensitive to avoid matching loop variable 'i'
55+
# we/our/my are case-insensitive to catch variants like WE, Our, MY, etc.
5556
FUTURE_TENSE_PATTERN = re.compile(r"\bwill\b", re.IGNORECASE)
5657

58+
# Compile a case-sensitive pattern for 'I' to avoid matching loop variable 'i'
59+
# This pattern matches 'I' (uppercase only) as a whole word, except when followed by /, o, or O
60+
FIRST_PERSON_I_PATTERN = re.compile(r"\bI\b(?![/oO])")
61+
62+
# Pattern for we/our/my (case-insensitive)
63+
FIRST_PERSON_WE_PATTERN = re.compile(r"\b(?:we|our|my)\b", re.IGNORECASE)
64+
5765
FUNCTION_PATTERN = re.compile(
5866
r'^\s*(?:template\s*<[^>]*>\s*)?(?:virtual\s+)?(?:inline\s+)?(?:explicit\s+)?(?:static\s+)?(?:constexpr\s+)?(?:consteval\s+)?(?:constinit\s+)?(?:friend\s+)?([\w:<>&\s]+?)(?:\s+const)?\s+(?!if|for|while|switch|return|static_assert|catch|union|struct|class)(\w+)\s*\(([^)]*)\)(?:\s*(?:const|volatile|noexcept(?:\([^)]*\))?|override|final|consteval|constinit)\s*)*(?:\s*=\s*0)?\s*[;{]',
5967
re.MULTILINE,
@@ -715,6 +723,16 @@ def looks_like_function_call_params(params_str: str) -> bool:
715723
if not return_type or not return_type.strip():
716724
continue
717725

726+
# Skip if return type is ":" - this is a member initializer list, not a function
727+
# Example: ": ICFAbstractAnimation(parent) {"
728+
if return_type.strip() == ':':
729+
continue
730+
731+
# Skip if return type is "return" - this is a return statement, not a function
732+
# Example: "return WeakPtr();"
733+
if return_type.strip() == 'return':
734+
continue
735+
718736
# Skip if function is inside a @code block (example code)
719737
if is_in_code_block(func_pos, content):
720738
continue
@@ -928,7 +946,12 @@ def looks_like_function_call_params(params_str: str) -> bool:
928946
def check_language_rules(content: str, file: Path) -> List[Violation]:
929947
violations: List[Violation] = []
930948

931-
if FIRST_PERSON_PATTERN.search(content):
949+
# Check for first-person pronouns
950+
# For 'I', use case-sensitive pattern to avoid matching loop variable 'i'
951+
if FIRST_PERSON_I_PATTERN.search(content):
952+
violations.append(Violation(file, "First-person usage detected"))
953+
# For we/our/my, use case-insensitive pattern
954+
elif FIRST_PERSON_WE_PATTERN.search(content):
932955
violations.append(Violation(file, "First-person usage detected"))
933956

934957
if FUTURE_TENSE_PATTERN.search(content):

test/tools/test_doxygen_linter/test_check_function_blocks.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,3 +305,94 @@ def test_multiple_functions(self) -> None:
305305
# Only second() should have violations
306306
assert_len(violations, 1)
307307
assert_equal(violations[0].symbol, "second")
308+
309+
def test_return_statement_not_matched_as_function(self) -> None:
310+
"""Test that return statements are not matched as function declarations."""
311+
content = """/**
312+
* @brief Dynamically converts Base to Derived.
313+
*
314+
* @param[in] other WeakPtr to the source object.
315+
*
316+
* @return WeakPtr<Derived> pointing to the same object.
317+
*
318+
* @throws None
319+
* @note The returned WeakPtr shares the same weak reference.
320+
* @warning Always check the returned WeakPtr's validity before use.
321+
* @since 0.1
322+
* @ingroup none
323+
*/
324+
template <typename Source>
325+
static WeakPtr DynamicCast(const WeakPtr<Source>& other) noexcept {
326+
if (!other.flag_ || !other.flag_->IsAlive()) {
327+
return WeakPtr(); // Should not be flagged as missing Doxygen
328+
}
329+
T* casted = dynamic_cast<T*>(other.ptr_);
330+
if (casted) {
331+
return WeakPtr(casted, other.flag_); // Should not be flagged
332+
}
333+
return WeakPtr(); // Should not be flagged
334+
}"""
335+
violations = lint.check_function_blocks(content, Path("test.h"))
336+
# DynamicCast is fully documented, return statements should not cause extra violations
337+
assert_len(violations, 0)
338+
339+
def test_undocumented_function_with_return_statements(self) -> None:
340+
"""Test that return statements don't create false violations."""
341+
content = """
342+
// Undocumented function
343+
static WeakPtr CreateWeakPtr() {
344+
return WeakPtr(); // Should not be flagged as a separate missing Doxygen
345+
}
346+
"""
347+
violations = lint.check_function_blocks(content, Path("test.h"))
348+
# Only CreateWeakPtr should have a violation, not the return statement
349+
assert_len(violations, 1)
350+
assert_equal(violations[0].symbol, "CreateWeakPtr")
351+
352+
def test_member_initializer_list_not_matched_as_function(self) -> None:
353+
"""Test that member initializer lists are not matched as function declarations."""
354+
content = """/**
355+
* @brief Constructor with spring easing preset.
356+
*
357+
* @param[in] easing Spring preset for physics parameters.
358+
* @param[in] parent QObject parent.
359+
*
360+
* @throws None
361+
* @note None
362+
* @warning None
363+
* @since 0.1
364+
* @ingroup ui_components
365+
*/
366+
ICFSpringAnimation(const base::Easing::SpringPreset& easing, QObject* parent = nullptr)
367+
: ICFAbstractAnimation(parent) { # Should not be flagged as a separate function
368+
easing_ = easing;
369+
}"""
370+
violations = lint.check_function_blocks(content, Path("test.h"))
371+
# Constructor is documented, initializer list should not cause extra violations
372+
assert_len(violations, 0)
373+
374+
def test_constructor_with_return_statement(self) -> None:
375+
"""Test constructor that contains return statements (for nested helper functions)."""
376+
content = """/**
377+
* @brief Default constructor: creates an empty weak reference.
378+
* @throws None
379+
* @note None
380+
* @warning None
381+
* @since 0.1
382+
* @ingroup none
383+
*/
384+
constexpr WeakPtr() noexcept = default;
385+
386+
/**
387+
* @brief Constructs an empty weak reference from nullptr.
388+
* @param[in] nullptr_t The nullptr literal.
389+
* @throws None
390+
* @note None
391+
* @warning None
392+
* @since 0.1
393+
* @ingroup none
394+
*/
395+
constexpr WeakPtr(std::nullptr_t) noexcept {}"""
396+
violations = lint.check_function_blocks(content, Path("test.h"))
397+
# Both constructors are documented
398+
assert_len(violations, 0)

test/tools/test_doxygen_linter/test_check_language_rules.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,46 @@ def test_empty_content(self) -> None:
134134
content = ""
135135
violations = lint.check_language_rules(content, Path("test.h"))
136136
assert_len(violations, 0)
137+
138+
def test_loop_variable_i_not_flagged(self) -> None:
139+
"""Test that loop variable 'i' is NOT flagged as first-person."""
140+
content = """/**
141+
* @brief Process items in a loop.
142+
*
143+
* @details Iterates through the array using index i.
144+
*
145+
* @param[in] data The data array.
146+
* @return Processed result.
147+
*/
148+
int process(const int* data);"""
149+
violations = lint.check_language_rules(content, Path("test.h"))
150+
assert_len(violations, 0)
151+
152+
def test_loop_variables_i_j_k_not_flagged(self) -> None:
153+
"""Test that common loop variables i, j, k are NOT flagged."""
154+
content = """/**
155+
* @brief Nested loop example.
156+
*
157+
* @details Uses i for outer loop, j for inner loop.
158+
*
159+
* @return The sum.
160+
*/
161+
int nestedLoop() {
162+
for (int i = 0; i < 10; ++i) {
163+
for (int j = 0; j < 10; ++j) {
164+
for (int k = 0; k < 10; ++k) {
165+
// Do something
166+
}
167+
}
168+
}
169+
return 0;
170+
}"""
171+
violations = lint.check_language_rules(content, Path("test.h"))
172+
assert_len(violations, 0)
173+
174+
def test_uppercase_i_is_flagged(self) -> None:
175+
"""Test that uppercase 'I' (first person) IS flagged."""
176+
content = "/** I think this works. */"
177+
violations = lint.check_language_rules(content, Path("test.h"))
178+
assert_len(violations, 1)
179+
assert_equal(violations[0].message, "First-person usage detected")

0 commit comments

Comments
 (0)