|
150 | 150 | from mypy.renaming import LimitedVariableRenameVisitor, VariableRenameVisitor |
151 | 151 | from mypy.stats import dump_type_stats |
152 | 152 | from mypy.stubinfo import is_module_from_legacy_bundled_package, stub_distribution_name |
| 153 | +from mypy.known_modules import get_known_modules |
| 154 | +from mypy.messages import best_matches, pretty_seq |
153 | 155 | from mypy.types import Type, instance_cache |
154 | 156 | from mypy.typestate import reset_global_state, type_state |
155 | 157 | from mypy.util import json_dumps, json_loads |
@@ -3189,6 +3191,23 @@ def module_not_found( |
3189 | 3191 | code = codes.IMPORT |
3190 | 3192 | errors.report(line, 0, msg.format(module=target), code=code) |
3191 | 3193 |
|
| 3194 | + if reason == ModuleNotFoundReason.NOT_FOUND: |
| 3195 | + top_level_target = target.split(".")[0] |
| 3196 | + known_modules = get_known_modules( |
| 3197 | + manager.find_module_cache.stdlib_py_versions, |
| 3198 | + manager.options.python_version, |
| 3199 | + ) |
| 3200 | + matches = best_matches(top_level_target, known_modules, n=3) |
| 3201 | + matches = [m for m in matches if m.lower() != top_level_target.lower()] |
| 3202 | + if matches: |
| 3203 | + errors.report( |
| 3204 | + line, |
| 3205 | + 0, |
| 3206 | + f'Did you mean {pretty_seq(matches, "or")}?', |
| 3207 | + severity="note", |
| 3208 | + code=code, |
| 3209 | + ) |
| 3210 | + |
3192 | 3211 | dist = stub_distribution_name(target) |
3193 | 3212 | for note in notes: |
3194 | 3213 | if "{stub_dist}" in note: |
|
0 commit comments