Repository: pylint-dev/pylint (Python)

Issue:
Pylint 2.8.2 broke pylint-quotes
<!--
  Hi there! Thank you for discovering and submitting an issue.

  Before you submit this, make sure that the issue doesn't already exist
  or if it is not closed.

  Is your issue fixed on the preview release?:
    pip install pylint astroid --pre -U
-->

### Steps to reproduce

See https://github.com/edaniszewski/pylint-quotes/issues/24
See `pylintrc` in attached [pylint_bug.zip](https://github.com/PyCQA/pylint/files/6393225/pylint_bug.zip)

```bash
python -m venv venv
. venv/bin/activate
pip install -r requirements.txt
pylint --rcfile pylintrc demo.py
```

### Current behavior

Running this configuration on a file containing:
```
foo = "bar"
```
results in an exception:
```
Traceback (most recent call last):
  File "/c/tmp/pylint_bug/venv/bin/pylint", line 10, in <module>
    sys.exit(run_pylint())
  File "/c/tmp/pylint_bug/venv/lib64/python3.8/site-packages/pylint/__init__.py", line 24, in run_pylint
    PylintRun(sys.argv[1:])
  File "/c/tmp/pylint_bug/venv/lib64/python3.8/site-packages/pylint/lint/run.py", line 381, in __init__
    linter.check(args)
  File "/c/tmp/pylint_bug/venv/lib64/python3.8/site-packages/pylint/lint/pylinter.py", line 873, in check
    self._check_files(
  File "/c/tmp/pylint_bug/venv/lib64/python3.8/site-packages/pylint/lint/pylinter.py", line 907, in _check_files
    self._check_file(get_ast, check_astroid_module, name, filepath, modname)
  File "/c/tmp/pylint_bug/venv/lib64/python3.8/site-packages/pylint/lint/pylinter.py", line 933, in _check_file
    check_astroid_module(ast_node)
  File "/c/tmp/pylint_bug/venv/lib64/python3.8/site-packages/pylint/lint/pylinter.py", line 1067, in check_astroid_module
    retval = self._check_astroid_module(
  File "/c/tmp/pylint_bug/venv/lib64/python3.8/site-packages/pylint/lint/pylinter.py", line 1110, in _check_astroid_module
    checker.process_tokens(tokens)
  File "/c/tmp/pylint_bug/venv/lib64/python3.8/site-packages/pylint_quotes/checker.py", line 259, in process_tokens
    self._process_string_token(token, start_row, start_col)
  File "/c/tmp/pylint_bug/venv/lib64/python3.8/site-packages/pylint_quotes/checker.py", line 295, in _process_string_token
    self._invalid_string_quote(
  File "/c/tmp/pylint_bug/venv/lib64/python3.8/site-packages/pylint_quotes/checker.py", line 341, in _invalid_string_quote
    **self.get_offset(col)
  File "/c/tmp/pylint_bug/venv/lib64/python3.8/site-packages/pylint_quotes/checker.py", line 360, in get_offset
    if (2, 2, 2) < pylint_version:
TypeError: '<' not supported between instances of 'int' and 'str'
```

### Expected behavior
`pylint` should not throw exceptions, and instead generate report complaining about the use of double quotes (`""`) and illegal variable name `foo`.


### pylint --version output

Result of `pylint --version` output:

```
→ pylint --version
pylint 2.8.2
astroid 2.5.6
Python 3.8.3 (default, Feb 26 2020, 00:00:00)
[GCC 9.3.1 20200408 (Red Hat 9.3.1-2)]
```

Additional dependencies:
```
pylint-quotes==0.2.1
```

### Probable cause:
1. `pylint` seems to have just changed versioning scheme: https://github.com/PyCQA/pylint/compare/pylint-2.8.1...v2.8.2
2. Which broke this line: https://github.com/edaniszewski/pylint-quotes/blob/master/pylint_quotes/checker.py#L360


Discussion:
Hello, thank you for the report. We changed the tuple from int to string because we can't be sure that we're capable of getting the version from ``importlib_metadata`` so there is a default ``2.8.2+`` value that would not be castable to an int.
Okay, but that was a breaking change for plugin developers, and as such I'm not entirely sure why this was released as PATCH version, and not at least MINOR. See: https://semver.org/#what-if-i-inadvertently-alter-the-public-api-in-a-way-that-is-not-compliant-with-the-version-number-change-ie-the-code-incorrectly-introduces-a-major-breaking-change-in-a-patch-release

The `pylint-quotes` plugin seems to be dead (no update for 2 years, opened issues) so I'm wondering if you plan on doing anything to keep it working, or should we rather start looking for an alternative. I've seen the `check-quote-consistency` setting which we may want to try. Can you confirm it's is this the same as what `pylint-quotes` did?

Personally I would advise to just use black or another auto-formatter. Flake8 had a lot of check to not use ``"`` in its pre-commit configuration at some point (you could check in their git history if you're interested) but even them have black now. Regardless numversion will be fixed in 2.8.3.
Btw 2.8 was the minor that removed it, we added it back in a patch.



Patch:
diff --git a/ChangeLog b/ChangeLog
index 62dd0f1ae6..15ba23760c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -9,6 +9,10 @@ Release date: 2021-04-26
 ..
   Put new features and bugfixes here and also in 'doc/whatsnew/2.9.rst'
 
+* numversion tuple contains integers again to fix multiple pylint's plugins that relied on it
+
+  Closes #4420
+
 * Fix false-positive ``too-many-ancestors`` when inheriting from builtin classes,
   especially from the ``collections.abc`` module
 
diff --git a/pylint/__pkginfo__.py b/pylint/__pkginfo__.py
index 8316089ec9..1a1d99dd28 100644
--- a/pylint/__pkginfo__.py
+++ b/pylint/__pkginfo__.py
@@ -1,5 +1,6 @@
 # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 # For details: https://github.com/PyCQA/pylint/blob/master/LICENSE
+from typing import Tuple
 
 from pkg_resources import DistributionNotFound, get_distribution
 
@@ -8,5 +9,32 @@
 except DistributionNotFound:
     __version__ = "2.8.2+"
 
-# Kept for compatibility reason, see https://github.com/PyCQA/pylint/issues/4399
-numversion = tuple(__version__.split("."))
+
+def get_numversion_from_version(v: str) -> Tuple:
+    """Kept for compatibility reason
+
+    See https://github.com/PyCQA/pylint/issues/4399
+    https://github.com/PyCQA/pylint/issues/4420,
+    """
+    v = v.replace("pylint-", "")
+    version = []
+    for n in v.split(".")[0:3]:
+        try:
+            version.append(int(n))
+        except ValueError:
+            num = ""
+            for c in n:
+                if c.isdigit():
+                    num += c
+                else:
+                    break
+            try:
+                version.append(int(num))
+            except ValueError:
+                version.append(0)
+    while len(version) != 3:
+        version.append(0)
+    return tuple(version)
+
+
+numversion = get_numversion_from_version(__version__)


Test patch:
diff --git a/tests/test_numversion.py b/tests/test_numversion.py
new file mode 100644
index 0000000000..d1f459a5a0
--- /dev/null
+++ b/tests/test_numversion.py
@@ -0,0 +1,26 @@
+# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+# For details: https://github.com/PyCQA/pylint/blob/master/LICENSE
+
+import pytest
+
+from pylint.__pkginfo__ import get_numversion_from_version
+
+
+@pytest.mark.parametrize(
+    "version,expected_numversion",
+    [
+        ["2.8.1", (2, 8, 1)],
+        ["2.8.2+", (2, 8, 2)],
+        ["3.0.0a0", (3, 0, 0)],
+        ["3..0", (3, 0, 0)],
+        ["1.a", (1, 0, 0)],
+        ["", (0, 0, 0)],
+        ["3.0.0b1", (3, 0, 0)],
+        ["3.0.0rc1", (3, 0, 0)],
+        ["3.0.0dev-234324234234f23abc4", (3, 0, 0)],
+        ["pylint-2.4.7", (2, 4, 7)],
+        ["2.8.3.dev3+g28c093c2.d20210428", (2, 8, 3)],
+    ],
+)
+def test_numversion(version, expected_numversion):
+    assert get_numversion_from_version(version) == expected_numversion