Repository: oppia/oppia

Issue:
Lint check for CODEOWNERS to ensure that the most important rules are at the end.
<!--
  - Thanks for taking the time to propose a new feature for the Oppia project.
  - Before filing a new issue, please do a quick search to check that it hasn't
  - already been filed on the [issue tracker](https://github.com/oppia/oppia/issues)._
  -->
A lint check needs to be employed to ensure that the most important rules are at the end of the CODEOWNERS file.
Reference: #6438 
Lint check for CODEOWNERS to ensure that the most important rules are at the end.
<!--
  - Thanks for taking the time to propose a new feature for the Oppia project.
  - Before filing a new issue, please do a quick search to check that it hasn't
  - already been filed on the [issue tracker](https://github.com/oppia/oppia/issues)._
  -->
A lint check needs to be employed to ensure that the most important rules are at the end of the CODEOWNERS file.
Reference: #6438 


Discussion:

Resolving patch:
diff --git a/scripts/pre_commit_linter.py b/scripts/pre_commit_linter.py
--- a/scripts/pre_commit_linter.py
+++ b/scripts/pre_commit_linter.py
@@ -321,6 +321,25 @@
     'webpack.dev.config.ts',
     'webpack.prod.config.ts')
 
+CODEOWNER_FILEPATH = '.github/CODEOWNERS'
+
+# This list needs to be in sync with the important patterns in the CODEOWNERS
+# file.
+CODEOWNER_IMPORTANT_PATHS = [
+    '/core/controllers/acl_decorators*.py',
+    '/core/controllers/base*.py',
+    '/core/domain/dependency_registry*.py',
+    '/core/domain/html*.py',
+    '/core/domain/rights_manager*.py',
+    '/core/domain/role_services*.py',
+    '/core/domain/user*.py',
+    '/core/storage/',
+    '/export/',
+    '/manifest.json',
+    '/package*.json',
+    '/scripts/install_third_party.sh',
+    '/.github/']
+
 if not os.getcwd().endswith('oppia'):
     print ''
     print 'ERROR    Please run this script from the oppia root directory.'
@@ -2188,42 +2207,102 @@ def _check_bad_patterns(self):
 
         return summary_messages
 
+    def check_for_important_patterns_at_bottom_of_codeowners(
+            self, important_patterns):
+        """Checks that the most important patterns are at the bottom
+        of the CODEOWNERS file.
+
+        Arguments:
+            important_patterns: list(str). List of the important
+                patterns for CODEOWNERS file.
+
+        Returns:
+            bool. Whether the CODEOWNERS "important pattern" check fails.
+        """
+
+        failed = False
+
+        # Check that there are no duplicate elements in the lists.
+        important_patterns_set = set(important_patterns)
+        codeowner_important_paths_set = set(CODEOWNER_IMPORTANT_PATHS)
+        if len(important_patterns_set) != len(important_patterns):
+            print('%s --> Duplicate pattern(s) found in critical rules'
+                  ' section.' % CODEOWNER_FILEPATH)
+            failed = True
+        if len(codeowner_important_paths_set) != len(CODEOWNER_IMPORTANT_PATHS):
+            print('scripts/pre_commit_linter.py --> Duplicate pattern(s) found '
+                  'in CODEOWNER_IMPORTANT_PATHS list.')
+            failed = True
+
+        # Check missing rules by set difference operation.
+        critical_rule_section_minus_list_set = (
+            important_patterns_set.difference(codeowner_important_paths_set))
+        list_minus_critical_rule_section_set = (
+            codeowner_important_paths_set.difference(important_patterns_set))
+        for rule in critical_rule_section_minus_list_set:
+            print('%s --> Rule %s is not present in the '
+                  'CODEOWNER_IMPORTANT_PATHS list in '
+                  'scripts/pre_commit_linter.py. Please add this rule in the '
+                  'mentioned list or remove this rule from the \'Critical files'
+                  '\' section.' % (CODEOWNER_FILEPATH, rule))
+            failed = True
+        for rule in list_minus_critical_rule_section_set:
+            print('%s --> Rule \'%s\' is not present in the \'Critical files\' '
+                  'section. Please place it under the \'Critical files\' '
+                  'section since it is an important rule. Alternatively please '
+                  'remove it from the \'CODEOWNER_IMPORTANT_PATHS\' list in '
+                  'scripts/pre_commit_linter.py if it is no longer an '
+                  'important rule.' % (CODEOWNER_FILEPATH, rule))
+            failed = True
+
+        return failed
+
     def _check_codeowner_file(self):
         """Checks the CODEOWNERS file for any uncovered dirs/files and also
         checks that every pattern in the CODEOWNERS file matches at least one
         file/dir. Note that this checks the CODEOWNERS file according to the
         glob patterns supported by Python2.7 environment. For more information
         please refer https://docs.python.org/2/library/glob.html.
+        This function also ensures that the most important rules are at the
+        bottom of the CODEOWNERS file.
         """
         if self.verbose_mode_enabled:
             print 'Starting CODEOWNERS file check'
             print '----------------------------------------'
 
         with _redirect_stdout(_TARGET_STDOUT):
-            codeowner_filepath = '.github/CODEOWNERS'
             failed = False
             summary_messages = []
             # Checks whether every pattern in the CODEOWNERS file matches at
             # least one dir/file.
+            critical_file_section_found = False
+            important_rules_in_critical_section = []
             file_patterns = []
             dir_patterns = []
             for line_num, line in enumerate(FileCache.readlines(
-                    codeowner_filepath)):
+                    CODEOWNER_FILEPATH)):
                 stripped_line = line.strip()
+                if '# Critical files' in line:
+                    critical_file_section_found = True
                 if stripped_line and stripped_line[0] != '#':
                     if '@' not in line:
                         print ('%s --> Pattern on line %s doesn\'t have '
-                               'codeowner' % (codeowner_filepath, line_num + 1))
+                               'codeowner' % (CODEOWNER_FILEPATH, line_num + 1))
                         failed = True
                     else:
                         # Extract the file pattern from the line.
                         line_in_concern = line.split('@')[0].strip()
+                        # This is being populated for the important rules
+                        # check.
+                        if critical_file_section_found:
+                            important_rules_in_critical_section.append(
+                                line_in_concern)
                         # Checks if the path is the full path relative to the
                         # root oppia directory.
                         if not line_in_concern.startswith('/'):
                             print ('%s --> Pattern on line %s is invalid. Use '
                                    'full path relative to the root directory'
-                                   % (codeowner_filepath, line_num + 1))
+                                   % (CODEOWNER_FILEPATH, line_num + 1))
                             failed = True
 
                         # The double asterisks pattern is supported by the
@@ -2232,7 +2311,7 @@ def _check_codeowner_file(self):
                         if '**' in line_in_concern:
                             print ('%s --> Pattern on line %s is invalid. '
                                    '\'**\' wildcard not allowed' % (
-                                       codeowner_filepath, line_num + 1))
+                                       CODEOWNER_FILEPATH, line_num + 1))
                             failed = True
                         # Adjustments to the dir paths in CODEOWNERS syntax
                         # for glob-style patterns to match correctly.
@@ -2250,7 +2329,7 @@ def _check_codeowner_file(self):
                         if not glob.glob(line_in_concern):
                             print ('%s --> Pattern on line %s doesn\'t match '
                                    'any file or directory' % (
-                                       codeowner_filepath, line_num + 1))
+                                       CODEOWNER_FILEPATH, line_num + 1))
                             failed = True
                         # The following list is being populated with the
                         # paths in the CODEOWNERS file with the removal of the
@@ -2275,6 +2354,10 @@ def _check_codeowner_file(self):
                         print '%s is not covered under CODEOWNERS' % file_path
                         failed = True
 
+            failed = failed or (
+                self.check_for_important_patterns_at_bottom_of_codeowners(
+                    important_rules_in_critical_section))
+
             if failed:
                 summary_message = '%s   CODEOWNERS file check failed' % (
                     _MESSAGE_TYPE_FAILED)


Test patch: