Skip to content

SublimeLinter 4 and improvements#3

Open
meengit wants to merge 28 commits into
qltysh-archive:masterfrom
meengit:master
Open

SublimeLinter 4 and improvements#3
meengit wants to merge 28 commits into
qltysh-archive:masterfrom
meengit:master

Conversation

@meengit
Copy link
Copy Markdown

@meengit meengit commented Jun 23, 2020

Hi!

This Pull Request is a follow up of the Pull Request #2 of @schuylr. It brings various fixes and improvements:

  • Support for SublimeLinter 4 (current)
  • SublimeText Project is no longer required, the linter will detect its context also on open folders and files
  • Updated Readme.md including examples

I hope to hear from you to bring this package forward. Please let me know if you are okay with my changes or if you expect any adjustments.

Thank you very much!

@braver
Copy link
Copy Markdown

braver commented Dec 23, 2020

Just a little heads-up. This package seems to be abandoned. We intend to replace its entry in Sublime Text package control with a fork that will be made compatible with SublimeLinter 4.

Comment thread linter.py Outdated
'selector': (
'source.css, '
'source.go, '
'source.haskall, '
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-> source.haskell

Comment thread linter.py Outdated
executable = "codeclimate"
defaults = {
'chdir': "${project}",
'executable': 'codeclimate',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably no need to set this, it's automatically chosen by the SL framework as it's the first item in the list you return from cmd().

Comment thread linter.py Outdated
)
executable = "codeclimate"
defaults = {
'chdir': "${project}",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confusing to have a chdir as we already have working_dir. What's the difference between them, when do you want to set different things here, one for working_dir, one for chdir. We also don't change a dir.

Comment thread linter.py Outdated
comment_re = None

root = None
path = None
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You actually never access self.root or self.path so initializing here is probably not necessary.

Comment thread linter.py Outdated

root = re.search(r"\{(\w+)\}", self.defaults['chdir']).group(1)
path = self.filename.replace(self.context.get(root) + '/', '')
return ['codeclimate', 'analyze', path]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So generally, you must or want to set the relative path to the current file. Relative to what? Usually the working_dir, no? (I don't see this requirement in the codeclimate docs btw.)

You can just get the working dir using self.get_working_dir() and compute the relative path to the filename, for example using os.path.relpath(self.filename, self.get_working_dir()) (add import os at the top of the file) as it is Windows compatible. (Although it looks like codeclimate is not either compatible or as easy to use on Windows.) As long as it's a file-only-linter this could work as you don't have to expect None values for filename or cwd. You do have to expect that the user opens random files within one window though. So a check that filename is actually under control of the folder seems reasonable.

Generally return ["codeclimate", "analyze", "$args", path] t.i. somehow telling where user args should be injected is usually a good idea.

Generally, you probably don't want to read in the defaults (self.defaults[...]). You do want to read the actual, final settings using self.settings[...]. Otherwise you don't get the values the user specified but always the defaults. Setting (mutating) self.defaults is probably not what you want for the same reasons.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for your review @kaste.

Your explanations helped me a lot to better understand how SublimeLinter works. I tried to improve my work based on your comments. Please feel free to request further changes.

Comment thread linter.py Outdated
else:
command = 'codeclimate'

return [command, 'analyze', '${args}', '${file}']
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now it's so easy you don't have to define a method at all. executable is handled by the SL framework.

Just define cmd = ('codeclimate', 'analyze', '${args}', '${file_on_disk}'), maybe right before defaults for readability. file_on_disk holds the same value as file but also acts as a marker for SublimeLinter.

Copy link
Copy Markdown
Author

@meengit meengit Dec 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @kaste

Thank you very much for your feedback.

Yes, that looks very easy. However, I noticed an additional issue. In some cases, I had results where shown in the wrong file – something like "the linter is showing File A's results in File B."
In short, I believe this effect appears because the file path is absolute and not relative to the working directory. (If you want to drive deeper into this, I believe qltysh/qlty#845 gives an overview of some of the key points.)

However, I solved the issue in doing this (short version without exception handling):

path = os.path.relpath(self.filename, self.get_working_dir())
return ['codeclimate', 'analyze', '${args}', path]

However, SublimeLinter appends the absolute path automatically. So, what I expect is:

INFO:SublimeLinter.lint.linter:Running ...

  /Users/run/Desktop/TEST  (working dir)
  $ /usr/local/bin/codeclimate analyze src/Plugin/Field/FieldWidget/ACLConfigSelector.php

But what I get at the moment is:

INFO:SublimeLinter.lint.linter:Running ...

  /Users/run/Desktop/TEST  (working dir)
  $ /usr/local/bin/codeclimate analyze src/Plugin/Field/FieldWidget/ACLConfigSelector.php /Users/run/Desktop/TEST/src/Plugin/Field/FieldWidget/ACLConfigSelector.php

I tried to get off this "automatic" automatic appending in several ways, but I found no way to solve it. What do I wrong?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, the automatic filename appending... That's a shitty part of SublimeLinter we couldn't get rid of for compatibility reasons. We probably can change that in 2021.

We need a marker in the cmd, iirc return ["codeclimate", ..., "${xoo}"] should work. (T.i. any unused, undefined marker.)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @kaste

Thank you very much for your answer. I'm not sure if I understand you correctly; or if my Python skills are not strong enough, to handle this "marker" thing.

I tried

    def cmd(self):
        file = os.path.relpath(self.filename, self.get_working_dir())

        return ['codeclimate', 'analyze', '${args}', file, '${xoo}']

and, surprisingly, this works. But it looks to me horribly wrong. May you have a working example for me?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's kinda horrible but easy to use. When we see a marker like $file_on_disk or anything else we do not append the filename automatically. When you use $xoo it will resolve to the empty string so this hack works afair. (Note that this plugin is the only plugin using this hack, all other plugins either use file_path or file_on_disk as all of them work with absolute file paths.)

The auto-appending code is actually in the finalize_cmd method but there is no need to implement/override this method as it's more complicated and brittle I think.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @kaste

Oh, ok… I'm relieved.

I updated the command and appended the marker '${xoo}'. I also tried to catch someone trying to run the linter against a file from outside the working directory*. For this purpose, I check with if not (abs_path.startswith(os.path.abspath(working_dir) + os.sep)) if the current file is in the working directory. If it is not, I try to print a logger.info message and preventing SublimeLinter from linting the current file (return None). As far as I can see, it works but raises a backend error. Can I solve this case more accurately?

I plan to eliminate the limitation in further releases, and I see several solutions and a bunch of work. For now, I only try to get this plugin to work together with SublimeLinter 4.

* I'm doing this because of the "the linter is showing File A's results in File B"-issue when codeclimate is called with an absolute file path or from outside the working directory.

Comment thread linter.py Outdated

if not (abs_path.startswith(os.path.abspath(working_dir) + os.sep)):
logger.info(msg)
return None
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of return None which is not valid, raise PermanentError("file not part of the working dir") (error message up to you). At the top add from SublimeLinter.lint import PermanentError.

You probably want to call self.notify_unassign() before the raise to not show any errors to the user, basically to unassign the linter from the view. (Just as logger.info does only print in debug mode.) If you want to indicate an error to the user use self.notify_failure() instead and logger.warn. (I don't think that's a use case here because the user cannot fix this, we just ignore arbitrary views from outside the main folder.)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it's enough to set

defaults = {
	...
	"excludes": ["!${folder::}*"]
}

t.i. exclude any filenames not starting with ${folder}. And since folder can be empty, with a fallback ${folder::} since no path can start with a :.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, maybe not that good since a user can change this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the exclusion statement, and it works as predicted. If I have opened a directory and then open a single file from another directory, the linter will not run:

SublimeLinter: linter.py:1042         codeclimate skipped '/Users/run/Desktop/ExternalFormatter.php', excluded by '!/Users/run/Desktop/TEST*'
INFO:SublimeLinter.lint.linter:codeclimate skipped '/Users/run/Desktop/ExternalFormatter.php', excluded by '!/Users/run/Desktop/TEST*'

I also updated my exception handling to:

msg = 'The file \'%s\' is not part of the working dir (%s). ' \
      'Please see the Linter\'s README.md to get further ' \
      'instructions.' % (abs_path, working_dir)

if not (abs_path.startswith(os.path.abspath(working_dir) + os.sep)):
    self.notify_unassign()
    raise PermanentError(msg)

I know it is a bit redundant along with the exclusion statement, but I suggest keeping it in place just in case…

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you need to log the message using the logger as we don't log the error message, sorry that was probably misleading. (In PermanentError(msg) the msg is not for the user; you could not set a msg as well.)

I don't think the excludes trick is worth it; as the user can set the working_dir manually, in that case our excludes ($folder) would not work or still point to a different directory.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the exception handling in doing a logger.warning first and then and then unassign and raise the permanent error in 281e165.

Ah yes, you're right. I removed the exclusion in c44c78a.

Comment thread linter.py Outdated

from SublimeLinter.lint import Linter, util, persist

logger = logging.getLogger('SublimeLinter.plugin.eslint')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eslint -> codeclimate

eslint is a different linter.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the logger messages for now.

Comment thread linter.py
'text.html'
)
}
regex = r'^(?P<line>\d+)(?:-\d+)?:\s(?P<message>.+)$'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible try to extract the filename the error is coming from to the group (?P<filename>). If that's possible SublimeLinter will handle errors from multiple files without any warnings.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output of codeclimate is:

== src/Plugin/Field/FieldType/ACLFieldsItem.php (1 issue) ==
29: Define a constant instead of duplicating this literal "value" 3 times. [sonar-php]

I set multiline to true and updated the regexp to:

regex = r'^== ((?P<filename>.*)(?= \(\d+ issue\) ==))( \(\d+ issue\) ==\n(?P<line>\d+))(?:-\d+)?:\s(?P<message>.+)$'

As far as I can see, it is working as expected. The doubled "(\d+ issue)" isn't that nice, but I found no other way to exclude the text from the file path with a positive lookahead and then set the second start in the same position for the rest.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file name here looks like a header; I doubt codeclimate will duplicate the filename information; so this won't extract multiple issues from one file I think. Have you tested this?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh… Oh! Yes, it is. Thank you for your close inspection. I reverted the regex in b7fa651.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kaste, I suggest preparing my repository to file the PR to overtake this linter plugin's maintenance in the next days. I will let the master branch, pointing to this pull request, as is, and switch with the protected branch to a newly created branch called main for further development.

Comment thread README.md Outdated

Suppose you use a `.codeclimate.yml`-configuration file. In that case, the `codeclimate` CLI needs to be executed in your configuration file's directory. Otherwise, it can't detect your configuration and runs only the default analyzes.

SublimeLinter takes the (first) open folder (or the folder of a single opened file) as the working directory for executing its linter commands. You can change this behavior by setting the working directory of execution:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify: It is possible to have multiple folders "open" in Sublime in one project/window. SublimeLinter will then take the first matching folder, t.i. usually a folder the file belongs to.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kaste, thank you very much for your awesome support. I also updated the docs. Do you see further improvements in my PR?

Copy link
Copy Markdown

@kaste kaste left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Ping @braver for how we actually take/transition the repo. Afair we only need a new release tag on the master branch of your repo.

@kaste
Copy link
Copy Markdown

kaste commented Dec 30, 2020

Beside the code review I have a question. This linter adapter registers for a lot of languages, are these supported out of the box actually, or downloaded and updated on the fly? For example they download some code on first request. (Iirc haskell is a GB on my machine.)

The docs state that piping in the source code is supported. Why is this here a saved-file only linter?

Comment thread README.md

## Installation
SublimeLinter 3 must be installed in order to use this plugin. If SublimeLinter 3 is not installed, please follow the instructions [here][installation].
SublimeLinter 4 must be installed in order to use this plugin. If SublimeLinter 4 is not installed, please follow the instructions [here][installation].
Copy link
Copy Markdown

@braver braver Jan 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or simply "SublimeLinter" without the "4". If you look for SublimeLinter in package control it's not super obvious it's v4 or v3. Nobody has v3 installed "by accident".

@braver
Copy link
Copy Markdown

braver commented Jan 1, 2021

how we actually take/transition the repo

We need to change the package control entry to point to your fork (so create a PR there!) and then in your fork you need to create a release that's > 1.0.0 (so 2.0.0 or 1.1.0, your pick).

@meengit
Copy link
Copy Markdown
Author

meengit commented Jan 3, 2021

Hi @kaste

As far as I understand, codeclimate does not install engines automatically. From the documentation:

As part of setting up your project, we recommend running codeclimate engines:install from within your repository before running codeclimate analyze, and after adding any new plugins to your configuration file.

I did a set of simple performance tests and think it is not that good to run the codeclimate CLI on each random file that will be opened in SublimeText.

I suggest removing all selectors from the defaults and letting the user decide which selectors they want to use and do these settings globally or in a SublimeText Project File.

From my point of view, we're now talking about changes that go beyond this pull request. It is no longer just about compatibility with SublimeLinter 4, but about further interventions. Therefore, I have decided to leave this pull request as it is and make the changes in a new branch in my repository. I've added you as a maintainer to my repository and as a reviewer to the corresponding pull request, which you can find here.

@meengit
Copy link
Copy Markdown
Author

meengit commented Jan 3, 2021

Hi @braver, thank you very much for your update. I will file my Pull Request once @kaste has proven the changes here.

@meengit
Copy link
Copy Markdown
Author

meengit commented Jan 4, 2021

Hi @braver, my fork is now ready to replace the original package. I filed my pull request to overtake the responsibility.

@codeclimate, I'm open to giving you back the control over this package in reverting the package control index. If you are not interested, I would appreciate a deprecation notice or something similar at your repository's README.md.

Thank you very much for developing the codeclimate CLI. It is an excellent tool and helps me every day to improve my code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants