Esempio n. 1
0
def test_check_mode_disable_errors_with_multiple_flags(
    mock_check_mode_start: Mock, ):

    main([
        'check',
        'tests/test_documents/contains_errors.md',
        '--disable-todo',
        '--disable-math-error',
    ])
    expected_args: CheckModeArgs = {
        'in_path':
        'tests/test_documents/contains_errors.md',
        'fix':
        False,
        'disabled_errors': [
            MathError.get_error_name(),
            TodoError.get_error_name(),
        ],
        'simple_errors':
        False,
    }
    expected_options: ModeOptions = {
        'visual': True,
        'args': expected_args,  # type: ignore
    }
    mock_check_mode_start.assert_called_once_with(expected_options)
Esempio n. 2
0
class CheckMode(BaseMode):
    """Check markdown files for errors and fix them if nessesary"""

    # The errors that can be found.
    # TODO: Replace with a more modular way
    possible_line_markdown_errors: List[MarkdownError] = [
        MathError(), TodoError(), RequiredSpaceAfterHeadersymbolError(),
    ]
    possible_multi_line_markdown_errors: List[MarkdownError] = [
        SeperatorError(),
        NewlineBeforeHeaderError(),
    ]

    possible_ast_errors: List[AstError] = [ListIndentError()]

    def _check_dir(self, dir_path: str) -> List[DocumentErrors]:
        """Checks all the markdown files in the given directory for errors

        Arguments:
            dir_path {str} -- The path to the directory containing the
                              markdown files to check.

        Raises:
            {NotADirectoryError} -- When the given dir_path does not exist,
                                    NotADirectoryError is raised.

        Returns:
            List[DocumentError] -- The found document errors

        """

        if not os.path.isdir(os.path.abspath(dir_path)):
            self._logger.error(
                f'Could not find directory: {os.path.abspath(dir_path)}. \
                Please provide a valid directory.',
            )
            raise NotADirectoryError

        md_files = find_all_md_files(dir_path)
        self._logger.info(f'Found {len(md_files)} to check')

        errors: List[DocumentErrors] = []
        for file in md_files:
            doc_errors = self._check_file(file)
            self._logger.info(
                f"Found {len(doc_errors['errors'])} errors in {file}",
            )
            errors.append(doc_errors)

        return errors

    def _check_file(self, file_path: str) -> DocumentErrors:
        """Opens a file and checks it for errors

        Uses the possible_errors list to check every line if a error is present

        Arguments:
            file_path {str} -- Absolute path to the file to check

        Returns:
            List[DocumentError] -- The errors that are found in the file.

        """

        # TODO: Check if file exists

        errors: List[ErrorMeta] = []
        # Open file
        lines: List[str] = []

        try:
            with open(file_path, 'r') as md_file:
                lines = md_file.readlines()
        except UnicodeDecodeError as e:
            # Try different reading mode
            # when UnicodeDecodeError error is thrown
            self._logger.debug(
                'Caught UnicodeDecodeError, swithcing read mode',
            )
            self._logger.debug(e)
            try:
                with open(file_path, 'r', encoding='windows-1252') as m_file:
                    lines = m_file.readlines()
            except Exception as e2:
                self._logger.warning(
                    f'Could not open {file_path}. Skipping the file...',
                )
                self._logger.info(e2)
                return DocumentErrors(file_path=file_path, errors=errors)
        except Exception as error:
            self._logger.warning(
                f'Could not open {file_path}. Skipping the file...',
            )
            self._logger.info(error)
            return DocumentErrors(file_path=file_path, errors=errors)

        for line_nr, line in enumerate(lines):
            # Go through the errors that can occur on the current line
            for err in self.possible_line_markdown_errors:
                if not err.validate([line]):
                    if err.get_error_name() not in self._disabled_errors:
                        new_err = ErrorMeta(
                            line_nr=line_nr, line=line, error_type=err,
                        )
                        errors.append(new_err)

            # Go through the errors that need multiple lines
            # to check for errors
            for m_err in self.possible_multi_line_markdown_errors:
                # SeperatorError needs access to multiple lines
                if isinstance(m_err, SeperatorError):
                    if line_nr == len(lines) - 1:
                        continue
                    if not m_err.validate([line, lines[line_nr + 1]]):
                        if m_err.get_error_name() not in self._disabled_errors:
                            new_err = ErrorMeta(
                                line_nr=line_nr, line=line, error_type=m_err,
                            )
                            errors.append(new_err)
                elif isinstance(m_err, NewlineBeforeHeaderError):
                    # NewlineBeforeHeaderError needs acces to the current
                    # and the previous line
                    if line_nr == 0:
                        continue
                    if not m_err.validate([lines[line_nr - 1], line]):
                        new_err = ErrorMeta(
                            line_nr=line_nr, line=line, error_type=m_err,
                        )
                        errors.append(new_err)

        # Check ast errors
        for ast_err in self.possible_ast_errors:
            if not ast_err.validate(lines):
                if ast_err.get_error_name() not in self._disabled_errors:
                    new_err = ErrorMeta(
                        # AstErrors do not need line nummers or line values
                        # When applying the fix the whole doc will be fixed
                        line_nr=None,
                        line=None,
                        error_type=ast_err,
                    )
                    errors.append(new_err)

        return DocumentErrors(file_path=file_path, errors=errors)

    def _fix_doc_errors(self, doc_errors: DocumentErrors):
        """Fixes the erros in the given document

        Arguments:
            doc_errors {DocumentErrors}  -- The document errors to fix

        Returns:
            None
        """
        # Get the file path of the current document
        file_path = doc_errors['file_path']
        wrong_line_nrs = [err['line_nr'] for err in doc_errors['errors']]
        error_types = [err['error_type'] for err in doc_errors['errors']]
        ast_errors = [et for et in error_types if isinstance(et, AstError)]

        self._logger.info(f'Fixing {file_path}')

        # Loop over lines
        with open(file_path, 'r') as read_file:
            lines = read_file.readlines()

        correct_lines: List[str] = []
        for line_nr, line in enumerate(lines):
            # If current line is in linenumbers
            # AstErrors have no line number so these should be skipped
            if line_nr in wrong_line_nrs:
                # Replace the current line with the correct line
                error_type = error_types[wrong_line_nrs.index(line_nr)]
                if error_type.fixable:
                    fixed_lines = error_type.fix([line])
                    for fl in fixed_lines:
                        correct_lines.append(fl)
                else:
                    correct_lines.append(line)
            else:
                correct_lines.append(line)

        # Loop over all the ast errors and check if they can be fixes
        # If they can apply the fix
        # Because AstErrors.fix returns all the lines of the file
        # correct_lines can be set to the return of the fix
        for ast_err in ast_errors:
            if ast_err.fixable:
                correct_lines = ast_err.fix(lines)

        # Write the fixed doc
        with open(file_path, 'w') as out_file:
            out_file.writelines(correct_lines)

    def _run(self, args: CheckModeArgs) -> None:
        """The internal entry point for CheckMode

        Checks if the given in_path is a directory or file and
        conintues accodingly

        Arguments:
            args {CheckModeArgs} -- The arguments as parsed by the parser
                                    for the check mode

        """

        self._disabled_errors = args['disabled_errors']
        # The default is set in the config
        self.simple_errors = args['simple_errors']

        errors: List[DocumentErrors] = []
        if os.path.isdir(os.path.abspath(args['in_path'])):
            self._logger.info(f'Checking directory {args["in_path"]}')
            errors = self._check_dir(args['in_path'])
        elif os.path.isfile(os.path.abspath(args['in_path'])):
            self._logger.info(f'Checking file {args["in_path"]}')
            doc_err = self._check_file(args['in_path'])
            self._logger.info(
                f"Found {len(doc_err['errors'])} errors in {args['in_path']}",
            )
            errors.append(doc_err)
        else:
            warning_msg = (
                'Could not find file or directory: ',
                os.path.abspath(args['in_path']),
                '\nPlease provide a valid file or directory.',

            )
            if self._visual:
                print(colored(''.join(warning_msg), 'red'))
            else:
                self._logger.error(
                    *warning_msg,
                )

            raise SystemExit(1)

        if args['fix']:
            for error in errors:
                self._fix_doc_errors(error)
                if self._visual:
                    if self.simple_errors:
                        print_simple_doc_error(error, True)
                    else:
                        print_doc_error(error, True)
        else:
            if self._visual:
                for error in errors:
                    if self.simple_errors:
                        print_simple_doc_error(error)
                    else:
                        print_doc_error(error)
Esempio n. 3
0

# Test the actual disabling of the error
# When an error is in _disabled_errors is should not be found
# in a document that contains the disabled error


@pytest.mark.parametrize(
    'file_contents,disabled_errors,valid',
    [
        (
            """\
[ ] Invalid todo
[x] Ivalid todo
         """,
            TodoError.get_error_name(),
            True,
        ),
        (
            """
There is $$invalid$$ math in this line
There only is correct $math$ in this line
There is one $correct$ and one $$wrong$$ math block
         """,
            [MathError.get_error_name()],
            True,
        ),
        (
            """\
[ ] Invalid todo
There is $$invalid$$ math in this line
def test_todo_error_str():
    todo_error = TodoError()
    assert str(todo_error) == 'Todo Error (no `-` used in todo item)'
def test_todo_error_fix_only_accepts_one_line():
    todo_error = TodoError()
    wrong_input = ['', '']
    with pytest.raises(Exception):
        todo_error.fix(wrong_input)
def test_todo_error_fix(test_input, expected):
    todo_error = TodoError()
    assert todo_error.fix([test_input])[0] == expected
def test_todo_error_only_accepts_one_line():
    todo_error = TodoError()
    with pytest.raises(Exception):
        todo_error.validate(['line 1', 'line 2'])
def test_todo_error_validate(test_input, expected):
    todo_error = TodoError()
    assert todo_error.validate([test_input]) == expected