mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-20 17:25:52 +03:00
feat(cfe-patch-method,cfe-diff,cfe-validate): распознавание английских ключевых слов BSL
Платформа принимает встроенный язык в двух написаниях, а навыки читали только русское: в расширении на английском перехватчики и блоки правок не находились, и отчёт выглядел чистым. -Check молчал, cfe-diff не показывал вставки, счётчик контролируемых методов в cfe-validate давал ноль. Пары ключевых слов сверены по таблицам строк платформы (backbas.dll, bsl.dll), а не по памяти. Разбор стал двуязычным везде, где навык читает модуль: аннотации, объявления и Конец*, Знач, директивы контекста, препроцессор в цепочке обрамления, поиск региона при переиспользовании. Маркеры правок распознаются по началу строки, а не сравнением целой строки, — заодно перестал теряться хвостовой комментарий (#Вставка // старая логика). Эмиссия подчиняется одному правилу: отдаём тем языком, который прочитали. Генерация берёт язык метода-источника, -Actualize — язык переписываемого перехватчика, чтобы актуализация не превращала чужие Procedure и #Insert в русские. Комментарии и вывод в консоль остаются русскими. Таблица ключевых слов и разбор аннотаций скопированы в три навыка и заведены семьями в реестре анти-дрейфа: разъехавшаяся пара слов дала бы ровно тот же тихий ложно-чистый отчёт. Проверено платформой: расширение с &ChangeAndValidate принимается (/CheckCanApplyConfigurationExtensions), после дрейфа оригинала отвергается, после -Actualize принимается снова. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e2c7f38518
commit
1b2c9388e8
@@ -1,4 +1,4 @@
|
||||
# cfe-diff v1.5 — Analyze and compare 1C configuration extension (CFE)
|
||||
# cfe-diff v1.6 — Analyze and compare 1C configuration extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
[CmdletBinding(PositionalBinding=$false)]
|
||||
param(
|
||||
@@ -162,24 +162,90 @@ function Get-BslFiles {
|
||||
return $bslFiles
|
||||
}
|
||||
|
||||
# --- Helper: parse interceptors from .bsl ---
|
||||
# Built-in language keywords in both spellings. The platform accepts either one in any module
|
||||
# (pairs taken from the platform string tables), so a module written in English is ordinary
|
||||
# source, not a broken one: we must read both and emit the spelling we read.
|
||||
function Get-BslKeywords {
|
||||
return @{
|
||||
ru = @{
|
||||
Async="Асинх"; Proc="Процедура"; EndProc="КонецПроцедуры"
|
||||
Func="Функция"; EndFunc="КонецФункции"; Val="Знач"
|
||||
Region="Область"; EndRegion="КонецОбласти"
|
||||
If="Если"; Then="Тогда"; ElsIf="ИначеЕсли"; Else="Иначе"; EndIf="КонецЕсли"
|
||||
And="И"; Not="НЕ"
|
||||
Insert="Вставка"; EndInsert="КонецВставки"; Delete="Удаление"; EndDelete="КонецУдаления"
|
||||
Before="Перед"; After="После"; Around="Вместо"; Control="ИзменениеИКонтроль"
|
||||
Proceed="ПродолжитьВызов"; Return="Возврат"
|
||||
# Not a keyword: name of the local the generated Instead-stub declares. Lives here so
|
||||
# that the language of emitted text is decided in exactly one place.
|
||||
ResultVar="Результат"
|
||||
Directives=@("НаКлиенте", "НаСервере", "НаСервереБезКонтекста", "НаКлиентеНаСервереБезКонтекста", "НаКлиентеНаСервере")
|
||||
}
|
||||
en = @{
|
||||
Async="Async"; Proc="Procedure"; EndProc="EndProcedure"
|
||||
Func="Function"; EndFunc="EndFunction"; Val="Val"
|
||||
Region="Region"; EndRegion="EndRegion"
|
||||
If="If"; Then="Then"; ElsIf="ElsIf"; Else="Else"; EndIf="EndIf"
|
||||
And="And"; Not="Not"
|
||||
Insert="Insert"; EndInsert="EndInsert"; Delete="Delete"; EndDelete="EndDelete"
|
||||
Before="Before"; After="After"; Around="Around"; Control="ChangeAndValidate"
|
||||
Proceed="ProceedWithCall"; Return="Return"
|
||||
ResultVar="Result"
|
||||
Directives=@("AtClient", "AtServer", "AtServerNoContext", "AtClientAtServerNoContext", "AtClientAtServer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$script:bslKw = Get-BslKeywords
|
||||
|
||||
# Edit marker kind at the start of a line: Insert/Delete/EndInsert/EndDelete, $null if none.
|
||||
# The marker owns the start of the line only — a trailing comment does not bother the platform
|
||||
# and must not bother us either.
|
||||
function Get-BslMarkerKind {
|
||||
param([string]$line)
|
||||
$kw = $script:bslKw
|
||||
foreach ($k in @("EndInsert", "EndDelete", "Insert", "Delete")) {
|
||||
if ($line -match ('^\s*#(?:' + $kw.ru[$k] + '|' + $kw.en[$k] + ')\b')) { return $k }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
# Parse interceptor annotations present in a module. Type is normalized to the Russian spelling
|
||||
# so everything downstream stays single-language; Raw keeps what the file actually says.
|
||||
function Get-Interceptors {
|
||||
param($lines)
|
||||
$kw = $script:bslKw
|
||||
$keys = @("Before", "After", "Control", "Around")
|
||||
$alt = @()
|
||||
$norm = @{}
|
||||
foreach ($k in $keys) {
|
||||
$alt += $kw.ru[$k]; $alt += $kw.en[$k]
|
||||
$norm[$kw.ru[$k].ToLower()] = $kw.ru[$k]
|
||||
$norm[$kw.en[$k].ToLower()] = $kw.ru[$k]
|
||||
}
|
||||
$re = '^&(' + ($alt -join '|') + ')\("([^"]+)"\)'
|
||||
$result = @()
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
$t = $lines[$i].Trim()
|
||||
if ($t -match $re) {
|
||||
$raw = $Matches[1]
|
||||
$result += @{ Type = $norm[$raw.ToLower()]; Raw = $raw; Method = $Matches[2]; Line = $i }
|
||||
}
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
# --- Helper: read a .bsl and parse its interceptors ---
|
||||
function Get-FileInterceptors {
|
||||
param([string]$bslPath)
|
||||
|
||||
if (-not (Test-Path $bslPath)) { return @() }
|
||||
$lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8)
|
||||
$interceptors = @()
|
||||
$i = 0
|
||||
while ($i -lt $lines.Count) {
|
||||
$line = $lines[$i].Trim()
|
||||
if ($line -match '^&(Перед|После|ИзменениеИКонтроль|Вместо)\("([^"]+)"\)') {
|
||||
$type = $Matches[1]
|
||||
$method = $Matches[2]
|
||||
$interceptors += @{ Type = $type; Method = $method; Line = $i + 1; File = $bslPath }
|
||||
}
|
||||
$i++
|
||||
$result = @()
|
||||
foreach ($ic in (Get-Interceptors $lines)) {
|
||||
$result += @{ Type = $ic.Type; Raw = $ic.Raw; Method = $ic.Method; Line = $ic.Line + 1; File = $bslPath }
|
||||
}
|
||||
return $interceptors
|
||||
return $result
|
||||
}
|
||||
|
||||
# --- Helper: extract #Вставка blocks from .bsl ---
|
||||
@@ -194,12 +260,13 @@ function Get-InsertionBlocks {
|
||||
$startLine = 0
|
||||
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
$line = $lines[$i].Trim()
|
||||
if ($line -eq "#Вставка") {
|
||||
$line = $lines[$i]
|
||||
$kind = Get-BslMarkerKind $line
|
||||
if ($kind -eq "Insert") {
|
||||
$inBlock = $true
|
||||
$blockLines = @()
|
||||
$startLine = $i + 1
|
||||
} elseif ($line -eq "#КонецВставки" -and $inBlock) {
|
||||
} elseif ($kind -eq "EndInsert" -and $inBlock) {
|
||||
$inBlock = $false
|
||||
$blocks += @{
|
||||
StartLine = $startLine
|
||||
@@ -298,10 +365,10 @@ if ($Mode -eq "A") {
|
||||
$bslFiles = Get-BslFiles $obj.Type $obj.Name
|
||||
foreach ($bsl in $bslFiles) {
|
||||
$relPath = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/")
|
||||
$interceptors = Get-Interceptors $bsl
|
||||
$interceptors = Get-FileInterceptors $bsl
|
||||
if ($interceptors.Count -gt 0) {
|
||||
foreach ($ic in $interceptors) {
|
||||
Write-Host " &$($ic.Type)(`"$($ic.Method)`") — line $($ic.Line) in $relPath"
|
||||
Write-Host " &$($ic.Raw)(`"$($ic.Method)`") — line $($ic.Line) in $relPath"
|
||||
}
|
||||
} else {
|
||||
Write-Host " $relPath (no interceptors)"
|
||||
@@ -412,7 +479,7 @@ if ($Mode -eq "B") {
|
||||
# Find .bsl files with &ИзменениеИКонтроль
|
||||
$bslFiles = Get-BslFiles $obj.Type $obj.Name
|
||||
foreach ($bsl in $bslFiles) {
|
||||
$interceptors = Get-Interceptors $bsl
|
||||
$interceptors = Get-FileInterceptors $bsl
|
||||
$macInterceptors = @($interceptors | Where-Object { $_.Type -eq "ИзменениеИКонтроль" })
|
||||
|
||||
if ($macInterceptors.Count -eq 0) { continue }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cfe-diff v1.5 — Analyze and compare 1C configuration extension (CFE)
|
||||
# cfe-diff v1.6 — Analyze and compare 1C configuration extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -165,34 +165,95 @@ def get_bsl_files(obj_type, obj_name, extension_path):
|
||||
return bsl_files
|
||||
|
||||
|
||||
# --- Helper: parse interceptors from .bsl ---
|
||||
# Built-in language keywords in both spellings. The platform accepts either one in any module
|
||||
# (pairs taken from the platform string tables), so a module written in English is ordinary
|
||||
# source, not a broken one: we must read both and emit the spelling we read.
|
||||
def bsl_keywords():
|
||||
return {
|
||||
"ru": {
|
||||
"Async": "Асинх", "Proc": "Процедура", "EndProc": "КонецПроцедуры",
|
||||
"Func": "Функция", "EndFunc": "КонецФункции", "Val": "Знач",
|
||||
"Region": "Область", "EndRegion": "КонецОбласти",
|
||||
"If": "Если", "Then": "Тогда", "ElsIf": "ИначеЕсли", "Else": "Иначе", "EndIf": "КонецЕсли",
|
||||
"And": "И", "Not": "НЕ",
|
||||
"Insert": "Вставка", "EndInsert": "КонецВставки", "Delete": "Удаление", "EndDelete": "КонецУдаления",
|
||||
"Before": "Перед", "After": "После", "Around": "Вместо", "Control": "ИзменениеИКонтроль",
|
||||
"Proceed": "ПродолжитьВызов", "Return": "Возврат",
|
||||
# Not a keyword: name of the local the generated Instead-stub declares. Lives here so
|
||||
# that the language of emitted text is decided in exactly one place.
|
||||
"ResultVar": "Результат",
|
||||
"Directives": ["НаКлиенте", "НаСервере", "НаСервереБезКонтекста", "НаКлиентеНаСервереБезКонтекста", "НаКлиентеНаСервере"],
|
||||
},
|
||||
"en": {
|
||||
"Async": "Async", "Proc": "Procedure", "EndProc": "EndProcedure",
|
||||
"Func": "Function", "EndFunc": "EndFunction", "Val": "Val",
|
||||
"Region": "Region", "EndRegion": "EndRegion",
|
||||
"If": "If", "Then": "Then", "ElsIf": "ElsIf", "Else": "Else", "EndIf": "EndIf",
|
||||
"And": "And", "Not": "Not",
|
||||
"Insert": "Insert", "EndInsert": "EndInsert", "Delete": "Delete", "EndDelete": "EndDelete",
|
||||
"Before": "Before", "After": "After", "Around": "Around", "Control": "ChangeAndValidate",
|
||||
"Proceed": "ProceedWithCall", "Return": "Return",
|
||||
"ResultVar": "Result",
|
||||
"Directives": ["AtClient", "AtServer", "AtServerNoContext", "AtClientAtServerNoContext", "AtClientAtServer"],
|
||||
},
|
||||
}
|
||||
|
||||
def get_interceptors(bsl_path):
|
||||
|
||||
BSL_KW = bsl_keywords()
|
||||
|
||||
|
||||
# Edit marker kind at the start of a line: Insert/Delete/EndInsert/EndDelete, None if none.
|
||||
# The marker owns the start of the line only — a trailing comment does not bother the platform
|
||||
# and must not bother us either.
|
||||
def bsl_marker_kind(line):
|
||||
kw = BSL_KW
|
||||
for k in ("EndInsert", "EndDelete", "Insert", "Delete"):
|
||||
if re.match(r'^\s*#(?:' + kw["ru"][k] + '|' + kw["en"][k] + r')\b', line, re.IGNORECASE):
|
||||
return k
|
||||
return None
|
||||
|
||||
|
||||
# Parse interceptor annotations present in a module. Type is normalized to the Russian spelling
|
||||
# so everything downstream stays single-language; raw keeps what the file actually says.
|
||||
def get_interceptors(lines):
|
||||
kw = BSL_KW
|
||||
keys = ("Before", "After", "Control", "Around")
|
||||
alt = []
|
||||
norm = {}
|
||||
for k in keys:
|
||||
alt.append(kw["ru"][k])
|
||||
alt.append(kw["en"][k])
|
||||
norm[kw["ru"][k].lower()] = kw["ru"][k]
|
||||
norm[kw["en"][k].lower()] = kw["ru"][k]
|
||||
pat = re.compile(r'^&(' + '|'.join(alt) + r')\("([^"]+)"\)', re.IGNORECASE)
|
||||
result = []
|
||||
for i in range(len(lines)):
|
||||
m = pat.match(lines[i].strip())
|
||||
if m:
|
||||
raw = m.group(1)
|
||||
result.append({"type": norm[raw.lower()], "raw": raw, "method": m.group(2), "line": i})
|
||||
return result
|
||||
|
||||
|
||||
# --- Helper: read a .bsl and parse its interceptors ---
|
||||
|
||||
def get_file_interceptors(bsl_path):
|
||||
if not os.path.isfile(bsl_path):
|
||||
return []
|
||||
|
||||
with open(bsl_path, "r", encoding="utf-8-sig") as fh:
|
||||
lines = fh.readlines()
|
||||
|
||||
interceptors = []
|
||||
pattern = re.compile(r'^&(\u041f\u0435\u0440\u0435\u0434|\u041f\u043e\u0441\u043b\u0435|\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435\u0418\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u044c|\u0412\u043c\u0435\u0441\u0442\u043e)\("([^"]+)"\)')
|
||||
# The above is: ^&(Перед|После|ИзменениеИКонтроль|Вместо)\("([^"]+)"\)
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
stripped = line.strip()
|
||||
m = pattern.match(stripped)
|
||||
if m:
|
||||
interceptors.append({
|
||||
"Type": m.group(1),
|
||||
"Method": m.group(2),
|
||||
"Line": i + 1,
|
||||
"File": bsl_path,
|
||||
})
|
||||
|
||||
return interceptors
|
||||
return [{
|
||||
"Type": ic["type"],
|
||||
"Raw": ic["raw"],
|
||||
"Method": ic["method"],
|
||||
"Line": ic["line"] + 1,
|
||||
"File": bsl_path,
|
||||
} for ic in get_interceptors(lines)]
|
||||
|
||||
|
||||
# --- Helper: extract #Вставка blocks from .bsl ---
|
||||
# --- Helper: extract insert-marker blocks from .bsl ---
|
||||
|
||||
def get_insertion_blocks(bsl_path):
|
||||
if not os.path.isfile(bsl_path):
|
||||
@@ -207,14 +268,12 @@ def get_insertion_blocks(bsl_path):
|
||||
start_line = 0
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
stripped = line.strip()
|
||||
if stripped == "\u0023\u0412\u0441\u0442\u0430\u0432\u043a\u0430":
|
||||
# #Вставка
|
||||
kind = bsl_marker_kind(line)
|
||||
if kind == "Insert":
|
||||
in_block = True
|
||||
block_lines = []
|
||||
start_line = i + 1
|
||||
elif stripped == "\u0023\u041a\u043e\u043d\u0435\u0446\u0412\u0441\u0442\u0430\u0432\u043a\u0438" and in_block:
|
||||
# #КонецВставки
|
||||
elif kind == "EndInsert" and in_block:
|
||||
in_block = False
|
||||
blocks.append({
|
||||
"StartLine": start_line,
|
||||
@@ -319,10 +378,10 @@ def mode_a(objects, extension_path):
|
||||
bsl_files = get_bsl_files(obj["Type"], obj["Name"], extension_path)
|
||||
for bsl in bsl_files:
|
||||
rel_path = bsl.replace(extension_path, "").lstrip("\\/")
|
||||
interceptor_list = get_interceptors(bsl)
|
||||
interceptor_list = get_file_interceptors(bsl)
|
||||
if len(interceptor_list) > 0:
|
||||
for ic in interceptor_list:
|
||||
print(f' &{ic["Type"]}("{ic["Method"]}") \u2014 line {ic["Line"]} in {rel_path}')
|
||||
print(f' &{ic["Raw"]}("{ic["Method"]}") \u2014 line {ic["Line"]} in {rel_path}')
|
||||
else:
|
||||
print(f" {rel_path} (no interceptors)")
|
||||
|
||||
@@ -434,7 +493,7 @@ def mode_b(objects, extension_path, config_path):
|
||||
# Find .bsl files with &ИзменениеИКонтроль
|
||||
bsl_files = get_bsl_files(obj["Type"], obj["Name"], extension_path)
|
||||
for bsl in bsl_files:
|
||||
interceptor_list = get_interceptors(bsl)
|
||||
interceptor_list = get_file_interceptors(bsl)
|
||||
mac_interceptors = [ic for ic in interceptor_list if ic["Type"] == "\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435\u0418\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u044c"]
|
||||
|
||||
if len(mac_interceptors) == 0:
|
||||
|
||||
Reference in New Issue
Block a user