Fix legacy build

The build process failed because webpack does not allow failed imports
when it is run as a module.
This detects modules with default exports and only generates import
statements for default exports where they are available.
This commit is contained in:
Maximilian Krög
2021-05-02 23:18:19 +02:00
parent 8ff40c40e8
commit 40f8e69675
12 changed files with 77 additions and 63 deletions

View File

@@ -0,0 +1,181 @@
/* eslint-disable import/no-commonjs */
/**
* Define an @api tag
* @param {Object} dictionary The tag dictionary.
*/
exports.defineTags = function (dictionary) {
dictionary.defineTag('api', {
mustNotHaveValue: true,
canHaveType: false,
canHaveName: false,
onTagged: function (doclet, tag) {
includeTypes(doclet);
doclet.stability = 'stable';
},
});
};
/*
* Based on @api annotations, and assuming that items with no @api annotation
* should not be documented, this plugin removes undocumented symbols
* from the documentation.
*/
const api = {};
const classes = {};
const types = {};
const modules = {};
function includeAugments(doclet) {
// Make sure that `observables` and `fires` are taken from an already processed `class` doclet.
// This is necessary because JSDoc generates multiple doclets with the same longname.
const cls = classes[doclet.longname];
if (cls.observables && !doclet.observables) {
doclet.observables = cls.observables;
}
if (doclet.fires && cls.fires) {
for (let i = 0, ii = cls.fires.length; i < ii; ++i) {
const fires = cls.fires[i];
if (doclet.fires.indexOf(fires) == -1) {
doclet.fires.push(fires);
}
}
}
if (cls.fires && !doclet.fires) {
doclet.fires = cls.fires;
}
const augments = doclet.augments;
if (augments) {
let cls;
for (let i = augments.length - 1; i >= 0; --i) {
cls = classes[augments[i]];
if (cls) {
includeAugments(cls);
if (cls.fires) {
if (!doclet.fires) {
doclet.fires = [];
}
cls.fires.forEach(function (f) {
if (doclet.fires.indexOf(f) == -1) {
doclet.fires.push(f);
}
});
}
if (cls.observables) {
if (!doclet.observables) {
doclet.observables = [];
}
cls.observables.forEach(function (f) {
if (doclet.observables.indexOf(f) == -1) {
doclet.observables.push(f);
}
});
}
cls._hideConstructor = true;
}
}
}
}
function extractTypes(item) {
item.type.names.forEach(function (type) {
const match = type.match(/^(.*<)?([^>]*)>?$/);
if (match) {
modules[match[2]] = true;
types[match[2]] = true;
}
});
}
function includeTypes(doclet) {
if (doclet.params) {
doclet.params.forEach(extractTypes);
}
if (doclet.returns) {
doclet.returns.forEach(extractTypes);
}
if (doclet.properties) {
doclet.properties.forEach(extractTypes);
}
if (doclet.type && doclet.meta.code.type == 'MemberExpression') {
extractTypes(doclet);
}
}
function sortOtherMembers(doclet) {
if (doclet.fires) {
doclet.fires.sort(function (a, b) {
return a.split(/#?event:/)[1] < b.split(/#?event:/)[1] ? -1 : 1;
});
}
if (doclet.observables) {
doclet.observables.sort(function (a, b) {
return a.name < b.name ? -1 : 1;
});
}
}
exports.handlers = {
newDoclet: function (e) {
const doclet = e.doclet;
if (doclet.stability) {
modules[doclet.longname.split(/[~\.]/).shift()] = true;
api[doclet.longname.split('#')[0]] = true;
}
if (doclet.kind == 'class') {
if (!(doclet.longname in classes)) {
classes[doclet.longname] = doclet;
} else if ('augments' in doclet) {
classes[doclet.longname].augments = doclet.augments;
}
}
if (doclet.name === doclet.longname && !doclet.memberof) {
// Make sure anonymous default exports are documented
doclet.setMemberof(doclet.longname);
}
},
parseComplete: function (e) {
const doclets = e.doclets;
const byLongname = doclets.index.longname;
for (let i = doclets.length - 1; i >= 0; --i) {
const doclet = doclets[i];
if (doclet.stability) {
if (doclet.kind == 'class') {
includeAugments(doclet);
}
sortOtherMembers(doclet);
// Always document namespaces and items with stability annotation
continue;
}
if (doclet.kind == 'module' && doclet.longname in modules) {
// Document all modules that are referenced by the API
continue;
}
if (doclet.isEnum || doclet.kind == 'typedef') {
continue;
}
if (doclet.kind == 'class' && doclet.longname in api) {
// Mark undocumented classes with documented members as unexported.
// This is used in ../template/tmpl/container.tmpl to hide the
// constructor from the docs.
doclet._hideConstructor = true;
includeAugments(doclet);
sortOtherMembers(doclet);
} else if (!doclet._hideConstructor) {
// Remove all other undocumented symbols
doclet.undocumented = true;
}
if (
doclet.memberof &&
byLongname[doclet.memberof] &&
byLongname[doclet.memberof][0].isEnum &&
!byLongname[doclet.memberof][0].properties.some((p) => p.stability)
) {
byLongname[doclet.memberof][0].undocumented = true;
}
}
},
};

View File

@@ -0,0 +1,35 @@
const defaultExports = {};
const path = require('path');
const moduleRoot = path.join(process.cwd(), 'src');
// Tag default exported Identifiers because their name should be the same as the module name.
exports.astNodeVisitor = {
visitNode: function (node, e, parser, currentSourceName) {
if (node.parent && node.parent.type === 'ExportDefaultDeclaration') {
const modulePath = path
.relative(moduleRoot, currentSourceName)
.replace(/\.js$/, '');
const exportName =
'module:' +
modulePath.replace(/\\/g, '/') +
(node.name ? '~' + node.name : '');
defaultExports[exportName] = true;
}
},
};
exports.handlers = {
processingComplete(e) {
const byLongname = e.doclets.index.longname;
for (const name in defaultExports) {
if (!(name in byLongname)) {
throw new Error(
`missing ${name} in doclet index, did you forget a @module tag?`
);
}
byLongname[name].forEach(function (doclet) {
doclet.isDefaultExport = true;
});
}
},
};

View File

@@ -0,0 +1,33 @@
/* eslint-disable import/no-commonjs */
/**
* @fileoverview This plugin extracts info from boolean defines. This only
* handles boolean defines with the default value in the description. Default
* is assumed to be provided with something like "default is `true`" (case
* insensitive, with or without ticks).
*/
const DEFAULT_VALUE = /default\s+is\s+`?(true|false)`?/i;
/**
* Hook to define new tags.
* @param {Object} dictionary The tag dictionary.
*/
exports.defineTags = function (dictionary) {
dictionary.defineTag('define', {
canHaveType: true,
mustHaveValue: true,
onTagged: function (doclet, tag) {
const types = tag.value.type.names;
if (types.length === 1 && types[0] === 'boolean') {
const match = tag.value.description.match(DEFAULT_VALUE);
if (match) {
doclet.define = {
default: match[1] === 'true',
};
doclet.description = tag.value.description;
}
}
},
});
};

View File

@@ -0,0 +1,39 @@
/* eslint-disable import/no-commonjs */
const events = {};
exports.handlers = {
newDoclet: function (e) {
const doclet = e.doclet;
if (doclet.kind !== 'event') {
return;
}
const cls = doclet.longname.split('#')[0];
if (!(cls in events)) {
events[cls] = [];
}
events[cls].push(doclet.longname);
},
parseComplete: function (e) {
const doclets = e.doclets;
for (let i = 0, ii = doclets.length - 1; i < ii; ++i) {
const doclet = doclets[i];
if (doclet.fires) {
if (doclet.kind == 'class') {
const fires = [];
for (let j = 0, jj = doclet.fires.length; j < jj; ++j) {
const event = doclet.fires[j].replace('event:', '');
if (events[event]) {
fires.push.apply(fires, events[event]);
} else if (doclet.fires[j] !== 'event:ObjectEvent') {
fires.push(doclet.fires[j]);
}
}
doclet.fires = fires;
}
}
}
},
};

View File

@@ -0,0 +1,52 @@
/* eslint-disable import/no-commonjs */
/**
* @fileoverview
* Inlines option params from typedefs
*/
const properties = {};
exports.handlers = {
/**
* Collects all typedefs, keyed by longname
* @param {Object} e Event object.
*/
newDoclet: function (e) {
if (e.doclet.kind == 'typedef' && e.doclet.properties) {
properties[e.doclet.longname] = e.doclet.properties;
}
},
/**
* Adds `options.*` params for options that match the longname of one of the
* collected typedefs.
* @param {Object} e Event object.
*/
parseComplete: function (e) {
const doclets = e.doclets;
for (let i = 0, ii = doclets.length; i < ii; ++i) {
const doclet = doclets[i];
if (doclet.params) {
const params = doclet.params;
for (let j = 0, jj = params.length; j < jj; ++j) {
const param = params[j];
if (param.type && param.type.names) {
const type = param.type.names[0];
if (type in properties) {
param.type.names[0] = type;
params.push.apply(
params,
properties[type].map((p) => {
const property = Object.assign({}, p);
property.name = `${param.name}.${property.name}`;
return property;
})
);
}
}
}
}
}
},
};

View File

@@ -0,0 +1,115 @@
/* eslint-disable import/no-commonjs */
/**
* Modified from JSDoc's plugins/markdown and lib/jsdoc/util/markdown modules
* (see https://github.com/jsdoc3/jsdoc/), which are licensed under the Apache 2
* license (see https://www.apache.org/licenses/LICENSE-2.0).
*
* This version does not protect http(s) urls from being turned into links, and
* works around an issue with `~` characters in module paths by escaping them.
*/
const marked = require('marked');
const format = require('util').format;
const tags = [
'author',
'classdesc',
'description',
'exceptions',
'params',
'properties',
'returns',
'see',
'summary',
];
const hasOwnProp = Object.prototype.hasOwnProperty;
const markedRenderer = new marked.Renderer();
// Allow prettyprint to work on inline code samples
markedRenderer.code = function (code, language) {
const langClass = language ? ' lang-' + language : '';
return format(
'<pre class="prettyprint source%s"><code>%s</code></pre>',
langClass,
escapeCode(code)
);
};
function escapeCode(source) {
return source
.replace(/</g, '&lt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function escapeUnderscoresAndTildes(source) {
return source.replace(/\{@[^}\r\n]+\}/g, function (wholeMatch) {
return wholeMatch.replace(/(^|[^\\])_/g, '$1\\_').replace('~', '&tilde;');
});
}
function unencodeQuotesAndTildes(source) {
return source.replace(/\{@[^}\r\n]+\}/g, function (wholeMatch) {
return wholeMatch.replace(/&quot;/g, '"').replace(/&tilde;/g, '~');
});
}
function parse(source) {
let result;
source = escapeUnderscoresAndTildes(source);
result = marked(source, {renderer: markedRenderer})
.replace(/\s+$/, '')
.replace(/&#39;/g, "'");
result = unencodeQuotesAndTildes(result);
return result;
}
function shouldProcessString(tagName, text) {
let shouldProcess = true;
// we only want to process `@author` and `@see` tags that contain Markdown links
if ((tagName === 'author' || tagName === 'see') && text.indexOf('[') === -1) {
shouldProcess = false;
}
return shouldProcess;
}
function process(doclet) {
tags.forEach(function (tag) {
if (!hasOwnProp.call(doclet, tag)) {
return;
}
if (
typeof doclet[tag] === 'string' &&
shouldProcessString(tag, doclet[tag])
) {
doclet[tag] = parse(doclet[tag]);
} else if (Array.isArray(doclet[tag])) {
doclet[tag].forEach(function (value, index, original) {
const inner = {};
inner[tag] = value;
process(inner);
original[index] = inner[tag];
});
} else if (doclet[tag]) {
process(doclet[tag]);
}
});
}
exports.handlers = {
newDoclet: function (e) {
process(e.doclet);
},
};

View File

@@ -0,0 +1,69 @@
/* eslint-disable import/no-commonjs */
const classes = {};
const observables = {};
exports.handlers = {
newDoclet: function (e) {
const doclet = e.doclet;
if (doclet.kind == 'class' && !(doclet.longname in classes)) {
classes[doclet.longname] = doclet;
}
},
parseComplete: function (e) {
const doclets = e.doclets;
let cls, doclet, event, i, ii, observable;
for (i = 0, ii = doclets.length - 1; i < ii; ++i) {
doclet = doclets[i];
cls = classes[doclet.longname.split('#')[0]];
if (typeof doclet.observable == 'string' && cls) {
let name = doclet.name.replace(/^[sg]et/, '');
name = name.substr(0, 1).toLowerCase() + name.substr(1);
const key = doclet.longname.split('#')[0] + '#' + name;
doclet.observable = key;
if (!observables[key]) {
observables[key] = {};
}
observable = observables[key];
observable.name = name;
observable.readonly =
typeof observable.readonly == 'boolean' ? observable.readonly : true;
if (doclet.name.indexOf('get') === 0) {
observable.type = doclet.returns[0].type;
observable.description = doclet.returns[0].description;
} else if (doclet.name.indexOf('set') === 0) {
observable.readonly = false;
}
if (doclet.stability) {
observable.stability = doclet.stability;
}
if (!cls.observables) {
cls.observables = [];
}
observable = observables[doclet.observable];
if (observable.type && cls.observables.indexOf(observable) == -1) {
cls.observables.push(observable);
}
if (!cls.fires) {
cls.fires = [];
}
event = 'module:ol/Object.ObjectEvent#event:change:' + name;
if (cls.fires.indexOf(event) == -1) {
cls.fires.push(event);
}
}
}
},
};
exports.defineTags = function (dictionary) {
dictionary.defineTag('observable', {
mustNotHaveValue: true,
canHaveType: false,
canHaveName: false,
onTagged: function (doclet, tag) {
doclet.observable = '';
},
});
};

View File

@@ -0,0 +1,16 @@
/* eslint-disable import/no-commonjs */
/**
* Handle the interface and abstract annotations.
* @param {Object} dictionary The tag dictionary.
*/
exports.defineTags = function (dictionary) {
const classTag = dictionary.lookUp('class');
dictionary.defineTag('interface', {
mustNotHaveValue: true,
onTagged: function (doclet, tag) {
classTag.onTagged.apply(this, arguments);
doclet.virtual = true;
},
});
};