|
9 | 9 |
|
10 | 10 | from pydocstringformatter import __version__, _formatting, _utils
|
11 | 11 | from pydocstringformatter._configuration.arguments_manager import ArgumentsManager
|
| 12 | +from pydocstringformatter._utils.exceptions import UnstableResultError |
12 | 13 |
|
13 | 14 |
|
14 | 15 | class _Run:
|
@@ -64,7 +65,7 @@ def format_file(self, filename: Path) -> bool:
|
64 | 65 | # the same later on.
|
65 | 66 | newlines = file.newlines
|
66 | 67 |
|
67 |
| - formatted_tokens, is_changed = self.format_file_tokens(tokens) |
| 68 | + formatted_tokens, is_changed = self.format_file_tokens(tokens, filename) |
68 | 69 |
|
69 | 70 | if is_changed:
|
70 | 71 | try:
|
@@ -112,30 +113,91 @@ def get_enabled_formatters(self) -> dict[str, _formatting.Formatter]:
|
112 | 113 | return enabled
|
113 | 114 |
|
114 | 115 | def format_file_tokens(
|
115 |
| - self, tokens: list[tokenize.TokenInfo] |
| 116 | + self, tokens: list[tokenize.TokenInfo], filename: Path |
116 | 117 | ) -> tuple[list[tokenize.TokenInfo], bool]:
|
117 |
| - """Format a list of tokens.""" |
| 118 | + """Format a list of tokens. |
| 119 | +
|
| 120 | + tokens: List of tokens to format. |
| 121 | + filename: Name of the file the tokens are from. |
| 122 | +
|
| 123 | + Returns: |
| 124 | + A tuple containing [1] the formatted tokens in a list |
| 125 | + and [2] a boolean indicating if the tokens were changed. |
| 126 | +
|
| 127 | + Raises: |
| 128 | + UnstableResultError:: |
| 129 | + If the formatters are not able to get to a stable result. |
| 130 | + It reports what formatters are still modifying the tokens. |
| 131 | + """ |
118 | 132 | formatted_tokens: list[tokenize.TokenInfo] = []
|
119 | 133 | is_changed = False
|
120 | 134 |
|
121 | 135 | for index, tokeninfo in enumerate(tokens):
|
122 | 136 | new_tokeninfo = tokeninfo
|
123 | 137 |
|
124 | 138 | if _utils.is_docstring(new_tokeninfo, tokens[index - 1]):
|
125 |
| - for _, formatter in self.enabled_formatters.items(): |
126 |
| - new_tokeninfo = formatter.treat_token(new_tokeninfo) |
127 |
| - formatted_tokens.append(new_tokeninfo) |
| 139 | + new_tokeninfo, changers = self.apply_formatters(new_tokeninfo) |
| 140 | + is_changed = is_changed or bool(changers) |
| 141 | + |
| 142 | + # Run formatters again (3rd time) to check if the result is stable |
| 143 | + _, changers = self._apply_formatters_once( |
| 144 | + new_tokeninfo, |
| 145 | + ) |
| 146 | + |
| 147 | + if changers: |
| 148 | + conflicting_formatters = { |
| 149 | + k: v |
| 150 | + for k, v in self.enabled_formatters.items() |
| 151 | + if k in changers |
| 152 | + } |
| 153 | + template = _utils.create_gh_issue_template( |
| 154 | + new_tokeninfo, conflicting_formatters, str(filename) |
| 155 | + ) |
128 | 156 |
|
129 |
| - if tokeninfo != new_tokeninfo: |
130 |
| - is_changed = True |
| 157 | + raise UnstableResultError(template) |
| 158 | + |
| 159 | + formatted_tokens.append(new_tokeninfo) |
131 | 160 |
|
132 | 161 | return formatted_tokens, is_changed
|
133 | 162 |
|
| 163 | + def apply_formatters( |
| 164 | + self, token: tokenize.TokenInfo |
| 165 | + ) -> tuple[tokenize.TokenInfo, set[str]]: |
| 166 | + """Apply the formatters twice to a token. |
| 167 | +
|
| 168 | + Also tracks which formatters changed the token. |
| 169 | +
|
| 170 | + Returns: |
| 171 | + A tuple containing: |
| 172 | + [1] the formatted token and |
| 173 | + [2] a set of formatters that changed the token. |
| 174 | + """ |
| 175 | + token, changers = self._apply_formatters_once(token) |
| 176 | + if changers: |
| 177 | + token, changers2 = self._apply_formatters_once(token) |
| 178 | + changers.update(changers2) |
| 179 | + return token, changers |
| 180 | + |
| 181 | + def _apply_formatters_once( |
| 182 | + self, token: tokenize.TokenInfo |
| 183 | + ) -> tuple[tokenize.TokenInfo, set[str]]: |
| 184 | + """Applies formatters to a token and keeps track of what changes it. |
| 185 | +
|
| 186 | + token: Token to apply formatters to |
| 187 | +
|
| 188 | + Returns: |
| 189 | + A tuple containing [1] the formatted token and [2] a set |
| 190 | + of formatters that changed the token. |
| 191 | + """ |
| 192 | + changers: set[str] = set() |
| 193 | + for formatter_name, formatter in self.enabled_formatters.items(): |
| 194 | + if (new_token := formatter.treat_token(token)) != token: |
| 195 | + changers.add(formatter_name) |
| 196 | + token = new_token |
| 197 | + |
| 198 | + return token, changers |
| 199 | + |
134 | 200 | def format_files(self, filepaths: list[Path]) -> bool:
|
135 | 201 | """Format a list of files."""
|
136 |
| - is_changed = False |
137 |
| - |
138 |
| - for file in filepaths: |
139 |
| - is_changed = self.format_file(file) or is_changed |
140 |
| - |
141 |
| - return is_changed |
| 202 | + is_changed = [self.format_file(file) for file in filepaths] |
| 203 | + return any(is_changed) |
0 commit comments