Reinforced.Typings — більше деталей

І знову здрастуйте.
Хто про що, а я знову про Reinforced.Typings — своїй бібліотеці для генерації TypeScript-ового glue-коду з C#-збірок, короткий вступ в яку я зробив в предыдщем пості. Після чого негайно отримав низку запитань і коментарів (не тільки на Хабрахабре, до речі, багато хто з них просто на ньому не зареєстровані). За це, звичайно, всім величезне спасибі, але виходячи з проаналізованої інформації, я зрозумів що одного короткого посту не достатньо, щоб описати як і що реалізується. Виходить що люди задають питання «а ось це підтримується?» і кожному доводиться пояснювати раз за разом, одне і те ж. Так що в цій статті я зроблю невеликий cheatsheet по атрибутах, fluent-конфігурації і розповім про додаткові можливості. Загалом, ласкаво просимо. Обережно, лонгрид і довідкова інформація!


У предыдщей серії

Коли я вперше розповідав про Reinforced.Typings, я мимохіть згадав, що є різні атрибути і у них багато всяких-різних кастомних властивостей, є якісь кодогенераторы і щось про процес складання TypeScript. Все якось сумбурно, поверхово, без подробиць. Давайте розкладемо по поличках. Я максимально стисло, але детально розповім про різні варіанти конфігурації, а під кінець вас чекає обіцяний приклад з генерацією обгорток методів контролерів.
У загальному і цілому ця стаття — своєрідний manual/методичка/cheatsheet за Reinforced.Typings, тому як у мене все ще немає часу і ресурсу написати повноцінну документацію англійською. Звичайно, не все так сумно — я вже зібрався з духом і написав аж цілий план документації. Однак на більше (+ статтю на Хабрахабре) «батька російської демократії» не вистачило. Отже, поїхали.
P. S.: Варто зазначити, що я трохи все-таки считерил і за час, який минув з моменту написання останньої статті, допилил деякі можливості у бібліотечці, полагодив баги (куди вже без них). Тому, представлений матеріал актуальний з версії 1.0.7.

Як воно в цілому працює

Ви розслаблені, задоволені, у вас теплі ноги та ви пишете на C# і ASP.NET MVC з використанням TypeScript. Ви встановлюєте Reinforced.Typings з NuGet командою

PM > Install-Package Reinforced.Typings

Reinforced.Typings відразу ж вбудовується у процес складання вашого додатки, запускаючись при кожному билде проекту. Цей момент контролюється з файлу Reinforced.Typings.settings.xml, який додається в корінь вашого проекту — я називаю це «складальна конфігурація». Запустившись, Reinforced.Typings робить свою темну справу, подгружая збірку вашого проекту і залазячи в неї через Reflection. Бажаний TypeScript glue-код генерується грунтуючись на двох можливих факторах:
  • Атрибутной конфігурації — тобто експортуються типи та їх члени, позначені спеціальними атрибутами (їх список і властивості будуть наведені нижче)
  • Fluent-конфігурації — коли для з'ясування як і що ви хочете згенерувати викликається метод, зазначений у складальному конфіги. Цей варіант конфігурації так само буде освітлено далі
Атрибутную і fluent-конфігурацію можна комбінувати — це не забороняється. Вони здебільшого дають рівні можливості, але розрізняються по гнучкості.
Загалом-то, всі таємні знання, необхідні для використання Reinforced.Typings, полягають у тому, щоб орієнтуватися в параметрах його конфігурації. І тут всього 3 точки налаштування, згадані вище — складальна конфігурація (Reinforced.Typings.settings.xml), атрибутная і fluent-конфігурація. Складальна конфігурація визначає глобальні параметри всього процесу кшталт того, які файли буде записаний згенерований код, експортувати документацію, які збірки підключати і т. п. Атрибутная і fluent-конфігурації визначають який саме код генерувати і для яких типів.

Складальна конфігурація

Reinforced.Typings генерує ваші тайпинги при кожній пересборке проекту. Як було зазначено вище, він вбудовується в процес складання проекту. Робиться це за допомогою мехнизма вбудовування .targets і .props-файлів з пакета, який з'явився у версії NuGet 2.5.
До речіСам генератор винесено на інструмент rtcli.exe, який запускається за допомогою вхідної в комплект MSBuild-таски RtCli. Словом, все в кращих традиціях викликання кастомних інструментів під час білду. В принципі вас ніщо не зупиняє від нахабного видирання rtcli.exe і Reinforced.Typings.dll з директорії /tools самого пакету і використання на свій розсуд (так само як і таски RtCli, яка лежить в /build/Reinforced.Typings.Integrate.dll), але давайте будемо об'єктивні — мало хто реально буде це робити.

Деякі параметри запуску генератора винесені в файл Reinforced.Typings.settings.xml, який являє собою шматок MSBuild-скрипта, що підключається в .targets-файлі самого пакета. Reinforced.Typings.settings.xml додається в корінь вашого проекту при установці пакету. За замовчуванням він виглядає ось так. Я б не став стверджувати, що він добре документований (в основному через ломанности своєї англійської), тому наводжу табличку-cheatsheet з описом параметрів конфігурації:
Параметри Reinforced.Typings.settings.xmlRtTargetFile (рядок)
Повний шлях до файлу, у який будуть записані всі згенеровані тайпинги. В ньому (як і у всіх інших параметрах) можна використовувати змінні MSBuild, в тому числі специфічні для Microsoft.Common.CurrentVersion.targets. Цей параметр є ключовим та обов'язковим, коли вам просто треба скинути тайпинги для вашого проекту в один файл, однак він не використовується, якщо включена функція розбиття згенерованого коду з багатьох файлів (див. нижче). Не забудьте додати цей файл на свій проект руками!

RtConfigurationMethod (рядок)
Задає повне ім'я методу, який буде викликаний для побудови fluent-конфігурації. Наприклад My.Assembly.Configuration.ConfigureTypings. Складання вказувати не потрібно — Reinforced.Typings сам знайде цей метод або в збірці самого проекту, або в його references. Метод повинен бути статичним і приймати на вхід єдиний параметр типу Reinforce.Typings.Fluent.ConfigurationBuilder (назва параметра не важливо). Варто відзначити, що у вас ще є можливість використовувати атрибути, навіть якщо ви використовуєте fluent-конфігурацію. Однак майте на увазі, що якщо для одного і того ж члена є і fluent і атрибутная конфігурація, то перевагу буде віддано fluent.

RtWriteWarningComment (true/false)
Контролює запис у вихідний файл предупреждалки про те, що файл згенерований автоматично. Якщо цей параметр встановлено в true, то в заголовку кожного згенерованого файлу буде красуватися надписть "// This code was generated by a Reinforced.Typings tool. " бла-бла-бла. Якщо чесно, я не знаю кому може знадобитися цей параметр, але він є.

RtDivideTypesAmongFiles (true/false)
Коли цей параметр false, то всі згенеровані тайпинги будуть записані в один файл, зазначений у параметрі RtTargetFile. Це не завжди зручно, бо може призвести, наприклад, до монструозным мерджам при використанні SCM. Якщо встановити цей параметр в true, то згенерований TypeScript буде раскидан з різних файлів (class-per-file). При цьому RtTargetFile буде проігноровано. Тільки не забудьте додати згенеровані файли в свій проект руками по мірі появи!
Ви можете налаштувати що і куди покласти за допомогою атрибута [TsFile]/fluent-виклику .ExportTo. Варто відзначити, що RT сам прекрасно розбереться з додаванням директив ///<reference ...> на сусідні використовувані типи в цьому випадку. Але при утрудненні ви завжди можете допомогти йому з допомогою атрибута [TsAddTypeReference]/fluent-виклику .AddReference

RtTargetDirectory (рядок)
Використовується спільно з RtDivideTypesAmongFiles для вказівки директорії, в яку будуть повалені всі згенеровані файли. Обов'язково вкажіть цей параметр, якщо ви використовуєте RtDivideTypesAmongFiles.

RtRootNamespace (рядок)
Так само використовується спільно з RtDivideTypesAmongFiles. Справа в тому, що через Reflection неможливо визначити кореневе простір імен складання. А без цього не вдасться правильно розкидати згенеровані файли по директоріях.

RtBypassTypeScriptCompilation (true/false)
TypeScript збирається першим при складанні проекту. Іноді виникає ситуація, що ви згенерували тайпинги, а вони зробили TypeScript-код вашого проекту несобираемым. І щоб полагодити це, вам потрібно перезібрати проект і перегенерувати тайпинги, однак ви не можете цього зробити, так як проект не збирається з-за того, що не збираються тайпскрипты. Вийти з цього порочного кола дозволяє установка RtBypassTypeScriptCompilation в true. Цей параметр вимикає збірку тайпскриптов перед складанням проекту і збирає їх після складання проекту, що дає .dll-ке вашого проекту спокійнісінько зібратися, а RT — згенерувати з неї свіжі тайпинги. Будь ласка, не забудьте повернути цей параметр в false, коли проблема усунеться. Інакше можливі проблеми з паблишингом зібраних javascript-ів.

RtCamelCaseForMethods (true/false) (реалізований фіч-реквест Tremor
Примусово преобразоывает назви всіх методів у camelCase (замість традиційного для .NET PascalCase). Використовується естетами від javascript. Так само camelCase-інг можна контролювати окремо для кожного методу шляхом використання властивості ShouldBeCamelCased атрибута TsFunction.

RtCamelCaseForProperties (true/false) (реалізований фіч-реквест Tremor
Те ж саме що RtCamelCaseForMethods, тільки для властивостей. ShouldBeCamelCased так само є у TsProperty.

RtGenerateDocumentation (true/false)
При установці в true, а так само включенні генерації документації в XML в налаштуваннях вашого проекту, Reinforced.Typings витягне шлях до файлу з XMLDOC-му в ході процесу складання і перетворює його в jsdoc, який і допише в генеруються файли. У цьому підході все чудово, за винятком того, що за замовчуванням, після включення в проекті експорту XML документації, компілятор починає засипати вас ворнингами для незадокументированных публічних класів/членів класу. Не те щоб це заважало технічно, але візуально бісить.

RtDisable (true/false)
Відключає Reinforced.Typings. Поки цей параметр true — процедура генерації тайпингов викликатися не буде. Однак, RtBypassTypeScriptCompilation як і раніше буде активний.

Item-група RtAdditionalAssembly
В цю Item-групу можна скинути додаткові збірки, які Reinforced.Typings повинен взяти до уваги при експорті (читай: експортувати тайпинги і з них теж). У розпорядженні RT є повні шляхи до References вашого проекту, тому в RtAdditionalAssembly можна включити просто ім'я збірки (все, що варто до .dll).


Атрибути

Однієї складальної конфігурації для успішного експорту недостатньо. Вам так само доведеться вказати RT що конкретно ви хочете бачити в згенерованих TypeScript-файли. Зробити це можна, наприклад, повісивши відповідні атрибути над експортованими типами (класами, інтерфейсами, переліком) та їх членами. Це я називаю «атрибутная конфігурація». Є ще fluent-конфігурація, але про неї я розповім трохи пізніше, бо вона повністю базується на атрибутной і у чому її повторює, а по сему — буде педагогичнее розповісти спочатку про атрибутную конфігурацію.
Всі атрибути Reinforced.Typings лежать, як не дивно, в просторі імен Reinforced.Typings.Attributes, що може збити новачка з пантелику (sarcasm). Від всіх атрибутів можна унаследоваться. Всі властивості перевантажуються. Як одна з доступних технік роботи — ви можете унаследоваться від будь-якого з них і зробити свій атрибут, щоб не тягати за собою пачку параметрів кожен раз.
Нижче я наводжу cheatsheet по всім доступним атрибутів. Вираз «обов'язковий атрибут» в ньому означає, що якщо цей атрибут не поставите, то экспортироватся в TypeScript відповідна сутність не буде.

Назва атрибута Обов'язковий? Результат в тайпинге Експортований (пацієнт)
TsInterface Так TypeScript-інтерфейс Клас, інтерфейс, структура
TsClass Так TypeScript-клас Клас, структура
TsProperty Немає Поле інтерфейсу/класу Property, полі (класу/структури)
TsFunction Немає Метод інтерфейсу/класу (у випадку з класом — тіло експортується як return null; якщо метод не-void і як порожнє в разі void) Property, полі (класу/структури)
TsEnum Так TypeScript-перерахування enum, очевидно
TsValue Немає Одне із значень TypeScript-перерахування Значення enum, неочевидно
TsParameter Немає Параметр (формальний аргумент) TypeScript-методу Параметр методу, як не дивно
TsGeneric Немає Тип-параметр TypeScript-методу/класу/whatever Тип-параметр
TsIgnore Немає Пацієнт не буде експортуватися в TypeScript Властивість, конструктор (!), поле, метод, параметр
Трохи про експорт конструкторівОкремо хочеться згадати атрибут TsBaseParam. RT може експортувати класи, а так само їхні конструктори. У разі, коли ви наслідуєте класи один від одного і явно викликаєте конструктор предка в конструкторі спадкоємця з використанням :base() — жодної інформації про це через Reflection отримати не можна. Тому для коректного експорту таких випадків, ви можете поставити над конструктором атрибут TsBaseParam. У нього один конструктор і він приймає на вхід масив рядків, в яких ви можете писати будь-які TypeScript-вирази. Це все буде записано в TypeScript-овий super(...). На практиці я слабо уявляю собі корисність цього атрибуту, але він є. Є думка що цей атрибут існує просто щоб показати наскільки крут Reinforced.Typings.

В іншому, якщо ви включите експорт конструкторів шляхом відповідного властивості атрибута [TsClass], то вони будуть експортовані коректно. Тобто, ніяких спеціальних атрибутів для конструкторів не передбачено.


Властивості атрибутів

Властивостей досить багато, тому я згорнув їх список в спойлер, щоб не засмічували статтю. Там, у списку властивостей я вказую ім'я властивості, в дужках значення властивості за замовчуванням і описую що воно контролює. Це здебільшого довідкова інформація, яка продубльована в XMLDOC-ах, тільки англійською. По сему — відкривати обережно.

Онуча тексту
TsInterface
  • AutoI (true) — ставити автоматично літеру I перед ім'ям пацієнта (якщо її немає)
  • AutoExportMethods (true) — експортувати автоматично всі методи пацієнта. Якщо ні — будете ставити [TsFunction] над методами самі
  • AutoExportProperties (true) — те ж, що й вище, але для пропертей і [TsProperty]
  • IncludeNamespace (true) — поміщати пацієнта в module при експорті
  • Name (null) — перевизначає ім'я пацієнта
  • Namespace (null) — перевизначає неймспейс пацієнта
TsClass
  • AutoExportProperties, AutoExportMethods, IncludeNamespace, Name, Namespace — аналогічно властивостями TsInterface. Плюс:
  • AutoExportFields (true) — те ж, що AutoExportProperties, але для полів
  • DefaultMethodCodeGenerator (null) — дозволяє перевизначити кодогенератор для всіх експортованих методів класу
TsProperty
  • Type (null, підставляється ім'я типу) — перевизначає ім'я типу пацієнта в TypeScript. Як рядок. Ну тобто можна написати взагалі що завгодно. Допомагає, коли тип властивості не виводиться в TS (ви отримуєте any), або треба зробити посилання на неэкспортируемый тип. Наприклад — на JQuery
  • StrongType null, підставляється ім'я типу) — те ж, що і Type, але ви можете вказати .NET-івський тип. Зручно для делегатів наприклад — написав StrongType = typeof(Func<int,bool>) і порядок, не треба паритися з тоннами скобочек
  • Name (null) — перевизначає ім'я пацієнта
  • ForceNullable (false) — говорить примусово зробити полі nullable. Ну тобто field:boolean перетворити у field?:boolean
  • ShouldBeCamelCased (false) — має ім'я властивості бути перетворено в camelCase (реалізований фіч-реквест Tremor
TsFunction
  • Type, StrongType — аналогічно властивостями TsProperty, але перевизначає зворотний тип методу
  • Name (null) — перевизначає ім'я пацієнта
  • ShouldBeCamelCased (false) — має ім'я методу бути перетворено в camelCase (реалізований фіч-реквест Tremor
TsEnum
  • IncludeNamespace, Name, Namespace — аналогічно властивостями TsInterface. Більш параметрів не має.
TsValue
  • Name — перевизначає ім'я пацієнта (конкретного значення enum-а)
TsParameter
  • Type, StrongType — аналогічно властивостями TsProperty, але зумовлюють тип аргументу методу
  • Name (null) — перевизначає ім'я пацієнта
  • DefaultValue (null) — вказує на значення за замовчуванням. Щоб підставлялося як parameter:boolean= false, наприклад. Обережно, тут можна вистрілити собі в ногу.
  • ShouldBeCamelCased (false) — має ім'я параметра бути перетворено в camelCase (реалізований фіч-реквест Tremor
TsGeneric
  • Type, StrongType — аналогічно властивостями TsProperty, але зумовлюють тип тип-
TsBaseParam
  • Values — масив рядків, предствляющих собою TypeScript-вирази. Їх вміст яких буде використано для генерації виклику super(...) в разі експорту TypeScript-класу
TsAddTypeReference
  • RawPath — шлях до файлу, який буде записаний в директиві ///<reference ...>, яка буде додана в файл з експортним типом
  • Type — тип, шлях до файлу, який містить, буде доданий у директиві ///<reference ...> у файл з експортним типом

Варто зазначити, що у атрибутів наявна невелика ієрархія наслідування, тому деякі властивості є в кількох атрибутах і роблять приблизно одне і те ж (скажімо, Name, перевизначає ім'я нового класу/інтерфейсу, але так само працює і для параметрів методу або властивості).

Експорт TypeScript-коду в кілька файлів

Ця можливість включається при установці RtDivideTypesAmongFiles в true в складальному конфіги. За замовчуванням, без додаткової конфігурації, RT розкидає всі ваші класи відповідно до старої-доброї традиції ОО-мов — по класу в окремий файл, так ще й покладе в піддиректорії згідно Namespace-ам (щоб не наплодити у зв'язку з цим зайвих директорій, рекомендується використовувати параметр складального конфига RtRootNamespace). Так само є один нюанс — студія не завжди бачить, що який-небудь з використовуваних .ts-файлі типів лежить в сусідньому файлі і підкреслює червоним почому дарма. Щоб такої ситуації не виникало — в усі .ts-файли потрібно додати директиву ///<reference path="..." >. Так от — в більшості випадків з коректним розміщенням цієї директиви RT справляється сам, інспектуючи залежності типів шляхом перегляду типів властивостей/полів, значень методів, типів параметрів і т. п. І це безцінне. Для решти є MasterCard атрибут TsAddTypeReference. В спойлері нижче мова піде про нього, а так само про деяких інших атрибутах, які влиялют на розкидування TypeScript-коду з різних файлів.

Ще одна онуча тексту
  • TsAddTypeReference — цей атрибут дозволяє додати директиву ///<reference ...> в файл, у який буде записаний згенерований код для пацієнта. Це необхідно коли ви, наприклад, переопределяете Type рядком для будь-яких експортуються членів і проінспектувати референси через Reflection не представляється можливим. Як параметр конструктора цього атрибута можна вказати тип (наприклад [TsAddTypeReference(typeof(AnotherExportedType))]). Тоді RT сам визначить в якому файлі він лежить і згенерує для директиви відповідний шлях. У випадку вказівки явного шляху (наприклад [TsAddTypeReference("../../jquery.d.ts")]), саме він буде використаний для додавання директиви. Цей атрибут вішається на класи, інтерфейси і enum-и. Допускається використовувати кілька разів.

  • TsFile — Вказує в який файл покласти згенерований код для пацієнта. Цей атрибут активний тільки коли RtDivideTypesAmongFiles в складальної конфігурації встановлено в true. Шлях у цьому атрибуті вказується щодо RtTargetDirectory
  • TsReference — Раз вже ми заговорили про обробці референсов, то треба згадати цей атрибут. Він вказує що згенерований файл (або всі файли) треба додати директиву ///<reference ...> на вказаний шлях. Без прив'язки до якогось конкретного типу. Референс, зазначений цим атрибутом буде доданий у кожен згенерований файл. Корисно для додавання референса, наприклад, на тайпинг для jQuery. Цей атрибут вішається на збірку, отже, писати його треба як [assembly: TsReference("~/Scripts/typings/jquery.d.ts")]. Так само допускається використовувати кілька разів.




CodeGenerator

Майже всі атрибути успадковані від TsAttributeBase, який визначає лише одну властивість — CodeGeneratorType типу Type. Що це таке? А це можливість вказати тип кодогенератора для будь-якого члена класу або типу. Кодогенератор — це спеціальний клас, який повинен реалізовувати інтерфейс Reinforced.Typings.Generators.ITsCodeGenerator<>, параметризований:
  • System.Type у разі кодогенератора для типу (використовується з TsEnum, TsClass, TsInterface)
  • System.Reflection.PropertyInfo у разі кодогенератора для property (використовується з TsProperty)
  • System.Reflection.FieldInfo у разі кодогенератора поля (використовується з TsField)
  • System.Reflection.MethodInfo у разі кодогенератора для методу (використовується з TsFunction)
  • System.Reflection.ParameterInfo у разі кодогенератора для параметра методу (використовується з TsParameter)
ITsCodeGenerator<> визначає лише одну властивість (Settings), яке досить реалізувати як auto-property і всього один метод — Generate, який приймає на вхід:
  • TElement, яким і параметризован генератор — сутність, для якої треба згенерувати TypeScript — код
  • TypeResolver — клас, самим цінним методом якого є метод ResolveTypeName, що приймає на вхід тип. Він повертає TypeScript-friendly ім'я типу, яке можна писати в генерується код.
  • WriterWrapper — невелика обгортка над TextWrapper-му, в який потрібно записати генерований код
  • Так само ви можете використовувати властивість Settings всередині методу Generate для того, щоб, наприклад, подивитися (або записати у вихідний потік) документацію для генерується сутності
В RT є кілька готових генераторів для класів, інтерфейсів, enum-ів, параметрів полів, методів і property — вони всі лежать в просторі імен Reinforced.Typings.Generators і від них можна успадковуватись. Звичайно ж, ніщо не заважає зробити свій кодогенератор безпосередньо реалізувавши інтерфейс ITsCodeGenerator<>.
Коли ви зробили свій кодогенератор, потрібно вказати, що потрібно використовувати саме його, встановивши властивість CodeGeneratorType відповідного атрибута в typeof від вашого генератора. Ніякого контролю типобезопасности тут, на жаль немає. Ну тобто якщо ви вкажете CodeGeneratorType = typeof(string), то IntelliSense не скаже, що щось не так. Однак, ця ж функціональність надається у fluent-конфігурації (метод .WithCodeGenerator<>), і ось там вже вказати невідповідний кодогенератор вам не дадуть.

А ось наприклад..., в моєму поточному проекті, я унаследовался від Reinforced.Typings.Generators.MethodCodeGenerator, зробивши свій генератор ActionInvokeGenerator, який пише glue-code для виклику методу MVC WebAPI і повернення promise. Після цього позначив потрібні контролери, методи яких треба прокинути в TypeScript атрибутом [TsClass(AutoExportMethods = false)], а самі методи — з [TsFunction(CodeGeneratorType = typeof(ActionInvokeGenerator))]. В результаті отримав обгортку для мене цікавлять методів на клієнті. Про це чудово досвіді я розповім в наступній статті.


Fluent-конфігурація

Розміщувати атрибути над експортованими типами не завжди зручно. Наприклад, у випадку, коли вам треба експортувати сотню типів з певного namespace-а, при тому видерти з них певні властивості і прописати для них певну конфігурацію експорту, то розміщувати атрибути стає справою кілька нудним і монотонним. До того ж, ви не зможете розмістити атрибути експорту над сутностями, до вихідного коду яких у вас немає доступу. Якраз щоб побороти це обставина, у версії 1.0.5 була додана вомзожность fluent-конфігурації. Почати використовувати його поряд з атрибутами можна в 2 простих кроки:
  1. Створіть окремий клас, оголосіть у ньому публічний статичний метод з єдиним параметром типу Reinforced.Typings.Fluent.ConfigurationBuilder. Наприклад, ось так:
    using Reinforced.Typings.Fluent;
    namespace TestProject.App_Start
    {
    public class TypingsConfiguration
    {
    public static void ConfigureTypings(ConfigurationBuilder builder)
    {
    // тут буде ваша fluent-конфігурація
    }
    }
    }
    

  2. складальної конфігурації вкажіть, який fluent-метод. Наприклад, так:
    <RtConfigurationMethod>TestProject.App_Start.TypingsConfiguration.ConfigureTypings</RtConfigurationMethod>
    

Всі. Тепер цей метод буде викликатися кожен раз при перегенерации тайпингов (читай: при пересборке проектів) і готувати конфігурацію експорту. Через fluent-конфігурацію досить легко можна експортувати пачками однотипні інтерфейси і класи з однотипною ж конфігурацією (реалізований фіч-реквест Tremor, нехай і трохи в іншому вигляді), що полегшує задачу в разі великого проекту. Так само безсумнівним плюсом, на мій погляд, є строго типізоване вказівку кодогенератора, завдяки якому ви вже ніколи помилково не застосуйте кодогенератор для методу на властивостях і навпаки.

З вашого дозволу, я не буду детально розписувати всі методи ConfigurationBuilder-а (і всіх вкладених білдер), бо вони інтуїтивно зрозумілі, а так само мають гарний XMLDOC. Наведу лише невеликий шматок коду fluent-конфігурації, щоб створити уявлення приблизно як вона виглядає:
Шматок коду
using Reinforced.Typings.Fluent;
namespace TestProject.App_Start
{
public class TypingsConfiguration
{
public static void ConfigureTypings(ConfigurationBuilder builder)
{
builder.ExportAsInterface<ILoginInformation>()
.WithPublicProperties()
.WithProperty(c => c.Email).Type<int>();

builder.ExportAsInterface<ILoginPage>()
.WithPublicProperties()
.WithPublicMethods()
.WithMethod(c => c.FillIn(Ts.Parameter<ILoginInformation>(o => o.Type<string> ())))
.Returns<string>();
builder.ExportAsInterface<ILoginPage>()
.WithMethod(c => c.AddOrders(Ts.Parameter<OrdersColelction>()));

builder.ExportAsInterfaces(
new[] {
typeof (ILoginPage), 
typeof (ILoginInformation),
typeof(IOrder)
},
c => c.ExportTo("login.ts").WithAllMethods(m => m.CamelCase()));
}
}
}


В окремому поясненні, мабуть, тут потребує лише Ts.Parameter і метод TryLookupDocumentationForAssembly.
Ts.Parameter — це спеціальний статичний метод, який може приймати на вхід будівник fluent-конфігурації для параметрів методу. Тобто коли ви хочете задати конфігурацію для параметра методу, ви просто говорите щось на кшталт
builder.ExportAsInterface<MyClass>()
.WithMethod(m => m.MyMethod(
Ts.Parameter<int>(p => p.OverrideName("apple").Type<string>())
))
.Returns<object>();

щоб отримати в TypeScript-ті
export IMyClass {
MyMethod(apple:string):any;
}

/*
При вихідних даних
class MyClass
{
public int MyMethod(int a)
{
return 0;
}
}
*/

На мій погляд, зручний і елегантний спосіб.
TryLookupDocumentationForAssembly — це метод самого ConfigurationBuilder-а, який говорить RT пошукати файл з xml-документацією для зазначеної збірки поруч з самої складанням. Має одну перевантаження, яка дозволяє вказати ім'я файлу безпосередньо (таким чином збірка буде використана тільки для визначення директорії, в якій треба шукати). Я прийшов до необхідності додати цей метод, коли постала необхідність експортувати документацію з іншої збірки.
Ось. Тепер точно все.

Висновок

Стаття вийшла досить великою, що, в принципі, відповідало моїй меті зробити більш-менш докладний мануал при повній відсутності документації за проектом і категоричну нестачі вільного часу для авторства. Ще мене не вистачило на приклади використання, але їм я присвячу наступну статтю і спробую дописати і докоммитить приклади на github. Не стріляйте в піаніста — він пише коли є час.
Як звичайно, я відкритий до побажань-пропозицій і іншого feedback-у. Якщо в ході використання ви знайдете баги — не соромтеся відправляти issues на github проекту. Несподіваним pull-реквестам я так само дуже радий писати в одного, поєднуючи з fulltime-роботою стає важкувато.
NuGet-пакет проекту лежить там же, де і лежав.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.