Exemplo n.º 1
0
    def on_python_module(self, python_module):
        for node in walk(python_module.module):
            if node.type != "decorator":
                continue

            name_parts = extract_func_name(node)
            if (len(name_parts) == 1 and name_parts[0] != "route") or (
                    len(name_parts) == 2 and
                (name_parts[0] != "http" or name_parts[1] != "route")):
                continue

            kwargs = {
                c.children[0].value: (
                    c.children[0].line,
                    column_index_1(c.children[0].start_pos),
                )
                for c in walk(node) if c.type == "argument"
            }

            for kw in kwargs.keys() - ROUTE_KWARGS[
                    python_module.addon.version]:
                yield Issue(
                    "unknown_route_kwarg",
                    f'Unknown `http.route()` keyword argument "{kw}"',
                    python_module.addon.manifest_path,
                    [Location(python_module.path, [kwargs[kw][0]])],
                    categories=["correctness"],
                )
Exemplo n.º 2
0
    def on_field_definition(self, field):
        addon, path = field.model.addon, field.model.path
        known_fields = FIELD_TYPE_VERSION_MAP.get(addon.version, set())
        get_odoo_string = get_odoo_string_compute_func(addon.version)
        sources = ([ODOO_SOURCE_URL_VERSION_MAP[addon.version]]
                   if addon.version in ODOO_SOURCE_URL_VERSION_MAP else [])
        if field.class_name not in known_fields:
            _LOG.warning("Unknown field type: %s", field.class_name)
            return

        string_kwarg = None
        for kwarg in field.kwargs:
            if kwarg.name == "string":
                string_kwarg = kwarg
                break
        if string_kwarg and string_kwarg.value == get_odoo_string(field.name):
            yield Issue(
                "redundant_field_attribute",
                f'Redundant field attribute `string="{string_kwarg.value}"` '
                f'for field "{field.name}". The same value will be computed '
                f"by Odoo automatically.",
                addon.manifest_path,
                [Location(path, [column_index_1(string_kwarg.start_pos)])],
                categories=["redundancy"],
                sources=sources,
            )
            return

        if field.args:
            arg_index = FIELD_TYPE_STRING_INDEX_MAP.get(field.class_name, 0)
            if len(field.args) < arg_index + 1:
                return
            string_arg = field.args[arg_index]

            if string_arg.value == get_odoo_string(field.name):
                yield Issue(
                    "redundant_field_attribute",
                    f"Redundant implied field attribute `string` "
                    f'"{string_arg.value}"` for field "{field.name}". '
                    f"The same value will be computed by Odoo automatically.",
                    addon.manifest_path,
                    [Location(path, [column_index_1(string_arg.start_pos)])],
                    categories=["redundancy"],
                    sources=sources,
                )
Exemplo n.º 3
0
 def _get_key_locations(self, module):
     key_locations = collections.defaultdict(list)
     for node in walk(module):
         if node.type == "dictorsetmaker":
             for child in node.children[::4]:
                 if is_string_node(child):
                     key_locations[get_string_node_value(child)].append(
                         column_index_1(child.start_pos))
             break
     return key_locations
Exemplo n.º 4
0
 def on_after(self, addon):
     for model_name, (filename, start_pos) in self._models.items():
         model_external_id = f"model_{model_name.replace('.', '_')}"
         if model_external_id not in self._access_rules:
             yield Issue(
                 "no_ir_model_access_record",
                 f'Model "{model_name}" has no `ir.model.access` records',
                 addon.manifest_path,
                 [Location(filename, [column_index_1(start_pos)])],
                 categories=["correctness", "security"],
             )
Exemplo n.º 5
0
 def issue(module_import, import_name):
     yield Issue(
         "legacy_import",
         f"Legacy import `{import_name}`",
         python_module.addon.manifest_path,
         [
             Location(
                 python_module.path, [column_index_1(module_import.start_pos)]
             )
         ],
         categories=["deprecated"],
     )
Exemplo n.º 6
0
        def _check_call(call: Call,
                        call_end_parts,
                        ref_values_getter,
                        model=UNKNOWN):
            for end_part in call_end_parts:
                if call.names[-len(end_part):] != end_part:
                    continue

                position = column_index_1(call.start_pos)
                for ref_value in ref_values_getter(addon_version, call):
                    yield _ref(
                        python_module.addon,
                        python_module.path,
                        position,
                        ref_value,
                        model,
                    )
Exemplo n.º 7
0
    def on_python_module(self, python_module):
        if python_module.addon.version < 12 or not python_module.path.name.startswith(
                "test_"):
            return

        imports = set()
        for imp in get_imports(python_module.module):
            from_part = ".".join(imp.from_names)
            if from_part:
                imports.update((from_part, n) for n in imp.names)
            else:
                imports.update(imp.names)

        for classdef in python_module.module.iter_classdefs():
            bases = get_bases(classdef.get_super_arglist())
            is_test_case = (("unittest", "TestCase") in bases
                            and "unittest" in imports) or (
                                ("TestCase", ) in bases and
                                ("unittest", "TestCase") in imports)
            if not is_test_case:
                continue

            tagged = False
            for decorator in classdef.get_decorators():
                if extract_func_name(decorator)[-1] == "tagged":
                    tagged = True
                    break
            if not tagged:
                yield Issue(
                    "unittest_testcase_not_tagged",
                    f"`unittest.TestCase` subclass `{classdef.name.value}` is not "
                    f"decorated with `@tagged()`, it will not be picked up by Odoo "
                    f"test runner",
                    python_module.addon.manifest_path,
                    [
                        Location(python_module.path,
                                 [column_index_1(classdef.start_pos)])
                    ],
                    categories=["correctness"],
                    sources=[
                        odoo_commit_url(
                            "b356b190338e3ee032b9e3a7f670f76468965006")
                    ],
                )
Exemplo n.º 8
0
    def on_field_definition(self, field: FieldDefinition):
        addon = field.model.addon
        if addon.version < 12:
            return

        for kwarg in field.kwargs:
            if kwarg.name == "track_visibility" and kwarg.value == "always":
                yield Issue(
                    "track_visibility_always_deprecated",
                    'Field `track_visibility` attribute value "always" is '
                    "deprecated since version 12.0",
                    addon.manifest_path,
                    [
                        Location(field.model.path,
                                 [column_index_1(kwarg.start_pos)])
                    ],
                    categories=["deprecated"],
                    sources=[
                        odoo_commit_url(
                            "c99de4551583e801ecc6669ac456c4f7e2eef1da")
                    ],
                )
Exemplo n.º 9
0
    def on_model_definition(self, model):
        if get_model_type(model.node) == UNKNOWN:
            return
        model_name = model.params.get("_name")
        if model.params.get("_inherit") or not model_name:
            return

        if not model.params.get("_description"):
            yield Issue(
                "no_model_description",
                f'Model "{model_name}" has no `_description` set',
                model.addon.manifest_path,
                [Location(model.path, [column_index_1(model.node.start_pos)])],
                categories=(["future-warning"]
                            if model.addon.version < 12 else ["correctness"]),
                sources=[
                    odoo_source_url(
                        "ff9ddfdacc9581361b555fd5f69e2da61800acad",
                        "odoo/models.py",
                        start=529,
                    )
                ],
            )
Exemplo n.º 10
0
    def on_field_definition(self, field):
        """
        for classdef in module.iter_classdefs():
            model = get_model_definition(classdef, extract_fields=True)
            if not model.name:
                continue

            for field in model.fields:
        """
        addon, path = field.model.addon, field.model.path
        known_fields = FIELD_TYPE_VERSION_MAP.get(addon.version, set())
        common_field_attrs = COMMON_ATTRS_VERSION_MAP.get(addon.version, set())

        if field.class_name not in known_fields:
            yield Issue(
                "unknown_field_type",
                f'Unknown field type "{field.class_name}"',
                addon.manifest_path,
                [Location(path, [column_index_1(field.start_pos)])],
                categories=["correctness"],
            )
            return

        kwargs = {kw.name: kw for kw in field.kwargs}
        model_attrs = MODEL_ATTR_VERSION_MAP.get(field.model.name,
                                                 {}).get(addon.version, set())
        deprecated_attrs = DEPRECATED_ATTR_VERSION_MAP.get(
            field.class_name, {}).get(addon.version, set())
        deprecated_attrs |= COMMON_DEPRECATED_ATTRS_VERSION_MAP.get(
            addon.version, set())
        expected_attrs = (ATTRS_VERSION_MAP.get(field.class_name, {}).get(
            addon.version, set())
                          | common_field_attrs
                          | model_attrs)
        if "compute" in kwargs:
            expected_attrs |= COMPUTE_ATTRS
        if "related" in kwargs:
            expected_attrs |= RELATED_ATTRS
        unknown_attrs = kwargs.keys() - expected_attrs
        renamed_attrs = dict(
            RENAMED_ATTRS_VERSION_MAP.get(addon.version, set()))

        for attr in unknown_attrs:
            if attr in renamed_attrs:
                yield Issue(
                    "renamed_field_attribute",
                    f'Field attribute "{attr}" '
                    f'was renamed to "{renamed_attrs[attr]}"',
                    addon.manifest_path,
                    [Location(path, [column_index_1(kwargs[attr].start_pos)])],
                    categories=["deprecated"],
                )
                continue

            if attr in deprecated_attrs:
                yield Issue(
                    "deprecated_field_attribute",
                    f'Deprecated field attribute "{attr}" '
                    f'for field type "{field.class_name}"',
                    addon.manifest_path,
                    [Location(path, [column_index_1(kwargs[attr].start_pos)])],
                    categories=["deprecated"],
                )
                continue
            yield Issue(
                "unknown_field_attribute",
                f'Unknown field attribute "{attr}" '
                f'for field type "{field.class_name}"',
                addon.manifest_path,
                [Location(path, [column_index_1(kwargs[attr].start_pos)])],
                categories=["correctness"],
            )