fix(cfe-patch-method): -Check ловит расхождение списка параметров

-Check сравнивал только тело и сигнатуру не смотрел вовсе. Если поставщик добавил
методу параметр, а тело не изменил (новый параметр часто ещё не используется),
платформа перехватчик отвергает — «Список параметров метода "X" не соответствует
методу "Y"» — а -Check говорил АКТУАЛЕН, и починка не запускалась.

Замер на стенде (8.3.24, /CheckCanApplyConfigurationExtensions + рантайм): платформа
сверяет только ЧИСЛО параметров. Имена, значения по умолчанию и лишний Экспорт не
сравнивает; дефолты копии вдобавок не действуют — берутся из оригинала. Поэтому
сверяем количество и ничего сверх того, иначе получили бы ложный дрейф там, где
платформа молчит.

Число параметров считается существующим Split-TopLevel по ParamsText обеих сторон;
при расхождении метод идёт обычной веткой дрейфа со статусом ДРЕЙФ и причиной
«список параметров: в оригинале N, в перехватчике M». -Actualize починку уже умел:
он собирает строку сигнатуры заново из оригинала — не запускался только потому, что
-Check не звал.

Кейсы: расхождение числа (ДРЕЙФ), переименованные параметры с другим дефолтом
(АКТУАЛЕН, как у платформы) и -Actualize, переписывающий список параметров.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoAJmoNbgWKobA7JGgN5S3
This commit is contained in:
Nick Shirokov
2026-08-30 19:17:18 +03:00
co-authored by Claude Opus 5
parent 533dd0ded4
commit d4832ce363
25 changed files with 1083 additions and 5 deletions
@@ -1,4 +1,4 @@
# cfe-patch-method v2.10 — Source-aware method interceptor for 1C extension (CFE)
# cfe-patch-method v2.11 — Source-aware method interceptor for 1C extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
@@ -370,6 +370,14 @@ function Get-ControlKey {
return (@($lines | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' }) -join "`n")
}
# Parameter count of a signature params text. The platform compares only the number of
# parameters of a &ИзменениеИКонтроль copy (names and default values it ignores).
function Get-ParamCount {
param([string]$paramsText)
if ([string]::IsNullOrWhiteSpace($paramsText)) { return 0 }
return @(Split-TopLevel $paramsText | Where-Object { $_.Trim() -ne '' }).Count
}
# Reconstruct v1 body and edit ops from a marked body
function Parse-MarkedBody {
param($bodyLines)
@@ -659,7 +667,14 @@ function Invoke-Resync {
$v1norm = @($v1 | ForEach-Object { Get-Normalized $_ })
$v2norm = @($v2 | ForEach-Object { Get-Normalized $_ })
if ([string]::Equals((Get-ControlKey $v1), (Get-ControlKey $v2), 'Ordinal')) {
# Signature: the platform rejects the interceptor when the parameter COUNT differs, so a
# body-only comparison would miss a vendor-added parameter. Names/defaults it ignores.
$extParamCount = Get-ParamCount $sig.ParamsText
$srcParamCount = Get-ParamCount $method.ParamsText
$paramsDrift = ($extParamCount -ne $srcParamCount)
$paramsReason = if ($paramsDrift) { "список параметров: в оригинале $srcParamCount, в перехватчике $extParamCount" } else { '' }
if (-not $paramsDrift -and [string]::Equals((Get-ControlKey $v1), (Get-ControlKey $v2), 'Ordinal')) {
return @{ Id = $methodId; Status = 'АКТУАЛЕН'; ExtBsl = $extBsl }
}
@@ -709,7 +724,9 @@ function Invoke-Resync {
if ($ReportOnly) {
$st = if ($disputed.Count -gt 0) { 'КОНФЛИКТ' } elseif ($transferred.Count -eq 0 -and $absorbed.Count -gt 0) { 'ПЕРЕНЕСЕНО В ОСНОВНУЮ' } else { 'ДРЕЙФ' }
if ($paramsDrift -and $st -eq 'ПЕРЕНЕСЕНО В ОСНОВНУЮ') { $st = 'ДРЕЙФ' }
$rsn = if ($disputed.Count -gt 0) { Get-ResyncConflictReason $disputed } elseif ($st -eq 'ПЕРЕНЕСЕНО В ОСНОВНУЮ') { 'все правки уже в основной конфигурации' } else { '' }
if ($paramsDrift) { $rsn = if ($rsn) { "$paramsReason; $rsn" } else { $paramsReason } }
return @{ Id = $methodId; Status = $st; ExtBsl = $extBsl; Transferred = $transferred.Count; Absorbed = $absorbed.Count; Disputed = $disputed.Count; Reason = $rsn; AbsorbedNotes = $absorbedNotes }
}
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# cfe-patch-method v2.10 — Source-aware method interceptor for 1C extension (CFE)
# cfe-patch-method v2.11 — Source-aware method interceptor for 1C extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -498,6 +498,14 @@ def control_key(lines):
return "\n".join([k for k in (x.strip() for x in lines) if k != ""])
# Parameter count of a signature params text. The platform compares only the number of
# parameters of a &ИзменениеИКонтроль copy (names and default values it ignores).
def param_count(params_text):
if not params_text or not params_text.strip():
return 0
return len([p for p in split_top_level(params_text) if p.strip()])
def parse_marked_body(body_lines):
v1 = []
ops = []
@@ -1136,7 +1144,7 @@ def resync_one(ext_bsl, ext_lines, dup, method, logical_module, conflict_folder,
sig = read_signature(ext_lines, sig_line_idx)
if not sig:
return {"id": method_id, "status": "ОШИБКА", "ext_bsl": ext_bsl, "reason": "не разобрать сигнатуру"}
_params, sig_end = sig
ext_params_text, sig_end = sig
is_func = bool(re.match(r'^\s*(?:Асинх\s+)?Функция\b', ext_lines[sig_line_idx], re.IGNORECASE))
end_re = re.compile(r'^\s*КонецФункции\b' if is_func else r'^\s*КонецПроцедуры\b', re.IGNORECASE)
block_end = -1
@@ -1153,7 +1161,15 @@ def resync_one(ext_bsl, ext_lines, dup, method, logical_module, conflict_folder,
v1norm = [normalize(x) for x in v1]
v2norm = [normalize(x) for x in v2]
if control_key(v1) == control_key(v2):
# Signature: the platform rejects the interceptor when the parameter COUNT differs, so a
# body-only comparison would miss a vendor-added parameter. Names/defaults it ignores.
ext_param_count = param_count(ext_params_text)
src_param_count = param_count(method["params_text"])
params_drift = ext_param_count != src_param_count
params_reason = ("список параметров: в оригинале %d, в перехватчике %d"
% (src_param_count, ext_param_count)) if params_drift else ""
if not params_drift and control_key(v1) == control_key(v2):
return {"id": method_id, "status": "АКТУАЛЕН", "ext_bsl": ext_bsl}
insert_top = []; insert_after = {}; del_start = set(); del_end = set(); disputed = []; transferred = 0; absorbed = 0; absorbed_notes = []
@@ -1215,7 +1231,11 @@ def resync_one(ext_bsl, ext_lines, dup, method, logical_module, conflict_folder,
st = "ПЕРЕНЕСЕНО В ОСНОВНУЮ"
else:
st = "ДРЕЙФ"
if params_drift and st == "ПЕРЕНЕСЕНО В ОСНОВНУЮ":
st = "ДРЕЙФ"
rsn = conflict_reason(disputed) if disputed else ("все правки уже в основной конфигурации" if st == "ПЕРЕНЕСЕНО В ОСНОВНУЮ" else "")
if params_drift:
rsn = ("%s; %s" % (params_reason, rsn)) if rsn else params_reason
return {"id": method_id, "status": st, "ext_bsl": ext_bsl, "transferred": transferred,
"absorbed": absorbed, "disputed": len(disputed), "reason": rsn, "absorbed_notes": absorbed_notes}