Repository: oppia/oppia

Issue:
Lint check to ensure a single space after yield keyword in python
A lint check needs to be implemented to ensure there is a single space after the `yield` keyword in python. All corresponding fixes (if any) should be made before or in the same PR as the check. Please see this for reference: https://github.com/oppia/oppia/pull/6249#discussion_r257108069. 


Discussion:
Hey @apb7. I would like to work on this issue. I'm new to lint checks, I would like to receive some guidance for the same. Thanks ! 
Hi @jeeb0xical , you can start working on the issue. You have to add the logic in pylint_extensions.py and it's corresponding test in pylint_extensions_test.py.
These are the links which can help you:
http://pylint.pycqa.org/en/latest/how_tos/custom_checkers.html
&
https://breadcrumbscollector.tech/writing-custom-checkers-for-pylint/

I hope it helps and feel free to ask.
Thanks a lot @anubhavsinha98. Will work on it !
Hi, @jeeb0xical Any updates on your work?
@jeeb0xical are you still working on it ?

Resolving patch:
diff --git a/scripts/pylint_extensions.py b/scripts/pylint_extensions.py
--- a/scripts/pylint_extensions.py
+++ b/scripts/pylint_extensions.py
@@ -903,6 +903,33 @@ def process_module(self, node):
             self.add_message('newline-at-eof', line=file_length)
 
 
+class SingleSpaceAfterYieldChecker(checkers.BaseChecker):
+  """Checks if only one space is used after a yield statement."""
+  __implements__ = interfaces.IRawChecker
+
+  name = 'single-space-after-yield'
+  priority = -1
+  msgs = {
+      'C0010': (
+          'Not using a single space after yield statement.',
+          'single-space-after-yield',
+          'Ensure a single space is used after yield statement.',
+      ),
+  }
+
+  def process_module(self, node):
+    """Process a module to ensure that yield keywords are followed by exactly
+    one space, so matching 'yield *' where * is not a whitespace character.
+
+      Args:
+          node: astroid.scoped_nodes.Function. Node to access module content.
+    """
+    file_content = read_from_node(node)
+    for (line_num, line) in enumerate(file_content):
+      source_line = line.lstrip()
+      if source_line.startswith('yield') and not re.search(r'^(yield) \S', source_line):
+        self.add_message('single-space-after-yield', line=line_num + 1)
+
 def register(linter):
     """Registers the checker with pylint.
 
@@ -917,3 +944,4 @@ def register(linter):
     linter.register_checker(FunctionArgsOrderChecker(linter))
     linter.register_checker(RestrictedImportChecker(linter))
     linter.register_checker(SingleCharAndNewlineAtEOFChecker(linter))
+    linter.register_checker(SingleSpaceAfterYieldChecker(linter))


Test patch:
diff --git a/scripts/pylint_extensions_test.py b/scripts/pylint_extensions_test.py
--- a/scripts/pylint_extensions_test.py
+++ b/scripts/pylint_extensions_test.py
@@ -883,3 +883,108 @@ def test_checks_single_char_and_newline_eof(self):
 
         with checker_test_object.assertNoMessages():
             temp_file.close()
+
+
+class SingleSpaceAfterYieldTests(unittest.TestCase):
+
+    def setUp(self):
+        super(SingleSpaceAfterYieldTests, self).setUp()
+        self.checker_test_object = testutils.CheckerTestCase()
+        self.checker_test_object.CHECKER_CLASS = (
+            pylint_extensions.SingleSpaceAfterYieldChecker)
+        self.checker_test_object.setup_method()
+
+    def test_well_formed_yield_statement_on_single_line(self):
+        node_well_formed_one_line_yield_file = astroid.scoped_nodes.Module(
+            name='test',
+            doc='Custom test')
+        temp_file = tempfile.NamedTemporaryFile()
+        filename = temp_file.name
+
+        with python_utils.open_file(filename, 'w') as tmp:
+            tmp.write(
+                u"""def helloworld():
+                    yield (5, 2)
+                """)
+        node_well_formed_one_line_yield_file.file = filename
+        node_well_formed_one_line_yield_file.path = filename
+
+        self.checker_test_object.checker.process_module(
+            node_well_formed_one_line_yield_file)
+
+        with self.checker_test_object.assertNoMessages():
+            temp_file.close()
+
+    def test_well_formed_yield_statement_on_multiple_lines(self):
+        node_well_formed_mult_lines_file = astroid.scoped_nodes.Module(
+            name='test',
+            doc='Custom test')
+        temp_file = tempfile.NamedTemporaryFile()
+        filename = temp_file.name
+
+        with python_utils.open_file(filename, 'w') as tmp:
+            tmp.write(
+                u"""def helloworld():
+                    yield (
+                        'This line was too long to be put on one line.')
+                """)
+        node_well_formed_mult_lines_file.file = filename
+        node_well_formed_mult_lines_file.path = filename
+
+        self.checker_test_object.checker.process_module(
+            node_well_formed_mult_lines_file)
+
+        with self.checker_test_object.assertNoMessages():
+            temp_file.close()
+
+    def test_too_many_spaces_after_yield_statement(self):
+        node_too_many_spaces_after_yield_file = astroid.scoped_nodes.Module(
+            name='test',
+            doc='Custom test')
+        temp_file = tempfile.NamedTemporaryFile()
+        filename = temp_file.name
+
+        with python_utils.open_file(filename, 'w') as tmp:
+            tmp.write(
+                u"""def helloworld():
+                    yield  (5, 2)
+                """)
+        node_too_many_spaces_after_yield_file.file = filename
+        node_too_many_spaces_after_yield_file.path = filename
+
+        self.checker_test_object.checker.process_module(
+            node_too_many_spaces_after_yield_file)
+
+        with self.checker_test_object.assertAddsMessages(
+            testutils.Message(
+                msg_id='single-space-after-yield',
+                line=2
+            ),
+        ):
+            temp_file.close()
+
+    def test_no_space_after_yield_statement(self):
+        node_no_spaces_after_yield_file = astroid.scoped_nodes.Module(
+            name='test',
+            doc='Custom test')
+        temp_file = tempfile.NamedTemporaryFile()
+        filename = temp_file.name
+
+        with python_utils.open_file(filename, 'w') as tmp:
+            tmp.write(
+                u"""def helloworld():
+                    yield(5, 2)
+                """)
+        node_no_spaces_after_yield_file.file = filename
+        node_no_spaces_after_yield_file.path = filename
+
+        self.checker_test_object.checker.process_module(
+            node_no_spaces_after_yield_file)
+
+        with self.checker_test_object.assertAddsMessages(
+            testutils.Message(
+                msg_id='single-space-after-yield',
+                line=2
+            ),
+        ):
+            temp_file.close()