# BSL-контексты и глобальные методы: руководство разработчика Этот документ — практическая инструкция по добавлению в OneScript новых BSL-контекстов (классов), методов и свойств, а также глобальных методов. Здесь собраны готовые сниппеты, чек-лист и ссылки на ключевые места в исходниках. См. также [`docs/developer_docs.md`](developer_docs.md) — карта компонентов и «куда лезть» при доработках. ## Содержание 1. [Что такое BSL-контекст](#1-что-такое-bsl-контекст) 2. [Добавление нового BSL-класса (контекста)](#2-добавление-нового-bsl-класса-контекста) 3. [Добавление свойства](#3-добавление-свойства) 4. [Добавление метода](#4-добавление-метода) 5. [Создание глобального контекста и глобальных методов](#5-создание-глобального-контекста-и-глобальных-методов) 6. [Регистрация библиотек и package-loader.os](#6-регистрация-библиотек-и-package-loaderos) 7. [i18n для API (двуязычные имена)](#7-i18n-для-api-двуязычные-имена) 8. [Депрекейшен и предупреждения](#8-депрекейшен-и-предупреждения) 9. [Тестирование (C# и BSL)](#9-тестирование-c-и-bsl) 10. [Документация (OneScriptDocumenter)](#10-документация-onescriptdocumenter) 11. [Безопасность](#11-безопасность) 12. [Чек-лист готовности](#12-чек-лист-готовности) ## 1. Что такое BSL-контекст - Контекст — это .NET-класс, методы и свойства которого доступны из BSL. Экземпляр контекста может создаваться оператором `Новый` (класс-контекст) или предоставляться глобально (глобальный контекст). - Отражение и метаданные описываются атрибутами: - `[ContextClass("РусИмя", "EngName")]` — класс-контекст; - `[ContextMethod("РусИмя", "EngName")]` — метод (процедура/функция); - `[ContextProperty("РусИмя", "EngName", CanRead = true, CanWrite = false, ...)]` — свойство; - `[GlobalContext(...)]` — глобальный контекст; - `[ScriptConstructor]` — фабричный метод для создания объектов через `Новый`. - Двуязычные имена обязательны: все элементы публичного API должны иметь пару имён Рус/Eng. Где в коде смотреть: - Атрибуты и метаданные: `src/OneScript.Core/Contexts/*`. - Базовые помощники контекстов: `src/ScriptEngine/Machine/Contexts/*`. - База глобальных контекстов: `GlobalContextBase` — `src/ScriptEngine/Machine/Contexts/GlobalContextBase.cs`. ## 2. Добавление нового BSL-класса (контекста) ### Минимальный шаблон ```csharp using OneScript.Contexts; // ContextClass, ContextMethod, ContextProperty, ScriptConstructor using OneScript.Execution; // IBslProcess using OneScript.Types; // TypeActivationContext using ScriptEngine.Machine; // IValue, ValueFactory using ScriptEngine.Machine.Contexts; // AutoContext [ContextClass("ПримерКласс", "SampleClass")] public class SampleClass : AutoContext { // Конструктор для BSL: Новый ПримерКласс() [ScriptConstructor(Name = "Без параметров")] public static SampleClass Ctor(TypeActivationContext ctx) => new SampleClass(); // Свойство только для чтения [ContextProperty("Версия", "Version", CanWrite = false)] public IValue Version => ValueFactory.Create("1.0"); // Процедура с доступом к bsl-процессу (можно запускать BSL-код из C#) [ContextMethod("Сообщить", "Message")] public void Message(IBslProcess process, IValue text) { // вызов bsl-метода в том же стеке вызовов, что и у переданного процесса process.Run(/*...*/); } // Функция с возвратом значения [ContextMethod("Сложить", "Add")] public IValue Add(IValue a, IValue b) { var sum = a.AsNumber() + b.AsNumber(); return ValueFactory.Create(sum); } } ``` ### Комментарии к шаблону - Наследуемся от `AutoContext` — это стандартная база для классов-контекстов. - `[ScriptConstructor]` — статический фабричный метод, возможно принимающий `TypeActivationContext`. Можно объявить несколько перегрузок для разных сигнатур конструктора. - `IBslProcess` можно внедрять первым параметром метода, чтобы получить доступ к сервисам/окружению выполнения. - Возвраты: - **Процедура** — метод без возвращаемого значения (`void`). - **Функция** — возвращает `IValue` или конвертируемый тип C# (см. `ContextValuesMarshaller`). ### Регистрация в движке При старте `ContextDiscoverer` (`src/ScriptEngine/Machine/Contexts/ContextDiscoverer.cs`) сканирует подключённые сборки и автоматически регистрирует все классы, помеченные атрибутами `ContextClass`, `GlobalContext` и `EnumerationType`. Дополнительная ручная регистрация при этом не требуется. ## 3. Добавление свойства ```csharp [ContextProperty("Порог", "Threshold", CanRead = true, CanWrite = true)] public IValue Threshold { get => ValueFactory.Create(_threshold); set => _threshold = value.AsNumber(); } private decimal _threshold = 0m; ``` Заметки: - `CanRead`/`CanWrite` управляют доступностью геттера и сеттера из BSL. Если параметры не указаны, используется наличие стандартных `get`/`set` у свойства. - Маршаллинг значения свойства автоматический через `ContextValuesMarshaller`. - Для значений, которые часто читаются и не меняются, имеет смысл вычислять и кешировать `IValue` в поле, а не пересоздавать его в геттере. ## 4. Добавление метода ```csharp // Функция, возвращающая удвоенное значение [ContextMethod("УдвоитьЧисло", "DoubleNumber")] public int DoubleNumber(int number) { return number * 2; } ``` Значения параметров и результат метода будут автоматически сконвертированы из типов C# в типы BSL. Заметки: - **Передача аргумента по ссылке** — используйте тип `IVariable` в сигнатуре метода. В него можно присвоить новое значение через `.Value`. - **Передача по значению** — используйте типы C# напрямую, если они поддерживаются маршаллером, или `IValue`. - **Необязательные параметры** — задавайте через значения по умолчанию C# (`int count = 0`, `string mode = null`). Эти значения будут видны вызывающему коду BSL. - **Перегрузки** — поддерживаются: можно объявить несколько методов с одним и тем же `ContextMethod`-именем, но с разными сигнатурами. ## 5. Создание глобального контекста и глобальных методов ### Глобальный контекст ```csharp using OneScript.Contexts; // GlobalContext, ContextMethod, IAttachableContext using ScriptEngine.Machine; // IValue, ValueFactory using ScriptEngine.Machine.Contexts; // GlobalContextBase [GlobalContext(Category = "Мои функции")] public class MyGlobals : GlobalContextBase { // Фабрика экземпляра для внедрения в глобальную область public static IAttachableContext CreateInstance() => new MyGlobals(); [ContextMethod("МояФункция", "MyFunc")] public IValue MyFunc(IValue x) { return ValueFactory.Create(x.ToString().Length); } } ``` Заметки: - По умолчанию глобальные контексты регистрируются автоматически (`ManualRegistration = false`). Достаточно, чтобы сборка с контекстом была подключена к окружению. - При необходимости можно внедрить контекст вручную через `HostedScriptEngine.InjectObject` или `IRuntimeEnvironment.InjectObject`. ### Добавление метода в существующий глобальный контекст - Например, в `StandardGlobalContext` (`src/OneScript.StandardLibrary/StandardGlobalContext.cs`): добавьте `[ContextMethod]` в соответствующий класс и реализуйте логику. - Внимание: изменение публичного API стандартной библиотеки требует обсуждения с мэйнтейнерами. ## 6. Регистрация библиотек и package-loader.os - `HostedScript` ищет библиотеку и вызывает `package-loader.os` (дефолтный или кастомный из самой библиотеки). - Основные операции загрузчика (см. `src/ScriptEngine.HostedScript/LibraryLoader.cs`): - `ДобавитьКласс`/`AddClass("path", "ИмяКласса")` — регистрирует новый BSL-тип; - `ДобавитьМодуль`/`AddModule("path", "ИмяМодуля")` — подключает модуль как глобальный; - `ДобавитьМакет`/`AddTemplate` — регистрирует шаблон. - Для отладки разрешения зависимостей полезно посмотреть `FileSystemDependencyResolver.cs` — порядок поиска и защита от циклических зависимостей. ## 7. i18n для API (двуязычные имена) - Каждый публичный элемент API (класс, метод, свойство, перечисление, элемент перечисления) должен иметь две формы имени — русскую и английскую — задаваемые в атрибутах: ```csharp [ContextClass("ИмяНаРусском", "EnglishName")] [ContextMethod("ВыполнитьДействие", "Perform")] [ContextProperty("Размер", "Size")] ``` - Имена должны быть согласованы с уже существующим API: смотрите `OneScript.StandardLibrary` и сгенерированный справочник (см. раздел [Документация](#10-документация-onescriptdocumenter)). - Имена параметров и текст исключений переводить не нужно — они остаются на одном языке (как правило, на русском, в соответствии с привычным стилем 1С). - Категории глобальных контекстов (`GlobalContext(Category = "...")`) тоже желательно делать осмысленными — они используются в подсказках и группировках в редакторах. ## 8. Депрекейшен и предупреждения Для обозначения устаревших имён, классов, методов и свойств используется атрибут `DeprecatedNameAttribute` (`src/OneScript.Core/Contexts/DeprecatedNameAttribute.cs`): ```csharp [ContextMethod("НоваяФункция", "NewFunc")] [DeprecatedName("СтараяФункция")] [DeprecatedName("OldFunc")] public IValue NewFunc() { /* ... */ } ``` - Аргумент `name` — устаревший псевдоним. При обращении к нему из BSL вызов всё ещё работает, но в системный лог пишется предупреждение. - Параметр `throwOnUse: true` превращает использование устаревшего имени в ошибку выполнения. Применяется, когда нужно жёстко удалить старый псевдоним. - Атрибут можно ставить и на сам элемент (`[DeprecatedName("OldName")]` рядом с `[ContextMethod]`), и на отдельные перечисления/типы. - Для классов, которые сами по себе устарели, реализуйте интерфейс `ISupportsDeprecation` (`src/OneScript.Core/Contexts/ISupportsDeprecation.cs`) — возвращайте `IsDeprecated = true`. Тогда система сможет логировать факт использования такого типа. Изменение и удаление публичных имён без депрекейшена считается ломающим изменением — старайтесь сначала пройти этап с предупреждением. ## 9. Тестирование (C# и BSL) В проекте принято покрывать новые контексты двумя слоями тестов. ### Модульные тесты (xUnit/NUnit) - Тесты лежат в `src/Tests/*` (xUnit/NUnit). - Для контекстов из `OneScript.StandardLibrary` подходит проект `src/Tests/OneScript.StandardLibrary.Tests`. Там же есть пример проверки атрибута `DeprecatedName` (`ObsoleteEnumTest.cs`). - Для проверки кода, который должен компилироваться, используйте инфраструктуру из `src/Tests/OneScript.Dynamic.Tests` (`CompilerTestBase`, `CompileHelper`). - Запуск: `dotnet test` в каталоге соответствующего проекта или одной командой: ```bat dotnet msbuild Build.csproj /t:UnitTests ``` ### Приёмочные тесты на BSL - Поведенческие сценарии лежат в каталоге `tests/*.os` и совместимы с форматом xUnitFor1C. - Для нового контекста создайте файл `tests/<ваш-контекст>.os` со списком тестов. Минимальный шаблон: ```bsl Перем юТест; Функция ПолучитьСписокТестов(ЮнитТестирование) Экспорт юТест = ЮнитТестирование; ВсеТесты = Новый Массив; ВсеТесты.Добавить("ТестДолжен_СоздатьЭкземпляр"); Возврат ВсеТесты; КонецФункции Процедура ТестДолжен_СоздатьЭкземпляр() Экспорт Объект = Новый ПримерКласс(); юТест.ПроверитьРавенство("1.0", Объект.Версия); КонецПроцедуры ``` - Запуск всего набора тестов через свежесобранный `oscript` (см. [`README.md`](../README.md), раздел «Тестирование»): ```bat rem Windows dotnet build src/oscript/oscript.csproj tests\run-bsl-tests.cmd src\oscript\bin\Debug\net8.0\oscript.exe ``` ```bash # Linux/macOS dotnet build src/oscript/oscript.csproj tests/run-bsl-tests.sh src/oscript/bin/Debug/net8.0/oscript ``` - Запуск одного теста: `<путь к oscript> tests/testrunner.os -run tests/<имя файла>.os`. ## 10. Документация (OneScriptDocumenter) - Утилита `OneScriptDocumenter` (см. `src/OneScriptDocumenter`) формирует справку по платформе из XML-документации сборок и оглавления `default_toc.json`. - Чтобы ваш контекст попал в справку: - включите для проекта генерацию XML-документации (это сделано во всех публичных проектах через `oscommon.targets`); - снабдите класс/методы/свойства XML-комментариями ``, ``, ``, `` — они извлекаются документатором; - при необходимости — отредактируйте оглавление `src/OneScriptDocumenter/default_toc.json`, чтобы новый раздел появился в нужном месте. - Сгенерировать локально: `dotnet msbuild Build.csproj /t:BuildDocumentation`. Результат окажется в `built/docs/` (markdown + json). ## 11. Безопасность - Не выполняйте произвольный код, поступивший «снаружи». Если контекст исполняет BSL, который пришёл от пользователя, делайте это только через явные точки расширения (`IBslProcess.Run`/`Eval`), осознавая, какие глобальные имена и контексты доступны. - При работе с файловой системой и сетью валидируйте пути и URL, проверяйте кодировки и таймауты. Для долгих операций предусматривайте отмену. - Не логируйте секреты (пароли, токены, заголовки `Authorization`) в открытом виде. - Не доверяйте внешним десериализаторам: для JSON/XML используйте уже существующие в `OneScript.StandardLibrary` обёртки и не добавляйте отключение защит «по умолчанию». - Если ваш контекст представляет собой обёртку поверх системного API, явно указывайте в XML-документации, какие побочные эффекты возможны (запись на диск, запуск процессов, сетевые вызовы). - При добавлении опасных по умолчанию операций предусматривайте явный «согласный» флаг в API, а не включайте их «втихую». ## 12. Чек-лист готовности Перед открытием Pull Request убедитесь, что выполнены пункты: - [ ] У всех публичных элементов API заданы оба имени — русское и английское. - [ ] Класс/метод/свойство снабжены XML-комментариями (``, ``, ``). - [ ] Параметры с разумными значениями по умолчанию указаны через дефолты C#, а не через перегрузки-обёртки. - [ ] Если переименовываете существующее имя — добавлен `DeprecatedName` со старым псевдонимом. - [ ] Добавлены модульные тесты на C# и/или приёмочные тесты на BSL. - [ ] Все тесты проходят локально (`dotnet msbuild Build.csproj /t:Test`). - [ ] Соблюдены требования [`CODESTYLE.md`](../CODESTYLE.md). - [ ] Проверено, что новый контекст корректно отображается в справке (`/t:BuildDocumentation`), если он публичный. - [ ] Описание изменений в PR содержит мотивацию и ссылки на тесты/issue.