funcname_cache.py 1.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
  1. import tokenize
  2. from typing import Dict, List, Optional
  3. cache: Dict[str, Dict[int, str]] = {}
  4. def clearcache() -> None:
  5. cache.clear()
  6. def _add_file(filename: str) -> None:
  7. try:
  8. with tokenize.open(filename) as f:
  9. tokens = list(tokenize.generate_tokens(f.readline))
  10. except OSError:
  11. cache[filename] = {}
  12. return
  13. # NOTE: undefined behavior if file is not valid Python source,
  14. # since tokenize will have undefined behavior.
  15. result: Dict[int, str] = {}
  16. # current full funcname, e.g. xxx.yyy.zzz
  17. cur_name = ""
  18. cur_indent = 0
  19. significant_indents: List[int] = []
  20. for i, token in enumerate(tokens):
  21. if token.type == tokenize.INDENT:
  22. cur_indent += 1
  23. elif token.type == tokenize.DEDENT:
  24. cur_indent -= 1
  25. # possible end of function or class
  26. if significant_indents and cur_indent == significant_indents[-1]:
  27. significant_indents.pop()
  28. # pop the last name
  29. cur_name = cur_name.rpartition(".")[0]
  30. elif (
  31. token.type == tokenize.NAME
  32. and i + 1 < len(tokens)
  33. and tokens[i + 1].type == tokenize.NAME
  34. and (token.string == "class" or token.string == "def")
  35. ):
  36. # name of class/function always follows class/def token
  37. significant_indents.append(cur_indent)
  38. if cur_name:
  39. cur_name += "."
  40. cur_name += tokens[i + 1].string
  41. result[token.start[0]] = cur_name
  42. cache[filename] = result
  43. def get_funcname(filename: str, lineno: int) -> Optional[str]:
  44. if filename not in cache:
  45. _add_file(filename)
  46. return cache[filename].get(lineno, None)