ad62739a6e
In addition to using const and let, this also upgrades our linter config and removes lint (mostly whitespace).
229 lines
6.3 KiB
JavaScript
229 lines
6.3 KiB
JavaScript
const async = require('async');
|
|
const fs = require('fs-extra');
|
|
const nomnom = require('nomnom');
|
|
|
|
const generateInfo = require('./generate-info');
|
|
|
|
const googRegEx = /^goog\..*$/;
|
|
|
|
/**
|
|
* Read the symbols from info file.
|
|
* @param {function(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;
|
|
}
|
|
const 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) {
|
|
const lines = [];
|
|
const processedSymbols = {};
|
|
const constructors = {};
|
|
const constructorOptionsTypes = {};
|
|
|
|
function addNamespaces(name) {
|
|
const parts = name.split('.');
|
|
parts.pop();
|
|
const namespace = [];
|
|
parts.forEach(function(part) {
|
|
namespace.push(part);
|
|
const 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;
|
|
}
|
|
|
|
// 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]);
|
|
|
|
let name = symbol.name;
|
|
if (name.indexOf('#') > 0) {
|
|
name = symbol.name.replace('#', '.prototype.');
|
|
const 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 {' + symbol.types.join('|') + '}');
|
|
}
|
|
const args = [];
|
|
if (symbol.params) {
|
|
symbol.params.forEach(function(param) {
|
|
findConstructorOptionsTypes(param.types);
|
|
args.push(param.name);
|
|
lines.push(' * @param {' +
|
|
(param.variable ? '...' : '') +
|
|
param.types.join('|') +
|
|
(param.optional ? '=' : '') + (param.nullable ? '!' : '') +
|
|
'} ' + param.name);
|
|
});
|
|
}
|
|
if (symbol.returns) {
|
|
lines.push(' * @return {' +
|
|
(symbol.returns.nullable ? '!' : '') +
|
|
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 {' + 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) {
|
|
let 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) {
|
|
const options = nomnom.options({
|
|
output: {
|
|
position: 0,
|
|
required: true,
|
|
help: 'Output path for the generated externs file.'
|
|
}
|
|
}).parse();
|
|
|
|
async.waterfall([
|
|
main,
|
|
fs.outputFile.bind(fs, 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;
|