Я написал небольшую, но полезную библиотечку для любителей TypeScript и ASP.NET MVC. Очень хотелось бы про нее рассказать — возможно какому-то разработчику на вышеупомянутой комбинации технологий (а возможно и целой команде) она существенно облегчит жизнь. Писать полноценную документацию на английском пока что времени нет. К ней вообще нужно подходить осмысленно, с чувством, толком и расстановкой. Поэтому я решил написать статью на Хабрахабр, и вот прямо сейчас, под катом, я сделаю краткий обзор и покажу какую магию можно делать этой штукой. Добро пожаловать.

Лирическое введение


Вообще, меня давно и прочно будоражит идея тесной интеграции Back-End и Front-End на .NET стеке, что в свою очередь даже вылилось в попытку с наскоку написать целый транслятор из C# в JavaScript. Не скажу, что никаких результатов не было достигнуто — мой транслятор успешно перевел в JavaScript несколько C#-классов и в конечном итоге ушел в анабиоз и переосмысление архитектуры. Когда-нибудь я к нему еще вернусь. Обязательно.

Но, тем не менее, текущие задачи в проектах надо было как-то решать. А текущие задачи в почти любом web-проекте, в целом, достаточно типичны и, не побоюсь этого слова, скучны. Вот сейчас я, с вашего позволения, абстрагируюсь от всяких сложных, но давно понятных материй, вроде использования Automapper, собирания IoC-контейнера и шуршания в БД посредством EF/NH/чего-нибудь, и переключусь ближе к фронтенду. Так вот — на стыке бекенда и фронтенда тоже много скучных и типичных задач (sarcasm). А конкретно — запросы к серверу на предмет JSON с данными, их отображение и выполнение всяких-разных операций AJAX-ом. Reinforced.Typings (а именно так я назвал свою маленькую помогалку) принесет в это царство уныния толику веселья, упрощение типичных задач, избавление от шаблонного кода и несколько больше консистентности.

С тех пор, как Microsoft подарил нам TypeScript, написание клиентских JavaScript-ов стало занятием гораздо более комфортным. TypeScript принес ощущение типизируемости и прекомпилируемость туда, где его не хватало. Если вы его еще не пробовали — то обязательно попробуйте (это не реклама, нет). Можно, конечно, много спорить по вопросу «быть или не быть» TypeScript-у в вашем конкретном проекте, но давайте опустим дискуссию и перейдем " — Ближе к телу! — как говорил Ги де Мопассан".

Практический пример


Итак, рассмотрим простой, но достаточно распространенный пример — вам необходимо сделать запрос к серверу, достать информацию о заказе и отобразить её каким-либо образом на страничке в браузере.

Что мы обычно делаем для решения этой задачи? Правильно. Мы делаем POCO модели заказа и метод контроллера, который будет возвращать ее экземпляр, обернутый в JSON. Вот они, наши герои (как вы поняли, я буду убирать лишний код для экономии места):

Моделька
public class OrderViewModel
{
	public string ItemName { get; set; }
	public int Quantity { get; set; }
	public decimal Subtotal { get; set; }
	public bool IsPaid { get; set; }
	public string ClientName { get; set; }
	public string Address { get; set; }
}


Метод контроллера
public ActionResult GetOrder(int orderId)
{
	var orderVm = new OrderViewModel()
	{
		// ... тестовые данные ...
	};

	return Json(orderVm, JsonRequestBehavior.AllowGet);
}


Здесь все более-менее понятно и комментарии, думаю, излишни. Давайте переключимся на клиент. Чтобы быть предельно понятным, я буду использовать jQuery для ajax-запросов, но при необходимости вы можете заменить его на что-нибудь свое. Как и ранее, я опускаю излишний glue code, а так же создание view, TypeScript-файла, подключение его к станице, установку jQuery из NuGet — это вы все сможете сделать и без меня. Подчеркиваю самую суть (еще раз напоминаю, что этот код на TypeScript):

Код TypeScript
private btnRequest_click() {
	$.ajax({
		url: '/Home/GetOrder?orderId=10',
		success:this.handleResponse
	});
}

private handleResponse(data: any) {
	var text = `${data.ClientName}, ${data.Address} (${data.ItemName}, ${data.Quantity})`;
	$('#divInfo').text(text);
}


Здесь прекрасно все. За исключением того, что принципиально от JavaScript эта конструкция ничем не отличается. Мы получаем с сервера кусок JSON-а, в котором какой-то объект и мы, рассчитывая на то, что у него есть поля ClientName, Address и прочие — выводим его в div. Звучит не очень стабильно. Если какой-нибудь горе-джуниор удалит из ViewModel-и и из C#-кода, скажем, поле ClientName (или переименует его в целях джуниор-рефакторинга), то все места на фронтенде, где используется такая конструкция — превратятся в детонаторы и будут ждать прихода тестировщика. Ну или end user-а — тут уж кому как повезет. Что же делать? Ответ очевиден — коль скоро мы используем TypeScript, то можно написать тайпинг для этой конкретной ViewModel-и и переписать код вот таким образом:

private handleResponse(data: IOrderViewModel) {
	var text = `${data.ClientName}, ${data.Address} (${data.ItemName}, ${data.Quantity})`;
	$('#divInfo').text(text);
}

Да, теперь нам стало несколько комфортнее — мы застраховались от доступа к незадекларированному полю. Но ситуация с джуниором, переименовывающим поле лично мне не дает спокойно спать. Да и написание тайпингов для всех ViewModel-ей… руками… ночью… А если их в проекте сотни? А если тысячи? Перспективка-то, откровенно, так себе.

Вот тут-то и вступает в игру Reinforced.Typings и начинается решение задачи кардинально другим путем. Итак, открываем PM-консоль (ну или кому удобно — можете сделать это через графический интерфейс) и ставим:

PM > Install-Package Reinforced.Typings

Замечаем в корне проекта новый файл Reinforced.Typings.settings.xml. Он достаточно детально документирован и переписывать все, изложенное в нем здесь я не вижу смысла (если конечно у кого-то из аудитории у меня не все так плохо с английским), предоставив это хабралюдям. Я изменю всего один параметр — это путь к файлу, куда ляжет сгенерированный тайпинг. В моем проекте он вот такой:

	<RtTargetFile>$(ProjectDir)Scripts\app\Server.ts</RtTargetFile>

После чего, я иду в код модельки и добавляю всего две строчки кода — юзинг на Reinforced.Typings.Attributes и атрибут [TsInterface] над самим классом модельки. Примерно вот так:

using Reinforced.Typings.Attributes;
[TsInterface]
public class OrderViewModel
{
	// в самом коде модельки ничего не изменилось
}

После чего я пересобираю проект (делаю ему Rebuild) и вручную добавляю в Scripts\app\ сгенерированный согласно конфигурации Server.ts. Он уже лежит в указанной папке — просто не добавлен в проект. Давайте откроем Server.ts и посмотрим что же в нем такое:

Содержимое Server.ts
//     This code was generated by a Reinforced.Typings tool. 
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.

module Reinforced.Typings.Samples.Simple.Quickstart.Models {
	export interface IOrderViewModel {	
		ItemName: string;		
		Quantity: number;		
		Subtotal: number;		
		IsPaid: boolean;		
		ClientName: string;		
		Address: string;		
	}

}


Вот видите? Чудненько. Теперь задача по написанию тайпингов для ViewModel-ей всего проекта не кажется таким уж страшным делом, не правда ли? Да и удаление-переименование properties ViewModel-и уже не является трагедией: при следующей же сборке проекта тайпинги перегенерируются и TypeScript-код, который на них завязан просто перестанет собираться, в чем вы можете убедиться собственноручно.

Думаю, на этом практическую демонстрацию основной возможности можно закончить, оставив более сложные примеры на следующие статьи и перейти к драматическому заключению.

Драматическое заключение и немного о том-о сем


На самом деле Reinforced.Typings поддерживает много чего. Вот краткий список:
  • Автоматические тайпинги делегатов, наследников IEnumerable и IDictionary (если вы используете их в качестве properties)
  • Тайпинги для enum-ов и классов (правда, тела методов он перевести в TypeScript автоматически не может. Но это можно сделать самостоятельно — об этом ниже)
  • Тайпинги со сложными типами — Reinforced.Typings понимает, что вы используете в классе другой экспортируемый тип и автоматически ставит в том месте full-qualified имя используемого типа. В противном случае — тактично использует any
  • Можно раскидать генерируемый код по разным файлам (class-per-file) с помощью соответствующего параметра конфигурации
  • Можно добавить ///<reference ...> в генерируемые файлы посредством assembly-атрибута [TsReference]. А в случае с class-per-file, reference на соседние файлы добавляется автоматически
  • Можно генерировать, .d.ts-файлы вместо обычного .ts-кода (есть некоторые отличия в синтаксисе)
  • Вишенка на тортик — у каждого атрибута присутствует свойство CodeGeneratorType, в котором можно указать тип-наследник от Reinforced.Typings.Generators.ITsCodeGenerator<> (как вариант — унаследоваться от существующего генератора) и сделать свою генерацию шаблонного TypeScript-кода для всего, чего угодно. Таким путем можно дойти до автоматического создания knockout-овских ViewModel-ей прямо из кода серверных ViewModel-ей. В проекте по моему текущему месту работы, я перегрузил генератор кода для экшнов контроллера и сгенерировал таким образом для многих методов js-ный glue code, вызывающий соответствующий метод контроллера с указанными параметрами. Возвращают такие методы q-шный promise (просто потому что я люблю Q.js). Об этом я и расскажу в следующем посте

Из минусов: Автоматически сгенерировать тела методов классов Reinforced.Typings не может — он работает через Reflection. Ну и еще хочется отметить некие проблемы в ситуации когда серверные сущности представляют правильный TypeScript-код, но в уже сгенерированном коде содержится семантическая ошибка (например, удалено поле). В силу особенностей сборки TypeScript-а (он собирается самым первым во всем проекте), вы не сможете пересобрать проект и сгенерировать правильные тайпинги, которые исправят ошибку, пока не поправите ошибку вручную. Но я над этим работаю. Магия MSBuild творит чудеса.

Еще по проекту, как было сказано выше, крайне мало документации (эта статья, да readme на гитхабе). НО! Очень детально расписан XMLDOC и прокомментирован файл настроек. Так что, я думаю, на первое время должно хватить. А там я уже завербую студента-техписателя и сделаю нормальную книгу Reinforced.Typings Best Practices документацию в вики-формате.

В общем-то на сегодня все. Если какие-то вопросы есть — пишите в комментариях. Постараюсь ответить.

Ссылочки на проект:
Reinforced.Typings на Github
Reinforced.Typings на NuGet
Полный код рассмотренного примера

Комментарии (30)


  1. jakobz
    15.09.2015 19:36

    У меня свой велосипед. Кроме перечисленного — я еще беру метаданные контроллеров MVC (через GetApiExplorer), и генерирую такие вот штуки на каждый контроллер:

    module api {
        export var someController = {
            someMethod: function(id : number) : JQueryPromise<void> {
                return api.processRequest(`/api/someController/someMethod?id=${id}`);
            }
        }
    }


    За счет этого, кроме того что не надо это руками писать, мне еще не надо ставить аттрибуты на viewmodels — я просто генерирую интерфейсы для всего что входит и выходит из контроллеров.

    Чтобы избавится от проблемы курицы и яйца (компиляция C#, генерация TS, и компиляция TS в одном проекте) — я просто вынес MVC-код в отдельную сборку.

    Кстати, твоя библиотека как-то связана с TypeLite ( http://type.litesolutions.net/ )?


    1. pnovikov
      15.09.2015 19:51

      У меня свой велосипед

      Вот у меня тоже началось со своего велосипеда.

      Просто генерирую интерфейсы для всего что входит и выходит из контроллеров

      Боюсь, если я так сделаю на живом проекте сейчас — ничего хорошего не получится :) Слишком много легаси-кода, слишком много всяких недокументированных методов. Повылазиет всякого… ну его :)

      MVC-код в отдельную сборку

      Мне такое решение не нравится, поэтому я предпочел хирургически препарировать MSBuild-скрипт (он же .csproj).

      TypeLite

      Впервые слышу о ней. Обязательно посмотрю.


    1. pnovikov
      15.09.2015 20:01

      Про атрибуты забыл дописать:
      Для некоторых экшнов я сделал такую генерацию и вот в следующем посте расскажу-покажу как. А ставить-не ставить атрибуты… ну тут палка о двух концах. В конце-то концов нам нужен всего лишь список методов (при том крайне желательно, чтобы мы могли его контролировать!).
      С другой стороны плюс атрибутов в том, что можно много всякого-разного понастраивать индивидуально для метода. Это можно, было бы, конечно, изобразить Fluent-конфигурацией или, упаси б-же, XML, но пока что мне не хочется сильно разделять TypeScript и C#-код, которые его представляет.
      С третьей стороны — я писал библиотечку для широкого круга пользователей и кто его знает — не все же используют именно jq-промисы. Кому-то может вообще понадобятся дополнительные фичи вроде добавления в список параметров id-шника индикатора загрузки, или еще какой радости. Или вообще обернуть это в какой-нибудь ангуляровский интерфейс для запросов к серверу. Если я сделаю фиксированное решение и скажу «делайте как я сказал» — меня, боюсь, многие матом будут крыть. :) А так — можно просто дописать пак атрибутов специально для MVC и дать возможность каждому выбрать.

      P.S: А вообще у моего [TsClass] можно задать дефолтный код-генератор для всех методов класса. Так-то.


  1. Tremor
    16.09.2015 01:33

    Я в своем проекте использую DataContract для помечания ViewModel и все свойства, необходимые на клиенте, явно помечаю DataMember-атрибутами. При конвертации в json использую JSON.Net, который из коробки прекрасно работает с dataconract'ом. Ну и ModelBinder для всего этого написан.

    Это позволяет не боятся серверных рефаткорингов даже при использовании чистого js и явно указывать, какие свойства нужны на клиенте (часть свойств вью-моделей нужны только для серверного рендеринга, гонят туда-сюда данные смысла нет). Ну и кроме того в шарпе принято свойство называть с большой буквы, а в js — с маленькой.

    Отсюда вопрос: поддерживает ли ваша библиотечка dataconract?
    Кстати, при конвертации в typescript generic-классы и классы с наследованием корректно генерируются?


    1. pnovikov
      16.09.2015 04:52

      Эм… я боюсь, мы как-то не поняли друг друга. Библиотечка тайпинги пишет, а не занимается сериализацией :) В свете этого не очень понятен вопрос про DataContract.

      Но на всякий случай: да, тайпинги для generic-классов она тоже пишет.


      1. Tremor
        16.09.2015 12:33
        +1

        Попробую пояснить на примере: у меня есть ViewModel вида

        [DataContract]
        public class OrderViewModel
        {
            [DataMember(Name = "itemName")]
            public string ItemName { get; set; }
        
            [DataMember(Name = "quantity")]
            public int Quantity { get; set; }
            public decimal Subtotal { get; set; }
            public bool IsPaid { get; set; }
            public string ClientName { get; set; }
            public string Address { get; set; }
        }
        


        из нее я бы хотел получить интерфейс вида

        interface OrderViewModel {	
            itemName: string;		
            quantity: number;		
        }
        


        А в идеале даже без явного указания Name:

        [DataContract]
        public class OrderViewModel
        {
            [DataMember]
            public string ItemName { get; set; }
        
            [DataMember]
            public int Quantity { get; set; }
            public decimal Subtotal { get; set; }
            public bool IsPaid { get; set; }
            public string ClientName { get; set; }
            public string Address { get; set; }
        }
        

        interface OrderViewModel {	
            itemName: string;		
            quantity: number;		
        }
        


        1. pnovikov
          16.09.2015 12:38
          +1

          Спасибо, ваш фич-реквест принят :)


          1. Tremor
            16.09.2015 12:47

            Спасибо :)
            Но атрибут [TsInterface] все равно нужен. DataContract может использоваться много для чего, и для всех классов, использующих его, генерить ts-интерфейсы не нужно, правильнее явно помечать классы, для которых нужны интерфейсы.


        1. pnovikov
          16.09.2015 12:43

          Правда мне все еще непонятно зачем вы используете DataContract, в то время как Json.NET прекрасно справляется и без них. И к тому же имеет свой, куда более гибкий [JsonProperty]

          P.S: совсем забыл — да, и с наследованием у Reinforced.Typings тоже все хорошо.


          1. Tremor
            16.09.2015 12:52

            Начал использовать еще до внедрения TypeScript'a. Это помогало не разломать фронтенд при рефакторинге бекэнда. С автоматической генерацией ts-интерфейсов эта проблема решается, но у нас проект большой и чистый js еще долго будет жить с нами.

            Кроме того мне кажется важным явно помечать свойства, с которыми нужно работать на клиенте. Это во-первых, позволяет немного сократить объем ненужного траффика между клиентом и сервером, а во-вторых, на клиенте работать с чистыми интерфейсами без лишних серверных свойств.


            1. pnovikov
              16.09.2015 13:02

              Ну тут вот фиг знает. На сколько я соображаю в системном дизайне, единственная роль ViewModel-и — хранить в себе данные, которые отображаются. Переиспользовать их же на клиенте (а особенно — переиспользовать на клиенте их часть) — в некотором роде нарушение SRP. Т.е. да, я за то, чтобы View рендерился от одной модели, а на клиент уходила другая (в общем случае). Хотя на практике возникают ситуации, когда в рендер и на клиент отправляется одна и та же модель — и чувствуешь себя с этим SRP как идиот. Для таких случаев можно и [TsInterface] пошалить.

              Кстати о птичках — коль пошла такая пляска — можете потыкать у Json.NET свойство атрибута… как же его… по-моему NullValueHandling — и эта радость не будет отправлять на клиент null-поля (которые там будут undefined, но при проверке через if (object.Field) это не страшно).

              Ну и чтобы не разносить на два комментария — вы упомянули, что не для всех классов, которые помечены [DataContract] нужно генерировать интерфейсы. Но тогда получается что надо классы еще чем-то помечать. Мол — ты, типа, для этих генерируй, а для этих не генерируй. Можно пометить их тем же [TsInterface], что и является дефолтной практикой для R.T.
              А вот, кстати, переименования автоматического у меня нет. Как-то совсем даже забыл про него. Привык на сервере и на клиенте использовать PascalCase (и можете бить меня за это тапками). Но в следующей версии сделаю.


              1. Tremor
                16.09.2015 21:20

                ViewModel хранит в себе данные, да. Часть из них мне удобнее рендерить серверно, а часть на клиенте (начали использовать реакт, но при этом разом перевести весь проект на реат невозможно). Что в этом плохого? А что такое SRP не просветите? :)

                Кстати о птичках — коль пошла такая пляска — можете потыкать у Json.NET свойство атрибута… как же его… по-моему NullValueHandling — и эта радость не будет отправлять на клиент null-поля (которые там будут undefined, но при проверке через if (object.Field) это не страшно).

                В моем случае это не поможет. У меня поля не Null, они заполнены, но на клиенте не используются.

                Ну и чтобы не разносить на два комментария — вы упомянули, что не для всех классов, которые помечены [DataContract] нужно генерировать интерфейсы. Но тогда получается что надо классы еще чем-то помечать. Мол — ты, типа, для этих генерируй, а для этих не генерируй. Можно пометить их тем же [TsInterface], что и является дефолтной практикой для R.T.

                Не имею ничего против аттрибута [TsInterface] :)

                А вот, кстати, переименования автоматического у меня нет. Как-то совсем даже забыл про него. Привык на сервере и на клиенте использовать PascalCase (и можете бить меня за это тапками). Но в следующей версии сделаю.

                Стараюсь все же придерживаться общепринятых стандартов. В этом есть свои плюсы)


                1. pnovikov
                  16.09.2015 21:38

                  SRP = Single Responsibility Principle, SOLID же.

                  Не имею ничего против аттрибута [TsInterface] :)

                  А вот в комментарии ниже вы его против лишний раз поставить. :)


                  1. Tremor
                    16.09.2015 21:53

                    Против TsProperty, но не против TsInterface :)
                    Про SRP скорее теперь скорее согласен.


            1. pnovikov
              16.09.2015 13:11

              Кстати про важные на клиенте свойства — в моем [TsInterface] вы можете явно сказать AutoExportProperties = false и пометить [TsProperty] те проперти (и через [TsField] те филды), которые должны быть экспортированы. При желании указав для них кодогенератор. И даже автоматическую букву I перед интерфейсами можете отключить. :)
              При наличии этой информации, фич-реквест еще актуален?


              1. Tremor
                16.09.2015 21:14

                Актуален. При таком подходе мне на каждое свойство придется вешать по 2 аттрибута: TsField для корректной конвертации в ts и DataMember/JsonProperty для корректной сериализации/десериализации в Json.
                Два аттрибута — это уже перебор, на мой взгляд.


                1. pnovikov
                  16.09.2015 21:33

                  … и я не теряю надежды.
                  Как вариант — вы можете расставить [TsIgnore] на все остальные свойства :)


                  1. Tremor
                    16.09.2015 21:46

                    Как вариант, да. Хотя и выглядит слегка костыльно. Но автоматическое переименование в CamelCase все равно нужно.


        1. jakobz
          16.09.2015 16:47

          Я тебе советую взять JSON.NET, в него легко встраивается конвертация имен: PersonName => personName при сериализации, и обратно при десериализации, делается автоматом без доп. аттрибутов.

          http://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Serialization_CamelCasePropertyNamesContractResolver.htm


          1. Tremor
            16.09.2015 21:12

            Я и так использую JSON.NET, у меня нет цели избаваиться от аттрибутов, я, наоборот, считаю их полезными.


  1. KvanTTT
    19.09.2015 02:44

    Вообще, меня давно и прочно будоражит идея тесной интеграции Back-End и Front-End на .NET стеке, что в свою очередь даже вылилось в попытку с наскоку написать целый транслятор из C# в JavaScript.

    Чем не устроили существующие трансляторы, коих сейчас и так много существует List of languages that compile to JS.

    Темой тесной интеграции я тоже занимался, правда давно уже: Универсальный код C# под .NET и JavaScript. Сейчас бы, понятное дело, использовал другие инструменты.


    1. pnovikov
      19.09.2015 11:44

      Чем не устроили существующие трансляторы

      Вы бы знали сколько раз мне задавали этот вопрос. Самый честный ответ: потому что я несколько по-своему вижу Use-Cases транслятора и имею свои представления о usability и flexibility такой штуки. Обратите внимание — ни один из предложенных вариантов не используют как «стандарт индустрии де-факто» и, в общем-то, все понимают почему (затруднена интеграция с MVC-проектом — тот же Script# подменяет mscorlib — то есть в одну сборку с проектом транслируемые классы уже не положишь, затруднена отладка, не очень понятно что у этих продуктов с расширяемостью, ну и много других мелких нюансов — не могу сходу перечислить). У меня свое видение, как это все должно работать, отлаживаться и тестироваться, по сему я и предпочел попытаться сделать свое. К слову, получилось в некотором роде похоже на DuoCode.


      1. KvanTTT
        19.09.2015 16:11

        Ну так не лучше ли взять какой-нибудь существующий, на рослине или нет, и доработать? С нуля довольно много придется разрабатывать. А Script# по факту давно устарел уже, сейчас его уже не имеет смысла использовать.


        1. pnovikov
          19.09.2015 16:42

          Я на Roslyn и написал уже, собсно, ту самую версию, которая ушла в анабиоз (при том вот сразу через пару дней после выхода Roslyn-а — пришлось Microsoft.CodeAnalyzis выдирать с мясом из исходников). Так что мне не привыкать много разрабатывать :).
          Там, на самом деле, затык в том, что в TS называется тайпинги. Т.е. придется переписывать интерфейсы для внешний библиотек, что руками делать долго и скучно.
          А на самом деле, первоначальная версия транслятора внезапно хороша тем, что там есть парсер JavaScript, который превосходно разбирает даже минифицированный jquery в AST. Не спрашивайте зачем он там нужен, но он есть.


          1. KvanTTT
            19.09.2015 17:07

            Хорошая грамматика для JavaScript внезапно есть в ANTLR: ECMAScript.g4. :)


            1. pnovikov
              19.09.2015 17:30

              Commits on Apr 24, 2014
              @bkiers
              Added an ECMAScript grammar.


              А я свою грамматику (на Coco\R) написал где-то в марте прошлого года. Время, беспощадная ж ты штука.
              А вообще то ли у меня руки не оттуда, то ли лыжи не едут… Но в общем была у меня какая-то грамматика для ANTLR-а, из которой я сгенерил все необходимое, сделал playground для тестирования, нажал F5 — и получил зацикливание на простеньком js-файле. С тех пор я ANTLR-ом не пользуюсь.


              1. KvanTTT
                19.09.2015 19:13

                Ну так вообще-то в апреле появилась грамматика для 4 версии ANTLR. Судя по этому сайту, грамматика ECMAScript существует аж с 2008 года: www.antlr3.org/grammar/list.html

                Но в общем была у меня какая-то грамматика для ANTLR-а, из которой я сгенерил все необходимое, сделал playground для тестирования, нажал F5 — и получил зацикливание на простеньком js-файле. С тех пор я ANTLR-ом не пользуюсь.

                Скорее всего вы не разобрались с устройством ANTLR и его грамматик. Я вот тоже раньше не разбирался в лексических режимах, предикатах и других вещах. А после того, как прочитал книгу и попрактиковался я понял, что это очень мощный инструмент, обладающий большими возможностями по разбору формальных языков, в том числе и контекстно-зависимых. Сейчас вот на работе занимаюсь грамматикой для PHP.


                1. pnovikov
                  19.09.2015 20:17

                  Я как бы и не говорю что «виновата скамейка», нет :) Просто с ANTLR-ом у меня был неудачный первый опыт (который во многом влияет на восприятие технологии как таковой) и как-то так получилось что я перешел на Coco\R и вот я здесь.

                  www.antlr3.org/grammar/list.html

                  Дадада. Вот оттуда я ее скачал и сгенерил парсер, который зациклился на самом простом js-файле.


  1. aleksey_bober
    04.10.2015 14:36

    в Web essentials для visual studio сейчас есть возможность сгенерить ts-файлик с C# класса + при изменении класса файлик будет изменяться автоматичски


    1. pnovikov
      04.10.2015 15:37

      О, а покажите ссылку? Мне что-то не по глазам, а как гуглу задать этот вопрос — не знаю :)

      P.S.: я еще допилю в свое творение fluent-конфигурацию (просто пока сильно больно времени нет) и будет следующая статья.