Объявление и проверка типов в примерах

Google Closure Compiler, как и любой кошерный компилятор, старается проверить правильность кода и предупредить о возможных ошибках.

Первым делом он, разумеется, проверяет структуру кода и сразу же выдает такие ошибки как пропущенная скобка или лишняя запятая.

Но, кроме этого, он умеет проверять типы переменных, используя как свои собственные знания о встроенных javascript-функциях и преобразованиях типов,
так и информацию о типах из JSDoc, указываемую javascript-разработчиком.

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

Для включения проверки типов используется флаг --check_types. Кроме того, --warning_level не должен быть QUIET, иначе вы не увидите предупреждений.

Задание типа при помощи аннотации

Самый очевидный способ задать тип - это использовать аннотацию. Полный список аннотаций вы найдете в документации.

В следующем примере параметр id функции f1 присваивается переменной boolVar другого типа:

/** @param {number} id */
function f1(id) {
	/** @type {boolean} */
	var boolVar

	boolVar = id // (!)
}

Компиляция с флагом --check_types выдаст ошибку:

d.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) { }

Такой функции приведет к ошибке:

f.js:3: WARNING - actual parameter 1 of f2 does not match formal parameter
found   : number
required: string
        f2(id)
           ^

Действительно, вызов функции f2 произошел с числовым типом вместо строки.

Кстати, по этому примеру видно, что тип переменной сохраняется после возврата из функции и других аналогичных операций.

Это возможно, благодаря графу взаимодействий и выведению (infer) типов, которое осуществляет компилятор.

Знания о преобразовании типов

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")
}

Выдаст

script.js:6: WARNING - actual parameter 1 of removeNode does not match formal parameter
found   : string
required: (Node|null)
        removeNode("123")

Обратите внимание - в этом примере компилятор выдает required: Node|null. Это потому, что указание объектного типа (не элементарного) подразумевает, что в функцию может быть передан null.

В следующем примере тип указан жестко, без возможности обнуления.

Пример: Объект не-null
/** @param {!Node} node */
function removeNode(node) {
	node.parentNode.removeChild(node)
}
document.onclick = function() {
	removeNode("123")
}

Выдаст

script.js:3: WARNING - actual parameter 1 of Node.prototype.removeChild does not match formal parameter
found   : (Node|string)
required: (Node|null)
        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 переменная может быть только функцией:

if (goog.isFunction(f)) {
  f.apply(1,2,3)
}

Выдаст следующую ошибку:

d.js:2: WARNING - Function Function.apply: called with 3 argument(s). Function requires at least 0 argument(s) and no mo
re than 2 argument(s).
  f.apply(1,2,3)
         ^

То есть, компилятор увидел, что f - это функция, то есть объект типа Function. Метод apply для экземпляра такого объекта принимает только 2 аргумента - обнаружена ошибка. Хорошо, что сейчас, а не позже, в момент запуска.

Резюме

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

Соответствующие различным типам и ограничениям на типы аннотации вы можете найти в Документации Google. В частности, возможно указание нескольких возможных типов, типа undefined и т.п.

Также можно указывать количество и тип параметров функции, ключевого слова this, объявлять классы, приватные методы и интерфейсы.

Проверка типов javascript, предоставляемая Google Closure Compiler - пожалуй, самая продвинутая из существующих на сегодняшний день.

C ней аннотации, документирующие типы и параметры, становятся не просто украшением, а реальным средством проверки, уменьшающим количество ошибок на production.