From 3fcf83df920512727eb158e7a8d4ed6731be0983 Mon Sep 17 00:00:00 2001 From: Patrick Dubroy Date: Sun, 29 Mar 2015 13:41:19 +0200 Subject: [PATCH] Convert all CoffeeScript source files to JS, and clean up project dir. --- .eslintrc | 60 +++ {lib => dist}/moonchild-bundle.js | 644 ++++++++++++++---------------- editor/index.html | 2 +- lib/metadata.js | 186 ++++----- lib/moonchild.js | 346 ++++++++-------- main.js | 1 + metadata.coffee | 77 ---- moonchild.coffee | 144 ------- package.json | 26 +- test/test-metadata.js | 13 +- test/test-moonchild.js | 6 +- 11 files changed, 664 insertions(+), 841 deletions(-) create mode 100644 .eslintrc rename {lib => dist}/moonchild-bundle.js (95%) create mode 100644 main.js delete mode 100644 metadata.coffee delete mode 100644 moonchild.coffee diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..206d20a --- /dev/null +++ b/.eslintrc @@ -0,0 +1,60 @@ +{ + // To minimize dependencies on Node- or browser-specific features, leave the + // env empty, and instead define globals as needed. + "env": {}, + + // Project-wide globals. If other globals are necessary, prefer putting them + // in a comment at the top of the file rather than adding them here. + "globals": { + "console": true, + "module": true, + "require": true, + }, + "rules": { + // Enforce "one true brace style", allowing start and end braces to be + // on the same line. + "brace-style": [2, "1tbs", {"allowSingleLine": true}], + + // Enforce the name 'self' when assigning `this` to a local variable. + "consistent-this": [0, "self"], + + // Allow braces to be omitted from `if` statements, etc. + curly: [0], + + // Enforce two-space indentation. + "indent": [2, 2, {indentSwitchCase: true}], + + // Enforce the use of strict mode at the file level. + "global-strict": [2, "always"], + + // Allow things like `while(true)`. + "no-constant-condition": 0, + + // Allow variable shadowing. + "no-shadow": 0, + + // Restrict what kind of objects can be used with 'throw'. + "no-throw-literal": 2, + + // Allow identifiers with leading or trailing underscores. + "no-underscore-dangle": 0, + + // Allow unused parameters, but not unused variables. + "no-unused-vars": [2, {"vars": "all", "args": "none"}], + + // Allow functions to be used before they are defined. + "no-use-before-define": [2, "nofunc"], + + // Use single quotes, except when escaping would be necessary. + "quotes": [2, "single", "avoid-escape"], + + // Don't require function-level strict mode, because global strict mode + // is already enforced. + "strict": 0, + + // Force IIFEs to be wrapped in parentheses. + "wrap-iife": [2, "inside"], + + "yoda": [2, "never", {"exceptRange": true}] + } +} diff --git a/lib/moonchild-bundle.js b/dist/moonchild-bundle.js similarity index 95% rename from lib/moonchild-bundle.js rename to dist/moonchild-bundle.js index 2377d5d..7a933e5 100644 --- a/lib/moonchild-bundle.js +++ b/dist/moonchild-bundle.js @@ -1,355 +1,291 @@ !function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var o;"undefined"!=typeof window?o=window:"undefined"!=typeof global?o=global:"undefined"!=typeof self&&(o=self),o.Moonchild=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 && !attachToPreviousNode(prev, metadata)) { - return ast.metadata = metadata[0]; - } - }; - - attachToRootNode = function(node, metadata) { - if (node.loc.start.line > metadata[0].loc.end.line + 1) { - return node.metadata = metadata.shift(); - } - }; - - attachToPreviousNode = function(node, metadata) { - if ((node != null) && followsOnLine(node, metadata[0])) { - return node.metadata = metadata.shift(); } - }; - - attachToFollowingNode = function(node, metadata) { - if (node.range[0] > metadata[0].range[1]) { - return node.metadata = metadata.shift(); - } - }; - - followsOnLine = function(first, second) { - return first.range[1] < second.range[0] && first.loc.end.line === second.loc.start.line; - }; - - containsMetadata = function(commentNode) { - return commentNode.value[0] === MAGIC_CHAR; - }; - - _ = _dereq_('underscore'); - - esprima = _dereq_('esprima'); - - estraverse = _dereq_('estraverse'); - - module.exports = { - parse: parse - }; - - if ((typeof _dereq_ !== "undefined" && _dereq_ !== null ? _dereq_.main : void 0) === module) { - parse(_dereq_('fs').readFileSync(process.argv[2])); + }); + if (metadata.length > 0 && !attachToPreviousNode(prev, metadata)) { + ast.metadata = metadata[0]; } +} -}).call(this); - -}).call(this,_dereq_("FWaASH")) -},{"FWaASH":4,"esprima":21,"estraverse":22,"fs":3,"underscore":24}],2:[function(_dereq_,module,exports){ -// Generated by CoffeeScript 1.7.1 -(function() { - var Extension, addHook, applySafely, estraverse, expanders, exportsExpander, getEditor, getHookArgs, globalEditor, globalExtensions, globalHooks, initializeExtension, invokeHook, onChange, parse, parser, registerExtension, setEditor, widgetExpander, _; - - parser = _dereq_('./metadata'); - - _ = _dereq_('underscore'); - - estraverse = _dereq_('estraverse'); - - expanders = _dereq_('expanders'); - - globalHooks = {}; - - globalExtensions = {}; - - globalEditor = {}; - - widgetExpander = expanders.createExpander('displayWidget'); - - exportsExpander = expanders.createExpander('extensionId'); +// Attach the next piece of metadata to `node` if there is at least one line +// between the end of the metadata and the beginning of the node. +function attachToRootNode(node, metadata) { + if (node.loc.start.line > metadata[0].loc.end.line + 1) { + node.metadata = metadata.shift(); + return true; + } + return false; +} - Extension = (function() { - function Extension(id) { - if (id in globalExtensions) { - throw new Error("An extension named '" + id + "' is already registered"); - } - this._id = id || _.uniqueId('ext-'); - this._hooks = {}; - this._expander = expanders.createExpander('extras'); - this.on = _.partial(addHook, this._id, globalHooks); - } +// Attach the next piece of metadata to `node` if the metadata begins on the +// same line that `node` ends on. +function attachToPreviousNode(node, metadata) { + if ((node != null) && followsOnLine(node, metadata[0])) { + node.metadata = metadata.shift(); + return true; + } + return false; +} - Extension.prototype.addWidget = function(pos, node, type, userData) { - if (widgetExpander.has(node, 'displayWidget')) { - throw new Error('Conflicting widgets on node'); - } - widgetExpander.set(node, 'displayWidget', { - type: type, - pos: pos, - data: userData - }); - }; +// Attach the next piece of metadata to `node` if the node follows the metadata. +function attachToFollowingNode(node, metadata) { + if (node.range[0] > metadata[0].range[1]) { + node.metadata = metadata.shift(); + return true; + } + return false; +} - Extension.prototype.getWidget = function(node) { - return widgetExpander.get(node, 'displayWidget'); - }; +// Returns true if `second` comes after `first`, and starts on the same line as +// `first` ends. +function followsOnLine(first, second) { + return first.range[1] < second.range[0] && first.loc.end.line === second.loc.start.line; +} - Extension.prototype.setExtras = function(node, data) { - return this._expander.set(node, 'extras', data); - }; +// Returns true if the given comment node contains metadata. +function containsMetadata(commentNode) { + return commentNode.value[0] === MAGIC_CHAR; +} - Extension.prototype.getExtras = function(node, ext) { - var exp, id; - exp = ext ? (id = exportsExpander.get(ext, 'extensionId'), globalExtensions[id]._expander) : this._expander; - return exp.get(node, 'extras'); - }; +module.exports = { + parse: parse +}; - Extension.prototype.BEFORE = 'before'; +},{"esprima":22,"estraverse":23,"underscore":25}],2:[function(_dereq_,module,exports){ +'use strict'; - Extension.prototype.AFTER = 'after'; +var _ = _dereq_('underscore'), + parser = _dereq_('./metadata'), + estraverse = _dereq_('estraverse'), + expanders = _dereq_('expanders'); - Extension.prototype.REPLACE = 'replace'; +var globalHooks = {}, + globalExtensions = {}, + globalEditor = {}; - return Extension; +var widgetExpander = expanders.createExpander('displayWidget'); +var exportsExpander = expanders.createExpander('extensionId'); - })(); +// Encapsulates the extension API that is provided to a single extension. +function Extension(id) { + if (id in globalExtensions) { + throw new Error("An extension named '" + id + "' is already registered"); + } + this._id = id || _.uniqueId('ext-'); + this._hooks = {}; + this._expander = expanders.createExpander('extras'); + this.on = _.partial(addHook, this._id, globalHooks); +} - addHook = function(id, hookState, hookName, func) { - var hooks; - if (id == null) { - id = _.uniqueId('hook-'); - } - hooks = hookState[hookName] != null ? hookState[hookName] : hookState[hookName] = {}; - if (hooks[id] == null) { - hooks[id] = []; - } - hooks[id].push(func); - }; +Extension.prototype.addWidget = function(pos, node, type, userData) { + // TODO: Figure out how to handle this. + if (widgetExpander.has(node, 'displayWidget')) { + throw new Error('Conflicting widgets on node'); + } + widgetExpander.set(node, 'displayWidget', { + type: type, + pos: pos, + data: userData + }); +}; - invokeHook = function(hook, args) { - return _.each(globalHooks[hook], function(hookFns, id) { - return _.each(hookFns, function(fn) { - return applySafely(fn, args); - }); - }); - }; +Extension.prototype.getWidget = function(node) { + // TODO: Should this only be exposed to certain types of extensions? + return widgetExpander.get(node, 'displayWidget'); +}; - initializeExtension = function(ext, deps, initFn) { - var result; - result = initFn != null ? initFn.apply(null, [ext].concat(deps)) : void 0; - if (result != null) { - if (_.isObject(result)) { - return result; - } - throw new TypeError('Invalid export from extension (must be an object)'); - } - return {}; - }; +Extension.prototype.setExtras = function(node, data) { + return this._expander.set(node, 'extras', data); +}; - registerExtension = function(id, deps, initFn) { - var ext; - if (!_.isArray(deps)) { - initFn = deps; - deps = []; - } - deps = deps.map(function(name) { - if (!(name in globalExtensions)) { - throw new Error("Unmet dependency " + name); - } - return globalExtensions[name].exports; - }); - ext = new Extension(id); - ext.exports = initializeExtension(ext, deps, initFn); - exportsExpander.set(ext.exports, 'extensionId', ext._id); - return globalExtensions[ext._id] = ext; - }; +Extension.prototype.getExtras = function(node, ext) { + // If `ext` is defined, it's the exports from an extension. Use that + // object to find the actual extension. + var exp, id; + exp = ext ? (id = exportsExpander.get(ext, 'extensionId'), globalExtensions[id]._expander) : this._expander; + return exp.get(node, 'extras'); +}; - parse = function(hooks, source) { - var tree; - tree = parser.parse(source); - invokeHook('parse', getHookArgs(tree)); - return tree; - }; +// Constants for the `pos` argument to `addWidget`. +Extension.prototype.BEFORE = 'before'; +Extension.prototype.AFTER = 'after'; +Extension.prototype.REPLACE = 'replace'; + +// Allows a client to hook the action named `hookName`. Every time the action +// performed, `visitor` will be called with the hook-specific arguments. +function addHook(id, hookState, hookName, func) { + var hooks; + if (id == null) { + id = _.uniqueId('hook-'); + } + hooks = hookState[hookName] != null ? hookState[hookName] : hookState[hookName] = {}; + if (hooks[id] == null) { + hooks[id] = []; + } + hooks[id].push(func); +} - getHookArgs = function(ast) { - var nodes; - nodes = []; - estraverse.traverse(ast, { - enter: function(node) { - return nodes.push(node); - } +function invokeHook(hook, args) { + return _.each(globalHooks[hook], function(hookFns, id) { + return _.each(hookFns, function(fn) { + return applySafely(fn, args); }); - return [_.chain(nodes), _.chain(ast.comments)]; - }; + }); +} - applySafely = function(func, args) { - var e; - try { - return func.apply(null, args); - } catch (_error) { - e = _error; - return console.log(e.stack || e); +function initializeExtension(ext, deps, initFn) { + var result; + result = initFn != null ? initFn.apply(null, [ext].concat(deps)) : void 0; + if (result != null) { + if (_.isObject(result)) { + return result; } - }; - - onChange = function(newValue) { - var e, hookArgs, tree; - try { - tree = parse(globalHooks, newValue); - } catch (_error) { - e = _error; - console.log(e); - return; - } - hookArgs = getHookArgs(tree); - invokeHook('display', hookArgs); - return invokeHook('render', hookArgs); - }; - - setEditor = function(editor) { - return globalEditor = editor; - }; - - getEditor = function() { - return globalEditor; - }; - - module.exports = { - on: _.partial(addHook, null, globalHooks), - onChange: onChange, - parse: parse, - registerExtension: registerExtension, - traverse: estraverse.traverse, - setEditor: setEditor, - getEditor: getEditor - }; - -}).call(this); - -},{"./metadata":1,"estraverse":22,"expanders":23,"underscore":24}],3:[function(_dereq_,module,exports){ - -},{}],4:[function(_dereq_,module,exports){ -// shim for using process in browser - -var process = module.exports = {}; - -process.nextTick = (function () { - var canSetImmediate = typeof window !== 'undefined' - && window.setImmediate; - var canPost = typeof window !== 'undefined' - && window.postMessage && window.addEventListener - ; + throw new TypeError('Invalid export from extension (must be an object)'); + } + return {}; +} - if (canSetImmediate) { - return function (f) { return window.setImmediate(f) }; +function registerExtension(id, deps, initFn) { + // Allow extensions with no explicit dependencies. + if (!_.isArray(deps)) { + initFn = deps; + deps = []; + } + deps = deps.map(function(name) { + if (!(name in globalExtensions)) { + throw new Error('Unmet dependency ' + name); } + return globalExtensions[name].exports; + }); + var ext = new Extension(id); + ext.exports = initializeExtension(ext, deps, initFn); + // Allow the exports object can be traced back to the extension itself. + exportsExpander.set(ext.exports, 'extensionId', ext._id); + globalExtensions[ext._id] = ext; + return ext; +} - if (canPost) { - var queue = []; - window.addEventListener('message', function (ev) { - var source = ev.source; - if ((source === window || source === null) && ev.data === 'process-tick') { - ev.stopPropagation(); - if (queue.length > 0) { - var fn = queue.shift(); - fn(); - } - } - }, true); +function parse(hooks, source) { + var tree = parser.parse(source); + invokeHook('parse', getHookArgs(tree)); + return tree; +} - return function nextTick(fn) { - queue.push(fn); - window.postMessage('process-tick', '*'); - }; +function getHookArgs(ast) { + // For API convenience, the tree is currently passed as an + // Underscore-wrapped list of nodes, but this should change. + var nodes = []; + estraverse.traverse(ast, { + enter: function(node) { + return nodes.push(node); } + }); + return [_.chain(nodes), _.chain(ast.comments)]; +} - return function nextTick(fn) { - setTimeout(fn, 0); - }; -})(); - -process.title = 'browser'; -process.browser = true; -process.env = {}; -process.argv = []; +function applySafely(func, args) { + try { + return func.apply(null, args); + } catch (e) { + return console.log(e.stack || e); // eslint-disable-line no-console + } +} -function noop() {} +function onChange(newValue) { + var tree; + try { + tree = parse(globalHooks, newValue); + } catch (e) { + console.log(e); // eslint-disable-line no-console + return; + } + // Run the display hooks. + // TODO: This should be moved into a function that can be invoked by the + // editor plugin. + var hookArgs = getHookArgs(tree); + invokeHook('display', hookArgs); + + // Run the render hooks. + invokeHook('render', hookArgs); +} -process.on = noop; -process.addListener = noop; -process.once = noop; -process.off = noop; -process.removeListener = noop; -process.removeAllListeners = noop; -process.emit = noop; +function setEditor(editor) { + globalEditor = editor; + return editor; +} -process.binding = function (name) { - throw new Error('process.binding is not supported'); +function getEditor() { + return globalEditor; } -// TODO(shtylman) -process.cwd = function () { return '/' }; -process.chdir = function (dir) { - throw new Error('process.chdir is not supported'); +module.exports = { + on: _.partial(addHook, null, globalHooks), + onChange: onChange, // TODO: Get rid of this. + parse: parse, + registerExtension: registerExtension, + traverse: estraverse.traverse, + setEditor: setEditor, + getEditor: getEditor }; -},{}],5:[function(_dereq_,module,exports){ +},{"./metadata":1,"estraverse":23,"expanders":24,"underscore":25}],3:[function(_dereq_,module,exports){ +module.exports = _dereq_('./lib/moonchild'); + +},{"./lib/moonchild":2}],4:[function(_dereq_,module,exports){ 'use strict'; module.exports = _dereq_('./is-implemented')() ? Symbol : _dereq_('./polyfill'); -},{"./is-implemented":6,"./polyfill":20}],6:[function(_dereq_,module,exports){ +},{"./is-implemented":5,"./polyfill":20}],5:[function(_dereq_,module,exports){ 'use strict'; module.exports = function () { @@ -361,7 +297,6 @@ module.exports = function () { // Return 'true' for polyfills if (typeof Symbol.isConcatSpreadable !== 'object') return false; - if (typeof Symbol.isRegExp !== 'object') return false; if (typeof Symbol.iterator !== 'object') return false; if (typeof Symbol.toPrimitive !== 'object') return false; if (typeof Symbol.toStringTag !== 'object') return false; @@ -370,6 +305,13 @@ module.exports = function () { return true; }; +},{}],6:[function(_dereq_,module,exports){ +'use strict'; + +module.exports = function (x) { + return (x && ((typeof x === 'symbol') || (x['@@toStringTag'] === 'Symbol'))) || false; +}; + },{}],7:[function(_dereq_,module,exports){ 'use strict'; @@ -566,59 +508,93 @@ module.exports = function (searchString/*, position*/) { },{}],20:[function(_dereq_,module,exports){ 'use strict'; -var d = _dereq_('d') +var d = _dereq_('d') + , validateSymbol = _dereq_('./validate-symbol') , create = Object.create, defineProperties = Object.defineProperties - , generateName, Symbol; + , defineProperty = Object.defineProperty, objPrototype = Object.prototype + , Symbol, HiddenSymbol, globalSymbols = create(null); -generateName = (function () { +var generateName = (function () { var created = create(null); return function (desc) { - var postfix = 0; + var postfix = 0, name; while (created[desc + (postfix || '')]) ++postfix; desc += (postfix || ''); created[desc] = true; - return '@@' + desc; + name = '@@' + desc; + defineProperty(objPrototype, name, d.gs(null, function (value) { + defineProperty(this, name, d(value)); + })); + return name; }; }()); -module.exports = Symbol = function (description) { +HiddenSymbol = function Symbol(description) { + if (this instanceof HiddenSymbol) throw new TypeError('TypeError: Symbol is not a constructor'); + return Symbol(description); +}; +module.exports = Symbol = function Symbol(description) { var symbol; - if (this instanceof Symbol) { - throw new TypeError('TypeError: Symbol is not a constructor'); - } - symbol = create(Symbol.prototype); + if (this instanceof Symbol) throw new TypeError('TypeError: Symbol is not a constructor'); + symbol = create(HiddenSymbol.prototype); description = (description === undefined ? '' : String(description)); return defineProperties(symbol, { __description__: d('', description), __name__: d('', generateName(description)) }); }; - -Object.defineProperties(Symbol, { - create: d('', Symbol('create')), +defineProperties(Symbol, { + for: d(function (key) { + if (globalSymbols[key]) return globalSymbols[key]; + return (globalSymbols[key] = Symbol(String(key))); + }), + keyFor: d(function (s) { + var key; + validateSymbol(s); + for (key in globalSymbols) if (globalSymbols[key] === s) return key; + }), hasInstance: d('', Symbol('hasInstance')), isConcatSpreadable: d('', Symbol('isConcatSpreadable')), - isRegExp: d('', Symbol('isRegExp')), iterator: d('', Symbol('iterator')), + match: d('', Symbol('match')), + replace: d('', Symbol('replace')), + search: d('', Symbol('search')), + species: d('', Symbol('species')), + split: d('', Symbol('split')), toPrimitive: d('', Symbol('toPrimitive')), toStringTag: d('', Symbol('toStringTag')), unscopables: d('', Symbol('unscopables')) }); +defineProperties(HiddenSymbol.prototype, { + constructor: d(Symbol), + toString: d('', function () { return this.__name__; }) +}); defineProperties(Symbol.prototype, { - properToString: d(function () { - return 'Symbol (' + this.__description__ + ')'; - }), - toString: d('', function () { return this.__name__; }) + toString: d(function () { return 'Symbol (' + validateSymbol(this).__description__ + ')'; }), + valueOf: d(function () { return validateSymbol(this); }) }); -Object.defineProperty(Symbol.prototype, Symbol.toPrimitive, d('', - function (hint) { - throw new TypeError("Conversion of symbol objects is not allowed"); - })); -Object.defineProperty(Symbol.prototype, Symbol.toStringTag, d('c', 'Symbol')); +defineProperty(Symbol.prototype, Symbol.toPrimitive, d('', + function () { return validateSymbol(this); })); +defineProperty(Symbol.prototype, Symbol.toStringTag, d('c', 'Symbol')); -},{"d":7}],21:[function(_dereq_,module,exports){ +defineProperty(HiddenSymbol.prototype, Symbol.toPrimitive, + d('c', Symbol.prototype[Symbol.toPrimitive])); +defineProperty(HiddenSymbol.prototype, Symbol.toStringTag, + d('c', Symbol.prototype[Symbol.toStringTag])); + +},{"./validate-symbol":21,"d":7}],21:[function(_dereq_,module,exports){ +'use strict'; + +var isSymbol = _dereq_('./is-symbol'); + +module.exports = function (value) { + if (!isSymbol(value)) throw new TypeError(value + " is not a symbol"); + return value; +}; + +},{"./is-symbol":6}],22:[function(_dereq_,module,exports){ /* Copyright (C) 2013 Ariya Hidayat Copyright (C) 2013 Thaddee Tyl @@ -4450,7 +4426,7 @@ parseStatement: true, parseSourceElement: true */ })); /* vim: set sw=4 ts=4 et tw=80 : */ -},{}],22:[function(_dereq_,module,exports){ +},{}],23:[function(_dereq_,module,exports){ /* Copyright (C) 2012-2013 Yusuke Suzuki Copyright (C) 2012 Ariya Hidayat @@ -5141,7 +5117,7 @@ parseStatement: true, parseSourceElement: true */ })); /* vim: set sw=4 ts=4 et tw=80 : */ -},{}],23:[function(_dereq_,module,exports){ +},{}],24:[function(_dereq_,module,exports){ 'use strict'; var Symbol = _dereq_('es6-symbol'); @@ -5294,7 +5270,7 @@ module.exports = { compose: compose }; -},{"es6-symbol":5}],24:[function(_dereq_,module,exports){ +},{"es6-symbol":4}],25:[function(_dereq_,module,exports){ // Underscore.js 1.8.2 // http://underscorejs.org // (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors @@ -6832,6 +6808,6 @@ module.exports = { } }.call(this)); -},{}]},{},[2]) -(2) +},{}]},{},[3]) +(3) }); \ No newline at end of file diff --git a/editor/index.html b/editor/index.html index b425c51..9857170 100644 --- a/editor/index.html +++ b/editor/index.html @@ -191,7 +191,7 @@ - + diff --git a/lib/metadata.js b/lib/metadata.js index a3840fd..ccc33b2 100644 --- a/lib/metadata.js +++ b/lib/metadata.js @@ -1,94 +1,100 @@ -// Generated by CoffeeScript 1.7.1 -(function() { - var MAGIC_CHAR, attachMetadata, attachToFollowingNode, attachToPreviousNode, attachToRootNode, containsMetadata, esprima, estraverse, followsOnLine, parse, parseMetadata, parseOptions, _; - - MAGIC_CHAR = '^'; - - parseOptions = { - comment: true, - loc: true, - range: true - }; - - parse = function(source) { - var ast, metadata; - ast = esprima.parse(source, parseOptions); - metadata = _.map(_.filter(ast.comments, containsMetadata), parseMetadata); - attachMetadata(ast, metadata); - return ast; - }; - - parseMetadata = function(commentNode) { - var value; - try { - value = eval('(' + commentNode.value.slice(1) + ')'); - } catch (_error) { - - } - commentNode.contents = commentNode.value; - commentNode.value = _.isObject(value) ? value : void 0; - return commentNode; - }; - - attachMetadata = function(ast, metadata) { - var prev; - prev = null; - estraverse.traverse(ast, { - enter: function(node, parent) { - if (metadata.length === 0) { - return estraverse.VisitorOption.Break; - } - if (node === ast) { - return attachToRootNode(ast, metadata); - } else { - attachToPreviousNode(prev, metadata) || attachToFollowingNode(node, metadata); - return prev = node; +'use strict'; + +var _ = require('underscore'), + esprima = require('esprima'), + estraverse = require('estraverse'); + +var MAGIC_CHAR = '^'; + +var parseOptions = { + comment: true, // Preserve comments. + loc: true, // Nodes include line- and column-based location info. + range: true // Nodes have an index-based location range (array). +}; + +function parse(source) { + var ast = esprima.parse(source, parseOptions); + var metadata = _.map(_.filter(ast.comments, containsMetadata), parseMetadata); + attachMetadata(ast, metadata); + return ast; +} + +function parseMetadata(commentNode) { + var value; + try { + // TODO: Don't be evil. + value = eval('(' + commentNode.value.slice(1) + ')'); // eslint-disable-line no-eval + } catch (e) {} // eslint-disable-line no-empty + commentNode.contents = commentNode.value; + commentNode.value = _.isObject(value) ? value : void 0; + return commentNode; +} + +// Traverses the AST and attaches the metadata. Uses a pre-order traversal +// so that metadata will be attached to the highest node that it applies to. +function attachMetadata(ast, metadata) { + // TODO: Handle multiple pieces of metadata per node. + var prev = null; + estraverse.traverse(ast, { + enter: function(node, parent) { + if (metadata.length === 0) { + return estraverse.VisitorOption.Break; + } + if (node === ast) { + attachToRootNode(ast, metadata); + } else { + if (!attachToPreviousNode(prev, metadata)) { + attachToFollowingNode(node, metadata); } + prev = node; } - }); - if (metadata.length > 0 && !attachToPreviousNode(prev, metadata)) { - return ast.metadata = metadata[0]; - } - }; - - attachToRootNode = function(node, metadata) { - if (node.loc.start.line > metadata[0].loc.end.line + 1) { - return node.metadata = metadata.shift(); - } - }; - - attachToPreviousNode = function(node, metadata) { - if ((node != null) && followsOnLine(node, metadata[0])) { - return node.metadata = metadata.shift(); } - }; - - attachToFollowingNode = function(node, metadata) { - if (node.range[0] > metadata[0].range[1]) { - return node.metadata = metadata.shift(); - } - }; - - followsOnLine = function(first, second) { - return first.range[1] < second.range[0] && first.loc.end.line === second.loc.start.line; - }; - - containsMetadata = function(commentNode) { - return commentNode.value[0] === MAGIC_CHAR; - }; - - _ = require('underscore'); - - esprima = require('esprima'); - - estraverse = require('estraverse'); - - module.exports = { - parse: parse - }; - - if ((typeof require !== "undefined" && require !== null ? require.main : void 0) === module) { - parse(require('fs').readFileSync(process.argv[2])); + }); + if (metadata.length > 0 && !attachToPreviousNode(prev, metadata)) { + ast.metadata = metadata[0]; } - -}).call(this); +} + +// Attach the next piece of metadata to `node` if there is at least one line +// between the end of the metadata and the beginning of the node. +function attachToRootNode(node, metadata) { + if (node.loc.start.line > metadata[0].loc.end.line + 1) { + node.metadata = metadata.shift(); + return true; + } + return false; +} + +// Attach the next piece of metadata to `node` if the metadata begins on the +// same line that `node` ends on. +function attachToPreviousNode(node, metadata) { + if ((node != null) && followsOnLine(node, metadata[0])) { + node.metadata = metadata.shift(); + return true; + } + return false; +} + +// Attach the next piece of metadata to `node` if the node follows the metadata. +function attachToFollowingNode(node, metadata) { + if (node.range[0] > metadata[0].range[1]) { + node.metadata = metadata.shift(); + return true; + } + return false; +} + +// Returns true if `second` comes after `first`, and starts on the same line as +// `first` ends. +function followsOnLine(first, second) { + return first.range[1] < second.range[0] && first.loc.end.line === second.loc.start.line; +} + +// Returns true if the given comment node contains metadata. +function containsMetadata(commentNode) { + return commentNode.value[0] === MAGIC_CHAR; +} + +module.exports = { + parse: parse +}; diff --git a/lib/moonchild.js b/lib/moonchild.js index 182c11e..0ebf361 100644 --- a/lib/moonchild.js +++ b/lib/moonchild.js @@ -1,179 +1,175 @@ -// Generated by CoffeeScript 1.7.1 -(function() { - var Extension, addHook, applySafely, estraverse, expanders, exportsExpander, getEditor, getHookArgs, globalEditor, globalExtensions, globalHooks, initializeExtension, invokeHook, onChange, parse, parser, registerExtension, setEditor, widgetExpander, _; - - parser = require('./metadata'); - - _ = require('underscore'); - - estraverse = require('estraverse'); - - expanders = require('expanders'); - - globalHooks = {}; - - globalExtensions = {}; - - globalEditor = {}; - - widgetExpander = expanders.createExpander('displayWidget'); - - exportsExpander = expanders.createExpander('extensionId'); - - Extension = (function() { - function Extension(id) { - if (id in globalExtensions) { - throw new Error("An extension named '" + id + "' is already registered"); - } - this._id = id || _.uniqueId('ext-'); - this._hooks = {}; - this._expander = expanders.createExpander('extras'); - this.on = _.partial(addHook, this._id, globalHooks); - } - - Extension.prototype.addWidget = function(pos, node, type, userData) { - if (widgetExpander.has(node, 'displayWidget')) { - throw new Error('Conflicting widgets on node'); - } - widgetExpander.set(node, 'displayWidget', { - type: type, - pos: pos, - data: userData - }); - }; - - Extension.prototype.getWidget = function(node) { - return widgetExpander.get(node, 'displayWidget'); - }; - - Extension.prototype.setExtras = function(node, data) { - return this._expander.set(node, 'extras', data); - }; - - Extension.prototype.getExtras = function(node, ext) { - var exp, id; - exp = ext ? (id = exportsExpander.get(ext, 'extensionId'), globalExtensions[id]._expander) : this._expander; - return exp.get(node, 'extras'); - }; - - Extension.prototype.BEFORE = 'before'; - - Extension.prototype.AFTER = 'after'; - - Extension.prototype.REPLACE = 'replace'; - - return Extension; - - })(); - - addHook = function(id, hookState, hookName, func) { - var hooks; - if (id == null) { - id = _.uniqueId('hook-'); - } - hooks = hookState[hookName] != null ? hookState[hookName] : hookState[hookName] = {}; - if (hooks[id] == null) { - hooks[id] = []; - } - hooks[id].push(func); - }; - - invokeHook = function(hook, args) { - return _.each(globalHooks[hook], function(hookFns, id) { - return _.each(hookFns, function(fn) { - return applySafely(fn, args); - }); +'use strict'; + +var _ = require('underscore'), + parser = require('./metadata'), + estraverse = require('estraverse'), + expanders = require('expanders'); + +var globalHooks = {}, + globalExtensions = {}, + globalEditor = {}; + +var widgetExpander = expanders.createExpander('displayWidget'); +var exportsExpander = expanders.createExpander('extensionId'); + +// Encapsulates the extension API that is provided to a single extension. +function Extension(id) { + if (id in globalExtensions) { + throw new Error("An extension named '" + id + "' is already registered"); + } + this._id = id || _.uniqueId('ext-'); + this._hooks = {}; + this._expander = expanders.createExpander('extras'); + this.on = _.partial(addHook, this._id, globalHooks); +} + +Extension.prototype.addWidget = function(pos, node, type, userData) { + // TODO: Figure out how to handle this. + if (widgetExpander.has(node, 'displayWidget')) { + throw new Error('Conflicting widgets on node'); + } + widgetExpander.set(node, 'displayWidget', { + type: type, + pos: pos, + data: userData + }); +}; + +Extension.prototype.getWidget = function(node) { + // TODO: Should this only be exposed to certain types of extensions? + return widgetExpander.get(node, 'displayWidget'); +}; + +Extension.prototype.setExtras = function(node, data) { + return this._expander.set(node, 'extras', data); +}; + +Extension.prototype.getExtras = function(node, ext) { + // If `ext` is defined, it's the exports from an extension. Use that + // object to find the actual extension. + var exp, id; + exp = ext ? (id = exportsExpander.get(ext, 'extensionId'), globalExtensions[id]._expander) : this._expander; + return exp.get(node, 'extras'); +}; + +// Constants for the `pos` argument to `addWidget`. +Extension.prototype.BEFORE = 'before'; +Extension.prototype.AFTER = 'after'; +Extension.prototype.REPLACE = 'replace'; + +// Allows a client to hook the action named `hookName`. Every time the action +// performed, `visitor` will be called with the hook-specific arguments. +function addHook(id, hookState, hookName, func) { + var hooks; + if (id == null) { + id = _.uniqueId('hook-'); + } + hooks = hookState[hookName] != null ? hookState[hookName] : hookState[hookName] = {}; + if (hooks[id] == null) { + hooks[id] = []; + } + hooks[id].push(func); +} + +function invokeHook(hook, args) { + return _.each(globalHooks[hook], function(hookFns, id) { + return _.each(hookFns, function(fn) { + return applySafely(fn, args); }); - }; - - initializeExtension = function(ext, deps, initFn) { - var result; - result = initFn != null ? initFn.apply(null, [ext].concat(deps)) : void 0; - if (result != null) { - if (_.isObject(result)) { - return result; - } - throw new TypeError('Invalid export from extension (must be an object)'); + }); +} + +function initializeExtension(ext, deps, initFn) { + var result; + result = initFn != null ? initFn.apply(null, [ext].concat(deps)) : void 0; + if (result != null) { + if (_.isObject(result)) { + return result; } - return {}; - }; - - registerExtension = function(id, deps, initFn) { - var ext; - if (!_.isArray(deps)) { - initFn = deps; - deps = []; - } - deps = deps.map(function(name) { - if (!(name in globalExtensions)) { - throw new Error("Unmet dependency " + name); - } - return globalExtensions[name].exports; - }); - ext = new Extension(id); - ext.exports = initializeExtension(ext, deps, initFn); - exportsExpander.set(ext.exports, 'extensionId', ext._id); - return globalExtensions[ext._id] = ext; - }; - - parse = function(hooks, source) { - var tree; - tree = parser.parse(source); - invokeHook('parse', getHookArgs(tree)); - return tree; - }; - - getHookArgs = function(ast) { - var nodes; - nodes = []; - estraverse.traverse(ast, { - enter: function(node) { - return nodes.push(node); - } - }); - return [_.chain(nodes), _.chain(ast.comments)]; - }; - - applySafely = function(func, args) { - var e; - try { - return func.apply(null, args); - } catch (_error) { - e = _error; - return console.log(e.stack || e); + throw new TypeError('Invalid export from extension (must be an object)'); + } + return {}; +} + +function registerExtension(id, deps, initFn) { + // Allow extensions with no explicit dependencies. + if (!_.isArray(deps)) { + initFn = deps; + deps = []; + } + deps = deps.map(function(name) { + if (!(name in globalExtensions)) { + throw new Error('Unmet dependency ' + name); } - }; - - onChange = function(newValue) { - var e, hookArgs, tree; - try { - tree = parse(globalHooks, newValue); - } catch (_error) { - e = _error; - console.log(e); - return; + return globalExtensions[name].exports; + }); + var ext = new Extension(id); + ext.exports = initializeExtension(ext, deps, initFn); + // Allow the exports object can be traced back to the extension itself. + exportsExpander.set(ext.exports, 'extensionId', ext._id); + globalExtensions[ext._id] = ext; + return ext; +} + +function parse(hooks, source) { + var tree = parser.parse(source); + invokeHook('parse', getHookArgs(tree)); + return tree; +} + +function getHookArgs(ast) { + // For API convenience, the tree is currently passed as an + // Underscore-wrapped list of nodes, but this should change. + var nodes = []; + estraverse.traverse(ast, { + enter: function(node) { + return nodes.push(node); } - hookArgs = getHookArgs(tree); - invokeHook('display', hookArgs); - return invokeHook('render', hookArgs); - }; - - setEditor = function(editor) { - return globalEditor = editor; - }; - - getEditor = function() { - return globalEditor; - }; - - module.exports = { - on: _.partial(addHook, null, globalHooks), - onChange: onChange, - parse: parse, - registerExtension: registerExtension, - traverse: estraverse.traverse, - setEditor: setEditor, - getEditor: getEditor - }; - -}).call(this); + }); + return [_.chain(nodes), _.chain(ast.comments)]; +} + +function applySafely(func, args) { + try { + return func.apply(null, args); + } catch (e) { + return console.log(e.stack || e); // eslint-disable-line no-console + } +} + +function onChange(newValue) { + var tree; + try { + tree = parse(globalHooks, newValue); + } catch (e) { + console.log(e); // eslint-disable-line no-console + return; + } + // Run the display hooks. + // TODO: This should be moved into a function that can be invoked by the + // editor plugin. + var hookArgs = getHookArgs(tree); + invokeHook('display', hookArgs); + + // Run the render hooks. + invokeHook('render', hookArgs); +} + +function setEditor(editor) { + globalEditor = editor; + return editor; +} + +function getEditor() { + return globalEditor; +} + +module.exports = { + on: _.partial(addHook, null, globalHooks), + onChange: onChange, // TODO: Get rid of this. + parse: parse, + registerExtension: registerExtension, + traverse: estraverse.traverse, + setEditor: setEditor, + getEditor: getEditor +}; diff --git a/main.js b/main.js new file mode 100644 index 0000000..5477947 --- /dev/null +++ b/main.js @@ -0,0 +1 @@ +module.exports = require('./lib/moonchild'); diff --git a/metadata.coffee b/metadata.coffee deleted file mode 100644 index b9f854a..0000000 --- a/metadata.coffee +++ /dev/null @@ -1,77 +0,0 @@ -MAGIC_CHAR = '^' - -# Esprima parse options. -parseOptions = - comment: true # Preserve comments. - loc: true # Nodes include line- and column-based location info. - range: true # Nodes have an index-based location range (array). - -parse = (source) -> - ast = esprima.parse(source, parseOptions) - metadata = _.map(_.filter(ast.comments, containsMetadata), parseMetadata) - attachMetadata(ast, metadata) - return ast - -parseMetadata = (commentNode) -> - try - value = eval '(' + commentNode.value[1..] + ')' # TODO: Don't be evil. - catch - commentNode.contents = commentNode.value - commentNode.value = if _.isObject(value) then value else undefined - commentNode - -# Traverses the AST and attaches the metadata. Uses a pre-order traversal -# so that metadata will be attached to the highest node that it applies to. -attachMetadata = (ast, metadata) -> - # TODO: Handle multiple pieces of metadata per node. - - prev = null - estraverse.traverse ast, - enter: (node, parent) -> - if metadata.length == 0 - return estraverse.VisitorOption.Break - - if node == ast - attachToRootNode(ast, metadata) - else - attachToPreviousNode(prev, metadata) or - attachToFollowingNode(node, metadata) - prev = node - - if metadata.length > 0 and not attachToPreviousNode(prev, metadata) - ast.metadata = metadata[0] # Attach to root node. - -# Attach the next piece of metadata to `node` if there is at least one line -# between the end of the metadata and the beginning of the node. -attachToRootNode = (node, metadata) -> - if node.loc.start.line > metadata[0].loc.end.line + 1 - node.metadata = metadata.shift() - -# Attach the next piece of metadata to `node` if the metadata begins on the -# same line that `node` ends on. -attachToPreviousNode = (node, metadata) -> - if node? and followsOnLine(node, metadata[0]) - node.metadata = metadata.shift() - -# Attach the next piece of metadata to `node` if the node follows the metadata. -attachToFollowingNode = (node, metadata) -> - if node.range[0] > metadata[0].range[1] - node.metadata = metadata.shift() - -# Returns true if `second` comes after `first`, and starts on the same line as -# `first` ends. -followsOnLine = (first, second) -> - first.range[1] < second.range[0] and - first.loc.end.line == second.loc.start.line - -# Returns true if the given comment node contains metadata. -containsMetadata = (commentNode) -> commentNode.value[0] == MAGIC_CHAR - -_ = require 'underscore' -esprima = require 'esprima' -estraverse = require 'estraverse' - -module.exports = { parse } - -if require?.main == module - parse require('fs').readFileSync(process.argv[2]) diff --git a/moonchild.coffee b/moonchild.coffee deleted file mode 100644 index 839fe35..0000000 --- a/moonchild.coffee +++ /dev/null @@ -1,144 +0,0 @@ -parser = require './metadata' - -_ = require 'underscore' -estraverse = require 'estraverse' -expanders = require 'expanders' - -globalHooks = {} -globalExtensions = {} -globalEditor = {} - -widgetExpander = expanders.createExpander('displayWidget') -exportsExpander = expanders.createExpander('extensionId') - -# Encapsulates the extension API that is provided to a single extension. -class Extension - constructor: (id) -> - if id of globalExtensions - throw new Error("An extension named '#{ id }' is already registered") - - @_id = id || _.uniqueId('ext-') - @_hooks = {} - @_expander = expanders.createExpander('extras') - @on = _.partial(addHook, @_id, globalHooks) - - addWidget: (pos, node, type, userData) -> - # TODO: Figure out how to handle this. - if widgetExpander.has(node, 'displayWidget') - throw new Error('Conflicting widgets on node') - - widgetExpander.set node, 'displayWidget', - type: type - pos: pos - data: userData - return - - getWidget: (node) -> - # TODO: Should this only be exposed to certain types of extensions? - widgetExpander.get(node, 'displayWidget') - - setExtras: (node, data) -> - @_expander.set node, 'extras', data - - getExtras: (node, ext) -> - # If `ext` is defined, it's the exports from an extension. Use that - # object to find the actual extension. - exp = if ext - id = exportsExpander.get(ext, 'extensionId') - globalExtensions[id]._expander - else - @_expander - exp.get(node, 'extras') - - # Constants for the `pos` argument to `addWidget`. - BEFORE: 'before' - AFTER: 'after' - REPLACE: 'replace' - -# Allows a client to hook the action named `hookName`. Every time the action -# performed, `visitor` will be called with the hook-specific arguments. -addHook = (id, hookState, hookName, func) -> - id ?= _.uniqueId 'hook-' - hooks = hookState[hookName] ?= {} - hooks[id] ?= [] - hooks[id].push(func) - return - -invokeHook = (hook, args) -> - _.each globalHooks[hook], (hookFns, id) -> - _.each hookFns, (fn) -> - applySafely(fn, args) - -initializeExtension = (ext, deps, initFn) -> - result = initFn?.apply(null, [ext].concat(deps)) - if result? - if _.isObject(result) then return result - throw new TypeError('Invalid export from extension (must be an object)') - {} - -registerExtension = (id, deps, initFn) -> - # Allow extensions with no explicit dependencies. - if !_.isArray(deps) - initFn = deps - deps = [] - - deps = deps.map (name) -> - if not (name of globalExtensions) - throw new Error "Unmet dependency #{name}" - return globalExtensions[name].exports - - ext = new Extension(id) - ext.exports = initializeExtension(ext, deps, initFn) - # Allow the exports object can be traced back to the extension itself. - exportsExpander.set(ext.exports, 'extensionId', ext._id) - - globalExtensions[ext._id] = ext - -parse = (hooks, source) -> - tree = parser.parse(source) - invokeHook('parse', getHookArgs(tree)) - tree - -getHookArgs = (ast) -> - # For API convenience, the tree is currently passed as an - # Underscore-wrapped list of nodes, but this should change. - nodes = [] - estraverse.traverse(ast, { enter: (node) -> nodes.push(node) }) - [_.chain(nodes), _.chain(ast.comments)] - -applySafely = (func, args) -> - try - func.apply(null, args) - catch e - console.log e.stack || e - -onChange = (newValue) -> - try - tree = parse(globalHooks, newValue) - catch e - console.log e - return - - # Run the display hooks. - # TODO: This should be moved into a function that can be invoked by the - # editor plugin. - hookArgs = getHookArgs(tree) - invokeHook('display', hookArgs) - - # Run the render hooks. - invokeHook('render', hookArgs) - -setEditor = (editor) -> - globalEditor = editor - -getEditor = -> globalEditor - -module.exports = { - on: _.partial(addHook, null, globalHooks) - onChange, # TODO: Get rid of this. - parse, - registerExtension, - traverse: estraverse.traverse, - setEditor, - getEditor -} diff --git a/package.json b/package.json index 69ce812..ec76e10 100644 --- a/package.json +++ b/package.json @@ -13,26 +13,28 @@ "devDependencies": { "beefy": "^2.1.0", "browserify": "^3.38.0", - "coffee-script": "~1.7.1", - "coffeelint": "^1.5.1", + "eslint": "^0.18.0", "expanders": "0.1.0", - "jshint": "~2.4.1", "node-static": "^0.7.4", "tap": "~0.4.8", + "tap-spec": "^2.2.2", "watchify": "~0.6.2" }, + "browserify": { + "opts": "main.js -s Moonchild -o dist/moonchild-bundle.js" + }, "scripts": { - "build": "browserify --extension '.coffee' -c 'coffee -sc' -s Moonchild -o lib/moonchild-bundle.js moonchild.coffee", - "watch": "watchify --extension '.coffee' -c 'coffee -sc' -s Moonchild -o lib/moonchild-bundle.js moonchild.coffee", - "test": "coffee -o lib *.coffee && tap test/*.js", - "whizzy-demo": "open examples/whizzy.html; beefy lib/whizzy.js -- -s whizzy", - "whizzy-tests": "open test/test-whizzy.html; beefy test/test-whizzy.js", + "build": "browserify $npm_package_browserify_opts", + "lint": "eslint lib test $npm_config_package_main", + "prepublish": "npm run build && npm run test && npm run lint", "serve": "http-server -o", - "dist": "npm run build && npm run test && npm run lint", - "jshint": "jshint *.js test/*.js", - "lint": "coffeelint --rules coffeelint.json *.coffee", - "start": "node server.js" + "start": "node server.js", + "test": "tap test/*.js", + "watch": "watchify $npm_package_browserify_opts", + "whizzy-demo": "open examples/whizzy.html; beefy lib/whizzy.js -- -s whizzy", + "whizzy-tests": "open test/test-whizzy.html; beefy test/test-whizzy.js" }, + "main": "main.js", "directories": { "test": "test" }, diff --git a/test/test-metadata.js b/test/test-metadata.js index 0a43bb9..ccb1968 100644 --- a/test/test-metadata.js +++ b/test/test-metadata.js @@ -1,8 +1,9 @@ -var test = require('tap').test; -var metadata = require('../lib/metadata'); +'use strict'; -var _ = require('underscore'); -var estraverse = require('estraverse'); +var _ = require('underscore'), + estraverse = require('estraverse'), + metadata = require('../lib/metadata'), + test = require('tap').test; // Returns a list of all the nodes in an AST using pre-order traversal. function nodes(ast) { @@ -11,7 +12,7 @@ function nodes(ast) { enter: function(node, parent) { result.push(node); } - }) + }); return result; } @@ -27,7 +28,7 @@ function firstWithMetadata(ast) { } test('basic metadata', function (t) { - var ast = parse('42;\n/*^ {} */\nvar meaning;') + var ast = parse('42;\n/*^ {} */\nvar meaning;'); t.equals(firstWithMetadata(ast).type, 'VariableDeclaration', 'Normal metadata is attached to the next node'); ast = parse('42; //^ {}'); diff --git a/test/test-moonchild.js b/test/test-moonchild.js index b6cc8a4..1facf7f 100644 --- a/test/test-moonchild.js +++ b/test/test-moonchild.js @@ -1,5 +1,7 @@ -var test = require('tap').test; -var Moonchild = require('../lib/moonchild'); +'use strict'; + +var test = require('tap').test, + Moonchild = require('../lib/moonchild'); test('hooks', function(t) { var tree;