8.8 KiB
GCC: статическая проверка типов
Google Closure Compiler, как и любой кошерный компилятор, старается проверить правильность кода и предупредить о возможных ошибках.
Первым делом он, разумеется, проверяет структуру кода и сразу же выдает такие ошибки как пропущенная скобка или лишняя запятая.
Но, кроме этого, он умеет проверять типы переменных, используя как свои собственные знания о встроенных javascript-функциях и преобразованиях типов, так и информацию о типах из JSDoc, указываемую javascript-разработчиком.
Это обеспечивает то, чем так гордятся компилируемые языки -- статическую проверку типов, что позволяет избежать лишних ошибок во время выполнения. [cut]
Для вывода предупреждений при проверки типов используется флаг --jscomp_warning checkTypes
.
Задание типа при помощи аннотации
Самый очевидный способ задать тип -- это использовать аннотацию. Полный список аннотаций вы найдете в документации.
В следующем примере параметр id
функции f1
присваивается переменной boolVar
другого типа:
/** @param {number} id */
function f(id) {
/** @type {boolean} */
var boolVar;
boolVar = id; // (!)
}
Компиляция с флагом --jscomp_warning checkTypes
выдаст предупреждение:
f.js:6: WARNING - assignment
found : number
required: boolean
boolVar = id; // (!)
^
Действительно: произошло присвоение значения типа number
переменной типа boolean
.
Типы отслеживаются по цепочке вызовов.
Еще пример, на этот раз вызов функции с некорректным параметром:
/** @param {number} id */
function f1(id) {
f2(id); // (!)
}
/** @param {string} id */
function f2(id) { }
Такой вызов приведёт к предупреждению со стороны минификатора:
f2.js:3: WARNING - actual parameter 1 of f2 does not match formal parameter
found : number
required: string
f2(id); // (!)
^
Действительно, вызов функции f2
произошел с числовым типом вместо строки.
Отслеживание приведений и типов идёт при помощи графа взаимодействий и выведению (infer) типов, который строит GCC по коду.
Знания о преобразовании типов
Google Closure Compiler знает, как операторы javascript преобразуют типы. Такой код уже не выдаст ошибку:
/** @param {number} id */
function f1(id) {
/** @type {boolean} */
var boolVar;
boolVar = !!id
}
Действительно - переменная преобразована к типу boolean двойным оператором НЕ.
А код boolVar = 'test-'+id
выдаст ошибку, т.к. конкатенация со строкой дает тип string
.
Знание о типах встроенных функций, объектные типы
Google Closure Compiler содержит описания большинства встроенных объектов и функций javascript вместе с типами параметров и результатов.
Например, объектный тип Node
соответствует узлу DOM.
Пример некорректного кода:
/** @param {Node} node */
function removeNode(node) {
node.parentNode.removeChild(node)
}
document.onclick = function() {
removeNode("123")
}
Выдаст предупреждение
f3.js:7: WARNING - actual parameter 1 of removeNode does not match formal parameter
found : string
required: (Node|null)
removeNode("123")
^
Обратите внимание - в этом примере компилятор выдает required: Node|null
. Это потому, что указание объектного типа (не элементарного) подразумевает, что в функцию может быть передан null
.
В следующем примере тип указан жестко, без возможности обнуления:
*!*
/** @param {!Node} node */
*/!*
function removeNode(node) {
node.parentNode.removeChild(node)
}
Восклицательный знак означает, что параметр обязатален.
Найти описания встроенных типов и объектов javascript вы можете в файле экстернов: externs.zip
находится в корне архива compiler.jar
, или в соответствующей директории SVN: http://closure-compiler.googlecode.com/svn/trunk/externs/.
Интеграция с проверками типов из Google Closure Library
В Google Closure Library есть функции проверки типов: goog.isArray
, goog.isDef
, goog.isNumber
и т.п.
Google Closure Compiler знает о них и понимает, что внутри следующего if
переменная может быть только функцией:
var goog = {
isFunction: function(f) { return typeof f == 'function' }
}
if (goog.isFunction(func)) {
func.apply(1, 2)
}
Сжатие с проверкой выдаст предупреждение:
f.js:6: WARNING - actual parameter 2 of Function.apply does not match formal parameter
found : number
required: (Object|null|undefined)
func.apply(1, 2)
^ ^
То есть, компилятор увидел, что код, использующий func
находится в if (goog.isFunction(func))
и сделал соответствующий вывод, что это в этой ветке func
является функцией, а значит вызов func.apply(1,2)
ошибочен (второй аргумент не может быть числом).
Дело тут именно в интеграции с Google Closure Library. Если поменять goog
на g
-- предупреждения не будет.
Резюме
Из нескольких примеров, которые мы рассмотрели, должна быть понятна общая логика проверки типов.
Соответствующие различным типам и ограничениям на типы аннотации вы можете найти в Документации Google. В частности, возможно указание нескольких возможных типов, типа undefined
и т.п.
Также можно указывать количество и тип параметров функции, ключевого слова this
, объявлять классы, приватные методы и интерфейсы.
Проверка типов javascript, предоставляемая Google Closure Compiler - пожалуй, самая продвинутая из существующих на сегодняшний день.
C ней аннотации, документирующие типы и параметры, становятся не просто украшением, а реальным средством проверки, уменьшающим количество ошибок на production.
Очень подробно проверка типов описана в книге Closure: The Definitive Guide, автора Michael Bolin.