defintion.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import json
  2. import re
  3. from pathlib import Path
  4. from typing import Iterator
  5. from ..diagnostic import Diagnostic
  6. from .linter import Linter
  7. from ..replacement import Replacement
  8. class Definition(Linter):
  9. """ Finds issues in definition files, such as overriding default parameters """
  10. def __init__(self, file: Path, settings: dict) -> None:
  11. super().__init__(file, settings)
  12. self._definitions = {}
  13. self._loadDefinitionFiles(file)
  14. self._content = self._file.read_text()
  15. self._loadBasePrinterSettings()
  16. @property
  17. def base_def(self):
  18. if "fdmextruder" in self._definitions:
  19. return "fdmextruder"
  20. return "fdmprinter"
  21. def check(self) -> Iterator[Diagnostic]:
  22. if self._settings["checks"].get("diagnostic-definition-redundant-override", False):
  23. for check in self.checkRedefineOverride():
  24. yield check
  25. # Add other which will yield Diagnostic's
  26. # TODO: A check to determine if the user set value is with the min and max value defined in the parent and doesn't trigger a warning
  27. # TODO: A check if the key exist in the first place
  28. # TODO: Check if the model platform exist
  29. yield
  30. def checkRedefineOverride(self) -> Iterator[Diagnostic]:
  31. """ Checks if definition file overrides its parents settings with the same value. """
  32. definition_name = list(self._definitions.keys())[0]
  33. definition = self._definitions[definition_name]
  34. if "overrides" in definition and definition_name not in ("fdmprinter", "fdmextruder"):
  35. for key, value_dict in definition["overrides"].items():
  36. is_redefined, child_key, child_value, parent = self._isDefinedInParent(key, value_dict, definition['inherits'])
  37. if is_redefined:
  38. redefined = re.compile(r'.*(\"' + key + r'\"[\s\:\S]*?)\{[\s\S]*?\},?')
  39. found = redefined.search(self._content)
  40. # TODO: Figure out a way to support multiline fixes in the PR review GH Action, for now suggest no fix to ensure no ill-formed json are created
  41. # see: https://github.com/platisd/clang-tidy-pr-comments/issues/37
  42. if len(found.group().splitlines()) > 1:
  43. replacements = []
  44. else:
  45. replacements = [Replacement(
  46. file = self._file,
  47. offset = found.span(1)[0],
  48. length = len(found.group()),
  49. replacement_text = "")]
  50. yield Diagnostic(
  51. file = self._file,
  52. diagnostic_name = "diagnostic-definition-redundant-override",
  53. message = f"Overriding {key} with the same value ({child_key}: {child_value}) as defined in parent definition: {definition['inherits']}",
  54. level = "Warning",
  55. offset = found.span(0)[0],
  56. replacements = replacements
  57. )
  58. def _loadDefinitionFiles(self, definition_file) -> None:
  59. """ Loads definition file contents into self._definitions. Also load parent definition if it exists. """
  60. definition_name = Path(definition_file.stem).stem
  61. if not definition_file.exists() or definition_name in self._definitions:
  62. return
  63. # Load definition file into dictionary
  64. self._definitions[definition_name] = json.loads(definition_file.read_text())
  65. # Load parent definition if it exists
  66. if "inherits" in self._definitions[definition_name]:
  67. if self._definitions[definition_name]['inherits'] in ("fdmextruder", "fdmprinter"):
  68. parent_file = definition_file.parent.parent.joinpath("definitions", f"{self._definitions[definition_name]['inherits']}.def.json")
  69. else:
  70. parent_file = definition_file.parent.joinpath(f"{self._definitions[definition_name]['inherits']}.def.json")
  71. self._loadDefinitionFiles(parent_file)
  72. def _isDefinedInParent(self, key, value_dict, inherits_from):
  73. if "overrides" not in self._definitions[inherits_from]:
  74. return self._isDefinedInParent(key, value_dict, self._definitions[inherits_from]["inherits"])
  75. parent = self._definitions[inherits_from]["overrides"]
  76. if key not in self._definitions[self.base_def]["overrides"]:
  77. is_number = False
  78. else:
  79. is_number = self._definitions[self.base_def]["overrides"][key]["type"] in ("float", "int")
  80. for child_key, child_value in value_dict.items():
  81. if key in parent:
  82. if child_key in ("default_value", "value"):
  83. check_values = [cv for cv in [parent[key].get("default_value", None), parent[key].get("value", None)] if cv is not None]
  84. else:
  85. check_values = [parent[key].get(child_key, None)]
  86. for check_value in check_values:
  87. if is_number and child_key in ("default_value", "value"):
  88. try:
  89. v = str(float(child_value))
  90. except:
  91. v = child_value
  92. try:
  93. cv = str(float(check_value))
  94. except:
  95. cv = check_value
  96. else:
  97. v = child_value
  98. cv = check_value
  99. if v == cv:
  100. return True, child_key, child_value, parent
  101. if "inherits" in parent:
  102. return self._isDefinedInParent(key, value_dict, parent["inherits"])
  103. return False, None, None, None
  104. def _loadBasePrinterSettings(self):
  105. """ TODO @Jelle please explain why this """
  106. settings = {}
  107. for k, v in self._definitions[self.base_def]["settings"].items():
  108. self._getSetting(k, v, settings)
  109. self._definitions[self.base_def] = {"overrides": settings}
  110. def _getSetting(self, name, setting, settings) -> None:
  111. if "children" in setting:
  112. for childname, child in setting["children"].items():
  113. self._getSetting(childname, child, settings)
  114. settings |= {name: setting}