11e8a9af9SDaniel Golle#!/usr/bin/env python3 21e8a9af9SDaniel Golle# SPDX-License-Identifier: GPL-2.0-only 31e8a9af9SDaniel Golle# 41e8a9af9SDaniel Golle# Check DTS coding style on YAML binding examples and on 51e8a9af9SDaniel Golle# .dts/.dtsi/.dtso source files. Enforces rules from 61e8a9af9SDaniel Golle# Documentation/devicetree/bindings/dts-coding-style.rst. 71e8a9af9SDaniel Golle# 81e8a9af9SDaniel Golle# Two modes: 91e8a9af9SDaniel Golle# --mode=relaxed (default) 101e8a9af9SDaniel Golle# Only rules that produce zero warnings on the current tree. 111e8a9af9SDaniel Golle# Suitable for dt_binding_check. 121e8a9af9SDaniel Golle# --mode=strict 131e8a9af9SDaniel Golle# All rules. Required for new submissions. 141e8a9af9SDaniel Golle# 151e8a9af9SDaniel Golle# Two input types (auto-detected by file extension): 161e8a9af9SDaniel Golle# *.yaml -- DT binding; check each example block 171e8a9af9SDaniel Golle# *.dts/*.dtsi/*.dtso -- DTS source; whole file is one block 181e8a9af9SDaniel Golle# 191e8a9af9SDaniel Golle# Rules are declared in a registry (see RULES below); each rule is 201e8a9af9SDaniel Golle# tagged with the lowest mode that runs it. Promoting a rule from 211e8a9af9SDaniel Golle# 'strict' to 'relaxed' is a one-line change. 221e8a9af9SDaniel Golle 231e8a9af9SDaniel Golleimport argparse 241e8a9af9SDaniel Golleimport re 251e8a9af9SDaniel Golleimport sys 261e8a9af9SDaniel Gollefrom enum import Enum, auto 271e8a9af9SDaniel Golle 281e8a9af9SDaniel Golleimport ruamel.yaml 291e8a9af9SDaniel Golle 301e8a9af9SDaniel Golle 311e8a9af9SDaniel Golle# --------------------------------------------------------------------------- 321e8a9af9SDaniel Golle# Line classification 331e8a9af9SDaniel Golle# --------------------------------------------------------------------------- 341e8a9af9SDaniel Golle 351e8a9af9SDaniel Golleclass LineType(Enum): 361e8a9af9SDaniel Golle BLANK = auto() 371e8a9af9SDaniel Golle COMMENT = auto() # // ... or /* ... */ on one line 381e8a9af9SDaniel Golle COMMENT_START = auto() # /* without closing */ 391e8a9af9SDaniel Golle COMMENT_BODY = auto() # inside a multi-line comment 401e8a9af9SDaniel Golle COMMENT_END = auto() # closing */ 411e8a9af9SDaniel Golle PREPROCESSOR = auto() # #include / #define / #ifdef / ... 421e8a9af9SDaniel Golle NODE_OPEN = auto() # something { (with optional label/name/addr) 431e8a9af9SDaniel Golle NODE_CLOSE = auto() # }; 441e8a9af9SDaniel Golle PROPERTY = auto() # name = value; or name; 451e8a9af9SDaniel Golle CONTINUATION = auto() # continuation of a multi-line property 461e8a9af9SDaniel Golle 471e8a9af9SDaniel Golle 481e8a9af9SDaniel Gollere_cpp_directive = re.compile( 491e8a9af9SDaniel Golle r'^#\s*(include|define|undef|ifdef|ifndef|if|else|elif|endif|' 501e8a9af9SDaniel Golle r'pragma|error|warning)\b') 511e8a9af9SDaniel Golle 5229a91b75SKrzysztof Kozlowskire_dtc_directive = re.compile( 5329a91b75SKrzysztof Kozlowski r'^/(dts-v1|include)/') 5429a91b75SKrzysztof Kozlowski 551e8a9af9SDaniel Golle# label: name@addr { -- label and addr optional; name can be "/" 561e8a9af9SDaniel Golle# Per the DT spec a node name may start with a digit (e.g. 1wire@...). 571e8a9af9SDaniel Golle# The address part is captured loosely (any non-space, non-brace run) so 581e8a9af9SDaniel Golle# malformed addresses (e.g. memory@0x1000) still reach 591e8a9af9SDaniel Golle# check_unit_address_format() instead of silently bypassing the check. 601e8a9af9SDaniel Gollere_node_header = re.compile( 611e8a9af9SDaniel Golle r'^(?:([a-zA-Z_][a-zA-Z0-9_]*):\s*)?' 621e8a9af9SDaniel Golle r'([a-zA-Z0-9][a-zA-Z0-9,._+-]*|/)' 631e8a9af9SDaniel Golle r'(?:@([^\s{]+))?' 641e8a9af9SDaniel Golle r'\s*\{$') 651e8a9af9SDaniel Golle 661e8a9af9SDaniel Gollere_ref_node = re.compile( 671e8a9af9SDaniel Golle r'^&([a-zA-Z_][a-zA-Z0-9_]*)\s*\{$') 681e8a9af9SDaniel Golle 691e8a9af9SDaniel Golle 701e8a9af9SDaniel Golledef is_preprocessor(stripped): 711e8a9af9SDaniel Golle """Tell C preprocessor directives apart from DTS '#'-prefixed props.""" 7229a91b75SKrzysztof Kozlowski if re_cpp_directive.match(stripped) is not None: 7329a91b75SKrzysztof Kozlowski return True 7429a91b75SKrzysztof Kozlowski if re_dtc_directive.match(stripped) is not None: 7529a91b75SKrzysztof Kozlowski return True 7629a91b75SKrzysztof Kozlowski return False 771e8a9af9SDaniel Golle 781e8a9af9SDaniel Golle 791e8a9af9SDaniel Golleclass DtsLine: 801e8a9af9SDaniel Golle __slots__ = ('lineno', 'raw', 'linetype', 'indent_str', 'stripped', 811e8a9af9SDaniel Golle 'prop_name', 'continuations', 821e8a9af9SDaniel Golle 'node_name', 'node_addr', 'label', 'ref_name', 'depth', 831e8a9af9SDaniel Golle 'closures') 841e8a9af9SDaniel Golle 85*d863ae62SKrzysztof Kozlowski def __init__(self, lineno, raw, linetype, depth, indent_str, stripped): 861e8a9af9SDaniel Golle self.lineno = lineno # 1-based within the block 871e8a9af9SDaniel Golle self.raw = raw 881e8a9af9SDaniel Golle self.linetype = linetype 891e8a9af9SDaniel Golle self.indent_str = indent_str # leading whitespace as-is 90*d863ae62SKrzysztof Kozlowski self.depth = depth 911e8a9af9SDaniel Golle self.stripped = stripped 921e8a9af9SDaniel Golle self.prop_name = None 931e8a9af9SDaniel Golle self.continuations = [] 941e8a9af9SDaniel Golle self.node_name = None 951e8a9af9SDaniel Golle self.node_addr = None 961e8a9af9SDaniel Golle self.label = None 971e8a9af9SDaniel Golle self.ref_name = None 981e8a9af9SDaniel Golle self.closures = 1 # count of '}' on a NODE_CLOSE line 991e8a9af9SDaniel Golle 1001e8a9af9SDaniel Golle 1011e8a9af9SDaniel Golledef _split_code(text): 1021e8a9af9SDaniel Golle """Return (code, opens_block) for a leading-stripped line: the 1031e8a9af9SDaniel Golle code portion with // and /* */ comments removed (string literals 1041e8a9af9SDaniel Golle kept verbatim), and whether a /* */ block comment is left open. 1051e8a9af9SDaniel Golle The code portion is right-stripped so the endswith() checks in 1061e8a9af9SDaniel Golle classify_lines see code only, not a trailing comment or blanks.""" 1071e8a9af9SDaniel Golle out = [] 1081e8a9af9SDaniel Golle i = 0 1091e8a9af9SDaniel Golle n = len(text) 1101e8a9af9SDaniel Golle while i < n: 1111e8a9af9SDaniel Golle c = text[i] 1121e8a9af9SDaniel Golle if c == '"': 1131e8a9af9SDaniel Golle j = i + 1 1141e8a9af9SDaniel Golle while j < n: 1151e8a9af9SDaniel Golle if text[j] == '\\': 1161e8a9af9SDaniel Golle j += 2 1171e8a9af9SDaniel Golle continue 1181e8a9af9SDaniel Golle if text[j] == '"': 1191e8a9af9SDaniel Golle j += 1 1201e8a9af9SDaniel Golle break 1211e8a9af9SDaniel Golle j += 1 1221e8a9af9SDaniel Golle out.append(text[i:j]) 1231e8a9af9SDaniel Golle i = j 1241e8a9af9SDaniel Golle continue 1251e8a9af9SDaniel Golle if c == '/' and i + 1 < n and text[i + 1] == '/': 1261e8a9af9SDaniel Golle break 1271e8a9af9SDaniel Golle if c == '/' and i + 1 < n and text[i + 1] == '*': 1281e8a9af9SDaniel Golle end = text.find('*/', i + 2) 1291e8a9af9SDaniel Golle if end < 0: 1301e8a9af9SDaniel Golle return (''.join(out).rstrip(), True) 1311e8a9af9SDaniel Golle i = end + 2 1321e8a9af9SDaniel Golle continue 1331e8a9af9SDaniel Golle out.append(c) 1341e8a9af9SDaniel Golle i += 1 1351e8a9af9SDaniel Golle return (''.join(out).rstrip(), False) 1361e8a9af9SDaniel Golle 1371e8a9af9SDaniel Golle 1381e8a9af9SDaniel Gollere_only_closures = re.compile(r'(?:\}\s*;?\s*)+$') 1391e8a9af9SDaniel Golle 1401e8a9af9SDaniel Golle 1411e8a9af9SDaniel Golledef classify_lines(text): 1421e8a9af9SDaniel Golle """Return a list of DtsLine. Tracks { } depth and groups 1431e8a9af9SDaniel Golle continuation lines onto their leading PROPERTY line.""" 1441e8a9af9SDaniel Golle out = [] 1451e8a9af9SDaniel Golle in_block_comment = False 1461e8a9af9SDaniel Golle in_cpp_macro = False 1471e8a9af9SDaniel Golle prev_complete = True 1481e8a9af9SDaniel Golle depth = 0 1491e8a9af9SDaniel Golle 1501e8a9af9SDaniel Golle # Split preserving the indent string verbatim 1511e8a9af9SDaniel Golle re_lead = re.compile(r'^([ \t]*)(.*)$') 1521e8a9af9SDaniel Golle 1531e8a9af9SDaniel Golle for i, raw in enumerate(text.split('\n'), start=1): 1541e8a9af9SDaniel Golle m = re_lead.match(raw) 1551e8a9af9SDaniel Golle indent_str = m.group(1) 1561e8a9af9SDaniel Golle stripped = m.group(2) 1571e8a9af9SDaniel Golle 1581e8a9af9SDaniel Golle # Continuation of a multi-line C preprocessor directive: the 1591e8a9af9SDaniel Golle # previous PREPROCESSOR line ended with a '\\' line splice, so 1601e8a9af9SDaniel Golle # this line is part of the same macro. Treat it as 1611e8a9af9SDaniel Golle # PREPROCESSOR until the splice chain ends (no trailing '\\' 1621e8a9af9SDaniel Golle # or a blank line). 1631e8a9af9SDaniel Golle if in_cpp_macro: 1641e8a9af9SDaniel Golle dl = DtsLine(i, raw, LineType.PREPROCESSOR, 165*d863ae62SKrzysztof Kozlowski depth, indent_str, stripped) 1661e8a9af9SDaniel Golle out.append(dl) 1671e8a9af9SDaniel Golle in_cpp_macro = (bool(stripped) and 1681e8a9af9SDaniel Golle stripped.rstrip().endswith('\\')) 1691e8a9af9SDaniel Golle continue 1701e8a9af9SDaniel Golle 1711e8a9af9SDaniel Golle if not stripped: 172*d863ae62SKrzysztof Kozlowski dl = DtsLine(i, raw, LineType.BLANK, depth, '', '') 1731e8a9af9SDaniel Golle out.append(dl) 1741e8a9af9SDaniel Golle continue 1751e8a9af9SDaniel Golle 1761e8a9af9SDaniel Golle if in_block_comment: 1771e8a9af9SDaniel Golle ltype = (LineType.COMMENT_END if '*/' in stripped 1781e8a9af9SDaniel Golle else LineType.COMMENT_BODY) 1791e8a9af9SDaniel Golle if ltype == LineType.COMMENT_END: 1801e8a9af9SDaniel Golle in_block_comment = False 181*d863ae62SKrzysztof Kozlowski dl = DtsLine(i, raw, ltype, depth, indent_str, stripped) 1821e8a9af9SDaniel Golle out.append(dl) 1831e8a9af9SDaniel Golle continue 1841e8a9af9SDaniel Golle 18529a91b75SKrzysztof Kozlowski if (stripped.startswith('#') or stripped.startswith('/')) and is_preprocessor(stripped): 186*d863ae62SKrzysztof Kozlowski dl = DtsLine(i, raw, LineType.PREPROCESSOR, depth, 1871e8a9af9SDaniel Golle indent_str, stripped) 1881e8a9af9SDaniel Golle out.append(dl) 1891e8a9af9SDaniel Golle prev_complete = True 1901e8a9af9SDaniel Golle in_cpp_macro = stripped.rstrip().endswith('\\') 1911e8a9af9SDaniel Golle continue 1921e8a9af9SDaniel Golle 1931e8a9af9SDaniel Golle # Strip comments first so all later structural checks see code 1941e8a9af9SDaniel Golle # only. An unclosed /* sets in_block_comment for the next line. 1951e8a9af9SDaniel Golle code, opens_block = _split_code(stripped) 1961e8a9af9SDaniel Golle if opens_block: 1971e8a9af9SDaniel Golle in_block_comment = True 1981e8a9af9SDaniel Golle 1991e8a9af9SDaniel Golle # Pure-comment line: nothing left after stripping. Classify as 2001e8a9af9SDaniel Golle # COMMENT_START (carries to next line) or COMMENT, and skip the 2011e8a9af9SDaniel Golle # structural classification entirely. 2021e8a9af9SDaniel Golle if not code: 2031e8a9af9SDaniel Golle ltype = LineType.COMMENT_START if opens_block else LineType.COMMENT 204*d863ae62SKrzysztof Kozlowski dl = DtsLine(i, raw, ltype, depth, indent_str, stripped) 2051e8a9af9SDaniel Golle out.append(dl) 2061e8a9af9SDaniel Golle continue 2071e8a9af9SDaniel Golle 2081e8a9af9SDaniel Golle if not prev_complete: 209*d863ae62SKrzysztof Kozlowski dl = DtsLine(i, raw, LineType.CONTINUATION, depth, indent_str, code) 2101e8a9af9SDaniel Golle out.append(dl) 2111e8a9af9SDaniel Golle prev_complete = (code.endswith(';') or 2121e8a9af9SDaniel Golle code.endswith('{') or 2131e8a9af9SDaniel Golle code.endswith('};')) 2141e8a9af9SDaniel Golle continue 2151e8a9af9SDaniel Golle 2161e8a9af9SDaniel Golle # NODE_CLOSE: the canonical form is "}" or "};" alone. A line 2171e8a9af9SDaniel Golle # that is nothing but closures (e.g. "}; };") is still treated 2181e8a9af9SDaniel Golle # as NODE_CLOSE for depth tracking, but the multi-closure case 2191e8a9af9SDaniel Golle # is flagged separately by check_node_close_alone via 2201e8a9af9SDaniel Golle # dl.closures. 2211e8a9af9SDaniel Golle if re_only_closures.match(code): 2221e8a9af9SDaniel Golle closures = code.count('}') 2231e8a9af9SDaniel Golle depth = max(depth - closures, 0) 224*d863ae62SKrzysztof Kozlowski dl = DtsLine(i, raw, LineType.NODE_CLOSE, depth, indent_str, code) 2251e8a9af9SDaniel Golle dl.closures = closures 2261e8a9af9SDaniel Golle out.append(dl) 2271e8a9af9SDaniel Golle prev_complete = True 2281e8a9af9SDaniel Golle continue 2291e8a9af9SDaniel Golle 2301e8a9af9SDaniel Golle if code.endswith('{'): 231*d863ae62SKrzysztof Kozlowski dl = DtsLine(i, raw, LineType.NODE_OPEN, depth, indent_str, code) 2321e8a9af9SDaniel Golle parse_node_header(dl) 2331e8a9af9SDaniel Golle out.append(dl) 2341e8a9af9SDaniel Golle depth += 1 2351e8a9af9SDaniel Golle prev_complete = True 2361e8a9af9SDaniel Golle continue 2371e8a9af9SDaniel Golle 2381e8a9af9SDaniel Golle # Property (or first line of a multi-line property). 239*d863ae62SKrzysztof Kozlowski dl = DtsLine(i, raw, LineType.PROPERTY, depth, indent_str, code) 2401e8a9af9SDaniel Golle parse_property_name(dl) 2411e8a9af9SDaniel Golle out.append(dl) 2421e8a9af9SDaniel Golle prev_complete = code.endswith(';') 2431e8a9af9SDaniel Golle 2441e8a9af9SDaniel Golle # Group continuation lines onto their leading PROPERTY. 2451e8a9af9SDaniel Golle last_prop = None 2461e8a9af9SDaniel Golle grouped = [] 2471e8a9af9SDaniel Golle for dl in out: 2481e8a9af9SDaniel Golle if dl.linetype == LineType.CONTINUATION and last_prop is not None: 2491e8a9af9SDaniel Golle last_prop.continuations.append(dl) 2501e8a9af9SDaniel Golle continue 2511e8a9af9SDaniel Golle if dl.linetype == LineType.PROPERTY: 2521e8a9af9SDaniel Golle last_prop = dl 2531e8a9af9SDaniel Golle elif dl.linetype != LineType.BLANK and \ 2541e8a9af9SDaniel Golle dl.linetype not in (LineType.COMMENT, LineType.COMMENT_BODY, 2551e8a9af9SDaniel Golle LineType.COMMENT_END, 2561e8a9af9SDaniel Golle LineType.COMMENT_START): 2571e8a9af9SDaniel Golle last_prop = None 2581e8a9af9SDaniel Golle grouped.append(dl) 2591e8a9af9SDaniel Golle return grouped 2601e8a9af9SDaniel Golle 2611e8a9af9SDaniel Golle 2621e8a9af9SDaniel Golledef parse_node_header(dl): 2631e8a9af9SDaniel Golle m = re_node_header.match(dl.stripped) 2641e8a9af9SDaniel Golle if m: 2651e8a9af9SDaniel Golle dl.label = m.group(1) 2661e8a9af9SDaniel Golle dl.node_name = m.group(2) 2671e8a9af9SDaniel Golle dl.node_addr = m.group(3) 2681e8a9af9SDaniel Golle return 2691e8a9af9SDaniel Golle m = re_ref_node.match(dl.stripped) 2701e8a9af9SDaniel Golle if m: 2711e8a9af9SDaniel Golle dl.ref_name = m.group(1) 2721e8a9af9SDaniel Golle 2731e8a9af9SDaniel Golle 2741e8a9af9SDaniel Golledef parse_property_name(dl): 2751e8a9af9SDaniel Golle m = re.match(r'^([a-zA-Z0-9#][a-zA-Z0-9,._+#-]*)\s*[=;]', dl.stripped) 2761e8a9af9SDaniel Golle if m: 2771e8a9af9SDaniel Golle dl.prop_name = m.group(1) 2781e8a9af9SDaniel Golle 2791e8a9af9SDaniel Golle 2801e8a9af9SDaniel Golledef collect_labels_and_refs(text): 2811e8a9af9SDaniel Golle """Return (defined_labels, referenced_labels) found anywhere outside 2821e8a9af9SDaniel Golle /* */ comments and string literals. Labels named fake_intc* (injected 2831e8a9af9SDaniel Golle by dt-extract-example) are skipped.""" 2841e8a9af9SDaniel Golle # Strip block comments first so labels inside them don't count 2851e8a9af9SDaniel Golle stripped = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL) 2861e8a9af9SDaniel Golle # Strip line comments 2871e8a9af9SDaniel Golle stripped = re.sub(r'//[^\n]*', '', stripped) 2881e8a9af9SDaniel Golle # Strip string literals so words inside quotes (e.g. "Error: foo") 2891e8a9af9SDaniel Golle # are not picked up as label definitions or &-references. 2901e8a9af9SDaniel Golle stripped = re.sub(r'"(?:[^"\\]|\\.)*"', '""', stripped) 2911e8a9af9SDaniel Golle defined = set() 2921e8a9af9SDaniel Golle referenced = set() 2931e8a9af9SDaniel Golle # A label precedes a node header; the next non-space token may start 2941e8a9af9SDaniel Golle # with a letter (foo, &ref), a digit (1wire), or '/' (root node). 2951e8a9af9SDaniel Golle for m in re.finditer( 2961e8a9af9SDaniel Golle r'(?:^|[\s{])([a-zA-Z_][a-zA-Z0-9_]*):\s*[a-zA-Z0-9/&]', 2971e8a9af9SDaniel Golle stripped): 2981e8a9af9SDaniel Golle name = m.group(1) 2991e8a9af9SDaniel Golle if not name.startswith('fake_intc'): 3001e8a9af9SDaniel Golle defined.add(name) 3011e8a9af9SDaniel Golle for m in re.finditer(r'&([a-zA-Z_][a-zA-Z0-9_]*)', stripped): 3021e8a9af9SDaniel Golle referenced.add(m.group(1)) 3031e8a9af9SDaniel Golle return defined, referenced 3041e8a9af9SDaniel Golle 3051e8a9af9SDaniel Golle 3061e8a9af9SDaniel Golle# --------------------------------------------------------------------------- 3071e8a9af9SDaniel Golle# Rule registry 3081e8a9af9SDaniel Golle# --------------------------------------------------------------------------- 3091e8a9af9SDaniel Golle 3101e8a9af9SDaniel Golleclass Ctx: 3111e8a9af9SDaniel Golle """Context passed to each rule check. Carries the parsed lines, 31246fb56e4SKrzysztof Kozlowski raw text, mode and kind.""" 3131e8a9af9SDaniel Golle 31446fb56e4SKrzysztof Kozlowski def __init__(self, lines, text, mode, kind): 3151e8a9af9SDaniel Golle self.lines = lines 3161e8a9af9SDaniel Golle self.text = text 3171e8a9af9SDaniel Golle self.mode = mode # 'relaxed' or 'strict' 31846fb56e4SKrzysztof Kozlowski if kind in DTS_FAMILY: 31946fb56e4SKrzysztof Kozlowski self.file_type = 'dts' 32046fb56e4SKrzysztof Kozlowski else: 32146fb56e4SKrzysztof Kozlowski self.file_type = 'yaml' 3221e8a9af9SDaniel Golle 3231e8a9af9SDaniel Golle 3241e8a9af9SDaniel Golleclass Rule: 3251e8a9af9SDaniel Golle __slots__ = ('name', 'mode', 'description', 'check', 'applies_to') 3261e8a9af9SDaniel Golle 3271e8a9af9SDaniel Golle def __init__(self, name, mode, description, check, 3281e8a9af9SDaniel Golle applies_to=('yaml', 'dts', 'dtsi', 'dtso')): 3291e8a9af9SDaniel Golle self.name = name 3301e8a9af9SDaniel Golle self.mode = mode # 'relaxed' or 'strict' 3311e8a9af9SDaniel Golle self.description = description 3321e8a9af9SDaniel Golle self.check = check 3331e8a9af9SDaniel Golle self.applies_to = applies_to # input types this rule covers 3341e8a9af9SDaniel Golle 3351e8a9af9SDaniel Golle 3361e8a9af9SDaniel Golle# --- individual rule check functions -------------------------------------- 3371e8a9af9SDaniel Golle 3381e8a9af9SDaniel Golledef check_trailing_whitespace(ctx): 3391e8a9af9SDaniel Golle for dl in ctx.lines: 3401e8a9af9SDaniel Golle if dl.raw != dl.raw.rstrip(): 3411e8a9af9SDaniel Golle yield (dl.lineno, 'trailing whitespace') 3421e8a9af9SDaniel Golle 3431e8a9af9SDaniel Golle 34449b5cf46SKrzysztof Kozlowskidef check_tab_in_yaml_example(ctx): 3451e8a9af9SDaniel Golle """Reject literal tabs in DTS lines when input is YAML. 3461e8a9af9SDaniel Golle 3471e8a9af9SDaniel Golle For YAML examples, indent and content must use spaces. Tabs inside 3481e8a9af9SDaniel Golle a #define value are tolerated (those are CPP macros, not DTS). 3491e8a9af9SDaniel Golle For .dts files, this rule does not apply -- tabs are required. 3501e8a9af9SDaniel Golle """ 35146fb56e4SKrzysztof Kozlowski if ctx.file_type != 'yaml': 3521e8a9af9SDaniel Golle return 3531e8a9af9SDaniel Golle for dl in ctx.lines: 3541e8a9af9SDaniel Golle if dl.linetype == LineType.PREPROCESSOR: 3551e8a9af9SDaniel Golle continue 3561e8a9af9SDaniel Golle if dl.linetype == LineType.BLANK: 3571e8a9af9SDaniel Golle continue 3581e8a9af9SDaniel Golle if '\t' in dl.raw: 3591e8a9af9SDaniel Golle yield (dl.lineno, 'tab character not allowed in DTS example') 3601e8a9af9SDaniel Golle 3611e8a9af9SDaniel Golle 3621e8a9af9SDaniel Golledef check_mixed_indent_chars(ctx): 36332541ce0SKrzysztof Kozlowski """Indent must be all-tabs, except for aligning indentation (comments 36432541ce0SKrzysztof Kozlowski or continued lines).""" 3651e8a9af9SDaniel Golle for dl in ctx.lines: 3661e8a9af9SDaniel Golle if not dl.indent_str: 3671e8a9af9SDaniel Golle continue 3681e8a9af9SDaniel Golle if dl.linetype == LineType.PREPROCESSOR: 3691e8a9af9SDaniel Golle continue 37032541ce0SKrzysztof Kozlowski if re.search(r' \t', dl.indent_str): 3711e8a9af9SDaniel Golle yield (dl.lineno, 'mixed tabs and spaces in indent') 37232541ce0SKrzysztof Kozlowski if dl.indent_str.count(' ') > 7: 37332541ce0SKrzysztof Kozlowski yield (dl.lineno, 'too many space characters in indent (more than 7)') 37432541ce0SKrzysztof Kozlowski for cont in dl.continuations: 37532541ce0SKrzysztof Kozlowski if not cont.indent_str: 37632541ce0SKrzysztof Kozlowski continue 37732541ce0SKrzysztof Kozlowski if cont.linetype == LineType.PREPROCESSOR: 37832541ce0SKrzysztof Kozlowski continue 37932541ce0SKrzysztof Kozlowski if re.search(r' \t', cont.indent_str): 38032541ce0SKrzysztof Kozlowski yield (cont.lineno, 'mixed tabs and spaces in indent') 3811e8a9af9SDaniel Golle 3821e8a9af9SDaniel Golle 3831e8a9af9SDaniel Golledef detect_indent_unit(ctx): 3841e8a9af9SDaniel Golle """Find the indent unit used at depth 1 in this block. 3851e8a9af9SDaniel Golle 38642879c68SKrzysztof Kozlowski Returns tuple of string (one of: ' ' (2 spaces), ' ' (4 spaces), 38742879c68SKrzysztof Kozlowski '\\t' (tab), or None if depth-1 is empty or ambiguous) and line number when 38842879c68SKrzysztof Kozlowski detection was made).""" 3891e8a9af9SDaniel Golle for dl in ctx.lines: 3901e8a9af9SDaniel Golle if dl.depth != 1: 3911e8a9af9SDaniel Golle continue 3921e8a9af9SDaniel Golle if dl.linetype in (LineType.BLANK, LineType.PREPROCESSOR): 3931e8a9af9SDaniel Golle continue 3941e8a9af9SDaniel Golle if dl.linetype in (LineType.COMMENT_BODY, LineType.COMMENT_END): 3951e8a9af9SDaniel Golle continue 3961e8a9af9SDaniel Golle if not dl.indent_str: 3971e8a9af9SDaniel Golle continue 3981e8a9af9SDaniel Golle if dl.indent_str == '\t': 39942879c68SKrzysztof Kozlowski return ('\t', dl.lineno) 4001e8a9af9SDaniel Golle if dl.indent_str == ' ': 40142879c68SKrzysztof Kozlowski return (' ', dl.lineno) 4021e8a9af9SDaniel Golle if dl.indent_str == ' ': 40342879c68SKrzysztof Kozlowski return (' ', dl.lineno) 4041e8a9af9SDaniel Golle # Anything else at depth 1 is non-canonical; flag elsewhere. 40542879c68SKrzysztof Kozlowski return (dl.indent_str, dl.lineno) 40642879c68SKrzysztof Kozlowski return (None, None) 4071e8a9af9SDaniel Golle 4081e8a9af9SDaniel Golle 4091e8a9af9SDaniel Golledef check_indent_unit_relaxed(ctx): 4101e8a9af9SDaniel Golle """YAML examples: 2 or 4 spaces. Never tabs or other widths.""" 41142879c68SKrzysztof Kozlowski (unit, lineno) = detect_indent_unit(ctx) 4121e8a9af9SDaniel Golle if unit is None: 4131e8a9af9SDaniel Golle return 4141e8a9af9SDaniel Golle if unit not in (' ', ' '): 41542879c68SKrzysztof Kozlowski yield (lineno, 'indent unit must be 2 or 4 spaces, got %r' % unit) 4161e8a9af9SDaniel Golle 4171e8a9af9SDaniel Golle 4181e8a9af9SDaniel Golledef check_indent_unit_dts(ctx): 4191e8a9af9SDaniel Golle """DTS files: 1 tab per level. Always required.""" 42042879c68SKrzysztof Kozlowski (unit, lineno) = detect_indent_unit(ctx) 4211e8a9af9SDaniel Golle if unit is None: 4221e8a9af9SDaniel Golle return 4231e8a9af9SDaniel Golle if unit != '\t': 42442879c68SKrzysztof Kozlowski yield (lineno, 'indent unit must be 1 tab in DTS, got %r' % unit) 4251e8a9af9SDaniel Golle 4261e8a9af9SDaniel Golle 4271e8a9af9SDaniel Golledef check_indent_unit_strict(ctx): 4281e8a9af9SDaniel Golle """YAML: must be exactly 4 spaces. DTS: 1 tab (same as relaxed).""" 42942879c68SKrzysztof Kozlowski (unit, lineno) = detect_indent_unit(ctx) 4301e8a9af9SDaniel Golle if unit is None: 4311e8a9af9SDaniel Golle return 43246fb56e4SKrzysztof Kozlowski if ctx.file_type == 'yaml': 4331e8a9af9SDaniel Golle if unit != ' ': 43442879c68SKrzysztof Kozlowski yield (lineno, 'indent unit must be 4 spaces in strict mode, ' 4351e8a9af9SDaniel Golle 'got %r' % unit) 4361e8a9af9SDaniel Golle 4371e8a9af9SDaniel Golle 4381e8a9af9SDaniel Golledef check_indent_consistent(ctx): 4391e8a9af9SDaniel Golle """All indented lines must be a multiple of the detected unit.""" 44042879c68SKrzysztof Kozlowski (unit, lineno) = detect_indent_unit(ctx) 4411e8a9af9SDaniel Golle if unit is None: 4421e8a9af9SDaniel Golle return 44346fb56e4SKrzysztof Kozlowski if ctx.file_type == 'yaml': 4441e8a9af9SDaniel Golle if unit not in (' ', ' '): 4451e8a9af9SDaniel Golle return # let check_indent_unit_* report this 4461e8a9af9SDaniel Golle else: 4471e8a9af9SDaniel Golle if unit != '\t': 4481e8a9af9SDaniel Golle return 4491e8a9af9SDaniel Golle 4501e8a9af9SDaniel Golle for dl in ctx.lines: 4511e8a9af9SDaniel Golle if dl.linetype in (LineType.BLANK, LineType.PREPROCESSOR): 4521e8a9af9SDaniel Golle continue 4531e8a9af9SDaniel Golle if dl.linetype == LineType.CONTINUATION: 4541e8a9af9SDaniel Golle continue # continuations align to <, not to indent unit 4551e8a9af9SDaniel Golle if dl.linetype in (LineType.COMMENT_BODY, LineType.COMMENT_END): 4561e8a9af9SDaniel Golle continue 4571e8a9af9SDaniel Golle if not dl.indent_str: 4581e8a9af9SDaniel Golle continue 4591e8a9af9SDaniel Golle # The indent must be 'unit' repeated dl.depth times, exactly. 4601e8a9af9SDaniel Golle # NODE_CLOSE lines have depth equal to the post-decrement value, 4611e8a9af9SDaniel Golle # which matches the indent expected. 4621e8a9af9SDaniel Golle expected = unit * dl.depth 4631e8a9af9SDaniel Golle if dl.indent_str != expected: 4641e8a9af9SDaniel Golle yield (dl.lineno, 4651e8a9af9SDaniel Golle 'indent mismatch (expected depth %d * %r)' % 4661e8a9af9SDaniel Golle (dl.depth, unit)) 4671e8a9af9SDaniel Golle 4681e8a9af9SDaniel Golle 4691e8a9af9SDaniel Golledef check_blank_lines(ctx): 4701e8a9af9SDaniel Golle """No two consecutive blank lines, no leading/trailing blank lines 4711e8a9af9SDaniel Golle in any node body.""" 4721e8a9af9SDaniel Golle lines = ctx.lines 4731e8a9af9SDaniel Golle # Consecutive blanks 4741e8a9af9SDaniel Golle for i in range(1, len(lines)): 4751e8a9af9SDaniel Golle if lines[i].linetype == LineType.BLANK and \ 4761e8a9af9SDaniel Golle lines[i - 1].linetype == LineType.BLANK: 4771e8a9af9SDaniel Golle yield (lines[i].lineno, 'consecutive blank lines') 4781e8a9af9SDaniel Golle # Blank right after { or right before } 4791e8a9af9SDaniel Golle for i, dl in enumerate(lines): 4801e8a9af9SDaniel Golle if dl.linetype != LineType.BLANK: 4811e8a9af9SDaniel Golle continue 4821e8a9af9SDaniel Golle prev = lines[i - 1] if i > 0 else None 4831e8a9af9SDaniel Golle nxt = lines[i + 1] if i + 1 < len(lines) else None 4841e8a9af9SDaniel Golle if prev is not None and prev.linetype == LineType.NODE_OPEN: 4851e8a9af9SDaniel Golle yield (dl.lineno, 'blank line at start of node body') 4861e8a9af9SDaniel Golle if nxt is not None and nxt.linetype == LineType.NODE_CLOSE: 4871e8a9af9SDaniel Golle yield (dl.lineno, 'blank line at end of node body') 4881e8a9af9SDaniel Golle 4891e8a9af9SDaniel Golle 4901e8a9af9SDaniel Golledef _walk_bodies(lines): 4911e8a9af9SDaniel Golle """Yield lists of immediate-child NODE_OPEN lines for each node body 4921e8a9af9SDaniel Golle in the input. Skips ref-nodes (&label) since those don't have an 4931e8a9af9SDaniel Golle intrinsic ordering.""" 4941e8a9af9SDaniel Golle body_stack = [[]] 4951e8a9af9SDaniel Golle for dl in lines: 4961e8a9af9SDaniel Golle if dl.linetype == LineType.NODE_OPEN: 4971e8a9af9SDaniel Golle body_stack[-1].append(dl) 4981e8a9af9SDaniel Golle body_stack.append([]) 4991e8a9af9SDaniel Golle continue 5001e8a9af9SDaniel Golle if dl.linetype == LineType.NODE_CLOSE: 5011e8a9af9SDaniel Golle if len(body_stack) <= 1: 5021e8a9af9SDaniel Golle # Unbalanced; ignore to avoid crashing on malformed input 5031e8a9af9SDaniel Golle continue 5041e8a9af9SDaniel Golle yield body_stack.pop() 5051e8a9af9SDaniel Golle continue 5061e8a9af9SDaniel Golle while body_stack: 5071e8a9af9SDaniel Golle yield body_stack.pop() 5081e8a9af9SDaniel Golle 5091e8a9af9SDaniel Golle 5101e8a9af9SDaniel Golledef _natural_sort_key(s): 5111e8a9af9SDaniel Golle """Split a string into a tuple of (kind, value) pairs that compares 5121e8a9af9SDaniel Golle numeric runs as ints, so 'foo10' sorts after 'foo2'.""" 5131e8a9af9SDaniel Golle parts = [] 5141e8a9af9SDaniel Golle for part in re.split(r'(\d+)', s): 5151e8a9af9SDaniel Golle if part.isdigit(): 5161e8a9af9SDaniel Golle parts.append((0, int(part))) 5171e8a9af9SDaniel Golle else: 5181e8a9af9SDaniel Golle parts.append((1, part)) 5191e8a9af9SDaniel Golle return tuple(parts) 5201e8a9af9SDaniel Golle 5211e8a9af9SDaniel Golle 5221e8a9af9SDaniel Golledef check_child_address_order(ctx): 5231e8a9af9SDaniel Golle """Addressed siblings (foo@N) must appear in ascending address 5241e8a9af9SDaniel Golle order within their parent node body.""" 5251e8a9af9SDaniel Golle for children in _walk_bodies(ctx.lines): 5261e8a9af9SDaniel Golle addressed = [] 5271e8a9af9SDaniel Golle for c in children: 5281e8a9af9SDaniel Golle if c.node_addr is None: 5291e8a9af9SDaniel Golle continue 5301e8a9af9SDaniel Golle try: 5311e8a9af9SDaniel Golle parts = tuple(int(p, 16) for p in c.node_addr.split(',')) 5321e8a9af9SDaniel Golle except ValueError: 5331e8a9af9SDaniel Golle continue 5341e8a9af9SDaniel Golle addressed.append((parts, c)) 5351e8a9af9SDaniel Golle for i in range(1, len(addressed)): 5361e8a9af9SDaniel Golle if addressed[i][0] < addressed[i - 1][0]: 5371e8a9af9SDaniel Golle dl = addressed[i][1] 5381e8a9af9SDaniel Golle yield (dl.lineno, 5391e8a9af9SDaniel Golle 'child node @%s out of address order' % 5401e8a9af9SDaniel Golle dl.node_addr) 5411e8a9af9SDaniel Golle 5421e8a9af9SDaniel Golle 5431e8a9af9SDaniel Golledef check_child_name_order(ctx): 5441e8a9af9SDaniel Golle """Unaddressed siblings must appear in natural-sort order by node 5451e8a9af9SDaniel Golle name within their parent node body. Addressed children are scoped 5461e8a9af9SDaniel Golle by check_child_address_order; reference nodes (&label { ... }) and 5471e8a9af9SDaniel Golle the root node are skipped.""" 5481e8a9af9SDaniel Golle for children in _walk_bodies(ctx.lines): 5491e8a9af9SDaniel Golle unaddressed = [] 5501e8a9af9SDaniel Golle for c in children: 5511e8a9af9SDaniel Golle if c.node_addr is not None: 5521e8a9af9SDaniel Golle continue 5531e8a9af9SDaniel Golle if c.node_name in (None, '/'): 5541e8a9af9SDaniel Golle continue 5551e8a9af9SDaniel Golle if c.ref_name is not None: 5561e8a9af9SDaniel Golle continue 5571e8a9af9SDaniel Golle unaddressed.append((_natural_sort_key(c.node_name), c)) 5581e8a9af9SDaniel Golle for i in range(1, len(unaddressed)): 5591e8a9af9SDaniel Golle if unaddressed[i][0] < unaddressed[i - 1][0]: 5601e8a9af9SDaniel Golle dl = unaddressed[i][1] 5611e8a9af9SDaniel Golle yield (dl.lineno, 5621e8a9af9SDaniel Golle 'child node %r out of name order' % dl.node_name) 5631e8a9af9SDaniel Golle 5641e8a9af9SDaniel Golle 5651e8a9af9SDaniel Golledef _property_bucket(name): 5661e8a9af9SDaniel Golle """Return the canonical bucket index for a property: 567597233e2SKrzysztof Kozlowski 0 device_type 568597233e2SKrzysztof Kozlowski 1 compatible 569597233e2SKrzysztof Kozlowski 2 reg / reg-names 570597233e2SKrzysztof Kozlowski 3 ranges 571597233e2SKrzysztof Kozlowski 4 standard properties (no vendor comma in #-stripped name) 572597233e2SKrzysztof Kozlowski 5 vendor-specific properties 573597233e2SKrzysztof Kozlowski 6 status 574597233e2SKrzysztof Kozlowski Plus a sub-key inside the bucket for fixed slots (device_type, compatible, 575597233e2SKrzysztof Kozlowski reg, reg-names, ranges, status). 'standard' and 'vendor' return None for 5761e8a9af9SDaniel Golle the sub-key, signalling that the within-bucket key is computed by 5771e8a9af9SDaniel Golle the pairing rules.""" 5781e8a9af9SDaniel Golle stripped = name.lstrip('#') 579597233e2SKrzysztof Kozlowski if name == 'device_type': 5801e8a9af9SDaniel Golle return (0, 0) 581597233e2SKrzysztof Kozlowski if name == 'compatible': 5821e8a9af9SDaniel Golle return (1, 0) 583597233e2SKrzysztof Kozlowski if name == 'reg': 5841e8a9af9SDaniel Golle return (2, 0) 585597233e2SKrzysztof Kozlowski if name == 'reg-names': 586597233e2SKrzysztof Kozlowski return (2, 1) 587597233e2SKrzysztof Kozlowski if name == 'ranges': 588597233e2SKrzysztof Kozlowski return (3, 0) 5891e8a9af9SDaniel Golle if name == 'status': 590597233e2SKrzysztof Kozlowski return (6, 0) 591597233e2SKrzysztof Kozlowski return (5 if ',' in stripped else 4, None) 5921e8a9af9SDaniel Golle 5931e8a9af9SDaniel Golle 5941e8a9af9SDaniel Golle# Declarative pairing rules: each is a callable 5951e8a9af9SDaniel Golle# (name, all_names) -> anchor_name_or_None 5961e8a9af9SDaniel Golle# If a rule returns an anchor, the property sorts immediately after the 5971e8a9af9SDaniel Golle# anchor. Rules are tried in order; the first match wins. If none 5981e8a9af9SDaniel Golle# matches, the within-bucket key falls back to natural sort by the 5991e8a9af9SDaniel Golle# #-stripped name. 6001e8a9af9SDaniel Golle 6011e8a9af9SDaniel Golledef _pair_pinctrl_names(name, all_names): 6021e8a9af9SDaniel Golle """pinctrl-names follows the highest pinctrl-N in the same node.""" 6031e8a9af9SDaniel Golle if name != 'pinctrl-names': 6041e8a9af9SDaniel Golle return None 6051e8a9af9SDaniel Golle cands = [n for n in all_names if re.match(r'^pinctrl-\d+$', n)] 6061e8a9af9SDaniel Golle if not cands: 6071e8a9af9SDaniel Golle return None 6081e8a9af9SDaniel Golle return max(cands, key=_natural_sort_key) 6091e8a9af9SDaniel Golle 6101e8a9af9SDaniel Golle 6111e8a9af9SDaniel Golledef _pair_x_names(name, all_names): 6121e8a9af9SDaniel Golle """Generic <x>-names follows its owning property. The owner is 6131e8a9af9SDaniel Golle usually plural (clocks/clock-names, dmas/dma-names, 6141e8a9af9SDaniel Golle resets/reset-names) but occasionally singular (reg/reg-names is 6151e8a9af9SDaniel Golle handled by the fixed slot above; this rule catches anything else).""" 6161e8a9af9SDaniel Golle if not name.endswith('-names'): 6171e8a9af9SDaniel Golle return None 6181e8a9af9SDaniel Golle base = name[:-len('-names')] 6191e8a9af9SDaniel Golle # Try plural and singular forms. 6201e8a9af9SDaniel Golle if (base + 's') in all_names: 6211e8a9af9SDaniel Golle return base + 's' 6221e8a9af9SDaniel Golle if base in all_names: 6231e8a9af9SDaniel Golle return base 6241e8a9af9SDaniel Golle return None 6251e8a9af9SDaniel Golle 6261e8a9af9SDaniel Golle 6271e8a9af9SDaniel GollePAIRING_RULES = (_pair_pinctrl_names, _pair_x_names) 6281e8a9af9SDaniel Golle 6291e8a9af9SDaniel Golle 6301e8a9af9SDaniel Golledef _property_sort_key(name, all_names): 6311e8a9af9SDaniel Golle """Sort key for a property among its node-body siblings. 6321e8a9af9SDaniel Golle 6331e8a9af9SDaniel Golle Format: (bucket, within_key, tiebreak). 'within_key' for 6341e8a9af9SDaniel Golle standard/vendor buckets follows pairing rules: a property paired 6351e8a9af9SDaniel Golle with anchor X sorts as if it were X with a higher tiebreak.""" 6361e8a9af9SDaniel Golle bucket, fixed_sub = _property_bucket(name) 6371e8a9af9SDaniel Golle if fixed_sub is not None: 6381e8a9af9SDaniel Golle return (bucket, (), fixed_sub) 6391e8a9af9SDaniel Golle 6401e8a9af9SDaniel Golle for rule in PAIRING_RULES: 6411e8a9af9SDaniel Golle anchor = rule(name, all_names) 6421e8a9af9SDaniel Golle if anchor is not None: 6431e8a9af9SDaniel Golle return (bucket, _natural_sort_key(anchor.lstrip('#')), 1) 6441e8a9af9SDaniel Golle 6451e8a9af9SDaniel Golle return (bucket, _natural_sort_key(name.lstrip('#')), 0) 6461e8a9af9SDaniel Golle 6471e8a9af9SDaniel Golle 6481e8a9af9SDaniel Golledef check_property_order(ctx): 6491e8a9af9SDaniel Golle """Properties within a node body must appear in canonical order: 6501e8a9af9SDaniel Golle compatible, reg(/reg-names), ranges, then the standard group, then 6511e8a9af9SDaniel Golle the vendor-specific group, then status. Inside the standard and 6521e8a9af9SDaniel Golle vendor groups, pairing rules apply (e.g. <x>-names follows <x>); 6531e8a9af9SDaniel Golle everything else falls back to natural sort by the #-stripped name.""" 6541e8a9af9SDaniel Golle lines = ctx.lines 6551e8a9af9SDaniel Golle for i, dl in enumerate(lines): 6561e8a9af9SDaniel Golle if dl.linetype != LineType.NODE_OPEN: 6571e8a9af9SDaniel Golle continue 6581e8a9af9SDaniel Golle body_depth = dl.depth + 1 6591e8a9af9SDaniel Golle props = [] 6601e8a9af9SDaniel Golle for j in range(i + 1, len(lines)): 6611e8a9af9SDaniel Golle d = lines[j] 6621e8a9af9SDaniel Golle if d.linetype == LineType.NODE_CLOSE and \ 6631e8a9af9SDaniel Golle d.depth == body_depth - 1: 6641e8a9af9SDaniel Golle break 6651e8a9af9SDaniel Golle if d.linetype == LineType.PROPERTY and d.depth == body_depth \ 6661e8a9af9SDaniel Golle and d.prop_name is not None: 6671e8a9af9SDaniel Golle props.append(d) 6681e8a9af9SDaniel Golle if len(props) < 2: 6691e8a9af9SDaniel Golle continue 6701e8a9af9SDaniel Golle all_names = [p.prop_name for p in props] 6711e8a9af9SDaniel Golle keyed = [(p, _property_sort_key(p.prop_name, all_names)) 6721e8a9af9SDaniel Golle for p in props] 6731e8a9af9SDaniel Golle for k in range(1, len(keyed)): 6741e8a9af9SDaniel Golle if keyed[k][1] < keyed[k - 1][1]: 6751e8a9af9SDaniel Golle p = keyed[k][0] 6761e8a9af9SDaniel Golle prev = keyed[k - 1][0] 6771e8a9af9SDaniel Golle yield (p.lineno, 6781e8a9af9SDaniel Golle 'property %r out of canonical order ' 6791e8a9af9SDaniel Golle '(should sort before %r)' % 6801e8a9af9SDaniel Golle (p.prop_name, prev.prop_name)) 6811e8a9af9SDaniel Golle 6821e8a9af9SDaniel Golle 6831e8a9af9SDaniel Golledef _strip_strings_and_comments(text): 6841e8a9af9SDaniel Golle """Remove string literals and /* */ + // comments from a single 6851e8a9af9SDaniel Golle line, replacing them with empty strings. Used so syntactic checks 6861e8a9af9SDaniel Golle (whitespace, hex case, etc.) don't false-positive on contents of 6871e8a9af9SDaniel Golle quoted strings or comments. An unclosed /* on the line is treated 6881e8a9af9SDaniel Golle as a comment running to end of line.""" 6891e8a9af9SDaniel Golle text = re.sub(r'"(?:[^"\\]|\\.)*"', '""', text) 6901e8a9af9SDaniel Golle text = re.sub(r'/\*.*?\*/', '', text) 6911e8a9af9SDaniel Golle text = re.sub(r'/\*.*$', '', text) 6921e8a9af9SDaniel Golle text = re.sub(r'//.*$', '', text) 6931e8a9af9SDaniel Golle return text 6941e8a9af9SDaniel Golle 6951e8a9af9SDaniel Golle 6961e8a9af9SDaniel Golledef check_required_blank_lines(ctx): 6971e8a9af9SDaniel Golle """A blank line must precede each child node and the 'status' 6981e8a9af9SDaniel Golle property within a node body, except when these are the first 6991e8a9af9SDaniel Golle substantive item in the body.""" 7001e8a9af9SDaniel Golle lines = ctx.lines 7011e8a9af9SDaniel Golle for i, open_dl in enumerate(lines): 7021e8a9af9SDaniel Golle if open_dl.linetype != LineType.NODE_OPEN: 7031e8a9af9SDaniel Golle continue 7041e8a9af9SDaniel Golle body_depth = open_dl.depth + 1 7051e8a9af9SDaniel Golle prev_substantive = None 7061e8a9af9SDaniel Golle between_blanks = 0 7071e8a9af9SDaniel Golle depth_inside = 0 7081e8a9af9SDaniel Golle for j in range(i + 1, len(lines)): 7091e8a9af9SDaniel Golle d = lines[j] 7101e8a9af9SDaniel Golle if d.linetype == LineType.NODE_CLOSE and \ 7111e8a9af9SDaniel Golle d.depth == body_depth - 1 and depth_inside == 0: 7121e8a9af9SDaniel Golle break 7131e8a9af9SDaniel Golle # Track depth inside nested children so we only look at 7141e8a9af9SDaniel Golle # immediate-body items. 7151e8a9af9SDaniel Golle if d.linetype == LineType.NODE_OPEN and \ 7161e8a9af9SDaniel Golle d.depth >= body_depth and depth_inside > 0: 7171e8a9af9SDaniel Golle depth_inside += 1 7181e8a9af9SDaniel Golle continue 7191e8a9af9SDaniel Golle if d.linetype == LineType.NODE_CLOSE and depth_inside > 0: 7201e8a9af9SDaniel Golle depth_inside -= 1 7211e8a9af9SDaniel Golle continue 7221e8a9af9SDaniel Golle if depth_inside > 0: 7231e8a9af9SDaniel Golle continue 7241e8a9af9SDaniel Golle if d.linetype == LineType.BLANK: 7251e8a9af9SDaniel Golle if prev_substantive is not None: 7261e8a9af9SDaniel Golle between_blanks += 1 7271e8a9af9SDaniel Golle continue 7281e8a9af9SDaniel Golle if d.linetype in (LineType.COMMENT, LineType.COMMENT_START, 7291e8a9af9SDaniel Golle LineType.COMMENT_BODY, LineType.COMMENT_END, 7301e8a9af9SDaniel Golle LineType.PREPROCESSOR): 7311e8a9af9SDaniel Golle continue 7321e8a9af9SDaniel Golle if d.linetype == LineType.CONTINUATION: 7331e8a9af9SDaniel Golle continue 7341e8a9af9SDaniel Golle 7351e8a9af9SDaniel Golle needs_blank = False 7361e8a9af9SDaniel Golle if d.linetype == LineType.NODE_OPEN: 7371e8a9af9SDaniel Golle needs_blank = True 7381e8a9af9SDaniel Golle depth_inside = 1 # entered the child body 7391e8a9af9SDaniel Golle elif d.linetype == LineType.PROPERTY and d.prop_name == 'status': 7401e8a9af9SDaniel Golle needs_blank = True 7411e8a9af9SDaniel Golle 7421e8a9af9SDaniel Golle if needs_blank and prev_substantive is not None and \ 7431e8a9af9SDaniel Golle between_blanks == 0: 7441e8a9af9SDaniel Golle if d.linetype == LineType.NODE_OPEN: 7451e8a9af9SDaniel Golle yield (d.lineno, 7461e8a9af9SDaniel Golle 'child node must be preceded by a blank line') 7471e8a9af9SDaniel Golle else: 7481e8a9af9SDaniel Golle yield (d.lineno, 7491e8a9af9SDaniel Golle '"status" must be preceded by a blank line') 7501e8a9af9SDaniel Golle 7511e8a9af9SDaniel Golle prev_substantive = d 7521e8a9af9SDaniel Golle between_blanks = 0 7531e8a9af9SDaniel Golle 7541e8a9af9SDaniel Golle 7551e8a9af9SDaniel Golledef check_hex_case(ctx): 7561e8a9af9SDaniel Golle """Hex literals (0xN) must use lowercase digits and prefix.""" 7571e8a9af9SDaniel Golle for dl in ctx.lines: 7581e8a9af9SDaniel Golle if dl.linetype in (LineType.BLANK, LineType.COMMENT, 7591e8a9af9SDaniel Golle LineType.COMMENT_START, LineType.COMMENT_BODY, 7601e8a9af9SDaniel Golle LineType.COMMENT_END, LineType.PREPROCESSOR): 7611e8a9af9SDaniel Golle continue 7621e8a9af9SDaniel Golle text = _strip_strings_and_comments(dl.raw) 7631e8a9af9SDaniel Golle for m in re.finditer(r'\b0[xX][0-9a-fA-F]+\b', text): 7641e8a9af9SDaniel Golle lit = m.group(0) 7651e8a9af9SDaniel Golle if any(c.isupper() for c in lit[2:]) or lit[1] == 'X': 7661e8a9af9SDaniel Golle yield (dl.lineno, 7671e8a9af9SDaniel Golle 'hex literal %r must be lowercase' % lit) 7681e8a9af9SDaniel Golle 7691e8a9af9SDaniel Golle 7701e8a9af9SDaniel Golledef check_unit_address_format(ctx): 7711e8a9af9SDaniel Golle """Unit addresses must be lowercase hex without leading zeros and 7721e8a9af9SDaniel Golle without a '0x' prefix. For multi-cell addresses (comma-separated), 7731e8a9af9SDaniel Golle each part is checked independently. A single '0' is permitted 7741e8a9af9SDaniel Golle (canonical zero).""" 7751e8a9af9SDaniel Golle for dl in ctx.lines: 7761e8a9af9SDaniel Golle if dl.linetype != LineType.NODE_OPEN: 7771e8a9af9SDaniel Golle continue 7781e8a9af9SDaniel Golle if dl.node_addr is None: 7791e8a9af9SDaniel Golle continue 7801e8a9af9SDaniel Golle addr = dl.node_addr 7811e8a9af9SDaniel Golle for part in addr.split(','): 7821e8a9af9SDaniel Golle if part[:2] in ('0x', '0X'): 7831e8a9af9SDaniel Golle yield (dl.lineno, 7841e8a9af9SDaniel Golle 'unit address %r must not have a "0x" prefix' % 7851e8a9af9SDaniel Golle addr) 7861e8a9af9SDaniel Golle break 7871e8a9af9SDaniel Golle if not re.match(r'^[0-9a-fA-F]+$', part): 7881e8a9af9SDaniel Golle yield (dl.lineno, 7891e8a9af9SDaniel Golle 'unit address %r is not valid hex' % addr) 7901e8a9af9SDaniel Golle break 7911e8a9af9SDaniel Golle if any(c in 'ABCDEF' for c in part): 7921e8a9af9SDaniel Golle yield (dl.lineno, 7931e8a9af9SDaniel Golle 'unit address %r must be lowercase hex' % addr) 7941e8a9af9SDaniel Golle break 7951e8a9af9SDaniel Golle if len(part) > 1 and part.startswith('0'): 7961e8a9af9SDaniel Golle yield (dl.lineno, 7971e8a9af9SDaniel Golle 'unit address %r has leading zeros' % addr) 7981e8a9af9SDaniel Golle break 7991e8a9af9SDaniel Golle 8001e8a9af9SDaniel Golle 8011e8a9af9SDaniel Golledef check_value_whitespace(ctx): 8021e8a9af9SDaniel Golle """A <...> cell list must have no whitespace directly after '<' 8031e8a9af9SDaniel Golle or directly before '>'. Continuation lines are joined onto the 8041e8a9af9SDaniel Golle property so a <...> split across lines is checked too; a '<' or 8051e8a9af9SDaniel Golle '>' at a line break is glued straight to the neighbouring value, 8061e8a9af9SDaniel Golle so the break itself is not counted as padding. Outside strings 8071e8a9af9SDaniel Golle and comments only.""" 8081e8a9af9SDaniel Golle for dl in ctx.lines: 8091e8a9af9SDaniel Golle if dl.linetype != LineType.PROPERTY: 8101e8a9af9SDaniel Golle continue 8111e8a9af9SDaniel Golle segs = [_strip_strings_and_comments(dl.raw).strip()] 8121e8a9af9SDaniel Golle for cont in dl.continuations: 8131e8a9af9SDaniel Golle segs.append(_strip_strings_and_comments(cont.stripped).strip()) 8141e8a9af9SDaniel Golle text = '' 8151e8a9af9SDaniel Golle for s in segs: 8161e8a9af9SDaniel Golle if not s: 8171e8a9af9SDaniel Golle continue 8181e8a9af9SDaniel Golle if not text or text.endswith('<') or s.startswith('>'): 8191e8a9af9SDaniel Golle text += s 8201e8a9af9SDaniel Golle else: 8211e8a9af9SDaniel Golle text += ' ' + s 8221e8a9af9SDaniel Golle for m in re.finditer(r'<([^<>]*)>', text): 8231e8a9af9SDaniel Golle content = m.group(1) 8241e8a9af9SDaniel Golle if content and content != content.strip(): 8251e8a9af9SDaniel Golle yield (dl.lineno, 'extra whitespace inside <...>') 8261e8a9af9SDaniel Golle break 8271e8a9af9SDaniel Golle 8281e8a9af9SDaniel Golle 8291e8a9af9SDaniel Golledef check_node_close_alone(ctx): 8301e8a9af9SDaniel Golle """The closing '};' of a node must be on its own line. The 8311e8a9af9SDaniel Golle classifier accepts a canonical "}" or "};" as NODE_CLOSE; a line 8321e8a9af9SDaniel Golle that is all closures (e.g. "}; };") is still NODE_CLOSE for depth 8331e8a9af9SDaniel Golle tracking but is flagged here via dl.closures. Any other line that 8341e8a9af9SDaniel Golle still contains '};' (in code, not in strings or comments) is 8351e8a9af9SDaniel Golle mixing a node close with something else.""" 8361e8a9af9SDaniel Golle for dl in ctx.lines: 8371e8a9af9SDaniel Golle if dl.linetype == LineType.NODE_CLOSE: 8381e8a9af9SDaniel Golle if dl.closures > 1: 8391e8a9af9SDaniel Golle yield (dl.lineno, 8401e8a9af9SDaniel Golle 'closing brace must be on its own line') 8411e8a9af9SDaniel Golle continue 8421e8a9af9SDaniel Golle if dl.linetype in (LineType.BLANK, LineType.COMMENT, 8431e8a9af9SDaniel Golle LineType.COMMENT_START, LineType.COMMENT_BODY, 8441e8a9af9SDaniel Golle LineType.COMMENT_END, LineType.PREPROCESSOR): 8451e8a9af9SDaniel Golle continue 8461e8a9af9SDaniel Golle text = _strip_strings_and_comments(dl.raw) 8471e8a9af9SDaniel Golle if '};' in text: 8481e8a9af9SDaniel Golle yield (dl.lineno, 8491e8a9af9SDaniel Golle 'closing brace must be on its own line') 8501e8a9af9SDaniel Golle 8511e8a9af9SDaniel Golle 8521e8a9af9SDaniel Golledef _display_col(text): 8531e8a9af9SDaniel Golle """Visual column width of text, with tabs expanded to the next 8541e8a9af9SDaniel Golle 8-column stop, matching how printf and most editors render a 8551e8a9af9SDaniel Golle line and the kernel-wide line length convention.""" 8561e8a9af9SDaniel Golle col = 0 8571e8a9af9SDaniel Golle for ch in text: 8581e8a9af9SDaniel Golle if ch == '\t': 8591e8a9af9SDaniel Golle col = (col // 8 + 1) * 8 8601e8a9af9SDaniel Golle else: 8611e8a9af9SDaniel Golle col += 1 8621e8a9af9SDaniel Golle return col 8631e8a9af9SDaniel Golle 8641e8a9af9SDaniel Golle 8651e8a9af9SDaniel Golledef check_line_length(ctx): 8661e8a9af9SDaniel Golle """Lines must not exceed 80 columns; tabs count as 8 (see 8671e8a9af9SDaniel Golle _display_col).""" 8681e8a9af9SDaniel Golle for dl in ctx.lines: 8691e8a9af9SDaniel Golle if dl.linetype == LineType.BLANK: 8701e8a9af9SDaniel Golle continue 8711e8a9af9SDaniel Golle cols = _display_col(dl.raw) 8721e8a9af9SDaniel Golle if cols > 80: 8731e8a9af9SDaniel Golle yield (dl.lineno, 8741e8a9af9SDaniel Golle 'line exceeds 80 columns (%d)' % cols) 8751e8a9af9SDaniel Golle 8761e8a9af9SDaniel Golle 8771e8a9af9SDaniel Golledef check_continuation_alignment(ctx): 8781e8a9af9SDaniel Golle """A multi-line property's continuation lines must align their 8791e8a9af9SDaniel Golle first non-whitespace character to the display column of the first 8801e8a9af9SDaniel Golle '<' or '"' after the '=' in the leading line. Display columns are 8811e8a9af9SDaniel Golle used so tab-indented .dts files (where a continuation aligns with 8821e8a9af9SDaniel Golle tabs plus spaces) are compared correctly.""" 8831e8a9af9SDaniel Golle for dl in ctx.lines: 8841e8a9af9SDaniel Golle if dl.linetype != LineType.PROPERTY: 8851e8a9af9SDaniel Golle continue 8861e8a9af9SDaniel Golle if not dl.continuations: 8871e8a9af9SDaniel Golle continue 8881e8a9af9SDaniel Golle eq = dl.raw.find('=') 8891e8a9af9SDaniel Golle if eq < 0: 8901e8a9af9SDaniel Golle continue 8911e8a9af9SDaniel Golle # First '<' or '"' after '=' 8921e8a9af9SDaniel Golle rest = dl.raw[eq + 1:] 8931e8a9af9SDaniel Golle m = re.search(r'[<"]', rest) 8941e8a9af9SDaniel Golle if not m: 8951e8a9af9SDaniel Golle continue 8961e8a9af9SDaniel Golle target_col = _display_col(dl.raw[:eq + 1 + m.start()]) 8971e8a9af9SDaniel Golle for cont in dl.continuations: 8981e8a9af9SDaniel Golle if _display_col(cont.indent_str) != target_col: 8991e8a9af9SDaniel Golle yield (cont.lineno, 9001e8a9af9SDaniel Golle 'continuation should align to column %d ' 9011e8a9af9SDaniel Golle '(under "<" or \\")' % (target_col + 1)) 9021e8a9af9SDaniel Golle 9031e8a9af9SDaniel Golle 9041e8a9af9SDaniel Golledef check_unclosed_block_comment(ctx): 9051e8a9af9SDaniel Golle """Every /* must have a matching */ in the same block. Catches both 9061e8a9af9SDaniel Golle a comment opened on its own line (COMMENT_START) and a tail comment 9071e8a9af9SDaniel Golle opened on a PROPERTY or other code line (where in_block_comment is 9081e8a9af9SDaniel Golle set by _split_code so the next line becomes COMMENT_BODY without a 9091e8a9af9SDaniel Golle preceding COMMENT_START).""" 9101e8a9af9SDaniel Golle open_lineno = None 9111e8a9af9SDaniel Golle for dl in ctx.lines: 9121e8a9af9SDaniel Golle if dl.linetype == LineType.COMMENT_START: 9131e8a9af9SDaniel Golle open_lineno = dl.lineno 9141e8a9af9SDaniel Golle elif dl.linetype == LineType.COMMENT_END: 9151e8a9af9SDaniel Golle open_lineno = None 9161e8a9af9SDaniel Golle elif dl.linetype == LineType.COMMENT_BODY and open_lineno is None: 9171e8a9af9SDaniel Golle # Block was opened by a /* tail on a code line; report at 9181e8a9af9SDaniel Golle # the first orphan body line since the originating line is 9191e8a9af9SDaniel Golle # already classified as something else. 9201e8a9af9SDaniel Golle open_lineno = dl.lineno 9211e8a9af9SDaniel Golle if open_lineno is not None: 9221e8a9af9SDaniel Golle yield (open_lineno, 'unclosed /* block comment') 9231e8a9af9SDaniel Golle 9241e8a9af9SDaniel Golle 9251e8a9af9SDaniel Golledef check_unused_labels(ctx): 9261e8a9af9SDaniel Golle """Labels defined but never referenced are clutter.""" 9271e8a9af9SDaniel Golle defined, referenced = collect_labels_and_refs(ctx.text) 9281e8a9af9SDaniel Golle for label in sorted(defined - referenced): 9291e8a9af9SDaniel Golle # Find the line where this label is defined for line-number 9301e8a9af9SDaniel Golle # reporting. 9311e8a9af9SDaniel Golle m = re.search(r'(?m)^.*\b' + re.escape(label) + r'\s*:', ctx.text) 9321e8a9af9SDaniel Golle lineno = ctx.text[:m.start()].count('\n') + 1 if m else 1 9331e8a9af9SDaniel Golle yield (lineno, 'label %r defined but never &-referenced' % label) 9341e8a9af9SDaniel Golle 9351e8a9af9SDaniel Golle 9361e8a9af9SDaniel Golle# --- registry -------------------------------------------------------------- 9371e8a9af9SDaniel Golle 9381e8a9af9SDaniel GolleRULES = [ 9391e8a9af9SDaniel Golle # 'relaxed' is the default; rules in this group must produce zero 9401e8a9af9SDaniel Golle # output on a clean kernel tree (post the small prep-cleanup 9411e8a9af9SDaniel Golle # commit at the head of this series). 9421e8a9af9SDaniel Golle Rule('trailing-whitespace', 'relaxed', 9431e8a9af9SDaniel Golle 'no trailing whitespace on any line', 9441e8a9af9SDaniel Golle check_trailing_whitespace), 94549b5cf46SKrzysztof Kozlowski Rule('tab-in-yaml', 'relaxed', 94649b5cf46SKrzysztof Kozlowski 'YAML (also DTS examples) may not contain tab characters', 94749b5cf46SKrzysztof Kozlowski check_tab_in_yaml_example, applies_to=('yaml',)), 9481e8a9af9SDaniel Golle Rule('mixed-indent-chars', 'relaxed', 9491e8a9af9SDaniel Golle 'indent must not mix tabs and spaces', 95032541ce0SKrzysztof Kozlowski check_mixed_indent_chars, applies_to=('dts', 'dtsi', 'dtso')), 9511e8a9af9SDaniel Golle Rule('unclosed-block-comment', 'relaxed', 9521e8a9af9SDaniel Golle 'every /* block comment must close with */', 9531e8a9af9SDaniel Golle check_unclosed_block_comment), 9541e8a9af9SDaniel Golle 9551e8a9af9SDaniel Golle # DTS files always use tabs; this is not negotiable per kernel 9561e8a9af9SDaniel Golle # coding style (.dts files are real source). Relaxed mode. 9571e8a9af9SDaniel Golle Rule('indent-unit-dts', 'relaxed', 9581e8a9af9SDaniel Golle 'DTS files: 1 tab per nesting level', 9591e8a9af9SDaniel Golle check_indent_unit_dts, 9601e8a9af9SDaniel Golle applies_to=('dts', 'dtsi', 'dtso')), 9611e8a9af9SDaniel Golle 9621e8a9af9SDaniel Golle # 'strict' rules are opt-in (e.g. for new submissions via 9631e8a9af9SDaniel Golle # checkpatch.pl in a follow-up series). They flag many existing 9641e8a9af9SDaniel Golle # files and can be promoted to relaxed once those are cleaned up. 9651e8a9af9SDaniel Golle Rule('indent-unit', 'strict', 9661e8a9af9SDaniel Golle 'YAML: 2 or 4 spaces per level', 9671e8a9af9SDaniel Golle check_indent_unit_relaxed, applies_to=('yaml',)), 9681e8a9af9SDaniel Golle Rule('indent-unit-strict', 'strict', 9691e8a9af9SDaniel Golle 'YAML: must be 4 spaces per level', 9701e8a9af9SDaniel Golle check_indent_unit_strict, applies_to=('yaml',)), 9711e8a9af9SDaniel Golle Rule('indent-consistent', 'strict', 9721e8a9af9SDaniel Golle 'every line indented at depth * unit', 9731e8a9af9SDaniel Golle check_indent_consistent), 9741e8a9af9SDaniel Golle Rule('blank-lines', 'strict', 9751e8a9af9SDaniel Golle 'no consecutive blanks; no blanks at node body edges', 9761e8a9af9SDaniel Golle check_blank_lines), 9771e8a9af9SDaniel Golle Rule('child-address-order', 'strict', 9781e8a9af9SDaniel Golle 'addressed siblings must be in ascending address order', 9791e8a9af9SDaniel Golle check_child_address_order), 9801e8a9af9SDaniel Golle Rule('child-name-order', 'strict', 9811e8a9af9SDaniel Golle 'unaddressed siblings must be in natural-sort name order', 9821e8a9af9SDaniel Golle check_child_name_order), 9831e8a9af9SDaniel Golle Rule('property-order', 'strict', 9841e8a9af9SDaniel Golle 'canonical bucket + pairing + natural-sort order of properties', 9851e8a9af9SDaniel Golle check_property_order), 9861e8a9af9SDaniel Golle Rule('required-blank-lines', 'strict', 9871e8a9af9SDaniel Golle 'blank line before child nodes and before "status"', 9881e8a9af9SDaniel Golle check_required_blank_lines), 9891e8a9af9SDaniel Golle Rule('hex-case', 'strict', 9901e8a9af9SDaniel Golle 'hex literals must be lowercase', 9911e8a9af9SDaniel Golle check_hex_case), 9921e8a9af9SDaniel Golle Rule('unit-address-format', 'strict', 9931e8a9af9SDaniel Golle 'unit addresses must be lowercase hex without leading zeros', 9941e8a9af9SDaniel Golle check_unit_address_format), 9951e8a9af9SDaniel Golle Rule('value-whitespace', 'strict', 9961e8a9af9SDaniel Golle 'no whitespace directly inside <...> brackets', 9971e8a9af9SDaniel Golle check_value_whitespace), 9981e8a9af9SDaniel Golle Rule('node-close-alone', 'strict', 9991e8a9af9SDaniel Golle 'closing brace must be on its own line', 10001e8a9af9SDaniel Golle check_node_close_alone), 10011e8a9af9SDaniel Golle Rule('line-length', 'strict', 10021e8a9af9SDaniel Golle 'lines must not exceed 80 columns', 10031e8a9af9SDaniel Golle check_line_length), 10041e8a9af9SDaniel Golle Rule('continuation-alignment', 'strict', 10051e8a9af9SDaniel Golle 'multi-line property continuations align under "<" or "\\""', 10061e8a9af9SDaniel Golle check_continuation_alignment), 10071e8a9af9SDaniel Golle Rule('unused-labels', 'strict', 10081e8a9af9SDaniel Golle 'every label must be &-referenced in the same example/file ' 10091e8a9af9SDaniel Golle '(skipped for .dtsi/.dtso since labels there are exported)', 10101e8a9af9SDaniel Golle check_unused_labels, applies_to=('yaml', 'dts')), 10111e8a9af9SDaniel Golle] 10121e8a9af9SDaniel Golle 10131e8a9af9SDaniel Golle 10141e8a9af9SDaniel Golledef select_rules(mode, input_kind): 10151e8a9af9SDaniel Golle """Return rules that apply to the given mode and input type.""" 10161e8a9af9SDaniel Golle rank = {'relaxed': 0, 'strict': 1} 10171e8a9af9SDaniel Golle out = [] 10181e8a9af9SDaniel Golle for r in RULES: 10191e8a9af9SDaniel Golle if rank[r.mode] > rank[mode]: 10201e8a9af9SDaniel Golle continue 10211e8a9af9SDaniel Golle if input_kind not in r.applies_to: 10221e8a9af9SDaniel Golle continue 10231e8a9af9SDaniel Golle out.append(r) 10241e8a9af9SDaniel Golle return out 10251e8a9af9SDaniel Golle 10261e8a9af9SDaniel Golle 10271e8a9af9SDaniel Golle# --------------------------------------------------------------------------- 10281e8a9af9SDaniel Golle# Block runner 10291e8a9af9SDaniel Golle# --------------------------------------------------------------------------- 10301e8a9af9SDaniel Golle 103146fb56e4SKrzysztof Kozlowskidef check_block(text, mode, input_type): 10321e8a9af9SDaniel Golle """Run all selected rules on a single block of DTS text. Returns a 10331e8a9af9SDaniel Golle list of (lineno, rule_name, message) tuples.""" 10341e8a9af9SDaniel Golle lines = classify_lines(text) 103546fb56e4SKrzysztof Kozlowski ctx = Ctx(lines, text, mode, input_type) 10361e8a9af9SDaniel Golle rules = select_rules(mode, input_type) 10371e8a9af9SDaniel Golle findings = [] 10381e8a9af9SDaniel Golle for r in rules: 10391e8a9af9SDaniel Golle for lineno, msg in r.check(ctx): 10401e8a9af9SDaniel Golle findings.append((lineno, r.name, msg)) 10411e8a9af9SDaniel Golle findings.sort(key=lambda t: (t[0], t[1])) 10421e8a9af9SDaniel Golle return findings 10431e8a9af9SDaniel Golle 10441e8a9af9SDaniel Golle 10451e8a9af9SDaniel Golle# --------------------------------------------------------------------------- 10461e8a9af9SDaniel Golle# Input drivers (YAML examples vs raw DTS) 10471e8a9af9SDaniel Golle# --------------------------------------------------------------------------- 10481e8a9af9SDaniel Golle 10491e8a9af9SDaniel Golledef _yaml_loader(): 10501e8a9af9SDaniel Golle return ruamel.yaml.YAML() 10511e8a9af9SDaniel Golle 10521e8a9af9SDaniel Golle 10531e8a9af9SDaniel Golledef iter_yaml_examples(filepath): 10541e8a9af9SDaniel Golle """Yield (example_text, base_lineno_in_file, example_index) tuples.""" 10551e8a9af9SDaniel Golle yaml = _yaml_loader() 10561e8a9af9SDaniel Golle try: 10571e8a9af9SDaniel Golle with open(filepath, encoding='utf-8') as f: 10581e8a9af9SDaniel Golle data = yaml.load(f) 10591e8a9af9SDaniel Golle except Exception as e: 10601e8a9af9SDaniel Golle print('%s: error loading YAML: %s' % (filepath, e), 10611e8a9af9SDaniel Golle file=sys.stderr) 10621e8a9af9SDaniel Golle return 10631e8a9af9SDaniel Golle if not isinstance(data, dict) or 'examples' not in data: 10641e8a9af9SDaniel Golle return 10651e8a9af9SDaniel Golle examples = data['examples'] 10661e8a9af9SDaniel Golle if not hasattr(examples, '__iter__'): 10671e8a9af9SDaniel Golle return 10681e8a9af9SDaniel Golle for i, ex in enumerate(examples): 10691e8a9af9SDaniel Golle if not isinstance(ex, str): 10701e8a9af9SDaniel Golle continue 10711e8a9af9SDaniel Golle try: 10721e8a9af9SDaniel Golle base = examples.lc.item(i)[0] + 2 10731e8a9af9SDaniel Golle except Exception: 10741e8a9af9SDaniel Golle base = 1 10751e8a9af9SDaniel Golle yield (str(ex), base, i) 10761e8a9af9SDaniel Golle 10771e8a9af9SDaniel Golle 10781e8a9af9SDaniel Golledef iter_dts_file(filepath): 10791e8a9af9SDaniel Golle """Treat the whole file as a single block.""" 10801e8a9af9SDaniel Golle try: 10811e8a9af9SDaniel Golle with open(filepath, encoding='utf-8') as f: 10821e8a9af9SDaniel Golle text = f.read() 10831e8a9af9SDaniel Golle except Exception as e: 10841e8a9af9SDaniel Golle print('%s: error reading: %s' % (filepath, e), file=sys.stderr) 10851e8a9af9SDaniel Golle return 10861e8a9af9SDaniel Golle yield (text, 1, None) 10871e8a9af9SDaniel Golle 10881e8a9af9SDaniel Golle 10891e8a9af9SDaniel Golle# --------------------------------------------------------------------------- 10901e8a9af9SDaniel Golle# Top-level processing 10911e8a9af9SDaniel Golle# --------------------------------------------------------------------------- 10921e8a9af9SDaniel Golle 10931e8a9af9SDaniel Golledef input_kind(filepath): 10941e8a9af9SDaniel Golle p = filepath.lower() 10951e8a9af9SDaniel Golle if p.endswith('.yaml') or p.endswith('.yml'): 10961e8a9af9SDaniel Golle return 'yaml' 10971e8a9af9SDaniel Golle if p.endswith('.dts'): 10981e8a9af9SDaniel Golle return 'dts' 10991e8a9af9SDaniel Golle if p.endswith('.dtsi'): 11001e8a9af9SDaniel Golle return 'dtsi' 11011e8a9af9SDaniel Golle if p.endswith('.dtso'): 11021e8a9af9SDaniel Golle return 'dtso' 11031e8a9af9SDaniel Golle return None 11041e8a9af9SDaniel Golle 11051e8a9af9SDaniel Golle 11061e8a9af9SDaniel Golle# All input types that use tab indentation and follow DTS coding style. 11071e8a9af9SDaniel GolleDTS_FAMILY = ('dts', 'dtsi', 'dtso') 11081e8a9af9SDaniel Golle 11091e8a9af9SDaniel Golle 11101e8a9af9SDaniel Golledef collect_findings(filepath, mode): 11111e8a9af9SDaniel Golle """Return a (lines, count) pair for filepath. lines is a list of 11121e8a9af9SDaniel Golle formatted output strings; count is the number of findings.""" 11131e8a9af9SDaniel Golle kind = input_kind(filepath) 11141e8a9af9SDaniel Golle if kind == 'yaml': 11151e8a9af9SDaniel Golle iterator = iter_yaml_examples(filepath) 11161e8a9af9SDaniel Golle elif kind in DTS_FAMILY: 11171e8a9af9SDaniel Golle iterator = iter_dts_file(filepath) 11181e8a9af9SDaniel Golle else: 11191e8a9af9SDaniel Golle return (['%s: unknown file type, skipping' % filepath], 0) 11201e8a9af9SDaniel Golle 11211e8a9af9SDaniel Golle out = [] 11221e8a9af9SDaniel Golle for text, base, idx in iterator: 112346fb56e4SKrzysztof Kozlowski for lineno, rule, msg in check_block(text, mode, kind): 11241e8a9af9SDaniel Golle abs_line = base + lineno - 1 11251e8a9af9SDaniel Golle ex_tag = '' if idx is None else ' example %d' % idx 11261e8a9af9SDaniel Golle out.append('%s:%d:%s [%s] %s' % 11271e8a9af9SDaniel Golle (filepath, abs_line, ex_tag, rule, msg)) 11281e8a9af9SDaniel Golle return (out, len(out)) 11291e8a9af9SDaniel Golle 11301e8a9af9SDaniel Golle 11311e8a9af9SDaniel Golle# Worker entry point for ProcessPoolExecutor.map(). Top-level so it is 11321e8a9af9SDaniel Golle# picklable on every platform. 11331e8a9af9SDaniel Golledef _worker(args): 11341e8a9af9SDaniel Golle filepath, mode = args 11351e8a9af9SDaniel Golle return collect_findings(filepath, mode) 11361e8a9af9SDaniel Golle 11371e8a9af9SDaniel Golle 11381e8a9af9SDaniel Golledef main(): 11391e8a9af9SDaniel Golle import os 11401e8a9af9SDaniel Golle ap = argparse.ArgumentParser( 11411e8a9af9SDaniel Golle description='Check DTS coding style on YAML examples and ' 11421e8a9af9SDaniel Golle '.dts/.dtsi/.dtso files.', 11431e8a9af9SDaniel Golle fromfile_prefix_chars='@') 11441e8a9af9SDaniel Golle ap.add_argument('--mode', choices=('relaxed', 'strict'), 11451e8a9af9SDaniel Golle default='relaxed', 11461e8a9af9SDaniel Golle help='which rule set to apply (default: relaxed)') 11471e8a9af9SDaniel Golle ap.add_argument('-j', '--jobs', type=int, default=0, 11481e8a9af9SDaniel Golle metavar='N', 11491e8a9af9SDaniel Golle help='run N workers in parallel (default: respect ' 11501e8a9af9SDaniel Golle 'the make jobserver via $PARALLELISM, otherwise ' 11511e8a9af9SDaniel Golle 'os.cpu_count(); use 1 to disable multiprocessing)') 11521e8a9af9SDaniel Golle ap.add_argument('--list-rules', action='store_true', 11531e8a9af9SDaniel Golle help='print all rules with their mode and exit') 11541e8a9af9SDaniel Golle ap.add_argument('files', nargs='*', metavar='file', 11551e8a9af9SDaniel Golle help='YAML binding files or .dts/.dtsi/.dtso files; ' 11561e8a9af9SDaniel Golle 'use @argfile to read paths from a file') 11571e8a9af9SDaniel Golle args = ap.parse_args() 11581e8a9af9SDaniel Golle 11591e8a9af9SDaniel Golle if args.list_rules: 11601e8a9af9SDaniel Golle for r in RULES: 11611e8a9af9SDaniel Golle applies = ','.join(r.applies_to) 11621e8a9af9SDaniel Golle print('%-22s %-7s [%s] %s' % 11631e8a9af9SDaniel Golle (r.name, r.mode, applies, r.description)) 11641e8a9af9SDaniel Golle return 0 11651e8a9af9SDaniel Golle 11661e8a9af9SDaniel Golle if not args.files: 11671e8a9af9SDaniel Golle ap.error('no input files') 11681e8a9af9SDaniel Golle 11691e8a9af9SDaniel Golle if args.jobs > 0: 11701e8a9af9SDaniel Golle jobs = args.jobs 11711e8a9af9SDaniel Golle else: 11721e8a9af9SDaniel Golle # When invoked under scripts/jobserver-exec, $PARALLELISM 11731e8a9af9SDaniel Golle # holds the slot count make has reserved for us; this lets 11741e8a9af9SDaniel Golle # `make -j N dt_binding_check` constrain our worker pool to N. 11751e8a9af9SDaniel Golle try: 11761e8a9af9SDaniel Golle jobs = int(os.environ['PARALLELISM']) 11771e8a9af9SDaniel Golle except (KeyError, ValueError): 11781e8a9af9SDaniel Golle jobs = os.cpu_count() or 1 11791e8a9af9SDaniel Golle # Single-process path: keep import surface small for tests and 11801e8a9af9SDaniel Golle # easy debugging. 11811e8a9af9SDaniel Golle if jobs == 1 or len(args.files) == 1: 11821e8a9af9SDaniel Golle total = 0 11831e8a9af9SDaniel Golle for f in args.files: 11841e8a9af9SDaniel Golle lines, n = collect_findings(f, args.mode) 11851e8a9af9SDaniel Golle for line in lines: 11861e8a9af9SDaniel Golle print(line, file=sys.stderr) 11871e8a9af9SDaniel Golle total += n 11881e8a9af9SDaniel Golle return 1 if total else 0 11891e8a9af9SDaniel Golle 11901e8a9af9SDaniel Golle # Multi-process path. ex.map preserves input order so output is 11911e8a9af9SDaniel Golle # deterministic across runs. 11921e8a9af9SDaniel Golle from concurrent.futures import ProcessPoolExecutor 11931e8a9af9SDaniel Golle total = 0 11941e8a9af9SDaniel Golle work = [(f, args.mode) for f in args.files] 11951e8a9af9SDaniel Golle chunk = max(1, len(work) // (jobs * 8)) if work else 1 11961e8a9af9SDaniel Golle with ProcessPoolExecutor(max_workers=jobs) as ex: 11971e8a9af9SDaniel Golle for lines, n in ex.map(_worker, work, chunksize=chunk): 11981e8a9af9SDaniel Golle for line in lines: 11991e8a9af9SDaniel Golle print(line, file=sys.stderr) 12001e8a9af9SDaniel Golle total += n 12011e8a9af9SDaniel Golle return 1 if total else 0 12021e8a9af9SDaniel Golle 12031e8a9af9SDaniel Golle 12041e8a9af9SDaniel Golleif __name__ == '__main__': 12051e8a9af9SDaniel Golle sys.exit(main()) 1206