diff --git a/buildcfg/jsdoc/info/publish.js b/buildcfg/jsdoc/info/publish.js index 8badcbf934..e2692d482a 100644 --- a/buildcfg/jsdoc/info/publish.js +++ b/buildcfg/jsdoc/info/publish.js @@ -13,19 +13,33 @@ var path = require('path'); * @param {Object} opts Options. */ exports.publish = function(data, opts) { - var cwd = process.cwd(); - // get all doclets with the "api" property or define (excluding enums, - // typedefs and events) + function getTypes(data) { + var types = []; + data.forEach(function(name) { + types.push(name.replace(/^function$/, 'Function')); + }); + return types; + } + + // get all doclets with the "api" property or define (excluding events) or + // with olx namespace var docs = data( - [{define: {isObject: true}}, {api: {isString: true}}], - {isEnum: {'!is': true}}, - {kind: {'!is': 'typedef'}}, + [ + {define: {isObject: true}}, + {api: {isString: true}}, + function() { + return this.meta && (/[\\\/]externs$/).test(this.meta.path); + } + ], + {kind: {'!is': 'file'}}, {kind: {'!is': 'event'}}).get(); // get symbols data, filter out those that are members of private classes var symbols = []; var defines = []; + var typedefs = []; + var externs = []; docs.filter(function(doc) { var include = true; var constructor = doc.memberof; @@ -36,24 +50,84 @@ exports.publish = function(data, opts) { } return include; }).forEach(function(doc) { - if (doc.define) { + var isExterns = (/[\\\/]externs$/).test(doc.meta.path); + if (isExterns && doc.longname.indexOf('olx.') === 0) { + if (doc.kind == 'typedef') { + typedefs.push({ + name: doc.longname, + types: ['{}'] + }); + } else { + var typedef = typedefs[typedefs.length - 1]; + var type = typedef.types[0]; + typedef.types[0] = type + .replace(/\}$/, ', ' + doc.longname.split('#')[1] + + ': (' + getTypes(doc.type.names).join('|') + ')}') + .replace('{, ', '{'); + } + } else if (doc.define) { defines.push({ name: doc.longname, description: doc.description, path: path.join(doc.meta.path, doc.meta.filename), default: doc.define.default }); + } else if (doc.kind == 'typedef' || doc.isEnum === true) { + typedefs.push({ + name: doc.longname, + types: getTypes(doc.type.names) + }); } else { - symbols.push({ + var types; + var symbol = { name: doc.longname, kind: doc.kind, description: doc.classdesc || doc.description, path: path.join(doc.meta.path, doc.meta.filename) - }); + }; + if (doc.type) { + symbol.types = getTypes(doc.type.names); + } + if (doc.params) { + var params = []; + doc.params.forEach(function(param) { + var paramInfo = { + name: param.name + }; + params.push(paramInfo); + paramInfo.types = getTypes(param.type.names); + if (typeof param.variable == 'boolean') { + paramInfo.variable = param.variable; + } + if (typeof param.optional == 'boolean') { + paramInfo.optional = param.optional; + } + }); + symbol.params = params; + } + if (doc.returns) { + symbol.returns = getTypes(doc.returns[0].type.names); + } + if (doc.tags) { + doc.tags.every(function(tag) { + if (tag.title == 'template') { + symbol.template = tag.value; + return false; + } + return true; + }); + } + var target = isExterns ? externs : symbols; + target.push(symbol); } }); process.stdout.write( - JSON.stringify({symbols: symbols, defines: defines}, null, 2)); + JSON.stringify({ + symbols: symbols, + defines: defines, + typedefs: typedefs, + externs: externs + }, null, 2)); }; diff --git a/externs/geojson.js b/externs/geojson.js index bec6c7b428..984d0728e4 100644 --- a/externs/geojson.js +++ b/externs/geojson.js @@ -52,6 +52,13 @@ GeoJSONCRS.prototype.properties; var GeoJSONCRSCode = function() {}; + +/** + * @constructor + */ +var GeoJSONCRSName = function() {}; + + /** * @type {string} * TODO: remove this when http://jira.codehaus.org/browse/GEOS-5996 is fixed @@ -60,13 +67,6 @@ var GeoJSONCRSCode = function() {}; GeoJSONCRSName.prototype.code; - -/** - * @constructor - */ -var GeoJSONCRSName = function() {}; - - /** * @type {string} */ diff --git a/externs/olx.js b/externs/olx.js index 2b946d0c79..b5c59853b3 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -106,6 +106,20 @@ olx.GeolocationOptions.prototype.projection; olx.LogoOptions; +/** + * Link url for the logo. Will be followed when the logo is clicked. + * @type {string} + */ +olx.LogoOptions.prototype.href; + + +/** + * Image src for the logo + * @type {string} + */ +olx.LogoOptions.prototype.src; + + /** * @typedef {{map: (ol.Map|undefined), * maxLines: (number|undefined), @@ -1186,6 +1200,7 @@ olx.format.PolylineOptions; /** * The factor by which the coordinates values will be scaled. * Default is `1e5`. + * @type {number} */ olx.format.PolylineOptions.prototype.factor; @@ -2788,7 +2803,7 @@ olx.source.FormatVectorOptions.prototype.format; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.FormatVectorOptions.prototype.logo; @@ -2830,7 +2845,7 @@ olx.source.GeoJSONOptions.prototype.defaultProjection; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.GeoJSONOptions.prototype.logo; @@ -2901,7 +2916,7 @@ olx.source.GPXOptions.prototype.doc; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.GPXOptions.prototype.logo; @@ -2976,7 +2991,7 @@ olx.source.TileImageOptions.prototype.crossOrigin; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.TileImageOptions.prototype.logo; @@ -3074,7 +3089,7 @@ olx.source.TileVectorOptions.prototype.format; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.TileVectorOptions.prototype.logo; @@ -3152,7 +3167,7 @@ olx.source.TopoJSONOptions.prototype.defaultProjection; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.TopoJSONOptions.prototype.logo; @@ -3360,7 +3375,7 @@ olx.source.KMLOptions.prototype.extractStyles; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.KMLOptions.prototype.logo; @@ -3531,7 +3546,7 @@ olx.source.OSMXMLOptions.prototype.doc; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.OSMXMLOptions.prototype.logo; @@ -3614,7 +3629,7 @@ olx.source.ImageCanvasOptions.prototype.canvasFunction; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.ImageCanvasOptions.prototype.logo; @@ -3671,7 +3686,7 @@ olx.source.ImageVectorOptions.prototype.attributions; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.ImageVectorOptions.prototype.logo; @@ -3762,7 +3777,7 @@ olx.source.ImageWMSOptions.prototype.serverType; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.ImageWMSOptions.prototype.logo; @@ -3902,7 +3917,7 @@ olx.source.ImageStaticOptions.prototype.imageSize; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.ImageStaticOptions.prototype.logo; @@ -3964,7 +3979,7 @@ olx.source.ServerVectorOptions.prototype.strategy; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.ServerVectorOptions.prototype.logo; @@ -4072,7 +4087,7 @@ olx.source.TileWMSOptions.prototype.hidpi; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.TileWMSOptions.prototype.logo; @@ -4159,7 +4174,7 @@ olx.source.VectorOptions.prototype.features; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.VectorOptions.prototype.logo; @@ -4225,7 +4240,7 @@ olx.source.StaticVectorOptions.prototype.format; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.StaticVectorOptions.prototype.logo; @@ -4311,7 +4326,7 @@ olx.source.WMTSOptions.prototype.crossOrigin; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.WMTSOptions.prototype.logo; @@ -4451,7 +4466,7 @@ olx.source.XYZOptions.prototype.crossOrigin; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.XYZOptions.prototype.logo; @@ -4551,7 +4566,7 @@ olx.source.ZoomifyOptions.prototype.crossOrigin; /** * Logo. - * @type{string|olx.LogoOptions|undefined} + * @type {string|olx.LogoOptions|undefined} */ olx.source.ZoomifyOptions.prototype.logo; diff --git a/src/ol/map.js b/src/ol/map.js index 8575556fc6..3fdbc5bbf4 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -554,7 +554,7 @@ ol.Map.prototype.disposeInternal = function() { * be `null`. To stop detection, callback functions can return a truthy * value. * @param {S=} opt_this Value to use as `this` when executing `callback`. - * @param {function(this: U, ol.layer.Layer): boolean=} opt_layerFilter Layer + * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer * filter function, only layers which are visible and for which this * function returns `true` will be tested for features. By default, all * visible layers will be tested. Feature overlays will always be tested. diff --git a/tasks/generate-externs.js b/tasks/generate-externs.js new file mode 100644 index 0000000000..1962883aa0 --- /dev/null +++ b/tasks/generate-externs.js @@ -0,0 +1,196 @@ +var fs = require('fs'); +var path = require('path'); + +var async = require('async'); +var fse = require('fs-extra'); +var nomnom = require('nomnom'); + +var generateInfo = require('./generate-info'); + +/** + * Read the symbols from info file. + * @param {funciton(Error, Array., Array.)} callback Called + * with the patterns and symbols (or any error). + */ +function getInfo(callback) { + generateInfo(function(err) { + if (err) { + callback(new Error('Trouble generating info: ' + err.message)); + return; + } + var typedefs = require('../build/info.json').typedefs; + var symbols = require('../build/info.json').symbols; + var externs = require('../build/info.json').externs; + callback(null, typedefs, symbols, externs); + }); +} + + +/** + * Generate externs code given a list symbols. + * @param {Array.} typedefs List of typedefs. + * @param {Array.} symbols List of symbols. + * @param {Array.} externs List of externs. + * @param {string|undefined} namespace Target object for exported symbols. + * @return {string} Export code. + */ +function generateExterns(typedefs, symbols, externs) { + var lines = []; + var processedSymbols = {}; + var constructors = {}; + + function addNamespaces(name) { + var parts = name.split('.'); + parts.pop(); + var namespace = []; + parts.forEach(function(part) { + namespace.push(part); + var partialNamespace = namespace.join('.'); + if (!(partialNamespace in processedSymbols || + partialNamespace in constructors)) { + lines.push('/**'); + lines.push(' * @type {Object}'); + lines.push(' */'); + lines.push(nameToJS(partialNamespace) + ';'); + lines.push('\n'); + } + }); + } + + function nameToJS(name) { + processedSymbols[name] = true; + if (name.indexOf('.') == -1) { + name = 'var ' + name; + } + return name; + } + + function noGoogTypes(typesWithGoog) { + typesWithoutGoog = []; + typesWithGoog.forEach(function(type) { + typesWithoutGoog.push(type.replace(/^goog\..*$/, '*')); + }); + return typesWithoutGoog; + } + + function processSymbol(symbol) { + addNamespaces(symbol.name.split('#')[0]); + + var name = symbol.name; + if (name.indexOf('#') > 0) { + name = symbol.name.replace('#', '.prototype.'); + var constructor = symbol.name.split('#')[0]; + if (!(constructor in constructors)) { + constructors[constructor] = true; + lines.push('/**'); + lines.push(' * @constructor'); + lines.push(' */'); + lines.push(nameToJS(constructor) + ' = function() {};'); + lines.push('\n'); + } + } + + lines.push('/**'); + if (symbol.kind == 'class') { + constructors[name] = true; + lines.push(' * @constructor'); + } + if (symbol.types) { + lines.push(' * @type {' + noGoogTypes(symbol.types).join('|') + '}'); + } + var args = []; + if (symbol.params) { + symbol.params.forEach(function(param) { + args.push(param.name); + lines.push(' * @param {' + + (param.variable ? '...' : '') + + noGoogTypes(param.types).join('|') + + (param.optional ? '=' : '') + + '} ' + param.name); + }); + } + if (symbol.returns) { + lines.push(' * @return {' + noGoogTypes(symbol.returns).join('|') + '}'); + } + if (symbol.template) { + lines.push(' * @template ' + symbol.template); + } + lines.push(' */'); + if (symbol.kind == 'function' || symbol.kind == 'class') { + lines.push(nameToJS(name) + ' = function(' + args.join(', ') + ') {};'); + } else { + lines.push(nameToJS(name) + ';'); + } + lines.push('\n'); + } + + externs.forEach(processSymbol); + + typedefs.forEach(function(typedef) { + addNamespaces(typedef.name); + lines.push('/**'); + lines.push(' * @typedef {' + noGoogTypes(typedef.types).join('|') + '}'); + lines.push(' */'); + lines.push(nameToJS(typedef.name) + ';'); + lines.push('\n'); + }); + + symbols.forEach(processSymbol); + + return lines.join('\n'); +} + + +/** + * Generate the exports code. + * + * @param {function(Error, string)} callback Called with the exports code or any + * error generating it. + */ +function main(callback) { + async.waterfall([ + getInfo, + function(typedefs, symbols, externs, done) { + var code, err; + try { + code = generateExterns(typedefs, symbols, externs); + } catch (e) { + err = e; + } + done(err, code); + } + ], callback); +} + + +/** + * If running this module directly, read the config file, call the main + * function, and write the output file. + */ +if (require.main === module) { + var options = nomnom.options({ + output: { + position: 0, + required: true, + help: 'Output path for the generated externs file.' + } + }).parse(); + + async.waterfall([ + main, + fse.outputFile.bind(fse, options.output) + ], function(err) { + if (err) { + console.error(err.message); + process.exit(1); + } else { + process.exit(0); + } + }); +} + + +/** + * Export main function. + */ +module.exports = main; diff --git a/tasks/generate-info.js b/tasks/generate-info.js index f94d22980d..7f6832115b 100644 --- a/tasks/generate-info.js +++ b/tasks/generate-info.js @@ -7,6 +7,11 @@ var fse = require('fs-extra'); var walk = require('walk').walk; var sourceDir = path.join(__dirname, '..', 'src'); +var externsDir = path.join(__dirname, '..', 'externs'); +var externsPaths = [ + path.join(externsDir, 'olx.js'), + path.join(externsDir, 'geojson.js') +]; var infoPath = path.join(__dirname, '..', 'build', 'info.json'); var jsdoc = path.join(__dirname, '..', 'node_modules', '.bin', 'jsdoc'); var jsdocConfig = path.join( @@ -33,16 +38,44 @@ function getInfoTime(callback) { } +/** + * Test whether externs/olx.js is newer than the provided date. + * @param {Date} date Modification time of info file. + * @param {function(Error, Date, boolen)} callback Called with any + * error, the mtime of the info file (zero date if it doesn't exist), and + * whether externs/olx.js is newer than that date. + */ +function getNewerExterns(date, callback) { + var newer = false; + var walker = walk(externsDir); + walker.on('file', function(root, stats, next) { + var sourcePath = path.join(root, stats.name); + externsPaths.forEach(function(path) { + if (sourcePath == path && stats.mtime > date) { + newer = true; + } + }); + next(); + }); + walker.on('errors', function() { + callback(new Error('Trouble walking ' + sourceDir)); + }); + walker.on('end', function() { + callback(null, date, newer); + }); +} + + /** * Generate a list of all .js paths in the source directory if any are newer * than the provided date. * @param {Date} date Modification time of info file. + * @param {boolean} newer Whether externs/olx.js is newer than date. * @param {function(Error, Array.)} callback Called with any * error and the array of source paths (empty if none newer). */ -function getNewer(date, callback) { - var paths = []; - var newer = false; +function getNewer(date, newer, callback) { + var paths = [].concat(externsPaths); var walker = walk(sourceDir); walker.on('file', function(root, stats, next) { @@ -219,6 +252,7 @@ function writeInfo(info, callback) { function main(callback) { async.waterfall([ getInfoTime, + getNewerExterns, getNewer, spawnJSDoc, addSymbolProvides, diff --git a/tasks/readme.md b/tasks/readme.md index 6cdc5b9031..ad3fff9f8c 100644 --- a/tasks/readme.md +++ b/tasks/readme.md @@ -89,6 +89,16 @@ The `defines` section of `build.json` above lists common settings for the Closur Called internally to generate a `build/exports.js` file optionally with a limited set of exports. +## `generate-externs.js` + +Can be called to generate a Closure externs file for the full OpenLayers 3 API. +See the `--help` option for more detail. + + node tasks/generate-externs.js --help + +This is useful for projects that use the Closure Compiler to build, but want to use OpenLayers 3 as external library rather than building together with OpenLayers 3. + + ## `generate-info.js` Called internally to parse the library for annotations and write out a `build/info.json` file.