Manifest Rules¶
manifest
¶
Fixit lint rules for Odoo __manifest__.py files.
Rules are auto-discovered by fixit when the module is referenced as a
QualifiedRule (see commands/manifest/common.py). Every public class
that inherits from LintRule in this module becomes an active rule.
HOW TO ADD A RULE¶
-
Subclass
LintRuleand give it a descriptive name::class MyNewRule(LintRule): MESSAGE = "Short default message shown when no message= is given."
-
Declare
AUTOFIX = Trueonly if your rule can always provide areplacementnode. Fixit will warn if you set it but don't supply one. -
Add
VALID/INVALIDlists of code snippets — fixit runs them as inline unit tests (uv run python -m pytestpicks them up automatically via the fixit pytest plugin). -
Declare class-level config attributes with safe defaults so the rule works even without an
.oops.yamlfile. Override them in__init__from_load_manifest_cfg()::some_setting: str = "" # "" → check disabled when no config some_list: List[str] = [] # empty → permissive fallback
-
Implement
visit_<NodeType>(self, node)where<NodeType>matches a libcst node class (e.g.Dict,SimpleString,Call). The visitor is called for every matching node in the file, so useself._checkedto process only the first top-level dict (the manifest dict itself)::def visit_Dict(self, node: cst.Dict) -> None: if self._checked: # ignore nested dicts (depends, data, …) return self._checked = True ...
-
Call
self.report(node, message="…")to flag a violation. For autofixes, passreplacement=node.with_changes(…)::fixed = node.with_changes(value=cst.SimpleString('"corrected"')) self.report(node, message="Wrong value.", replacement=fixed)
-
That's it — no registration needed. The rule is picked up automatically because
common.pyusesQualifiedRule("oops.rules.manifest").
LIBCST QUICK REFERENCE¶
Manifest dicts are Python dict literals, so the relevant CST nodes are:
cst.Dict — the { … } literal itself
cst.DictElement — one key: value pair inside it
cst.SimpleString — a plain string like "Acme" or '16.0.1.0.0'
cst.List — a list literal like ["alice", "bob"]
cst.Element — one item inside a cst.List
Reading a string value safely
_string_value(node) → returns the Python str or None
Building a replacement node
node.with_changes(field=new_value) — returns a new immutable node
AUTOFIX CONFLICT NOTE¶
When two rules both provide a replacement on overlapping nodes (e.g.
ManifestKeyOrder replaces the whole dict while OdooManifestAuthorMaintainers
replaces a child string), fixit can only apply one per pass. The outermost
node wins (ManifestKeyOrder). The inner fix is applied cleanly on the next
oops-man-fix pass. This is expected and acceptable behaviour.
Classes:
| Name | Description |
|---|---|
ManifestKeyOrder |
Enforce the canonical key order in |
ManifestNoExtraKeys |
Reject keys not present in the configured allowed list. |
ManifestRequiredKeys |
Ensure all required keys are present in the manifest dict. |
ManifestVersionBump |
Verify that the manifest version is bumped when addon files are staged. |
OdooManifestAuthorMaintainers |
Validate manifest field values: author, maintainers, summary, version. |
ManifestKeyOrder
¶
flowchart TD
oops.rules.manifest.ManifestKeyOrder[ManifestKeyOrder]
click oops.rules.manifest.ManifestKeyOrder href "" "oops.rules.manifest.ManifestKeyOrder"
Enforce the canonical key order in __manifest__.py dict literals.
The replacement node preserves all comments and trailing commas; only the
element sequence is reordered. Keys absent from key_order are sorted
after all known keys (stable, alphabetical).
Because this rule replaces the whole dict node, its autofix takes
priority over child-node fixes from other rules in the same fixit pass.
Run oops-man-fix a second time to apply those remaining fixes.
Source code in oops/rules/manifest.py
ManifestNoExtraKeys
¶
flowchart TD
oops.rules.manifest.ManifestNoExtraKeys[ManifestNoExtraKeys]
click oops.rules.manifest.ManifestNoExtraKeys href "" "oops.rules.manifest.ManifestNoExtraKeys"
Reject keys not present in the configured allowed list.
Unknown keys are likely typos or leftover debug entries. The allowed list
is the same as key_order from .oops.yaml so both rules stay in sync.
Source code in oops/rules/manifest.py
ManifestRequiredKeys
¶
flowchart TD
oops.rules.manifest.ManifestRequiredKeys[ManifestRequiredKeys]
click oops.rules.manifest.ManifestRequiredKeys href "" "oops.rules.manifest.ManifestRequiredKeys"
Ensure all required keys are present in the manifest dict.
The list of required keys is read from manifest.required_keys in
.oops.yaml; the class-level default covers standalone usage.
Source code in oops/rules/manifest.py
ManifestVersionBump
¶
flowchart TD
oops.rules.manifest.ManifestVersionBump[ManifestVersionBump]
click oops.rules.manifest.ManifestVersionBump href "" "oops.rules.manifest.ManifestVersionBump"
Verify that the manifest version is bumped when addon files are staged.
This rule is git-aware: it reads the list of staged files from the index,
determines which addons are affected, and only activates for those. Addons
that have no staged files are silently skipped, so the rule is safe to
run on all manifests via oops-man-check.
Two strategies are supported (set manifest.version_bump_strategy in
.oops.yaml):
strict (default when not off)
The staged version must be strictly greater than the version at HEAD.
Enforces a version bump on every commit that touches the addon.
trunk
The staged version must be strictly greater than the version at the
last git tag. One bump per release cycle is sufficient — fits
trunk-based / squash-merge workflows.
off (default)
Rule is disabled. Set explicitly to activate::
manifest:
version_bump_strategy: strict # or trunk
New addons (absent from the reference commit / tag) are always exempt. Only the module-specific tail of the version is compared (last 3 parts of the 5-part Odoo string), so migrating to a new Odoo major version without bumping the module version does not trigger a false positive.
Source code in oops/rules/manifest.py
OdooManifestAuthorMaintainers
¶
flowchart TD
oops.rules.manifest.OdooManifestAuthorMaintainers[OdooManifestAuthorMaintainers]
click oops.rules.manifest.OdooManifestAuthorMaintainers href "" "oops.rules.manifest.OdooManifestAuthorMaintainers"
Validate manifest field values: author, maintainers, summary, version.
All four checks run in a single visit_Dict pass; each is extracted into
its own _check_* method so they can be read and extended independently.
Autofixable checks
~~~~~~~~~~~~~~~~~~
- author — replaced with the configured value (preserves quote style)
- version — digit-lookalike typos corrected (O→0, l/I→1) when the
corrected string passes the pattern