240 lines
6.6 KiB
JavaScript
240 lines
6.6 KiB
JavaScript
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');
|
||
|
||
var googRegEx = /^goog\..*$/;
|
||
|
||
/**
|
||
* Read the symbols from info file.
|
||
* @param {funciton(Error, Array.<string>, Array.<Object>)} 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 info = require('../build/info.json');
|
||
callback(null, info.typedefs, info.symbols, info.externs, info.base);
|
||
});
|
||
}
|
||
|
||
|
||
/**
|
||
* Generate externs code given a list symbols.
|
||
* @param {Array.<Object>} typedefs List of typedefs.
|
||
* @param {Array.<Object>} symbols List of symbols.
|
||
* @param {Array.<Object>} externs List of externs.
|
||
* @param {Array.<Object>} base List of base.
|
||
* @param {string|undefined} namespace Target object for exported symbols.
|
||
* @return {string} Export code.
|
||
*/
|
||
function generateExterns(typedefs, symbols, externs, base) {
|
||
var lines = [];
|
||
var processedSymbols = {};
|
||
var constructors = {};
|
||
var constructorOptionsTypes = {};
|
||
|
||
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) {
|
||
var typesWithoutGoog = [];
|
||
typesWithGoog.forEach(function(type) {
|
||
typesWithoutGoog.push(type.replace(googRegEx, '*'));
|
||
});
|
||
return typesWithoutGoog;
|
||
}
|
||
|
||
// Store in "constructorOptionsTypes" type names that start
|
||
// with "ol." and end with "Options".
|
||
function findConstructorOptionsTypes(types) {
|
||
types.forEach(function(type) {
|
||
if (type.match(/^ol\..*Options$/)) {
|
||
constructorOptionsTypes[type] = true;
|
||
}
|
||
});
|
||
}
|
||
|
||
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.extends && !googRegEx.test(symbol.extends)) {
|
||
lines.push(' * @extends {' + symbol.extends + '}');
|
||
}
|
||
}
|
||
if (symbol.types) {
|
||
lines.push(' * @type {' + noGoogTypes(symbol.types).join('|') + '}');
|
||
}
|
||
var args = [];
|
||
if (symbol.params) {
|
||
symbol.params.forEach(function(param) {
|
||
findConstructorOptionsTypes(param.types);
|
||
args.push(param.name);
|
||
lines.push(' * @param {' +
|
||
(param.variable ? '...' : '') +
|
||
noGoogTypes(param.types).join('|') +
|
||
(param.optional ? '=' : '') + (param.nullable ? '!' : '') +
|
||
'} ' + param.name);
|
||
});
|
||
}
|
||
if (symbol.returns) {
|
||
lines.push(' * @return {' +
|
||
(symbol.returns.nullable ? '!' : '') +
|
||
noGoogTypes(symbol.returns.types).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);
|
||
|
||
base.forEach(processSymbol);
|
||
|
||
symbols.forEach(processSymbol);
|
||
|
||
typedefs.forEach(function(typedef) {
|
||
// we're about to add a @typedef for "typedef.name" so remove that
|
||
// type from constructorOptionsTypes
|
||
delete constructorOptionsTypes[typedef.name];
|
||
|
||
addNamespaces(typedef.name);
|
||
lines.push('/**');
|
||
lines.push(' * @typedef {' + noGoogTypes(typedef.types).join('|') + '}');
|
||
lines.push(' */');
|
||
lines.push(nameToJS(typedef.name) + ';');
|
||
lines.push('\n');
|
||
});
|
||
|
||
|
||
// At this point constructorOptionsTypes includes options types for which we
|
||
// did not have a @typedef yet. For those we add @typedef {Object}.
|
||
//
|
||
// This is used for abstract base classes. Abstract base classes should be
|
||
// defined as types in the ol externs file. But the corresponding @typedef's
|
||
// are not marked with an @api annotation because abstract base classes are
|
||
// not instantiated by applications. Yet, we need to have a type defined
|
||
// in the ol externs file for these options types. So we just use
|
||
// @typedef {Object}.
|
||
Object.keys(constructorOptionsTypes).forEach(function(key) {
|
||
lines.push('/**');
|
||
lines.push(' * No `@api` annotation for `' + key + '`, use `Object`.');
|
||
lines.push(' * @typedef {Object}');
|
||
lines.push(' */');
|
||
lines.push(nameToJS(key) + ';');
|
||
lines.push('\n');
|
||
});
|
||
|
||
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, base, done) {
|
||
var code, err;
|
||
try {
|
||
code = generateExterns(typedefs, symbols, externs, base);
|
||
} 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) {
|
||
process.stderr.write(err.message + '\n');
|
||
process.exit(1);
|
||
} else {
|
||
process.exit(0);
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
/**
|
||
* Export main function.
|
||
*/
|
||
module.exports = main;
|