Example #1
0
def parse_module(filename: str) -> Module:
    """
    @cc 5
    @desc parse a module into our archives' models
    @arg filename: the python file to parse
    @ret a parsed Module object of the given file
    """
    state = get_state()
    contents = ""
    if str(filename)[-2:] == "/-":
        contents, _, __ = decode_bytes(sys.stdin.buffer.read())
    elif not os.path.isfile(filename):
        raise Exception("file does not exist")
    else:
        with open(filename, encoding="utf-8",
                  errors="replace") as file_to_read:
            contents += file_to_read.read()
    try:
        ast = ast3.parse(contents)
    except:  # noqa
        out("error in parsing", color="red")
        if state.ignore_exceptions:
            sys.exit(0)
    module = Module(ast, filename)  # type: ignore
    return module
Example #2
0
def archives_doc(ctx: click.Context, sources: Set[Path], state: State) -> None:
    """
    @cc 1
    @desc perform archives documentation generation
    @arg ctx: the click context of the current run
    @arg sources: the source files to lint
    @arg state: the current click state
    """
    modules = {
        file.parts[-1]: parse_module(str(file.absolute())).serialize()
        for file in sources
    }

    out(modules)
    ctx.exit(0)
Example #3
0
def archives(
    ctx: click.Context,
    quiet: bool,
    verbose: bool,
    include: str,
    exclude: str,
    format: str,  # pylint: disable=redefined-builtin
    disable: str,
    list_rules: bool,
    list_tags: bool,
    stats: bool,
    ignore_exceptions: bool,
    doc: bool,
    src: Tuple[str],
) -> None:
    """
    check if your code's archives are incomplete!
    \f
    @cc 8
    @desc the main cli method for archives
    @arg ctx: the click context arg
    @arg quiet: the cli quiet flag
    @arg verbose: the cli verbose flag
    @arg include: a regex for what files to include
    @arg exclude: a regex for what files to exclude
    @arg format: a flag to specify output format for the issues
    @arg disable: a comma separated disable list for rules
    @arg list_rules: a flag to print the list of rules and exit
    @arg list_tags: a flag to print the list of tags and their descriptions
    @arg stats: a flag to print extra stats at the end of a lint run
    @arg ignore_exceptions: a flag to ignore parsing errors and exit 0
    @arg doc: a flag to specify if we should generate docs instead of lint
    @arg src: a file or directory to scan for files to lint
    """
    state = ctx.ensure_object(State)
    state.verbose = verbose
    state.quiet = quiet
    state.format = format
    state.disable_list = disable.split(",")
    state.ignore_exceptions = ignore_exceptions
    state.stats = stats

    if list_rules:
        for rule in [
            *MODULE_RULES,
            *CLASS_RULES,
            *FUNCTION_RULES,
            MISSING_ARG,
            UNEXPECTED_ARG,
            UNTYPED_ARG,
        ]:
            out(f"{rule.code}: {rule.desc}")
        ctx.exit(0)
    if list_tags:
        for tag in Tags.all():
            out(f"{CHAR}{tag.name}\t{tag.desc}")
        ctx.exit(0)
    try:
        include_regex = re.compile(include)
    except re.error:
        err(f"invalid regex for include: {include!r}")
        ctx.exit(2)
    try:
        exclude_regex = re.compile(exclude)
    except re.error:
        err(f"invalid regex for exclude: {exclude!r}")
        ctx.exit(2)
    root = find_project_root(src)
    sources: Set[Path] = set()
    path_empty(src, ctx)
    for source in src:
        path = Path(source)
        if path.is_dir():
            sources.update(get_python_files(path, root, include_regex, exclude_regex))
        elif path.is_file() or source == "-":
            # if a file was explicitly given, we don't care about its extension
            sources.add(path)
        else:
            err(f"invalid path: {source}")
    if not sources:
        if state.verbose or not state.quiet:
            out("no python files are detected")
        ctx.exit(0)
    if doc:
        archives_doc(ctx, sources, state)
    archives_lint(ctx, sources, state)
Example #4
0
def archives_lint(ctx: click.Context, sources: Set[Path], state: State) -> None:
    """
    @cc 4
    @desc perform an archives documentation lint
    @arg ctx: the click context of the current run
    @arg sources: the source files to lint
    @arg state: the current click state
    """

    # apply disables to the global rule state
    state.module_rules = [x for x in MODULE_RULES if x.code not in state.disable_list]
    state.class_rules = [x for x in CLASS_RULES if x.code not in state.disable_list]
    state.function_rules = [
        x for x in FUNCTION_RULES if x.code not in state.disable_list
    ]

    # lint the files
    issues = []
    for file in sources:
        module = parse_module(str(file.absolute()))
        issues.extend(lint(module))

    for issue in issues:
        obj = issue.obj
        rule = issue.rule
        module = obj if isinstance(obj, Module) else obj.module
        extra_info = dict(name=obj.name)

        # function specific info
        if isinstance(obj, Function):
            extra_info["cc"] = obj.complexity
            if obj.doc:
                extra_info["doc_cc"] = obj.doc.cc

        message = FORMATS[state.format].format_map(
            defaultdict(
                str,
                path=module.path,
                line=issue.line,
                column=issue.column,
                code=rule.code,
                text=rule.desc.format_map(
                    defaultdict(str, **extra_info, **issue.extra)
                ),
            )
        )
        out(message, color="blue")
    if not state.quiet:
        if issues:
            trailing_s = "s" if len(issues) != 1 else ""
            out(f"\nImpossible! Perhaps your archives are incomplete?", color="red")
            out(f"{len(issues)} issue{trailing_s} found", color="red")
        else:
            out(
                f"Incredible! It appears that your archives are complete!", color="blue"
            )
            out(f"0 issues found", color="blue")

        if state.stats:
            _mods = state.module_count
            _cls = state.class_count
            _fns = state.function_count
            out(
                f"{_mods} module{'s' if _mods != 1 else ''} ({state.module_nolint_count} nolint)"
            )
            out(
                f"{_cls} class{'es' if _cls != 1 else ''} ({state.class_nolint_count} nolint)"
            )
            out(
                f"{_fns} function{'s' if _fns != 1 else ''} ({state.function_nolint_count} nolint)"
            )

    ctx.exit(0 if not issues else 1)