docstringify 1.1.0__tar.gz → 1.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {docstringify-1.1.0 → docstringify-1.1.1}/PKG-INFO +1 -1
  2. {docstringify-1.1.0 → docstringify-1.1.1}/pyproject.toml +1 -1
  3. {docstringify-1.1.0 → docstringify-1.1.1}/src/docstringify/nodes/base.py +10 -0
  4. {docstringify-1.1.0 → docstringify-1.1.1}/src/docstringify/traversal/transformer.py +55 -9
  5. {docstringify-1.1.0 → docstringify-1.1.1}/uv.lock +1 -1
  6. {docstringify-1.1.0 → docstringify-1.1.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  7. {docstringify-1.1.0 → docstringify-1.1.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  8. {docstringify-1.1.0 → docstringify-1.1.1}/.github/dependabot.yml +0 -0
  9. {docstringify-1.1.0 → docstringify-1.1.1}/.github/pull_request_template.md +0 -0
  10. {docstringify-1.1.0 → docstringify-1.1.1}/.github/release.yml +0 -0
  11. {docstringify-1.1.0 → docstringify-1.1.1}/.github/workflows/ci.yml +0 -0
  12. {docstringify-1.1.0 → docstringify-1.1.1}/.github/workflows/greetings.yml +0 -0
  13. {docstringify-1.1.0 → docstringify-1.1.1}/.github/workflows/pypi-publish.yml +0 -0
  14. {docstringify-1.1.0 → docstringify-1.1.1}/.gitignore +0 -0
  15. {docstringify-1.1.0 → docstringify-1.1.1}/.pre-commit-config.yaml +0 -0
  16. {docstringify-1.1.0 → docstringify-1.1.1}/.pre-commit-hooks.yaml +0 -0
  17. {docstringify-1.1.0 → docstringify-1.1.1}/.python-version +0 -0
  18. {docstringify-1.1.0 → docstringify-1.1.1}/CODE_OF_CONDUCT.md +0 -0
  19. {docstringify-1.1.0 → docstringify-1.1.1}/CONTRIBUTING.md +0 -0
  20. {docstringify-1.1.0 → docstringify-1.1.1}/LICENSE +0 -0
  21. {docstringify-1.1.0 → docstringify-1.1.1}/README.md +0 -0
  22. {docstringify-1.1.0 → docstringify-1.1.1}/src/docstringify/__init__.py +0 -0
  23. {docstringify-1.1.0 → docstringify-1.1.1}/src/docstringify/__main__.py +0 -0
  24. {docstringify-1.1.0 → docstringify-1.1.1}/src/docstringify/cli.py +0 -0
  25. {docstringify-1.1.0 → docstringify-1.1.1}/src/docstringify/components.py +0 -0
  26. {docstringify-1.1.0 → docstringify-1.1.1}/src/docstringify/converters/__init__.py +0 -0
  27. {docstringify-1.1.0 → docstringify-1.1.1}/src/docstringify/converters/base.py +0 -0
  28. {docstringify-1.1.0 → docstringify-1.1.1}/src/docstringify/converters/google.py +0 -0
  29. {docstringify-1.1.0 → docstringify-1.1.1}/src/docstringify/converters/numpydoc.py +0 -0
  30. {docstringify-1.1.0 → docstringify-1.1.1}/src/docstringify/converters/stub.py +0 -0
  31. {docstringify-1.1.0 → docstringify-1.1.1}/src/docstringify/exceptions.py +0 -0
  32. {docstringify-1.1.0 → docstringify-1.1.1}/src/docstringify/nodes/__init__.py +0 -0
  33. {docstringify-1.1.0 → docstringify-1.1.1}/src/docstringify/nodes/function.py +0 -0
  34. {docstringify-1.1.0 → docstringify-1.1.1}/src/docstringify/traversal/__init__.py +0 -0
  35. {docstringify-1.1.0 → docstringify-1.1.1}/src/docstringify/traversal/visitor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docstringify
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: Flag missing docstrings and, optionally, generate them from signatures and type annotations.
5
5
  Project-URL: Documentation, https://github.com/stefmolin/docstringify
6
6
  Project-URL: Homepage, https://github.com/stefmolin/docstringify
@@ -5,7 +5,7 @@ requires = [ "hatchling" ]
5
5
 
6
6
  [project]
7
7
  name = "docstringify"
8
- version = "1.1.0"
8
+ version = "1.1.1"
9
9
  description = "Flag missing docstrings and, optionally, generate them from signatures and type annotations."
10
10
  readme = "README.md"
11
11
  keywords = [
@@ -70,6 +70,16 @@ class DocstringNode:
70
70
  )
71
71
  """Callable to get the source code for the AST node (:attr:`.ast_node`)."""
72
72
 
73
+ self.original_docstring_location: tuple[int, int] | None = (
74
+ None
75
+ if self.docstring is None
76
+ else (self.ast_node.body[0].lineno, self.ast_node.body[0].end_lineno)
77
+ )
78
+ """Start and end line of the docstring in the original file, if there was one,
79
+ otherwise, ``None``. This is necessary for the rewriting algorithm to exclude
80
+ lines containing "empty" docstrings from the file. Empty docstrings are empty
81
+ strings or strings containing only whitespace characters."""
82
+
73
83
  @property
74
84
  def docstring(self) -> str | None:
75
85
  """
@@ -6,6 +6,7 @@ on the source code.
6
6
  from __future__ import annotations
7
7
 
8
8
  import ast
9
+ import itertools
9
10
  from textwrap import indent
10
11
  from typing import TYPE_CHECKING
11
12
 
@@ -79,6 +80,7 @@ class DocstringTransformer(ast.NodeTransformer, DocstringVisitor):
79
80
  source_code = self.source_code.splitlines()
80
81
  output_lines = []
81
82
  write_line = start_line = 0
83
+ skip_lines = None
82
84
 
83
85
  for missing_docstring in self.missing_docstrings:
84
86
  docstring_node = missing_docstring.ast_node.body[0]
@@ -88,28 +90,72 @@ class DocstringTransformer(ast.NodeTransformer, DocstringVisitor):
88
90
  # write everything before docstring
89
91
  if not isinstance(missing_docstring.ast_node, ast.Module):
90
92
  # line before a code node
91
- start_line = missing_docstring.ast_node.body[1].lineno - 1
92
-
93
- if isinstance(missing_docstring.ast_node, ast.ClassDef):
94
- start_line = docstring_node.lineno
95
-
93
+ skip_lines = missing_docstring.original_docstring_location
94
+
95
+ if skip_lines:
96
+ start_line = skip_lines[-1]
97
+ else:
98
+ # search for the start of the body code (may be comment, blank line,
99
+ # an empty docstring, etc.). restrict the search to between the
100
+ # class/function definition start and the first body item
101
+ search_end_line_number = missing_docstring.ast_node.body[1].lineno
102
+ node_source_code = missing_docstring.get_source_segment(
103
+ missing_docstring.ast_node
104
+ ).splitlines()[
105
+ : search_end_line_number - missing_docstring.ast_node.lineno
106
+ ]
107
+
108
+ expected_indent = ' ' * 4
109
+ for line_number, line in zip(
110
+ itertools.count(start=search_end_line_number, step=-1),
111
+ node_source_code[::-1],
112
+ strict=False,
113
+ ):
114
+ # starting from the end line of the search to the definition
115
+ # line, the first, non-empty line without the required indent
116
+ # will be the start of the body logic
117
+ if line and not line.startswith(expected_indent):
118
+ start_line = line_number
119
+ break
120
+
121
+ if start_line == missing_docstring.ast_node.body[1].lineno:
122
+ # the above logic handles multiline definitions, but if it is
123
+ # a single line and code is immediately afterward, we need to
124
+ # make sure to not overwrite that
125
+ start_line = docstring_node.lineno
126
+ elif (
127
+ skip_lines is None
128
+ and start_line < missing_docstring.ast_node.body[1].lineno
129
+ ):
130
+ start_line -= 1
131
+
132
+ # handle edge case of "single line" definitions like `def test(): ...`
96
133
  if len(body := missing_docstring.ast_node.body) == 2:
97
134
  code_node = body[1]
98
135
  line_number = code_node.lineno - 1
99
136
  line = source_code[line_number]
100
137
 
101
138
  if line.strip() != (function_body := line[code_node.col_offset :]):
102
- # if the function body is on the same line as the signature, cut it out
103
- # in order to inject the docstring in the right spot
139
+ # if the function body is on the same line as the signature,
140
+ # cut it out in order to inject the docstring in the right spot
104
141
  source_code[line_number] = source_code[line_number][
105
142
  : code_node.col_offset
106
143
  ].rstrip()
107
144
 
108
145
  # add the logic under the docstring
109
- start_line = missing_docstring.ast_node.body[1].lineno
146
+ start_line = body[1].lineno
110
147
  suffix = function_body
111
148
 
112
- output_lines += source_code[write_line:start_line]
149
+ if skip_lines:
150
+ # need to skip over the empty docstring in the original file
151
+ # note that line numbers are one-based so we have to account for that
152
+ # in the slicing logic
153
+ output_lines += (
154
+ source_code[write_line : skip_lines[0] - 1]
155
+ + source_code[skip_lines[1] : start_line]
156
+ )
157
+ else:
158
+ output_lines += source_code[write_line:start_line]
113
159
 
114
160
  # write docstring
115
161
  if not (docstring := missing_docstring.docstring):
@@ -110,7 +110,7 @@ wheels = [
110
110
 
111
111
  [[package]]
112
112
  name = "docstringify"
113
- version = "1.1.0"
113
+ version = "1.1.1"
114
114
  source = { editable = "." }
115
115
 
116
116
  [package.dev-dependencies]
File without changes
File without changes
File without changes