Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def get_version():
'pytest',
'pytest-randomly',
'mypy; python_version >= "3.3"',
'mock; python_version < "3.3"',
]
}
)
2 changes: 2 additions & 0 deletions shopify_python/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

from pylint import lint
from shopify_python import google_styleguide
from shopify_python import shopify_styleguide


__version__ = '0.1.2'


def register(linter): # type: (lint.PyLinter) -> None
google_styleguide.register_checkers(linter)
shopify_styleguide.register_checkers(linter)
53 changes: 53 additions & 0 deletions shopify_python/shopify_styleguide.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import re
import tokenize
import typing # pylint: disable=unused-import

import pylint.utils

from pylint import checkers
from pylint import interfaces
from pylint import lint # pylint: disable=unused-import


def register_checkers(linter): # type: (lint.PyLinter) -> None
"""Register checkers."""
linter.register_checker(ShopifyStyleGuideChecker(linter))


class ShopifyStyleGuideChecker(checkers.BaseTokenChecker):
"""
Pylint checker for Shopify-specific Code Style.
"""
__implements__ = (interfaces.ITokenChecker,)

name = 'shopify-styleguide-checker'

msgs = {
'C6101': ("%(code)s disabled as a message code, use '%(name)s' instead",
'disable-name-only',
"Disable pylint rules via message name (e.g. unused-import) and not message code (e.g. W0611) to "
"help code reviewers understand why a linter rule was disabled for a line of code."),
}

RE_PYLINT_DISABLE = re.compile(r'^#[ \t]*pylint:[ \t]*(disable|enable)[ \t]*=(?P<messages>[a-zA-Z0-9\-_, \t]+)$')
RE_PYLINT_MESSAGE_CODE = re.compile(r'^[A-Z]{1,2}[0-9]{4}$')

def process_tokens(self, tokens):
# type: (typing.Sequence[typing.Tuple]) -> None
for _type, string, start, _, _ in tokens:
start_row, _ = start
if _type == tokenize.COMMENT:

def get_name(code):
try:
return self.linter.msgs_store.get_msg_display_string(code)
except pylint.utils.UnknownMessage:
return 'unknown'

matches = self.RE_PYLINT_DISABLE.match(string)
if matches:
for msg in matches.group('messages').split(','):
msg = msg.strip()
if self.RE_PYLINT_MESSAGE_CODE.match(msg):
self.add_message('disable-name-only', line=start_row,
args={'code': msg, 'name': get_name(msg)})
55 changes: 55 additions & 0 deletions tests/shopify_python/test_shopify_styleguide.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import pylint.testutils

try:
import unittest.mock as mock # Python 3.3+
except ImportError:
import mock # Python < 3.3

from shopify_python import shopify_styleguide


class TestShopifyStyleGuideChecker(pylint.testutils.CheckerTestCase):

CHECKER_CLASS = shopify_styleguide.ShopifyStyleGuideChecker

def test_disable_name_only(self):

# Patch 'msgs_store'
mock_msgs_store = mock.Mock()
mock_msgs_store.get_msg_display_string = mock.Mock()
mock_msgs_store.get_msg_display_string.return_value = 'mocked'
setattr(self.linter, 'msgs_store', mock_msgs_store)

# Create tokens
tokens = pylint.testutils.tokenize_str("""
import os # pylint: disable=unused-import
import os # pylint: disable=unused-import,W0611
import os # pylint: disable=W0611,C0302,C0303
import os # pylint: disable=W0611
import os #pylint:disable=W0611
import os # pylint:\t\t disable \t\t = \t\t W0611
def fnc():
# pylint: disable=C0112
pass
""".strip())

# Expected
expected_line_msgcodes = [
(2, 'W0611'),
(3, 'W0611'),
(3, 'C0302'),
(3, 'C0303'),
(4, 'W0611'),
(5, 'W0611'),
(6, 'W0611'),
(8, 'C0112'),
]

# Test
with self.assertAddsMessages(*[
pylint.testutils.Message('disable-name-only', line=line, args={'code': code, 'name': 'mocked'})
for line, code in expected_line_msgcodes
]):
self.checker.process_tokens(tokens)
mock_msgs_store.get_msg_display_string.assert_has_calls(
[mock.call(code) for _, code in expected_line_msgcodes])