diff --git a/src/oca_pre_commit_hooks/checks_odoo_module_xml.py b/src/oca_pre_commit_hooks/checks_odoo_module_xml.py index def64f4..24dc68f 100644 --- a/src/oca_pre_commit_hooks/checks_odoo_module_xml.py +++ b/src/oca_pre_commit_hooks/checks_odoo_module_xml.py @@ -79,6 +79,33 @@ class ChecksOdooModuleXML(BaseChecker): xpath_xpath = etree.XPath("//xpath") xpath_oe_chatter = etree.XPath("//div[hasclass('oe_chatter')]") + # Mapping ``{deprecated_class: replacement}`` for ``check_xml_deprecated_css_class``. + # Each entry is a one-to-one substitution that does not depend on the + # element's tag, parent, or surrounding markup. Ambiguous cases (e.g. + # ``oe_inline``, where the BS5 replacement depends on context) are + # intentionally omitted. + deprecated_css_classes = { + # Bootstrap 3 → 5 utility renames. Odoo bundles Bootstrap 5 since + # version 15.0. + "pull-left": "float-start", + "pull-right": "float-end", + "panel": "card", + "panel-default": "card", + "panel-heading": "card-header", + "panel-body": "card-body", + "panel-title": "card-title", + "panel-footer": "card-footer", + "img-responsive": "img-fluid", + "btn-default": "btn-secondary", + # Odoo legacy classes that were superseded by BS5 utilities. + "oe_left": "float-start", + "oe_right": "float-end", + "oe_grey": "text-muted", + } + xpath_deprecated_css_class = { + css_class: etree.XPath(f"//*[hasclass('{css_class}')]") for css_class in deprecated_css_classes + } + tree_deprecate_attrs = {"string", "colors", "fonts"} xpath_tree_deprecated = etree.XPath(f'.//tree[{"|".join(f"@{a}" for a in tree_deprecate_attrs)}]') @@ -926,3 +953,37 @@ def check_xml_deprecated_oe_chatter(self): filepath=manifest_data["filename_short"], line=xpath_node.sourceline, ) + + @utils.only_required_for_checks("xml-deprecated-css-class") + def check_xml_deprecated_css_class(self): + """* Check xml-deprecated-css-class + + Detect legacy CSS classes in XML / QWeb views and suggest the + modern replacement. Two sources are covered: + + * Bootstrap 3 utility classes that were renamed in Bootstrap 5. + Odoo bundles Bootstrap 5 since version 15.0, so any module + targeting 15.0+ should drop the legacy names. + * Odoo legacy classes (``oe_left``, ``oe_right``, ``oe_grey``) + that were superseded by Bootstrap 5 utilities. + + Only unambiguous one-to-one mappings are flagged here; ambiguous + classes (``oe_inline``, ``oe_button_box``, …) are left out so the + rule can be enabled without manual review of every hit. + """ + if not self.module_version or (self.module_version and self.module_version < Version("15")): + return + + for manifest_data in self.manifest_datas: + for css_class, replacement in self.deprecated_css_classes.items(): + xpath = self.xpath_deprecated_css_class[css_class] + for xpath_node in xpath(manifest_data["node"]): + self.register_error( + code="xml-deprecated-css-class", + message=( + f"Deprecated CSS class {css_class!r}; " + f"replace with {replacement!r}." + ), + filepath=manifest_data["filename_short"], + line=xpath_node.sourceline, + ) diff --git a/test_repo/odoo18_module/__manifest__.py b/test_repo/odoo18_module/__manifest__.py index f6bdf98..d788577 100644 --- a/test_repo/odoo18_module/__manifest__.py +++ b/test_repo/odoo18_module/__manifest__.py @@ -8,6 +8,7 @@ ], 'data': [ 'views/deprecated_chatter.xml', + 'views/deprecated_css_class.xml', 'views/deprecated_qweb_directives15.xml', ], } diff --git a/test_repo/odoo18_module/views/deprecated_css_class.xml b/test_repo/odoo18_module/views/deprecated_css_class.xml new file mode 100644 index 0000000..c966b6e --- /dev/null +++ b/test_repo/odoo18_module/views/deprecated_css_class.xml @@ -0,0 +1,20 @@ + + + deprecated.css.view + random.model + +
+
Right-floated content
+
+

Title

+
Body
+ +
+ + +
Old-style left
+
Old-style right
+ Faded label +
+
+
diff --git a/tests/test_checks.py b/tests/test_checks.py index b4c5535..f3d0a7d 100644 --- a/tests/test_checks.py +++ b/tests/test_checks.py @@ -53,6 +53,7 @@ "xml-header-missing": 2, "xml-header-wrong": 18, "xml-id-position-first": 9, + "xml-deprecated-css-class": 12, "xml-deprecated-oe-chatter": 1, "xml-field-bool-without-eval": 2, "xml-field-numeric-without-eval": 7,