Flow — статический анализ типов в JS от Facebook

    Flow — это статический анализатор кода и набор синтаксических конструкций, для прямого указания типа переменной.
    Flow умеет вычислять тип переменной, без внесения изменений в код (в отличии от TypeScript) что позволяет начать использовать его уже сейчас в любом проекте. Также есть возможность самостоятельно указывать типы в стиле TypeScript.

    Есть 3 режима:
    1. Не проверять ничего, по умолчанию
    2. Проверка без использования аннотаций (с коментарием-аннотацией, как в React)
    3. Строгое указание типа переменной (с внесением изменения непосредственно в код)


    /* @flow */
    function foo(x) {
      return x * 10;
    }
    foo('Hello, world!');
    

    $> flow
    hello.js:5:5,19: string
    This type is incompatible with
      hello.js:3:10,15: number
    


    В данном случае анализатор самостоятельно вывел тип переменной, но можно и подсказать:

    /* @flow */
    function foo(x: string, y: number): string {
      return x.length * y;
    }
    foo('Hello', 42);
    

    $> flow
    hello.js:3:10,21: number
    This type is incompatible with
      hello.js:2:37,42: string
    


    Также есть weak режим, позволяющий проверять код на ошибки, которые легко быстро исправить:
    • Потенциально возможные значения undefined и null, которые легко исправить добавлением проверки
    • Примитивне ошибки с типами, вроде true + 3


    Установка
    Flow написан на OCaml (>=4.01.0).
    Для OSX и Linux есть бинарные сборки, Windows не поддерживается.
    Для тех, кто пишет на OCaml, можно воспользоваться пакетным менеджером OPAM:
    opam install flowtype
    

    А пользователям OSX есть возможность поставить через Brew:
    brew install flow
    


    Планы на будущее
    • Поддержка существующих для TypeScript файлов интерфейсов (.d.ts) с DefinitelyTyped.org в аналогичный формат для Flow
    • Поддержка модулей ES6
    • Компиляция Flow в JS с помощью js_of_ocaml
    • Интеграция с редакторами кода и IDE
    • Сортировка ошибок и фильтрация по файлам
    • Улучшить сообщения об ошибках: причина ошибки и ее трейс
    • Куча фич типизированных систем, таких как ограниченный полиморфизм, перечисления, анализ чистоты функций и многое другое


    Ссылки
    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну, и что?
    Реклама
    Комментарии 56
    • +3
      Пробежался по доке. Ключевые отличия от текущего тайпскрипта (в будущем обещают реализовать):
      1. объединения типов
      2. как следствие — наличие не nullable типов
      3. ветвление по типам
      В остальном разница лишь косметическая. Я думаю было бы куда больше пользы, если бы они реализовали какой-нибудь typescript2.0, чем делать несовместимый в мелочах язык.
      И, кстати, на typescript код переводить тоже можно постепенно (пофайлово), а вот с typescript на flow — не получится.
      • +2
        И это говорит профессиональный велосипедист? Чем Фейсбук хуже?
        • 0
          Я не говорил, что он хуже. Я говорю, что им с Microsoft стоило бы объединить усилия вместо того, чтобы конкурировать.
          • +5
            Именно. TypeScript от Microsoft, AtScript от Google, Flow от Facebook — все пилят свой собственный JavaScript, с типами и интерфейсами. С минимумом различий. Ну их всех к чёрту, если договориться не могут. С таким подходом буду ждать аннотации типов в самом ECMAScript, тем более с этим, вроде, есть подвижки.
            • 0
              Чем больше крупногабаритных контор набросятся на несчастные типы в JS, тем скорее, я надеюсь, появится новый стандарт.
        • 0
          > пофайлово
          Я бы сказал, что нет ограничений и typescript также можно переводить постепенно. Файлы TS могут содержать как TS так и JS код, иногда для этого нужно написать интерфейсы к JS части кода, но это вполне логично и нужно.

          Но я не против, пусть будет, может что интересное получится)
          • +1
            В typescript нельзя переводить постепенно. Вы не можете взять js файл и просто изменить расширение. Вам либо прийдется для всех остальных классов проставить динамические типы в и не получать никакого анализа, либо переводить полностью.
            В flow же есть вывод типа, что позволяет действительно ничего не делая получить информацию о потенциальных ошибках.
            • 0
              Значит я делаю невозможное :-)

              Например, при переводе какого-то модуля на TS обнаружилась зависимость от модуля $jin.asyn2sync. Чтобы не переводить его сейчас же на TS, я добавил файл с декларацией его апи. В результате при компиляции TS будет проведена проверка типов в соответствии с декларацией. Когда дойдут руки до перевода и этого модуля на TS — декларации и JS будут удалены и заменены на нормальную TS-реализацию.

              А Flow вообще ничего не поймет, потому, что функция в том модуле создаётся через указание полного пути литералом.
              • 0
                А вы пробовали Flow, или просто так говорите? Мой поинт в том, что Flow позволяет делать то, что вы описали без .d.ts как раз.
                • 0
                  Я пролистнул документацию и не нашёл там даже поддержки jsdoc, а значит вытащить такие идиомы как «класс» или даже «примесь» из js кода он не сможет, потому, что в js есть тысяча и один способ реализовать наследование.
                  • +1
                    Все это пустые слова. Покажите код, посмотрим как отреагирует Flow.
                    Вместо тупой проверки типа переменной, Flow учитывает природу динамической типизации.
                    /* @flow */
                    function Foo (a) {
                        this.a = a;
                    }
                    
                    Foo.prototype.bar = function(foo) {
                        console.log(foo.a);
                    }
                    
                    var foo = new Foo(1);
                    foo.bar(foo);
                    foo.bar(123);
                    

                    Вот вам пример, ошибка где надо — последняя строка
                    PS у меня есть предположение, что ядром flow является VM, а значит анализируется рантайм, а значит вы любым способом можете определить/унаследовать класс, а Flow все отследит. Только вот не могу ничем подтвердить догадки
                    • 0
                      function define( config ){
                      	for( var path in config ) {
                      		var current = this
                      		var names = path.split( '.' )
                      		for( var i = 0 ; i < names.length - 1 ; ++i ) {
                      			var name = names[ i ] || 'prototype'
                      			var next = current[ name ]
                      			if( !next ) next = current[ name ] = {}
                      			current = next
                      		}
                      		current[ names[ i ] || 'prototype' ] = config[ path ]
                      	}
                      }
                      
                      define({
                      	'Foo' : function Foo (a) {
                      	    this.a = a;
                      	},
                      	'Foo..bar' : function(foo) {
                      	    console.log(foo.a);
                      	}
                      })
                      
                      var foo = new Foo(1);
                      foo.bar(foo);
                      foo.bar(123);
                      
                      • 0
                        /Users/nkt/Desktop/flow.js:9:24,38: access of computed property/element
                        Computed property/element cannot be accessed on global object
                        
                        /Users/nkt/Desktop/flow.js:10:32,51: assignment of computed property/element
                        Computed property/element cannot be assigned on global object
                        
                        /Users/nkt/Desktop/flow.js:13:9,61: assignment of computed property/element
                        Computed property/element cannot be assigned on global object
                        
                        /Users/nkt/Desktop/flow.js:26:15,17: identifier Foo
                        Unknown global name
                        
                        Found 4 errors
                        

                        Если this в define заменить на window — останется неизвестный Foo.
                        Будем ждать, что уж. Тут наверное тот случай, когда можно подсказать компилятору.
                        Плюс, на сколько я понимаю, Flow заточен под использование с ES6 и наверное не будет развивать es5, что в принципе логично.
            • 0
              Сравнение с typescript немного некорректно. Его придется компилировать, а парни из Flow утверждают, что все пашет само прямо на живую. Что несомненно лучше. Единственное, на мой взгляд, лучше бы они JSDoc начали парсить, чем вводить какие-то навороты по синтаксису, а то и правда TS получается.
            • +2
              ИМХО, с внедрением статической типизацией сейчас такой же дикий запад, как был в свое время с модульными системами. Когда каждый писал свой велосипед и было неизвестно доживет ли система модулей хотя бы до следующего года. Сейчас все более-менее устаканилось: browserify, webpack, requirejs. Еще одна реализация системы типов, это ок. Посмотрим какая из них реально выживет и в итоге станет стандартом. Пока что Flow это очень умный препроцессор, который почти не меняет исходный js-код. Посмотрим, что будет по мере добавления фич.
              • +1
                Flow это не просто препроцессор, это скорее статический анализатор. То есть его изначально можно использовать только как статический анализатор, по моему это очень круто.
              • +2
                В отличии от TypeScript, есть возможность делать код типизированным постепенно.

                Что вы имели в виду под этой фразой? В Typescript можно просто поменять расширение с js на ts и всё будет компилироваться, поскольку Typescript — надмножество Javascript. А потом постепенно заменять типы — например, начиная с локальных переменных.
                • 0
                  Судя по каментам автор имел ввиду что typescript, при простом изменении расширения будет воспринимать код как обычный js, при этом будет игнорировать опасные моменты (например var q = 5; w = «5»; e = q + w;), flow же в такой ситуации выдаст предупреждение что проходит не математическая операция а конкатенация (типы не совместимы). Как-то так. Естественно оба языка будут работать с чистым js без проблем, они оба полностью совместимы с js.
                  • 0
                    Typescript на само сложение не ругается, подразумевая конкатенацию. Однако тип всех переменных выведет и вызвать на переменной e метод типа number не даст.
                  • 0
                    Да, вы правы.
                    taliban вы тоже правы, поправил начало.
                    • 0
                      Вы, мягко выражаясь, не правы

                      function foo( a ){
                          alert( a || 'hello world' )
                      }
                      foo() // error TS2081: Supplied parameters do not match any signature of call target.
                      
                      • 0
                        Да, прошу прощения — вы правы, количество аргументов проверяется жестко. Но какой вариант логичнее с точки зрения статической проверки — показывать ошибку или нет — однозначно сказать нельзя. Всё зависит от того, как используется a внутри функции.

                        В Typescript есть еще одна запрещенная конструкция, которая является допустимой в JS:

                        var x = "test";
                        x = 123;
                        

                        Было бы интересно узнать, что Flow говорит в обоих этих случаях.
                        • 0
                          Для вызова функции без аргумента — Too few arguments (expected default/rest parameters in function)
                          Если изменить сигнатуру на function foo(a = undefined) — нет ошибок, es6 такое позволяет
                          Для второго примера все ок.
                          Более того
                          /* @flow */
                          var x = "test";
                          x.indexOf('t');
                          x = 123;
                          x.indexOf('t');
                          

                          Ошибка на последней строке. Вот этим Flow и лучше TypeScript.
                          • 0
                            Не сказал бы, что лучше. Обычно типы переменных не должны меняться. К тому же, что он выдаст на такое?

                            /* @flow */
                            var x = "test";
                            x.indexOf('t');
                            while( o > 1 ) {
                                o--;
                                x = Math.random();
                            }
                            x.toFixed( 2 );
                            
                            • 0
                              /Users/nkt/Desktop/flow.js:5:8,8: identifier o
                              Unknown global name
                              
                              /Users/nkt/Desktop/flow.js:9:1,14: call of method toFixed
                              Property not found in
                              

                              А вот так ошибок нет:
                              /* @flow */
                              var x = "test";
                              x.indexOf('t');
                              var o = 0;
                              do {
                                  o--;
                                  x = Math.random();
                              }while( o > 1 );
                              x.toFixed( 2 );
                              
                              • 0
                                function( o ){
                                    var x = "test";
                                    x.indexOf('t'); 
                                    while( o > 1 ) {
                                        o--;
                                        x = Math.random();
                                    }
                                    x.toFixed( 2 );
                                }
                                
                                • 0
                                  У вас ус отклеился)
                                  Если добавить название функции:
                                  /Users/nkt/Desktop/flow.js:9:5,18: call of method toFixed
                                  Property not found
                                  
                                  • 0
                                    Ну то есть он не замечает, что в цикле тип меняется.
                            • 0
                              Лучше? Я бы наоборот назвал это фундаментальным недостатком :)

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

                              Кстати, а если переопределять значение переменной в цикле, что оно будет делать?
                              var data = ["123", 123];
                              var elem;
                              for(var i =0; i < data.length; i++)
                              {
                                  elem = data[i];
                                  elem.indexOf('1'); // что-то будет?
                              }
                              


                              UPD: Товарищ vintage меня опередил, даже пример практически такой же.
                    • +2
                      На самом деле лучше бы в typescript добавил то что им не хватает. Выведение типов без аннотаций умеет google closure компилятор.
                      • –4
                        Я так понял, парням из фейсбука в typescript не понравилось то, что там нету ихнего безумного JSX. А гуглю с аналогичной поделкой — видимо что-то не хватает для из ангуляра. Может тоже строго-типизированного HTML, лол. Обоим двум пока не хватает мозга понять что JSX и Angular нужно просто выкинуть в помойку как побочные эффекты эволюции.

                        Но нет — будет у нас ещё два тайпскрипта. Привычно, в очередной раз, вздыхаем, берем попкорн и смотрим шоу. Которое, разумеется, закончится тем, что JSX и ангуляр выкинут в помойку.
                        • 0
                          JSX и ангуляр выкинут в помойку

                          Это вы как себе представляете? А на чем приложения писать на чистом js?
                          • –6
                            На реакте и его потомках. Если ничего лучше не придумают.

                            На то что подход ангуляра не взлетел и больше никогда не взлетит — коственно указывает практически полное отсутствие под него компонентов и остальной инфраструктуры, то что гугль сам его толком не использует, ну и просто здравый смысл.
                            • +3
                              Эм. Под ангуляр куча компонентов, это не армия jquery плагинов, но тоже достаточно для среднестатистического проекта. React — это только view, не стоит про это забывать.
                          • 0
                            AngularJS отлично дружит с Typescript. Единственное, чего пока не видел — качественного автодополнения кода в выражениях типа ng-repeat.
                            • –3
                              До реорганизации хабра, я имел возможность к статьям ни о чем оставлять камменты, и из разговоров иной раз выносить какие-то мысли. Иногда набрасывал, иногда — давал какие-то советы, иногда — помогали мне. Было в целом прикольно, и у меня был даже какой-то положительный бюджет в карме, который я воспринимал чисто как автомагическое определение моего поведение как нормального человека — не тролля и не спаммера.

                              Сейчас, за два минуса, мне выдали бедж «отхабренный», и ограничили возможность комментировать одним камментов в пять минут.

                              Учитывая такое тупорылое поведение робота, настроенного тупорылыми хозяевами ресурса (которые сворее всего — тупорылые PHP-шники), пожалуй, это будет мой последний сюда визит. Искренне желаю тупорылой команде хабра придти к своей исконной цели — стать местячковой тусовкой тупорылых PHP-шников. С перепостами новостей месячной давности, и физической невозможностью высказывать по этому поводу любые мысли, перпендикулярные общепринятым в тупорылом PHP-сообществе тупорылым мейнстримным понятиям.

                              За сим — откланиваюсь.
                          • +2
                            Как по мне, так google closure compiler с его прекрасной системой аннотирования. Которые совместно не только позволяют искать ошибки на ранних стадиях, но и ужимать скрипты, повышать производительность и даже использовать информацию о типах при оптимизации — вещь на много более полезная. К сожалению стилистические требования для продвинутой оптимизации немного высоковаты, что не позволяет его широко использовать.
                            Но мне нравится направление движения сообщества, которому не хватает типов в таком типо-разгильдяйском языке как JS.
                            • 0
                              Я имею большой опыт разработки на closure compiler и могу сказать что писать для closure компилятора довольно утомительно, код получается зашумлённый и нужнопостоянно проверять что написанный код в Release режиме работает так же как в debug`е. Есть загнувшийся проект который модифицировал typescript компилятор таким образом что он при компиляции выдавал js код с аннотациями для closure компилятора, я считаю была очень здравая мысль, жалко не развилась.
                              • 0
                                По поводу утомительно и зашумлено — не согласен. То же самое, что и на других языках типа java. Иногда бывают танци-с-бубном для избавления от варнингов, но при некотором опыте — это не напрягает.
                                По поводу проверки работает ли оптимизированный код — так я последний раз сталкивался год назад с тем, что компилятор не то «нагеморировал». И то в не совсем красивом инжекшине.

                                По поводу тайпскрипта — это да, это очень приятно. Но у меня опустились руки, как только подумал сколько времени нужно потратить, дабы преобразовать саурсмап от откомпилированного google closure compiler результата через саурсмапы тайпскрипта к исходникам…
                                • 0
                                  Я не знаю как в сравнении с Java, но в сравнении с C# анотации closure это очень утомительно и не читабельно.
                                  По поводу нагеморировал, вот такой код
                                  /** param {Object} a */
                                  function(a){
                                  var x = a.someField;
                                  }

                                  closure в advanced режиме преобразует в такой
                                  function(a){
                                  var x = a.mangledField;
                                  }

                                  Спрашивает, какого он переименовал поле, если не имеет о нём никакой информации?
                                  • 0
                                    «Спрашивает» -> «Спрашивается»
                                    • 0
                                      >… но в сравнении с C# аннотации closure это очень утомительно и не читабельно.
                                      Возможно XML Comments чем-то лучше чем javaDoc формат, но как по мне это не принципиально. (Только не скажите, что доки писать вредно!)

                                      Прошу прощения за оффтопик
                                      /** @param {Object} a */
                                      function(a){
                                      var x = a['someField'];
                                      }
                                      //Преобразует к 
                                      function(a){
                                      var x = a.someField;
                                      }
                                      

                                      Не стоит жаловаться на переименование свойств, если используете адвансед оптимизацию — пишите просто верно. Ко всему прочему есть различные externs, publish итп. В привате отвечу на вопросы, а тут не стоит засорять.
                                • 0
                                  Я лично именно за такой вариант, когда при применении RequireJS (ну или Cajon, если CommonJS синтаксис ближе) есть и модульность и нет необходимости что-то компилировать перед запуском, а парсинг JSDoc обеспечивает более-менее вменяемое автодополнение при написании и подсветку потенциально опасных мест. На мой взгляд, компилировать язык, который без этого прекрасно обходится — есть безусловное зло. Только при создании билда на продакшен, там это оправдано.

                                  Сейчас IntelliJ продукты (WebStorm, IDEA и т.д.) прекрасно справляются с большинством ситуаций при наличии хорошо написанного JSDoc.

                                  Надеюсь это направление со временем не отомрет и JS не придет к тому, что любой чих надо будет перекомпилировать…
                                  • 0
                                    Это конечно очень приятно, и в целом я с Вами согласен.

                                    А в дебаг режиме не нужно ничего компилировать, оно и так работает как RequireJS.

                                    Но иногда бывают проекты с многими мегабайтами исходников, которые должны быть загружены за 6 секунд максимум по вайфай, и работать на андроиде с 512 памяти. Тогда различие в десятки раз по времени загрузки, в разы по трафику и в существенные проценты по потреблению памяти играют огромную роль. Но на много важнее то, что есть список варнингов, который учитывает и приведения типов, и вычисляемые выражения и много чего еще.
                                    Анализаторы InteliJ подсвечивают лишь малую часть возможных варнингов.

                                    > Надеюсь это направление со временем не отомрет и JS не придет к тому, что любой чих надо будет перекомпилировать…
                                    Не отомрет! ;)
                                    (Только привычка разработки методом, близким к «научному тыку», как правило менее качественна, чем у людей, которые не могут себе позволить, даже в голове, писать маленькими не полными кусочками. Парадокс, но я к примеру делаю больше ошибок в примитивных для-себя страничках на jQuery, чем на ассемблере 20 лет назад.)
                                    • 0
                                      А в дебаг режиме не нужно ничего компилировать, оно и так работает как RequireJS.

                                      Если Вы про google closure compiler, то да, а вот фишки Flow, в которых задействовано в т.ч. расширение синтаксиса, без пре-компиляции не взлетят. Так что я сугубо за подход через аннотации в комментариях.

                                      Что касается проектов с мегабайтами исходников — для таких только бандлы функциональности, загружаемые по мере надобности, и, безусловно, прекомпиляция, но так то ж для продакшена. Там можно многое, чего не желательно иметь в дев режиме. И наоборот…
                                    • 0
                                      Да ладно, прекрасно? Разве в таком коде:
                                      /** param {Array.} strings
                                      function(strings) {
                                      var s = _.find(function(str){ return str.indexOf('s') != -1;})
                                      }

                                      редактор сможет дать адекватную подсказку для методов имеющихся у str?
                                      • 0
                                        Я боюсь это пример того случая, в котором редактор будет жестоко тупить…
                                        • 0
                                          Вместе с spyjs/COLT сможет.
                                    • 0
                                      А как код потом выполняется?
                                      Насколько я знаю JS (а я знаю я его по Qt Quick только), там нельзя указывать типы подобным образом…
                                      • 0
                                        Пишите в начале файлик с типами через function (a: number, b: string), а дальше препроцессор прогоняет ваш файлик и вырезает все ваши указания типов, оставляя function(a, b)
                                        • 0
                                          Это грустно как-то, по сути в системе контроля версий имеешь неработоспособный код, который перед использованием еще нужно чем-то прогонять =\
                                          • 0
                                            Для js — это вполне нормальная ситуация. Клиентские приложения часто пишутся с использованием например commonjs, сборка в этом случае все равно требуется, несмотря на то что сам код написан на чистом js.
                                            • 0
                                              а в c++ не так? Перед использованием, код нужно прогнать компилятором)
                                              • 0
                                                Не совсем компилятором, конечно, но в общем и целом Ваш пример вполне себе корректный!
                                                Да, приходится признать, что отсутствие опыта программирования на JS в самом привычном его варианте использования заставляет мыслить иначе)
                                        • +1
                                          Вместо того, чтобы изменить сам язык, лучше сделать невозможное и написать для него статический анализатор :)

                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                          Самое читаемое