diff --git a/crates/ruff_linter/resources/test/fixtures/pydoclint/DOC101_google.py b/crates/ruff_linter/resources/test/fixtures/pydoclint/DOC101_google.py new file mode 100644 index 0000000000000..74db06c8ae3e0 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pydoclint/DOC101_google.py @@ -0,0 +1,372 @@ +# OK +def add_numbers(a, b): + """ + Adds two numbers and returns the result. + + Args: + a (int): The first number to add. + b (int): The second number to add. + + Returns: + int: The sum of the two numbers. + """ + return a + b + + +# OK +def multiply_list_elements(lst, multiplier): + """ + Multiplies each element in a list by a given multiplier. + + Args: + lst (list of int): A list of integers. + multiplier (int): The multiplier for each element in the list. + + Returns: + list of int: A new list with each element multiplied. + """ + return [x * multiplier for x in lst] + + +# OK +def find_max_value(numbers): + """ + Finds the maximum value in a list of numbers. + + Args: + numbers (list of int): A list of integers to search through. + + Returns: + int: The maximum value found in the list. + """ + return max(numbers) + + +# OK +def create_user_profile(name, age, email, location="here"): + """ + Creates a user profile with basic information. + + Args: + name (str): The name of the user. + age (int): The age of the user. + email (str): The user's email address. + location (str): The location of the user. + + Returns: + dict: A dictionary containing the user's profile. + """ + return { + 'name': name, + 'age': age, + 'email': email, + 'location': location + } + + +# OK +def calculate_total_price(item_prices, tax_rate, discount): + """ + Calculates the total price after applying tax and a discount. + + Args: + item_prices (list of float): A list of prices for each item. + tax_rate (float): The tax rate to apply. + discount (float): The discount to subtract from the total. + + Returns: + float: The final total price after tax and discount. + """ + total = sum(item_prices) + total_with_tax = total + (total * tax_rate) + final_total = total_with_tax - discount + return final_total + + +# OK +def send_email(subject, body, to_address, cc_address=None, bcc_address=None): + """ + Sends an email to the specified recipients. + + Args: + subject (str): The subject of the email. + body (str): The content of the email. + to_address (str): The recipient's email address. + cc_address (str, optional): The email address for CC. Defaults to None. + bcc_address (str, optional): The email address for BCC. Defaults to None. + + Returns: + bool: True if the email was sent successfully, False otherwise. + """ + return True + + +# OK +def concatenate_strings(separator, *args): + """ + Concatenates multiple strings with a specified separator. + + Args: + separator (str): The separator to use between strings. + *args (str): Variable length argument list of strings to concatenate. + + Returns: + str: A single concatenated string. + """ + return separator.join(args) + + +# OK +def process_order(order_id, *items, **details): + """ + Processes an order with a list of items and optional order details. + + Args: + order_id (int): The unique identifier for the order. + *items (str): Variable length argument list of items in the order. + **details (dict): Additional details such as shipping method and address. + + Returns: + dict: A dictionary containing the order summary. + """ + return { + 'order_id': order_id, + 'items': items, + 'details': details + } + + +class Calculator: + """ + A simple calculator class that can perform basic arithmetic operations. + """ + + # OK + def __init__(self, value=0): + """ + Initializes the calculator with an initial value. + + Args: + value (int, optional): The initial value of the calculator. Defaults to 0. + """ + self.value = value + + # OK + def add(self, number): + """ + Adds a number to the current value. + + Args: + number (int or float): The number to add to the current value. + + Returns: + int or float: The updated value after addition. + """ + self.value += number + number2 + return self.value + + # OK + @classmethod + def from_string(cls, value_str): + """ + Creates a Calculator instance from a string representation of a number. + + Args: + value_str (str): The string representing the initial value. + + Returns: + Calculator: A new instance of Calculator initialized with the value from the string. + """ + value = float(value_str) + return cls(value) + + # OK + @staticmethod + def is_valid_number(number): + """ + Checks if a given number is valid (int or float). + + Args: + number (any): The value to check. + + Returns: + bool: True if the number is valid, False otherwise. + """ + return isinstance(number, (int, float)) + + +# DOC101 +def add_numbers(a, b): + """ + Adds two numbers and returns the result. + + Args: + a (int): The first number to add. + + Returns: + int: The sum of the two numbers. + """ + return a + b + + +# DOC101 +def multiply_list_elements(lst, multiplier): + """ + Multiplies each element in a list by a given multiplier. + + Args: + lst (list of int): A list of integers. + + Returns: + list of int: A new list with each element multiplied. + """ + return [x * multiplier for x in lst] + + +# DOC101 +def find_max_value(numbers): + """ + Finds the maximum value in a list of numbers. + + Returns: + int: The maximum value found in the list. + """ + return max(numbers) + + +# DOC101 +def create_user_profile(name, age, email, location="here"): + """ + Creates a user profile with basic information. + + Args: + email (str): The user's email address. + location (str): The location of the user. + + Returns: + dict: A dictionary containing the user's profile. + """ + return { + 'name': name, + 'age': age, + 'email': email, + 'location': location + } + + +# DOC101 +def calculate_total_price(item_prices, tax_rate, discount): + """ + Calculates the total price after applying tax and a discount. + + Args: + item_prices (list of float): A list of prices for each item. + + Returns: + float: The final total price after tax and discount. + """ + total = sum(item_prices) + total_with_tax = total + (total * tax_rate) + final_total = total_with_tax - discount + return final_total + + +# DOC101 +def send_email(subject, body, to_address, cc_address=None, bcc_address=None): + """ + Sends an email to the specified recipients. + + Args: + subject (str): The subject of the email. + body (str): The content of the email. + to_address (str): The recipient's email address. + + Returns: + bool: True if the email was sent successfully, False otherwise. + """ + return True + + +# DOC101 +def concatenate_strings(separator, *args): + """ + Concatenates multiple strings with a specified separator. + + Args: + separator (str): The separator to use between strings. + + Returns: + str: A single concatenated string. + """ + return separator.join(args) + + +# DOC101 +def process_order(order_id, *items, **details): + """ + Processes an order with a list of items and optional order details. + + Args: + order_id (int): The unique identifier for the order. + + Returns: + dict: A dictionary containing the order summary. + """ + return { + 'order_id': order_id, + 'items': items, + 'details': details + } + + +class Calculator: + """ + A simple calculator class that can perform basic arithmetic operations. + """ + + # DOC101 + def __init__(self, value=0): + """ + Initializes the calculator with an initial value. + + Returns: + None + """ + self.value = value + + # DOC101 + def add(self, number, number2): + """ + Adds a number to the current value. + + Args: + number (int or float): The number to add to the current value. + + Returns: + int or float: The updated value after addition. + """ + self.value += number + number2 + return self.value + + # DOC101 + @classmethod + def from_string(cls, value_str): + """ + Creates a Calculator instance from a string representation of a number. + + Returns: + Calculator: A new instance of Calculator initialized with the value from the string. + """ + value = float(value_str) + return cls(value) + + # DOC101 + @staticmethod + def is_valid_number(number): + """ + Checks if a given number is valid (int or float). + + Returns: + bool: True if the number is valid, False otherwise. + """ + return isinstance(number, (int, float)) diff --git a/crates/ruff_linter/resources/test/fixtures/pydoclint/DOC101_numpy.py b/crates/ruff_linter/resources/test/fixtures/pydoclint/DOC101_numpy.py new file mode 100644 index 0000000000000..4f47f905a6a3e --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pydoclint/DOC101_numpy.py @@ -0,0 +1,495 @@ +# OK +def add_numbers(a, b): + """ + Adds two numbers and returns the result. + + Parameters + ---------- + a : int + The first number to add. + b : int + The second number to add. + + Returns + ------- + int + The sum of the two numbers. + """ + return a + b + + +# OK +def multiply_list_elements(lst, multiplier): + """ + Multiplies each element in a list by a given multiplier. + + Parameters + ---------- + lst : list of int + A list of integers. + multiplier : int + The multiplier for each element in the list. + + Returns + ------- + list of int + A new list with each element multiplied. + """ + return [x * multiplier for x in lst] + + +# OK +def find_max_value(numbers): + """ + Finds the maximum value in a list of numbers. + + Parameters + ---------- + numbers : list of int + A list of integers to search through. + + Returns + ------- + int + The maximum value found in the list. + """ + return max(numbers) + + +# OK +def create_user_profile(name, age, email, location="here"): + """ + Creates a user profile with basic information. + + Parameters + ---------- + name : str + The name of the user. + age : int + The age of the user. + email : str + The user's email address. + location : str, optional + The location of the user, by default "here". + + Returns + ------- + dict + A dictionary containing the user's profile. + """ + return { + 'name': name, + 'age': age, + 'email': email, + 'location': location + } + + +# OK +def calculate_total_price(item_prices, tax_rate, discount): + """ + Calculates the total price after applying tax and a discount. + + Parameters + ---------- + item_prices : list of float + A list of prices for each item. + tax_rate : float + The tax rate to apply. + discount : float + The discount to subtract from the total. + + Returns + ------- + float + The final total price after tax and discount. + """ + total = sum(item_prices) + total_with_tax = total + (total * tax_rate) + final_total = total_with_tax - discount + return final_total + + +# OK +def send_email(subject, body, to_address, cc_address=None, bcc_address=None): + """ + Sends an email to the specified recipients. + + Parameters + ---------- + subject : str + The subject of the email. + body : str + The content of the email. + to_address : str + The recipient's email address. + cc_address : str, optional + The email address for CC, by default None. + bcc_address : str, optional + The email address for BCC, by default None. + + Returns + ------- + bool + True if the email was sent successfully, False otherwise. + """ + return True + + +# OK +def concatenate_strings(separator, *args): + """ + Concatenates multiple strings with a specified separator. + + Parameters + ---------- + separator : str + The separator to use between strings. + *args : str + Variable length argument list of strings to concatenate. + + Returns + ------- + str + A single concatenated string. + """ + return separator.join(args) + + +# OK +def process_order(order_id, *items, **details): + """ + Processes an order with a list of items and optional order details. + + Parameters + ---------- + order_id : int + The unique identifier for the order. + *items : str + Variable length argument list of items in the order. + **details : dict + Additional details such as shipping method and address. + + Returns + ------- + dict + A dictionary containing the order summary. + """ + return { + 'order_id': order_id, + 'items': items, + 'details': details + } + + +class Calculator: + """ + A simple calculator class that can perform basic arithmetic operations. + """ + + # OK + def __init__(self, value=0): + """ + Initializes the calculator with an initial value. + + Parameters + ---------- + value : int, optional + The initial value of the calculator, by default 0. + """ + self.value = value + + # OK + def add(self, number, number2): + """ + Adds two numbers to the current value. + + Parameters + ---------- + number : int or float + The first number to add. + number2 : int or float + The second number to add. + + Returns + ------- + int or float + The updated value after addition. + """ + self.value += number + number2 + return self.value + + # OK + @classmethod + def from_string(cls, value_str): + """ + Creates a Calculator instance from a string representation of a number. + + Parameters + ---------- + value_str : str + The string representing the initial value. + + Returns + ------- + Calculator + A new instance of Calculator initialized with the value from the string. + """ + value = float(value_str) + return cls(value) + + # OK + @staticmethod + def is_valid_number(number): + """ + Checks if a given number is valid (int or float). + + Parameters + ---------- + number : any + The value to check. + + Returns + ------- + bool + True if the number is valid, False otherwise. + """ + return isinstance(number, (int, float)) + + +# DOC101 +def add_numbers(a, b): + """ + Adds two numbers and returns the result. + + Parameters + ---------- + a : int + The first number to add. + + Returns + ------- + int + The sum of the two numbers. + """ + return a + b + + +# DOC101 +def multiply_list_elements(lst, multiplier): + """ + Multiplies each element in a list by a given multiplier. + + Parameters + ---------- + lst : list of int + A list of integers. + + Returns + ------- + list of int + A new list with each element multiplied. + """ + return [x * multiplier for x in lst] + + +# DOC101 +def find_max_value(numbers): + """ + Finds the maximum value in a list of numbers. + + Returns + ------- + int + The maximum value found in the list. + """ + return max(numbers) + + +# DOC101 +def create_user_profile(name, age, email, location="here"): + """ + Creates a user profile with basic information. + + Parameters + ---------- + email : str + The user's email address. + location : str, optional + The location of the user, by default "here". + + Returns + ------- + dict + A dictionary containing the user's profile. + """ + return { + 'name': name, + 'age': age, + 'email': email, + 'location': location + } + + +# DOC101 +def calculate_total_price(item_prices, tax_rate, discount): + """ + Calculates the total price after applying tax and a discount. + + Parameters + ---------- + item_prices : list of float + A list of prices for each item. + + Returns + ------- + float + The final total price after tax and discount. + """ + total = sum(item_prices) + total_with_tax = total + (total * tax_rate) + final_total = total_with_tax - discount + return final_total + + +# DOC101 +def send_email(subject, body, to_address, cc_address=None, bcc_address=None): + """ + Sends an email to the specified recipients. + + Parameters + ---------- + subject : str + The subject of the email. + body : str + The content of the email. + to_address : str + The recipient's email address. + + Returns + ------- + bool + True if the email was sent successfully, False otherwise. + """ + return True + + +# DOC101 +def concatenate_strings(separator, *args): + """ + Concatenates multiple strings with a specified separator. + + Parameters + ---------- + separator : str + The separator to use between strings. + + Returns + ------- + str + A single concatenated string. + """ + return separator.join(args) + + +# DOC101 +def process_order(order_id, *items, **details): + """ + Processes an order with a list of items and optional order details. + + Parameters + ---------- + order_id : int + The unique identifier for the order. + + Returns + ------- + dict + A dictionary containing the order summary. + """ + return { + 'order_id': order_id, + 'items': items, + 'details': details + } + + +class Calculator: + """ + A simple calculator class that can perform basic arithmetic operations. + """ + + # DOC101 + def __init__(self, value=0): + """ + Initializes the calculator with an initial value. + + """ + self.value = value + + # DOC101 + def add(self, number, number2): + """ + Adds two numbers to the current value. + + Parameters + ---------- + number : int or float + The first number to add. + + Returns + ------- + int or float + The updated value after addition. + """ + self.value += number + number2 + return self.value + + # DOC101 + @classmethod + def from_string(cls, value_str): + """ + Creates a Calculator instance from a string representation of a number. + + Returns + ------- + Calculator + A new instance of Calculator initialized with the value from the string. + """ + value = float(value_str) + return cls(value) + + # DOC101 + @staticmethod + def is_valid_number(number): + """ + Checks if a given number is valid (int or float). + + Returns + ------- + bool + True if the number is valid, False otherwise. + """ + return isinstance(number, (int, float)) + +# OK +def function_with_pep484_type_annotations(param1: int, param2: str) -> bool: + """Example function with PEP 484 type annotations. + + The return type must be duplicated in the docstring to comply + with the NumPy docstring style. + + Parameters + ---------- + param1 + The first parameter. + param2 + The second parameter. + + Returns + ------- + bool + True if successful, False otherwise. + + """ + return False diff --git a/crates/ruff_linter/resources/test/fixtures/pydoclint/DOC102_google.py b/crates/ruff_linter/resources/test/fixtures/pydoclint/DOC102_google.py new file mode 100644 index 0000000000000..f5cab892db43a --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pydoclint/DOC102_google.py @@ -0,0 +1,225 @@ +# DOC102 +def add_numbers(b): + """ + Adds two numbers and returns the result. + + Args: + a (int): The first number to add. + b (int): The second number to add. + + Returns: + int: The sum of the two numbers. + """ + return a + b + + +# DOC102 +def multiply_list_elements(lst): + """ + Multiplies each element in a list by a given multiplier. + + Args: + lst (list of int): A list of integers. + multiplier (int): The multiplier for each element in the list. + + Returns: + list of int: A new list with each element multiplied. + """ + return [x * multiplier for x in lst] + + +# DOC102 +def find_max_value(): + """ + Finds the maximum value in a list of numbers. + + Args: + numbers (list of int): A list of integers to search through. + + Returns: + int: The maximum value found in the list. + """ + return max(numbers) + + +# DOC102 +def create_user_profile(location="here"): + """ + Creates a user profile with basic information. + + Args: + name (str): The name of the user. + age (int): The age of the user. + email (str): The user's email address. + location (str): The location of the user. + + Returns: + dict: A dictionary containing the user's profile. + """ + return { + 'name': name, + 'age': age, + 'email': email, + 'location': location + } + + +# DOC102 +def calculate_total_price(item_prices, discount): + """ + Calculates the total price after applying tax and a discount. + + Args: + item_prices (list of float): A list of prices for each item. + tax_rate (float): The tax rate to apply. + discount (float): The discount to subtract from the total. + + Returns: + float: The final total price after tax and discount. + """ + total = sum(item_prices) + total_with_tax = total + (total * tax_rate) + final_total = total_with_tax - discount + return final_total + + +# DOC102 +def send_email(subject, body, bcc_address=None): + """ + Sends an email to the specified recipients. + + Args: + subject (str): The subject of the email. + body (str): The content of the email. + to_address (str): The recipient's email address. + cc_address (str, optional): The email address for CC. Defaults to None. + bcc_address (str, optional): The email address for BCC. Defaults to None. + + Returns: + bool: True if the email was sent successfully, False otherwise. + """ + return True + + +# DOC102 +def concatenate_strings(*args): + """ + Concatenates multiple strings with a specified separator. + + Args: + separator (str): The separator to use between strings. + *args (str): Variable length argument list of strings to concatenate. + + Returns: + str: A single concatenated string. + """ + return separator.join(args) + + +# DOC102 +def process_order(order_id): + """ + Processes an order with a list of items and optional order details. + + Args: + order_id (int): The unique identifier for the order. + *items (str): Variable length argument list of items in the order. + **details (dict): Additional details such as shipping method and address. + + Returns: + dict: A dictionary containing the order summary. + """ + return { + 'order_id': order_id, + 'items': items, + 'details': details + } + + +class Calculator: + """ + A simple calculator class that can perform basic arithmetic operations. + """ + + # DOC102 + def __init__(self): + """ + Initializes the calculator with an initial value. + + Args: + value (int, optional): The initial value of the calculator. Defaults to 0. + """ + self.value = value + + # DOC102 + def add(self, number2): + """ + Adds a number to the current value. + + Args: + number (int or float): The number to add to the current value. + + Returns: + int or float: The updated value after addition. + """ + self.value += number + number2 + return self.value + + # DOC102 + @classmethod + def from_string(cls): + """ + Creates a Calculator instance from a string representation of a number. + + Args: + value_str (str): The string representing the initial value. + + Returns: + Calculator: A new instance of Calculator initialized with the value from the string. + """ + value = float(value_str) + return cls(value) + + # DOC102 + @staticmethod + def is_valid_number(): + """ + Checks if a given number is valid (int or float). + + Args: + number (any): The value to check. + + Returns: + bool: True if the number is valid, False otherwise. + """ + return isinstance(number, (int, float)) + +# OK +def foo(param1, param2, *args, **kwargs): + """Foo. + + Args: + param1 (int): The first parameter. + param2 (:obj:`str`, optional): The second parameter. Defaults to None. + Second line of description: should be indented. + *args: Variable length argument list. + **kwargs: Arbitrary keyword arguments. + """ + return + +# OK +def on_server_unloaded(self, server_context: ServerContext) -> None: + ''' Execute ``on_server_unloaded`` from ``server_lifecycle.py`` (if + it is defined) when the server cleanly exits. (Before stopping the + server's ``IOLoop``.) + + Args: + server_context (ServerContext) : + + .. warning:: + In practice this code may not run, since servers are often killed + by a signal. + + + ''' + return self._lifecycle_handler.on_server_unloaded(server_context) diff --git a/crates/ruff_linter/resources/test/fixtures/pydoclint/DOC102_numpy.py b/crates/ruff_linter/resources/test/fixtures/pydoclint/DOC102_numpy.py new file mode 100644 index 0000000000000..d21db5a46253d --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pydoclint/DOC102_numpy.py @@ -0,0 +1,257 @@ +# DOC102 +def add_numbers(b): + """ + Adds two numbers and returns the result. + + Parameters + ---------- + a : int + The first number to add. + b : int + The second number to add. + + Returns + ------- + int + The sum of the two numbers. + """ + return a + b + + +# DOC102 +def multiply_list_elements(lst): + """ + Multiplies each element in a list by a given multiplier. + + Parameters + ---------- + lst : list of int + A list of integers. + multiplier : int + The multiplier for each element in the list. + + Returns + ------- + list of int + A new list with each element multiplied. + """ + return [x * multiplier for x in lst] + + +# DOC102 +def find_max_value(): + """ + Finds the maximum value in a list of numbers. + + Parameters + ---------- + numbers : list of int + A list of integers to search through. + + Returns + ------- + int + The maximum value found in the list. + """ + return max(numbers) + + +# DOC102 +def create_user_profile(location="here"): + """ + Creates a user profile with basic information. + + Parameters + ---------- + name : str + The name of the user. + age : int + The age of the user. + email : str + The user's email address. + location : str, optional + The location of the user, by default "here". + + Returns + ------- + dict + A dictionary containing the user's profile. + """ + return { + 'name': name, + 'age': age, + 'email': email, + 'location': location + } + + +# DOC102 +def calculate_total_price(item_prices, discount): + """ + Calculates the total price after applying tax and a discount. + + Parameters + ---------- + item_prices : list of float + A list of prices for each item. + tax_rate : float + The tax rate to apply. + discount : float + The discount to subtract from the total. + + Returns + ------- + float + The final total price after tax and discount. + """ + total = sum(item_prices) + total_with_tax = total + (total * tax_rate) + final_total = total_with_tax - discount + return final_total + + +# DOC102 +def send_email(subject, body, bcc_address=None): + """ + Sends an email to the specified recipients. + + Parameters + ---------- + subject : str + The subject of the email. + body : str + The content of the email. + to_address : str + The recipient's email address. + cc_address : str, optional + The email address for CC, by default None. + bcc_address : str, optional + The email address for BCC, by default None. + + Returns + ------- + bool + True if the email was sent successfully, False otherwise. + """ + return True + + +# DOC102 +def concatenate_strings(*args): + """ + Concatenates multiple strings with a specified separator. + + Parameters + ---------- + separator : str + The separator to use between strings. + *args : str + Variable length argument list of strings to concatenate. + + Returns + ------- + str + A single concatenated string. + """ + return separator.join(args) + + +# DOC102 +def process_order(order_id): + """ + Processes an order with a list of items and optional order details. + + Parameters + ---------- + order_id : int + The unique identifier for the order. + *items : str + Variable length argument list of items in the order. + **details : dict + Additional details such as shipping method and address. + + Returns + ------- + dict + A dictionary containing the order summary. + """ + return { + 'order_id': order_id, + 'items': items, + 'details': details + } + + +class Calculator: + """ + A simple calculator class that can perform basic arithmetic operations. + """ + + # DOC102 + def __init__(self): + """ + Initializes the calculator with an initial value. + + Parameters + ---------- + value : int, optional + The initial value of the calculator, by default 0. + """ + self.value = value + + # DOC102 + def add(self, number2): + """ + Adds two numbers to the current value. + + Parameters + ---------- + number : int or float + The first number to add. + number2 : int or float + The second number to add. + + Returns + ------- + int or float + The updated value after addition. + """ + self.value += number + number2 + return self.value + + # DOC102 + @classmethod + def from_string(cls): + """ + Creates a Calculator instance from a string representation of a number. + + Parameters + ---------- + value_str : str + The string representing the initial value. + + Returns + ------- + Calculator + A new instance of Calculator initialized with the value from the string. + """ + value = float(value_str) + return cls(value) + + # DOC102 + @staticmethod + def is_valid_number(): + """ + Checks if a given number is valid (int or float). + + Parameters + ---------- + number : any + The value to check. + + Returns + ------- + bool + True if the number is valid, False otherwise. + """ + return isinstance(number, (int, float)) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs b/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs index ef2434b3e6643..2cbaa9096d262 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs @@ -84,6 +84,8 @@ pub(crate) fn definitions(checker: &mut Checker) { Rule::UndocumentedPublicPackage, ]); let enforce_pydoclint = checker.any_enabled(&[ + Rule::DocstringMissingParameter, + Rule::DocstringExtraneousParameter, Rule::DocstringMissingReturns, Rule::DocstringExtraneousReturns, Rule::DocstringMissingYields, diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 25c150dfe556d..071463c358726 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -923,6 +923,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (FastApi, "003") => (RuleGroup::Preview, rules::fastapi::rules::FastApiUnusedPathParameter), // pydoclint + (Pydoclint, "101") => (RuleGroup::Preview, rules::pydoclint::rules::DocstringMissingParameter), + (Pydoclint, "102") => (RuleGroup::Preview, rules::pydoclint::rules::DocstringExtraneousParameter), (Pydoclint, "201") => (RuleGroup::Preview, rules::pydoclint::rules::DocstringMissingReturns), (Pydoclint, "202") => (RuleGroup::Preview, rules::pydoclint::rules::DocstringExtraneousReturns), (Pydoclint, "402") => (RuleGroup::Preview, rules::pydoclint::rules::DocstringMissingYields), diff --git a/crates/ruff_linter/src/rules/pydoclint/mod.rs b/crates/ruff_linter/src/rules/pydoclint/mod.rs index 68565de689e19..c60deb56c83bb 100644 --- a/crates/ruff_linter/src/rules/pydoclint/mod.rs +++ b/crates/ruff_linter/src/rules/pydoclint/mod.rs @@ -26,6 +26,8 @@ mod tests { Ok(()) } + #[test_case(Rule::DocstringMissingParameter, Path::new("DOC101_google.py"))] + #[test_case(Rule::DocstringExtraneousParameter, Path::new("DOC102_google.py"))] #[test_case(Rule::DocstringMissingReturns, Path::new("DOC201_google.py"))] #[test_case(Rule::DocstringExtraneousReturns, Path::new("DOC202_google.py"))] #[test_case(Rule::DocstringMissingYields, Path::new("DOC402_google.py"))] @@ -45,6 +47,8 @@ mod tests { Ok(()) } + #[test_case(Rule::DocstringMissingParameter, Path::new("DOC101_numpy.py"))] + #[test_case(Rule::DocstringExtraneousParameter, Path::new("DOC102_numpy.py"))] #[test_case(Rule::DocstringMissingReturns, Path::new("DOC201_numpy.py"))] #[test_case(Rule::DocstringExtraneousReturns, Path::new("DOC202_numpy.py"))] #[test_case(Rule::DocstringMissingYields, Path::new("DOC402_numpy.py"))] diff --git a/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs b/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs index 40d88023033e6..c220840c579da 100644 --- a/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs +++ b/crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs @@ -1,4 +1,5 @@ use itertools::Itertools; +use regex::Regex; use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Violation; use ruff_macros::{derive_message_formats, violation}; @@ -7,6 +8,7 @@ use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::name::QualifiedName; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{self as ast, visitor, Expr, Stmt}; +use ruff_python_semantic::analyze::visibility::is_staticmethod; use ruff_python_semantic::analyze::{function_type, visibility}; use ruff_python_semantic::{Definition, SemanticModel}; use ruff_text_size::{Ranged, TextRange}; @@ -18,6 +20,139 @@ use crate::docstrings::Docstring; use crate::registry::Rule; use crate::rules::pydocstyle::settings::Convention; +/// ## What it does +/// Checks for function docstrings that do not include documentation for all +/// parameters. +/// +/// ## Why is this bad? +/// If a function accepts a parameter without documenting it in its docstring, +/// it can be misleading to users and/or a sign of incomplete documentation or +/// refactors. +/// +/// ## Example +/// ```python +/// def calculate_speed(distance: float, time: float) -> float: +/// """Calculate speed as distance divided by time. +/// +/// Args: +/// distance: Distance traveled. +/// +/// Returns: +/// Speed as distance divided by time. +/// """ +/// return distance / time +/// ``` +/// +/// Use instead: +/// ```python +/// def calculate_speed(distance: float, time: float) -> float: +/// """Calculate speed as distance divided by time. +/// +/// Args: +/// distance: Distance traveled. +/// time: Time spent travelling. +/// +/// Returns: +/// Speed as distance divided by time. +/// """ +/// return distance / time +/// ``` raise FasterThanLightError from exc +/// ``` +#[violation] +pub struct DocstringMissingParameter { + ids: Vec, +} + +impl Violation for DocstringMissingParameter { + #[derive_message_formats] + fn message(&self) -> String { + let DocstringMissingParameter { ids } = self; + + if let [id] = ids.as_slice() { + format!("Parameter `{id}` missing from the docstring") + } else { + format!( + "These parameters are missing from the docstring: {}", + ids.iter().map(|id| format!("`{id}`")).join(", ") + ) + } + } + + fn fix_title(&self) -> Option { + let DocstringMissingParameter { ids } = self; + let s = if ids.len() == 1 { "" } else { "s" }; + Some(format!("Add the missing parameter{s} to the docstring")) + } +} + +/// ## What it does +/// Checks for function docstrings that include parameters which are not +/// in the function signature. +/// +/// ## Why is this bad? +/// If a docstring documents a parameter which is not in the function signature, +/// it can be misleading to users and/or a sign of incomplete documentation or +/// refactors. +/// +/// ## Example +/// ```python +/// def calculate_speed(distance: float, time: float) -> float: +/// """Calculate speed as distance divided by time. +/// +/// Args: +/// distance: Distance traveled. +/// time: Time spent traveling. +/// acceleration: Rate of change of speed. +/// +/// Returns: +/// Speed as distance divided by time. +/// """ +/// return distance / time +/// ``` +/// +/// Use instead: +/// ```python +/// def calculate_speed(distance: float, time: float) -> float: +/// """Calculate speed as distance divided by time. +/// +/// Args: +/// distance: Distance traveled. +/// time: Time spent traveling. +/// +/// Returns: +/// Speed as distance divided by time. +/// """ +/// return distance / time +/// ``` +#[violation] +pub struct DocstringExtraneousParameter { + ids: Vec, +} + +impl Violation for DocstringExtraneousParameter { + #[derive_message_formats] + fn message(&self) -> String { + let DocstringExtraneousParameter { ids } = self; + + if let [id] = ids.as_slice() { + format!("Documented parameter `{id}` is not in the function's signature") + } else { + format!( + "These documented parameters are not in the function's signature: {}", + ids.iter().map(|id| format!("`{id}`")).join(", ") + ) + } + } + + fn fix_title(&self) -> Option { + let DocstringExtraneousParameter { ids } = self; + let s = if ids.len() == 1 { "" } else { "s" }; + Some(format!( + "Remove the extraneous parameter{s} from the docstring" + )) + } +} + /// ## What it does /// Checks for functions with explicit returns missing a "returns" section in /// their docstring. @@ -396,7 +531,31 @@ impl<'a> RaisesSection<'a> { /// a "Raises" section. fn from_section(section: &SectionContext<'a>, style: Option) -> Self { Self { - raised_exceptions: parse_entries(section.following_lines_str(), style), + raised_exceptions: parse_raises(section.following_lines_str(), style), + range: section.range(), + } + } +} + +/// An "Args" or "Parameters" section in a docstring. +#[derive(Debug)] +struct ParametersSection<'a> { + parameters: Vec<&'a str>, + range: TextRange, +} + +impl Ranged for ParametersSection<'_> { + fn range(&self) -> TextRange { + self.range + } +} + +impl<'a> ParametersSection<'a> { + /// Return the parameters for the docstring, or `None` if the docstring does not contain + /// an "Args" or "Parameters" section. + fn from_section(section: &SectionContext<'a>, style: Option) -> Self { + Self { + parameters: parse_parameters(section.following_lines_str(), style), range: section.range(), } } @@ -407,6 +566,7 @@ struct DocstringSections<'a> { returns: Option, yields: Option, raises: Option>, + parameters: Option>, } impl<'a> DocstringSections<'a> { @@ -414,6 +574,10 @@ impl<'a> DocstringSections<'a> { let mut docstring_sections = Self::default(); for section in sections { match section.kind() { + SectionKind::Args | SectionKind::Arguments | SectionKind::Parameters => { + docstring_sections.parameters = + Some(ParametersSection::from_section(§ion, style)); + } SectionKind::Raises => { docstring_sections.raises = Some(RaisesSection::from_section(§ion, style)); } @@ -430,18 +594,110 @@ impl<'a> DocstringSections<'a> { } } +/// Parse the entries in a "Parameters" section of a docstring. +/// +/// Attempts to parse using the specified [`SectionStyle`], falling back to the other style if no +/// entries are found. +fn parse_parameters(content: &str, style: Option) -> Vec<&str> { + match style { + Some(SectionStyle::Google) => parse_parameters_google(content), + Some(SectionStyle::Numpy) => parse_parameters_numpy(content), + None => { + let entries = parse_parameters_google(content); + if entries.is_empty() { + parse_parameters_numpy(content) + } else { + entries + } + } + } +} + +/// Parses Google-style "Args" sections of the form: +/// +/// ```python +/// Args: +/// a (int): The first number to add. +/// b (int): The second number to add. +/// ``` +fn parse_parameters_google(content: &str) -> Vec<&str> { + let mut entries: Vec<&str> = Vec::new(); + // Find first entry to determine indentation + let mut indentation = None; + for potential in content.lines() { + if potential.find(':').is_none() { + continue; + }; + indentation = Some(&potential[..potential.len() - potential.trim_start().len()]); + break; + } + let Some(indentation) = indentation else { + return entries; + }; + for potential in content.lines() { + if let Some(entry) = potential.strip_prefix(indentation) { + if entry + .chars() + .next() + .is_some_and(|first_char| !first_char.is_whitespace()) + { + let Some((before_colon, _)) = entry.split_once(':') else { + continue; + }; + if let Some(param) = before_colon.split_whitespace().next() { + entries.push(param.trim_start_matches('*')); + } + } + } + } + entries +} + +/// Parses NumPy-style "Parameters" sections of the form: +/// +/// ```python +/// Parameters +/// ---------- +/// a : int +/// The first number to add. +/// b : int +/// The second number to add. +/// ``` +fn parse_parameters_numpy(content: &str) -> Vec<&str> { + let mut entries: Vec<&str> = Vec::new(); + let mut lines = content.lines(); + let Some(dashes) = lines.next() else { + return entries; + }; + let indentation = &dashes[..dashes.len() - dashes.trim_start().len()]; + for potential in lines { + if let Some(entry) = potential.strip_prefix(indentation) { + if entry + .chars() + .next() + .is_some_and(|first_char| !first_char.is_whitespace()) + { + if let Some(param) = entry.split(':').next() { + entries.push(param.trim_end().trim_start_matches('*')); + } + } + } + } + entries +} + /// Parse the entries in a "Raises" section of a docstring. /// /// Attempts to parse using the specified [`SectionStyle`], falling back to the other style if no /// entries are found. -fn parse_entries(content: &str, style: Option) -> Vec { +fn parse_raises(content: &str, style: Option) -> Vec { match style { - Some(SectionStyle::Google) => parse_entries_google(content), - Some(SectionStyle::Numpy) => parse_entries_numpy(content), + Some(SectionStyle::Google) => parse_raises_google(content), + Some(SectionStyle::Numpy) => parse_raises_numpy(content), None => { - let entries = parse_entries_google(content); + let entries = parse_raises_google(content); if entries.is_empty() { - parse_entries_numpy(content) + parse_raises_numpy(content) } else { entries } @@ -449,14 +705,14 @@ fn parse_entries(content: &str, style: Option) -> Vec Vec { +fn parse_raises_google(content: &str) -> Vec { let mut entries: Vec = Vec::new(); for potential in content.lines() { let Some(colon_idx) = potential.find(':') else { @@ -468,7 +724,7 @@ fn parse_entries_google(content: &str) -> Vec { entries } -/// Parses NumPy-style docstring sections of the form: +/// Parses NumPy-style "Raises" section of the form: /// /// ```python /// Raises @@ -478,7 +734,7 @@ fn parse_entries_google(content: &str) -> Vec { /// DivisionByZero /// If attempting to divide by zero. /// ``` -fn parse_entries_numpy(content: &str) -> Vec { +fn parse_raises_numpy(content: &str) -> Vec { let mut entries: Vec = Vec::new(); let mut lines = content.lines(); let Some(dashes) = lines.next() else { @@ -827,6 +1083,25 @@ fn is_generator_function_annotated_as_returning_none( .is_some_and(GeneratorOrIteratorArguments::indicates_none_returned) } +fn parameters_from_signature<'a>( + docstring: &'a Docstring, + semantic: &'a SemanticModel, + dummy_variable_rgx: &'a Regex, +) -> Vec<&'a str> { + let mut parameters = Vec::new(); + let Some(function) = docstring.definition.as_function_def() else { + return parameters; + }; + for param in function.parameters.iter().skip(usize::from( + docstring.definition.is_method() && !is_staticmethod(&function.decorator_list, semantic), + )) { + if !dummy_variable_rgx.is_match(param.name()) { + parameters.push(param.name()); + } + } + parameters +} + /// DOC201, DOC202, DOC402, DOC403, DOC501, DOC502 pub(crate) fn check_docstring( checker: &mut Checker, @@ -866,6 +1141,42 @@ pub(crate) fn check_docstring( visitor.finish() }; + let signature_parameters = + parameters_from_signature(docstring, semantic, &checker.settings.dummy_variable_rgx); + + // DOC101 + if checker.enabled(Rule::DocstringMissingParameter) { + let mut missing_parameters = Vec::new(); + for signature_param in &signature_parameters { + if !docstring_sections + .parameters + .as_ref() + .is_some_and(|section| { + section + .parameters + .iter() + .any(|param| param == signature_param) + }) + { + missing_parameters.push((*signature_param).to_string()); + } + } + if !missing_parameters.is_empty() { + let range = if let Some(ref docstring_params) = docstring_sections.parameters { + docstring_params.range() + } else { + docstring.range() + }; + let diagnostic = Diagnostic::new( + DocstringMissingParameter { + ids: missing_parameters, + }, + range, + ); + diagnostics.push(diagnostic); + } + } + // DOC201 if checker.enabled(Rule::DocstringMissingReturns) { if !returns_documented(docstring, &docstring_sections, convention) { @@ -963,6 +1274,30 @@ pub(crate) fn check_docstring( // Avoid applying "extraneous" rules to abstract methods. An abstract method's docstring _could_ // document that it raises an exception without including the exception in the implementation. if !visibility::is_abstract(&function_def.decorator_list, semantic) { + // DOC102 + if checker.enabled(Rule::DocstringExtraneousParameter) { + if let Some(docstring_params) = docstring_sections.parameters { + let mut extraneous_parameters = Vec::new(); + for docstring_param in &docstring_params.parameters { + if !signature_parameters + .iter() + .any(|param| param == docstring_param) + { + extraneous_parameters.push((*docstring_param).to_string()); + } + } + if !extraneous_parameters.is_empty() { + let diagnostic = Diagnostic::new( + DocstringExtraneousParameter { + ids: extraneous_parameters, + }, + docstring_params.range(), + ); + diagnostics.push(diagnostic); + } + } + } + // DOC202 if checker.enabled(Rule::DocstringExtraneousReturns) { if let Some(ref docstring_returns) = docstring_sections.returns { diff --git a/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-extraneous-parameter_DOC102_google.py.snap b/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-extraneous-parameter_DOC102_google.py.snap new file mode 100644 index 0000000000000..a5919824f8450 --- /dev/null +++ b/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-extraneous-parameter_DOC102_google.py.snap @@ -0,0 +1,182 @@ +--- +source: crates/ruff_linter/src/rules/pydoclint/mod.rs +--- +DOC102_google.py:6:1: DOC102 Documented parameter `a` is not in the function's signature + | + 4 | Adds two numbers and returns the result. + 5 | + 6 | / Args: + 7 | | a (int): The first number to add. + 8 | | b (int): The second number to add. + 9 | | +10 | | Returns: + | |_^ DOC102 +11 | int: The sum of the two numbers. +12 | """ + | + = help: Remove the extraneous parameter from the docstring + +DOC102_google.py:21:1: DOC102 Documented parameter `multiplier` is not in the function's signature + | +19 | Multiplies each element in a list by a given multiplier. +20 | +21 | / Args: +22 | | lst (list of int): A list of integers. +23 | | multiplier (int): The multiplier for each element in the list. +24 | | +25 | | Returns: + | |_^ DOC102 +26 | list of int: A new list with each element multiplied. +27 | """ + | + = help: Remove the extraneous parameter from the docstring + +DOC102_google.py:36:1: DOC102 Documented parameter `numbers` is not in the function's signature + | +34 | Finds the maximum value in a list of numbers. +35 | +36 | / Args: +37 | | numbers (list of int): A list of integers to search through. +38 | | +39 | | Returns: + | |_^ DOC102 +40 | int: The maximum value found in the list. +41 | """ + | + = help: Remove the extraneous parameter from the docstring + +DOC102_google.py:50:1: DOC102 These documented parameters are not in the function's signature: `name`, `age`, `email` + | +48 | Creates a user profile with basic information. +49 | +50 | / Args: +51 | | name (str): The name of the user. +52 | | age (int): The age of the user. +53 | | email (str): The user's email address. +54 | | location (str): The location of the user. +55 | | +56 | | Returns: + | |_^ DOC102 +57 | dict: A dictionary containing the user's profile. +58 | """ + | + = help: Remove the extraneous parameters from the docstring + +DOC102_google.py:72:1: DOC102 Documented parameter `tax_rate` is not in the function's signature + | +70 | Calculates the total price after applying tax and a discount. +71 | +72 | / Args: +73 | | item_prices (list of float): A list of prices for each item. +74 | | tax_rate (float): The tax rate to apply. +75 | | discount (float): The discount to subtract from the total. +76 | | +77 | | Returns: + | |_^ DOC102 +78 | float: The final total price after tax and discount. +79 | """ + | + = help: Remove the extraneous parameter from the docstring + +DOC102_google.py:91:1: DOC102 These documented parameters are not in the function's signature: `to_address`, `cc_address` + | + 89 | Sends an email to the specified recipients. + 90 | + 91 | / Args: + 92 | | subject (str): The subject of the email. + 93 | | body (str): The content of the email. + 94 | | to_address (str): The recipient's email address. + 95 | | cc_address (str, optional): The email address for CC. Defaults to None. + 96 | | bcc_address (str, optional): The email address for BCC. Defaults to None. + 97 | | + 98 | | Returns: + | |_^ DOC102 + 99 | bool: True if the email was sent successfully, False otherwise. +100 | """ + | + = help: Remove the extraneous parameters from the docstring + +DOC102_google.py:109:1: DOC102 Documented parameter `separator` is not in the function's signature + | +107 | Concatenates multiple strings with a specified separator. +108 | +109 | / Args: +110 | | separator (str): The separator to use between strings. +111 | | *args (str): Variable length argument list of strings to concatenate. +112 | | +113 | | Returns: + | |_^ DOC102 +114 | str: A single concatenated string. +115 | """ + | + = help: Remove the extraneous parameter from the docstring + +DOC102_google.py:124:1: DOC102 These documented parameters are not in the function's signature: `items`, `details` + | +122 | Processes an order with a list of items and optional order details. +123 | +124 | / Args: +125 | | order_id (int): The unique identifier for the order. +126 | | *items (str): Variable length argument list of items in the order. +127 | | **details (dict): Additional details such as shipping method and address. +128 | | +129 | | Returns: + | |_^ DOC102 +130 | dict: A dictionary containing the order summary. +131 | """ + | + = help: Remove the extraneous parameters from the docstring + +DOC102_google.py:149:1: DOC102 Documented parameter `value` is not in the function's signature + | +147 | Initializes the calculator with an initial value. +148 | +149 | / Args: +150 | | value (int, optional): The initial value of the calculator. Defaults to 0. +151 | | """ + | |________^ DOC102 +152 | self.value = value + | + = help: Remove the extraneous parameter from the docstring + +DOC102_google.py:159:1: DOC102 Documented parameter `number` is not in the function's signature + | +157 | Adds a number to the current value. +158 | +159 | / Args: +160 | | number (int or float): The number to add to the current value. +161 | | +162 | | Returns: + | |_^ DOC102 +163 | int or float: The updated value after addition. +164 | """ + | + = help: Remove the extraneous parameter from the docstring + +DOC102_google.py:174:1: DOC102 Documented parameter `value_str` is not in the function's signature + | +172 | Creates a Calculator instance from a string representation of a number. +173 | +174 | / Args: +175 | | value_str (str): The string representing the initial value. +176 | | +177 | | Returns: + | |_^ DOC102 +178 | Calculator: A new instance of Calculator initialized with the value from the string. +179 | """ + | + = help: Remove the extraneous parameter from the docstring + +DOC102_google.py:189:1: DOC102 Documented parameter `number` is not in the function's signature + | +187 | Checks if a given number is valid (int or float). +188 | +189 | / Args: +190 | | number (any): The value to check. +191 | | +192 | | Returns: + | |_^ DOC102 +193 | bool: True if the number is valid, False otherwise. +194 | """ + | + = help: Remove the extraneous parameter from the docstring diff --git a/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-extraneous-parameter_DOC102_numpy.py.snap b/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-extraneous-parameter_DOC102_numpy.py.snap new file mode 100644 index 0000000000000..6596d97a364c7 --- /dev/null +++ b/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-extraneous-parameter_DOC102_numpy.py.snap @@ -0,0 +1,222 @@ +--- +source: crates/ruff_linter/src/rules/pydoclint/mod.rs +--- +DOC102_numpy.py:6:1: DOC102 Documented parameter `a` is not in the function's signature + | + 4 | Adds two numbers and returns the result. + 5 | + 6 | / Parameters + 7 | | ---------- + 8 | | a : int + 9 | | The first number to add. +10 | | b : int +11 | | The second number to add. +12 | | +13 | | Returns + | |_^ DOC102 +14 | ------- +15 | int + | + = help: Remove the extraneous parameter from the docstring + +DOC102_numpy.py:26:1: DOC102 Documented parameter `multiplier` is not in the function's signature + | +24 | Multiplies each element in a list by a given multiplier. +25 | +26 | / Parameters +27 | | ---------- +28 | | lst : list of int +29 | | A list of integers. +30 | | multiplier : int +31 | | The multiplier for each element in the list. +32 | | +33 | | Returns + | |_^ DOC102 +34 | ------- +35 | list of int + | + = help: Remove the extraneous parameter from the docstring + +DOC102_numpy.py:46:1: DOC102 Documented parameter `numbers` is not in the function's signature + | +44 | Finds the maximum value in a list of numbers. +45 | +46 | / Parameters +47 | | ---------- +48 | | numbers : list of int +49 | | A list of integers to search through. +50 | | +51 | | Returns + | |_^ DOC102 +52 | ------- +53 | int + | + = help: Remove the extraneous parameter from the docstring + +DOC102_numpy.py:64:1: DOC102 These documented parameters are not in the function's signature: `name`, `age`, `email` + | +62 | Creates a user profile with basic information. +63 | +64 | / Parameters +65 | | ---------- +66 | | name : str +67 | | The name of the user. +68 | | age : int +69 | | The age of the user. +70 | | email : str +71 | | The user's email address. +72 | | location : str, optional +73 | | The location of the user, by default "here". +74 | | +75 | | Returns + | |_^ DOC102 +76 | ------- +77 | dict + | + = help: Remove the extraneous parameters from the docstring + +DOC102_numpy.py:93:1: DOC102 Documented parameter `tax_rate` is not in the function's signature + | + 91 | Calculates the total price after applying tax and a discount. + 92 | + 93 | / Parameters + 94 | | ---------- + 95 | | item_prices : list of float + 96 | | A list of prices for each item. + 97 | | tax_rate : float + 98 | | The tax rate to apply. + 99 | | discount : float +100 | | The discount to subtract from the total. +101 | | +102 | | Returns + | |_^ DOC102 +103 | ------- +104 | float + | + = help: Remove the extraneous parameter from the docstring + +DOC102_numpy.py:118:1: DOC102 These documented parameters are not in the function's signature: `to_address`, `cc_address` + | +116 | Sends an email to the specified recipients. +117 | +118 | / Parameters +119 | | ---------- +120 | | subject : str +121 | | The subject of the email. +122 | | body : str +123 | | The content of the email. +124 | | to_address : str +125 | | The recipient's email address. +126 | | cc_address : str, optional +127 | | The email address for CC, by default None. +128 | | bcc_address : str, optional +129 | | The email address for BCC, by default None. +130 | | +131 | | Returns + | |_^ DOC102 +132 | ------- +133 | bool + | + = help: Remove the extraneous parameters from the docstring + +DOC102_numpy.py:144:1: DOC102 Documented parameter `separator` is not in the function's signature + | +142 | Concatenates multiple strings with a specified separator. +143 | +144 | / Parameters +145 | | ---------- +146 | | separator : str +147 | | The separator to use between strings. +148 | | *args : str +149 | | Variable length argument list of strings to concatenate. +150 | | +151 | | Returns + | |_^ DOC102 +152 | ------- +153 | str + | + = help: Remove the extraneous parameter from the docstring + +DOC102_numpy.py:164:1: DOC102 These documented parameters are not in the function's signature: `items`, `details` + | +162 | Processes an order with a list of items and optional order details. +163 | +164 | / Parameters +165 | | ---------- +166 | | order_id : int +167 | | The unique identifier for the order. +168 | | *items : str +169 | | Variable length argument list of items in the order. +170 | | **details : dict +171 | | Additional details such as shipping method and address. +172 | | +173 | | Returns + | |_^ DOC102 +174 | ------- +175 | dict + | + = help: Remove the extraneous parameters from the docstring + +DOC102_numpy.py:195:1: DOC102 Documented parameter `value` is not in the function's signature + | +193 | Initializes the calculator with an initial value. +194 | +195 | / Parameters +196 | | ---------- +197 | | value : int, optional +198 | | The initial value of the calculator, by default 0. +199 | | """ + | |________^ DOC102 +200 | self.value = value + | + = help: Remove the extraneous parameter from the docstring + +DOC102_numpy.py:207:1: DOC102 Documented parameter `number` is not in the function's signature + | +205 | Adds two numbers to the current value. +206 | +207 | / Parameters +208 | | ---------- +209 | | number : int or float +210 | | The first number to add. +211 | | number2 : int or float +212 | | The second number to add. +213 | | +214 | | Returns + | |_^ DOC102 +215 | ------- +216 | int or float + | + = help: Remove the extraneous parameter from the docstring + +DOC102_numpy.py:228:1: DOC102 Documented parameter `value_str` is not in the function's signature + | +226 | Creates a Calculator instance from a string representation of a number. +227 | +228 | / Parameters +229 | | ---------- +230 | | value_str : str +231 | | The string representing the initial value. +232 | | +233 | | Returns + | |_^ DOC102 +234 | ------- +235 | Calculator + | + = help: Remove the extraneous parameter from the docstring + +DOC102_numpy.py:247:1: DOC102 Documented parameter `number` is not in the function's signature + | +245 | Checks if a given number is valid (int or float). +246 | +247 | / Parameters +248 | | ---------- +249 | | number : any +250 | | The value to check. +251 | | +252 | | Returns + | |_^ DOC102 +253 | ------- +254 | bool + | + = help: Remove the extraneous parameter from the docstring diff --git a/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-missing-parameter_DOC101_google.py.snap b/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-missing-parameter_DOC101_google.py.snap new file mode 100644 index 0000000000000..63769d07bacaf --- /dev/null +++ b/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-missing-parameter_DOC101_google.py.snap @@ -0,0 +1,182 @@ +--- +source: crates/ruff_linter/src/rules/pydoclint/mod.rs +--- +DOC101_google.py:203:1: DOC101 Parameter `b` missing from the docstring + | +201 | Adds two numbers and returns the result. +202 | +203 | / Args: +204 | | a (int): The first number to add. +205 | | +206 | | Returns: + | |_^ DOC101 +207 | int: The sum of the two numbers. +208 | """ + | + = help: Add the missing parameter to the docstring + +DOC101_google.py:217:1: DOC101 Parameter `multiplier` missing from the docstring + | +215 | Multiplies each element in a list by a given multiplier. +216 | +217 | / Args: +218 | | lst (list of int): A list of integers. +219 | | +220 | | Returns: + | |_^ DOC101 +221 | list of int: A new list with each element multiplied. +222 | """ + | + = help: Add the missing parameter to the docstring + +DOC101_google.py:228:5: DOC101 Parameter `numbers` missing from the docstring + | +226 | # DOC101 +227 | def find_max_value(numbers): +228 | """ + | _____^ +229 | | Finds the maximum value in a list of numbers. +230 | | +231 | | Returns: +232 | | int: The maximum value found in the list. +233 | | """ + | |_______^ DOC101 +234 | return max(numbers) + | + = help: Add the missing parameter to the docstring + +DOC101_google.py:242:1: DOC101 These parameters are missing from the docstring: `name`, `age` + | +240 | Creates a user profile with basic information. +241 | +242 | / Args: +243 | | email (str): The user's email address. +244 | | location (str): The location of the user. +245 | | +246 | | Returns: + | |_^ DOC101 +247 | dict: A dictionary containing the user's profile. +248 | """ + | + = help: Add the missing parameters to the docstring + +DOC101_google.py:262:1: DOC101 These parameters are missing from the docstring: `tax_rate`, `discount` + | +260 | Calculates the total price after applying tax and a discount. +261 | +262 | / Args: +263 | | item_prices (list of float): A list of prices for each item. +264 | | +265 | | Returns: + | |_^ DOC101 +266 | float: The final total price after tax and discount. +267 | """ + | + = help: Add the missing parameters to the docstring + +DOC101_google.py:279:1: DOC101 These parameters are missing from the docstring: `cc_address`, `bcc_address` + | +277 | Sends an email to the specified recipients. +278 | +279 | / Args: +280 | | subject (str): The subject of the email. +281 | | body (str): The content of the email. +282 | | to_address (str): The recipient's email address. +283 | | +284 | | Returns: + | |_^ DOC101 +285 | bool: True if the email was sent successfully, False otherwise. +286 | """ + | + = help: Add the missing parameters to the docstring + +DOC101_google.py:295:1: DOC101 Parameter `args` missing from the docstring + | +293 | Concatenates multiple strings with a specified separator. +294 | +295 | / Args: +296 | | separator (str): The separator to use between strings. +297 | | +298 | | Returns: + | |_^ DOC101 +299 | str: A single concatenated string. +300 | """ + | + = help: Add the missing parameter to the docstring + +DOC101_google.py:309:1: DOC101 These parameters are missing from the docstring: `items`, `details` + | +307 | Processes an order with a list of items and optional order details. +308 | +309 | / Args: +310 | | order_id (int): The unique identifier for the order. +311 | | +312 | | Returns: + | |_^ DOC101 +313 | dict: A dictionary containing the order summary. +314 | """ + | + = help: Add the missing parameters to the docstring + +DOC101_google.py:329:9: DOC101 Parameter `value` missing from the docstring + | +327 | # DOC101 +328 | def __init__(self, value=0): +329 | """ + | _________^ +330 | | Initializes the calculator with an initial value. +331 | | +332 | | Returns: +333 | | None +334 | | """ + | |___________^ DOC101 +335 | self.value = value + | + = help: Add the missing parameter to the docstring + +DOC101_google.py:342:1: DOC101 Parameter `number2` missing from the docstring + | +340 | Adds a number to the current value. +341 | +342 | / Args: +343 | | number (int or float): The number to add to the current value. +344 | | +345 | | Returns: + | |_^ DOC101 +346 | int or float: The updated value after addition. +347 | """ + | + = help: Add the missing parameter to the docstring + +DOC101_google.py:354:9: DOC101 Parameter `value_str` missing from the docstring + | +352 | @classmethod +353 | def from_string(cls, value_str): +354 | """ + | _________^ +355 | | Creates a Calculator instance from a string representation of a number. +356 | | +357 | | Returns: +358 | | Calculator: A new instance of Calculator initialized with the value from the string. +359 | | """ + | |___________^ DOC101 +360 | value = float(value_str) +361 | return cls(value) + | + = help: Add the missing parameter to the docstring + +DOC101_google.py:366:9: DOC101 Parameter `number` missing from the docstring + | +364 | @staticmethod +365 | def is_valid_number(number): +366 | """ + | _________^ +367 | | Checks if a given number is valid (int or float). +368 | | +369 | | Returns: +370 | | bool: True if the number is valid, False otherwise. +371 | | """ + | |___________^ DOC101 +372 | return isinstance(number, (int, float)) + | + = help: Add the missing parameter to the docstring diff --git a/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-missing-parameter_DOC101_numpy.py.snap b/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-missing-parameter_DOC101_numpy.py.snap new file mode 100644 index 0000000000000..965d65258d76f --- /dev/null +++ b/crates/ruff_linter/src/rules/pydoclint/snapshots/ruff_linter__rules__pydoclint__tests__docstring-missing-parameter_DOC101_numpy.py.snap @@ -0,0 +1,205 @@ +--- +source: crates/ruff_linter/src/rules/pydoclint/mod.rs +--- +DOC101_numpy.py:265:1: DOC101 Parameter `b` missing from the docstring + | +263 | Adds two numbers and returns the result. +264 | +265 | / Parameters +266 | | ---------- +267 | | a : int +268 | | The first number to add. +269 | | +270 | | Returns + | |_^ DOC101 +271 | ------- +272 | int + | + = help: Add the missing parameter to the docstring + +DOC101_numpy.py:283:1: DOC101 Parameter `multiplier` missing from the docstring + | +281 | Multiplies each element in a list by a given multiplier. +282 | +283 | / Parameters +284 | | ---------- +285 | | lst : list of int +286 | | A list of integers. +287 | | +288 | | Returns + | |_^ DOC101 +289 | ------- +290 | list of int + | + = help: Add the missing parameter to the docstring + +DOC101_numpy.py:298:5: DOC101 Parameter `numbers` missing from the docstring + | +296 | # DOC101 +297 | def find_max_value(numbers): +298 | """ + | _____^ +299 | | Finds the maximum value in a list of numbers. +300 | | +301 | | Returns +302 | | ------- +303 | | int +304 | | The maximum value found in the list. +305 | | """ + | |_______^ DOC101 +306 | return max(numbers) + | + = help: Add the missing parameter to the docstring + +DOC101_numpy.py:314:1: DOC101 These parameters are missing from the docstring: `name`, `age` + | +312 | Creates a user profile with basic information. +313 | +314 | / Parameters +315 | | ---------- +316 | | email : str +317 | | The user's email address. +318 | | location : str, optional +319 | | The location of the user, by default "here". +320 | | +321 | | Returns + | |_^ DOC101 +322 | ------- +323 | dict + | + = help: Add the missing parameters to the docstring + +DOC101_numpy.py:339:1: DOC101 These parameters are missing from the docstring: `tax_rate`, `discount` + | +337 | Calculates the total price after applying tax and a discount. +338 | +339 | / Parameters +340 | | ---------- +341 | | item_prices : list of float +342 | | A list of prices for each item. +343 | | +344 | | Returns + | |_^ DOC101 +345 | ------- +346 | float + | + = help: Add the missing parameters to the docstring + +DOC101_numpy.py:360:1: DOC101 These parameters are missing from the docstring: `cc_address`, `bcc_address` + | +358 | Sends an email to the specified recipients. +359 | +360 | / Parameters +361 | | ---------- +362 | | subject : str +363 | | The subject of the email. +364 | | body : str +365 | | The content of the email. +366 | | to_address : str +367 | | The recipient's email address. +368 | | +369 | | Returns + | |_^ DOC101 +370 | ------- +371 | bool + | + = help: Add the missing parameters to the docstring + +DOC101_numpy.py:382:1: DOC101 Parameter `args` missing from the docstring + | +380 | Concatenates multiple strings with a specified separator. +381 | +382 | / Parameters +383 | | ---------- +384 | | separator : str +385 | | The separator to use between strings. +386 | | +387 | | Returns + | |_^ DOC101 +388 | ------- +389 | str + | + = help: Add the missing parameter to the docstring + +DOC101_numpy.py:400:1: DOC101 These parameters are missing from the docstring: `items`, `details` + | +398 | Processes an order with a list of items and optional order details. +399 | +400 | / Parameters +401 | | ---------- +402 | | order_id : int +403 | | The unique identifier for the order. +404 | | +405 | | Returns + | |_^ DOC101 +406 | ------- +407 | dict + | + = help: Add the missing parameters to the docstring + +DOC101_numpy.py:424:9: DOC101 Parameter `value` missing from the docstring + | +422 | # DOC101 +423 | def __init__(self, value=0): +424 | """ + | _________^ +425 | | Initializes the calculator with an initial value. +426 | | +427 | | """ + | |___________^ DOC101 +428 | self.value = value + | + = help: Add the missing parameter to the docstring + +DOC101_numpy.py:435:1: DOC101 Parameter `number2` missing from the docstring + | +433 | Adds two numbers to the current value. +434 | +435 | / Parameters +436 | | ---------- +437 | | number : int or float +438 | | The first number to add. +439 | | +440 | | Returns + | |_^ DOC101 +441 | ------- +442 | int or float + | + = help: Add the missing parameter to the docstring + +DOC101_numpy.py:451:9: DOC101 Parameter `value_str` missing from the docstring + | +449 | @classmethod +450 | def from_string(cls, value_str): +451 | """ + | _________^ +452 | | Creates a Calculator instance from a string representation of a number. +453 | | +454 | | Returns +455 | | ------- +456 | | Calculator +457 | | A new instance of Calculator initialized with the value from the string. +458 | | """ + | |___________^ DOC101 +459 | value = float(value_str) +460 | return cls(value) + | + = help: Add the missing parameter to the docstring + +DOC101_numpy.py:465:9: DOC101 Parameter `number` missing from the docstring + | +463 | @staticmethod +464 | def is_valid_number(number): +465 | """ + | _________^ +466 | | Checks if a given number is valid (int or float). +467 | | +468 | | Returns +469 | | ------- +470 | | bool +471 | | True if the number is valid, False otherwise. +472 | | """ + | |___________^ DOC101 +473 | return isinstance(number, (int, float)) + | + = help: Add the missing parameter to the docstring diff --git a/ruff.schema.json b/ruff.schema.json index c39a68ebd6aa7..93667ef0b6fb5 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2917,6 +2917,10 @@ "DJ012", "DJ013", "DOC", + "DOC1", + "DOC10", + "DOC101", + "DOC102", "DOC2", "DOC20", "DOC201",