Last active
August 29, 2015 14:11
-
-
Save ne-sachirou/562a7bd59772147a83df to your computer and use it in GitHub Desktop.
JS コメントで型注釈を書き実行時に自動で型検査
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
TypeScriptやFlowやAtScriptやES6を使はずに、関数にコメントを書くことで型註釋[注釈]を行なひ、 | |
実行時に自動で検査します。 | |
例: | |
var f = typed(function (a/*:string*/)/*:number*/ { | |
return a; | |
}); | |
var g = typed(function (a/*:number*/, b/*:number?*/)/*:number*/ { | |
b = b || 0; | |
return a + b; | |
}); | |
var h = typed(function (a/*:A*/)/*:A*/ { | |
return a; | |
}); | |
License: CC 0 or Public Domain |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
* { | |
margin: 0; | |
padding: 0; | |
border: 0; | |
} | |
body { | |
background: #dff; | |
font: 30px sans-serif; | |
} | |
.error { | |
color: red; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div id='console'></div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @author ne_Sachirou <[email protected]> | |
* @date 2014 - 2014 | |
* @license CC 0 or Public Domain | |
*/ | |
var assert; | |
if (this.require) { | |
assert = require('assert'); | |
} else { | |
assert = function () {}; | |
assert.throws = function () {}; | |
} | |
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith | |
if (!String.prototype.endsWith) { | |
Object.defineProperty(String.prototype, 'endsWith', { | |
enumerable: false, | |
configurable: false, | |
writable: false, | |
value: function (searchString, position) { | |
position = position || this.length; | |
position = position - searchString.length; | |
if (position < 0) { position = 0; } // MDN's polyfill has a bug. | |
return this.lastIndexOf(searchString) === position; | |
} | |
}); | |
} | |
if (this.document) { | |
console.log = function (obj) { | |
var node = document.getElementById('console'); | |
node.innerHTML += '<div>'+obj+'</div>'; | |
}; | |
console.error = function (err) { | |
var node = document.getElementById('console'); | |
node.innerHTML += '<div class="error">'+err+'</div>'; | |
}; | |
} | |
(function (global) { | |
'use strict'; | |
// https://plus.google.com/u/0/+InoueSachirou/posts/cGtJLikXC4P | |
// https://gist.github.com/SirAnthony/e1c7b9522e7ab02c6679 | |
// http://jsperf.com/typeof-v-s-instanceof | |
var typeCheckers = { | |
string : function (obj) { return 'string' === typeof obj || obj instanceof String; }, | |
'String' : function (obj) { return 'string' === typeof obj || obj instanceof String; }, | |
number : function (obj) { return 'number' === typeof obj || obj instanceof Number; }, | |
'Number' : function (obj) { return 'number' === typeof obj || obj instanceof Number; }, | |
'boolean' : function (obj) { return 'boolean' === typeof obj || obj instanceof Boolean; }, | |
'Boolean' : function (obj) { return 'boolean' === typeof obj || obj instanceof Boolean; }, | |
'function' : function (obj) { return 'function' === typeof obj; }, | |
object : function (obj) { return 'object' === typeof obj; }, | |
array : Array.isArray, | |
}; | |
function checkType(obj/*:object?*/, type/*:string?*/)/*:undefined*/ { | |
var constructor, i = 0, iz = 0; | |
if (!type) { return; } | |
if (type[type.length - 1] === '?') { | |
if (null === obj || void 0 === obj) { return; } | |
type = type.slice(0, type.length - 1); | |
} else if (null === obj || void 0 === obj) { | |
throw new TypeError(obj+' is not a '+type); | |
} | |
if (type.endsWith('[]')) { | |
if (!Array.isArray(obj)) { | |
throw new TypeError(obj+' is not a '+type); | |
} | |
type = type.slice(0, type.length - 2); | |
for (i = 0, iz = obj.length; i < iz; ++i) { | |
checkType(obj[i], type); | |
} | |
return; | |
} | |
if (typeCheckers[type]) { | |
if (typeCheckers[type](obj)) { return; } | |
throw new TypeError(obj+' is not a '+type); | |
} | |
while (obj) { | |
if (obj.constructor.name === type) { return; } | |
obj = obj.__proto__; | |
} | |
throw new TypeError(obj+' is not a '+type); | |
} | |
// {{{!test | |
(function () { | |
checkType('A', 'string'); | |
checkType('', 'string'); | |
checkType(String(''), 'string'); | |
checkType(new String(''), 'string'); | |
checkType('A', 'String'); | |
checkType('', 'String'); | |
checkType(String(''), 'String'); | |
checkType(new String(''), 'String'); | |
checkType(1, 'number'); | |
checkType(0, 'number'); | |
checkType(Number(0), 'number'); | |
checkType(new Number(0), 'number'); | |
checkType(NaN, 'number'); | |
checkType(Infinity, 'number'); | |
checkType(1, 'Number'); | |
checkType(0, 'Number'); | |
checkType(Number(0), 'Number'); | |
checkType(new Number(0), 'Number'); | |
checkType(NaN, 'Number'); | |
checkType(Infinity, 'Number'); | |
checkType(true, 'boolean'); | |
checkType(false, 'boolean'); | |
checkType(Boolean(false), 'boolean'); | |
checkType(new Boolean(false), 'boolean'); | |
checkType(true, 'Boolean'); | |
checkType(false, 'Boolean'); | |
checkType(Boolean(false), 'Boolean'); | |
checkType(new Boolean(false), 'Boolean'); | |
checkType(function () {}, 'function'); | |
checkType(Function(''), 'function'); | |
checkType(new Function(''), 'function'); | |
checkType(function () {}, 'Function'); | |
checkType(Function(''), 'Function'); | |
checkType(new Function(''), 'Function'); | |
checkType({}, 'object'); | |
checkType(Object(), 'object'); | |
checkType(new Object(), 'object'); | |
checkType({}, 'Object'); | |
checkType(Object(), 'Object'); | |
checkType(new Object(), 'Object'); | |
checkType([], 'array'); | |
checkType(Array(), 'array'); | |
checkType(new Array(), 'array'); | |
checkType([], 'Array'); | |
checkType(Array(), 'Array'); | |
checkType(new Array(), 'Array'); | |
checkType(/r/, 'RegExp'); | |
checkType(RegExp(''), 'RegExp'); | |
checkType(new RegExp(''), 'RegExp'); | |
function Parent() { } | |
function Child() { Parent.call(this); } | |
Child.prototype = Object.create(Parent.prototype); | |
Child.prototype.constructor = Child; | |
checkType(new Parent, 'Parent'); | |
checkType(new Child, 'Child'); | |
checkType(new Child, 'Parent'); | |
assert.throws(function () { checkType(0, 'string'); }, TypeError); | |
assert.throws(function () { checkType(new Parent, 'Child'); }, TypeError); | |
assert.throws(function () { checkType(null, 'number'); }, TypeError); | |
checkType(null, 'number?'); | |
checkType(void 0, 'number?'); | |
checkType(0, 'number?'); | |
checkType([0], 'number[]'); | |
checkType([], 'number[]'); | |
assert.throws(function () { checkType(0, 'number[]'); }, TypeError); | |
assert.throws(function () { checkType([0, ''], 'number[]'); }, TypeError); | |
}()); | |
// }}}!test | |
function typed(func/*:function*/)/*:function*/ { | |
var params, returnType; | |
params = func.toString(). | |
replace(/\r?\n/g, ' '). | |
match(/function[^\(]*\(([^\)]*)\)/)[1]. | |
split(','). | |
map(function (param) { | |
var m; | |
m = param.trim().match(/\/\*:([\w?]+)\*\/$/); | |
return m ? m[1] : void 0; | |
}); | |
returnType = func.toString().match(/function[^\(]*\([^\)]*\)([^{]*){/)[1]. | |
trim(). | |
match(/\/\*:([\w?]+)\*\//); | |
returnType = returnType ? returnType[1] : void 0; | |
return function () { | |
var result, i = 0, iz = 0; | |
for (i = 0, iz = arguments.length; i < iz; ++i) { | |
checkType(arguments[i], params[i]); | |
} | |
result = func.apply(null, arguments); | |
checkType(result, returnType); | |
return result; | |
}; | |
} | |
global.typed = typed; | |
}(this.self || global)); | |
// ==================== ここから使用例 ==================== | |
var f = typed(function (a/*:string*/)/*:number*/ { | |
return a; | |
}); | |
try { f('str'); } catch (err) { console.error(err); } | |
try { f(42); } catch (err) { console.error(err); } | |
var g = typed(function (a/*:number*/, b/*:number?*/)/*:number*/ { | |
b = b || 0; | |
return a + b; | |
}); | |
console.log(g(5, 6)); | |
console.log(g(5)); | |
try { g(5, '') } catch (err) { console.error(err); } | |
function A() { | |
this.a = 42; | |
} | |
A.prototype.toString = function () { | |
return 'A{a='+this.a+'}'; | |
}; | |
var h = typed(function (a/*:A*/)/*:A*/ { | |
return a; | |
}); | |
console.log(h(new A())); | |
try { h(42); } catch (err) { console.error(err); } | |
try { h(null); } catch (err) { console.error(err); } | |
try { h(); } catch (err) { console.error(err); } | |
// vim:fdm=marker: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment