def parse_message(self, message):
        """
        Parse message object into standard elements of an error and return them.

        """

        error_message = message['message']
        line = message['line'] - 1
        col = message['col']

        # set error and warning flags based on message type
        error = None
        warning = None
        if message['type'] == 'error':
            error = True
            warning = False
        elif message['type'] == 'warning':
            error = False
            warning = True
        elif message['type'] == 'info':
            # ignore info messages by setting message to None
            message = None

        persist.debug('message -- msg:"{}", line:{}, col:{}, error: {}, warning: {}, message_obj:{}'.format(error_message, line, col, error, warning, message))

        return message, line, col, error, warning, error_message, None
Ejemplo n.º 2
0
    def is_current_file(self, working_dir, matched_file):
        """
        Return true if `matched_file` is logically the same file as `self.filename`.

        Cargo example demonstrating how matching is done:

          - os.getcwd() = '/Applications/Sublime Text.app/Contents/MacOS'
          - `working_dir` = '/path/to/project'
          - `matched_file` = 'src/foo.rs'
          - `self.filename` = '/path/to/project/src/foo.rs'

        The current OS directory is not considered at all -- comparison is only done
        relative to where Cargo.toml was found.  `os.path.realpath` is used to
        normalize the filenames so that they can be directly compared after manipulation.
        """
        abs_matched_file = os.path.join(working_dir, matched_file)

        persist.debug('Sublime Text cwd: ', os.getcwd())
        persist.debug('Build cwd: ', working_dir)
        persist.debug('Current filename: ', self.filename)
        persist.debug('Matched filename: ', matched_file)
        persist.debug('Compared filename: ', abs_matched_file)

        return os.path.realpath(self.filename) == os.path.realpath(
            abs_matched_file)
Ejemplo n.º 3
0
    def find_errors(self, output):
        """
        Convert flow's json output into a set of matches SublimeLinter can process.

        I'm not sure why find_errors isn't exposed in SublimeLinter's docs, but
        this would normally attempt to parse a regex and then return a generator
        full of sanitized matches. Instead, this implementation returns a list
        of errors processed by _error_to_tuple, ready for SublimeLinter to unpack
        """
        try:
            # calling flow in a matching syntax without a `flowconfig` will cause the
            # output of flow to be an error message. catch and return []
            check, coverage = json.loads(output)
        except ValueError:
            persist.debug('flow {}'.format(output))
            return []

        errors = check.get('errors', [])

        persist.debug('flow {} errors. passed: {}'.format(
            len(errors), check.get('passed', True)
        ))

        return chain(
            map(self._error_to_tuple, errors),
            map(self._uncovered_to_tuple,
                coverage.get('expressions', {}).get('uncovered_locs', []),
                repeat(set()))
        )
    def get_cmd(self):
        """Add condition before calling foodcritic."""

        cmd = super(RubyLinter, self).get_cmd()

        # Do nothing for unsaved files
        if self.view.is_dirty():
            persist.debug("foodcritic: Can't handle unsaved files, skipping")
            return False

        return cmd
Ejemplo n.º 5
0
    def run(self, cmd, code):
        """
        Flow lint code if `@flow` pragma is present.

        if not present, this method noops
        """
        _flow_comment_re = r'\@flow'

        if not re.search(_flow_comment_re, code) \
                and not self._inline_setting_bool('all'):
            persist.debug("did not find @flow pragma")
            return ''

        persist.debug("found flow pragma!")
        check = super().run(cmd, code)

        coverage = super().run(_build_coverage_cmd(cmd), code) \
            if self._inline_setting_bool('coverage') else '{}'

        return '[%s,%s]' % (check, coverage)
Ejemplo n.º 6
0
    def schema_path(self):
        self.schema_exists = True
        try:
            xsi = 'http://www.w3.org/2001/XMLSchema-instance'
            root = minidom.parseString(self.code).documentElement
            schema = root.getAttributeNS(xsi, 'noNamespaceSchemaLocation')

            if schema:
                online = schema.find('http://') == 0 or schema.find('https://') == 0
                if not (online or os.path.isabs(schema)):
                    schema = os.path.dirname(self.filename) + '/' + schema

                if not online and not os.path.isfile(schema):
                    self.schema_exists = False
                    return None

                return schema
        except Exception as e:
            persist.debug(e)

        return None
    def lint(self, path, content):
        """Lint the content of a file."""
        output = ""
        try:
            persist.debug("Connecting to Julia lint server ({}, {})".format(self.address, self.port))  # noqa
            output = self._lint(path, content)
        except Exception as e:
            persist.debug(e)

            if not self.auto_start:
                persist.printf("Julia lint server is not running")
            else:
                # if self.proc is not None:
                #     if self.proc.poll() is None:
                #         persist.debug("Local Julia lint server was started")
                #         return
                #     else:
                #        raise subprocess.SubprocessError(self.proc.returncode)

                persist.printf("Launching Julia lint server on localhost port {}".format(self.port))  # noqa
                self.start()

                persist.printf("Julia lint server starting up, server will be operational shortly")  # noqa
                try:
                    # Wait to give Julia time to start
                    sleep(5)

                    output = self._lint(path, content)
                except Exception as e:
                    persist.debug(e)
                    persist.printf("Julia lint server failed to start")
                else:
                    persist.printf("Julia lint server now operational")

        return output
Ejemplo n.º 8
0
    def cmd(self):
        """Return a list with the command line to execute."""

        cmd = [
            self.executable_path,
            '*',
            '--follow-imports=silent',  # or 'skip'
            '--ignore-missing-imports',
            '--show-column-numbers',
            '--hide-error-context',
            '@'
        ]

        if self.tempfile_suffix == "-":
            # --shadow-file SOURCE_FILE SHADOW_FILE
            #
            # '@' needs to be the shadow file,
            # while we request the normal filename
            # to be checked in its normal environment.
            # Trying to be smart about view.is_dirty and optimizing '--shadow-file' away
            # doesn't work well with SublimeLinter internals.
            cmd[-1:] = ['--shadow-file', self.filename, '@', self.filename]

        # Add a temporary cache dir to the command if none was specified.
        # Helps keep the environment clean
        # by not littering everything with `.mypy_cache` folders.
        settings = self.get_view_settings()
        if not settings.get('cache-dir'):
            cwd = os.getcwd()
            if cwd in tmpdirs:
                cache_dir = tmpdirs[cwd].name
            else:
                tmp_dir = tempfile.TemporaryDirectory(prefix=TMPDIR_PREFIX)
                tmpdirs[cwd] = tmp_dir
                cache_dir = tmp_dir.name
                persist.debug("Created temporary cache dir at: " + cache_dir)
            cmd[1:1] = ["--cache-dir", cache_dir]

        return cmd
Ejemplo n.º 9
0
    def is_current_file(self, working_dir, matched_file):
        """
        Return true if `matched_file` is logically the same file as `self.filename`.

        Cargo example demonstrating how matching is done:

          - os.getcwd() = '/Applications/Sublime Text.app/Contents/MacOS'
          - `working_dir` = '/path/to/project'
          - `matched_file` = 'src/foo.rs'
          - `self.filename` = '/path/to/project/src/foo.rs'

        The current OS directory is not considered at all -- comparison is only done
        relative to where Cargo.toml was found.  `os.path.realpath` is used to
        normalize the filenames so that they can be directly compared after manipulation.
        """
        abs_matched_file = os.path.join(working_dir, matched_file)

        persist.debug('Sublime Text cwd: ', os.getcwd())
        persist.debug('Build cwd: ', working_dir)
        persist.debug('Current filename: ', self.filename)
        persist.debug('Matched filename: ', matched_file)
        persist.debug('Compared filename: ', abs_matched_file)

        return os.path.realpath(self.filename) == os.path.realpath(abs_matched_file)
Ejemplo n.º 10
0
    def _error_to_tuple(self, error):
        """
        Map an array of flow error messages to a fake regex match tuple.

        flow returns errors like this: {message: [{<msg>},..,]} where
        <msg>:Object {
            descr: str,
            level: str,
            path: str,
            line: number,
            endline: number,
            start: number,
            end: number
        }

        Which means we can mostly avoid dealing with regex parsing since the
        flow devs have already done that for us. Thanks flow devs!
        """
        error_messages = error.get('message', [])
        match = self.filename == error_messages[0]['path']
        # TODO(nsfmc): `line_col_base` won't work b/c we avoid `split_match`'s codepath
        line = error_messages[0]['line'] - 1
        col = error_messages[0]['start'] - 1

        level = error_messages[0]['level']
        is_error = level == 'error'
        is_warning = level == 'warning'

        combined_message = " ".join([m.get('descr', '') for m in error_messages])

        near_match = re.search(self.__flow_near_re, combined_message)
        near = near_match.group('near') if near_match else None
        persist.debug('flow line: {}, col: {}, level: {}, message: {}'.format(
            line, col, level, combined_message))

        return (match, line, col, is_error, is_warning, combined_message, near)
Ejemplo n.º 11
0
    def split_match(self, match):
        """
        Pre-process the matchObject before passing it upstream.

        Several reasons for this:
          * unrelated library files can throw errors, and
            we only want errors from the linted file.
          * our regex contains more than just the basic
            capture groups (filename, line, message, etc.)
            but we still need to pass a match object that
            contains the above groups upstream.
          * Line is not reported for some macro errors
          * etc..

        """
        dummy_match = None

        if match:
            captures = match.groupdict()
            dummy_string = self.build_dummy_string(captures)
            dummy_match = re.match(self.dummy_regex, dummy_string)

        if dummy_match:
            filename = os.path.join(self.chdir, dummy_match.group('filename'))

            persist.debug("Linted file: %s" % self.filename)
            persist.debug("Error source: %s" % filename)

            if self.filename != filename:
                persist.debug(
                    "Ignore error from %s (linted file: %s)" %
                    (filename, self.filename)
                )

                dummy_match = None

        return super().split_match(dummy_match)
Ejemplo n.º 12
0
    def build_dummy_string(self, captures):
        """
        Build a string to be matched against self.dummy_regex.

        It is used to ensure that a matchObject with the
        appropriate group names is passed upstream.

        Returns a string with the following format:
        {filename}:{line}:{error_type}:{message}

        """
        if captures['e_file1'] is not None:
            persist.debug('Error type 1')
            dummy_str = '%s:%s:%s:%s' % (captures['e_file1'],
                                         captures['e_line1'], 'error',
                                         captures['e_msg1'])
        elif captures['e_file2'] is not None:
            persist.debug('Error type 2')
            dummy_str = "%s:%s:%s:%s" % (captures['e_file2'],
                                         captures['e_line2'], 'error',
                                         captures['e_msg2'])
        elif captures['e_file3'] is not None:
            persist.debug('Error type 3')
            dummy_str = "%s:%s:%s:%s" % (captures['e_file3'],
                                         captures['e_line3'], 'error',
                                         captures['e_msg3'])
        elif captures['w_file1'] is not None:
            persist.debug('Warning type 1')
            dummy_str = "%s:%s:%s:%s" % (captures['w_file1'],
                                         captures['w_line1'], 'warning',
                                         captures['w_msg1'])
        elif captures['w_file2'] is not None:
            persist.debug('Warning type 2')
            dummy_str = "%s:%s:%s:%s" % (captures['w_file2'],
                                         captures['w_line2'], 'warning',
                                         captures['w_msg2'])
        else:
            persist.debug('No match')
            dummy_str = ""

        persist.debug("Dummy string: %s" % dummy_str)
        return dummy_str
Ejemplo n.º 13
0
    def _error_to_tuple(self, error):
        """
        Map an array of flow error messages to a fake regex match tuple.

        this is described in `flow/tsrc/flowResult.js` in the flow repo

        flow returns errors like this
        type FlowError = {
            kind: string,
            level: string,
            message: Array<FlowMessage>,
            trace: ?Array<FlowMessage>,
            operation?: FlowMessage,
            extra?: FlowExtra,
        };
        type FlowMessage = {
            descr: string,
            type: "Blame" | "Comment",
            context?: ?string,
            loc?: ?FlowLoc,
            indent?: number,
        };

        Which means we can mostly avoid dealing with regex parsing since the
        flow devs have already done that for us. Thanks flow devs!
        """
        error_messages = error.get('message', [])
        # TODO(nsfmc): `line_col_base` won't work b/c we avoid `split_match`'s
        # codepath
        operation = error.get('operation', {})
        loc = operation.get('loc') or error_messages[0].get('loc', {})

        message = self._find_matching_msg_for_file(error)
        if message is None:
            return (False, 0, 0, False, False, '', None)

        error_context = message.get('context', '')
        loc = message.get('loc')
        message_start = loc.get('start', {})
        message_end = loc.get('end', {})

        line = message_start.get('line', None)
        if line:
            line -= 1
        col = message_start.get('column', None)
        if col:
            col -= 1
        end = message_end.get('column', None)

        # slice the error message from the context and loc positions
        # If error spans multiple lines, though, don't highlight them all
        # but highlight the 1st error character by passing None as near
        # SublimeLinter will strip quotes of `near` strings as documented in
        # http://www.sublimelinter.com/en/latest/linter_attributes.html#regex
        # In order to preserve quotes, we have to wrap strings with more
        # quotes.
        if end and line == (message_end.get('line') - 1):
            near = '"' + error_context[col:end] + '"'
        else:
            near = None

        level = error.get('level', False)
        is_error = level == 'error'
        is_warning = level == 'warning'

        combined_message = " ".join(
            [self._format_message(msg) for msg in error_messages]
        ).strip()

        persist.debug('flow line: {}, col: {}, level: {}, message: {}'.format(
            line, col, level, combined_message))

        return (True, line, col, is_error, is_warning, combined_message, near)
Ejemplo n.º 14
0
    def build_dummy_string(self, captures):
        """
        Build a string to be matched against self.dummy_regex.

        It is used to ensure that a matchObject with the
        appropriate group names is passed upstream.

        Returns a string with the following format:
        {filename}:{line}:{error_type}:{message}

        """
        if captures['e_file1'] is not None:
            persist.debug('Error type 1')
            dummy_str = '%s:%s:%s:%s' % (
                captures['e_file1'],
                captures['e_line1'],
                'error',
                captures['e_msg1']
            )
        elif captures['e_file2'] is not None:
            persist.debug('Error type 2')
            dummy_str = "%s:%s:%s:%s" % (
                captures['e_file2'],
                captures['e_line2'],
                'error',
                captures['e_msg2']
            )
        elif captures['e_file3'] is not None:
            persist.debug('Error type 3')
            dummy_str = "%s:%s:%s:%s" % (
                captures['e_file3'],
                captures['e_line3'],
                'error',
                captures['e_msg3']
            )
        elif captures['w_file1'] is not None:
            persist.debug('Warning type 1')
            dummy_str = "%s:%s:%s:%s" % (
                captures['w_file1'],
                captures['w_line1'],
                'warning',
                captures['w_msg1']
            )
        else:
            persist.debug('No match')
            dummy_str = ""

        persist.debug("Dummy string: %s" % dummy_str)
        return dummy_str
Ejemplo n.º 15
0
    def _error_to_tuple(self, error):
        """
        Map an array of flow error messages to a fake regex match tuple.

        this is described in `flow/tsrc/flowResult.js` in the flow repo

        flow returns errors like this
        type FlowError = {
            kind: string,
            level: string,
            message: Array<FlowMessage>,
            trace: ?Array<FlowMessage>,
            operation?: FlowMessage,
            extra?: FlowExtra,
        };
        type FlowMessage = {
            descr: string,
            type: "Blame" | "Comment",
            context?: ?string,
            loc?: ?FlowLoc,
            indent?: number,
        };

        Which means we can mostly avoid dealing with regex parsing since the
        flow devs have already done that for us. Thanks flow devs!
        """
        error_messages = error.get('message', [])
        # TODO(nsfmc): `line_col_base` won't work b/c we avoid `split_match`'s
        # codepath
        operation = error.get('operation', {})
        loc = operation.get('loc') or error_messages[0].get('loc', {})

        message = self._find_matching_msg_for_file(error)
        if message is None:
            return (False, 0, 0, False, False, '', None)

        error_context = message.get('context', '')
        loc = message.get('loc')
        message_start = loc.get('start', {})
        message_end = loc.get('end', {})

        line = message_start.get('line', None)
        if line:
            line -= 1
        col = message_start.get('column', None)
        if col:
            col -= 1
        end = message_end.get('column', None)

        # slice the error message from the context and loc positions
        # If error spans multiple lines, though, don't highlight them all
        # but highlight the 1st error character by passing None as near
        # SublimeLinter will strip quotes of `near` strings as documented in
        # http://www.sublimelinter.com/en/latest/linter_attributes.html#regex
        # In order to preserve quotes, we have to wrap strings with more
        # quotes.
        if end and line == (message_end.get('line') - 1):
            near = '"' + error_context[col:end] + '"'
        else:
            near = None

        level = error.get('level', False)
        is_error = level == 'error'
        is_warning = level == 'warning'

        combined_message = " ".join(
            [self._format_message(msg) for msg in error_messages]
        ).strip()

        persist.debug('flow line: {}, col: {}, level: {}, message: {}'.format(
            line, col, level, combined_message))

        return (True, line, col, is_error, is_warning, combined_message, near)
Ejemplo n.º 16
0
    def context_sensitive_executable_path(self, cmd):
        """Try to find an executable for a given cmd."""
        settings = self.get_view_settings()

        # If the user explicitly set an executable, it takes precedence.
        # We expand environment variables. E.g. a user could have a project
        # structure where a virtual environment is always located within
        # the project structure. She could then simply specify
        # `${project_path}/venv/bin/flake8`. Note that setting `@python`
        # to a path will have a similar effect.
        executable = settings.get('executable', '')
        if executable:
            executable = util.expand_variables(executable)

            persist.debug("{}: wanted executable is '{}'".format(
                self.name, executable))

            if util.can_exec(executable):
                return True, executable

            persist.printf("ERROR: {} deactivated, cannot locate '{}' ".format(
                self.name, executable))
            # no fallback, the user specified something, so we err
            return True, None

        # `@python` can be number or a string. If it is a string it should
        # point to a python environment, NOT a python binary.
        # We expand environment variables. E.g. a user could have a project
        # structure where virtual envs are located always like such
        # `some/where/venvs/${project_base_name}` or she has the venv
        # contained in the project dir `${project_path}/venv`. She then
        # could edit the global settings once and can be sure that always the
        # right linter installed in the virtual environment gets executed.
        python = settings.get('@python', None)
        if isinstance(python, str):
            python = util.expand_variables(python)

        persist.debug("{}: wanted @python is '{}'".format(self.name, python))

        cmd_name = cmd[0] if isinstance(cmd, (list, tuple)) else cmd

        if python:
            if isinstance(python, str):
                executable = util.find_script_by_python_env(python, cmd_name)
                if not executable:
                    persist.printf(
                        "WARNING: {} deactivated, cannot locate '{}' "
                        "for given @python '{}'".format(
                            self.name, cmd_name, python))
                    # Do not fallback, user specified something we didn't find
                    return True, None

                return True, executable

            else:
                executable = util.find_script_by_python_version(
                    cmd_name, str(python))

                # If we didn't find anything useful, use the legacy
                # code from SublimeLinter for resolving that version.
                if executable is None:
                    persist.debug("{}: Still trying to resolve {}, now trying "
                                  "SublimeLinter's legacy code.".format(
                                      self.name, python))
                    _, executable, *_ = util.find_python(str(python), cmd_name)

                if executable is None:
                    persist.printf(
                        "WARNING: {} deactivated, cannot locate '{}' "
                        "for given @python '{}'".format(
                            self.name, cmd_name, python))
                    return True, None

                persist.debug("{}: Using {} for given @python '{}'".format(
                    self.name, executable, python))
                return True, executable

        # If we're here the user didn't specify anything. This is the default
        # experience. So we kick in some 'magic'
        chdir = self.get_chdir(settings)
        executable = util.ask_pipenv(cmd[0], chdir)
        if executable:
            persist.debug("{}: Using {} according to 'pipenv'".format(
                self.name, executable))
            return True, executable

        # Should we try a `pyenv which` as well? Problem: I don't have it,
        # it's MacOS only.

        persist.debug("{}: trying to use globally installed {}".format(
            self.name, cmd_name))
        # fallback, similiar to a which(cmd)
        executable = util.find_executable(cmd_name)
        if executable is None:
            persist.printf(
                "WARNING: cannot locate '{}'. Fill in the '@python' or "
                "'executable' setting.".format(self.name))
        return True, executable