diff --git a/lib/processors/jsdoc/lib/createIndexFiles.js b/lib/processors/jsdoc/lib/createIndexFiles.js
index a0f5b7821..f258e16a6 100644
--- a/lib/processors/jsdoc/lib/createIndexFiles.js
+++ b/lib/processors/jsdoc/lib/createIndexFiles.js
@@ -1,7 +1,7 @@
/*
* Node script to create cross-library API index files for use in the UI5 SDKs.
*
- * (c) Copyright 2009-2019 SAP SE or an SAP affiliate company.
+ * (c) Copyright 2009-2020 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
@@ -248,13 +248,12 @@ function createIndexFiles(versionInfoFile, unpackedTestresourcesRoot, targetFile
let aTree = [];
// Filter out black list libraries
- symbols = symbols.filter(({lib}) => ["sap.ui.demokit", "sap.ui.documentation"].indexOf(lib) === -1);
+ symbols = symbols.filter(({lib}) => ["sap.ui.documentation"].indexOf(lib) === -1);
// Create treeName and displayName
symbols.forEach(oSymbol => {
oSymbol.treeName = oSymbol.name.replace(/^module:/, "").replace(/\//g, ".");
oSymbol.displayName = oSymbol.treeName.split(".").pop();
- oSymbol.bIsDeprecated = !!oSymbol.deprecated;
});
// Create missing - virtual namespaces
@@ -269,8 +268,7 @@ function createIndexFiles(versionInfoFile, unpackedTestresourcesRoot, targetFile
displayName: sPart,
lib: oSymbol.lib,
kind: "namespace",
- visibility: "public", // Virtual namespace are always public
- bIsDeprecated: false // Virtual namespace can't be deprecated
+ visibility: "public" // Virtual namespace are always public
});
}
});
@@ -326,6 +324,12 @@ function createIndexFiles(versionInfoFile, unpackedTestresourcesRoot, targetFile
return 0;
});
+ // walk the tree *from bottom to top*
+ // in order to detect all parent nodes
+ // that should be marked as content-deprecated
+ // because their children are explicitly deprecated
+ toChildrenFirstArray(aTree).forEach(markDeprecatedNodes);
+
// Clean tree - keep file size down
function cleanTree (oSymbol) {
delete oSymbol.treeName;
@@ -339,6 +343,68 @@ function createIndexFiles(versionInfoFile, unpackedTestresourcesRoot, targetFile
return aTree;
}
+ /**
+ * Creates an array of all tree nodes,
+ * where the child nodes precede the parent nodes
+ * @param aTree
+ * @returns {Array}
+ */
+ function toChildrenFirstArray(aTree) {
+ var aChildrenFirst = [];
+ function addToLeafsFirst(node) {
+ if (node.nodes) {
+ node.nodes.forEach(function(child) {
+ addToLeafsFirst(child);
+ });
+ }
+ aChildrenFirst.push(node);
+ }
+ aTree.forEach(function(parent) {
+ addToLeafsFirst(parent);
+ });
+ return aChildrenFirst;
+ }
+
+ /**
+ * Sets the bAllContentDeprecated
flag of each symbol
+ *
+ * The bAllContentDeprecated
flag differs from the already existing deprecated
flag
+ * in the following respect:
+ *
+ * 1) if a node is deprecated => all its children should be marked as bAllContentDeprecated
+ * (even if not explicitly deprecated in their JSDoc)
+ * 2) if all children of the node are deprecated => that node should also be marked as bAllContentDeprecated
+ * (even if not explicitly deprecated in its JSDoc)
+ * 3) if a node is explicitly deprecated in its JSDoc => it should also be marked as bAllContentDeprecated
+ * (for consistency)
+ *
+ * @param oSymbol
+ */
+ function markDeprecatedNodes(oSymbol) {
+ // 1. If the symbol is deprecated all content in it should be also deprecated
+ if (oSymbol.deprecated) {
+ // 2. If all content in the symbol is deprecated, flag should explicitly be passed to its child nodes.
+ propagateFlags(oSymbol, { bAllContentDeprecated: true });
+ } else {
+ // 3. If all children are deprecated, then the parent is marked as content-deprecated
+ oSymbol.bAllContentDeprecated = !!oSymbol.nodes && oSymbol.nodes.every(node => node.bAllContentDeprecated);
+ }
+ }
+
+ /**
+ * Merges the set of flags from oFlags
into the given oSymbol
+ * @param oSymbol
+ * @param oFlags
+ */
+ function propagateFlags(oSymbol, oFlags) {
+ Object.assign(oSymbol, oFlags);
+ if (oSymbol.nodes) {
+ oSymbol.nodes.forEach(node => {
+ propagateFlags(node, oFlags);
+ })
+ }
+ }
+
function createOverallIndex() {
let version = "0.0.0";
const filesToReturn = {};
diff --git a/lib/processors/jsdoc/lib/transformApiJson.js b/lib/processors/jsdoc/lib/transformApiJson.js
index 7071d4c08..af8585e20 100644
--- a/lib/processors/jsdoc/lib/transformApiJson.js
+++ b/lib/processors/jsdoc/lib/transformApiJson.js
@@ -1,7 +1,7 @@
/*
* Node script to preprocess api.json files for use in the UI5 SDKs.
*
- * (c) Copyright 2009-2019 SAP SE or an SAP affiliate company.
+ * (c) Copyright 2009-2020 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
@@ -132,16 +132,24 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
// Attach children info
symbols.forEach(oSymbol => {
if (oSymbol.parent) {
- let oParent = symbols.find(({treeName}) => treeName === oSymbol.parent);
+ let oParent = symbols.find(({treeName}) => treeName === oSymbol.parent),
+ oNode;
if (!oParent.nodes) oParent.nodes = [];
- oParent.nodes.push({
+
+ oNode = {
name: oSymbol.displayName,
description: formatters._preProcessLinksInTextBlock(
extractFirstSentence(oSymbol.description)
),
- href: "#/api/" + encodeURIComponent(oSymbol.name)
- });
+ href: "api/" + encodeURIComponent(oSymbol.name)
+ };
+
+ if (oSymbol.deprecated) {
+ oNode.deprecated = true;
+ }
+
+ oParent.nodes.push(oNode);
}
});
@@ -308,7 +316,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
// Link Enabled
if (oSymbol.kind !== "enum" && !isBuiltInType(oProperty.type) && possibleUI5Symbol(oProperty.type)) {
oProperty.linkEnabled = true;
- oProperty.href = "#/api/" + oProperty.type.replace("[]", "");
+ oProperty.href = "api/" + oProperty.type.replace("[]", "");
}
// Keep file size in check
@@ -392,7 +400,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
oAggregation.description = formatters.formatDescription(oAggregation.description,
oAggregation.deprecated.text, oAggregation.deprecated.since);
} else {
- oAggregation.description = formatters.formatDescription(oAggregation.description);
+ oAggregation.description = formatters.formatDescriptionSince(oAggregation.description, oAggregation.since);
}
// Link enabled
@@ -426,13 +434,39 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
oAssociation.description = formatters.formatDescription(oAssociation.description,
oAssociation.deprecated.text, oAssociation.deprecated.since);
} else {
- oAssociation.description = formatters.formatDescription(oAssociation.description);
+ oAssociation.description = formatters.formatDescriptionSince(oAssociation.description, oAssociation.since);
}
});
}
// Events
if (oMeta.events) {
+
+ oMeta.events.forEach(oEvent => {
+ let aParams = oEvent.parameters;
+
+ aParams && Object.keys(aParams).forEach(sParam => {
+ let sSince = aParams[sParam].since;
+ let oDeprecated = aParams[sParam].deprecated;
+ let oEvtInSymbol = oSymbol.events.find(e => e.name === oEvent.name);
+ let oParamInSymbol = oEvtInSymbol && oEvtInSymbol.parameters[0] &&
+ oEvtInSymbol.parameters[0].parameterProperties &&
+ oEvtInSymbol.parameters[0].parameterProperties.getParameters &&
+ oEvtInSymbol.parameters[0].parameterProperties.getParameters.parameterProperties &&
+ oEvtInSymbol.parameters[0].parameterProperties.getParameters.parameterProperties[sParam];
+
+ if (typeof oParamInSymbol === 'object' && oParamInSymbol !== null) {
+ if (sSince) {
+ oParamInSymbol.since = sSince;
+ }
+
+ if (oDeprecated) {
+ oParamInSymbol.deprecated = oDeprecated;
+ }
+ }
+ })
+ });
+
// We don't need event's data from the UI5-metadata for now. Keep file size in check
delete oMeta.events;
}
@@ -508,13 +542,15 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
}
// Description
- if (oParameter.deprecated) {
- oParameter.description = formatters.formatDescription(oParameter.description,
- oParameter.deprecated.text, oParameter.deprecated.since);
- } else {
- oParameter.description = formatters.formatDescription(oParameter.description);
+ if (oParameter.description) {
+ oParameter.description = formatters.formatDescriptionSince(oParameter.description, oParameter.since);
}
+ // Deprecated
+ if (oParameter.deprecated) {
+ oParameter.deprecatedText = formatters.formatDeprecated(oParameter.deprecated.since,
+ oParameter.deprecated.text);
+ }
});
}
@@ -535,8 +571,8 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
oMethod.name = formatters.formatEntityName(oMethod.name, oSymbol.name, oMethod.static);
// Link
- oMethod.href = "#/api/" + encodeURIComponent(oSymbol.name) +
- "/methods/" + encodeURIComponent(oMethod.name);
+ oMethod.href = "api/" + oSymbol.name +
+ "#methods/" + oMethod.name;
}
formatters.formatReferencesInDescription(oMethod);
@@ -571,7 +607,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
// Link Enabled
if (!isBuiltInType(oType.value) && possibleUI5Symbol(oType.value)) {
oType.linkEnabled = true;
- oType.href = "#/api/" + oType.value.replace("[]", "");
+ oType.href = "api/" + oType.value.replace("[]", "");
}
});
@@ -603,7 +639,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
// Link Enabled
if (!isBuiltInType(oType.value)) {
- oType.href = "#/api/" + encodeURIComponent(oType.value.replace("[]", ""));
+ oType.href = "api/" + oType.value.replace("[]", "");
oType.linkEnabled = true;
}
@@ -993,11 +1029,13 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
* @returns string - the formatted text
*/
formatAnnotationNamespace: function (namespace) {
- var result,
- aNamespaceParts = namespace.split(".");
+ var aNamespaceParts = namespace.split("."),
+ result, target, text;
if (aNamespaceParts[0] === "Org" && aNamespaceParts[1] === "OData") {
- result = '' + namespace + '';
+ target = this.ANNOTATIONS_NAMESPACE_LINK + namespace + ".xml";
+ text = namespace;
+ result = this.handleExternalUrl(target, text);
} else {
result = namespace;
}
@@ -1110,15 +1148,15 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
var sReturn;
switch (defaultValue) {
- case null:
- case undefined:
- sReturn = '';
- break;
- case '':
- sReturn = 'empty string';
- break;
- default:
- sReturn = defaultValue;
+ case null:
+ case undefined:
+ sReturn = '';
+ break;
+ case '':
+ sReturn = 'empty string';
+ break;
+ default:
+ sReturn = defaultValue;
}
return Array.isArray(sReturn) ? sReturn.join(', ') : sReturn;
@@ -1313,7 +1351,7 @@ title="Information published on ${bSAPHosted ? '' : 'non '}SAP site" class="sapU
let oProperty = findProperty(oSymbol, methodName, target);
if (oProperty) {
sResult = this.createLink({
- name: oProperty.name,
+ name: className,
text: text
});
return true;
@@ -1409,21 +1447,18 @@ title="Information published on ${bSAPHosted ? '' : 'non '}SAP site" class="sapU
name = name.replace(/^module:/, "");
}
- name = encodeURIComponent(name);
- className = encodeURIComponent(className);
-
// Build the link
- sLink = type ? `${className}/${type}/${name}` : name;
+ sLink = type ? `${className}#${type}/${name}` : name;
if (hrefAppend) {
sLink += hrefAppend;
}
if (local) {
- return `${text}`;
+ return `${text}`;
}
- return `${text}`;
+ return `${text}`;
},
/**
@@ -1453,7 +1488,7 @@ title="Information published on ${bSAPHosted ? '' : 'non '}SAP site" class="sapU
// topic:xxx Topic
aMatch = sTarget.match(/^topic:(\w{32})$/);
if (aMatch) {
- return '' + sText + '';
+ return '' + sText + '';
}
// sap.x.Xxx.prototype.xxx - In case of prototype we have a link to method
@@ -1499,7 +1534,7 @@ title="Information published on ${bSAPHosted ? '' : 'non '}SAP site" class="sapU
return this.createLink({
name: sName,
- hrefAppend: "/constructor",
+ hrefAppend: "#constructor",
text: sText
});
}
@@ -1974,14 +2009,14 @@ title="Information published on ${bSAPHosted ? '' : 'non '}SAP site" class="sapU
// Start the work here
let p = getLibraryPromise(oChainObject)
- .then(extractComponentAndDocuindexUrl)
- .then(flattenComponents)
- .then(extractSamplesFromDocuIndex)
- .then(getDependencyLibraryFilesList)
- .then(getAPIJSONPromise)
- .then(loadDependencyLibraryFiles)
- .then(transformApiJson)
- .then(createApiRefApiJson);
+ .then(extractComponentAndDocuindexUrl)
+ .then(flattenComponents)
+ .then(extractSamplesFromDocuIndex)
+ .then(getDependencyLibraryFilesList)
+ .then(getAPIJSONPromise)
+ .then(loadDependencyLibraryFiles)
+ .then(transformApiJson)
+ .then(createApiRefApiJson);
return p;
};
diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js
index ce18e7a9e..1f5eef9dd 100644
--- a/lib/processors/jsdoc/lib/ui5/plugin.js
+++ b/lib/processors/jsdoc/lib/ui5/plugin.js
@@ -1,7 +1,7 @@
/*
* JSDoc3 plugin for UI5 documentation generation.
*
- * (c) Copyright 2009-2019 SAP SE or an SAP affiliate company.
+ * (c) Copyright 2009-2020 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
@@ -56,7 +56,7 @@ var Syntax = require('jsdoc/src/syntax').Syntax;
var Doclet = require('jsdoc/doclet').Doclet;
var fs = require('jsdoc/fs');
var path = require('jsdoc/path');
-var pluginConfig = (env.conf && env.conf.templates && env.conf.templates.ui5) || {};
+var pluginConfig = (env.conf && env.conf.templates && env.conf.templates.ui5) || env.opts.sapui5 || {};
/* ---- global vars---- */
@@ -99,7 +99,7 @@ var currentProgram;
* have a 'module' and optionally a 'path' value. In that case, the local name represents an AMD
* module import or a shortcut derived from such an import.
*
- * See {@link getREsolvedObjectName} how the knowledge about locale names is used.
+ * See {@link getResolvedObjectName} how the knowledge about locale names is used.
*
* @type {{name:string,resource:string,module:string,localName:Object}}
*/
@@ -117,6 +117,17 @@ var currentSource;
*/
var classInfos = Object.create(null);
+/**
+ * Map of enum value objects keyed by a unqiue enum ID.
+ *
+ * When the AST visitor detects an object literal that might be an enum, it cannot easily determine
+ * the name of the enum. Therefore, the collected enum values are stored in this map keyed by a
+ * unique ID derived from the key set of the (potential) enum (ID = sorted key set, joined with '|').
+ *
+ * In the parseComplete phase, the found values are merged into enum symbols (as detected by JSDoc).
+ */
+var enumValues = Object.create(null);
+
/**
*
*/
@@ -281,7 +292,7 @@ function analyzeModuleDefinition(node) {
/**
* Searches the given body for variable declarations that can be evaluated statically,
- * either because they refer to known AMD modukle imports (e.g. shortcut varialbes)
+ * either because they refer to known AMD module imports (e.g. shortcut variables)
* or because they have a (design time) constant value.
*
* @param {ASTNode} body AST node of a function body that shall be searched for shortcuts
@@ -494,6 +505,19 @@ function isProbingRequireCall(node) {
);
}
+function isPotentialEnum(node) {
+ if ( node == null || node.type !== Syntax.ObjectExpression ) {
+ return false;
+ }
+ return node.properties.every(function(prop) {
+ return isCompileTimeConstant(prop.value);
+ });
+}
+
+function isCompileTimeConstant(node) {
+ return node && node.type === Syntax.Literal;
+}
+
function getObjectName(node) {
if ( node.type === Syntax.MemberExpression && !node.computed && node.property.type === Syntax.Identifier ) {
var prefix = getObjectName(node.object);
@@ -686,7 +710,8 @@ function collectClassInfo(extendCall, classDoclet) {
methods : {},
annotations : {},
designtime: false,
- stereotype: null
+ stereotype: null,
+ metadataClass: undefined
};
function upper(n) {
@@ -713,6 +738,17 @@ function collectClassInfo(extendCall, classDoclet) {
}
}
+ if ( extendCall.arguments.length > 2 ) {
+ // new class defines its own metadata class type
+ var metadataClass = getResolvedObjectName(extendCall.arguments[2]);
+ if ( metadataClass ) {
+ oClassInfo.metadataClass = getResolvedObjectName(extendCall.arguments[2]);
+ debug("found metadata class name '" + oClassInfo.metadataClass + "'");
+ } else {
+ error("cannot understand metadata class parameter (AST node type '" + extendCall.arguments[2] + "')");
+ }
+ }
+
var classInfoNode = extendCall.arguments[1];
var classInfoMap = createPropertyMap(classInfoNode);
if ( classInfoMap && classInfoMap.metadata && classInfoMap.metadata.value.type !== Syntax.ObjectExpression ) {
@@ -1211,6 +1247,25 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment
return prefix + n.slice(0,1).toUpperCase() + n.slice(1);
}
+ function generateParamTag(n, type, description, defaultValue){
+ var s = "@param {" + info.type + "} ";
+
+ if (defaultValue !== null) {
+ s += "[" + varname(n, type, true) + "=";
+ if (type === "string"){
+ defaultValue = "\"" + defaultValue + "\"";
+ }
+ s += defaultValue + "]";
+
+ } else {
+ s += varname(n, type, true);
+ }
+
+ s += " " + description;
+
+ return s;
+ }
+
// add a list of the possible settings if and only if
// - documentation for the constructor exists
// - no (generated) documentation for settings exists already
@@ -1357,7 +1412,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment
"",
"@param {string} sClassName Name of the class being created",
"@param {object} [oClassInfo] Object literal with information about the class",
- "@param {function} [FNMetaImpl] Constructor function for the metadata object; if not given, it defaults to sap.ui.core.ElementMetadata
",
+ "@param {function} [FNMetaImpl] Constructor function for the metadata object; if not given, it defaults to the metadata implementation used by this class",
"@returns {function} Created class / constructor function",
"@public",
"@static",
@@ -1395,7 +1450,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment
"When called with a value of null
or undefined
, the default value of the property will be restored.",
"",
info.defaultValue !== null ? "Default value is " + (info.defaultValue === "" ? "empty string" : info.defaultValue) + "
." : "",
- "@param {" + info.type + "} " + varname(n,info.type,true) + " New value for property " + n + "
",
+ generateParamTag(n, info.type, "New value for property " + n + "
", info.defaultValue),
"@returns {" + oClassInfo.name + "} Reference to this
in order to allow method chaining",
info.since ? "@since " + info.since : "",
info.deprecation ? "@deprecated " + info.deprecation : "",
@@ -2278,6 +2333,28 @@ exports.handlers = {
};
}
+ if ( doclet.kind === 'member' && doclet.isEnum && Array.isArray(doclet.properties) ) {
+ // determine unique enum identifier from key set
+ var enumID = doclet.properties.map(function(prop) {
+ return prop.name;
+ }).sort().join("|");
+ if ( enumValues[enumID] ) {
+ // debug("found enum values for ", enumID, enumValues[enumID]);
+ var standardEnum = true;
+ /* eslint-disable no-loop-func */
+ doclet.properties.forEach(function(prop) {
+ prop.__ui5.value = enumValues[enumID][prop.name];
+ if ( prop.__ui5.value !== prop.name ) {
+ standardEnum = false;
+ }
+ });
+ /* eslint-enable no-loop-func */
+ if ( standardEnum ) {
+ doclet.__ui5.stereotype = 'enum';
+ }
+ }
+ }
+
// check for duplicates: last one wins
if ( j > 0 && doclets[j - 1].longname === doclet.longname ) {
if ( !doclets[j - 1].synthetic && !doclet.__ui5.updatedDoclet ) {
@@ -2331,6 +2408,18 @@ exports.astNodeVisitor = {
}
}
+ function processPotentialEnum(literal, comment) {
+ var values = literal.properties.reduce(function(map, prop) {
+ map[getPropertyKey(prop)] = convertValue(prop.value);
+ return map;
+ }, Object.create(null));
+ // determine unique enum ID from key set
+ var enumID = Object.keys(values).sort().join("|");
+ // and remember the values with that ID
+ enumValues[enumID] = values;
+ // debug("found enum values for key-set", enumID);
+ }
+
if ( node.type === Syntax.ExpressionStatement ) {
if ( isSapUiDefineCall(node.expression) ) {
analyzeModuleDefinition(node.expression);
@@ -2372,6 +2461,9 @@ exports.astNodeVisitor = {
comment = (idx === 0 ? getLeadingCommentNode(node) : undefined) || getLeadingCommentNode(decl);
// console.log("ast node with comment " + comment);
processExtendCall(decl.init, comment);
+ } else if ( isPotentialEnum(decl.init) ) {
+ comment = (idx === 0 ? getLeadingCommentNode(node) : undefined) || getLeadingCommentNode(decl);
+ processPotentialEnum(decl.init, comment);
}
});
@@ -2392,6 +2484,10 @@ exports.astNodeVisitor = {
processDataType(node.expression.right);
// TODO remember knowledge about type and its name (left hand side of assignment)
+ } else if ( isPotentialEnum(node.expression.right) ) {
+ comment = getLeadingCommentNode(node) || getLeadingCommentNode(node.expression);
+ // console.log(getResolvedObjectName(node.expression.left));
+ processPotentialEnum(node.expression.right, comment);
}
}
diff --git a/lib/processors/jsdoc/lib/ui5/template/publish.js b/lib/processors/jsdoc/lib/ui5/template/publish.js
index c7a08d445..ac619c469 100644
--- a/lib/processors/jsdoc/lib/ui5/template/publish.js
+++ b/lib/processors/jsdoc/lib/ui5/template/publish.js
@@ -1,7 +1,7 @@
/*
* JSDoc3 template for UI5 documentation generation.
*
- * (c) Copyright 2009-2019 SAP SE or an SAP affiliate company.
+ * (c) Copyright 2009-2020 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
@@ -19,6 +19,7 @@ var template = require('jsdoc/template'),
/* globals, constants */
var MY_TEMPLATE_NAME = "ui5",
+ MY_ALT_TEMPLATE_NAME = "sapui5-jsdoc3",
ANONYMOUS_LONGNAME = doclet.ANONYMOUS_LONGNAME,
A_SECURITY_TAGS = [
{
@@ -56,8 +57,9 @@ var MY_TEMPLATE_NAME = "ui5",
var rSecurityTags = new RegExp(A_SECURITY_TAGS.map(function($) {return $.name.toLowerCase(); }).join('|'), "i");
//debug(A_SECURITY_TAGS.map(function($) {return $.name; }).join('|'));
-var templateConf = (env.conf.templates || {})[MY_TEMPLATE_NAME] || {},
- pluginConf = templateConf,
+var templatesConf = (env.conf.templates || {}),
+ templateConf = templatesConf[MY_TEMPLATE_NAME] || templatesConf[MY_ALT_TEMPLATE_NAME] || {},
+ pluginConf = templateConf,
conf = {},
view;
@@ -580,50 +582,51 @@ function publish(symbolSet) {
var now = new Date();
info("start publishing");
- for (var i = 0; i < templateConf.variants.length; i++) {
+ if ( Array.isArray(templateConf.variants) ) {
+ templateConf.variants.forEach(function(vVariant) {
- var vVariant = templateConf.variants[i];
- if ( typeof vVariant === "string" ) {
- vVariant = { variant : vVariant };
- }
-
- info("");
-
- if ( PUBLISHING_VARIANTS[vVariant.variant] ) {
-
- // Merge different sources of configuration (listed in increasing priority order - last one wins)
- // and expose the result in the global 'conf' variable
- // - global defaults
- // - defaults for current variant
- // - user configuration for sapui5 template
- // - user configuration for current variant
- //
- // Note: trailing slash expected for dirs
- conf = merge({
- ext: ".html",
- filter: function($) { return true; },
- templatesDir: "/templates/sapui5/",
- symbolsDir: "symbols/",
- modulesDir: "modules/",
- topicUrlPattern: "../../guide/{{topic}}.html",
- srcDir: "symbols/src/",
- creationDate : now.getFullYear() + "-" + (now.getMonth() + 1) + "-" + now.getDay() + " " + now.getHours() + ":" + now.getMinutes(),
- outdir: env.opts.destination
- }, PUBLISHING_VARIANTS[vVariant.variant].defaults, templateConf, vVariant);
-
- info("publishing as variant '" + vVariant.variant + "'");
- debug("final configuration:");
- debug(conf);
-
- PUBLISHING_VARIANTS[vVariant.variant].processor(conf);
+ if ( typeof vVariant === "string" ) {
+ vVariant = { variant : vVariant };
+ }
- info("done with variant " + vVariant.variant);
+ info("");
+
+ if ( PUBLISHING_VARIANTS[vVariant.variant] ) {
+
+ // Merge different sources of configuration (listed in increasing priority order - last one wins)
+ // and expose the result in the global 'conf' variable
+ // - global defaults
+ // - defaults for current variant
+ // - user configuration for sapui5 template
+ // - user configuration for current variant
+ //
+ // Note: trailing slash expected for dirs
+ conf = merge({
+ ext: ".html",
+ filter: function($) { return true; },
+ templatesDir: "/templates/sapui5/",
+ symbolsDir: "symbols/",
+ modulesDir: "modules/",
+ topicUrlPattern: "../../guide/{{topic}}.html",
+ srcDir: "symbols/src/",
+ creationDate : now.getFullYear() + "-" + (now.getMonth() + 1) + "-" + now.getDay() + " " + now.getHours() + ":" + now.getMinutes(),
+ outdir: env.opts.destination
+ }, PUBLISHING_VARIANTS[vVariant.variant].defaults, templateConf, vVariant);
+
+ info("publishing as variant '" + vVariant.variant + "'");
+ debug("final configuration:");
+ debug(conf);
+
+ PUBLISHING_VARIANTS[vVariant.variant].processor(conf);
+
+ info("done with variant " + vVariant.variant);
- } else {
+ } else {
- info("cannot publish unknown variant '" + vVariant.variant + "' (ignored)");
+ info("cannot publish unknown variant '" + vVariant.variant + "' (ignored)");
- }
+ }
+ });
}
var builtinSymbols = templateConf.builtinSymbols;
@@ -741,17 +744,23 @@ function createInheritanceTree() {
function getOrCreateClass(sClass, sExtendingClass) {
var oClass = lookup(sClass);
if ( !oClass ) {
- warning("create missing class " + sClass + " (extended by " + sExtendingClass + ")");
- var sBaseClass = 'Object';
+ var sKind = "class",
+ sBaseClass = 'Object',
+ sVisibility = "public";
if ( externalSymbols[sClass] ) {
+ sKind = externalSymbols[sClass].kind || sKind;
sBaseClass = externalSymbols[sClass].extends || sBaseClass;
+ sVisibility = externalSymbols[sClass].visibility || sVisibility;
+ debug("create doclet for external class " + sClass + " (extended by " + sExtendingClass + ")");
+ } else {
+ warning("create missing class " + sClass + " (extended by " + sExtendingClass + ")");
}
var oBaseClass = getOrCreateClass(sBaseClass, sClass);
oClass = makeDoclet(sClass, [
"@extends " + sBaseClass,
- "@class",
+ "@" + sKind,
"@synthetic",
- "@public"
+ sVisibility === "restricted" ? "@ui5-restricted" : "@" + sVisibility
]);
oClass.__ui5.base = oBaseClass;
oBaseClass.__ui5.derived = oBaseClass.__ui5.derived || [];
@@ -786,12 +795,17 @@ function createInheritanceTree() {
for (var j = 0; j < oClass.implements.length; j++) {
var oInterface = lookup(oClass.implements[j]);
if ( !oInterface ) {
- warning("create missing interface " + oClass.implements[j]);
+ var sVisibility = "public";
+ if ( externalSymbols[oClass.implements[j]] ) {
+ sVisibility = externalSymbols[oClass.implements[j]] || sVisibility;
+ debug("create doclet for external interface " + oClass.implements[j]);
+ } else {
+ warning("create missing interface " + oClass.implements[j]);
+ }
oInterface = makeDoclet(oClass.implements[j], [
- "@extends Object",
"@interface",
"@synthetic",
- "@public"
+ sVisibility === "restricted" ? "@ui5-restricted" : "@" + sVisibility
]);
oInterface.__ui5.base = oObject;
oObject.__ui5.derived = oObject.__ui5.derived || [];
@@ -1697,7 +1711,7 @@ function TypeParser(defaultBuilder) {
* - function(this:) // type of this
* - function(new:) // constructor
*/
- var rLexer = /\s*(Array\.?<|Object\.?<|Set\.?<|Promise\.?<|function\(|\{|:|\(|\||\}|>|\)|,|\[\]|\*|\?|!|\.\.\.)|\s*((?:module:)?\w+(?:[\/.#~]\w+)*)|./g;
+ var rLexer = /\s*(Array\.?<|Object\.?<|Set\.?<|Promise\.?<|function\(|\{|:|\(|\||\}|\.?<|>|\)|,|\[\]|\*|\?|!|\.\.\.)|\s*((?:module:)?\w+(?:[\/.#~]\w+)*)|./g;
var input,
builder,
@@ -1836,9 +1850,24 @@ function TypeParser(defaultBuilder) {
} else {
type = builder.simpleType(tokenStr);
next('symbol');
- while ( token === '[]' ) {
+ // check for suffix operators: either 'type application' (generics) or 'array', but not both of them
+ if ( token === "<" || token === ".<" ) {
next();
- type = builder.array(type);
+ var templateTypes = [];
+ while ( token !== ">" ) {
+ var templateType = parseType();
+ templateTypes.push(templateType);
+ if ( token === ',' ) {
+ next();
+ }
+ }
+ next(">");
+ type = builder.typeApplication(type, templateTypes);
+ } else {
+ while ( token === '[]' ) {
+ next();
+ type = builder.array(type);
+ }
}
}
if ( nullable ) {
@@ -1942,6 +1971,13 @@ TypeParser.ASTBuilder = {
repeatable: function(type) {
type.repeatable = true;
return type;
+ },
+ typeApplication: function(type, templateTypes) {
+ return {
+ type: 'typeApplication',
+ baseType: type,
+ templateTypes: templateTypes
+ };
}
};
@@ -1972,6 +2008,7 @@ TypeParser.LinkBuilder.prototype = {
array: function(componentType) {
if ( componentType.needsParenthesis || componentType.simpleComponent === false ) {
return {
+ simpleComponent: false,
str: "Array.<" + this.safe(componentType) + ">"
};
}
@@ -1982,20 +2019,24 @@ TypeParser.LinkBuilder.prototype = {
object: function(keyType, valueType) {
if ( keyType.synthetic ) {
return {
+ simpleComponent: false,
str: "Object." + this.lt + this.safe(valueType) + this.gt
};
}
return {
+ simpleComponent: false,
str: "Object." + this.lt + this.safe(keyType) + "," + this.safe(valueType) + this.gt
};
},
set: function(elementType) {
return {
+ simpleComponent: false,
str: 'Set.' + this.lt + this.safe(elementType) + this.gt
};
},
promise: function(fulfillmentType) {
return {
+ simpleComponent: false,
str: 'Promise.' + this.lt + this.safe(fulfillmentType) + this.gt
};
},
@@ -2040,6 +2081,12 @@ TypeParser.LinkBuilder.prototype = {
repeatable: function(type) {
type.str = "..." + type.str;
return type;
+ },
+ typeApplication: function(type, templateTypes) {
+ return {
+ simpleComponent: false,
+ str: this.safe(type) + this.lt + templateTypes.map(function(type) { return this.safe(type); }, this).join(',') + this.gt
+ };
}
};
@@ -2183,7 +2230,13 @@ function createAPIJSON(symbols, filename) {
// sort only a copy(!) of the symbols, otherwise the SymbolSet lookup is broken
symbols.slice(0).sort(sortByAlias).forEach(function(symbol) {
if ( isFirstClassSymbol(symbol) && !symbol.synthetic ) { // dump a symbol if it as a class symbol and if it is not a synthetic symbol
- api.symbols.push(createAPIJSON4Symbol(symbol, false));
+ try {
+ var json = createAPIJSON4Symbol(symbol, false);
+ api.symbols.push(json);
+ } catch (e) {
+ error("failed to create api summary for " + symbol.name, e);
+ throw e;
+ }
}
});
@@ -2214,6 +2267,13 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
return true;
}
+ // In some cases, JSDoc does not provide a basename in property symbol.name, but a partially qualified name
+ // this function reduces this to the base name
+ function basename(name) {
+ var p = name.lastIndexOf(".");
+ return p < 0 ? name : name.slice(p + 1);
+ }
+
function tag(name, value, omitEmpty) {
if ( omitEmpty && !value ) {
@@ -2766,7 +2826,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
tag(kind);
attrib("name", symbol.longname);
- attrib("basename", symbol.name);
+ attrib("basename", basename(symbol.name));
if ( symbol.__ui5.resource ) {
attrib("resource", symbol.__ui5.resource);
}
@@ -2810,16 +2870,20 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
var skipMembers = false;
var i, j, member, param;
+ var standardEnum = false;
if ( kind === 'class' ) {
- if ( symbol.__ui5.stereotype || hasSettings(symbol) ) {
+ if ( symbol.__ui5.stereotype || (symbol.__ui5.metadata && symbol.__ui5.metadata.metadataClass) || hasSettings(symbol) ) {
tag("ui5-metadata");
if ( symbol.__ui5.stereotype ) {
attrib("stereotype", symbol.__ui5.stereotype);
}
+ if ( symbol.__ui5.metadata && symbol.__ui5.metadata.metadataClass ) {
+ attrib("metadataClass", symbol.__ui5.metadata.metadataClass);
+ }
writeMetadata(symbol);
@@ -2827,7 +2891,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
}
- // IF @hideconstructor tag is present we omit the whole constructor
+ // if @hideconstructor tag is present we omit the whole constructor
if ( !symbol.hideconstructor ) {
tag("constructor");
@@ -2882,6 +2946,13 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
});
endCollection("properties");
}
+ } else if ( kind === 'enum' ) {
+ if ( symbol.__ui5.stereotype ) {
+ tag("ui5-metadata");
+ attrib("stereotype", symbol.__ui5.stereotype);
+ standardEnum = symbol.__ui5.stereotype === 'enum';
+ closeTag("ui5-metadata");
+ }
} else if ( kind === 'function' ) {
methodSignature(symbol);
}
@@ -2898,6 +2969,9 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
attrib("module", member.__ui5.module);
attrib("export", undefined, '', true);
}
+ if ( kind === 'enum' && !standardEnum && member.__ui5.value !== undefined ) {
+ attrib("value", member.__ui5.value, undefined, /* raw = */true);
+ }
attrib("visibility", visibility(member), 'public');
if ( member.scope === 'static' ) {
attrib("static", true, false, /* raw = */true);
@@ -3069,7 +3143,10 @@ function postProcessAPIJSON(api) {
symbols.forEach(function(symbol) {
// debug("check ", symbol.name, "against", defaultExport, "and", moduleNamePath);
- if ( symbol.name === moduleNamePath ) {
+ if ( symbol.symbol.kind === "typedef" || symbol.symbol.kind === "interface" ) {
+ // type definitions and interfaces have no representation on module level
+ symbol.symbol.export = undefined;
+ } else if ( symbol.name === moduleNamePath ) {
// symbol name is the same as the module namepath -> symbol is the default export
symbol.symbol.export = "";
} else if ( symbol.name.lastIndexOf(moduleNamePath + ".", 0) === 0 ) {
@@ -3091,7 +3168,7 @@ function postProcessAPIJSON(api) {
|| (symbol.symbol.methods && symbol.symbol.methods.length > 0) ) {
error("could not identify export name of '" + symbol.name + "', contained in module '" + moduleName + "'");
} else {
- debug("could not identify export name of namespace '" + symbol.name + "', contained in module '" + moduleName + "'");
+ debug("could not identify export name of " + symbol.symbol.kind + " '" + symbol.name + "', contained in module '" + moduleName + "'");
}
}
});
@@ -3101,21 +3178,61 @@ function postProcessAPIJSON(api) {
for ( n in modules ) {
guessExports(n, modules[n]);
}
+
+ function findSymbol(name) {
+ if ( name == null || name === '' ) {
+ return null;
+ }
+ return symbols.find(function(candidate) {
+ return candidate.name === name;
+ }) || externalSymbols[name];
+ }
+
+ function findMetadataClass(symbol) {
+ while ( symbol ) {
+ if ( symbol["ui5-metadata"] && symbol["ui5-metadata"].metadataClass ) {
+ var metadataSymbol = findSymbol(symbol["ui5-metadata"].metadataClass);
+ if ( metadataSymbol != null && metadataSymbol.visibility === "public" ) {
+ return symbol["ui5-metadata"].metadataClass;
+ }
+ }
+ symbol = findSymbol(symbol.extends);
+ }
+ // return undefined
+ }
+
+ symbols.forEach(function(symbol) {
+ if ( !symbol["ui5-metadata"] ) {
+ return;
+ }
+ if ( Array.isArray(symbol.methods) ) {
+ symbol.methods.forEach(function(method) {
+ if ( method.name === "getMetadata" && method.returnValue ) {
+ var metadataClass = findMetadataClass(symbol);
+ if ( metadataClass && metadataClass !== method.returnValue.type ) {
+ method.returnValue.type = metadataClass;
+ debug(" return type of " + symbol.name + (method.static ? "." : "#") + "getMetadata changed to '" + metadataClass + "'");
+ }
+ }
+ });
+ }
+ });
+
}
var builtinTypes = {
- void:true,
+ "void":true,
any:true,
- boolean:true,
- int: true,
- float:true,
+ "boolean":true,
+ "int": true,
+ "float":true,
array:true,
- function:true,
+ "function":true,
string:true,
object:true,
"*": true,
number:true,
- null:true,
+ "null":true,
undefined:true,
// builtin objects
@@ -3129,22 +3246,45 @@ var builtinTypes = {
Promise:true,
ArrayBuffer:true,
Uint8Array:true,
- Blob:true,
Error:true,
TypeError:true,
SyntaxError:true,
- // DOM & Browser APIs
+ // Web APIs
+ Blob:true,
Document:true,
Element:true,
Event:true,
+ File:true,
HTMLElement: true,
Node:true,
Touch:true,
TouchList:true,
Window: true
+
};
+var typeNormalizer = (function() {
+ function TypeNormalizer() {
+ TypeParser.LinkBuilder.call(this, 'text', false);
+ }
+ TypeNormalizer.prototype = Object.create(TypeParser.LinkBuilder.prototype);
+ TypeNormalizer.prototype.simpleType = function(type) {
+ if ( type === 'map' ) {
+ return this.object(
+ this.simpleType('string'),
+ this.simpleType('any')
+ );
+ }
+ if ( type === '*' ) {
+ type = 'any';
+ }
+ return TypeParser.LinkBuilder.prototype.simpleType.call(this, type);
+ };
+
+ return new TypeNormalizer();
+}());
+
function validateAPIJSON(api) {
// create map of defined symbols (built-in types, dependency libraries, current library)
@@ -3153,14 +3293,66 @@ function validateAPIJSON(api) {
api.symbols.forEach(function(symbol) { defined[symbol.name] = symbol; });
}
+ var naming = Object.create(null);
var missing = Object.create(null);
+ var rValidNames = /^[$A-Z_a-z][$0-9A-Z_a-z]*$/i;
+ var rValidModuleNames = /^[$A-Z_a-z][$\-\.0-9A-Z_a-z]*$/i;
+
+ function checkName(name, hint) {
+ if ( !rValidNames.test(name) ) {
+ naming[name] = naming[name] || [];
+ naming[name].push(hint);
+ }
+ }
+
+ function checkModuleName(name, hint) {
+ if ( !rValidModuleNames.test(name) ) {
+ naming[name] = naming[name] || [];
+ naming[name].push(hint);
+ }
+ }
+
+ function checkCompoundName(name, hint) {
+
+ if ( name.startsWith("module:") ) {
+ var segments = name.slice("module:".length).split("/");
+
+ // split last segment into a module name part and a symbol name part
+ var p = segments[segments.length - 1].search(/[.~#]/);
+ if ( p >= 0 ) {
+ name = segments[segments.length - 1].slice(p + 1);
+ segments[segments.length - 1] = segments[segments.length - 1].slice(0, p);
+ }
+
+ // check all module name parts
+ segments.forEach(function(segment) {
+ checkModuleName(segment, "path segment of " + hint);
+ });
+
+ if ( p < 0 ) {
+ // module name only, no export name to check
+ return;
+ }
+ }
+
+ name.split(/[.~#]/).forEach(function(segment) {
+ checkName(segment, "name segment of " + hint);
+ });
+ }
+
function reportError(type, usage) {
missing[type] = missing[type] || [];
missing[type].push(usage);
}
- function check(type, hint) {
+ function checkSimpleType(typeName, hint) {
+ if ( !defined[typeName] ) {
+ reportError(typeName, hint);
+ }
+ }
+
+ function checkType(type, hint) {
function _check(type) {
if ( type == null ) {
@@ -3168,9 +3360,7 @@ function validateAPIJSON(api) {
}
switch (type.type) {
case 'simpleType':
- if ( !defined[type.name] ) {
- reportError(type.name, hint);
- }
+ checkSimpleType(type.name, hint);
break;
case 'array':
_check(type.component);
@@ -3199,21 +3389,31 @@ function validateAPIJSON(api) {
case 'union':
type.types.forEach(_check);
break;
+ case 'typeApplication':
+ _check(type.baseType);
+ type.templateTypes.forEach(_check);
+ // TODO check number of templateTypes against declaration of baseType
+ // requires JSDoc support of @template tag, which is currently missing
+ break;
default:
break;
}
}
try {
- // console.log("check", type);
- var ast = typeParser.parse(type);
+ // debug("normalize", type);
+ type.type = typeParser.parse(type.type, typeNormalizer).str;
+ // debug("check", type);
+ var ast = typeParser.parse(type.type);
_check(ast);
} catch (e) {
- reportError(type, "failed to parse type of " + hint);
+ error(e);
+ reportError(type.type, "failed to parse type of " + hint);
}
}
function checkParam(param, prefix, hint) {
- check(param.type, "param " + prefix + param.name + " of " + hint);
+ checkName(param.name, "name of param " + prefix + param.name + " of " + hint);
+ checkType(param, "param " + prefix + param.name + " of " + hint);
if ( param.parameterProperties ) {
Object.keys(param.parameterProperties).forEach(function(sub) {
checkParam(param.parameterProperties[sub], prefix + param.name + ".", hint);
@@ -3221,9 +3421,9 @@ function validateAPIJSON(api) {
}
}
- function checkMethod(method, hint) {
+ function checkMethodSignature(method, hint) {
if ( method.returnValue ) {
- check(method.returnValue.type, "return value of " + hint);
+ checkType(method.returnValue, "return value of " + hint);
}
if ( method.parameters ) {
method.parameters.forEach(function(param) {
@@ -3232,7 +3432,7 @@ function validateAPIJSON(api) {
}
if ( method.throws ) {
method.throws.forEach(function(ex) {
- check(ex.type, "exception of " + hint);
+ checkType(ex, "exception of " + hint);
});
}
}
@@ -3240,49 +3440,93 @@ function validateAPIJSON(api) {
function checkClassAgainstInterface(symbol, oIntfAPI) {
if ( oIntfAPI.methods ) {
oIntfAPI.methods.forEach(function(intfMethod) {
- // ignore optional methods
- if ( intfMethod.optional ) {
- return;
- }
// search for method implementation
var implMethod = symbol.methods.find(function(candidateMethod) {
return candidateMethod.name === intfMethod.name && !candidateMethod.static;
- })
+ });
if ( !implMethod ) {
- reportError(oIntfAPI.name, "implementation of " + intfMethod.name + " missing in " + symbol.name);
+ if ( !intfMethod.optional ) {
+ reportError(oIntfAPI.name, "implementation of " + intfMethod.name + " missing in " + symbol.name);
+ }
+ } else {
+ if ( intfMethod.parameters ) {
+ intfMethod.parameters.forEach(function(intfParam, idx) {
+ var implParam = implMethod.parameters && implMethod.parameters[idx];
+ if ( !implParam ) {
+ if ( !intfParam.optional ) {
+ reportError(oIntfAPI.name, "parameter " + intfParam.name + " missing in implementation of " + symbol.name + "#" + intfMethod.name);
+ }
+ } else {
+ if ( implParam.type !== intfParam.type ) {
+ reportError(oIntfAPI.name, "type of parameter " + intfParam.name + " of interface method differs from type in implementation " + symbol.name + "#" + intfMethod.name);
+ }
+ // TODO check nested properties
+ }
+ });
+ }
+ if ( intfMethod.returnValue != null && implMethod.returnValue == null ) {
+ reportError(oIntfAPI.name, "return value of interface method missing in implementation " + symbol.name + "#" + intfMethod);
+ } else if ( intfMethod.returnValue == null && implMethod.returnValue != null ) {
+ reportError(oIntfAPI.name, "while interface method is void, implementation " + symbol.name + "#" + intfMethod.name + " returns a value");
+ } else if ( intfMethod.returnValue != null && implMethod.returnValue != null ) {
+ if ( intfMethod.returnValue.type !== implMethod.returnValue.type ) {
+ reportError(oIntfAPI.name, "return type of interface method differs from return type of implementation " + symbol.name + "#" + intfMethod.name);
+ }
+ }
+ }
+ });
+ }
+ }
+
+ function checkEnum(symbol) {
+ if ( symbol["ui5-metamodel"] && !(symbol["ui5-metadata"] && symbol["ui5-metadata"].stereotype === "enum") ) {
+ reportError(symbol.name, "enum is metamodel relevant but keys and values differ");
+ }
+ checkCompoundName(symbol.name, "name of " + symbol.name);
+ if ( symbol.properties ) {
+ symbol.properties.forEach(function(prop) {
+ checkName(prop.name, "name of " + symbol.name + "." + prop.name);
+ if ( prop.type ) {
+ checkType(prop, "type of " + symbol.name + "." + prop.name);
}
- // TODO check parameters
});
}
}
function checkClass(symbol) {
+ checkCompoundName(symbol.name, "name of " + symbol.name);
if ( symbol.extends ) {
- check(symbol.extends, "base class of " + symbol.name);
+ checkSimpleType(symbol.extends, "base class of " + symbol.name);
}
if ( symbol.implements ) {
symbol.implements.forEach(function(intf) {
- check(intf, "interface of " + symbol.name);
+ checkSimpleType(intf, "interface of " + symbol.name);
var oIntfAPI = defined[intf];
if ( oIntfAPI ) {
checkClassAgainstInterface(symbol, oIntfAPI);
}
});
}
+ if ( Object.prototype.hasOwnProperty.call(symbol, "constructor") ) {
+ checkMethodSignature(symbol.constructor, symbol.name + ".constructor");
+ }
if ( symbol.properties ) {
symbol.properties.forEach(function(prop) {
+ checkName(prop.name, "name of " + symbol.name + "." + prop.name);
if ( prop.type ) {
- check(prop.type, "type of " + symbol.name + "." + prop.name);
+ checkType(prop, "type of " + symbol.name + "." + prop.name);
}
});
}
if ( symbol.methods ) {
symbol.methods.forEach(function(method) {
- checkMethod(method, symbol.name + "." + method.name);
+ checkName(method.name, "name of " + symbol.name + "." + method.name);
+ checkMethodSignature(method, symbol.name + "." + method.name);
});
}
if ( symbol.events ) {
symbol.events.forEach(function(event) {
+ checkName(event.name, "name of " + symbol.name + "." + event.name);
if ( event.parameters ) {
event.parameters.forEach(function(param) {
checkParam(param, '', symbol.name + "." + event.name);
@@ -3294,20 +3538,38 @@ function validateAPIJSON(api) {
api.symbols.forEach(function(symbol) {
if ( symbol.kind === 'function' ) {
- checkMethod(symbol, symbol.name);
+ checkCompoundName(symbol.name, "name of " + symbol.name);
+ checkMethodSignature(symbol, symbol.name);
+ } else if ( symbol.kind === 'enum' ) {
+ checkEnum(symbol);
} else {
checkClass(symbol);
}
});
- for (var type in missing) {
- if ( Array.isArray(missing[type]) ) {
- error(type);
- missing[type].forEach(function(usage) {
- error(" " + usage);
- });
- }
+ if ( Object.keys(missing).length > 0 || Object.keys(naming).length > 0 ) {
+ error("API validation errors:");
+
+ Object.keys(missing).forEach(function(type) {
+ if ( Array.isArray(missing[type]) ) {
+ error("type '" + type + "'");
+ missing[type].forEach(function(usage) {
+ error(" " + usage);
+ });
+ }
+ });
+ Object.keys(naming).forEach(function(name) {
+ if ( Array.isArray(naming[name]) ) {
+ error("invalid name '" + name + "'");
+ naming[name].forEach(function(usage) {
+ error(" " + usage);
+ });
+ }
+ });
+ } else {
+ info("API validation succeeded.");
}
+
}
//---- add on: API XML -----------------------------------------------------------------
@@ -4353,3 +4615,4 @@ function makeExample(example) {
/* ---- exports ---- */
exports.publish = publish;
+