From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mx0b-0031df01.pphosted.com (mx0b-0031df01.pphosted.com [205.220.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ED331346ADB for ; Sat, 29 Aug 2026 19:53:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=205.220.180.131 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788033224; cv=none; b=cFrmqjAQG309gqQ5GOibSBVnqDniwPEW6m2fzKGWNl4Q1CDng81xzx8UwM2VTFXLSfvA7cXv/wgDJVmAAnyS3YLplXm3TuftTS9mE3MyGEnOjp1pA5sVlRJlKyRTT2v8pjADlc+YatOQ977hQkVRSamLB88w+pQvZuWaLYmA3nA= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788033224; c=relaxed/simple; bh=3D23pkruwVdjlWXcvI4IbBb/R1IKkVVvnVyarpPlI+M=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=PW0S7pcj7DQiwT5EiwQi3MW5o/mrSRHEBU+feer8peeYTGKY3KqLHEQLexxQpbVtc9XfBOebyUJUD/jQze1Ugy4cmSI2cLk1AHkCttc3Q6rxQDqeWTEvaRCu/DTUanrHJ7fr6a7aZmtaE6Zz6NWS/O36Q/ER4dvtzj7r0IPDgBA= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com; spf=pass smtp.mailfrom=oss.qualcomm.com; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b=mv/+Wr2C; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b=VIt7DBfM; arc=none smtp.client-ip=205.220.180.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b="mv/+Wr2C"; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b="VIt7DBfM" Received: from pps.filterd (m0279868.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 67TJlelc2541585 for ; Sat, 29 Aug 2026 19:53:40 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=qualcomm.com; h= cc:content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s=qcppdkim1; bh= OBfa2YKDANgkRg0aGIM/I1Kyr4RJbFdYeKfNGHAAmpQ=; b=mv/+Wr2Cljb2KFAR RjzDTi6mlPrH68U7ejmX0gm2q91i20P7HEcF45lzwOZcOjVwo8pCYTWWiboDAwfu pEp1UWIsJN4E3tKmckblEVvi/CahItJ7BJvCDYUiA1nTpcsgXrqANsfiEQDDxXch xsPlzcRIwOnfH2NnPrChrr3baPYTkT41fDqjlrhnwnxoseYmZbgjl2kiBHwCoev/ Y0Y2nehfxK2XB7LNOgUcCoJNqnkfo7yndaP8IlJypzGSpagQSeor2bH4Lgv2tHRp RNdQGWmUNIfiOVVbS0UIz1nUf3ZZIeMlt6i8joNJYY+ffoqEUiAcL8UvM2KVCHMa l7HeOA== Received: from mail-qt1-f199.google.com (mail-qt1-f199.google.com [209.85.160.199]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4gbqe29q7w-1 (version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT) for ; Sat, 29 Aug 2026 19:53:40 +0000 (GMT) Received: by mail-qt1-f199.google.com with SMTP id d75a77b69052e-52d33c89a29so51855871cf.1 for ; Sat, 29 Aug 2026 12:53:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oss.qualcomm.com; s=google; t=1788033220; x=1788638020; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=OBfa2YKDANgkRg0aGIM/I1Kyr4RJbFdYeKfNGHAAmpQ=; b=VIt7DBfMS/GBNmHeBlxtR5CFmUKNliG0T4J2PRHLceCyZKrnCTFWFXJHyUEBslEAbb sull0ISuwigSKQtRjlRKBdv0+KUDRS8rs4Dx115bT5adCZAVvpaThyoY2EsvwzxiUcra m8QTBSjOMwhTdv16nlFQPNRzHvzbi/ooSmynZ8jgxkZy2IbiELj71NVtt5o/PWNFuAku DiAepkPewbWIQBTD2+5y1oTWUx2aKdCHsmXPRzvRb8uZdy3/TNtAVLyLFAR3Uio7Nx45 RzrGLAll0Oz8WnhFOVpRnrcrhQUt8C7D5ddDWFvg7wnrh9zH+Q6kbisHEO6fVCjLEvZs eiMA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788033220; x=1788638020; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=OBfa2YKDANgkRg0aGIM/I1Kyr4RJbFdYeKfNGHAAmpQ=; b=lYqk//S4RR5UYT5B8jbhoe0ZQiqbq4mofeG/BwaUn+MlzcqM6y7cy5rTwg9Q/3Cq4E OMtQgrd8wUI/eQf//6G+lw3Xt4VpgjaNiFwg9rX8Qn+O3iE1F17etuxB+9nGH/2nJGQm 7Gfb8w70ZzwSgmLqKdoCh33T2WGX7I0qCzfakD67+Am1tqgUo6iqxb8PcFJ8ab9ZGbkd ufpMMkcr2XluF+OuSZ05RG/VP3jb1BgtxiIWx7tKxGESJmUfrjDYwotCFNXfq7lWdCyT /2EdV1kqj2OsEZt/uFH2p8eA7yNsGX+PMTxiLwDJeUFFmf7ccDZWSXghXoiGN9EnfTAd z9IA== X-Gm-Message-State: AFuF++lYCNxUV5Sanuld1cSI042x8OyHc4jokOT/P9t1WtTEPlrIjAUT Y+eccaDUvAyb+CIMEaX3Lhf+8WICsnd1NqbNkzfVLPGU7Vjdpf/MU5eD2dn5TaqPxQSHGJidMmX u2rnKMuN9I0GvX3f5ETSG+vdcrqoN2SlHzbXl5nwx2J8rzKIz+9DYwX257Q5DpcxW X-Gm-Gg: AR+sD12NMc1F+wOF652eyo1k2CInx+x1ehJeLb2gMLSeIppmfDea0LkP8A3/y6DKpdB iQbdV3tIsErvnUV8YleoDI18CmM1xHVgARr8XXJ8RX9BxTsnQOi+CDCEFlWXVSuz2fuSvTjFI5T 4Jkxv5yEjRP5RBv/89DvSA6ZbINhnMyWFpGLpgs3z2WcvobYJ5UTJ0CbUkKUrTWbe4vRCKKgw3m 2tXcLszPxJBxSzDY1JgvtiPz2D81DgnPjFENVrqtzcA+Ary/eXnDzcoYpgygAI/L8q/b9iajiPL 0E56uh1M2cihlhp5keLBtm61M/oBc9H5IEx6If222jv81Mn5/JmKv7ks1/rWef9M3Pyi2i9dAGX oZvLUJs6f0GN1BRanCTeOZzo= X-Received: by 2002:a05:622a:e3d0:10b0:52f:fd65:d939 with SMTP id d75a77b69052e-52ffd65dcf0mr33364771cf.22.1788033219547; Sat, 29 Aug 2026 12:53:39 -0700 (PDT) X-Received: by 2002:a05:622a:e3d0:10b0:52f:fd65:d939 with SMTP id d75a77b69052e-52ffd65dcf0mr33364391cf.22.1788033218834; Sat, 29 Aug 2026 12:53:38 -0700 (PDT) Received: from [127.0.1.1] ([178.197.219.214]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-482fbb32d30sm12446343f8f.34.2026.08.29.12.53.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 29 Aug 2026 12:53:38 -0700 (PDT) From: Krzysztof Kozlowski Date: Sat, 29 Aug 2026 21:53:18 +0200 Subject: [PATCH 2/6] dtc: dt-check-style: Sort rule functions by name Precedence: bulk X-Mailing-List: devicetree@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20260829-dts-style-checker-sort-v1-2-6693a37d7ae1@oss.qualcomm.com> References: <20260829-dts-style-checker-sort-v1-0-6693a37d7ae1@oss.qualcomm.com> In-Reply-To: <20260829-dts-style-checker-sort-v1-0-6693a37d7ae1@oss.qualcomm.com> To: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Saravana Kannan Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Krzysztof Kozlowski X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=28485; i=krzysztof.kozlowski@oss.qualcomm.com; h=from:subject:message-id; bh=3D23pkruwVdjlWXcvI4IbBb/R1IKkVVvnVyarpPlI+M=; b=owEBbQKS/ZANAwAKAcE3ZuaGi4PXAcsmYgBqkzi0a59a889rpZkJp5cnFoxlST+fE1GujDmhN xsp62BksSuJAjMEAAEKAB0WIQTd0mIoPREbIztuuKjBN2bmhouD1wUCapM4tAAKCRDBN2bmhouD 17Y8D/wMCfl5dOLSYHAsdgx71Tfxgd1svmgEnFoAnLEMHe2Mc6VR6yBpI2LjCprzU4cdofSwkpk 4is3DMscUzUvzGTph/b0TjB1+P7LA6fnEMXids/tTVhcakmD7vru9Rm1gQXY44+UC2Avmq47UPb aVYqL4EcCJDGfWzMfjbf/GSfJXOrdE6+tkyGFPzIwJrrDtiSTdPR2XhL0dzVYiJ1onQPOiXdh7F LiRL3qmEtWFNnItO5n18FyYx6+ytSkFeduOMePIGjCj4rVcjUrnYFB7FrAFGrB5rtR3J9kSm/Bl V+6jWi4HiyggBpjW/nil8eR+C7lUpzE5d3v1sS5Xun+Vh09Khb6J3ghumc22FxcuTbscNp64BxZ 2FNBDNwgAQB6O8Q2X602NZMDamt6ipZVBEbB5zxj6vGvly7j/3MLNcXBGDM1aS2Vot+UxB4aDNZ J6DzGOy0agFf4DC0Qdj8c6KG2VyC7PRpvVSMfx5ovsEBmdeunktj96KS3b0po8zExr5CznboQsM DWjwUP0NjRJswWYjzvskAd88vpDzPGTuGFPRfw4aJh8CZphG5tX3tzcjPN1iWltHPQY/17eXrQy DHKx2qpIQ/RNkahvxPQNCjSMPfdxYro38gUi2wxQTolqTD8ErEuwXzO/VcsABfeqoQa1GbwL3UD mXFsI0ttcowJs+g== X-Developer-Key: i=krzysztof.kozlowski@oss.qualcomm.com; a=openpgp; fpr=9BD07E0E0C51F8D59677B7541B93437D3B41629B X-Authority-Analysis: v=2.4 cv=Iskutr/g c=1 sm=1 tr=0 ts=6a9338c4 cx=c_pps a=WeENfcodrlLV9YRTxbY/uA==:117 a=+bKQE0WJfmhK2875HamI0Q==:17 a=IkcTkHD0fZMA:10 a=Sv0fKeRqtYgA:10 a=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=u7WPNUs3qKkmUXheDGA7:22 a=ZpdpYltYx_vBUK5n70dp:22 a=EUspDBNiAAAA:8 a=Z9rIwXeQ7yUTy2gfAJsA:9 a=ib9xVW6GgGiRPqfr:21 a=QEXdDO2ut3YA:10 a=kacYvNCVWA4VmyqE58fU:22 X-Proofpoint-GUID: ssCW97WxdB65O7dSP0SwFOt_-bOOeFUa X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwODI5MDE3MSBTYWx0ZWRfX4nXXORhjhWmB NQAxPYxEv3Qx9Ed2p7iWkCMEG5YNupSwk2Uvx1GbZg/vL8u28c48wllqfgftjoJ0JIFmZhCUfmH DqAoBq/ZMzO/wV0k9kqBlmEqA5zGJlOYFwKIzOT9f82APqk6xaqEa70PI1QLB1nbXxVZKOB2J0g 2Wow+SRua09X1QvUR4YS/Ewao3Z/z+sCSSl9398lknYdFC/l2tH0b99CmBugFpkQimwTOS41ymo eLO9Ds8q+Qtdipg/Lwja/KglXjV4Nn/sqEprkXhayDyNGyP4rWyhYmeyqrAV3XZLm6m6Rh5xIap hENNbPONWH2b2hagr1fp8UsKV1qeXvL5ktvZUqsOgN8OPdOm/Ahy7ttBCm603z/ozxOsw6/r1eV HVKomX7jvdKWYLiVqniV3PYHdpCwfRtfMU2l970EKdUop++6amRZx9r2CWKt+Qu1wHh4IWvaYiu vlNnEDBOy72mn/n7xBw== X-Proofpoint-ORIG-GUID: ssCW97WxdB65O7dSP0SwFOt_-bOOeFUa X-Proofpoint-Spam-Info: AW1haW4tMjYwODI5MDE3MSBTYWx0ZWRfX3b/i4hMGx6wM 0TboJ0HBwysCqDdvB/wpJE26fkjDRqDZUMn/BX/5Im1sob0+vlR8v79Wn3jPldnMcyGksNE3Fsp BgCEJJLBAiKD2/xh8wU2wADFevfyTTo= X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1176,Hydra:6.1.134,FMLib:17.12.100.49 definitions=2026-08-29_06,2026-08-27_02,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 spamscore=0 bulkscore=0 malwarescore=0 adultscore=0 lowpriorityscore=0 clxscore=1015 priorityscore=1501 impostorscore=0 suspectscore=0 phishscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2608290171 Sort all the functions implementing style rules by name, so managing this will be at bit easier and simultaneous addons of new rules less conflict-prone. No functional changes, except adding underscore to _detect_indent_unit() to match other helpers used by the rules. Signed-off-by: Krzysztof Kozlowski --- scripts/dtc/dt-check-style | 607 +++++++++++++++++++++++---------------------- 1 file changed, 304 insertions(+), 303 deletions(-) diff --git a/scripts/dtc/dt-check-style b/scripts/dtc/dt-check-style index 49b5c28563eb..5385a03f377e 100755 --- a/scripts/dtc/dt-check-style +++ b/scripts/dtc/dt-check-style @@ -343,88 +343,8 @@ class Rule: # --- individual rule check functions -------------------------------------- -def check_trailing_whitespace(ctx): - for dl in ctx.lines: - if dl.raw != dl.raw.rstrip(): - yield (dl.lineno, 'trailing whitespace') - -def _check_redundant_whitespace(dl): - if dl.linetype in (LineType.COMMENT, LineType.COMMENT_BODY, - LineType.COMMENT_END, LineType.COMMENT_START, - LineType.PREPROCESSOR): - return - if re.search(r'(\s\s+|\t)\{', dl.code): - yield (dl.lineno, 'extra whitespace before {') - if re.search(r':(\s\s+|\t)', dl.code): - yield (dl.lineno, 'extra whitespace after :') - if re.search(r'\s+;', dl.code): - yield (dl.lineno, 'extra whitespace before ;') - - -def check_redundant_whitespace(ctx): - """No whitespace between brackets or other code elements. - See also check_value_whitespace() for more checks.""" - for dl in ctx.lines: - yield from _check_redundant_whitespace(dl) - for cont in dl.continuations: - yield from _check_redundant_whitespace(cont) - - -def check_redundant_whitespace_strict(ctx): - """No whitespace between brackets or other code elements. - See also check_value_whitespace() for more checks.""" - for dl in ctx.lines: - if dl.linetype in (LineType.COMMENT, LineType.COMMENT_BODY, - LineType.COMMENT_END, LineType.COMMENT_START, - LineType.PREPROCESSOR): - continue - if re.search(r'(\s\s+|\t)=', dl.code): - yield (dl.lineno, 'extra whitespace before =') - if re.search(r'=(\s\s+|\t)', dl.code): - yield (dl.lineno, 'extra whitespace after =') - - -def check_tab_in_yaml_example(ctx): - """Reject literal tabs in DTS lines when input is YAML. - - For YAML examples, indent and content must use spaces. Tabs inside - a #define value are tolerated (those are CPP macros, not DTS). - For .dts files, this rule does not apply -- tabs are required. - """ - if ctx.file_type != 'yaml': - return - for dl in ctx.lines: - if dl.linetype == LineType.PREPROCESSOR: - continue - if dl.linetype == LineType.BLANK: - continue - if '\t' in dl.raw: - yield (dl.lineno, 'tab character not allowed in DTS example') - - -def check_mixed_indent_chars(ctx): - """Indent must be all-tabs, except for aligning indentation (comments - or continued lines).""" - for dl in ctx.lines: - if not dl.indent_str: - continue - if dl.linetype == LineType.PREPROCESSOR: - continue - if re.search(r' \t', dl.indent_str): - yield (dl.lineno, 'mixed tabs and spaces in indent') - if dl.indent_str.count(' ') > 7: - yield (dl.lineno, 'too many space characters in indent (more than 7)') - for cont in dl.continuations: - if not cont.indent_str: - continue - if cont.linetype == LineType.PREPROCESSOR: - continue - if re.search(r' \t', cont.indent_str): - yield (cont.lineno, 'mixed tabs and spaces in indent') - - -def detect_indent_unit(ctx): +def _detect_indent_unit(ctx): """Find the indent unit used at depth 1 in this block. Returns tuple of string (one of: ' ' (2 spaces), ' ' (4 spaces), @@ -450,85 +370,42 @@ def detect_indent_unit(ctx): return (None, None) -def check_indent_unit_relaxed(ctx): - """YAML examples: 2 or 4 spaces. Never tabs or other widths.""" - (unit, lineno) = detect_indent_unit(ctx) - if unit is None: - return - if unit not in (' ', ' '): - yield (lineno, 'indent unit must be 2 or 4 spaces, got %r' % unit) +def _display_col(text): + """Visual column width of text, with tabs expanded to the next + 8-column stop, matching how printf and most editors render a + line and the kernel-wide line length convention.""" + col = 0 + for ch in text: + if ch == '\t': + col = (col // 8 + 1) * 8 + else: + col += 1 + return col -def check_indent_unit_dts(ctx): - """DTS files: 1 tab per level. Always required.""" - (unit, lineno) = detect_indent_unit(ctx) - if unit is None: - return - if unit != '\t': - yield (lineno, 'indent unit must be 1 tab in DTS, got %r' % unit) +def _natural_sort_key(s): + """Split a string into a tuple of (kind, value) pairs that compares + numeric runs as ints, so 'foo10' sorts after 'foo2'.""" + parts = [] + for part in re.split(r'(\d+)', s): + if part.isdigit(): + parts.append((0, int(part))) + else: + parts.append((1, part)) + return tuple(parts) -def check_indent_unit_strict(ctx): - """YAML: must be exactly 4 spaces. DTS: 1 tab (same as relaxed).""" - (unit, lineno) = detect_indent_unit(ctx) - if unit is None: - return - if ctx.file_type == 'yaml': - if unit != ' ': - yield (lineno, 'indent unit must be 4 spaces in strict mode, ' - 'got %r' % unit) - - -def check_indent_consistent(ctx): - """All indented lines must be a multiple of the detected unit.""" - (unit, lineno) = detect_indent_unit(ctx) - if unit is None: - return - if ctx.file_type == 'yaml': - if unit not in (' ', ' '): - return # let check_indent_unit_* report this - else: - if unit != '\t': - return - - for dl in ctx.lines: - if dl.linetype in (LineType.BLANK, LineType.PREPROCESSOR): - continue - if dl.linetype == LineType.CONTINUATION: - continue # continuations align to <, not to indent unit - if dl.linetype in (LineType.COMMENT_BODY, LineType.COMMENT_END): - continue - if not dl.indent_str: - continue - # The indent must be 'unit' repeated dl.depth times, exactly. - # NODE_CLOSE lines have depth equal to the post-decrement value, - # which matches the indent expected. - expected = unit * dl.depth - if dl.indent_str != expected: - yield (dl.lineno, - 'indent mismatch (expected depth %d * %r)' % - (dl.depth, unit)) - - -def check_blank_lines(ctx): - """No two consecutive blank lines, no leading/trailing blank lines - in any node body.""" - lines = ctx.lines - # Consecutive blanks - for i in range(1, len(lines)): - if lines[i].linetype == LineType.BLANK and \ - lines[i - 1].linetype == LineType.BLANK: - yield (lines[i].lineno, 'consecutive blank lines') - # Blank right after { or right before } - for i, dl in enumerate(lines): - if dl.linetype != LineType.BLANK: - continue - prev = lines[i - 1] if i > 0 else None - nxt = lines[i + 1] if i + 1 < len(lines) else None - if prev is not None and prev.linetype == LineType.NODE_OPEN: - yield (dl.lineno, 'blank line at start of node body') - if nxt is not None and nxt.linetype == LineType.NODE_CLOSE: - yield (dl.lineno, 'blank line at end of node body') +def _strip_strings_and_comments(text): + """Remove string literals and /* */ + // comments from a single + line, replacing them with empty strings. Used so syntactic checks + (whitespace, hex case, etc.) don't false-positive on contents of + quoted strings or comments. An unclosed /* on the line is treated + as a comment running to end of line.""" + text = re.sub(r'"(?:[^"\\]|\\.)*"', '""', text) + text = re.sub(r'/\*.*?\*/', '', text) + text = re.sub(r'/\*.*$', '', text) + text = re.sub(r'//.*$', '', text) + return text def _walk_bodies(lines): @@ -559,16 +436,25 @@ def _walk_bodies(lines): yield body_stack.pop() -def _natural_sort_key(s): - """Split a string into a tuple of (kind, value) pairs that compares - numeric runs as ints, so 'foo10' sorts after 'foo2'.""" - parts = [] - for part in re.split(r'(\d+)', s): - if part.isdigit(): - parts.append((0, int(part))) - else: - parts.append((1, part)) - return tuple(parts) +def check_blank_lines(ctx): + """No two consecutive blank lines, no leading/trailing blank lines + in any node body.""" + lines = ctx.lines + # Consecutive blanks + for i in range(1, len(lines)): + if lines[i].linetype == LineType.BLANK and \ + lines[i - 1].linetype == LineType.BLANK: + yield (lines[i].lineno, 'consecutive blank lines') + # Blank right after { or right before } + for i, dl in enumerate(lines): + if dl.linetype != LineType.BLANK: + continue + prev = lines[i - 1] if i > 0 else None + nxt = lines[i + 1] if i + 1 < len(lines) else None + if prev is not None and prev.linetype == LineType.NODE_OPEN: + yield (dl.lineno, 'blank line at start of node body') + if nxt is not None and nxt.linetype == LineType.NODE_CLOSE: + yield (dl.lineno, 'blank line at end of node body') def check_child_address_order(ctx): @@ -624,6 +510,172 @@ def check_child_name_order(ctx): 'child node %r out of name order' % dl.node_name) +def check_continuation_alignment(ctx): + """A multi-line property's continuation lines must align their + first non-whitespace character to the display column of: + 1. the first '<' or '"' after the '=' in the leading line, if continuation is with '<' or '"' + 2. the first value, if the continuation is still the same phandle. + Display columns are used so tab-indented .dts files (where a continuation + aligns with tabs plus spaces) are compared correctly.""" + for dl in ctx.lines: + if dl.linetype != LineType.PROPERTY: + continue + if not dl.continuations: + continue + eq = dl.raw.find('=') + if eq < 0: + continue + # First '<' or '"' after '=', but ignore comments and strip trailing + # whitespace (e.g. remaining after removing the comment) + rest = _strip_strings_and_comments(dl.raw[eq + 1:]).rstrip() + m = re.search(r'\s*([<"])', rest) + if not m: + continue + dl_value_complete = rest.endswith('",') or rest.endswith('>,') + target_col = _display_col(_strip_strings_and_comments(dl.raw[:eq + 1 + m.start(1)])) + for cont in dl.continuations: + target_offset = 0 + err_msg_explanation = 'to < or "' + if not dl_value_complete: + target_offset = 1 + err_msg_explanation = 'to the value under <' + if _display_col(cont.indent_str) != target_col + target_offset: + yield (cont.lineno, + 'continuation should align to column %d ' + '(%s)' % (target_col + target_offset + 1, err_msg_explanation)) + # Align to the value within <> or "" of continuation (so the previous line) + dl_value_complete = cont.stripped.endswith('",') or cont.stripped.endswith('>,') + + +def check_hex_case(ctx): + """Hex literals (0xN) must use lowercase digits and prefix.""" + for dl in ctx.lines: + if dl.linetype in (LineType.BLANK, LineType.COMMENT, + LineType.COMMENT_START, LineType.COMMENT_BODY, + LineType.COMMENT_END, LineType.PREPROCESSOR): + continue + for m in re.finditer(r'\b0[xX][0-9a-fA-F]+\b', dl.code): + lit = m.group(0) + if any(c.isupper() for c in lit[2:]) or lit[1] == 'X': + yield (dl.lineno, + 'hex literal %r must be lowercase' % lit) + + +def check_indent_consistent(ctx): + """All indented lines must be a multiple of the detected unit.""" + (unit, lineno) = _detect_indent_unit(ctx) + if unit is None: + return + if ctx.file_type == 'yaml': + if unit not in (' ', ' '): + return # let check_indent_unit_* report this + else: + if unit != '\t': + return + + for dl in ctx.lines: + if dl.linetype in (LineType.BLANK, LineType.PREPROCESSOR): + continue + if dl.linetype == LineType.CONTINUATION: + continue # continuations align to <, not to indent unit + if dl.linetype in (LineType.COMMENT_BODY, LineType.COMMENT_END): + continue + if not dl.indent_str: + continue + # The indent must be 'unit' repeated dl.depth times, exactly. + # NODE_CLOSE lines have depth equal to the post-decrement value, + # which matches the indent expected. + expected = unit * dl.depth + if dl.indent_str != expected: + yield (dl.lineno, + 'indent mismatch (expected depth %d * %r)' % + (dl.depth, unit)) + + +def check_indent_unit_dts(ctx): + """DTS files: 1 tab per level. Always required.""" + (unit, lineno) = _detect_indent_unit(ctx) + if unit is None: + return + if unit != '\t': + yield (lineno, 'indent unit must be 1 tab in DTS, got %r' % unit) + + +def check_indent_unit_relaxed(ctx): + """YAML examples: 2 or 4 spaces. Never tabs or other widths.""" + (unit, lineno) = _detect_indent_unit(ctx) + if unit is None: + return + if unit not in (' ', ' '): + yield (lineno, 'indent unit must be 2 or 4 spaces, got %r' % unit) + + +def check_indent_unit_strict(ctx): + """YAML: must be exactly 4 spaces. DTS: 1 tab (same as relaxed).""" + (unit, lineno) = _detect_indent_unit(ctx) + if unit is None: + return + if ctx.file_type == 'yaml': + if unit != ' ': + yield (lineno, 'indent unit must be 4 spaces in strict mode, ' + 'got %r' % unit) + + +def check_line_length(ctx): + """Lines must not exceed 80 columns; tabs count as 8 (see + _display_col).""" + for dl in ctx.lines: + if dl.linetype == LineType.BLANK: + continue + cols = _display_col(dl.raw) + if cols > 80: + yield (dl.lineno, + 'line exceeds 80 columns (%d)' % cols) + + +def check_mixed_indent_chars(ctx): + """Indent must be all-tabs, except for aligning indentation (comments + or continued lines).""" + for dl in ctx.lines: + if not dl.indent_str: + continue + if dl.linetype == LineType.PREPROCESSOR: + continue + if re.search(r' \t', dl.indent_str): + yield (dl.lineno, 'mixed tabs and spaces in indent') + if dl.indent_str.count(' ') > 7: + yield (dl.lineno, 'too many space characters in indent (more than 7)') + for cont in dl.continuations: + if not cont.indent_str: + continue + if cont.linetype == LineType.PREPROCESSOR: + continue + if re.search(r' \t', cont.indent_str): + yield (cont.lineno, 'mixed tabs and spaces in indent') + + +def check_node_close_alone(ctx): + """The closing '};' of a node must be on its own line. The + classifier accepts a canonical "}" or "};" as NODE_CLOSE; a line + that is all closures (e.g. "}; };") is still NODE_CLOSE for depth + tracking but is flagged here via dl.closures. Any other line that + still contains '};' (in code, not in strings or comments) is + mixing a node close with something else.""" + for dl in ctx.lines: + if dl.linetype == LineType.NODE_CLOSE: + if dl.closures > 1: + yield (dl.lineno, + 'closing brace must be on its own line') + continue + if dl.linetype in (LineType.BLANK, LineType.COMMENT, + LineType.COMMENT_START, LineType.COMMENT_BODY, + LineType.COMMENT_END, LineType.PREPROCESSOR): + continue + if '};' in dl.code: + yield (dl.lineno, + 'closing brace must be on its own line') + + def _property_bucket(name): """Return the canonical bucket index for a property: 0 device_type @@ -769,17 +821,40 @@ def check_property_order(ctx): (p.prop_name, prev.prop_name)) -def _strip_strings_and_comments(text): - """Remove string literals and /* */ + // comments from a single - line, replacing them with empty strings. Used so syntactic checks - (whitespace, hex case, etc.) don't false-positive on contents of - quoted strings or comments. An unclosed /* on the line is treated - as a comment running to end of line.""" - text = re.sub(r'"(?:[^"\\]|\\.)*"', '""', text) - text = re.sub(r'/\*.*?\*/', '', text) - text = re.sub(r'/\*.*$', '', text) - text = re.sub(r'//.*$', '', text) - return text +def _check_redundant_whitespace(dl): + if dl.linetype in (LineType.COMMENT, LineType.COMMENT_BODY, + LineType.COMMENT_END, LineType.COMMENT_START, + LineType.PREPROCESSOR): + return + if re.search(r'(\s\s+|\t)\{', dl.code): + yield (dl.lineno, 'extra whitespace before {') + if re.search(r':(\s\s+|\t)', dl.code): + yield (dl.lineno, 'extra whitespace after :') + if re.search(r'\s+;', dl.code): + yield (dl.lineno, 'extra whitespace before ;') + + +def check_redundant_whitespace(ctx): + """No whitespace between brackets or other code elements. + See also check_value_whitespace() for more checks.""" + for dl in ctx.lines: + yield from _check_redundant_whitespace(dl) + for cont in dl.continuations: + yield from _check_redundant_whitespace(cont) + + +def check_redundant_whitespace_strict(ctx): + """No whitespace between brackets or other code elements. + See also check_value_whitespace() for more checks.""" + for dl in ctx.lines: + if dl.linetype in (LineType.COMMENT, LineType.COMMENT_BODY, + LineType.COMMENT_END, LineType.COMMENT_START, + LineType.PREPROCESSOR): + continue + if re.search(r'(\s\s+|\t)=', dl.code): + yield (dl.lineno, 'extra whitespace before =') + if re.search(r'=(\s\s+|\t)', dl.code): + yield (dl.lineno, 'extra whitespace after =') def check_required_blank_lines(ctx): @@ -841,18 +916,49 @@ def check_required_blank_lines(ctx): between_blanks = 0 -def check_hex_case(ctx): - """Hex literals (0xN) must use lowercase digits and prefix.""" +def check_tab_in_yaml_example(ctx): + """Reject literal tabs in DTS lines when input is YAML. + + For YAML examples, indent and content must use spaces. Tabs inside + a #define value are tolerated (those are CPP macros, not DTS). + For .dts files, this rule does not apply -- tabs are required. + """ + if ctx.file_type != 'yaml': + return for dl in ctx.lines: - if dl.linetype in (LineType.BLANK, LineType.COMMENT, - LineType.COMMENT_START, LineType.COMMENT_BODY, - LineType.COMMENT_END, LineType.PREPROCESSOR): + if dl.linetype == LineType.PREPROCESSOR: continue - for m in re.finditer(r'\b0[xX][0-9a-fA-F]+\b', dl.code): - lit = m.group(0) - if any(c.isupper() for c in lit[2:]) or lit[1] == 'X': - yield (dl.lineno, - 'hex literal %r must be lowercase' % lit) + if dl.linetype == LineType.BLANK: + continue + if '\t' in dl.raw: + yield (dl.lineno, 'tab character not allowed in DTS example') + + +def check_trailing_whitespace(ctx): + for dl in ctx.lines: + if dl.raw != dl.raw.rstrip(): + yield (dl.lineno, 'trailing whitespace') + + +def check_unclosed_block_comment(ctx): + """Every /* must have a matching */ in the same block. Catches both + a comment opened on its own line (COMMENT_START) and a tail comment + opened on a PROPERTY or other code line (where in_block_comment is + set by _split_code so the next line becomes COMMENT_BODY without a + preceding COMMENT_START).""" + open_lineno = None + for dl in ctx.lines: + if dl.linetype == LineType.COMMENT_START: + open_lineno = dl.lineno + elif dl.linetype == LineType.COMMENT_END: + open_lineno = None + elif dl.linetype == LineType.COMMENT_BODY and open_lineno is None: + # Block was opened by a /* tail on a code line; report at + # the first orphan body line since the originating line is + # already classified as something else. + open_lineno = dl.lineno + if open_lineno is not None: + yield (open_lineno, 'unclosed /* block comment') def check_unit_address_format(ctx): @@ -886,6 +992,17 @@ def check_unit_address_format(ctx): break +def check_unused_labels(ctx): + """Labels defined but never referenced are clutter.""" + defined, referenced = collect_labels_and_refs(ctx.text) + for label in sorted(defined - referenced): + # Find the line where this label is defined for line-number + # reporting. + m = re.search(r'(?m)^.*\b' + re.escape(label) + r'\s*:', ctx.text) + lineno = ctx.text[:m.start()].count('\n') + 1 if m else 1 + yield (lineno, 'label %r defined but never &-referenced' % label) + + def check_value_whitespace(ctx): """A <...> cell list must have no whitespace directly after '<' or directly before '>'. Continuation lines are joined onto the @@ -914,122 +1031,6 @@ def check_value_whitespace(ctx): break -def check_node_close_alone(ctx): - """The closing '};' of a node must be on its own line. The - classifier accepts a canonical "}" or "};" as NODE_CLOSE; a line - that is all closures (e.g. "}; };") is still NODE_CLOSE for depth - tracking but is flagged here via dl.closures. Any other line that - still contains '};' (in code, not in strings or comments) is - mixing a node close with something else.""" - for dl in ctx.lines: - if dl.linetype == LineType.NODE_CLOSE: - if dl.closures > 1: - yield (dl.lineno, - 'closing brace must be on its own line') - continue - if dl.linetype in (LineType.BLANK, LineType.COMMENT, - LineType.COMMENT_START, LineType.COMMENT_BODY, - LineType.COMMENT_END, LineType.PREPROCESSOR): - continue - if '};' in dl.code: - yield (dl.lineno, - 'closing brace must be on its own line') - - -def _display_col(text): - """Visual column width of text, with tabs expanded to the next - 8-column stop, matching how printf and most editors render a - line and the kernel-wide line length convention.""" - col = 0 - for ch in text: - if ch == '\t': - col = (col // 8 + 1) * 8 - else: - col += 1 - return col - - -def check_line_length(ctx): - """Lines must not exceed 80 columns; tabs count as 8 (see - _display_col).""" - for dl in ctx.lines: - if dl.linetype == LineType.BLANK: - continue - cols = _display_col(dl.raw) - if cols > 80: - yield (dl.lineno, - 'line exceeds 80 columns (%d)' % cols) - - -def check_continuation_alignment(ctx): - """A multi-line property's continuation lines must align their - first non-whitespace character to the display column of: - 1. the first '<' or '"' after the '=' in the leading line, if continuation is with '<' or '"' - 2. the first value, if the continuation is still the same phandle. - Display columns are used so tab-indented .dts files (where a continuation - aligns with tabs plus spaces) are compared correctly.""" - for dl in ctx.lines: - if dl.linetype != LineType.PROPERTY: - continue - if not dl.continuations: - continue - eq = dl.raw.find('=') - if eq < 0: - continue - # First '<' or '"' after '=', but ignore comments and strip trailing - # whitespace (e.g. remaining after removing the comment) - rest = _strip_strings_and_comments(dl.raw[eq + 1:]).rstrip() - m = re.search(r'\s*([<"])', rest) - if not m: - continue - dl_value_complete = rest.endswith('",') or rest.endswith('>,') - target_col = _display_col(_strip_strings_and_comments(dl.raw[:eq + 1 + m.start(1)])) - for cont in dl.continuations: - target_offset = 0 - err_msg_explanation = 'to < or "' - if not dl_value_complete: - target_offset = 1 - err_msg_explanation = 'to the value under <' - if _display_col(cont.indent_str) != target_col + target_offset: - yield (cont.lineno, - 'continuation should align to column %d ' - '(%s)' % (target_col + target_offset + 1, err_msg_explanation)) - # Align to the value within <> or "" of continuation (so the previous line) - dl_value_complete = cont.stripped.endswith('",') or cont.stripped.endswith('>,') - - -def check_unclosed_block_comment(ctx): - """Every /* must have a matching */ in the same block. Catches both - a comment opened on its own line (COMMENT_START) and a tail comment - opened on a PROPERTY or other code line (where in_block_comment is - set by _split_code so the next line becomes COMMENT_BODY without a - preceding COMMENT_START).""" - open_lineno = None - for dl in ctx.lines: - if dl.linetype == LineType.COMMENT_START: - open_lineno = dl.lineno - elif dl.linetype == LineType.COMMENT_END: - open_lineno = None - elif dl.linetype == LineType.COMMENT_BODY and open_lineno is None: - # Block was opened by a /* tail on a code line; report at - # the first orphan body line since the originating line is - # already classified as something else. - open_lineno = dl.lineno - if open_lineno is not None: - yield (open_lineno, 'unclosed /* block comment') - - -def check_unused_labels(ctx): - """Labels defined but never referenced are clutter.""" - defined, referenced = collect_labels_and_refs(ctx.text) - for label in sorted(defined - referenced): - # Find the line where this label is defined for line-number - # reporting. - m = re.search(r'(?m)^.*\b' + re.escape(label) + r'\s*:', ctx.text) - lineno = ctx.text[:m.start()].count('\n') + 1 if m else 1 - yield (lineno, 'label %r defined but never &-referenced' % label) - - # --- registry -------------------------------------------------------------- RULES = [ -- 2.53.0