1*4882a593Smuzhiyun# See utils/checkpackagelib/readme.txt before editing this file. 2*4882a593Smuzhiyun# Kconfig generates errors if someone introduces a typo like "boool" instead of 3*4882a593Smuzhiyun# "bool", so below check functions don't need to check for things already 4*4882a593Smuzhiyun# checked by running "make menuconfig". 5*4882a593Smuzhiyun 6*4882a593Smuzhiyunimport re 7*4882a593Smuzhiyun 8*4882a593Smuzhiyunfrom checkpackagelib.base import _CheckFunction 9*4882a593Smuzhiyunfrom checkpackagelib.lib import ConsecutiveEmptyLines # noqa: F401 10*4882a593Smuzhiyunfrom checkpackagelib.lib import EmptyLastLine # noqa: F401 11*4882a593Smuzhiyunfrom checkpackagelib.lib import NewlineAtEof # noqa: F401 12*4882a593Smuzhiyunfrom checkpackagelib.lib import TrailingSpace # noqa: F401 13*4882a593Smuzhiyun 14*4882a593Smuzhiyun 15*4882a593Smuzhiyundef _empty_or_comment(text): 16*4882a593Smuzhiyun line = text.strip() 17*4882a593Smuzhiyun # ignore empty lines and comment lines indented or not 18*4882a593Smuzhiyun return line == "" or line.startswith("#") 19*4882a593Smuzhiyun 20*4882a593Smuzhiyun 21*4882a593Smuzhiyundef _part_of_help_text(text): 22*4882a593Smuzhiyun return text.startswith("\t ") 23*4882a593Smuzhiyun 24*4882a593Smuzhiyun 25*4882a593Smuzhiyun# used in more than one check 26*4882a593Smuzhiyunentries_that_should_not_be_indented = [ 27*4882a593Smuzhiyun "choice", "comment", "config", "endchoice", "endif", "endmenu", "if", 28*4882a593Smuzhiyun "menu", "menuconfig", "source"] 29*4882a593Smuzhiyun 30*4882a593Smuzhiyun 31*4882a593Smuzhiyunclass AttributesOrder(_CheckFunction): 32*4882a593Smuzhiyun attributes_order_convention = { 33*4882a593Smuzhiyun "bool": 1, "prompt": 1, "string": 1, "default": 2, "depends": 3, 34*4882a593Smuzhiyun "select": 4, "help": 5} 35*4882a593Smuzhiyun 36*4882a593Smuzhiyun def before(self): 37*4882a593Smuzhiyun self.state = 0 38*4882a593Smuzhiyun 39*4882a593Smuzhiyun def check_line(self, lineno, text): 40*4882a593Smuzhiyun if _empty_or_comment(text) or _part_of_help_text(text): 41*4882a593Smuzhiyun return 42*4882a593Smuzhiyun 43*4882a593Smuzhiyun attribute = text.split()[0] 44*4882a593Smuzhiyun 45*4882a593Smuzhiyun if attribute in entries_that_should_not_be_indented: 46*4882a593Smuzhiyun self.state = 0 47*4882a593Smuzhiyun return 48*4882a593Smuzhiyun if attribute not in self.attributes_order_convention.keys(): 49*4882a593Smuzhiyun return 50*4882a593Smuzhiyun new_state = self.attributes_order_convention[attribute] 51*4882a593Smuzhiyun wrong_order = self.state > new_state 52*4882a593Smuzhiyun 53*4882a593Smuzhiyun # save to process next line 54*4882a593Smuzhiyun self.state = new_state 55*4882a593Smuzhiyun 56*4882a593Smuzhiyun if wrong_order: 57*4882a593Smuzhiyun return ["{}:{}: attributes order: type, default, depends on," 58*4882a593Smuzhiyun " select, help ({}#_config_files)" 59*4882a593Smuzhiyun .format(self.filename, lineno, self.url_to_manual), 60*4882a593Smuzhiyun text] 61*4882a593Smuzhiyun 62*4882a593Smuzhiyun 63*4882a593Smuzhiyunclass CommentsMenusPackagesOrder(_CheckFunction): 64*4882a593Smuzhiyun def before(self): 65*4882a593Smuzhiyun self.level = 0 66*4882a593Smuzhiyun self.menu_of_packages = ["The top level menu"] 67*4882a593Smuzhiyun self.new_package = "" 68*4882a593Smuzhiyun self.package = [""] 69*4882a593Smuzhiyun self.print_package_warning = [True] 70*4882a593Smuzhiyun self.state = "" 71*4882a593Smuzhiyun 72*4882a593Smuzhiyun def get_level(self): 73*4882a593Smuzhiyun return len(self.state.split('-')) - 1 74*4882a593Smuzhiyun 75*4882a593Smuzhiyun def initialize_package_level_elements(self, text): 76*4882a593Smuzhiyun try: 77*4882a593Smuzhiyun self.menu_of_packages[self.level] = text[:-1] 78*4882a593Smuzhiyun self.package[self.level] = "" 79*4882a593Smuzhiyun self.print_package_warning[self.level] = True 80*4882a593Smuzhiyun except IndexError: 81*4882a593Smuzhiyun self.menu_of_packages.append(text[:-1]) 82*4882a593Smuzhiyun self.package.append("") 83*4882a593Smuzhiyun self.print_package_warning.append(True) 84*4882a593Smuzhiyun 85*4882a593Smuzhiyun def initialize_level_elements(self, text): 86*4882a593Smuzhiyun self.level = self.get_level() 87*4882a593Smuzhiyun self.initialize_package_level_elements(text) 88*4882a593Smuzhiyun 89*4882a593Smuzhiyun def check_line(self, lineno, text): 90*4882a593Smuzhiyun # We only want to force sorting for the top-level menus 91*4882a593Smuzhiyun if self.filename not in ["fs/Config.in", 92*4882a593Smuzhiyun "package/Config.in", 93*4882a593Smuzhiyun "package/Config.in.host", 94*4882a593Smuzhiyun "package/kodi/Config.in"]: 95*4882a593Smuzhiyun return 96*4882a593Smuzhiyun 97*4882a593Smuzhiyun source_line = re.match(r'^\s*source ".*/([^/]*)/Config.in(.host)?"', text) 98*4882a593Smuzhiyun 99*4882a593Smuzhiyun if text.startswith("comment "): 100*4882a593Smuzhiyun if not self.state.endswith("-comment"): 101*4882a593Smuzhiyun self.state += "-comment" 102*4882a593Smuzhiyun 103*4882a593Smuzhiyun self.initialize_level_elements(text) 104*4882a593Smuzhiyun 105*4882a593Smuzhiyun elif text.startswith("if "): 106*4882a593Smuzhiyun self.state += "-if" 107*4882a593Smuzhiyun 108*4882a593Smuzhiyun self.initialize_level_elements(text) 109*4882a593Smuzhiyun 110*4882a593Smuzhiyun elif text.startswith("menu "): 111*4882a593Smuzhiyun if self.state.endswith("-comment"): 112*4882a593Smuzhiyun self.state = self.state[:-8] 113*4882a593Smuzhiyun 114*4882a593Smuzhiyun self.state += "-menu" 115*4882a593Smuzhiyun 116*4882a593Smuzhiyun self.initialize_level_elements(text) 117*4882a593Smuzhiyun 118*4882a593Smuzhiyun elif text.startswith("endif") or text.startswith("endmenu"): 119*4882a593Smuzhiyun if self.state.endswith("-comment"): 120*4882a593Smuzhiyun self.state = self.state[:-8] 121*4882a593Smuzhiyun 122*4882a593Smuzhiyun if text.startswith("endif"): 123*4882a593Smuzhiyun self.state = self.state[:-3] 124*4882a593Smuzhiyun 125*4882a593Smuzhiyun elif text.startswith("endmenu"): 126*4882a593Smuzhiyun self.state = self.state[:-5] 127*4882a593Smuzhiyun 128*4882a593Smuzhiyun self.level = self.get_level() 129*4882a593Smuzhiyun 130*4882a593Smuzhiyun elif source_line: 131*4882a593Smuzhiyun self.new_package = source_line.group(1) 132*4882a593Smuzhiyun 133*4882a593Smuzhiyun # We order _ before A, so replace it with . 134*4882a593Smuzhiyun new_package_ord = self.new_package.replace('_', '.') 135*4882a593Smuzhiyun 136*4882a593Smuzhiyun if self.package[self.level] != "" and \ 137*4882a593Smuzhiyun self.print_package_warning[self.level] and \ 138*4882a593Smuzhiyun new_package_ord < self.package[self.level]: 139*4882a593Smuzhiyun self.print_package_warning[self.level] = False 140*4882a593Smuzhiyun prefix = "{}:{}: ".format(self.filename, lineno) 141*4882a593Smuzhiyun spaces = " " * len(prefix) 142*4882a593Smuzhiyun return ["{prefix}Packages in: {menu},\n" 143*4882a593Smuzhiyun "{spaces}are not alphabetically ordered;\n" 144*4882a593Smuzhiyun "{spaces}correct order: '-', '_', digits, capitals, lowercase;\n" 145*4882a593Smuzhiyun "{spaces}first incorrect package: {package}" 146*4882a593Smuzhiyun .format(prefix=prefix, spaces=spaces, 147*4882a593Smuzhiyun menu=self.menu_of_packages[self.level], 148*4882a593Smuzhiyun package=self.new_package), 149*4882a593Smuzhiyun text] 150*4882a593Smuzhiyun 151*4882a593Smuzhiyun self.package[self.level] = new_package_ord 152*4882a593Smuzhiyun 153*4882a593Smuzhiyun 154*4882a593Smuzhiyunclass HelpText(_CheckFunction): 155*4882a593Smuzhiyun HELP_TEXT_FORMAT = re.compile(r"^\t .{,62}$") 156*4882a593Smuzhiyun URL_ONLY = re.compile(r"^(http|https|git)://\S*$") 157*4882a593Smuzhiyun 158*4882a593Smuzhiyun def before(self): 159*4882a593Smuzhiyun self.help_text = False 160*4882a593Smuzhiyun 161*4882a593Smuzhiyun def check_line(self, lineno, text): 162*4882a593Smuzhiyun if _empty_or_comment(text): 163*4882a593Smuzhiyun return 164*4882a593Smuzhiyun 165*4882a593Smuzhiyun entry = text.split()[0] 166*4882a593Smuzhiyun 167*4882a593Smuzhiyun if entry in entries_that_should_not_be_indented: 168*4882a593Smuzhiyun self.help_text = False 169*4882a593Smuzhiyun return 170*4882a593Smuzhiyun if text.strip() == "help": 171*4882a593Smuzhiyun self.help_text = True 172*4882a593Smuzhiyun return 173*4882a593Smuzhiyun 174*4882a593Smuzhiyun if not self.help_text: 175*4882a593Smuzhiyun return 176*4882a593Smuzhiyun 177*4882a593Smuzhiyun if self.HELP_TEXT_FORMAT.match(text.rstrip()): 178*4882a593Smuzhiyun return 179*4882a593Smuzhiyun if self.URL_ONLY.match(text.strip()): 180*4882a593Smuzhiyun return 181*4882a593Smuzhiyun return ["{}:{}: help text: <tab><2 spaces><62 chars>" 182*4882a593Smuzhiyun " ({}#writing-rules-config-in)" 183*4882a593Smuzhiyun .format(self.filename, lineno, self.url_to_manual), 184*4882a593Smuzhiyun text, 185*4882a593Smuzhiyun "\t " + "123456789 " * 6 + "12"] 186*4882a593Smuzhiyun 187*4882a593Smuzhiyun 188*4882a593Smuzhiyunclass Indent(_CheckFunction): 189*4882a593Smuzhiyun ENDS_WITH_BACKSLASH = re.compile(r"^[^#].*\\$") 190*4882a593Smuzhiyun entries_that_should_be_indented = [ 191*4882a593Smuzhiyun "bool", "default", "depends", "help", "prompt", "select", "string"] 192*4882a593Smuzhiyun 193*4882a593Smuzhiyun def before(self): 194*4882a593Smuzhiyun self.backslash = False 195*4882a593Smuzhiyun 196*4882a593Smuzhiyun def check_line(self, lineno, text): 197*4882a593Smuzhiyun if _empty_or_comment(text) or _part_of_help_text(text): 198*4882a593Smuzhiyun self.backslash = False 199*4882a593Smuzhiyun return 200*4882a593Smuzhiyun 201*4882a593Smuzhiyun entry = text.split()[0] 202*4882a593Smuzhiyun 203*4882a593Smuzhiyun last_line_ends_in_backslash = self.backslash 204*4882a593Smuzhiyun 205*4882a593Smuzhiyun # calculate for next line 206*4882a593Smuzhiyun if self.ENDS_WITH_BACKSLASH.search(text): 207*4882a593Smuzhiyun self.backslash = True 208*4882a593Smuzhiyun else: 209*4882a593Smuzhiyun self.backslash = False 210*4882a593Smuzhiyun 211*4882a593Smuzhiyun if last_line_ends_in_backslash: 212*4882a593Smuzhiyun if text.startswith("\t"): 213*4882a593Smuzhiyun return 214*4882a593Smuzhiyun return ["{}:{}: continuation line should be indented using tabs" 215*4882a593Smuzhiyun .format(self.filename, lineno), 216*4882a593Smuzhiyun text] 217*4882a593Smuzhiyun 218*4882a593Smuzhiyun if entry in self.entries_that_should_be_indented: 219*4882a593Smuzhiyun if not text.startswith("\t{}".format(entry)): 220*4882a593Smuzhiyun return ["{}:{}: should be indented with one tab" 221*4882a593Smuzhiyun " ({}#_config_files)" 222*4882a593Smuzhiyun .format(self.filename, lineno, self.url_to_manual), 223*4882a593Smuzhiyun text] 224*4882a593Smuzhiyun elif entry in entries_that_should_not_be_indented: 225*4882a593Smuzhiyun if not text.startswith(entry): 226*4882a593Smuzhiyun # four Config.in files have a special but legitimate indentation rule 227*4882a593Smuzhiyun if self.filename in ["package/Config.in", 228*4882a593Smuzhiyun "package/Config.in.host", 229*4882a593Smuzhiyun "package/kodi/Config.in", 230*4882a593Smuzhiyun "package/x11r7/Config.in"]: 231*4882a593Smuzhiyun return 232*4882a593Smuzhiyun return ["{}:{}: should not be indented" 233*4882a593Smuzhiyun .format(self.filename, lineno), 234*4882a593Smuzhiyun text] 235