From 98dbe0f5eed6a4ae0ff7218f6458e4e34359e476 Mon Sep 17 00:00:00 2001 From: Frank Weigel Date: Tue, 26 Jun 2018 22:08:49 +0200 Subject: [PATCH 01/38] [WIP] Task to create JSDoc for a library, using the UI5 template/plugin Note: the code is full of console.log just to track down an unexpected exit of the Node.js process. --- lib/processors/jsdoc/create-api-index.js | 270 ++ lib/processors/jsdoc/dummy-child.js | 5 + .../jsdoc/jsdoc-config-template.json | 23 + lib/processors/jsdoc/jsdoc.js | 128 + .../jsdoc/transform-apijson-for-sdk.js | 1732 +++++++ lib/processors/jsdoc/ui5/plugin.js | 2321 ++++++++++ lib/processors/jsdoc/ui5/template/publish.js | 4056 +++++++++++++++++ lib/tasks/createJSDoc.js | 29 + lib/tasks/taskRepository.js | 1 + lib/types/library/LibraryBuilder.js | 18 + package-lock.json | 68 +- package.json | 4 +- 12 files changed, 8612 insertions(+), 43 deletions(-) create mode 100644 lib/processors/jsdoc/create-api-index.js create mode 100644 lib/processors/jsdoc/dummy-child.js create mode 100644 lib/processors/jsdoc/jsdoc-config-template.json create mode 100644 lib/processors/jsdoc/jsdoc.js create mode 100644 lib/processors/jsdoc/transform-apijson-for-sdk.js create mode 100644 lib/processors/jsdoc/ui5/plugin.js create mode 100644 lib/processors/jsdoc/ui5/template/publish.js create mode 100644 lib/tasks/createJSDoc.js diff --git a/lib/processors/jsdoc/create-api-index.js b/lib/processors/jsdoc/create-api-index.js new file mode 100644 index 000000000..b68357dde --- /dev/null +++ b/lib/processors/jsdoc/create-api-index.js @@ -0,0 +1,270 @@ +/* + * Node script to create cross-library API index files for use in the UI5 SDKs. + * + * (c) Copyright 2009-2018 SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ + +"use strict"; +const fs = require("fs"); +const path = require("path"); + +function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetFileDeprecated, targetFileExperimental, targetFileSince) { + + console.log("[INFO] creating API index files"); + console.log("[INFO] sap-ui-version.json: " + versionInfoFile); + console.log("[INFO] unpacked test-resources: " + unpackedTestresourcesRoot); + console.log("[INFO] target file: " + targetFile); + console.log("[INFO] target file deprecated: " + targetFileDeprecated); + console.log("[INFO] target file experimental: " + targetFileExperimental); + console.log("[INFO] target file since: " + targetFileSince); + console.log("[INFO]"); + + // Deprecated, Experimental and Since collections + let oListCollection = { + deprecated: { + noVersion: { + apis: [] + } + }, + experimental: { + noVersion: { + apis: [] + } + }, + since: { + noVersion: { + apis: [] + } + } + }; + + function readJSONFile(file) { + return new Promise(function (resolve, reject) { + fs.readFile(file, 'utf8', function (err, data) { + if (err) { + reject(err); + } else { + // Handle empty files scenario + if (data.trim() === "") { + resolve({}); + } else { + resolve(JSON.parse(String(data))); + } + } + }); + }); + } + + function mkdirSync(dir) { + if (dir && !fs.existsSync(dir)) { + mkdirSync( path.dirname(dir) ); + fs.mkdirSync(dir); + } + } + + function writeJSON(file, content) { + return new Promise(function(resolve,reject) { + // Create dir if it does not exist + mkdirSync( path.dirname(file) ); + fs.writeFile(file, JSON.stringify(content), "utf-8", function(err) { + if ( err ) { + reject(err); + return; + } + resolve(true); + }); + }); + } + + /* + * Extracts main symbol information from a library api.json. + * Also collects deprecated, experimental and since api's. + * Returns a promise that resolves with an array of symbols. + */ + function createSymbolSummaryForLib(lib) { + let file = path.join(unpackedTestresourcesRoot, lib.replace(/\./g, "/"), "designtime/api.json"); + + return readJSONFile(file).then(function (apijson) { + if (!apijson.hasOwnProperty("symbols") || !Array.isArray(apijson.symbols)) { + // Ignore libraries with invalid api.json content like empty object or non-array "symbols" property. + return []; + } + return apijson.symbols.map(symbol => { + collectLists(symbol); + return { + name: symbol.name, + kind: symbol.kind, + visibility: symbol.visibility, + extends: symbol.extends, + implements: symbol.implements, + lib: lib + }; + }); + }) + } + + /* + * Collects Deprecated, Experimental and Since data from passed symbol + * including symbol itself, methods and events. + */ + function collectLists(oSymbol) { + + function addData(oDataType, oEntityObject, sObjectType, sSymbolName) { + let sSince = oDataType !== "since" ? oEntityObject[oDataType].since : oEntityObject.since, + oData = { + control: sSymbolName, + text: oEntityObject[oDataType].text || oEntityObject.description, + type: sObjectType, + "static": !!oEntityObject.static, + visibility: oEntityObject.visibility + }; + + // For class we skip entityName + if (sObjectType !== "class") { + oData.entityName = oEntityObject.name; + } + + if (sSince) { + // take only major and minor versions + let sVersion = sSince.split(".").slice(0, 2).join("."); + + oData.since = sSince; + + if (!oListCollection[oDataType][sVersion]) { + oListCollection[oDataType][sVersion] = { + name: sVersion, + apis: [] + }; + } + + oListCollection[oDataType][sVersion].apis.push(oData); + } else { + oListCollection[oDataType].noVersion.apis.push(oData); + } + } + + // Classes + if (oSymbol.deprecated) { + addData("deprecated", oSymbol, "class", oSymbol.name); + } + + if (oSymbol.experimental) { + addData("experimental", oSymbol, "class", oSymbol.name); + } + + if (oSymbol.since) { + addData("since", oSymbol, "class", oSymbol.name); + } + + // Methods + oSymbol.methods && oSymbol.methods.forEach(oMethod => { + if (oMethod.deprecated) { + addData("deprecated", oMethod, "methods", oSymbol.name); + } + + if (oMethod.experimental) { + addData("experimental", oMethod, "methods", oSymbol.name); + } + + if (oMethod.since) { + addData("since", oMethod, "methods", oSymbol.name); + } + }); + + // Events + oSymbol.events && oSymbol.events.forEach(oEvent => { + if (oEvent.deprecated) { + addData("deprecated", oEvent, "events", oSymbol.name); + } + + if (oEvent.experimental) { + addData("experimental", oEvent, "events", oSymbol.name); + } + + if (oEvent.since) { + addData("since", oEvent, "events", oSymbol.name); + } + }); + + } + + function deepMerge(arrayOfArrays) { + return arrayOfArrays.reduce((array, items) => { + array.push.apply(array, items); + return array; + }, []); + } + + function expandHierarchyInfo(symbols) { + let byName = new Map(); + symbols.forEach(symbol => { + byName.set(symbol.name, symbol); + }); + symbols.forEach(symbol => { + let parent = symbol.extends && byName.get(symbol.extends); + if (parent) { + parent.extendedBy = parent.extendedBy ||  []; + parent.extendedBy.push(symbol.name); + } + if (symbol.implements) { + symbol.implements.forEach(intfName => { + let intf = byName.get(intfName); + if (intf) { + intf.implementedBy = intf.implementedBy ||  []; + intf.implementedBy.push(symbol.name); + } + }); + } + }); + return symbols; + } + + function createOverallIndex() { + let version = "0.0.0"; + + var p = readJSONFile(versionInfoFile) + .then(versionInfo => { + version = versionInfo.version; + return Promise.all( + versionInfo.libraries.map( + lib => createSymbolSummaryForLib(lib.name).catch(err => { + // ignore 'file not found' errors as some libs don't have an api.json (themes, server libs) + if (err.code === 'ENOENT') { + return []; + } + throw err; + }) + ) + ); + }) + .then(deepMerge) + .then(expandHierarchyInfo) + .then(symbols => { + let result = { + "$schema-ref": "http://schemas.sap.com/sapui5/designtime/api.json/1.0", + version: version, + library: "*", + symbols: symbols + }; + return writeJSON(targetFile, result); + }) + .then(() => Promise.all([ + // write deprecated, experimental and since collections in the respective index files + writeJSON(targetFileDeprecated, oListCollection.deprecated), + writeJSON(targetFileExperimental, oListCollection.experimental), + writeJSON(targetFileSince, oListCollection.since) + ])) + .catch(err => { + console.error("**** failed to create API index for libraries:", err) + throw err; + }); + + return p; + } + + return createOverallIndex(); + +} + +module.exports = process; diff --git a/lib/processors/jsdoc/dummy-child.js b/lib/processors/jsdoc/dummy-child.js new file mode 100644 index 000000000..64a4a6727 --- /dev/null +++ b/lib/processors/jsdoc/dummy-child.js @@ -0,0 +1,5 @@ +console.log('child executing'); +setTimeout(function() { + console.log("child done"); + //process.exit(0); +},20000); diff --git a/lib/processors/jsdoc/jsdoc-config-template.json b/lib/processors/jsdoc/jsdoc-config-template.json new file mode 100644 index 000000000..52569ef4c --- /dev/null +++ b/lib/processors/jsdoc/jsdoc-config-template.json @@ -0,0 +1,23 @@ +{ + "source": { + "excludePattern": "(/|\\\\)library-all\\.js|(/|\\\\).*-preload\\.js|^jquery-.*\\.js|^sap-.*\\.js" + }, + "opts" : { + "recurse": true, + "template" : "lib/jsdoc/ui5/template" + }, + "plugins": [ + "lib/jsdoc/ui5/plugin.js" + ], + "templates" : { + "ui5" : { + "variants": [ + "apijson" + ], + "version": "${version}", + "apiJsonFolder": "${apiJsonFolder}", + "apiJsonFile": "${apiJsonFile}", + "includeSettingsInConstructor": false + } + } +} diff --git a/lib/processors/jsdoc/jsdoc.js b/lib/processors/jsdoc/jsdoc.js new file mode 100644 index 000000000..8cc13afd7 --- /dev/null +++ b/lib/processors/jsdoc/jsdoc.js @@ -0,0 +1,128 @@ +const spawn = require('cross-spawn').spawn; +const fs = require('fs'); +const path = require('path'); +const tmp = require('tmp'); +const resourceFactory = require("@ui5/fs").resourceFactory; + +function createJSDocConfig({source, target, namespace, libraryName, version}) { + // resolve path to the package.json to get the path to the jsdocext folder + const jsdocext = path.normalize(__dirname); + + const config = `{ + "plugins": ["${jsdocext}/ui5/plugin.js"], + "opts": { + "recurse": true, + "lenient": true, + "template": "${jsdocext}/ui5/template", + "ui5": { + "saveSymbols": true + } + }, + "templates": { + "ui5": { + "variants": [ "apijson", "fullapixml", "apijs", "api.xml"], + "version": "${version}", + "jsapiFile": "${target}/libraries/${libraryName}.js", + "apiJsonFolder": "${target}/dependency-apis", + "apiJsonFile": "${target}/test-resources/${namespace}/designtime/api.json" + } + } + }`; + console.log(config); + return config; +} + +function jsdoc({sources, target, namespace, libraryName, version}) { + + const tmpobj = tmp.fileSync(); + fs.writeFileSync(tmpobj.name, createJSDocConfig({target, namespace, libraryName, version}), 'utf8'); // TODO async + promise + + console.log("jsdoc called for ", sources); + var args = [ + require.resolve("jsdoc/jsdoc"), + '-c', + tmpobj.name, + '--verbose' + ]; + args = args.concat(sources); + + return new Promise((resolve, reject) => { + const child = spawn('node', args); + child.stdout.on('data', function(data) { + console.log(String(data)); + }); + child.stderr.on('data', function(data) { + console.error(String(data)); + }); + child.on('exit', function(code) { + var resolvedDest; + console.log("jsdoc exited with code ", code); + if (code === 0 || code === 1) { + resolve(code); + } else { + reject(code) + } + }); + }); +} + +/** + * Creates *-dbg.js files for all JavaScript-resources supplied and writes them to target locator. + * + * @module build/processors/dbg + * + * @param {Object} parameters Parameters + * @param {Array} parameters.resources List of resources to be processed + * @param {ResourceLocatorCollection} parameters.sourceLocator Source locator + * @param {ResourceLocator} parameters.targetLocator Target locator + * @param {Object} [parameters.config] Configuration + * @return {Promise} Promise resolving with undefined once data has been written to the target locator + */ +module.exports = function({resources, options}) { + if ( !options.libraryName ) { + throw new TypeError("Cannot execute JSDoc build without a library name"); + } + const namespace = options.libraryName.replace(/\./g, "/"); + const tmpDirObj = tmp.dirSync(); + const tmpSourceDir = path.join(tmpDirObj.name, 'src'); + const tmpTargetDir = path.join(tmpDirObj.name, 'target'); + + const fsSources = resourceFactory.createAdapter({ + fsBasePath: tmpSourceDir, + virBasePath: "/resources/" + }); + const fsTarget = resourceFactory.createAdapter({ + fsBasePath: tmpTargetDir, + virBasePath: "/" + }); + + //return Promise.resolve([]); + + return Promise.all( + // write all resources to the tmp folder + resources.map((resource) => fsSources.write(resource)) + // after this step, a follow-up step aborts silenty for an unknown reasons + // cloning the resources before writing them avoids the problem: + // resources.map((resource) => resource.clone().then((resource) => fsSources.write(resource))) + ).then(() => [], (err) => { + console.log(err); + return []; + }).then((files) => { + return jsdoc({ + sources: [tmpSourceDir], + target: tmpTargetDir, + namespace, + libraryName: options.libraryName, + version: options.version + }); + }).then(() => { + // create resources from the output files + return Promise.all([ + fsTarget.byPath(`/test-resources/${namespace}/designtime/api.json`) + //,fsTarget.byPath(`/libraries/${options.libraryName}.js`) + ]).then((res) => res.filter($=>$)); + }).then((result) => { + // TODO cleanup tmp dir + return result; + }); +}; diff --git a/lib/processors/jsdoc/transform-apijson-for-sdk.js b/lib/processors/jsdoc/transform-apijson-for-sdk.js new file mode 100644 index 000000000..f4f3a8544 --- /dev/null +++ b/lib/processors/jsdoc/transform-apijson-for-sdk.js @@ -0,0 +1,1732 @@ +/* + * Node script to preprocess api.json files for use in the UI5 SDKs. + * + * (c) Copyright 2009-2018 SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ + +"use strict"; +const fs = require("fs"); +const cheerio = require("cheerio"); +const path = require('path'); + +module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { + + console.log("[INFO] Transform API index files for sap.ui.documentation"); + console.log("[INFO] original file: " + sInputFile); + console.log("[INFO] output file: " + sOutputFile); + console.log("[INFO]"); + + /** + * Transforms api.json file + * @param {object} oChainObject chain object + */ + let transformApiJson = function (oChainObject) { + function isBuiltInType(type) { + return formatters._baseTypes.indexOf(type) >= 0; + } + + /** + * Heuristically determining if there is a possibility the given input string + * to be a UI5 symbol + * @param {string} sName + * @returns {boolean} + */ + function possibleUI5Symbol(sName) { + return /^[a-zA-Z][a-zA-Z.]*[a-zA-Z]$/.test(sName); + } + + // Function is a copy from: LibraryInfo.js => LibraryInfo.prototype._getActualComponent => "match" inline method + function matchComponent(sModuleName, sPattern) { + sModuleName = sModuleName.toLowerCase(); + sPattern = sPattern.toLowerCase(); + return ( + sModuleName === sPattern + || sPattern.match(/\*$/) && sModuleName.indexOf(sPattern.slice(0,-1)) === 0 // simple prefix match + || sPattern.match(/\.\*$/) && sModuleName === sPattern.slice(0,-2) // directory pattern also matches directory itself + ); + } + + // Transform to object + let oData = JSON.parse(oChainObject.fileData); + + // Attach default component for the library if available + if (oChainObject.defaultComponent) { + oData.defaultComponent = oChainObject.defaultComponent; + } + + // Populate methods.aTreeContent for later use for symbol children if applicable + // NOTE: This will inject missing root sub_namespace entries in oData.symbols array!!! + methods._parseLibraryElements(oData.symbols); + + // Apply formatter's and modify data as needed + oData.symbols.forEach((oSymbol) => { + + // when the module name starts with the library name, then we apply the default component + if (oSymbol.name.indexOf(oData.library) === 0) { + oSymbol.component = oChainObject.defaultComponent; + } + + // Attach symbol specific component if available (special cases) + // Note: Last hit wins as there may be a more specific component pattern + if (oChainObject.customSymbolComponents) { + Object.keys(oChainObject.customSymbolComponents).forEach(sComponent => { + if (matchComponent(oSymbol.name, sComponent)) { + oSymbol.component = oChainObject.customSymbolComponents[sComponent]; + } + }); + } + + // Attach symbol sample flag if available + if (oChainObject.entitiesWithSamples) { + oSymbol.hasSample = oChainObject.entitiesWithSamples.indexOf(oSymbol.name) >= 0; + } + + // Apply settings to formatter object - needed until formatter's are rewritten + formatters._sTopicId = oSymbol.name; + formatters._oTopicData = oSymbol; + + // Format Page Title + oSymbol.title = (oSymbol.abstract ? "abstract " : "") + oSymbol.kind + " " + oSymbol.name; + oSymbol.subTitle = formatters.formatSubtitle(oSymbol.deprecated); + + // Symbol children + let aControlChildren = methods._getControlChildren(oSymbol.name); + if (aControlChildren) { + oSymbol.nodes = aControlChildren; + methods._addChildrenDescription(oData.symbols, oSymbol.nodes); + } + + // Constructor + if (oSymbol.constructor) { + let oConstructor = oSymbol.constructor; + + // Description + if (oConstructor.description) { + oConstructor.description = formatters.formatDescription(oConstructor.description); + } + + // References + methods.modifyReferences(oSymbol); + + // Examples + if (oConstructor.examples) { + oConstructor.examples.forEach((oExample) => { + oExample.data = formatters.formatExample(oExample.caption, oExample.text); + + // Keep file size in check + if (oExample.caption) { + delete oExample.caption; + } + if (oExample.text) { + delete oExample.text; + } + }); + } + + // Code Example string + oConstructor.codeExample = formatters.formatConstructor(oSymbol.name, oConstructor.parameters); + + // Parameters + if (oConstructor.parameters) { + oConstructor.parameters = methods.buildConstructorParameters(oConstructor.parameters); + + let aParameters = oConstructor.parameters; + aParameters.forEach(oParameter => { + + // Types + oParameter.types = []; + if (oParameter.type) { + let aTypes = oParameter.type.split("|"); + + for (let i = 0; i < aTypes.length; i++) { + oParameter.types.push({ + name: aTypes[i], + linkEnabled: !isBuiltInType(aTypes[i]) + }); + } + + // Keep file size in check + delete oParameter.type; + } + + // Default value + oParameter.defaultValue = formatters.formatDefaultValue(oParameter.defaultValue); + + // Description + if (oParameter.description) { + oParameter.description = formatters.formatDescription(oParameter.description); + } + + }) + } + + // Throws + if (oConstructor.throws) { + oConstructor.throws.forEach(oThrows => { + + // Description + if (oThrows.description) { + oThrows.description = formatters.formatDescription(oThrows.description); + } + + // Exception link enabled + if (oThrows.type) { + oThrows.linkEnabled = formatters.formatExceptionLink(oThrows.type); + } + + }); + } + } + + // Description + if (oSymbol.description) { + oSymbol.description = formatters.formatOverviewDescription(oSymbol.description, oSymbol.constructor.references); + } + + // Deprecated + if (oSymbol.deprecated) { + oSymbol.deprecatedText = formatters.formatDeprecated(oSymbol.deprecated.since, oSymbol.deprecated.text); + // Keep file size in check + delete oSymbol.deprecated; + } + + // Properties + if (oSymbol.properties) { + oSymbol.properties.forEach((oProperty) => { + + // Name + oProperty.name = formatters.formatEntityName(oProperty.name, oSymbol.name, oProperty.static); + + // Description + if (oProperty.deprecated) { + oProperty.description = formatters.formatDescription(oProperty.description, + oProperty.deprecated.text, oProperty.deprecated.since); + } else { + oProperty.description = formatters.formatDescription(oProperty.description); + } + + // Link Enabled + if (!isBuiltInType(oProperty.type)) { + oProperty.linkEnabled = true; + } + + // Keep file size in check + if (oProperty.static) { + delete oProperty.static; + } + if (oProperty.type) { + delete oProperty.type; + } + + }); + } + + // UI5 Metadata + if (oSymbol["ui5-metadata"]) { + let oMeta = oSymbol["ui5-metadata"]; + + // Properties + if (oMeta.properties) { + // Sort + oMeta.properties.sort(function (a, b) { + if (a.name < b.name) { + return -1; + } else if (a.name > b.name) { + return 1; + } else { + return 0; + } + }); + + // Pre-process + oMeta.properties.forEach((oProperty) => { + // Name + oProperty.name = formatters.formatEntityName(oProperty.name, oSymbol.name, oProperty.static); + + // Description + oProperty.description = formatters.formatDescriptionSince(oProperty.description, oProperty.since); + + // Link Enabled + if (!isBuiltInType(oProperty.type)) { + oProperty.linkEnabled = true; + } + + // Default value + oProperty.defaultValue = formatters.formatDefaultValue(oProperty.defaultValue); + + // Deprecated + if (oProperty.deprecated) { + oProperty.deprecatedText = formatters.formatDeprecated(oProperty.deprecated.since, + oProperty.deprecated.text); + + // Keep file size in check + delete oProperty.deprecated; + } + }); + } + + // Aggregations + if (oMeta.aggregations) { + // Sort + oMeta.aggregations.sort(function (a, b) { + if (a.name < b.name) { + return -1; + } else if (a.name > b.name) { + return 1; + } else { + return 0; + } + }); + + // Pre-process + oMeta.aggregations.forEach((oAggregation) => { + // Link Enabled + if (!isBuiltInType(oAggregation.type)) { + oAggregation.linkEnabled = true; + } + + // Description + if (oAggregation.deprecated) { + oAggregation.description = formatters.formatDescription(oAggregation.description, + oAggregation.deprecated.text, oAggregation.deprecated.since); + } else { + oAggregation.description = formatters.formatDescription(oAggregation.description); + } + + // Link enabled + oAggregation.linkEnabled = !isBuiltInType(oAggregation.type); + }); + } + + // Associations + + if (oMeta.associations) { + // Sort + oMeta.associations.sort(function (a, b) { + if (a.name < b.name) { + return -1; + } else if (a.name > b.name) { + return 1; + } else { + return 0; + } + }); + + // Pre-process + oMeta.associations.forEach((oAssociation) => { + // Link Enabled + if (!isBuiltInType(oAssociation.type)) { + oAssociation.linkEnabled = true; + } + + // Description + if (oAssociation.deprecated) { + oAssociation.description = formatters.formatDescription(oAssociation.description, + oAssociation.deprecated.text, oAssociation.deprecated.since); + } else { + oAssociation.description = formatters.formatDescription(oAssociation.description); + } + }); + } + + // Events + if (oMeta.events) { + // We don't need event's data from the UI5-metadata for now. Keep file size in check + delete oMeta.events; + } + + // Special Settings + if (oMeta.specialSettings) { + oMeta.specialSettings.forEach(oSetting => { + + // Link Enabled + if (!isBuiltInType(oSetting.type)) { + oSetting.linkEnabled = true; + } + + // Description + if (oSetting.deprecated) { + oSetting.description = formatters.formatDescription(oSetting.description, + oSetting.deprecated.text, oSetting.deprecated.since); + } else { + oSetting.description = formatters.formatDescription(oSetting.description); + } + + }); + } + + // Annotations + if (oMeta.annotations) { + oMeta.annotations.forEach(oAnnotation => { + + // Description + oAnnotation.description = formatters.formatAnnotationDescription(oAnnotation.description, + oAnnotation.since); + + // Namespace + oAnnotation.namespaceText = oAnnotation.namespace; + oAnnotation.namespace = formatters.formatAnnotationNamespace(oAnnotation.namespace); + + // Target + oAnnotation.target = formatters.formatAnnotationTarget(oAnnotation.target); + + // Applies to + oAnnotation.appliesTo = formatters.formatAnnotationTarget(oAnnotation.appliesTo); + + }); + } + + } + + if (oSymbol.events) { + + // Pre-process events + methods.buildEventsModel(oSymbol.events); + + oSymbol.events.forEach(oEvent => { + + // Description + if (oEvent.description) { + oEvent.description = formatters.formatDescriptionSince(oEvent.description, oEvent.since); + } + + // Deprecated + if (oEvent.deprecated) { + oEvent.deprecatedText = formatters.formatEventDeprecated(oEvent.deprecated.since, + oEvent.deprecated.text); + } + + // Parameters + if (oEvent.parameters && Array.isArray(oEvent.parameters)) { + oEvent.parameters.forEach(oParameter => { + + // Link Enabled + if (!isBuiltInType(oParameter.type)) { + oParameter.linkEnabled = true; + } + + // Description + if (oParameter.deprecated) { + oParameter.description = formatters.formatDescription(oParameter.description, + oParameter.deprecated.text, oParameter.deprecated.since); + } else { + oParameter.description = formatters.formatDescription(oParameter.description); + } + + }); + } + + }); + + } + + // Methods + if (oSymbol.methods) { + + // Pre-process methods + methods.buildMethodsModel(oSymbol.methods); + + oSymbol.methods.forEach(oMethod => { + + // Name + if (oMethod.name) { + oMethod.name = formatters.formatEntityName(oMethod.name, oSymbol.name, oMethod.static); + } + + // Description + if (oMethod.description) { + oMethod.description = formatters.formatDescription(oMethod.description); + } + + // Examples + oMethod.examples && oMethod.examples.forEach(oExample => { + oExample = formatters.formatExample(oExample.caption, oExample.text); + }); + + // Deprecated + if (oMethod.deprecated) { + oMethod.deprecatedText = formatters.formatEventDeprecated(oMethod.deprecated.since, + oMethod.deprecated.text); + } + + // Code example + oMethod.code = formatters.formatMethodCode(oMethod.name, oMethod.parameters, oMethod.returnValue); + + // Parameters + if (oMethod.parameters) { + oMethod.parameters.forEach(oParameter => { + + // Types + if (oParameter.types) { + oParameter.types.forEach(oType => { + + // Link Enabled + if (!isBuiltInType(oType.value) && possibleUI5Symbol(oType.value)) { + oType.linkEnabled = true; + oType.href = "#/api/" + oType.value.replace("[]", ""); + } + + }); + } + + // Default value + oParameter.defaultValue = formatters.formatDefaultValue(oParameter.defaultValue); + + // Description + if (oParameter.deprecated) { + oParameter.description = formatters.formatDescription(oParameter.description, + oParameter.deprecated.text, oParameter.deprecated.since); + } else { + oParameter.description = formatters.formatDescription(oParameter.description); + } + + }); + } + + // Return value + if (oMethod.returnValue) { + + // Description + oMethod.returnValue.description = formatters.formatDescription(oMethod.returnValue.description); + + // Types + if (oMethod.returnValue.types) { + oMethod.returnValue.types.forEach(oType => { + + // Link Enabled + if (!isBuiltInType(oType.value)) { + oType.linkEnabled = true; + } + + }); + } + + } + + // Throws + if (oMethod.throws) { + oMethod.throws.forEach(oThrows => { + + // Description + if (oThrows.description) { + oThrows.description = formatters.formatDescription(oThrows.description); + } + + // Exception link enabled + if (oThrows.type) { + oThrows.linkEnabled = formatters.formatExceptionLink(oThrows.type); + } + + }); + } + + // Examples + if (oMethod.examples) { + oMethod.examples.forEach((oExample) => { + oExample.data = formatters.formatExample(oExample.caption, oExample.text); + + // Keep file size in check + if (oExample.caption) { + delete oExample.caption; + } + if (oExample.text) { + delete oExample.text; + } + }); + + } + + + }); + } + + }); + + oChainObject.parsedData = oData; + + return oChainObject; + }; + + /** + * Create api.json from parsed data + * @param oChainObject chain object + */ + function createApiRefApiJson(oChainObject) { + let sOutputDir = path.dirname(oChainObject.outputFile); + + // Create dir if it does not exist + if (!fs.existsSync(sOutputDir)) { + fs.mkdirSync(sOutputDir); + } + + // Write result to file + fs.writeFileSync(oChainObject.outputFile, JSON.stringify(oChainObject.parsedData) /* Transform back to string */, 'utf8'); + } + + /** + * Load .library file + * @param oChainObject chain return object + * @returns {Promise} library file promise + */ + function getLibraryPromise(oChainObject) { + return new Promise(function(oResolve) { + fs.readFile(oChainObject.libraryFile, 'utf8', (oError, oData) => { + oChainObject.libraryFileData = oData; + oResolve(oChainObject); + }); + }); + } + + /** + * Extracts components list and docuindex.json relative path from .library file data + * @param {object} oChainObject chain object + * @returns {object} chain object + */ + function extractComponentAndDocuindexUrl(oChainObject) { + oChainObject.modules = []; + + if (oChainObject.libraryFileData) { + let $ = cheerio.load(oChainObject.libraryFileData, { + ignoreWhitespace: true, + xmlMode: true, + lowerCaseTags: false + }); + + // Extract documentation URL + oChainObject.docuPath = $("appData documentation").attr("indexUrl"); + + // Extract components + $("ownership > component").each((i, oComponent) => { + + if (oComponent.children) { + if (oComponent.children.length === 1) { + oChainObject.defaultComponent = $(oComponent).text(); + } else { + let sCurrentComponentName = $(oComponent).find("name").text(); + let aCurrentModules = []; + $(oComponent).find("module").each((a, oC) => { + aCurrentModules.push($(oC).text().replace(/\//g, ".")); + }); + + oChainObject.modules.push({ + componentName: sCurrentComponentName, + modules: aCurrentModules + }); + } + } + + }); + + } + + return oChainObject; + } + + /** + * Adds to the passed object custom symbol component map generated from the extracted components list + * to be easily searchable later + * @param {object} oChainObject chain object + * @returns {object} chain object + */ + function flattenComponents(oChainObject) { + if (oChainObject.modules && oChainObject.modules.length > 0) { + oChainObject.customSymbolComponents = {}; + oChainObject.modules.forEach(oComponent => { + let sCurrentComponent = oComponent.componentName; + oComponent.modules.forEach(sModule => { + oChainObject.customSymbolComponents[sModule] = sCurrentComponent; + }); + }); + } + + return oChainObject; + } + + /** + * Adds to the passed object array with entities which have explored samples + * @param {object} oChainObject chain object + * @returns {object} chain object + */ + function extractSamplesFromDocuIndex(oChainObject) { + // If we have not extracted docuPath we return early + if (!oChainObject.docuPath) { + return oChainObject; + } + return new Promise(function(oResolve) { + // Join .library path with relative docuindex.json path + let sPath = path.join(path.dirname(oChainObject.libraryFile), oChainObject.docuPath); + // Normalize path to resolve relative path + sPath = path.normalize(sPath); + + fs.readFile(sPath, 'utf8', (oError, oFileData) => { + if (!oError) { + oFileData = JSON.parse(oFileData); + if (oFileData.explored && oFileData.explored.entities && oFileData.explored.entities.length > 0) { + oChainObject.entitiesWithSamples = []; + oFileData.explored.entities.forEach(oEntity => { + oChainObject.entitiesWithSamples.push(oEntity.id); + }); + } + } + // We aways resolve as this data is not mandatory + oResolve(oChainObject); + }); + + }); + } + + /** + * Load api.json file + * @param {object} oChainObject chain object + * @returns {object} chain object + */ + function getAPIJSONPromise(oChainObject) { + return new Promise(function(oResolve, oReject) { + fs.readFile(sInputFile, 'utf8', (oError, sFileData) => { + if (oError) { + oReject(oError); + } else { + oChainObject.fileData = sFileData; + oResolve(oChainObject); + } + }); + }); + } + + /* + * ===================================================================================================================== + * IMPORTANT NOTE: Formatter code is a copy from APIDetail.controller.js with a very little modification and mocking and + * code can be significantly improved + * ===================================================================================================================== + */ + let formatters = { + + _sTopicId: "", + _oTopicData: {}, + _baseTypes: [ + "sap.ui.core.any", + "sap.ui.core.object", + "sap.ui.core.function", + "sap.ui.core.number", // TODO discuss with Thomas, type does not exist + "sap.ui.core.float", + "sap.ui.core.int", + "sap.ui.core.boolean", + "sap.ui.core.string", + "sap.ui.core.void", + "null", + "any", + "any[]", + "Error", + "Error[]", + "array", + "element", + "Element", + "DomRef", + "object", + "Object", + "object[]", + "object|object[]", + "[object Object][]", + "Array.<[object Object]>", + "Object.", + "function", + "float", + "int", + "boolean", + "string", + "string[]", + "number", + "map", + "promise", + "Promise", + "document", + "Document", + "Touch", + "TouchList", + "undefined" + ], + ANNOTATIONS_LINK: 'http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part3-csdl.html', + ANNOTATIONS_NAMESPACE_LINK: 'http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/vocabularies/', + + /** + * Adds "deprecated" information if such exists to the header area + * @param deprecated - object containing information about deprecation + * @returns {string} - the deprecated text to display + */ + formatSubtitle: function (deprecated) { + var result = ""; + + if (deprecated) { + result += "Deprecated in version: " + deprecated.since; + } + + return result; + }, + + /** + * Formats the target and applies to texts of annotations + * @param target - the array of texts to be formatted + * @returns string - the formatted text + */ + formatAnnotationTarget: function (target) { + var result = ""; + + if (target) { + target.forEach(function (element) { + result += element + '
'; + }); + } + + result = this._preProcessLinksInTextBlock(result); + return result; + }, + + /** + * Formats the namespace of annotations + * @param namespace - the namespace to be formatted + * @returns string - the formatted text + */ + formatAnnotationNamespace: function (namespace) { + var result, + aNamespaceParts = namespace.split("."); + + if (aNamespaceParts[0] === "Org" && aNamespaceParts[1] === "OData") { + result = '' + namespace + ''; + } else { + result = namespace; + } + + result = this._preProcessLinksInTextBlock(result); + return result; + }, + + /** + * Formats the description of annotations + * @param description - the description of the annotation + * @param since - the since version information of the annotation + * @returns string - the formatted description + */ + formatAnnotationDescription: function (description, since) { + var result = description || ""; + + result += '
For more information, see ' + 'OData v4 Annotations'; + + if (since) { + result += '

Since: ' + since + '.'; + } + + result = this._preProcessLinksInTextBlock(result); + return result; + }, + + formatExceptionLink: function (linkText) { + linkText = linkText || ''; + return linkText.indexOf('sap.ui.') !== -1; + }, + + formatMethodCode: function (sName, aParams, aReturnValue) { + var result = '
' + sName + '(';
+
+			if (aParams && aParams.length > 0) {
+				/* We consider only root level parameters so we get rid of all that are not on the root level */
+				aParams = aParams.filter(oElem => {
+					return oElem.depth === undefined;
+				});
+				aParams.forEach(function (element, index, array) {
+					result += element.name;
+
+					if (element.optional) {
+						result += '?';
+					}
+
+					if (index < array.length - 1) {
+						result += ', ';
+					}
+				});
+			}
+
+			result += ') : ';
+
+			if (aReturnValue) {
+				result += aReturnValue.type;
+			} else {
+				result += 'void';
+			}
+
+			result += "
"; + + return result; + }, + + /** + * Formats method deprecation message and pre-process jsDoc link and code blocks + * @param {string} sSince since text + * @param {string} sDescription deprecation description text + * @returns {string} formatted deprecation message + */ + formatMethodDeprecated: function (sSince, sDescription) { + return this.formatDeprecated(sSince, sDescription, "methods"); + }, + + /** + * Formats event deprecation message and pre-process jsDoc link and code blocks + * @param {string} sSince since text + * @param {string} sDescription deprecation description text + * @returns {string} formatted deprecation message + */ + formatEventDeprecated: function (sSince, sDescription) { + return this.formatDeprecated(sSince, sDescription, "events"); + }, + + /** + * Formats the description of control properties + * @param description - the description of the property + * @param since - the since version information of the property + * @returns string - the formatted description + */ + formatDescriptionSince: function (description, since) { + var result = description || ""; + + if (since) { + result += '

Since: ' + since + '.'; + } + + result = this._preProcessLinksInTextBlock(result); + return result; + }, + + /** + * Formats the default value of the property as a string. + * @param defaultValue - the default value of the property + * @returns string - The default value of the property formatted as a string. + */ + formatDefaultValue: function (defaultValue) { + var sReturn; + + switch (defaultValue) { + case null: + case undefined: + sReturn = ''; + break; + case '': + sReturn = 'empty string'; + break; + default: + sReturn = defaultValue; + } + + return Array.isArray(sReturn) ? sReturn.join(', ') : sReturn; + }, + + /** + * Formats the constructor of the class + * @param name + * @param params + * @returns string - The code needed to create an object of that class + */ + formatConstructor: function (name, params) { + var result = '
new ';
+
+			if (name) {
+				result += name + '(';
+			}
+
+			if (params) {
+				params.forEach(function (element, index, array) {
+					result += element.name;
+
+					if (element.optional) {
+						result += '?';
+					}
+
+					if (index < array.length - 1) {
+						result += ', ';
+					}
+				});
+			}
+
+			if (name) {
+				result += ')
'; + } + + return result; + }, + + formatExample: function (sCaption, sText) { + return this.formatDescription( + ["Example: ", + sCaption, + "
",
+					sText,
+					"
"].join("") + ); + }, + + /** + * Formats the name of a property or a method depending on if it's static or not + * @param sName {string} - Name + * @param sClassName {string} - Name of the class + * @param bStatic {boolean} - If it's static + * @returns {string} - Formatted name + */ + formatEntityName: function (sName, sClassName, bStatic) { + return (bStatic === true) ? sClassName + "." + sName : sName; + }, + + JSDocUtil: function () { + + var rEscapeRegExp = /[[\]{}()*+?.\\^$|]/g; + + // Local mocked methods + var escapeRegExp = function escapeRegExp(sString) { + return sString.replace(rEscapeRegExp, "\\$&"); + }; + + function defaultLinkFormatter(target, text) { + return "" + (text || target) + ""; + } + + function format(src, options) { + + options = options || {}; + var beforeParagraph = options.beforeParagraph === undefined ? '

' : options.beforeParagraph; + var afterParagraph = options.afterParagraph === undefined ? '

' : options.afterParagraph; + var beforeFirstParagraph = options.beforeFirstParagraph === undefined ? beforeParagraph : options.beforeFirstParagraph; + var afterLastParagraph = options.afterLastParagraph === undefined ? afterParagraph : options.afterLastParagraph; + var linkFormatter = typeof options.linkFormatter === 'function' ? options.linkFormatter : defaultLinkFormatter; + + /* + * regexp to recognize important places in the text + * + * Capturing groups of the RegExp: + * group 1: begin of a pre block + * group 2: end of a pre block + * group 3: begin of a header, implicitly ends a paragraph + * group 4: end of a header, implicitly starts a new paragraph + * group 5: target portion of an inline @link tag + * group 6: (optional) text portion of an inline link tag + * group 7: an empty line which implicitly starts a new paragraph + * + * [--
 block -] [---- some header ----] [---- an inline [@link ...} tag ----] [---------- an empty line ---------]  */
+				var r = /(
)|(<\/pre>)|()|(<\/h[\d+]>)|\{@link\s+([^}\s]+)(?:\s+([^\}]*))?\}|((?:\r\n|\r|\n)[ \t]*(?:\r\n|\r|\n))/gi;
+				var inpre = false;
+
+				src = src || '';
+				linkFormatter = linkFormatter || defaultLinkFormatter;
+
+				src = beforeFirstParagraph + src.replace(r, function(match, pre, endpre, header, endheader, linkTarget, linkText, emptyline) {
+					if ( pre ) {
+						inpre = true;
+					} else if ( endpre ) {
+						inpre = false;
+					} else if ( header ) {
+						if ( !inpre ) {
+							return afterParagraph + match;
+						}
+					} else if ( endheader ) {
+						if ( !inpre ) {
+							return match + beforeParagraph;
+						}
+					} else if ( emptyline ) {
+						if ( !inpre ) {
+							return afterParagraph + beforeParagraph;
+						}
+					} else if ( linkTarget ) {
+						if ( !inpre ) {
+							return linkFormatter(linkTarget, linkText);
+						}
+					}
+					return match;
+				}) + afterLastParagraph;
+
+				// remove empty paragraphs
+				if (beforeParagraph !== "" && afterParagraph !== "") {
+					src = src.replace(new RegExp(escapeRegExp(beforeParagraph) + "\\s*" + escapeRegExp(afterParagraph), "g"), "");
+				}
+
+				return src;
+			}
+
+			return {
+				formatTextBlock: format
+			};
+
+		},
+
+		/**
+		 * Pre-process links in text block
+		 * @param {string} sText text block
+		 * @returns {string} processed text block
+		 * @private
+		 */
+		_preProcessLinksInTextBlock: function (sText, bSkipParagraphs) {
+			var topicsData = this._oTopicData, //this.getModel('topics').oData,
+				topicName = topicsData.name || "",
+				topicMethods = topicsData.methods || [],
+				oOptions = {
+					linkFormatter: function (target, text) {
+						var iHashIndex, // indexOf('#')
+							iHashDotIndex, // indexOf('#.')
+							iHashEventIndex, // indexOf('#event:')
+							aMatched,
+							sRoute = "api",
+							sTargetBase,
+							sScrollHandlerClass = "scrollToMethod",
+							sEntityName,
+							aMatch,
+							sLink;
+
+						text = text || target; // keep the full target in the fallback text
+
+						// If the link has a protocol, do not modify, but open in a new window
+						if (target.match("://")) {
+							return '' + text + '';
+						}
+
+						target = target.trim().replace(/\.prototype\./g, "#");
+
+						// Link matches the pattern of an static extend method sap.ui.core.Control.extend
+						// BCP: 1780477951
+						aMatch = target.match(/^([a-zA-Z0-9\.]*)\.extend$/);
+						if (aMatch) {
+							// In this case the link should be a link to a static method of the control like for example
+							// #/api/sap.ui.core.Control/methods/sap.ui.core.Control.extend
+							target = aMatch[1] + "/methods/" + aMatch[0];
+							sEntityName = aMatch[1];
+							sScrollHandlerClass = false; // No scroll handler needed
+						} else {
+
+							iHashIndex = target.indexOf('#');
+							iHashDotIndex = target.indexOf('#.');
+							iHashEventIndex = target.indexOf('#event:');
+
+							if (iHashIndex === -1) {
+								var lastDotIndex = target.lastIndexOf('.'),
+									entityName = sEntityName = target.substring(lastDotIndex + 1),
+									targetMethod = topicMethods.filter(function (method) {
+										if (method.name === entityName) {
+											return method;
+										}
+									})[0];
+
+								if (targetMethod) {
+									if (targetMethod.static === true) {
+										sEntityName = target;
+										// We need to handle links to static methods in a different way if static method is
+										// a child of the current or a different entity
+										sTargetBase = target.replace("." + entityName, "");
+										if (sTargetBase.length > 0 && sTargetBase !== topicName) {
+											// Different entity
+											target = sTargetBase + "/methods/" + target;
+											// We will navigate to a different entity so no scroll is needed
+											sScrollHandlerClass = false;
+										} else {
+											// Current entity
+											target = topicName + '/methods/' + target;
+										}
+									} else {
+										target = topicName + '/methods/' + entityName;
+									}
+								} else {
+									// Handle links to documentation
+									aMatched = target.match(/^topic:(\w{32})$/);
+									if (aMatched) {
+										target = sEntityName = aMatched[1];
+										sRoute = "topic";
+									}
+								}
+							}
+
+							if (iHashDotIndex === 0) {
+								// clear '#.' from target string
+								target = target.slice(2);
+
+								target = topicName + '/methods/' + topicName + '.' + target;
+							} else if (iHashEventIndex >= 0) {
+								//format is 'className#event:eventName'  or  '#event:eventName'
+								var sClassName = target.substring(0, iHashIndex);
+								target = target.substring(iHashIndex);
+
+								// clear '#event:' from target string
+								target = target.slice('#event:'.length);
+
+								if (!sClassName) {
+									sClassName = topicName; // if no className => event is relative to current topicName
+									sScrollHandlerClass = "scrollToEvent"; // mark the element as relative link to the events section
+								}
+
+								target = sClassName + '/events/' + target;
+								sEntityName = target;
+
+							} else if (iHashIndex === 0) {
+								// clear '#' from target string
+								target = target.slice(1);
+								sEntityName = target;
+
+								target = topicName + '/methods/' + target;
+							} else if (iHashIndex > 0) {
+								target = target.replace('#', '/methods/');
+								sEntityName = target;
+							}
+
+						}
+
+						sLink = '' + text + '';
+
+						return sLink;
+
+					}
+				};
+
+			if (bSkipParagraphs) {
+				oOptions.beforeParagraph = "";
+				oOptions.afterParagraph = "";
+			}
+
+			return this.JSDocUtil().formatTextBlock(sText, oOptions);
+		},
+
+		/**
+		 * Formatter for Overview section
+		 * @param {string} sDescription - Class about description
+		 * @param {array} aReferences - References
+		 * @returns {string} - formatted text block
+		 */
+		formatOverviewDescription: function (sDescription, aReferences) {
+			var iLen,
+				i;
+
+			// format references
+			if (aReferences && aReferences.length > 0) {
+				sDescription += "

Documentation links:
    "; + + iLen = aReferences.length; + for (i = 0; i < iLen; i++) { + // We treat references as links but as they may not be defined as such we enforce it if needed + if (/{@link.*}/.test(aReferences[i])) { + sDescription += "
  • " + aReferences[i] + "
  • "; + } else { + sDescription += "
  • {@link " + aReferences[i] + "}
  • "; + } + } + + sDescription += "
"; + } + + // Calling formatDescription so it could handle further formatting + return this.formatDescription(sDescription); + }, + + /** + * Formats the description of the property + * @param description - the description of the property + * @param deprecatedText - the text explaining this property is deprecated + * @param deprecatedSince - the version when this property was deprecated + * @returns string - the formatted description + */ + formatDescription: function (description, deprecatedText, deprecatedSince) { + if (!description && !deprecatedText && !deprecatedSince) { + return ""; + } + + var result = description || ""; + + if (deprecatedSince || deprecatedText) { + // Note: sapUiDocumentationDeprecated - transformed to sapUiDeprecated to keep json file size low + result += "
"; + + result += this.formatDeprecated(deprecatedSince, deprecatedText); + + result += "
"; + } + + result = this._preProcessLinksInTextBlock(result); + return result; + }, + + /** + * Formats the entity deprecation message and pre-process jsDoc link and code blocks + * @param {string} sSince since text + * @param {string} sDescription deprecation description text + * @param {string} sEntityType string representation of entity type + * @returns {string} formatted deprecation message + */ + formatDeprecated: function (sSince, sDescription, sEntityType) { + var aResult; + + // Build deprecation message + // Note: there may be no since or no description text available + aResult = ["Deprecated"]; + if (sSince) { + aResult.push(" as of version " + sSince); + } + if (sDescription) { + // Evaluate code blocks - Handle ... pattern + sDescription = sDescription.replace(/(\S+)<\/code>/gi, function (sMatch, sCodeEntity) { + return ['', sCodeEntity, ''].join(""); + } + ); + + // Evaluate links in the deprecation description + aResult.push(". " + this._preProcessLinksInTextBlock(sDescription, true)); + } + + return aResult.join(""); + }, + + _formatChildDescription: function (description) { + if (description) { + return this._extractFirstSentence(description); + } + }, + + /** Just the first sentence (up to a full stop). Should not break on dotted variable names. */ + _extractFirstSentence: function(desc) { + if ( desc ) { + desc = String(desc).replace(/\s+/g, ' '). + replace(/^(<\/?p>||\w+<\/h\d>|\s)+/, ''); + + var match = /([\w\W]+?\.)[^a-z0-9_$]/i.exec(desc); + return match ? match[1] : desc; + } + return ""; + }, + + _sliceSpecialTags: function (descriptionCopy, startSymbol, endSymbol) { + var startIndex, endIndex; + while (descriptionCopy.indexOf(startSymbol) !== -1 && descriptionCopy.indexOf(startSymbol) < descriptionCopy.indexOf(".")) { + startIndex = descriptionCopy.indexOf(startSymbol); + endIndex = descriptionCopy.indexOf(endSymbol); + descriptionCopy = descriptionCopy.slice(0, startIndex) + descriptionCopy.slice(endIndex + endSymbol.length, descriptionCopy.length); + } + return descriptionCopy; + } + + }; + + /* Methods direct copy from API Detail */ + let methods = { + + /** + * Pre-process and modify references + * @param {object} oSymbol control data object which will be modified + * @private + */ + modifyReferences: function (oSymbol) { + var bHeaderDocuLinkFound = false, + bUXGuidelinesLinkFound = false, + aReferences = []; + + if (oSymbol.constructor.references && oSymbol.constructor.references.length > 0) { + oSymbol.constructor.references.forEach(function (sReference) { + var aParts; + + // Docu link - For the header we take into account only the first link that matches one of the patterns + if (!bHeaderDocuLinkFound) { + + // Handled patterns: + // * topic:59a0e11712e84a648bb990a1dba76bc7 + // * {@link topic:59a0e11712e84a648bb990a1dba76bc7} + // * {@link topic:59a0e11712e84a648bb990a1dba76bc7 Link text} + aParts = sReference.match(/^{@link\s+topic:(\w{32})(\s.+)?}$|^topic:(\w{32})$/); + + if (aParts) { + if (aParts[3]) { + // Link is of type topic:GUID + oSymbol.docuLink = aParts[3]; + oSymbol.docuLinkText = oSymbol.basename; + } else if (aParts[1]) { + // Link of type {@link topic:GUID} or {@link topic:GUID Link text} + oSymbol.docuLink = aParts[1]; + oSymbol.docuLinkText = aParts[2] ? aParts[2] : oSymbol.basename; + } + bHeaderDocuLinkFound = true; + return; + } + } + + // Fiori link - Handled patterns: + // * fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/ + // * {@link fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/} + // * {@link fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/ Flexible Column Layout} + aParts = sReference.match(/^{@link\s+fiori:(\S+)(\s.+)?}$|^fiori:(\S+)$/); + + if (aParts) { + + if (!bUXGuidelinesLinkFound) { + // Extract first found UX Guidelines link as primary + if (aParts) { + if (aParts[3]) { + // String of type: "fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/" + oSymbol.uxGuidelinesLink = aParts[3]; + oSymbol.uxGuidelinesLinkText = oSymbol.basename; + } else if (aParts[1]) { + // String of type: "{@link fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/}" + // or + // String of type: "{@link fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/ Flexible Column Layout}" + oSymbol.uxGuidelinesLink = aParts[1]; + oSymbol.uxGuidelinesLinkText = aParts[2] ? aParts[2] : oSymbol.basename; + } + bUXGuidelinesLinkFound = true; + return; + } + } else { + // BCP: 1870155880 - Every consecutive "fiori:" link should be handled as a normal link + sReference = sReference.replace("fiori:", ""); + } + + } + + aReferences.push(sReference); + }); + oSymbol.constructor.references = aReferences; + } else { + oSymbol.constructor.references = []; + } + }, + + /** + * Adjusts methods info so that it can be easily displayed in a table + * @param aMethods - the methods array initially coming from the server + */ + buildMethodsModel: function (aMethods) { + var fnCreateTypesArr = function (sTypes) { + return sTypes.split("|").map(function (sType) { + return {value: sType} + }); + }; + var fnExtractParameterProperties = function (oParameter, aParameters, iDepth, aPhoneName) { + if (oParameter.parameterProperties) { + Object.keys(oParameter.parameterProperties).forEach(function (sProperty) { + var oProperty = oParameter.parameterProperties[sProperty]; + + oProperty.depth = iDepth; + + // Handle types + if (oProperty.type) { + oProperty.types = fnCreateTypesArr(oProperty.type); + } + + // Phone name - available only for parameters + oProperty.phoneName = [aPhoneName.join("."), oProperty.name].join("."); + + // Add property to parameter array as we need a simple structure + aParameters.push(oProperty); + + // Handle child parameterProperties + fnExtractParameterProperties(oProperty, aParameters, (iDepth + 1), aPhoneName.concat([oProperty.name])); + + // Keep file size in check + delete oProperty.type; + }); + + // Keep file size in check + delete oParameter.parameterProperties; + } + }; + aMethods.forEach(function (oMethod) { + // New array to hold modified parameters + var aParameters = []; + + // Handle parameters + if (oMethod.parameters) { + oMethod.parameters.forEach(function (oParameter) { + if (oParameter.type) { + oParameter.types = fnCreateTypesArr(oParameter.type); + } + + // Keep file size in check + delete oParameter.type; + + // Add the parameter before the properties + aParameters.push(oParameter); + + // Handle Parameter Properties + // Note: We flatten the structure + fnExtractParameterProperties(oParameter, aParameters, 1, [oParameter.name]); + + }); + + // Override the old data + oMethod.parameters = aParameters; + } + + // Handle return values + if (oMethod.returnValue && oMethod.returnValue.type) { + // Handle types + oMethod.returnValue.types = fnCreateTypesArr(oMethod.returnValue.type); + } + + }); + }, + + /** + * Adjusts events info so that it can be easily displayed in a table + * @param {Array} aEvents - the events array initially coming from the server + */ + buildEventsModel: function (aEvents) { + var fnExtractParameterProperties = function (oParameter, aParameters, iDepth, aPhoneName) { + if (oParameter.parameterProperties) { + Object.keys(oParameter.parameterProperties).forEach(function (sProperty) { + var oProperty = oParameter.parameterProperties[sProperty], + sPhoneTypeSuffix; + + oProperty.depth = iDepth; + + // Phone name - available only for parameters + sPhoneTypeSuffix = oProperty.type === "array" ? "[]" : ""; + oProperty.phoneName = [aPhoneName.join("."), (oProperty.name + sPhoneTypeSuffix)].join("."); + + // Add property to parameter array as we need a simple structure + aParameters.push(oProperty); + + // Handle child parameterProperties + fnExtractParameterProperties(oProperty, aParameters, (iDepth + 1), + aPhoneName.concat([oProperty.name + sPhoneTypeSuffix])); + }); + + // Keep file size in check + delete oParameter.parameterProperties; + } + }; + aEvents.forEach(function (aEvents) { + // New array to hold modified parameters + var aParameters = []; + + // Handle parameters + if (aEvents.parameters) { + aEvents.parameters.forEach(function (oParameter) { + // Add the parameter before the properties + aParameters.push(oParameter); + + // Handle Parameter Properties + // Note: We flatten the structure + fnExtractParameterProperties(oParameter, aParameters, 1, [oParameter.name]); + }); + + // Override the old data + aEvents.parameters = aParameters; + } + }); + }, + + /** + * Adjusts constructor parameters info so that it can be easily displayed in a table + * @param {Array} aParameters - the events array initially coming from the server + */ + buildConstructorParameters: function (aParameters) { + // New array to hold modified parameters + var aNodes = [], + processNode = function (oNode, sPhoneName, iDepth, aNodes) { + // Handle phone name + oNode.phoneName = sPhoneName ? [sPhoneName, oNode.name].join(".") : oNode.name; + + // Depth + oNode.depth = iDepth; + + // Add to array + aNodes.push(oNode); + + // Handle nesting + if (oNode.parameterProperties) { + Object.keys(oNode.parameterProperties).forEach(function (sNode) { + processNode(oNode.parameterProperties[sNode], oNode.phoneName, (iDepth + 1), aNodes); + }); + } + + delete oNode.parameterProperties; + }; + + aParameters.forEach(function (oParameter) { + // Handle Parameter Properties + // Note: We flatten the structure + processNode(oParameter, undefined, 0, aNodes); + }); + + return aNodes; + }, + + oLibsData: {}, + aTreeContent: [], + + _getControlChildren: function (sTopicId) { + // Find tree node + var findTreeNode = function (aNodes, sTopicId) { + var iLen, + oNode, + i; + + for (i = 0, iLen = aNodes.length; i < iLen; i++) { + oNode = aNodes[i]; + if (oNode.name === sTopicId) { + return oNode; + } + if (oNode.nodes) { + oNode = findTreeNode(aNodes[i].nodes, sTopicId); + if (oNode) { + return oNode; + } + } + } + }, + oNode = findTreeNode(this.aTreeContent, sTopicId); + + return oNode.nodes ? oNode.nodes : false; + }, + + _parseLibraryElements : function (aLibraryElements) { + var oLibraryElement, + aNodes, + i; + + for (i = 0; i < aLibraryElements.length; i++) { + oLibraryElement = aLibraryElements[i]; + aNodes = oLibraryElement.nodes; + + if (!aNodes) { + this.oLibsData[oLibraryElement.name] = oLibraryElement; + } + + this._addElementToTreeData(oLibraryElement, aLibraryElements); + + if (aNodes) { + this._parseLibraryElements(aNodes, true); + } + } + + return this.aTreeContent; + }, + + _addElementToTreeData : function (oJSONElement, aLibraryElements) { + var oNewNodeNamespace; + + if (oJSONElement.kind !== "namespace") { + var aNameParts = oJSONElement.name.split("."), + sBaseName = aNameParts.pop(), + sNodeNamespace = aNameParts.join("."), // Note: Array.pop() on the previous line modifies the array itself + oTreeNode = this._createTreeNode(sBaseName, oJSONElement.name), + oExistingNodeNamespace = this._findNodeNamespaceInTreeStructure(sNodeNamespace); + + if (oExistingNodeNamespace) { + if (!oExistingNodeNamespace.nodes) { + oExistingNodeNamespace.nodes = []; + } + oExistingNodeNamespace.nodes.push(oTreeNode); + } else if (sNodeNamespace) { + oNewNodeNamespace = this._createTreeNode(sNodeNamespace, sNodeNamespace); + oNewNodeNamespace.nodes = []; + oNewNodeNamespace.nodes.push(oTreeNode); + this.aTreeContent.push(oNewNodeNamespace); + + this._removeDuplicatedNodeFromTree(sNodeNamespace); + + // Inject missing new root namespace in main collection + aLibraryElements.push({ + kind: "namespace", // Note: we show this elements as namespaces + name: sNodeNamespace, + ref: "#/api/" + sNodeNamespace + }); + + } else { + // Entities for which we can't resolve namespace we are shown in the root level + oNewNodeNamespace = this._createTreeNode(oJSONElement.name, oJSONElement.name); + this.aTreeContent.push(oNewNodeNamespace); + } + } else { + oNewNodeNamespace = this._createTreeNode(oJSONElement.name, oJSONElement.name); + this.aTreeContent.push(oNewNodeNamespace); + } + }, + + _createTreeNode : function (text, name, sLib) { + var oTreeNode = {}; + oTreeNode.text = text; + oTreeNode.name = name; + oTreeNode.ref = "#/api/" + name; + return oTreeNode; + }, + + _findNodeNamespaceInTreeStructure : function (sNodeNamespace, aTreeStructure) { + aTreeStructure = aTreeStructure || this.aTreeContent; + for (var i = 0; i < aTreeStructure.length; i++) { + var oTreeNode = aTreeStructure[i]; + if (oTreeNode.name === sNodeNamespace) { + return oTreeNode; + } + if (oTreeNode.nodes) { + var oChildNode = this._findNodeNamespaceInTreeStructure(sNodeNamespace, oTreeNode.nodes); + if (oChildNode) { + return oChildNode; + } + } + } + }, + + _removeNodeFromNamespace : function (sNode, oNamespace) { + for (var i = 0; i < oNamespace.nodes.length; i++) { + if (oNamespace.nodes[i].text === sNode) { + oNamespace.nodes.splice(i, 1); + return; + } + } + }, + + _removeDuplicatedNodeFromTree : function (sNodeFullName) { + if (this.oLibsData[sNodeFullName]) { + var sNodeNamespace = sNodeFullName.substring(0, sNodeFullName.lastIndexOf(".")); + var oNamespace = this._findNodeNamespaceInTreeStructure(sNodeNamespace); + var sNode = sNodeFullName.substring(sNodeFullName.lastIndexOf(".") + 1, sNodeFullName.lenght); + this._removeNodeFromNamespace(sNode, oNamespace); + } + }, + _addChildrenDescription: function (aLibsData, aControlChildren) { + function getDataByName (sName) { + var iLen, + i; + + for (i = 0, iLen = aLibsData.length; i < iLen; i++) { + if (aLibsData[i].name === sName) { + return aLibsData[i]; + } + } + return false; + } + for (var i = 0; i < aControlChildren.length; i++) { + aControlChildren[i].description = formatters._formatChildDescription(getDataByName(aControlChildren[i].name).description); + aControlChildren[i].description = formatters._preProcessLinksInTextBlock(aControlChildren[i].description, true); + + // Handle nesting + if (aControlChildren[i].nodes) { + this._addChildrenDescription(aLibsData, aControlChildren[i].nodes); + } + } + } + }; + + // Create the chain object + let oChainObject = { + inputFile: sInputFile, + outputFile: sOutputFile, + libraryFile: sLibraryFile + }; + + // Start the work here + var p = getLibraryPromise(oChainObject) + .then(extractComponentAndDocuindexUrl) + .then(flattenComponents) + .then(extractSamplesFromDocuIndex) + .then(getAPIJSONPromise) + .then(transformApiJson) + .then(createApiRefApiJson); + return p; + +} diff --git a/lib/processors/jsdoc/ui5/plugin.js b/lib/processors/jsdoc/ui5/plugin.js new file mode 100644 index 000000000..9a323ea06 --- /dev/null +++ b/lib/processors/jsdoc/ui5/plugin.js @@ -0,0 +1,2321 @@ +/* + * JSDoc3 plugin for UI5 documentation generation. + * + * (c) Copyright 2009-2018 SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ + +/* global require, exports, env */ +/* eslint strict: [2, "global"]*/ + +'use strict'; + +/** + * UI5 plugin for JSDoc3 (3.3.0-alpha5) + * + * The plugin adds the following SAPUI5 specific tag definitions to JSDoc3 + * + * disclaimer + * + * experimental + * + * final + * + * interface + * + * implements + * + * + * + * It furthermore listens to the following JSDoc3 events to implement additional functionality + * + * parseBegin + * to create short names for all file that are to be parsed + * + * fileBegin + * to write some line to the log (kind of a progress indicator) + * + * jsdocCommentFound + * to pre-process comments, empty lines are used as paragraph markers + * a default visibility is added, legacy tag combinations used in JSdoc2 are converted to JSDoc3 conventions + * + * newDoclet + * + * parseComplete + * remove undocumented/ignored/private doclets or duplicate doclets + * + * + * Last but not least, it implements an astNodeVisitor to detect UI5 specific "extend" calls and to create + * documentation for the properties, aggregations etc. that are created with the "extend" call. + * + * @module plugins/sapui5-jsdoc + */ + +/* imports */ +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) || {}; + +/* ---- global vars---- */ + +/** + * Potential path prefixes. + * + * Will be determined in the handler for the parseBegin event + */ +var pathPrefixes = []; + +/** + * Prefixes of the UI5 unified resource name for the source files is NOT part of the file name. + * (e.g. when a common root namespaces has been omitted from the folder structure). + * + * The prefix will be prepended to all resource names. + */ +var resourceNamePrefixes = []; + +/** + * A UI5 specific unique Id for all doclets. + */ +var docletUid = 0; + +var currentProgram; + +/** + * Information about the current module. + * + * The info object is created in the 'fileBegin' event handler and the 'resource' and 'module' properties + * are derived from the filename provided by the event. The derived information is only correct, when the + * resource name prefix is known for the directory from which a source is loaded (prefixes can be configured + * via sapui5.resourceNamePrefixes, for UI5 libraries it is empty by default). + * + * During AST visiting, the 'name' property and the 'localeNames' map will be filled. + * 'name' will be the name of the class defined by the module (assuming that there is only one). + * 'localNames' will contain information objects for each parameter of an AMD Factory function and for + * all shortcut variables that are defined top-level in the module factory function (e.g. something like + * var ButtonDesign = coreLibrary.ButtonDesign; ). + * An info object for a local name either can have a 'value' property (simple, constant value) or it can + * 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. + * + * @type {{name:string,resource:string,module:string,localName:Object}} + */ +var currentModule; + +var currentSource; + +/** + * Cached UI5 metadata for encountered UI5 classes. + * + * The metadata is collected from the 'metadata' property of 'extend' calls. It is stored + * in this map keyed by the name of the class (as defined in the first parameter of the extend call). + * Only after all files have been parsed, the collected information can be associated with the + * corresponding JSDoc doclet (e.g. with the class documentation). + */ +var classInfos = Object.create(null); + +/** + * + */ +var typeInfos = Object.create(null); + +/** + * Cached designtime info for encountered sources. + * + * The designtime information is collected only for files named '*.designtime.js'. + * It is stored in this map keyed by the corresponding module name (e.g. 'sap/m/designtime/Button.designtime'). + * Only after all files have been parsed, the collected information can be associated with runtime metadata + * that refers to that designtime module name. + */ +var designtimeInfos = Object.create(null); + +/* ---- private functions ---- */ + +function ui5data(doclet) { + return doclet.__ui5 || (doclet.__ui5 = { id: ++docletUid }); +} + +var pendingMessageHeader; + +function msgHeader(str) { + pendingMessageHeader = str; +} + +function debug() { + if ( env.opts.debug ) { + console.log.apply(console, arguments); + } +} + +function info() { + if ( env.opts.verbose || env.opts.debug ) { + if ( pendingMessageHeader ) { + console.log(""); + pendingMessageHeader = null; + } + console.log.apply(console, arguments); + } +} + +function warning(msg) { + if ( pendingMessageHeader ) { + if ( !env.opts.verbose && !env.opts.debug ) { + console.log(pendingMessageHeader); + } else { + console.log(""); + } + pendingMessageHeader = null; + } + var args = Array.prototype.slice.apply(arguments); + args[0] = "**** warning: " + args[0]; + console.log.apply(console, args); +} + +function error(msg) { + if ( pendingMessageHeader && !env.opts.verbose && !env.opts.debug ) { + if ( !env.opts.verbose && !env.opts.debug ) { + console.log(pendingMessageHeader); + } else { + console.log(""); + } + pendingMessageHeader = null; + } + var args = Array.prototype.slice.apply(arguments); + args[0] = "**** error: " + args[0]; + console.log.apply(console, args); +} + +//---- path handling --------------------------------------------------------- + +function ensureEndingSlash(path) { + path = path || ''; + return path && path.slice(-1) !== '/' ? path + '/' : path; +} + +function getRelativePath(filename) { + var relative = path.resolve(filename); + for ( var i = 0; i < pathPrefixes.length; i++ ) { + if ( relative.indexOf(pathPrefixes[i]) === 0 ) { + relative = relative.slice(pathPrefixes[i].length); + break; + } + } + return relative.replace(/\\/g, '/'); +} + +function getResourceName(filename) { + var resource = path.resolve(filename); + for ( var i = 0; i < pathPrefixes.length; i++ ) { + if ( resource.indexOf(pathPrefixes[i]) === 0 ) { + resource = resourceNamePrefixes[i] + resource.slice(pathPrefixes[i].length); + break; + } + } + return resource.replace(/\\/g, '/'); +} + +function getModuleName(resource) { + return resource.replace(/\.js$/,''); +} + +/* + * resolves relative AMD module identifiers relative to a given base name + */ +function resolveModuleName(base, name) { + var stack = base.split('/'); + stack.pop(); + name.split('/').forEach(function(segment, i) { + if ( segment == '..' ) { + stack.pop(); + } else if ( segment === '.' ) { + // ignore + } else { + if ( i === 0 ) { + stack = []; + } + stack.push(segment); + } + }); + return stack.join('/'); +} + +// ---- AMD handling + +function analyzeModuleDefinition(node) { + var args = node.arguments; + var arg = 0; + if ( arg < args.length + && args[arg].type === Syntax.Literal && typeof args[arg].value === 'string' ) { + warning("module explicitly defined a module name '" + args[arg].value + "'"); + currentModule.name = args[arg].value; + arg++; + } + if ( arg < args.length && args[arg].type === Syntax.ArrayExpression ) { + currentModule.dependencies = convertValue(args[arg], "string[]"); + arg++; + } + if ( arg < args.length && args[arg].type === Syntax.FunctionExpression ) { + currentModule.factory = args[arg]; + arg++; + } + if ( currentModule.dependencies && currentModule.factory ) { + for ( var i = 0; i < currentModule.dependencies.length && i < currentModule.factory.params.length; i++ ) { + var name = currentModule.factory.params[i].name; + var module = resolveModuleName(currentModule.module, currentModule.dependencies[i]); + debug(" import " + name + " from '" + module + "'"); + currentModule.localNames[name] = { + module: module + // no (or empty) path + }; + } + } + if ( currentModule.factory ) { + collectShortcuts(currentModule.factory.body); + } +} + +/** + * 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) + * or because they have a (design time) constant value. + * + * @param {ASTNode} body + */ +function collectShortcuts(body) { + + function checkAssignment(name, valueNode) { + if ( valueNode.type === Syntax.Literal ) { + currentModule.localNames[name] = { + value: valueNode.value + }; + debug("compile time constant found ", name, valueNode.value); + } else if ( valueNode.type === Syntax.MemberExpression ) { + var _import = getLeftmostName(valueNode); + var local = _import && currentModule.localNames[_import]; + if ( typeof local === 'object' && local.module ) { + currentModule.localNames[name] = { + module: local.module, + path: getObjectName(valueNode).split('.').slice(1).join('.') // TODO chaining if local has path + }; + debug(" found local shortcut: ", name, currentModule.localNames[name]); + } + } + } + + if ( body.type === Syntax.BlockStatement ) { + body.body.forEach(function ( stmt ) { + // console.log(stmt); + if ( stmt.type === Syntax.VariableDeclaration ) { + stmt.declarations.forEach(function(decl) { + if ( decl.init ) { + checkAssignment(decl.id.name, decl.init); + } + }) + } else if ( stmt.type === Syntax.ExpressionStatement + && stmt.expression.type === Syntax.AssignmentExpression + && stmt.expression.left.type === Syntax.Identifier ) { + checkAssignment(stmt.expression.left.name, stmt.expression.right); + } + }); + } +} + +// ---- text handling --------------------------------------------------------- + +var rPlural = /(children|ies|ves|oes|ses|ches|shes|xes|s)$/i; +var mSingular = {'children' : -3, 'ies' : 'y', 'ves' : 'f', 'oes' : -2, 'ses' : -2, 'ches' : -2, 'shes' : -2, 'xes' : -2, 's' : -1 }; + +function guessSingularName(sPluralName) { + return sPluralName.replace(rPlural, function($,sPlural) { + var vRepl = mSingular[sPlural.toLowerCase()]; + return typeof vRepl === "string" ? vRepl : sPlural.slice(0,vRepl); + }); +} + +/** + * Creates a map of property values from an AST 'object literal' node. + * + * The values in the map are again AST 'property' nodes (representing key/value pairs). + * It would be more convenient to just return the values, but the property node is needed + * to find the corresponding (preceding) documentation comment. + * + * @param node + * @param defaultKey + * @returns {Map} + */ +function createPropertyMap(node, defaultKey) { + + var result; + + if ( node != null ) { + + // if, instead of an object literal only a literal is given and there is a defaultKey, then wrap the literal in a map + if ( node.type === Syntax.Literal && defaultKey != null ) { + result = {}; + result[defaultKey] = { type: Syntax.Property, value: node }; + return result; + } + + if ( node.type != Syntax.ObjectExpression ) { + // something went wrong, it's not an object literal + error("not an object literal:" + node.type + ":" + node.value); + // console.log(node.toSource()); + return undefined; + } + + // invariant: node.type == Syntax.ObjectExpression + result = {}; + for (var i = 0; i < node.properties.length; i++) { + var prop = node.properties[i]; + var name; + //console.log("objectproperty " + prop.type); + if ( prop.key.type === Syntax.Identifier ) { + name = prop.key.name; + } else if ( prop.key.type === Syntax.Literal ) { + name = String(prop.key.value); + } else { + name = prop.key.toSource(); + } + //console.log("objectproperty " + prop.type + ":" + name); + result[name] = prop; + } + } + return result; +} + +function isExtendCall(node) { + + return ( + node + && node.type === Syntax.CallExpression + && node.callee.type === Syntax.MemberExpression + && node.callee.property.type === Syntax.Identifier + && node.callee.property.name === 'extend' + && node.arguments.length >= 2 + && node.arguments[0].type === Syntax.Literal + && typeof node.arguments[0].value === "string" + && node.arguments[1].type === Syntax.ObjectExpression + ); + +} + +function isSapUiDefineCall(node) { + + return ( + node + && node.type === Syntax.CallExpression + && node.callee.type === Syntax.MemberExpression + && node.callee.object.type === Syntax.MemberExpression + && node.callee.object.object.type === Syntax.Identifier + && node.callee.object.object.name === 'sap' + && node.callee.object.property.type === Syntax.Identifier + && node.callee.object.property.name === 'ui' + && node.callee.property.type === Syntax.Identifier + && node.callee.property.name === 'define' + ); + +} + +function isCreateDataTypeCall(node) { + return ( + node + && node.type === Syntax.CallExpression + && node.callee.type === Syntax.MemberExpression + && /^(sap\.ui\.base\.)?DataType$/.test(getObjectName(node.callee.object)) + && node.callee.property.type === Syntax.Identifier + && node.callee.property.name === 'createType' + ); +} + +function getObjectName(node) { + if ( node.type === Syntax.MemberExpression && !node.computed && node.property.type === Syntax.Identifier ) { + var prefix = getObjectName(node.object); + return prefix ? prefix + "." + node.property.name : null; + } else if ( node.type === Syntax.Identifier ) { + return /* scope[node.name] ? scope[node.name] : */ node.name; + } else { + return null; + } +} + +/* + * Checks whether the node is a qualified name (a.b.c) and if so, + * returns the leftmost identifier a + */ +function getLeftmostName(node) { + while ( node.type === Syntax.MemberExpression ) { + node = node.object; + } + if ( node.type === Syntax.Identifier ) { + return node.name; + } + // return undefined; +} + +function getResolvedObjectName(node) { + var name = getObjectName(node); + var _import = getLeftmostName(node); + var local = _import && currentModule.localNames[_import]; + if ( local && local.module ) { + var resolvedName = local.module.replace(/\//g, ".").replace(/\.library$/, ""); + if ( local.path ) { + resolvedName = resolvedName + "." + local.path; + } + if ( name.indexOf('.') > 0 ) { + resolvedName = resolvedName + name.slice(name.indexOf('.')); + } + debug("resolved " + name + " to " + resolvedName); + return resolvedName; + } + return name; +} + +function convertValue(node, type, propertyName) { + + var value; + + if ( node.type === Syntax.Literal ) { + + // 'string' or number or true or false + return node.value; + + } else if ( node.type === Syntax.UnaryExpression + && node.prefix + && node.argument.type === Syntax.Literal + && typeof node.argument.value === 'number' + && ( node.operator === '-' || node.operator === '+' )) { + + // -n or +n + value = node.argument.value; + return node.operator === '-' ? -value : value; + + } else if ( node.type === Syntax.MemberExpression && type ) { + + // enum value (a.b.c) + value = getResolvedObjectName(node); + if ( value.indexOf(type + ".") === 0 ) { + // starts with fully qualified enum name -> cut off name + return value.slice(type.length + 1); +// } else if ( value.indexOf(type.split(".").slice(-1)[0] + ".") === 0 ) { +// // unqualified name might be a local name (just a guess - would need static code analysis for proper solution) +// return value.slice(type.split(".").slice(-1)[0].length + 1); + } else { + warning("did not understand default value '%s'%s, falling back to source", value, propertyName ? " of property '" + propertyName + "'" : ""); + return value; + } + + } else if ( node.type === Syntax.Identifier ) { + if ( node.name === 'undefined') { + // undefined + return undefined; + } + var local = currentModule.localNames[node.name]; + if ( typeof local === 'object' && 'value' in local ) { + // TODO check type + return local.value; + } + } else if ( node.type === Syntax.ArrayExpression ) { + + if ( node.elements.length === 0 ) { + // empty array literal + return "[]"; // TODO return this string or an empty array + } + + if ( type && type.slice(-2) === "[]" ) { + var componentType = type.slice(0,-2); + return node.elements.map( function(elem) { + return convertValue(elem, componentType, propertyName); + }); + } + + } else if ( node.type === Syntax.ObjectExpression ) { + + if ( node.properties.length === 0 && (type === 'object' || type === 'any') ) { + return {}; + } + + } + + value = '???'; + if ( currentSource && node.range ) { + value = currentSource.slice( node.range[0], node.range[1] ); + } + error("unexpected type of default value (type='%s', source='%s')%s, falling back to '%s'", node.type, node.toString(), propertyName ? " of property '" + propertyName + "'" : "", value); + return value; +} + +function convertStringArray(node) { + if ( node.type !== Syntax.ArrayExpression ) { + throw new Error("not an array"); + } + var result = []; + for ( var i = 0; i < node.elements.length; i++ ) { + if ( node.elements[i].type !== Syntax.Literal || typeof node.elements[i].value !== 'string' ) { + throw new Error("not a string literal"); + } + result.push(node.elements[i].value); + } + // console.log(result); + return result; +} + +function convertDragDropValue(node, cardinality) { + var mDragDropValue; + var mPossibleKeys = {draggable: 1, droppable: 1}; + var mDefaults = { + "self" : { draggable : true, droppable: true }, + "0..1" : { draggable : true, droppable: true }, + "0..n" : { draggable : false, droppable: false } + }; + + if ( node.type === Syntax.ObjectExpression ) { + mDragDropValue = (node.properties || []).reduce(function(oObject, oProperty) { + var sKey = convertValue(oProperty.key); + if (mPossibleKeys[sKey]) { + oObject[sKey] = convertValue(oProperty.value); + } + return oObject; + }, {}); + } else if ( node.type === Syntax.Literal ) { + mDragDropValue = { + draggable : node.value, + droppable : node.value + }; + } else { + throw new Error("not a valid dnd node"); + } + + return Object.assign(mDefaults[cardinality || "self"], mDragDropValue); +} + +function collectClassInfo(extendCall, classDoclet) { + + var baseType; + if ( classDoclet && classDoclet.augments && classDoclet.augments.length === 1 ) { + baseType = classDoclet.augments[0]; + } + if ( extendCall.callee.type === Syntax.MemberExpression ) { + var baseCandidate = getResolvedObjectName(extendCall.callee.object); + if ( baseCandidate && baseType == null ) { + baseType = baseCandidate; + } else if ( baseCandidate !== baseType ) { + error("documented base type '" + baseType + "' doesn't match technical base type '" + baseCandidate + "'"); + } + } + + var oClassInfo = { + name : extendCall.arguments[0].value, + baseType : baseType, + interfaces : [], + doc : classDoclet && classDoclet.description, + deprecation : classDoclet && classDoclet.deprecated, + since : classDoclet && classDoclet.since, + experimental : classDoclet && classDoclet.experimental, + specialSettings : {}, + properties : {}, + aggregations : {}, + associations : {}, + events : {}, + methods : {}, + annotations : {}, + designtime: false + }; + + function upper(n) { + return n.slice(0,1).toUpperCase() + n.slice(1); + } + + function each(node, defaultKey, callback) { + var map,n,settings,doclet; + + map = node && createPropertyMap(node.value); + if ( map ) { + for (n in map ) { + if ( map.hasOwnProperty(n) ) { + doclet = getLeadingDoclet(map[n]); + settings = createPropertyMap(map[n].value, defaultKey); + if ( settings == null ) { + error("no valid metadata for " + n + " (AST type '" + map[n].value.type + "')"); + continue; + } + + callback(n, settings, doclet, map[n]); + } + } + } + } + + var classInfoNode = extendCall.arguments[1]; + var classInfoMap = createPropertyMap(classInfoNode); + if ( classInfoMap && classInfoMap.metadata && classInfoMap.metadata.value.type !== Syntax.ObjectExpression ) { + warning("class metadata exists but can't be analyzed. It is not of type 'ObjectExpression', but a '" + classInfoMap.metadata.value.type + "'."); + return null; + } + + var metadata = classInfoMap && classInfoMap.metadata && createPropertyMap(classInfoMap.metadata.value); + if ( metadata ) { + + debug(" analyzing metadata for '" + oClassInfo.name + "'"); + + oClassInfo["abstract"] = !!(metadata["abstract"] && metadata["abstract"].value.value); + oClassInfo["final"] = !!(metadata["final"] && metadata["final"].value.value); + oClassInfo.dnd = metadata.dnd && convertDragDropValue(metadata.dnd.value); + + if ( metadata.interfaces ) { + oClassInfo.interfaces = convertStringArray(metadata.interfaces.value); + } + + each(metadata.specialSettings, "type", function(n, settings, doclet) { + oClassInfo.specialSettings[n] = { + name : n, + doc : doclet && doclet.description, + since : doclet && doclet.since, + deprecation : doclet && doclet.deprecated, + experimental : doclet && doclet.experimental, + visibility : (settings.visibility && settings.visibility.value.value) || "public", + type : settings.type ? settings.type.value.value : "any" + }; + }); + + oClassInfo.defaultProperty = (metadata.defaultProperty && metadata.defaultProperty.value.value) || undefined; + + each(metadata.properties, "type", function(n, settings, doclet) { + var type; + var N = upper(n); + var methods; + oClassInfo.properties[n] = { + name : n, + doc : doclet && doclet.description, + since : doclet && doclet.since, + deprecation : doclet && doclet.deprecated, + experimental : doclet && doclet.experimental, + visibility : (settings.visibility && settings.visibility.value.value) || "public", + type : (type = settings.type ? settings.type.value.value : "string"), + defaultValue : settings.defaultValue ? convertValue(settings.defaultValue.value, type, n) : null, + group : settings.group ? settings.group.value.value : 'Misc', + bindable : settings.bindable ? !!convertValue(settings.bindable.value) : false, + methods: (methods = { + "get": "get" + N, + "set": "set" + N + }) + }; + if ( oClassInfo.properties[n].bindable ) { + methods["bind"] = "bind" + N; + methods["unbind"] = "unbind" + N; + } + // if ( !settings.defaultValue ) { + // console.log("property without defaultValue: " + oClassInfo.name + "." + n); + //} + if ( oClassInfo.properties[n].visibility !== 'public' ) { + error("Property '" + n + "' uses visibility '" + oClassInfo.properties[n].visibility + "' which is not supported by the runtime"); + } + }); + + oClassInfo.defaultAggregation = (metadata.defaultAggregation && metadata.defaultAggregation.value.value) || undefined; + + each(metadata.aggregations, "type", function(n, settings, doclet) { + var N = upper(n); + var methods; + var aggr = oClassInfo.aggregations[n] = { + name: n, + doc : doclet && doclet.description, + deprecation : doclet && doclet.deprecated, + since : doclet && doclet.since, + experimental : doclet && doclet.experimental, + visibility : (settings.visibility && settings.visibility.value.value) || "public", + type : settings.type ? settings.type.value.value : "sap.ui.core.Control", + altTypes: settings.altTypes ? convertStringArray(settings.altTypes.value) : undefined, + singularName : settings.singularName ? settings.singularName.value.value : guessSingularName(n), + cardinality : (settings.multiple && !settings.multiple.value.value) ? "0..1" : "0..n", + bindable : settings.bindable ? !!convertValue(settings.bindable.value) : false, + methods: (methods = { + "get": "get" + N, + "destroy": "destroy" + N + }) + }; + + aggr.dnd = settings.dnd && convertDragDropValue(settings.dnd.value, aggr.cardinality); + + if ( aggr.cardinality === "0..1" ) { + methods["set"] = "set" + N; + } else { + var N1 = upper(aggr.singularName); + methods["insert"] = "insert" + N1; + methods["add"] = "add" + N1; + methods["remove"] = "remove" + N1; + methods["indexOf"] = "indexOf" + N1; + methods["removeAll"] = "removeAll" + N; + } + if ( aggr.bindable ) { + methods["bind"] = "bind" + N; + methods["unbind"] = "unbind" + N; + } + }); + + each(metadata.associations, "type", function(n, settings, doclet) { + var N = upper(n); + var methods; + oClassInfo.associations[n] = { + name: n, + doc : doclet && doclet.description, + deprecation : doclet && doclet.deprecated, + since : doclet && doclet.since, + experimental : doclet && doclet.experimental, + visibility : (settings.visibility && settings.visibility.value.value) || "public", + type : settings.type ? settings.type.value.value : "sap.ui.core.Control", + singularName : settings.singularName ? settings.singularName.value.value : guessSingularName(n), + cardinality : (settings.multiple && settings.multiple.value.value) ? "0..n" : "0..1", + methods: (methods = { + "get": "get" + N + }) + }; + if ( oClassInfo.associations[n].cardinality === "0..1" ) { + methods["set"] = "set" + N; + } else { + var N1 = upper(oClassInfo.associations[n].singularName); + methods["add"] = "add" + N1; + methods["remove"] = "remove" + N1; + methods["removeAll"] = "removeAll" + N; + } + if ( oClassInfo.associations[n].visibility !== 'public' ) { + error("Association '" + n + "' uses visibility '" + oClassInfo.associations[n].visibility + "' which is not supported by the runtime"); + } + }); + + each(metadata.events, null, function(n, settings, doclet) { + var N = upper(n); + var info = oClassInfo.events[n] = { + name: n, + doc : doclet && doclet.description, + deprecation : doclet && doclet.deprecated, + since : doclet && doclet.since, + experimental : doclet && doclet.experimental, + visibility : /* (settings.visibility && settings.visibility.value.value) || */ "public", + allowPreventDefault : !!(settings.allowPreventDefault && settings.allowPreventDefault.value.value), + parameters : {}, + methods: { + "attach": "attach" + N, + "detach": "detach" + N, + "fire": "fire" + N + } + }; + each(settings.parameters, "type", function(pName, pSettings, pDoclet) { + info.parameters[pName] = { + name : pName, + doc : pDoclet && pDoclet.description, + deprecation : pDoclet && pDoclet.deprecated, + since : pDoclet && pDoclet.since, + experimental : pDoclet && pDoclet.experimental, + type : pSettings && pSettings.type ? pSettings.type.value.value : "" + }; + }); + }); + + var designtime = (metadata.designtime && convertValue(metadata.designtime.value)) || (metadata.designTime && convertValue(metadata.designTime.value)); + if ( typeof designtime === 'string' || typeof designtime === 'boolean' ) { + oClassInfo.designtime = designtime; + } + // console.log(oClassInfo.name + ":" + JSON.stringify(oClassInfo, null, " ")); + } + + // remember class info by name + classInfos[oClassInfo.name] = oClassInfo; + + return oClassInfo; +} + +function collectDesigntimeInfo(dtNode) { + + function each(node, defaultKey, callback) { + var map,n,settings,doclet; + + map = node && createPropertyMap(node.value); + if ( map ) { + for (n in map ) { + if ( map.hasOwnProperty(n) ) { + doclet = getLeadingDoclet(map[n], true); + settings = createPropertyMap(map[n].value, defaultKey); + if ( settings == null ) { + error("no valid metadata for " + n + " (AST type '" + map[n].value.type + "')"); + continue; + } + + callback(n, settings, doclet, map[n]); + } + } + } + } + + var oDesigntimeInfo; + + var map = createPropertyMap(dtNode.argument); + + if (map.annotations) { + + oDesigntimeInfo = { + annotations: {} + }; + + each(map.annotations, null, function(n, settings, doclet) { + var appliesTo = [], + targets = [], + i, oAnno, iPos; + + if (settings.appliesTo) { + for (i = 0; i < settings.appliesTo.value.elements.length; i++) { + appliesTo.push(settings.appliesTo.value.elements[i].value); + } + } + + if (settings.target) { + for (i = 0; i < settings.target.value.elements.length; i++) { + targets.push(settings.target.value.elements[i].value); + } + } + + oDesigntimeInfo.annotations[n] = { + name: n, + doc : doclet && doclet.description, + deprecation : doclet && doclet.deprecated, + since : doclet && doclet.since || settings.since && settings.since.value.value, + namespace: settings.namespace && settings.namespace.value.value, + annotation: settings.annotation && settings.annotation.value.value, + appliesTo: appliesTo, + target: targets, + interpretation: settings.interpretation && settings.interpretation.value.value, + defaultValue: settings.defaultValue && settings.defaultValue.value.value + }; + + oAnno = oDesigntimeInfo.annotations[n].annotation; + iPos = oAnno && oAnno.lastIndexOf("."); + + if ( !oDesigntimeInfo.annotations[n].namespace && iPos > 0 ) { + oDesigntimeInfo.annotations[n].namespace = oAnno.slice(0, iPos); + oDesigntimeInfo.annotations[n].annotation = oAnno.slice(iPos + 1); + } + }) + } + + return oDesigntimeInfo; +} + +function determineValueRangeBorder(range, expression, varname, inverse) { + if ( expression.type === Syntax.BinaryExpression ) { + var value; + if ( expression.left.type === Syntax.Identifier && expression.left.name === varname && expression.right.type === Syntax.Literal ) { + value = expression.right.value; + } else if ( expression.left.type === Syntax.Literal && expression.right.type === Syntax.Identifier && expression.right.name === varname ) { + inverse = !inverse; + value = expression.left.value; + } else { + return false; + } + switch (expression.operator) { + case '<': + range[inverse ? 'minExclusive' : 'maxExclusive'] = value; + break; + case '<=': + range[inverse ? 'minInclusive' : 'maxInclusive'] = value; + break; + case '>=': + range[inverse ? 'maxInclusive' : 'minInclusive'] = value; + break; + case '>': + range[inverse ? 'maxExclusive' : 'minExclusive'] = value; + break; + default: + return false; + } + return true; + } + return false; +} + +function determineValueRange(expression, varname, inverse) { + var range = {}; + if ( expression.type === Syntax.LogicalExpression + && expression.operator === '&&' + && expression.left.type === Syntax.BinaryExpression + && expression.right.type === Syntax.BinaryExpression + && determineValueRangeBorder(range, expression.left, varname, inverse) + && determineValueRangeBorder(range, expression.right, varname, inverse) ) { + return range; + } else if ( expression.type === Syntax.BinaryExpression + && determineValueRangeBorder(range, expression, varname, inverse) ) { + return range; + } + return undefined; +} + +function collectDataTypeInfo(extendCall, classDoclet) { + var args = extendCall.arguments, + i = 0, + name, def, base, pattern, range; + + if ( i < args.length && args[i].type === Syntax.Literal && typeof args[i].value === 'string' ) { + name = args[i++].value; + } + if ( i < args.length && args[i].type === Syntax.ObjectExpression ) { + def = createPropertyMap(args[i++]); + } + if ( i < args.length ) { + if ( args[i].type === Syntax.Literal && typeof args[i].value === 'string' ) { + base = args[i++].value; + } else if ( args[i].type === Syntax.CallExpression + && args[i].callee.type === Syntax.MemberExpression + && /^(sap\.ui\.base\.)?DataType$/.test(getObjectName(args[i].callee.object)) + && args[i].callee.property.type === Syntax.Identifier + && args[i].callee.property.name === 'getType' + && args[i].arguments.length === 1 + && args[i].arguments[0].type === Syntax.Literal + && typeof args[i].arguments[0].value === 'string' ) { + base = args[i++].arguments[0].value; + } else { + error("could not identify base type of data type '" + name + "'"); + } + } else { + base = "any"; + } + + if ( def + && def.isValid + && def.isValid.value.type === Syntax.FunctionExpression + && def.isValid.value.params.length === 1 + && def.isValid.value.params[0].type === Syntax.Identifier + && def.isValid.value.body.body.length === 1 ) { + var varname = def.isValid.value.params[0].name; + var stmt = def.isValid.value.body.body[0]; + if ( stmt.type === Syntax.ReturnStatement && stmt.argument ) { + if ( stmt.argument.type === Syntax.CallExpression + && stmt.argument.callee.type === Syntax.MemberExpression + && stmt.argument.callee.object.type === Syntax.Literal + && stmt.argument.callee.object.regex + && stmt.argument.callee.property.type === Syntax.Identifier + && stmt.argument.callee.property.name === 'test' ) { + pattern = stmt.argument.callee.object.regex.pattern; + // console.log(pattern); + } else { + range = determineValueRange(stmt.argument, varname, false); + } + } else if ( stmt.type === Syntax.IfStatement + && stmt.consequent.type === Syntax.BlockStatement + && stmt.consequent.body.length === 1 + && stmt.consequent.body[0].type === Syntax.ReturnStatement + && stmt.consequent.body[0].argument + && stmt.consequent.body[0].argument.type === Syntax.Literal + && typeof stmt.consequent.body[0].argument.value === 'boolean' + && stmt.alternate.type === Syntax.BlockStatement + && stmt.alternate.body.length === 1 + && stmt.alternate.body[0].type === Syntax.ReturnStatement + && stmt.alternate.body[0].argument + && stmt.alternate.body[0].argument.type === Syntax.Literal + && typeof stmt.alternate.body[0].argument.value === 'boolean' + && stmt.consequent.body[0].argument.value !== typeof stmt.alternate.body[0].argument.value ) { + var inverse = stmt.alternate.body[0].argument.value; + range = determineValueRange(stmt.test, varname, inverse); + } else { + console.log(stmt); + } + } + + // remember type info by name + if ( name && def && base ) { + typeInfos[name] = { + name: name, + def: def, + pattern: pattern, + range: range, + base: base + }; + // console.log("found data type:", typeInfos[name]); + } +} + +var rEmptyLine = /^\s*$/; + +function createAutoDoc(oClassInfo, classComment, node, parser, filename, commentAlreadyProcessed) { + + var newStyle = !!pluginConfig.newStyle, + includeSettings = !!pluginConfig.includeSettingsInConstructor, + rawClassComment = getRawComment(classComment), + p,n,n1,pName,info,lines,link; + + function isEmpty(obj) { + if ( !obj ) { + return true; + } + for (var n in obj) { + if ( obj.hasOwnProperty(n) ) { + return false; + } + } + return true; + } + + function jsdocCommentFound(comment) { + parser.emit('jsdocCommentFound', { + event:'jsdocCommentFound', + comment : comment, + lineno : node.loc.start.line, + filename : filename, + range : [ node.range[0], node.range[0] ] + }, parser); + } + + function removeDuplicateEmptyLines(lines) { + var lastWasEmpty = false, + i,j,l,line; + + for (i = 0, j = 0, l = lines.length; i < l; i++) { + line = lines[i]; + if ( line == null || rEmptyLine.test(line) ) { + if ( !lastWasEmpty ) { + lines[j++] = line; + } + lastWasEmpty = true; + } else { + lines[j++] = line; + lastWasEmpty = false; + } + } + return j < i ? lines.slice(0,j) : lines; + } + + function newJSDoc(lines) { + //console.log("add completely new jsdoc comment to prog " + node.type + ":" + node.nodeId + ":" + Object.keys(node)); + + lines = removeDuplicateEmptyLines(lines); + lines.push("@synthetic"); + + var comment = " * " + lines.join("\r\n * "); + jsdocCommentFound("/**\r\n" + comment + "\r\n */") + + var m = /@name\s+([^\r\n\t ]+)/.exec(comment); + debug(" creating synthetic comment '" + (m && m[1]) + "'"); + } + + function rname(prefix,n,_static) { + return (_static ? "." : "#") + prefix + n.slice(0,1).toUpperCase() + n.slice(1); + } + + function name(prefix,n,_static) { + return oClassInfo.name + rname(prefix,n,_static); + } + + /* + * creates a JSDoc type string from the given metadata info object. + * It takes into account the type, the altTypes and the cardinality + * (the latter only if componentTypeOnly is not set). + */ + function makeTypeString(aggr, componentTypeOnly) { + var s = aggr.type; + if ( aggr.altTypes ) { + s = s + "|" + aggr.altTypes.join("|"); + } + if ( !componentTypeOnly && aggr.cardinality === "0..n" ) { + // if multiple types are allowed, use Array.<> for proper grouping + if ( aggr.altTypes ) { + s = "Array.<" + s + ">"; + } else { + s = s + "[]"; + } + } + return s; + } + +// function shortname(s) { +// return s.slice(s.lastIndexOf('.') + 1); +// } + + var HUNGARIAN_PREFIXES = { + 'int' : 'i', + 'boolean' : 'b', + 'float' : 'f', + 'string' : 's', + 'function' : 'fn', + 'object' : 'o', + 'regexp' : 'r', + 'jQuery' : '$', + 'any' : 'o', + 'variant' : 'v', + 'map' : 'm' + }; + + function varname(n, type, property) { + var prefix = HUNGARIAN_PREFIXES[type] || (property ? "s" : "o"); + return prefix + n.slice(0,1).toUpperCase() + n.slice(1); + } + + // add a list of the possible settings if and only if + // - documentation for the constructor exists + // - no (generated) documentation for settings exists already + // - a suitable place for inserting the settings can be found + var m = /(?:^|\r\n|\n|\r)[ \t]*\**[ \t]*@[a-zA-Z]/.exec(rawClassComment); + p = m ? m.index : -1; + var hasSettingsDocs = rawClassComment.indexOf("The supported settings are:") >= 0; + + // heuristic to recognize a ManagedObject + var isManagedObject = ( + /@extends\s+sap\.ui\.(?:base\.ManagedObject|core\.(?:Element|Control|Component))(?:\s|$)/.test(rawClassComment) + || oClassInfo.library + || !isEmpty(oClassInfo.specialSettings) + || !isEmpty(oClassInfo.properties) + || !isEmpty(oClassInfo.aggregations) + || !isEmpty(oClassInfo.associations) + || !isEmpty(oClassInfo.events) + ); + + if ( p >= 0 && !hasSettingsDocs ) { + lines = [ + "" + ]; + + if ( isManagedObject ) { // only a ManagedObject has settings + + if ( oClassInfo.name !== "sap.ui.base.ManagedObject" ) { + // add the hint for the general description only when the current class is not ManagedObject itself + lines.push( + "", + "Accepts an object literal mSettings that defines initial", + "property values, aggregated and associated objects as well as event handlers.", + "See {@link sap.ui.base.ManagedObject#constructor} for a general description of the syntax of the settings object." + ); + } + + // add the settings section only if there are any settings + if ( !isEmpty(oClassInfo.properties) + || !isEmpty(oClassInfo.aggregations) + || !isEmpty(oClassInfo.associations) + || !isEmpty(oClassInfo.events) ) { + + lines.push( + "", + includeSettings ? "" : "@ui5-settings", + "The supported settings are:", + "
    " + ); + if ( !isEmpty(oClassInfo.properties) ) { + lines.push("
  • Properties"); + lines.push("
      "); + for (n in oClassInfo.properties) { + lines.push("
    • {@link " + rname("get", n) + " " + n + "} : " + oClassInfo.properties[n].type + (oClassInfo.properties[n].defaultValue !== null ? " (default: " + oClassInfo.properties[n].defaultValue + ")" : "") + (oClassInfo.defaultProperty == n ? " (default)" : "") + "
    • "); + } + lines.push("
    "); + lines.push("
  • "); + } + if ( !isEmpty(oClassInfo.aggregations) ) { + lines.push("
  • Aggregations"); + lines.push("
      "); + for (n in oClassInfo.aggregations) { + if ( oClassInfo.aggregations[n].visibility !== "hidden" ) { + lines.push("
    • {@link " + rname("get", n) + " " + n + "} : " + makeTypeString(oClassInfo.aggregations[n]) + (oClassInfo.defaultAggregation == n ? " (default)" : "") + "
    • "); + } + } + lines.push("
    "); + lines.push("
  • "); + } + if ( !isEmpty(oClassInfo.associations) ) { + lines.push("
  • Associations"); + lines.push("
      "); + for (n in oClassInfo.associations) { + lines.push("
    • {@link " + rname("get", n) + " " + n + "} : (sap.ui.core.ID | " + oClassInfo.associations[n].type + ")" + (oClassInfo.associations[n].cardinality === "0..n" ? "[]" : "") + "
    • "); + } + lines.push("
    "); + lines.push("
  • "); + } + if ( !isEmpty(oClassInfo.events) ) { + lines.push("
  • Events"); + lines.push("
      "); + for (n in oClassInfo.events) { + lines.push("
    • {@link " + "#event:" + n + " " + n + "} : fnListenerFunction or [fnListenerFunction, oListenerObject] or [oData, fnListenerFunction, oListenerObject]
    • "); + } + lines.push("
    "); + lines.push("
  • "); + } + lines.push("
"); + + // add the reference to the base class only if this is not ManagedObject and if the base class is known + if ( oClassInfo.name !== "sap.ui.base.ManagedObject" && oClassInfo.baseType ) { + lines.push( + "", + "In addition, all settings applicable to the base type {@link " + oClassInfo.baseType + "#constructor " + oClassInfo.baseType + "}", + "can be used as well." + ); + } + lines.push(""); + + } else if ( oClassInfo.name !== "sap.ui.base.ManagedObject" && oClassInfo.baseType && oClassInfo.hasOwnProperty("abstract") ) { + + // if a class has no settings, but metadata, point at least to the base class - if it makes sense + lines.push( + "", + newStyle && !includeSettings ? "@ui5-settings" : "", + "This class does not have its own settings, but all settings applicable to the base type", + "{@link " + oClassInfo.baseType + "#constructor " + oClassInfo.baseType + "} can be used." + ); + + } + } + + debug(" enhancing constructor documentation with settings"); + var enhancedComment = + rawClassComment.slice(0,p) + + "\n * " + removeDuplicateEmptyLines(lines).join("\n * ") + + (commentAlreadyProcessed ? "@ui5-updated-doclet\n * " : "") + + rawClassComment.slice(p); + enhancedComment = preprocessComment({ comment : enhancedComment, lineno : classComment.lineno }); + + if ( commentAlreadyProcessed ) { + jsdocCommentFound(enhancedComment); + } else { + setRawComment(classComment, enhancedComment); + } + + } + + newJSDoc([ + "Returns a metadata object for class " + oClassInfo.name + ".", + "", + "@returns {sap.ui.base.Metadata} Metadata object describing this class", + "@public", + "@static", + "@name " + name("getMetadata", "", true), + "@function" + ]); + + if ( !oClassInfo["final"] ) { + newJSDoc([ + "Creates a new subclass of class " + oClassInfo.name + " with name sClassName", + "and enriches it with the information contained in oClassInfo.", + "", + "oClassInfo might contain the same kind of information as described in {@link " + (oClassInfo.baseType ? oClassInfo.baseType + ".extend" : "sap.ui.base.Object.extend Object.extend") + "}.", + "", + "@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", + "@returns {function} Created class / constructor function", + "@public", + "@static", + "@name " + name("extend", "", true), + "@function" + ]); + } + + for (n in oClassInfo.properties ) { + info = oClassInfo.properties[n]; + if ( info.visibility === 'hidden' ) { + continue; + } + // link = newStyle ? "{@link #setting:" + n + " " + n + "}" : "" + n + ""; + link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; + newJSDoc([ + "Gets current value of property " + link + ".", + "", + !newStyle && info.doc ? info.doc : "", + "", + info.defaultValue !== null ? "Default value is " + (info.defaultValue === "" ? "empty string" : info.defaultValue) + "." : "", + "@returns {" + info.type + "} Value of property " + n + "", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("get",n), + "@function" + ]); + newJSDoc([ + "Sets a new value for property " + link + ".", + "", + !newStyle && info.doc ? info.doc : "", + "", + "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 + "", + "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("set",n), + "@function" + ]); + if ( info.bindable ) { + newJSDoc([ + "Binds property " + link + " to model data.", + "", + "See {@link sap.ui.base.ManagedObject#bindProperty ManagedObject.bindProperty} for a ", + "detailed description of the possible properties of oBindingInfo", + "@param {object} oBindingInfo The binding information", + "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("bind",n), + "@function" + ]); + newJSDoc([ + "Unbinds property " + link + " from model data.", + "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("unbind",n), + "@function" + ]); + } + } + + for (n in oClassInfo.aggregations ) { + info = oClassInfo.aggregations[n]; + if ( info.visibility === 'hidden' ) { + continue; + } + // link = newStyle ? "{@link #setting:" + n + " " + n + "}" : "" + n + ""; + link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; + newJSDoc([ + "Gets content of aggregation " + link + ".", + "", + !newStyle && info.doc ? info.doc : "", + "", + n === info.defaultAggregation ? "Note: this is the default aggregation for " + n + "." : "", + "@returns {" + makeTypeString(info) + "}", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("get",n), + "@function" + ]); + if ( info.cardinality == "0..n" ) { + n1 = info.singularName; + newJSDoc([ + "Inserts a " + n1 + " into the aggregation " + link + ".", + "", + "@param {" + makeTypeString(info, true) + "}", + " " + varname(n1,info.altTypes ? "variant" : info.type) + " The " + n1 + " to insert; if empty, nothing is inserted", + "@param {int}", + " iIndex The 0-based index the " + n1 + " should be inserted at; for", + " a negative value of iIndex, the " + n1 + " is inserted at position 0; for a value", + " greater than the current size of the aggregation, the " + n1 + " is inserted at", + " the last position", + "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("insert",n1), + "@function" + ]); + newJSDoc([ + "Adds some " + n1 + " to the aggregation " + link + ".", + + "@param {" + makeTypeString(info, true) + "}", + " " + varname(n1,info.altTypes ? "variant" : info.type) + " The " + n1 + " to add; if empty, nothing is inserted", + "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("add",n1), + "@function" + ]); + newJSDoc([ + "Removes a " + n1 + " from the aggregation " + link + ".", + "", + "@param {int | string | " + makeTypeString(info, true) + "} " + varname(n1,"variant") + " The " + n1 + " to remove or its index or id", + "@returns {" + makeTypeString(info, true) + "} The removed " + n1 + " or null", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("remove", n1), + "@function" + ]); + newJSDoc([ + "Removes all the controls from the aggregation " + link + ".", + "", + "Additionally, it unregisters them from the hosting UIArea.", + "@returns {" + makeTypeString(info) + "} An array of the removed elements (might be empty)", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("removeAll", n), + "@function" + ]); + newJSDoc([ + "Checks for the provided " + info.type + " in the aggregation " + link + ".", + "and returns its index if found or -1 otherwise.", + "@param {" + makeTypeString(info, true) + "}", + " " + varname(n1, info.altTypes ? "variant" : info.type) + " The " + n1 + " whose index is looked for", + "@returns {int} The index of the provided control in the aggregation if found, or -1 otherwise", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("indexOf", n1), + "@function" + ]); + } else { + newJSDoc([ + "Sets the aggregated " + link + ".", + "@param {" + makeTypeString(info) + "} " + varname(n, info.altTypes ? "variant" : info.type) + " The " + n + " to set", + "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("set", n), + "@function" + ]); + } + newJSDoc([ + "Destroys " + (info.cardinality === "0..n" ? "all " : "") + "the " + n + " in the aggregation " + link + ".", + "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("destroy", n), + "@function" + ]); + if ( info.bindable ) { + newJSDoc([ + "Binds aggregation " + link + " to model data.", + "", + "See {@link sap.ui.base.ManagedObject#bindAggregation ManagedObject.bindAggregation} for a ", + "detailed description of the possible properties of oBindingInfo.", + "@param {object} oBindingInfo The binding information", + "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("bind",n), + "@function" + ]); + newJSDoc([ + "Unbinds aggregation " + link + " from model data.", + "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("unbind",n), + "@function" + ]); + } + } + + for (n in oClassInfo.associations ) { + info = oClassInfo.associations[n]; + if ( info.visibility === 'hidden' ) { + continue; + } + // link = newStyle ? "{@link #setting:" + n + " " + n + "}" : "" + n + ""; + link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; + newJSDoc([ + info.cardinality === "0..n" ? + "Returns array of IDs of the elements which are the current targets of the association " + link + "." : + "ID of the element which is the current target of the association " + link + ", or null.", + "", + newStyle && info.doc ? info.doc : "", + "", + "@returns {sap.ui.core.ID" + (info.cardinality === "0..n" ? "[]" : "") + "}", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("get",n), + "@function" + ]); + if ( info.cardinality === "0..n" ) { + n1 = info.singularName; + newJSDoc([ + "Adds some " + n1 + " into the association " + link + ".", + "", + "@param {sap.ui.core.ID | " + info.type + "} " + varname(n1, "variant") + " The " + n + " to add; if empty, nothing is inserted", + "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("add",n1), + "@function" + ]); + newJSDoc([ + "Removes an " + n1 + " from the association named " + link + ".", + "@param {int | sap.ui.core.ID | " + info.type + "} " + varname(n1,"variant") + " The " + n1 + " to be removed or its index or ID", + "@returns {sap.ui.core.ID} The removed " + n1 + " or null", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("remove", n1), + "@function" + ]); + newJSDoc([ + "Removes all the controls in the association named " + link + ".", + "@returns {sap.ui.core.ID[]} An array of the removed elements (might be empty)", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("removeAll", n), + "@function" + ]); + } else { + newJSDoc([ + "Sets the associated " + link + ".", + "@param {sap.ui.core.ID | " + info.type + "} " + varname(n, info.type) + " ID of an element which becomes the new target of this " + n + " association; alternatively, an element instance may be given", + "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("set", n), + "@function" + ]); + } + } + + for (n in oClassInfo.events ) { + info = oClassInfo.events[n]; + //link = newStyle ? "{@link #event:" + n + " " + n + "}" : "" + n + ""; + link = "{@link #event:" + n + " " + n + "}"; + + lines = [ + info.doc ? info.doc : "", + "", + "@name " + oClassInfo.name + "#" + n, + "@event", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@param {sap.ui.base.Event} oControlEvent", + "@param {sap.ui.base.EventProvider} oControlEvent.getSource", + "@param {object} oControlEvent.getParameters" + ]; + for (pName in info.parameters ) { + lines.push( + "@param {" + (info.parameters[pName].type || "") + "} oControlEvent.getParameters." + pName + " " + (info.parameters[pName].doc || "") + ); + } + lines.push("@public"); + newJSDoc(lines); + + newJSDoc([ + "Attaches event handler fnFunction to the " + link + " event of this " + oClassInfo.name + ".", + "", + "When called, the context of the event handler (its this) will be bound to oListener if specified, ", + "otherwise it will be bound to this " + oClassInfo.name + " itself.", + "", + !newStyle && info.doc ? info.doc : "", + "", + "@param {object}", + " [oData] An application-specific payload object that will be passed to the event handler along with the event object when firing the event", + "@param {function}", + " fnFunction The function to be called when the event occurs", + "@param {object}", + " [oListener] Context object to call the event handler with. Defaults to this " + oClassInfo.name + " itself", + "", + "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", + "@public", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@name " + name("attach", n), + "@function" + ]); + newJSDoc([ + "Detaches event handler fnFunction from the " + link + " event of this " + oClassInfo.name + ".", + "", + "The passed function and listener object must match the ones used for event registration.", + "", + "@param {function}", + " fnFunction The function to be called, when the event occurs", + "@param {object}", + " oListener Context object on which the given function had to be called", + "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@public", + "@name " + name("detach", n), + "@function" + ]); + + // build documentation for fireEvent. It contains conditional parts which makes it a bit more complicated + lines = [ + "Fires event " + link + " to attached listeners." + ]; + if ( info.allowPreventDefault ) { + lines.push( + "", + "Listeners may prevent the default action of this event by using the preventDefault-method on the event object.", + ""); + } + lines.push( + "", + "@param {object} [mParameters] Parameters to pass along with the event" + ); + if ( !isEmpty(info.parameters) ) { + for (pName in info.parameters) { + lines.push( + "@param {" + (info.parameters[pName].type || "any") + "} [mParameters." + pName + "] " + (info.parameters[pName].doc || "") + ); + } + lines.push(""); + } + if ( info.allowPreventDefault ) { + lines.push("@returns {boolean} Whether or not to prevent the default action"); + } else { + lines.push("@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining"); + } + lines.push( + "@protected", + info.since ? "@since " + info.since : "", + info.deprecation ? "@deprecated " + info.deprecation : "", + info.experimental ? "@experimental " + info.experimental : "", + "@name " + name("fire", n), + "@function" + ); + newJSDoc(lines); + } + +} + +function createDataTypeAutoDoc(oTypeInfo, classComment, node, parser, filename) { +} + +/** + * Creates a human readable location info for a given doclet. + * @param doclet + * @returns {String} + */ +function location(doclet) { + var filename = (doclet.meta && doclet.meta.filename) || "unknown"; + return " #" + ui5data(doclet).id + "@" + filename + (doclet.meta.lineno != null ? ":" + doclet.meta.lineno : "") + (doclet.synthetic ? "(synthetic)" : ""); +} + +// ---- Comment handling --------------------------------------------------------------------------- + +// --- comment related functions that depend on the JSdoc version (e.g. on the used parser) + +var isDocComment; +var getLeadingCommentNode; + +// JSDoc added the node type Syntax.File with the same change that activated Babylon +// See https://github.com/jsdoc3/jsdoc/commit/ffec4a42291de6d68e6240f304b68d6abb82a869 +if ( Syntax.File === 'File' ) { + + // JSDoc starting with version 3.5.0 + + isDocComment = function isDocCommentBabylon(comment) { + return comment && comment.type === 'CommentBlock' && comment.value && comment.value.charAt(0) === '*'; + } + + getLeadingCommentNode = function getLeadingCommentNodeBabylon(node, longname) { + var leadingComments = node.leadingComments; + if ( Array.isArray(leadingComments) ) { + // in babylon, all comments are already attached to the node + // and the last one is the closest one and should win + // non-block comments have to be filtered out + leadingComments = leadingComments.filter(isDocComment); + if ( leadingComments.length > 0 ) { + return leadingComments[leadingComments.length - 1]; + } + } + } + +} else { + + // JSDoc versions before 3.5.0 + + isDocComment = function isDoccommentEsprima(comment) { + return comment && comment.type === 'Block'; + }; + + getLeadingCommentNode = function getLeadingCommentNodeEsprima(node, longname) { + var comment, + leadingComments = node.leadingComments; + + // when espree is used, JSDOc attached the leading comment and the first one was picked + if (Array.isArray(leadingComments) && leadingComments.length && leadingComments[0].raw) { + comment = leadingComments[0]; + } + + // also check all comments attached to the Program node (if found) whether they refer to the same longname + // TODO check why any matches here override the direct leading comment from above + if ( longname && currentProgram && currentProgram.leadingComments && currentProgram.leadingComments.length ) { + leadingComments = currentProgram.leadingComments; + var rLongname = new RegExp("@(name|alias|class|namespace)\\s+" + longname.replace(/\./g, '\\.')); + for ( var i = 0; i < leadingComments.length; i++ ) { + var raw = getRawComment(leadingComments[i]); + if ( /^\/\*\*[\s\S]*\*\/$/.test(raw) && rLongname.test(raw) ) { + comment = leadingComments[i]; + // console.log("\n\n**** alternative comment found for " + longname + " on program level\n\n", comment); + break; + } + } + } + + return comment; + } +} + +//--- comment related functions that are independent from the JSdoc version + +function getLeadingComment(node) { + var comment = getLeadingCommentNode(node); + return comment ? getRawComment(comment) : null; +} + +function getLeadingDoclet(node, preprocess) { + var comment = getLeadingComment(node) + if ( comment && preprocess ) { + comment = preprocessComment({comment:comment, lineno: node.loc.start.line }); + } + return comment ? new Doclet(comment, {}) : null; +} + +/** + * Determines the raw comment string (source code form, including leading and trailing comment markers / *...* /) from a comment node. + * Works for Esprima and Babylon based JSDoc versions. + * @param commentNode + * @returns + */ +function getRawComment(commentNode) { + // in esprima, there's a 'raw' property, in babylon, the 'raw' string has to be reconstructed from the 'value' by adding the markers + return commentNode ? commentNode.raw || '/*' + commentNode.value + '*/' : ''; +} + +function setRawComment(commentNode, newRawComment) { + if ( commentNode.raw ) { + commentNode.raw = newRawComment; + } + commentNode.value = newRawComment.slice(2, -2); +} + +/** + * Removes the mandatory comment markers and the optional but common asterisks at the beginning of each JSDoc comment line. + * + * The result is easier to parse/analyze. + * + * Implementation is a 1:1 copy from JSDoc's lib/jsdoc/doclet.js (closure function, not directly reusable) + * + * @param {string} docletSrc the source comment with or without block comment markers + * @returns {string} the unwrapped content of the JSDoc comment + * + */ +function unwrap(docletSrc) { + if (!docletSrc) { return ''; } + + // note: keep trailing whitespace for @examples + // extra opening/closing stars are ignored + // left margin is considered a star and a space + // use the /m flag on regex to avoid having to guess what this platform's newline is + docletSrc = + docletSrc.replace(/^\/\*\*+/, '') // remove opening slash+stars + .replace(/\**\*\/$/, "\\Z") // replace closing star slash with end-marker + .replace(/^\s*(\* ?|\\Z)/gm, '') // remove left margin like: spaces+star or spaces+end-marker + .replace(/\s*\\Z$/g, ''); // remove end-marker + + return docletSrc; +} + +/** + * Inverse operation of unwrap. + * + * The prefix for lines is fixed to be " * ", lines are separated with '\n', independent from the platform. + */ +function wrap(lines) { + if ( typeof lines === "string" ) { + lines = lines.split(/\r\n?|\n/); + } + return "/**\n * " + lines.join('\n * ') + "\n */"; +} + +/** + * Preprocesses a JSDoc comment string to ensure some UI5 standards. + * + * @param {event} e Event for the new comment + * @returns {event} + */ +function preprocessComment(e) { + + var src = e.comment; + + // add a default visibility + if ( !/@private|@public|@protected|@sap-restricted|@ui5-restricted/.test(src) ) { + src = unwrap(src); + src = src + "\n@private"; + src = wrap(src); + // console.log("added default visibility to '" + src + "'"); + } + + if ( /@class/.test(src) && /@static/.test(src) ) { + warning("combination of @class and @static is no longer supported with jsdoc3, converting it to @namespace and @classdesc: (line " + e.lineno + ")"); + src = unwrap(src); + src = src.replace(/@class/, "@classdesc").replace(/@static/, "@namespace"); + src = wrap(src); + //console.log(src); + } + + return src; + +} + +// ---- other functionality --------------------------------------------------------------------------- + +// HACK: override cli.exit() to avoid that JSDoc3 exits the VM +if ( pluginConfig.noExit ) { + info("disabling exit() call"); + require( path.join(global.env.dirname, 'cli') ).exit = function(retval) { + info("cli.exit(): do nothing (ret val=" + retval + ")"); + }; +} + + +// ---- exports ---------------------------------------------------------------------------------------- + +exports.defineTags = function(dictionary) { + + /** + * a special value that is not 'falsy' but results in an empty string when output + * Used for the disclaimer and experimental tag + */ + var EMPTY = { + toString: function() { return ""; } + }; + + /** + * A sapui5 specific tag to add a disclaimer to a symbol + */ + dictionary.defineTag('disclaimer', { + // value is optional + onTagged: function(doclet, tag) { + doclet.disclaimer = tag.value || EMPTY; + } + }); + + /** + * A sapui5 specific tag to mark a symbol as experimental. + */ + dictionary.defineTag('experimental', { + // value is optional + onTagged: function(doclet, tag) { + doclet.experimental = tag.value || EMPTY; + } + }); + + /** + * Re-introduce the deprecated 'final tag. JSDoc used it as a synonym for readonly, but we use it to mark classes as final + */ + dictionary.defineTag('final', { + mustNotHaveValue: true, + onTagged: function(doclet, tag) { + doclet.final_ = true; + } + }); + + /** + * Introduce a new kind of symbol: 'interface' + * 'interface' is like 'class', but without a constructor. + * Support for 'interface' might not be complete (only standard UI5 use cases tested) + */ + dictionary.defineTag('interface', { + //mustNotHaveValue: true, + onTagged: function(doclet, tag) { + // debug("setting kind of " + doclet.name + " to 'interface'"); + doclet.kind = 'interface'; + if ( tag.value ) { + doclet.classdesc = tag.value; + } + } + }); + + /** + * Classes can declare that they implement a set of interfaces + */ + dictionary.defineTag('implements', { + mustHaveValue: true, + onTagged: function(doclet, tag) { + // console.log("setting implements of " + doclet.name + " to 'interface'"); + if ( tag.value ) { + doclet.implements = doclet.implements || []; + tag.value.split(/\s*,\s*/g).forEach(function($) { + if ( doclet.implements.indexOf($) < 0 ) { + doclet.implements.push($); + } + }); + } + } + }); + + /** + * Set the visibility of a doclet to 'restricted'. + */ + dictionary.defineTag('ui5-restricted', { + onTagged: function(doclet, tag) { + doclet.access = 'restricted'; + if ( tag.value ) { + ui5data(doclet).stakeholders = tag.value.trim().split(/(?:\s*,\s*|\s+)/); + } + } + }); + dictionary.defineSynonym('ui5-restricted', 'sap-restricted'); + + /** + * Mark a doclet as synthetic. + * + * Used for doclets that the autodoc generation creates. This helps the template + * later to recognize such doclets and maybe filter them out. + */ + dictionary.defineTag('synthetic', { + mustNotHaveValue: true, + onTagged: function(doclet, tag) { + doclet.synthetic = true; + } + }); + + /** + * Mark a doclet that intentionally updates a previous doclet + */ + dictionary.defineTag('ui5-updated-doclet', { + mustNotHaveValue: true, + onTagged: function(doclet, tag) { + ui5data(doclet).updatedDoclet = true; + } + }); + + /** + * The @hideconstructor tag tells JSDoc that the generated documentation should not display the constructor for a class. + * Note: this tag will be natively available in JSDoc >= 3.5.0 + */ + dictionary.defineTag('hideconstructor', { + mustNotHaveValue: true, + onTagged: function(doclet, tag) { + doclet.hideconstructor = true; + } + }); + +}; + +exports.handlers = { + + /** + * Before all files are parsed, determine the common path prefix of all filenames + */ + parseBegin : function(e) { + + pathPrefixes = env.opts._.reduce(function(result, fileOrDir) { + fileOrDir = path.resolve( path.normalize(fileOrDir) ); + if ( fs.statSync(fileOrDir).isDirectory() ) { + if ( !fileOrDir.endsWith(path.sep) ) { + fileOrDir += path.sep; + } + result.push(fileOrDir); + } + return result; + }, []); + resourceNamePrefixes = pluginConfig.resourceNamePrefixes || []; + if ( !Array.isArray(resourceNamePrefixes) ) { + resourceNamePrefixes = [resourceNamePrefixes]; + } + resourceNamePrefixes.forEach(ensureEndingSlash); + while ( resourceNamePrefixes.length < pathPrefixes.length ) { + resourceNamePrefixes.push(''); + } + + debug("path prefixes " + JSON.stringify(pathPrefixes)); + debug("resource name prefixes " + JSON.stringify(resourceNamePrefixes)); + }, + + /** + * Log each file before it is parsed + */ + fileBegin: function (e) { + currentProgram = undefined; + currentModule = { + name: null, + resource: getResourceName(e.filename), + module: getModuleName(getResourceName(e.filename)), + localNames: Object.create(null) + }; + debug(currentModule); + }, + + fileComplete: function (e) { + currentSource = undefined; + currentProgram = undefined; + currentModule = undefined; + }, + + jsdocCommentFound: function(e) { + // console.log("jsdocCommentFound: " + e.comment); + e.comment = preprocessComment(e); + }, + + symbolFound: function(e) { + // console.log("symbolFound: " + e.comment); + }, + + newDoclet: function(e) { + + var _ui5data = ui5data(e.doclet); + + // remove code: this is a try to reduce the required heap size + if ( e.doclet.meta ) { + if ( e.doclet.meta.code ) { + e.doclet.meta.code = {}; + } + var filepath = (e.doclet.meta.path && e.doclet.meta.path !== 'null' ) ? path.join(e.doclet.meta.path, e.doclet.meta.filename) : e.doclet.meta.filename; + e.doclet.meta.__shortpath = getRelativePath(filepath); + _ui5data.resource = currentModule.resource; + _ui5data.module = currentModule.name || currentModule.module; + } + + + // JSDoc 3 has a bug when it encounters a property in an object literal with an empty string as name + // (e.g. { "" : something } will result in a doclet without longname + if ( !e.doclet.longname ) { + if ( e.doclet.memberof ) { + e.doclet.longname = e.doclet.memberof + "." + e.doclet.name; // TODO '.' depends on scope? + warning("found doclet without longname, derived longname: " + e.doclet.longname + " " + location(e.doclet)); + } else { + error("found doclet without longname, could not derive longname " + location(e.doclet)); + } + return; + } + + // try to detect misused memberof + if ( e.doclet.memberof && e.doclet.longname.indexOf(e.doclet.memberof) !== 0 ) { + warning("potentially unsupported use of @name and @memberof " + location(e.doclet)); + //console.log(e.doclet); + } + + if ( e.doclet.returns + && e.doclet.returns.length > 0 + && e.doclet.returns[0] + && e.doclet.returns[0].type + && e.doclet.returns[0].type.names + && e.doclet.returns[0].type.names[0] === 'this' + && e.doclet.memberof ) { + warning("fixing return type 'this' with " + e.doclet.memberof); + e.doclet.returns[0].type.names[0] = e.doclet.memberof; + } + }, + + beforeParse : function(e) { + msgHeader("parsing " + getRelativePath(e.filename)); + currentSource = e.source; + }, + + parseComplete : function(e) { + + var doclets = e.doclets; + var l = doclets.length,i,j,doclet; + //var noprivate = !env.opts.private; + var rAnonymous = /^(~|$)/; + + // remove undocumented symbols, ignored symbols, anonymous functions and their members, scope members + for (i = 0, j = 0; i < l; i++) { + + doclet = doclets[i]; + if ( !doclet.undocumented && + !doclet.ignore && + !(doclet.memberof && rAnonymous.test(doclet.memberof)) && + doclet.longname.indexOf("~") < 0 ) { + doclets[j++] = doclet; + } + } + if ( j < l ) { + doclets.splice(j, l - j); + info("removed " + (l - j) + " undocumented, ignored or anonymous symbols"); + l = j; + } + + // sort doclets by name, synthetic, lineno, uid + // 'ignore' is a combination of criteria, see function above + debug("sorting doclets by name"); + doclets.sort(function(a,b) { + if ( a.longname === b.longname ) { + if ( a.synthetic === b.synthetic ) { + if ( a.meta && b.meta && a.meta.filename == b.meta.filename ) { + if ( a.meta.lineno !== b.meta.lineno ) { + return a.meta.lineno < b.meta.lineno ? -1 : 1; + } + } + return a.__ui5.id - b.__ui5.id; + } + return a.synthetic && !b.synthetic ? -1 : 1; + } + return a.longname < b.longname ? -1 : 1; + }); + debug("sorting doclets by name done."); + + for (i = 0, j = 0; i < l; i++) { + + doclet = doclets[i]; + + // add metadata to symbol + if ( classInfos[doclet.longname] ) { + doclet.__ui5.metadata = classInfos[doclet.longname]; + + // add designtime infos, if configured + var designtimeModule = doclet.__ui5.metadata.designtime; + if ( designtimeModule && typeof designtimeModule !== 'string' ) { + designtimeModule = doclet.__ui5.module + ".designtime"; + } + if ( designtimeModule && designtimeInfos[designtimeModule] ) { + info("associating designtime data with class metadata: ", designtimeModule); + // TODO do a more generic merge or maybe add whole information as "designtime" information + doclet.__ui5.metadata.annotations = designtimeInfos[designtimeModule].annotations; + } + + // derive extends from UI5 APIs + if ( doclet.__ui5.metadata.baseType + && !(doclet.augments && doclet.augments.length > 0) ) { + doclet.augments = doclet.augments || []; + info(" @extends " + doclet.__ui5.metadata.baseType + " derived from UI5 APIs (" + doclet.longname + ")"); + doclet.augments.push(doclet.__ui5.metadata.baseType); + } + + // derive interface implementations from UI5 metadata + if ( doclet.__ui5.metadata.interfaces && doclet.__ui5.metadata.interfaces.length ) { + doclet.__ui5.metadata.interfaces.forEach(function(intf) { + doclet.implements = doclet.implements || []; + if ( doclet.implements.indexOf(intf) < 0 ) { + info(" @implements " + intf + " derived from UI5 metadata (" + doclet.longname + ")"); + doclet.implements.push(intf); + } + }) + } + } + + if ( typeInfos[doclet.longname] ) { + doclet.__ui5.stereotype = 'datatype'; + doclet.__ui5.metadata = { + basetype: typeInfos[doclet.longname].base, + pattern: typeInfos[doclet.longname].pattern, + range: typeInfos[doclet.longname].range + }; + } + + // check for duplicates: last one wins + if ( j > 0 && doclets[j - 1].longname === doclet.longname ) { + if ( !doclets[j - 1].synthetic && !doclet.__ui5.updatedDoclet ) { + // replacing synthetic comments or updating comments are trivial case. Just log non-trivial duplicates + debug("ignoring duplicate doclet for " + doclet.longname + ":" + location(doclet) + " overrides " + location(doclets[j - 1])); + } + doclets[j - 1] = doclet; + } else { + doclets[j++] = doclet; + } + } + + if ( j < l ) { + doclets.splice(j, l - j); + info("removed " + (l - j) + " duplicate symbols - " + doclets.length + " remaining"); + } + + if ( pluginConfig.saveSymbols ) { + + fs.mkPath(env.opts.destination); + fs.writeFileSync(path.join(env.opts.destination, "symbols-parseComplete.json"), JSON.stringify(e.doclets, null, "\t"), 'utf8'); + + } + + } +}; + +exports.astNodeVisitor = { + + visitNode: function(node, e, parser, currentSourceName) { + + var comment; + + if ( node.type === Syntax.Program ) { + currentProgram = node; + } + + function processExtendCall(extendCall, comment, commentAlreadyProcessed) { + var doclet = comment && new Doclet(getRawComment(comment), {}); + var classInfo = collectClassInfo(extendCall, doclet); + if ( classInfo ) { + createAutoDoc(classInfo, comment, extendCall, parser, currentSourceName, commentAlreadyProcessed); + } + } + + function processDataType(createCall, comment) { + var doclet = comment && new Doclet(getRawComment(comment), {}); + var typeInfo = collectDataTypeInfo(createCall, doclet); + if ( typeInfo ) { + createDataTypeAutoDoc(typeInfo, comment, createCall, parser, currentSourceName); + } + } + + if ( node.type === Syntax.ExpressionStatement ) { + if ( isSapUiDefineCall(node.expression) ) { + analyzeModuleDefinition(node.expression); + /* + } else if ( isJQuerySapDeclareCall(node.expression) + && node.expression.arguments.length > 0 + && node.expression.arguments[0].type === Syntax.Literal + && typeof node.expression.arguments[0].value === "string" ) { + warning("module has explicit module name " + node.expression.arguments[0].value); + */ + } + + } + + if (node.type === Syntax.ReturnStatement && node.argument && node.argument.type === Syntax.ObjectExpression && /\.designtime\.js$/.test(currentSourceName) ) { + + // assume this node to return designtime metadata. Collect it and remember it by its module name + var oDesigntimeInfo = collectDesigntimeInfo(node); + if ( oDesigntimeInfo ) { + designtimeInfos[currentModule.module] = oDesigntimeInfo; + info("collected designtime info " + currentModule.module); + } + + } else if ( node.type === Syntax.ExpressionStatement && isExtendCall(node.expression) ) { + + // Something.extend(...) -- return value (new class) is not used in an assignment + + // className = node.expression.arguments[0].value; + comment = getLeadingCommentNode(node) || getLeadingCommentNode(node.expression); + // console.log("ast node with comment " + comment); + processExtendCall(node.expression, comment); + + } else if ( node.type === Syntax.VariableDeclaration && node.declarations.length == 1 && isExtendCall(node.declarations[0].init) ) { + + // var NewClass = Something.extend(...) + + // className = node.declarations[0].init.arguments[0].value; + comment = getLeadingCommentNode(node) || getLeadingCommentNode(node.declarations[0]); + // console.log("ast node with comment " + comment); + processExtendCall(node.declarations[0].init, comment); + + } else if ( node.type === Syntax.ReturnStatement && isExtendCall(node.argument) ) { + + // return Something.extend(...) + + var className = node.argument.arguments[0].value; + comment = getLeadingCommentNode(node, className) || getLeadingCommentNode(node.argument, className); + // console.log("ast node with comment " + comment); + processExtendCall(node.argument, comment, true); + } else if ( node.type === Syntax.ExpressionStatement && node.expression.type === Syntax.AssignmentExpression && isCreateDataTypeCall(node.expression.right) ) { + + // thisLib.TypeName = DataType.createType( ... ) + comment = getLeadingCommentNode(node) || getLeadingCommentNode(node.expression); + processDataType(node.expression.right); + } + } + +}; diff --git a/lib/processors/jsdoc/ui5/template/publish.js b/lib/processors/jsdoc/ui5/template/publish.js new file mode 100644 index 000000000..5a766771a --- /dev/null +++ b/lib/processors/jsdoc/ui5/template/publish.js @@ -0,0 +1,4056 @@ +/* + * JSDoc3 template for UI5 documentation generation. + * + * (c) Copyright 2009-2018 SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ + +/*global env: true */ +/*eslint strict: [2, "global"]*/ + +"use strict"; + +/* imports */ +var template = require('jsdoc/template'), + helper = require('jsdoc/util/templateHelper'), + fs = require('jsdoc/fs'), + doclet = require('jsdoc/doclet'), + path = require('jsdoc/path'); + +/* globals, constants */ +var MY_TEMPLATE_NAME = "ui5", + ANONYMOUS_LONGNAME = doclet.ANONYMOUS_LONGNAME, + A_SECURITY_TAGS = [ + { + name : "SecSource", + caption : "Taint Source", + description : "APIs that might introduce tainted data into an application, e.g. due to user input or network access", + params : ["out","flags"] + }, + { + name : "SecEntryPoint", + caption : "Taint Entry Point", + description: "APIs that are called implicitly by a framework or server and trigger execution of application logic", + params : ["in","flags"] + }, + { + name : "SecSink", + caption : "Taint Sink", + description : "APIs that pose a security risk when they receive tainted data", + params : ["in","flags"] + }, + { + name : "SecPassthrough", + caption : "Taint Passthrough", + description : "APIs that might propagate tainted data when they receive it as input", + params : ["in","out","flags"] + }, + { + name : "SecValidate", + caption : "Validation", + description : "APIs that (partially) cleanse tainted data so that it no longer poses a security risk in the further data flow of an application", + params : ["in","out","flags"] + } + ]; + +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, + conf = {}, + view; + +var __db; +var __longnames; +var __missingLongnames = {}; + +/** + * Maps the symbol 'longname's to the unique filename that contains the documentation of that symbol. + * This map is maintained to deal with names that only differ in case (e.g. the namespace sap.ui.model.type and the class sap.ui.model.Type). + */ +var __uniqueFilenames = {}; + +function info() { + if ( env.opts.verbose || env.opts.debug ) { + console.log.apply(console, arguments); + } +} + +function warning(msg) { + var args = Array.prototype.slice.apply(arguments); + args[0] = "**** warning: " + args[0]; + console.log.apply(console, args); +} + +function error(msg) { + var args = Array.prototype.slice.apply(arguments); + args[0] = "**** error: " + args[0]; + console.log.apply(console, args); +} + +function debug() { + if ( env.opts.debug ) { + console.log.apply(console, arguments); + } +} + +function merge(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + Object.keys(source).forEach(function(p) { + var v = source[p]; + target[p] = ( v.constructor === Object ) ? merge(target[p] || {}, v) : v; + }); + } + return target; +} + +function lookup(longname /*, variant*/) { + var key = longname; // variant ? longname + "|" + variant : longname; + if ( !Object.prototype.hasOwnProperty.call(__longnames, key) ) { + __missingLongnames[key] = (__missingLongnames[key] || 0) + 1; + var oResult = __db({longname: longname /*, variant: variant ? variant : {isUndefined: true}*/}); + __longnames[key] = oResult.first(); + } + return __longnames[key]; +} + +var externalSymbols = {}; + +function loadExternalSymbols(apiJsonFolder) { + + var files; + + try { + files = fs.readdirSync(templateConf.apiJsonFolder); + } catch (e) { + error("failed to list symbol files in folder '" + apiJsonFolder + "': " + (e.message || e)); + return; + } + + if ( files && files.length ) { + files.forEach(function(localFileName) { + try { + var file = path.join(templateConf.apiJsonFolder, localFileName); + var sJSON = fs.readFileSync(file, 'UTF-8'); + var data = JSON.parse(sJSON); + if ( !Array.isArray(data.symbols) ) { + throw new TypeError("api.json does not contain a 'symbols' array"); + } + data.symbols.forEach(function(symbol) { + debug(" adding external symbol " + symbol.name); + externalSymbols[symbol.name] = symbol; + }); + } catch (e) { + error("failed to load symbols from " + file + ": " + (e.message || e)); + } + }); + } +} + +function isaClass($) { + return /^(namespace|interface|class|typedef)$/.test($.kind) || ($.kind === 'member' && $.isEnum) /* isNonEmptyNamespace($) */; +} + +var REGEXP_ARRAY_TYPE = /^Array\.<(.*)>$/; + +// ---- Version class ----------------------------------------------------------------------------------------------------------------------------------------------------------- + +var Version = (function() { + + var rVersion = /^[0-9]+(?:\.([0-9]+)(?:\.([0-9]+))?)?(.*)$/; + + /** + * Returns a Version instance created from the given parameters. + * + * This function can either be called as a constructor (using new) or as a normal function. + * It always returns an immutable Version instance. + * + * The parts of the version number (major, minor, patch, suffix) can be provided in several ways: + *
    + *
  • Version("1.2.3-SNAPSHOT") - as a dot-separated string. Any non-numerical char or a dot followed by a non-numerical char starts the suffix portion. + * Any missing major, minor or patch versions will be set to 0.
  • + *
  • Version(1,2,3,"-SNAPSHOT") - as individual parameters. Major, minor and patch must be integer numbers or empty, suffix must be a string not starting with digits.
  • + *
  • Version([1,2,3,"-SNAPSHOT"]) - as an array with the individual parts. The same type restrictions apply as before.
  • + *
  • Version(otherVersion) - as a Version instance (cast operation). Returns the given instance instead of creating a new one.
  • + *
+ * + * To keep the code size small, this implementation mainly validates the single string variant. + * All other variants are only validated to some degree. It is the responsibility of the caller to + * provide proper parts. + * + * @param {int|string|any[]|jQuery.sap.Version} vMajor the major part of the version (int) or any of the single parameter variants explained above. + * @param {int} iMinor the minor part of the version number + * @param {int} iPatch the patch part of the version number + * @param {string} sSuffix the suffix part of the version number + * @return {jQuery.sap.Version} the version object as determined from the parameters + * + * @class Represents a version consisting of major, minor, patch version and suffix, e.g. '1.2.7-SNAPSHOT'. + * + * @author SAP SE + * @version ${version} + * @constructor + * @public + * @since 1.15.0 + * @name jQuery.sap.Version + */ + function Version(versionStr) { + + var match = rVersion.exec(versionStr) || []; + + function norm(v) { + v = parseInt(v,10); + return isNaN(v) ? 0 : v; + } + + Object.defineProperty(this, "major", { + enumerable: true, + value: norm(match[0]) + }); + Object.defineProperty(this, "minor", { + enumerable: true, + value: norm(match[1]) + }); + Object.defineProperty(this, "patch", { + enumerable: true, + value: norm(match[2]) + }); + Object.defineProperty(this, "suffix", { + enumerable: true, + value: String(match[3] || "") + }); + + } + + Version.prototype.toMajorMinor = function() { + return new Version(this.major + "." + this.minor); + }; + + Version.prototype.toString = function() { + return this.major + "." + this.minor + "." + this.patch + this.suffix; + }; + + Version.prototype.compareTo = function(other) { + return this.major - other.major || + this.minor - other.minor || + this.patch - other.patch || + ((this.suffix < other.suffix) ? -1 : (this.suffix === other.suffix) ? 0 : 1); + }; + + return Version; + +}()); + +// ---- Link class -------------------------------------------------------------------------------------------------------------------------------------------------------------- + +//TODO move to separate module + +var Link = (function() { + + var Link = function() { + }; + + Link.prototype.toSymbol = function(longname) { + if ( longname != null ) { + longname = String(longname); + if ( /#constructor$/.test(longname) ) { + if ( !this.innerName ) { + this.innerName = 'constructor'; + } + longname = longname.slice(0, -"#constructor".length); + } + this.longname = longname; + } + return this; + }; + + Link.prototype.withText = function(text) { + this.text = text; + return this; + }; + + Link.prototype.withTooltip = function(text) { + this.tooltip = text; + return this; + }; + + Link.prototype.toFile = function(file) { + if ( file != null ) this.file = file; + return this; + }; + + function _makeLink(href, target, tooltip, text) { + return '' + text + ''; + } + + Link.prototype.toString = function() { + var longname = this.longname, + linkString; + + if (longname) { + + if ( /^(?:(?:ftp|https?):\/\/|\.\.?\/)/.test(longname) ) { + // handle real hyperlinks (TODO should be handled with a different "to" method + linkString = _makeLink(longname, this.targetName, this.tooltip, this.text || longname); + } else if ( /^topic:/.test(longname) ) { + // handle documentation links + longname = conf.topicUrlPattern.replace("{{topic}}", longname.slice("topic:".length)); + linkString = _makeLink(longname, this.targetName, this.tooltip, this.text || longname); + } else { + linkString = this._makeSymbolLink(longname); + } + + } else if (this.file) { + linkString = _makeLink(Link.base + this.file, this.targetName, null, this.text || this.file); + } + + return linkString; + }; + + var missingTypes = {}; + Link.getMissingTypes = function() { + return Object.keys(missingTypes); + }; + + Link.prototype._makeSymbolLink = function(longname) { + + // normalize .prototype. and # + longname = longname.replace(/\.prototype\./g, '#'); + + // if it is an internal reference, then don't validate against symbols, just create a link + if ( longname.charAt(0) == "#" ) { + + return _makeLink(longname + (this.innerName ? "#" + this.innerName : ""), this.targetName, this.tooltip, this.text || longname.slice(1)); + + } + + var linkTo = lookup(longname); + // if there is no symbol by that name just return the name unaltered + if ( !linkTo ) { + + missingTypes[longname] = true; + + return this.text || longname; + + } + + // it's a full symbol reference (potentially to another file) + var mainSymbol, anchor; + if ( (linkTo.kind === 'member' && !linkTo.isEnum) || linkTo.kind === 'constant' || linkTo.kind === 'function' || linkTo.kind === 'event' ) { // it's a method or property + + mainSymbol = linkTo.memberof; + anchor = ( linkTo.kind === 'event' ? "event:" : "") + Link.symbolNameToLinkName(linkTo); + + } else { + + mainSymbol = linkTo.longname; + anchor = this.innerName; + + } + + return _makeLink(Link.baseSymbols + __uniqueFilenames[mainSymbol] + conf.ext + (anchor ? "#" + anchor : ""), this.targetName, this.tooltip, this.text || longname); + } + + Link.symbolNameToLinkName = function(symbol) { + var linker = ""; + if ( symbol.scope === 'static' ) { + linker = "."; + } else if (symbol.isInner) { + linker = "-"; // TODO-migrate? + } + return linker + symbol.name; + }; + + return Link; + +}()); + + + +// ---- publish() - main entry point for JSDoc templates ------------------------------------------------------------------------------------------------------- + +/** Called automatically by JsDoc Toolkit. */ +function publish(symbolSet) { + + info("entering sapui5 template"); + + // create output dir + fs.mkPath(env.opts.destination); + +// if ( symbolSet().count() < 20000 ) { +// info("writing raw symbols to " + path.join(env.opts.destination, "symbols-unpruned-ui5.json")); +// fs.writeFileSync(path.join(env.opts.destination, "symbols-unpruned-ui5.json"), JSON.stringify(symbolSet().get(), filter, "\t"), 'utf8'); +// } + + info("before prune: " + symbolSet().count() + " symbols."); + symbolSet = helper.prune(symbolSet); + info("after prune: " + symbolSet().count() + " symbols."); + + __db = symbolSet; + __longnames = {}; + __db().each(function($) { + __longnames[$.longname] = $; + }); + + if ( templateConf.apiJsonFolder ) { + info("loading external apis from folder '" + templateConf.apiJsonFolder + "'"); + loadExternalSymbols(templateConf.apiJsonFolder); + } + + var templatePath = path.join(env.opts.template, 'tmpl/'); + info("using templates from '" + templatePath + "'"); + view = new template.Template(templatePath); + + function filter(key,value) { + if ( key === 'meta' ) { + //return; + } + if ( key === '__ui5' && value ) { + var v = { + resource: value.resource, + module: value.module, + stakeholders: value.stakeholders + }; + if ( value.derived ) { + v.derived = value.derived.map(function($) { return $.longname }); + } + if ( value.base ) { + v.base = value.base.longname; + } + if ( value.implementations ) { + v.base = value.implementations.map(function($) { return $.longname }); + } + if ( value.parent ) { + v.parent = value.parent.longname; + } + if ( value.children ) { + v.children = value.children.map(function($) { return $.longname }); + } + return v; + } + return value; + } + + // now resolve relationships + var aRootNamespaces = createNamespaceTree(); + var hierarchyRoots = createInheritanceTree(); + collectMembers(); + mergeEventDocumentation(); + + if ( symbolSet().count() < 20000 ) { + info("writing raw symbols to " + path.join(env.opts.destination, "symbols-pruned-ui5.json")); + fs.writeFileSync(path.join(env.opts.destination, "symbols-pruned-ui5.json"), JSON.stringify(symbolSet().get(), filter, "\t"), 'utf8'); + } + + // used to allow Link to check the details of things being linked to + Link.symbolSet = symbolSet; + + // get an array version of the symbol set, useful for filtering + var symbols = symbolSet().get(); + + // ----- + + var PUBLISHING_VARIANTS = { + + "apixml" : { + defaults : { + apiXmlFile: path.join(env.opts.destination, "jsapi.xml") + }, + processor : function(conf) { + createAPIXML(symbols, conf.apiXmlFile, { + legacyContent: true + }); + } + }, + + "apijson" : { + defaults : { + apiJsonFile: path.join(env.opts.destination, "api.json") + }, + processor : function(conf) { + createAPIJSON(symbols, conf.apiJsonFile); + } + }, + + "fullapixml" : { + defaults : { + fullXmlFile: path.join(env.opts.destination, "fulljsapi.xml") + }, + processor : function(conf) { + createAPIXML(symbols, conf.fullXmlFile, { + roots: aRootNamespaces, + omitDefaults : conf.omitDefaultsInFullXml, + resolveInheritance: true + }); + } + }, + + "apijs" : { + defaults: { + jsapiFile: path.join(env.opts.destination, "api.js") + }, + processor: function(conf) { + createAPIJS(symbols, conf.jsapiFile); + } + }, + + "full" : { + defaults : { + outdir: path.join(env.opts.destination, "full/"), + contentOnly: false, + hierarchyIndex: true + }, + processor: function() { + publishClasses(symbolSet, aRootNamespaces, hierarchyRoots); + } + }, + + "public" : { + defaults: { + outdir: path.join(env.opts.destination, "public/"), + filter: function($) { return $.access === 'public' || $.access === 'protected' || $.access == null; }, + contentOnly: false, + hierarchyIndex: true + }, + processor: function(conf) { + publishClasses(symbolSet, aRootNamespaces, hierarchyRoots); + } + }, + + "demokit" : { + defaults: { + outdir: path.join(env.opts.destination, "demokit/"), + filter: function($) { return $.access === 'public' || $.access === 'protected' || $.access == null; }, + contentOnly: true, + modulePages: true, + hierarchyIndex: false, + securityIndex: true, + sinceIndex: true, + deprecationIndex: true, + experimentalIndex: true, + suppressAuthor: true, + suppressVersion: true + }, + processor: function(conf) { + publishClasses(symbolSet, aRootNamespaces, hierarchyRoots); + } + }, + + "demokit-internal" : { + defaults: { + outdir: path.join(env.opts.destination, "demokit-internal/"), + // filter: function($) { return $.access === 'public' || $.access === 'protected' || $.access === 'restricted' || $.access == null; }, + contentOnly: true, + modulePages: true, + hierarchyIndex: false, + securityIndex: true, + sinceIndex: true, + deprecationIndex: true, + experimentalIndex: true, + suppressAuthor: true, + suppressVersion: true + }, + processor: function(conf) { + publishClasses(symbolSet, aRootNamespaces, hierarchyRoots); + } + } + + }; + + var now = new Date(); + + info("start publishing"); + for (var i = 0; i < templateConf.variants.length; i++) { + + 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); + + info("done with variant " + vVariant.variant); + + } else { + + info("cannot publish unknown variant '" + vVariant.variant + "' (ignored)"); + + } + } + + var builtinSymbols = templateConf.builtinSymbols; + if ( builtinSymbols ) { + Link.getMissingTypes().filter(function($) { + return builtinSymbols.indexOf($) < 0; + }).sort().forEach(function($) { + error(" unresolved reference: " + $); + }); + } + info("publishing done."); + +} + +//---- namespace tree -------------------------------------------------------------------------------- + +/** + * Completes the tree of namespaces. Namespaces for which content is available + * but which have not been documented are created as dummy without documentation. + */ +function createNamespaceTree() { + + info("create namespace tree (" + __db().count() + " symbols)"); + + var aRootNamespaces = []; + var aTypes = __db(function() { return isaClass(this); }).get(); + + for (var i = 0; i < aTypes.length; i++) { // loop with a for-loop as it can handle concurrent modifications + + var symbol = aTypes[i]; + if ( symbol.memberof ) { + + var parent = lookup(symbol.memberof); + if ( !parent ) { + warning("create missing namespace '" + symbol.memberof + "' (referenced by " + symbol.longname + ")"); + parent = makeNamespace(symbol.memberof); + __longnames[symbol.memberof] = parent; + __db.insert(parent); + aTypes.push(parent); // concurrent modification: parent will be processed later in this loop + } + symbol.__ui5.parent = parent; + parent.__ui5.children = parent.__ui5.children || []; + parent.__ui5.children.push(symbol); + + } else if ( symbol.longname !== ANONYMOUS_LONGNAME ) { + + aRootNamespaces.push(symbol); + + } + } + + return aRootNamespaces; +} + +function makeNamespace(memberof) { + + info("adding synthetic namespace symbol " + memberof); + + var comment = [ + "@name " + memberof, + "@namespace", + "@synthetic", + "@public" + ]; + + var symbol = new doclet.Doclet("/**\n * " + comment.join("\n * ") + "\n */", {}); + symbol.__ui5 = {}; + + return symbol; +} + +//---- inheritance hierarchy ---------------------------------------------------------------------------- + +/** + * Calculates the inheritance hierarchy for all class/interface/namespace symbols. + * Each node in the tree has the content + * + * Node : { + * longname : {string} // name of the node (usually equals symbol.longname) + * symbol : {Symbol} // backlink to the original symbol + * base : {Node} // parent node or undefined for root nodes + * derived : {Node[]} // subclasses/-types + * } + * + */ +function createInheritanceTree() { + + function makeDoclet(longname, lines) { + lines.push("@name " + longname); + var newDoclet = new doclet.Doclet("/**\n * " + lines.join("\n * ") + "\n */", {}); + newDoclet.__ui5 = {}; + __longnames[longname] = newDoclet; + __db.insert(newDoclet); + return newDoclet; + } + + info("create inheritance tree (" + __db().count() + " symbols)"); + + var oTypes = __db(function() { return isaClass(this); }); + var aRootTypes = []; + + var oObject = lookup("Object"); + if ( !oObject ) { + oObject = makeDoclet("Object", [ + "@class", + "@synthetic", + "@public" + ]); + aRootTypes.push(oObject); + } + + function getOrCreateClass(sClass, sExtendingClass) { + var oClass = lookup(sClass); + if ( !oClass ) { + warning("create missing class " + sClass + " (extended by " + sExtendingClass + ")"); + var sBaseClass = 'Object'; + if ( externalSymbols[sClass] ) { + sBaseClass = externalSymbols[sClass].extends || sBaseClass; + } + var oBaseClass = getOrCreateClass(sBaseClass, sClass); + oClass = makeDoclet(sClass, [ + "@extends " + sBaseClass, + "@class", + "@synthetic", + "@public" + ]); + oClass.__ui5.base = oBaseClass; + oBaseClass.__ui5.derived = oBaseClass.__ui5.derived || []; + oBaseClass.__ui5.derived.push(oClass); + } + return oClass; + } + + // link them according to the inheritance infos + oTypes.each(function(oClass) { + + if ( oClass.longname === 'Object') { + return; + } + + var sBaseClass = "Object"; + if ( oClass.augments && oClass.augments.length > 0 ) { + if ( oClass.augments.length > 1 ) { + warning("multiple inheritance detected in " + oClass.longname); + } + sBaseClass = oClass.augments[0]; + } else { + aRootTypes.push(oClass); + } + + var oBaseClass = getOrCreateClass(sBaseClass, oClass.longname); + oClass.__ui5.base = oBaseClass; + oBaseClass.__ui5.derived = oBaseClass.__ui5.derived || []; + oBaseClass.__ui5.derived.push(oClass); + + if ( oClass.implements ) { + for (var j = 0; j < oClass.implements.length; j++) { + var oInterface = lookup(oClass.implements[j]); + if ( !oInterface ) { + warning("create missing interface " + oClass.implements[j]); + oInterface = makeDoclet(oClass.implements[j], [ + "@extends Object", + "@interface", + "@synthetic", + "@public" + ]); + oInterface.__ui5.base = oObject; + oObject.__ui5.derived = oObject.__ui5.derived || []; + oObject.__ui5.derived.push(oInterface); + } + oInterface.__ui5.implementations = oInterface.__ui5.implementations || []; + oInterface.__ui5.implementations.push(oClass); + } + } + }); + + function setStereotype(oSymbol, sStereotype) { + if ( !oSymbol ) { + return; + } + oSymbol.__ui5.stereotype = sStereotype; + var derived = oSymbol.__ui5.derived; + if ( derived ) { + for (var i = 0; i < derived.length; i++ ) { + if ( !derived[i].__ui5.stereotype ) { + setStereotype(derived[i], sStereotype); + } + } + } + } + + setStereotype(lookup("sap.ui.core.Component"), "component"); + setStereotype(lookup("sap.ui.core.Control"), "control"); + setStereotype(lookup("sap.ui.core.Element"), "element"); + setStereotype(lookup("sap.ui.base.Object"), "object"); + + // check for cyclic inheritance (not supported) + // Note: the check needs to run bottom up, not top down as a typical cyclic dependency never will end at the root node + oTypes.each(function(oStartClass) { + var visited = {}; + function visit(oClass) { + if ( visited[oClass.longname] ) { + throw new Error("cyclic inheritance detected: " + JSON.stringify(Object.keys(visited))); + } + if ( oClass.__ui5.base ) { + visited[oClass.longname] = true; + visit(oClass.__ui5.base); + delete visited[oClass.longname]; + } + } + visit(oStartClass); + }); + + // collect root nodes (and ignore pure packages) + return aRootTypes; + /* + return __db(function() { + return R_KINDS.test(this.kind) && this.__ui5 && this.__ui5.base == null; + }).get(); + */ +} + +function collectMembers() { + __db().each(function($) { + if ( $.memberof ) { + var parent = lookup($.memberof); + if ( parent && isaClass(parent) ) { + parent.__ui5.members = parent.__ui5.members || []; + parent.__ui5.members.push($); + } + } + }); +} + +function mergeEventDocumentation() { + + console.log("merging JSDoc event documentation into UI5 metadata"); + + var oTypes = __db(function() { return isaClass(this); }); + + oTypes.each(function(symbol) { + + var metadata = symbol.__ui5.metadata; + var members = symbol.__ui5.members; + + if ( !metadata || !metadata.events || Object.keys(metadata.events).length <= 0 || !members ) { + return; + } + + // console.log('mergeing events for ' + symbol.longname); + members.forEach(function($) { + if ( $.kind === 'event' && !$.inherited + && ($.access === 'public' || $.access === 'protected' || $.access == null) + && metadata.events[$.name] + && Array.isArray($.params) + && !$.synthetic ) { + + var event = metadata.events[$.name]; + var modified = false; + //console.log("<<<<<<<"); + //console.log(event); + //console.log("======="); + //console.log($); + + $.params.forEach(function(param) { + var m = /^\w+\.getParameters\.(.*)$/.exec(param.name); + if ( m ) { + var pname = m[1]; + var ui5param = event.parameters[pname] || ( event.parameters[pname] = {}); + if ( ui5param.type == null ) { + ui5param.type = listTypes(param.type); + modified = true; + } + if ( ui5param.doc == null ) { + ui5param.doc = param.description; + modified = true; + } + } + }); + + if ( modified ) { + console.log(" merged documentation for managed event " + symbol.longname + "#" + $.name); + } + + //console.log("======="); + //console.log(JSON.stringify(event, null, '\t')); + //console.log(">>>>>>>"); + } + }); + + }); + +} + +// ---- publishing ----------------------------------------------------------------------- + +function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { + + // create output dir + fs.mkPath(path.join(conf.outdir, conf.symbolsDir)); + + // get a list of all the classes in the symbolset + var classes = symbols(function() { + return isaClass(this) && conf.filter(this); + }).order("longname"); + + // create unique file names + __uniqueFilenames = {}; + var filenames = {}; + classes.get().sort(sortByAlias).forEach(function(symbol) { + var filename = escape(symbol.longname); + if ( filenames.hasOwnProperty(filename.toUpperCase()) && (filenames[filename.toUpperCase()].longname !== symbol.longname) ) { + // find an unused filename by appending "-n" where n is an integer > 0 + for (var j = 1; filenames.hasOwnProperty(filename.toUpperCase() + "-" + j); j++); + warning("duplicate symbol names " + filenames[filename.toUpperCase()].longname + " and " + symbol.longname + ", renaming the latter to " + filename + "-" + j); + filename = filename + "-" + j; + } + filenames[filename.toUpperCase()] = symbol; + __uniqueFilenames[symbol.longname] = filename; + }); + filenames = null; + + // create a class index, displayed in the left-hand column of every class page + var classTemplate; + if ( !conf.contentOnly ) { + info("create embedded class index"); + Link.base = "../"; + Link.baseSymbols = ""; + classTemplate = 'classWithIndex.html.tmpl'; + publish.header = processTemplate("_header.tmpl", classes); + publish.footer = processTemplate("_footer.tmpl", classes); + publish.classesIndex = processTemplate("_navIndex.tmpl", classes); // kept in memory + } else { + var newStyle = !!pluginConf.newStyle; + classTemplate = newStyle ? "class-new.html.tmpl" : "class.html.tmpl"; + publish.header = ''; + publish.footer = ''; + publish.classesIndex = ''; + + // instead create an index as XML + Link.base = ""; + Link.baseSymbols = conf.symbolsDir; + processTemplateAndSave("index.xml.tmpl", aRootNamespaces, "index.xml"); + } + + // create each of the class pages + info("create class/namespace pages"); + Link.base = "../"; + Link.baseSymbols = ""; + classes.each(function(symbol) { + var sOutName = path.join(conf.symbolsDir, __uniqueFilenames[symbol.longname]) + conf.ext; + processTemplateAndSave(classTemplate, symbol, sOutName); + }); + + if ( conf.modulePages ) { + info("create module pages"); + Link.base = "../"; + Link.baseSymbols = "../" + conf.symbolsDir; + fs.mkPath(path.join(conf.outdir, conf.modulesDir)); + groupByModule(classes.get()).forEach(function(module) { + var sOutName = path.join(conf.modulesDir, module.name.replace(/\//g, '_')) + conf.ext; + processTemplateAndSave("module.html.tmpl", module, sOutName); + }); + } + + // regenerate the index with a different link base, used in the overview pages + info("create global class/namespace index"); + Link.base = ""; + Link.baseSymbols = conf.symbolsDir; + publish.header = processTemplate("_header.tmpl", classes); + publish.footer = processTemplate("_footer.tmpl", classes); + publish.classesIndex = processTemplate("_navIndex.tmpl", classes); + + // create the all classes index + processTemplateAndSave("index.html.tmpl", classes, "index" + conf.ext); + + // create the class hierarchy page + if ( conf.hierarchyIndex ) { + info("create class hierarchy index"); + Link.base = ""; + Link.baseSymbols = conf.symbolsDir; + processTemplateAndSave("hierarchy.html.tmpl", hierarchyRoots.filter(conf.filter), "hierarchy" + conf.ext); + } + + if ( conf.sinceIndex ) { + info("create API by version index"); + Link.base = ""; + Link.baseSymbols = conf.symbolsDir; + var sinceSymbols = symbols(function() { + var r = !!this.since && !this.inherited && conf.filter(this); + if ( r && this.memberof ) { + var parent = lookup(this.memberof); + // filter out symbol when parent is filtered out + if ( !parent || !conf.filter(parent) ) { + debug("since index: filtering out " + this.longname + ", member of " + this.memberof); + r = false; + } + if ( parent && parent.since === this.since ) { + // r = false; + } + } + return r; + }).order("longname"); + processTemplateAndSave("since.html.tmpl", sinceSymbols, "since" + conf.ext); + } + + if ( conf.deprecationIndex ) { + info("create deprecated API index"); + Link.base = ""; + Link.baseSymbols = conf.symbolsDir; + var deprecatedSymbols = symbols(function() { + return !!this.deprecated && !this.inherited && conf.filter(this); + }).order("longname"); + processTemplateAndSave("deprecation.html.tmpl", deprecatedSymbols, "deprecation" + conf.ext); + } + + if ( conf.experimentalIndex ) { + info("create experimental API index"); + Link.base = ""; + Link.baseSymbols = conf.symbolsDir; + var experimentalSymbols = symbols(function() { + return !!this.experimental && !this.inherited && conf.filter(this); + }).order("longname"); + processTemplateAndSave("experimental.html.tmpl", experimentalSymbols, "experimental" + conf.ext); + } + + if ( conf.securityIndex ) { + info("create Security Relevant API index"); + + var securityRelevantSymbols = {}; + A_SECURITY_TAGS.forEach(function(oTagDef) { + securityRelevantSymbols[oTagDef.name.toLowerCase()] = { tag : oTagDef, symbols: [] }; + }); + symbols().each(function($) { + var tags = $.tags; + if ( !$.inherited && conf.filter($) && tags ) { + for (var i = 0; i < tags.length; i++) { + if ( rSecurityTags.test(tags[i].title) ) { + securityRelevantSymbols[tags[i].title.toLowerCase()].symbols.push({ symbol: $, tag : tags[i]}); + } + } + } + }); + + Link.base = ""; + Link.baseSymbols = conf.symbolsDir; + processTemplateAndSave("security.html.tmpl", securityRelevantSymbols, "security" + conf.ext); + } + + classes = null; + + // copy needed mimes + info("copy mimes"); + // copy the template's static files to outdir + var templatePath = env.opts.template; + var fromDir = path.join(templatePath, 'static'); + var staticFiles = fs.ls(fromDir, 3); + staticFiles.forEach(function(fileName) { + var toDir = fs.toDir( fileName.replace(fromDir, conf.outdir) ); + fs.mkPath(toDir); + fs.copyFileSync(fileName, toDir); + }); + + __uniqueFilenames = null; + + info("publishing done."); +} + +// ---- helper functions for the templates ---- + +var rSinceVersion = /^([0-9]+(?:\.[0-9]+(?:\.[0-9]+)?)?([-.][0-9A-Z]+)?)(?:\s|$)/i; + +function extractVersion(value) { + + if ( !value ) { + return; + } + + if ( value === true ) { + value = ''; + } else { + value = String(value); + } + + var m = rSinceVersion.exec(value); + return m ? m[1] : undefined; + +} + +var rSince = /^(?:as\s+of|since)(?:\s+version)?\s*([0-9]+(?:\.[0-9]+(?:\.[0-9]+)?)?([-.][0-9A-Z]+)?)(?:\.$|\.\s+|[,:]\s*|\s-\s*|\s|$)/i; + +function extractSince(value) { + + if ( !value ) { + return; + } + + if ( value === true ) { + value = ''; + } else { + value = String(value); + } + + var m = rSince.exec(value); + if ( m ) { + return { + since : m[1], + pos : m[0].length, + value : value.slice(m[0].length).trim() + } + } + + return { + pos : 0, + value: value.trim() + }; + +} + +function sortByAlias(a, b) { + var partsA = a.longname.split(/[.#]/); + var partsB = b.longname.split(/[.#]/); + var i = 0; + while ( i < partsA.length && i < partsB.length ) { + if ( partsA[i].toLowerCase() < partsB[i].toLowerCase() ) + return -1; + if ( partsA[i].toLowerCase() > partsB[i].toLowerCase() ) + return 1; + i++; + } + if ( partsA.length < partsB.length ) + return -1; + if ( partsA.length > partsB.length ) + return 1; + // as a last resort, try to compare the aliases case sensitive in case we have aliases that only + // differ in case like with "sap.ui.model.type" and "sap.ui.model.Type" + if ( a.longname < b.longname ) { + return -1; + } + if ( a.longname > b.longname ) { + return 1; + } + return 0; +} + +/* +function isNonEmptyNamespace($) { + return $.isNamespace && ( + ($.properties && $.properties.length > 0) || + ($.methods && $.methods.length > 0) || + ($.augments && $.augments.length > 0) || + ($.children && $.children.length > 0)); +};*/ + +/** Just the first sentence (up to a full stop). Should not break on dotted variable names. */ +function summarize(desc) { + if ( desc != null ) { + desc = String(desc).replace(/\s+/g, ' '). + replace(/"'/g, '"'). + replace(/^(<\/?p>||\s)+/, ''); + + var match = /([\w\W]+?\.)[^a-z0-9_$]/i.exec(desc); + return match ? match[1] : desc; + } +} + +/** Make a symbol sorter by some attribute. */ +function makeSortby(/* fields ...*/) { + var aFields = Array.prototype.slice.apply(arguments), + aNorms = [], + aFuncs = []; + for (var i = 0; i < arguments.length; i++) { + aNorms[i] = 1; + if ( typeof aFields[i] === 'function' ) { + aFuncs[i] = aFields[i]; + continue; + } + aFuncs[i] = function($,n) { return $[n]; }; + if ( aFields[i].indexOf("!") === 0 ) { + aNorms[i] = -1; + aFields[i] = aFields[i].slice(1); + } + if ( aFields[i] === 'deprecated' ) { + aFuncs[i] = function($,n) { return !!$[n]; }; + } else if ( aFields[i] === 'static' ) { + aFields[i] = 'scope'; + aFuncs[i] = function($,n) { return $[n] === 'static'; }; + } else if ( aFields[i].indexOf("#") === 0 ) { + aFields[i] = aFields[i].slice(1); + aFuncs[i] = function($,n) { return $.comment.getTag(n).length > 0; }; + } + } + return function(a, b) { + // info("compare " + a.longname + " : " + b.longname); + var r = 0,i,va,vb; + for (i = 0; r === 0 && i < aFields.length; i++) { + va = aFuncs[i](a,aFields[i]); + vb = aFuncs[i](b,aFields[i]); + if ( va && !vb ) { + r = -aNorms[i]; + } else if ( !va && vb ) { + r = aNorms[i]; + } else if ( va && vb ) { + va = String(va).toLowerCase(); + vb = String(vb).toLowerCase(); + if (va < vb) r = -aNorms[i]; + if (va > vb) r = aNorms[i]; + } + // debug(" " + aFields[i] + ": " + va + " ? " + vb + " = " + r); + } + return r; + } +} + +/** Pull in the contents of an external file at the given path. */ + +function processTemplateAndSave(sTemplateName, oData, sOutputName) { + var sResult = processTemplate(sTemplateName, oData); + if ( conf.normalizeWhitespace && /\.html$/.test(sOutputName) ) { + sResult = normalizeWhitespace(sResult); + } + var sOutpath = path.join(conf.outdir, sOutputName); + try { + fs.writeFileSync(sOutpath, sResult, 'utf8'); + } catch (e) { + error("failed to write generated file '" + sOutpath + "':" + (e.message || String(e))); + } +} + +function processTemplate(sTemplateName, data) { + debug("processing template '" + sTemplateName + "' for " + data.longname); + + try { + var result = view.render(sTemplateName, { + asPlainSummary: asPlainSummary, + bySimpleName: bySimpleName, + childrenOfKind: childrenOfKind, + conf: conf, + data: data, + getConstructorDescription : getConstructorDescription, + getNSClass: getNSClass, + groupByVersion: groupByVersion, + extractSince: extractSince, + include: processTemplate, + Link: Link, + listTypes: listTypes, + linkTypes: linkTypes, + makeExample: makeExample, + makeLinkList: makeLinkList, + makeLinkToSymbolFile: makeLinkToSymbolFile, + makeSignature: makeSignature, + makeSortby: makeSortby, + publish : publish, + formatText: formatText, + simpleNameOf: simpleNameOf, + sortByAlias: sortByAlias, + summarize: summarize, + Version : Version + }); + } catch (e) { + if ( e.source ) { + var filename = path.join(env.opts.destination, sTemplateName + ".js"); + console.log("**** failed to process template, source written to " + filename); + fs.mkPath(path.dirname(filename)); + fs.writeFileSync(filename, e.source, 'utf8'); + } + console.log("error while processing " + sTemplateName); + throw e; + } + debug("processing template done."); + return result; +} + +function groupByVersion(symbols, extractVersion) { + + var map = {}; + + symbols.forEach(function(symbol) { + + var version = extractVersion(symbol), + key = String(version); + + if ( !map[key] ) { + map[key] = { version: version, symbols : [] }; + } + map[key].symbols.push(symbol); + + }); + + var groups = Object.keys(map).map(function(key) { return map[key]; }); + + return groups.sort(function(a,b) { + if ( !a.version && b.version ) { + return -1; + } else if ( a.version && !b.version ) { + return 1; + } else if ( a.version && b.version ) { + return -a.version.compareTo(b.version); + } + return 0; + }); +} + +function groupByModule(symbols) { + + var map = {}; + + function add(key, symbol) { + if ( !map[key] ) { + map[key] = { name: key, symbols : [] }; + } + if ( map[key].symbols.indexOf(symbol) < 0 ) { + map[key].symbols.push(symbol); + } + } + + symbols.forEach(function(symbol) { + + var key = symbol.__ui5.module; + + if ( key ) { + add(key, symbol); + if ( symbol.__ui5.members ) { + symbol.__ui5.members.forEach(function($) { + if ( !$.inherited && $.__ui5.module && $.__ui5.module !== key && conf.filter($) ) { + add($.__ui5.module, $); + } + }); + } + } + + }); + + var groups = Object.keys(map).map(function(key) { return map[key]; }); + + return groups; +} + + +var REGEXP_TAG = /<(\/?(?:[A-Z][A-Z0-9_-]*:)?[A-Z][A-Z0-9_-]*)(?:\s[^>]*)?>/gi; + +/** + * Removes unnecessary whitespace from an HTML document: + * - if the text between two adjacent HTML tags consists of whitespace only, the whole text is removed + * - otherwise, any sequence of whitespace in the text is reduced to a single blank + * - inside a
 tag, whitespace is preserved
+ *
+ * Whitespace inside an element tag is not touched (although it could be normalized as well)
+ * @param {string} content raw HTML file
+ * @returns {string} HTML file with normalized whitespace
+ */
+function normalizeWhitespace(content) {
+	var compressed = '',
+		preformatted = 0,
+		p = 0, m, text;
+
+	REGEXP_TAG.lastIndex = 0;
+	while ( m = REGEXP_TAG.exec(content) ) {
+		if ( m.index > p ) {
+			text = content.slice(p, m.index);
+			if ( preformatted ) {
+				compressed += text;
+				// console.log('  "' + text + '" (preformatted)');
+			} else {
+				text = text.replace(/\s+/g,' ');
+				if ( text.trim() ) {
+					compressed += text;
+				}
+				// console.log('  "' + text + '" (trimmed)');
+			}
+		}
+
+		compressed += m[0];
+		// console.log('  "' + m[0] + '" (tag)');
+		p = m.index + m[0].length;
+
+		if ( /^pre$/i.test(m[1]) ) {
+			preformatted++;
+		} else if ( /^\/pre$/i.test(m[1]) && preformatted ) {
+			preformatted--;
+		}
+
+	}
+
+	if ( content.length > p ) {
+		text = content.slice(p, content.length);
+		if ( preformatted ) {
+			compressed += text;
+			// console.log('  "' + text + '" (preformatted)');
+		} else {
+			text = text.replace(/\s+/g,' ');
+			if ( text.trim() ) {
+				compressed += text;
+			}
+			// console.log('  "' + text + '" (trimmed)');
+		}
+	}
+
+	return compressed;
+}
+
+function makeLinkToSymbolFile(longname) {
+	return Link.baseSymbols + __uniqueFilenames[longname] + conf.ext;
+}
+
+function simpleNameOf(longname) {
+	longname = String(longname);
+	var p = longname.lastIndexOf('.');
+	return p < 0 ? longname : longname.slice(p + 1);
+}
+
+function bySimpleName(a,b) {
+	if ( a === b ) {
+		return 0;
+	}
+	var simpleA = simpleNameOf(a);
+	var simpleB = simpleNameOf(b);
+	if ( simpleA === simpleB ) {
+		return a < b ? -1 : 1;
+	} else {
+		return simpleA < simpleB ? -1 : 1;
+	}
+}
+
+/** Build output for displaying function parameters. */
+function makeSignature(params) {
+	var r = ['('], desc;
+	if ( params ) {
+		for (var i = 0, p; p = params[i]; i++) {
+			// ignore @param tags for 'virtual' params that are used to document members of config-like params
+			// (e.g. like "@param param1.key ...")
+			if (p.name && p.name.indexOf('.') == -1) {
+				if (i > 0)
+					r.push(', ');
+
+				r.push('');
+				r.push(p.name);
+				r.push('');
+				if ( p.optional )
+					r.push('?');
+			}
+		}
+	}
+	r.push(')');
+	return r.join('');
+}
+
+
+/*
+ * regexp to recognize important places in the text
+ *
+ * Capturing groups of the RegExp:
+ *   group 1: begin of a pre block
+ *   group 2: end of a pre block
+ *   group 3: begin of a header/ul/ol/table, implicitly ends a paragraph
+ *   group 4: end of a header/ul/ol/table, implicitly starts a new paragraph
+ *   group 5: target portion of an inline @link tag
+ *   group 6: (optional) text portion of an inline link tag
+ *   group 7: an empty line which implicitly starts a new paragraph
+ *
+ *                 [------- 
 block -------] [----------------------- some flow content -----------------------] [---- an inline {@link ...} tag ----] [---------- an empty line ---------]  */
+var rFormatText = /(]*)?>)|(<\/pre>)|(<(?:h[\d+]|ul|ol|table)(?:\s[^>]*)?>)|(<\/(?:h[\d+]|ul|ol|table)>)|\{@link\s+([^}\s]+)(?:\s+([^\}]*))?\}|((?:\r\n|\r|\n)[ \t]*(?:\r\n|\r|\n))/gi;
+
+function formatText(text) {
+
+	if ( !text ) {
+		return '';
+	}
+
+	var inpre = false,
+		paragraphs = 0;
+	
+	text = String(text).replace(rFormatText, function(match, pre, endpre, flow, endflow, linkTarget, linkText, emptyline) {
+		if ( pre ) {
+			inpre = true;
+			return pre.replace(/
/gi, "
").replace(//gi, "
");
+		} else if ( endpre ) {
+			inpre = false;
+		} else if ( flow ) {
+			if ( !inpre ) {
+				paragraphs++;
+				return '

' + match; + } + } else if ( endflow ) { + if ( !inpre ) { + paragraphs++; + return match + '

'; + } + } else if ( emptyline ) { + if ( !inpre ) { + paragraphs++; + return '

'; + } + } else if ( linkTarget ) { + if ( !inpre ) { + // convert to a hyperlink + var link = new Link().toSymbol(linkTarget); + // if link tag contained a replacement text, use it + if ( linkText && linkText.trim()) { + link = link.withText(linkText.trim()); + } + return link.toString(); + } + } + return match; + }); + + if ( paragraphs > 0 ) { + text = '

' + text + '

'; + } + + // remove empty paragraphs + text = text.replace(/

\s*<\/p>/g, ''); + + return text; +} + + +//console.log("#### samples"); +//console.log(formatText(summarize("This is a first\n\nparagraph with empty \n \n \nlines in it. This is the remainder."))); + +function childrenOfKind(data, kind) { + /* old version based on TaffyDB (slow) + var oChildren = symbolSet({kind: kind, memberof: data.longname === GLOBAL_LONGNAME ? {isUndefined: true} : data.longname}).filter(function() { return conf.filter(this); }); + return { + own : oChildren.filter({inherited: {isUndefined:true}}).get().sort(makeSortby("!deprecated","static","name")), + borrowed : groupByContributors(data, oChildren.filter({inherited: true}).get().sort(makeSortby("name"))) + } */ + var oResult = { + own: [], + borrowed: [] + }; + //console.log("calculating kind " + kind + " from " + data.longname); + //console.log(data); + var fnFilter; + switch (kind) { + case 'property': + fnFilter = function($) { + return $.kind === 'constant' || ($.kind === 'member' && !$.isEnum); + } + break; + case 'event': + fnFilter = function($) { + return $.kind === 'event'; + } + break; + case 'method': + fnFilter = function($) { + return $.kind === 'function'; + } + break; + default: + // default: none + fnFilter = function($) { return false; }; + break; + } + + if ( data.__ui5.members ) { + data.__ui5.members.forEach(function($) { + if ( fnFilter($) && conf.filter($) ) { + oResult[$.inherited ? 'borrowed' : 'own'].push($); + } + }); + } + oResult.own.sort(makeSortby("!deprecated","static","name")); + oResult.borrowed = groupByContributors(data, oResult.borrowed); + + return oResult; +} + +/** + * Determines the set of contributors of the given borrowed members. + * The contributors are sorted according to the inheritance hierarchy: + * first the base class of symbol, then the base class of the base class etc. + * Any contributors that can not be found in the hierarchy are appended + * to the set. + * + * @param symbol of which these are the members + * @param borrowedMembers set of borrowed members to determine the contributors for + * @return sorted array of contributors + */ +function groupByContributors(symbol, aBorrowedMembers) { + + var MAX_ORDER = 1000, // a sufficiently large number + mContributors = {}, + aSortedContributors = [], + i,order; + + aBorrowedMembers.forEach(function($) { + $ = lookup($.inherits); + if ($ && mContributors[$.memberof] == null) { + mContributors[$.memberof] = { order : MAX_ORDER, items : [$] }; + } else { + mContributors[$.memberof].items.push($); + } + }); + + // order contributors according to their distance in the inheritance hierarchy + order = 0; + (function handleAugments(oSymbol) { + var i,oTarget,aParentsToVisit; + if ( oSymbol.augments ) { + aParentsToVisit = []; + // first assign an order + for (i = 0; i < oSymbol.augments.length; i++) { + if ( mContributors[oSymbol.augments[i]] != null && mContributors[oSymbol.augments[i]].order === MAX_ORDER ) { + mContributors[oSymbol.augments[i]].order = ++order; + aParentsToVisit.push(oSymbol.augments[i]); + } + } + // only then dive into parents (breadth first search) + for (i = 0; i < aParentsToVisit.length; i++) { + oTarget = lookup(aParentsToVisit); + if ( oTarget ) { + handleAugments(oTarget); + } + } + } + }(symbol)); + + // convert to an array and sort by order + for (i in mContributors) { + aSortedContributors.push(mContributors[i]); + } + aSortedContributors.sort(function (a,b) { return a.order - b.order; }); + + return aSortedContributors; + +} + +function makeLinkList(aSymbols) { + return aSymbols + .sort(makeSortby("name")) + .map(function($) { return new Link().toSymbol($.longname).withText($.name); }) + .join(", "); +} + +// ---- type parsing --------------------------------------------------------------------------------------------- + +function TypeParser(defaultBuilder) { + + /* TODO + * - function(this:) // type of this + * - function(new:) // constructor + */ + var rLexer = /\s*(Array\.?<|Object\.?<|Set\.?<|Promise\.?<|function\(|\{|:|\(|\||\}|>|\)|,|\[\]|\*|\?|!|\.\.\.)|\s*(\w+(?:[.#~]\w+)*)|./g; + + var input, + builder, + token, + tokenStr; + + function next(expected) { + if ( expected !== undefined && token !== expected ) { + throw new SyntaxError("TypeParser: expected '" + expected + "', but found '" + tokenStr + "' (pos: " + rLexer.lastIndex + ", input='" + input + "')"); + } + var match = rLexer.exec(input); + if ( match ) { + tokenStr = match[1] || match[2]; + token = match[1] || (match[2] && 'symbol'); + if ( !token ) { + throw new SyntaxError("TypeParser: unexpected '" + tokenStr + "' (pos: " + match.index + ", input='" + input + "')"); + } + } else { + tokenStr = token = null; + } + } + + function parseType() { + var nullable = false; + var mandatory = false; + if ( token === '?' ) { + next(); + nullable = true; + } else if ( token === '!' ) { + next(); + mandatory = true; + } + + var type; + + if ( token === 'Array.<' || token === 'Array<' ) { + next(); + var componentType = parseType(); + next('>'); + type = builder.array(componentType); + } else if ( token === 'Object.<' || token === 'Object<' ) { + next(); + var keyType; + var valueType = parseType(); + if ( token === ',' ) { + next(); + keyType = valueType; + valueType = parseType(); + } else { + keyType = builder.synthetic(builder.simpleType('string')); + } + next('>'); + type = builder.object(keyType, valueType); + } else if ( token === 'Set.<' || token === 'Set<' ) { + next(); + var elementType = parseType(); + next('>'); + type = builder.set(elementType); + } else if ( token === 'Promise.<' || token === 'Promise<' ) { + next(); + var elementType = parseType(); + next('>'); + type = builder.promise(elementType); + } else if ( token === 'function(' ) { + next(); + var thisType, constructorType, paramTypes = [], returnType; + if ( tokenStr === 'this' ) { + next(); + next(':'); + thisType = parseType(); + if ( token === ',' ) { + next(); + } + } else if ( tokenStr === 'new' ) { + next(); + next(':'); + constructorType = parseType(); + if ( token === ',' ) { + next(); + } + } + while ( token === 'symbol' || token === '...' ) { + var repeatable = token === '...'; + if ( repeatable) { + next(); + } + var paramType = parseType(); + if ( repeatable ) { + paramType = builder.repeatable(paramType); + } + paramTypes.push(paramType); + if ( token === ',' ) { + if ( repeatable ) { + throw new SyntaxError("TypeParser: only the last parameter of a function can be repeatable (pos: " + rLexer.lastIndex + ", input='" + input + "')"); + } + next(); + } + } + next(')'); + if ( token === ':' ) { + next(':'); + returnType = parseType(); + } + type = builder.function(paramTypes, returnType, thisType, constructorType); + } else if ( token === '{' ) { + var structure = Object.create(null); + var propName,propType; + next(); + do { + propName = tokenStr; + if ( !/^\w+$/.test(propName) ) { + throw new SyntaxError("TypeParser: structure field must have a simple name (pos: " + rLexer.lastIndex + ", input='" + input + "', field:'" + propName + "')"); + } + next('symbol'); + if ( token === ':' ) { + next(); + propType = parseType(); + } else { + propType = builder.synthetic(builder.simpleType('any')); + } + structure[propName] = propType; + if ( token === '}' ) { + break; + } + next(','); + } while (token); + next('}'); + type = builder.structure(structure); + } else if ( token === '(' ) { + next(); + type = parseTypes(); + next(')'); + } else if ( token === '*' ) { + next(); + type = builder.simpleType('*'); + } else { + type = builder.simpleType(tokenStr); + next('symbol'); + while ( token === '[]' ) { + next(); + type = builder.array(type); + } + } + if ( nullable ) { + type = builder.nullable(type); + } + if ( mandatory ) { + type = builder.mandatory(type); + } + return type; + } + + function parseTypes() { + var types = []; + do { + types.push(parseType()); + if ( token !== '|' ) { + break; + } + next(); + } while (token); + return types.length === 1 ? types[0] : builder.union(types); + } + + this.parse = function(typeStr, tempBuilder) { + builder = tempBuilder || defaultBuilder || TypeParser.ASTBuilder; + input = String(typeStr); + rLexer.lastIndex = 0; + next(); + var type = parseTypes(); + next(null); + return type; + } + +} + +TypeParser.ASTBuilder = { + simpleType: function(type) { + return { + type: 'simpleType', + name: type + }; + }, + array: function(componentType) { + return { + type: 'array', + component: componentType + }; + }, + object: function(keyType, valueType) { + return { + type: 'object', + key: keyType, + value: valueType + }; + }, + set: function(elementType) { + return { + type: 'set', + element: elementType + }; + }, + promise: function(fulfillmentType) { + return { + type: 'promise', + fulfill: fulfillmentType + }; + }, + function: function(paramTypes, returnType, thisType, constructorType) { + return { + type: 'function', + params: paramTypes, + return: returnType, + this: thisType, + constructor: constructorType + }; + }, + structure: function(structure) { + return { + type: 'structure', + fields: structure + }; + }, + union: function(types) { + return { + type: 'union', + types: types + }; + }, + synthetic: function(type) { + type.synthetic = true; + return type; + }, + nullable: function(type) { + type.nullable = true; + return type; + }, + mandatory: function(type) { + type.mandatory = true; + return type; + }, + repeatable: function(type) { + type.repeatable = true; + return type; + } +}; + +TypeParser.LinkBuilder = function(style, encoded) { + this.linkStyle = style; + this.lt = encoded ? "<" : "<"; + this.gt = encoded ? ">" : ">"; +}; +TypeParser.LinkBuilder.prototype = { + safe: function(type) { + return type.needsParenthesis ? "(" + type.str + ")" : type.str; + }, + simpleType: function(type) { + if ( this.linkStyle === 'text' ) { + return { + str: type + }; + } + var link = new Link().toSymbol(type); + if ( this.linkStyle === 'short' ) { + link.withText(simpleNameOf(type)).withTooltip(type); + } + return { + str: link.toString() + }; + }, + array: function(componentType) { + if ( componentType.needsParenthesis ) { + return { + str: "Array.<" + componentType.str + ">" + }; + } + return { + str: componentType.str + "[]" + }; + }, + object: function(keyType, valueType) { + if ( keyType.synthetic ) { + return { + str: "Object." + this.lt + valueType.str + this.gt + }; + } + return { + str: "Object." + this.lt + keyType.str + "," + valueType.str + this.gt + }; + }, + set: function(elementType) { + return { + str: 'Set.' + this.lt + elementType.str + this.gt + }; + }, + promise: function(fulfillmentType) { + return { + str: 'Promise.' + this.lt + fulfillmentType.str + this.gt + }; + }, + function: function(paramTypes, returnType) { + return { + str: "function(" + paramTypes.map(function(type) { return type.str; }).join(',') + ")" + ( returnType ? " : " + this.safe(returnType) : "") + }; + }, + structure: function(structure) { + var r = []; + for ( var fieldName in structure ) { + if ( structure[fieldName].synthetic ) { + r.push(fieldName); + } else { + r.push(fieldName + ":" + structure[fieldName].str); + } + } + return { + str: "{" + r.join(",") + "}" + }; + }, + union: function(types) { + return { + needsParenthesis: true, + str: types.map( this.safe.bind(this) ).join('|') + }; + }, + synthetic: function(type) { + type.synthetic = true; + return type; + }, + nullable: function(type) { + type.str = "?" + type.str; + return type; + }, + mandatory: function(type) { + type.str = "!" + type.str; + return type; + }, + repeatable: function(type) { + type.str = "..." + type.str; + return type; + } +}; + +var typeParser = new TypeParser(); +var _SHORT_BUILDER = new TypeParser.LinkBuilder('short', true); +var _LONG_BUILDER = new TypeParser.LinkBuilder('long', true); +var _TEXT_BUILDER = new TypeParser.LinkBuilder('text', false); +var _TEXT_BUILDER_ENCODED = new TypeParser.LinkBuilder('text', true); + +/* +function testTypeParser(type) { + console.log("Type: '" + type + "' gives AST"); + try { + console.log(typeParser.parse(type)); + } catch (e) { + console.log("**** throws: " + e); + } +} + +testTypeParser("Array."); +testTypeParser("Array"); +testTypeParser("Object."); +testTypeParser("Object"); +testTypeParser("function(...string):Set"); +testTypeParser("{a:int,b,c:float,d,e}"); +*/ + +function _processTypeString(type, builder) { + if ( type && Array.isArray(type.names) ) { + type = type.names.join('|'); + } + if ( type ) { + try { + return typeParser.parse( type, builder ).str; + } catch (e) { + error("failed to parse type string '" + type + "': " + e); + return type; + } + } +} + +function listTypes(type, encoded) { + return _processTypeString(type, encoded ? _TEXT_BUILDER_ENCODED : _TEXT_BUILDER); +} + +function linkTypes(type, short) { + return _processTypeString(type, short ? _SHORT_BUILDER : _LONG_BUILDER ); +} + +/** + * Reduces the given text to a summary and removes all tags links etc. and escapes double quotes. + * The result therefore should be suitable as content for an HTML tag attribute (e.g. title). + * @param sText + * @return summarized, plain attribute + */ +function asPlainSummary(sText) { + return sText ? summarize(sText).replace(/<.*?>/g, '').replace(/\{\@link\s*(.*?)\}/g, '$1').replace(/"/g,""") : ''; +} + +function getNSClass(item) { + if (item.kind === 'interface') { + return " interface"; + } else if (item.kind === 'namespace') { + return " namespace"; + } else if (item.kind === 'typedef' ) { + return " typedef"; + } else if (item.kind === 'member' && item.isEnum ) { + return " enum"; + } else { + return ""; + } +} + +/* + * regexp to recognize important places in the text + * + * Capturing groups of the RegExp: + * group 1: begin of a pre block + * group 2: end of a pre block + * group 3: an empty line + surrounding whitespace (implicitly starts a new paragraph) + * group 4: an isolated line feed + surrounding whitespace + * + * [-------

 block -------] [---- an empty line and surrounding whitespace ----] [---- new line or whitespaces ----] */
+var rNormalizeText = /(]*)?>)|(<\/pre>)|([ \t]*(?:\r\n|\r|\n)[ \t]*(?:\r\n|\r|\n)[ \t\r\n]*)|([ \t]*(?:\r\n|\r|\n)[ \t]*|[ \t]+)/gi;
+
+function normalizeWS(text) {
+	if ( text == null ) {
+		return text;
+	}
+
+	var inpre = false;
+	return String(text).replace(rNormalizeText, function(match, pre, endpre, emptyline, ws) {
+		if ( pre ) {
+			inpre = true;
+			return pre;
+		} else if ( endpre ) {
+			inpre = false;
+			return endpre;
+		} else if ( emptyline ) {
+			return inpre ? emptyline : '\n\n';
+		} else if ( ws ) {
+			return inpre ? ws : ' ';
+		}
+		return match;
+	});
+
+}
+
+//---- add on: API JSON -----------------------------------------------------------------
+
+function createAPIJSON(symbols, filename) {
+
+	var api = {
+		"$schema-ref": "http://schemas.sap.com/sapui5/designtime/api.json/1.0"
+	}
+
+	if ( templateConf.version ) {
+		api.version = templateConf.version.replace(/-SNAPSHOT$/,"");
+	}
+	if ( templateConf.uilib ) {
+		api.library = templateConf.uilib;
+	}
+
+	api.symbols = [];
+	// sort only a copy(!) of the symbols, otherwise the SymbolSet lookup is broken
+	symbols.slice(0).sort(sortByAlias).forEach(function(symbol) {
+		if ( isaClass(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));
+		}
+	});
+
+	postProcessAPIJSON(api);
+
+	fs.mkPath(path.dirname(filename));
+	fs.writeFileSync(filename, JSON.stringify(api), 'utf8');
+	info("  apiJson saved as " + filename);
+}
+
+function createAPIJSON4Symbol(symbol, omitDefaults) {
+
+	var obj = [];
+	var curr = obj;
+	var attribForKind = 'kind';
+	var stack = [];
+
+	function isEmpty(obj) {
+		if ( !obj ) {
+			return true;
+		}
+		for (var n in obj) {
+			if ( obj.hasOwnProperty(n) ) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	function tag(name, value, omitEmpty) {
+
+		if ( omitEmpty && !value ) {
+			return;
+		}
+		if ( arguments.length === 1 ) { // opening tag
+			stack.push(curr);
+			stack.push(attribForKind);
+			var obj = {};
+			if ( Array.isArray(curr) ) {
+				if ( attribForKind != null ) {
+					obj[attribForKind] = name;
+				}
+				curr.push(obj);
+			} else {
+				curr[name] = obj;
+			}
+			curr = obj;
+			attribForKind = null;
+			return;
+		}
+		if ( value == null ) {
+			curr[name] = true;
+		} else {
+			curr[name] = String(value);
+		}
+	}
+
+	function attrib(name, value, defaultValue, raw) {
+		var emptyTag = arguments.length === 1;
+		if ( omitDefaults && arguments.length >= 3 && value === defaultValue ) {
+			return;
+		}
+		curr[name] = emptyTag ? true : (raw ? value : String(value));
+	}
+
+	function closeTag(name, noIndent) {
+		attribForKind = stack.pop();
+		curr  = stack.pop();
+	}
+
+	function collection(name, attribForKind) {
+		stack.push(curr);
+		stack.push(attribForKind);
+		// TODO only supported if this.curr was an object check or fully implement
+		curr = curr[name] = [];
+		attribForKind = attribForKind || null;
+	}
+
+	function endCollection(name) {
+		attribForKind = stack.pop();
+		curr  = stack.pop();
+	}
+
+	function tagWithSince(name, value) {
+
+		if ( !value ) {
+			return;
+		}
+
+		var info = extractSince(value);
+
+		tag(name);
+		if ( info.since ) {
+			attrib("since", info.since);
+		}
+		if ( info.value ) {
+			curr["text"] = normalizeWS(info.value);
+		}
+		closeTag(name, true);
+
+	}
+
+	function examples(symbol) {
+		var j, example;
+
+		if ( symbol.examples && symbol.examples.length ) {
+			collection("examples");
+			for ( j = 0; j < symbol.examples.length; j++) {
+				example = makeExample(symbol.examples[j]);
+				tag("example");
+				if ( example.caption ) {
+					attrib("caption", example.caption);
+				}
+				attrib("text", example.example);
+				closeTag("example");
+			}
+			endCollection("examples");
+		}
+	}
+
+	function referencesList(symbol) {
+		if ( symbol.see && symbol.see.length ) {
+			curr["references"] = symbol.see.slice();
+		}
+	}
+
+	function visibility($) {
+		if ( $.access === 'protected' ) {
+			return "protected";
+		} else if ( $.access === 'restricted' ) {
+			return "restricted";
+		} else if ( $.access === 'private' ) {
+			return "private";
+		} else {
+			return "public";
+		}
+	}
+
+	function exceptions(symbol) {
+		var array = symbol.exceptions,
+			j, exception;
+		
+		if ( Array.isArray(array) ) {
+			array = array.filter( function (ex) {
+				return (ex.type && listTypes(ex.type)) || (ex.description && ex.description.trim());
+			});
+		} 
+		if ( array == null || array.length === 0 ) {
+			return;
+		}
+		
+		collection("throws");
+		for (j = 0; j < array.length; j++) {
+			exception = array[j];
+			tag("exception");
+			if ( exception.type !== undefined ) {
+				attrib("type", listTypes(exception.type));
+			}
+			tag("description", normalizeWS(exception.description), true);
+			closeTag("exception");
+		}
+		endCollection("throws");
+	}
+
+	function methodList(tagname, methods) {
+		methods = methods && Object.keys(methods).map(function(key) { return methods[key]; });
+		if ( methods != null && methods.length > 0 ) {
+			curr[tagname] = methods;
+		}
+	}
+
+	function interfaceList(tagname, interfaces) {
+		if ( interfaces != null && interfaces.length > 0 ) {
+			curr[tagname] = interfaces.slice();
+		}
+	}
+
+	function hasSettings($, visited) {
+
+		visited = visited || {};
+
+		if ( $.augments && $.augments.length > 0 ) {
+			var baseSymbol = $.augments[0];
+			if ( visited.hasOwnProperty(baseSymbol) ) {
+				error("detected cyclic inheritance when looking at " + $.longname + ": " + JSON.stringify(visited));
+				return false;
+			}
+			visited[baseSymbol] = true;
+			baseSymbol = lookup(baseSymbol) ;
+			if ( hasSettings(baseSymbol, visited) ) {
+				return true;
+			}
+		}
+
+		var metadata = $.__ui5.metadata;
+		return metadata &&
+			(
+				!isEmpty(metadata.specialSettings)
+				|| !isEmpty(metadata.properties)
+				|| !isEmpty(metadata.aggregations)
+				|| !isEmpty(metadata.associations)
+				|| !isEmpty(metadata.annotations)
+				|| !isEmpty(metadata.events)
+			);
+	}
+
+	function writeMetadata($) {
+
+		var metadata = $.__ui5.metadata;
+		if ( !metadata ) {
+			return;
+		}
+
+		var n;
+
+		if ( metadata.specialSettings && Object.keys(metadata.specialSettings).length > 0 ) {
+			collection("specialSettings");
+			for ( n in metadata.specialSettings ) {
+				var special = metadata.specialSettings[n];
+				tag("specialSetting");
+				attrib("name", special.name);
+				attrib("type", special.type);
+				attrib("visibility", special.visibility, 'public');
+				if ( special.since ) {
+					attrib("since", extractVersion(special.since));
+				}
+				tag("description", normalizeWS(special.doc), true);
+				tagWithSince("experimental", special.experimental);
+				tagWithSince("deprecated", special.deprecation);
+				methodList("method", special.methods);
+				closeTag("specialSetting");
+			}
+			endCollection("specialSettings");
+		}
+
+		if ( metadata.properties && Object.keys(metadata.properties).length > 0 ) {
+			collection("properties");
+			for ( n in metadata.properties ) {
+				var prop = metadata.properties[n];
+				tag("property");
+				attrib("name", prop.name);
+				attrib("type", prop.type, 'string');
+				attrib("defaultValue", prop.defaultValue, null, /* raw = */true);
+				attrib("group", prop.group, 'Misc');
+				attrib("visibility", prop.visibility, 'public');
+				if ( prop.since ) {
+					attrib("since", extractVersion(prop.since));
+				}
+				if ( prop.bindable ) {
+					attrib("bindable", prop.bindable, false, /* raw = */true);
+				}
+				tag("description", normalizeWS(prop.doc), true);
+				tagWithSince("experimental", prop.experimental);
+				tagWithSince("deprecated", prop.deprecation);
+				methodList("methods", prop.methods);
+				closeTag("property");
+			}
+			endCollection("properties");
+		}
+
+		if ( metadata.defaultProperty ) {
+			tag("defaultProperty", metadata.defaultProperty);
+		}
+
+		if ( metadata.dnd ) {
+			curr.dnd = metadata.dnd;
+		}
+
+		if ( metadata.aggregations && Object.keys(metadata.aggregations).length > 0 ) {
+			collection("aggregations");
+			for ( n in metadata.aggregations ) {
+				var aggr = metadata.aggregations[n];
+				tag("aggregation");
+				attrib("name", aggr.name);
+				attrib("singularName", aggr.singularName); // TODO omit default?
+				attrib("type", aggr.type, 'sap.ui.core.Control');
+				if ( aggr.altTypes ) {
+					curr.altTypes = aggr.altTypes.slice();
+				}
+				attrib("cardinality", aggr.cardinality, '0..n');
+				attrib("visibility", aggr.visibility, 'public');
+				if ( aggr.since ) {
+					attrib("since", extractVersion(aggr.since));
+				}
+				if ( aggr.bindable ) {
+					attrib("bindable", aggr.bindable, false, /* raw = */true);
+				}
+				if ( aggr.dnd ) {
+					curr.dnd = aggr.dnd;
+				}
+				tag("description", normalizeWS(aggr.doc), true);
+				tagWithSince("experimental", aggr.experimental);
+				tagWithSince("deprecated", aggr.deprecation);
+				methodList("methods", aggr.methods);
+				closeTag("aggregation");
+			}
+			endCollection("aggregations");
+		}
+
+		if ( metadata.defaultAggregation ) {
+			tag("defaultAggregation", metadata.defaultAggregation);
+		}
+
+		if ( metadata.associations && Object.keys(metadata.associations).length > 0 ) {
+			collection("associations");
+			for ( n in metadata.associations ) {
+				var assoc = metadata.associations[n];
+				tag("association");
+				attrib("name", assoc.name);
+				attrib("singularName", assoc.singularName); // TODO omit default?
+				attrib("type", assoc.type, 'sap.ui.core.Control');
+				attrib("cardinality", assoc.cardinality, '0..1');
+				attrib("visibility", assoc.visibility, 'public');
+				if ( assoc.since ) {
+					attrib("since", extractVersion(assoc.since));
+				}
+				tag("description", normalizeWS(assoc.doc), true);
+				tagWithSince("experimental", assoc.experimental);
+				tagWithSince("deprecated", assoc.deprecation);
+				methodList("methods", assoc.methods);
+				closeTag("association");
+			}
+			endCollection("associations");
+		}
+
+		if ( metadata.events && Object.keys(metadata.events).length > 0 ) {
+			collection("events");
+			for ( n in metadata.events ) {
+				var event = metadata.events[n];
+				tag("event");
+				attrib("name", event.name);
+				attrib("visibility", event.visibility, 'public');
+				if ( event.since ) {
+					attrib("since", extractVersion(event.since));
+				}
+				tag("description", normalizeWS(event.doc), true);
+				tagWithSince("experimental", event.experimental);
+				tagWithSince("deprecated", event.deprecation);
+				if ( event.parameters && Object.keys(event.parameters).length > 0 ) {
+					tag("parameters");
+					for ( var pn in event.parameters ) {
+						if ( event.parameters.hasOwnProperty(pn) ) {
+							var param = event.parameters[pn];
+							tag(pn);
+							attrib("name", pn);
+							attrib("type", param.type);
+							if ( param.since ) {
+								attrib("since", extractVersion(param.since));
+							}
+							tag("description", normalizeWS(param.doc), true);
+							tagWithSince("experimental", param.experimental);
+							tagWithSince("deprecated", param.deprecation);
+							closeTag(pn);
+						}
+					}
+					closeTag("parameters");
+				}
+				methodList("methods", event.methods, true);
+				closeTag("event");
+			}
+			endCollection("events");
+		}
+
+		if ( metadata.annotations && Object.keys(metadata.annotations).length > 0 ) {
+			collection("annotations");
+			for ( n in metadata.annotations ) {
+				var anno = metadata.annotations[n];
+				tag("annotation");
+				attrib("name", anno.name);
+				attrib("namespace", anno.namespace);
+				if ( anno.target && anno.target.length > 0 ) {
+					curr.target = anno.target.slice();
+				}
+				attrib("annotation", anno.annotation);
+				attrib("defaultValue", anno.defaultValue);
+				if ( anno.appliesTo && anno.appliesTo.length > 0 ) {
+					curr.appliesTo = anno.appliesTo.slice();
+				}
+				if ( anno.since ) {
+					attrib("since", extractVersion(anno.since));
+				}
+				tag("description", normalizeWS(anno.doc), true);
+				tagWithSince("deprecated", anno.deprecation);
+				closeTag("annotation");
+			}
+			endCollection("annotations");
+		}
+		
+		if ( metadata.designtime ) { // don't write falsy values
+			tag("designtime", metadata.designtime);
+		}
+
+	}
+
+	function writeParameterProperties(paramName, params) {
+		var prefix = paramName + '.',
+			count = 0,
+			i;
+
+		for ( i = 0; i < params.length; i++ ) {
+
+			var name = params[i].name;
+			if ( name.lastIndexOf(prefix, 0) !== 0 ) { // startsWith
+				continue;
+			}
+			name = name.slice(prefix.length);
+			if ( name.indexOf('.') >= 0 ) {
+				continue;
+			}
+
+			if ( count === 0 ) {
+				tag("parameterProperties");
+			}
+
+			count++;
+
+			tag(name);
+			attrib("name", name);
+			attrib("type", listTypes(params[i].type));
+			attrib("optional", !!params[i].optional, false, /* raw = */true);
+			if ( params[i].defaultvalue !== undefined ) {
+				attrib("defaultValue", params[i].defaultvalue, undefined, /* raw = */true);
+			}
+			if ( params[i].since ) {
+				attrib("since", extractVersion(params[i].since));
+			}
+
+			writeParameterProperties(params[i].name, params);
+
+			tag("description", normalizeWS(params[i].description), true);
+			tagWithSince("experimental", params[i].experimental);
+			tagWithSince("deprecated", params[i].deprecated);
+
+			closeTag(name);
+		}
+
+		if ( count > 0 ) {
+			closeTag("parameterProperties");
+		}
+	}
+
+	/*
+	var rSplitSecTag = /^\s*\{([^\}]*)\}/;
+
+	function secTags($) {
+		if ( true ) {
+			return;
+		}
+		var aTags = $.tags;
+		if ( !aTags ) {
+			return;
+		}
+		for (var iTag = 0; iTag < A_SECURITY_TAGS.length; iTag++  ) {
+			var oTagDef = A_SECURITY_TAGS[iTag];
+			for (var j = 0; j < aTags.length; j++ ) {
+				if ( aTags[j].title.toLowerCase() === oTagDef.name.toLowerCase() ) {
+					tag(oTagDef.name);
+					var m = rSplitSecTag.exec(aTags[j].text);
+					if ( m && m[1].trim() ) {
+						var aParams = m[1].trim().split(/\s*\|\s* /); <-- remember to remove the space!
+						for (var iParam = 0; iParam < aParams.length; iParam++ ) {
+							tag(oTagDef.params[iParam], aParams[iParam]);
+						}
+					}
+					var sDesc = aTags[j].description;
+					tag("description", sDesc, true);
+					closeTag(oTagDef.name);
+				}
+			}
+		}
+	}
+	*/
+
+	var kind = (symbol.kind === 'member' && symbol.isEnum) ? "enum" : symbol.kind; // handle pseudo-kind 'enum'
+
+	tag(kind);
+
+	attrib("name", symbol.longname);
+	attrib("basename", symbol.name);
+	if ( symbol.__ui5.resource ) {
+		attrib("resource", symbol.__ui5.resource);
+	}
+	if ( symbol.__ui5.module ) {
+		attrib("module", symbol.__ui5.module);
+		attrib("export", undefined, '', true);
+	}
+	if ( symbol.virtual ) {
+		attrib("abstract", true, false, /* raw = */true);
+	}
+	if ( symbol.final_ ) {
+		attrib("final", true, false, /* raw = */true);
+	}
+	if ( symbol.scope === 'static' ) {
+		attrib("static", true, false, /* raw = */true);
+	}
+	attrib("visibility", visibility(symbol), 'public');
+	if ( symbol.since ) {
+		attrib("since", extractVersion(symbol.since));
+	}
+	if ( symbol.augments && symbol.augments.length ) {
+		tag("extends", symbol.augments.sort().join(",")); // TODO what about multiple inheritance?
+	}
+	interfaceList("implements", symbol.implements);
+	tag("description", normalizeWS(symbol.classdesc || (symbol.kind === 'class' ? '' : symbol.description)), true);
+	tagWithSince("experimental", symbol.experimental);
+	tagWithSince("deprecated", symbol.deprecated);
+	if ( symbol.tags && symbol.tags.some(function(tag) { return tag.title === 'ui5-metamodel'; }) ) {
+		attrib('ui5-metamodel', true, false, /* raw = */true);
+	}
+
+	var i, j, member, param;
+
+	if ( kind === 'class' ) {
+
+		if ( symbol.__ui5.stereotype || hasSettings(symbol) ) {
+
+			tag("ui5-metadata");
+
+			if ( symbol.__ui5.stereotype ) {
+				attrib("stereotype", symbol.__ui5.stereotype);
+			}
+
+			writeMetadata(symbol);
+
+			closeTag("ui5-metadata");
+		}
+
+
+		// IF @hideconstructor tag is present we omit the whole constructor
+		if ( !symbol.hideconstructor ) {
+
+			tag("constructor");
+			attrib("visibility", visibility(symbol));
+			if (symbol.params && symbol.params.length > 0) {
+				collection("parameters");
+				for (j = 0; j < symbol.params.length; j++) {
+					param = symbol.params[j];
+					if (param.name.indexOf('.') >= 0) {
+						continue;
+					}
+					tag("parameter");
+					attrib("name", param.name);
+					attrib("type", listTypes(param.type));
+					attrib("optional", !!param.optional, false, /* raw = */true);
+					if (param.defaultvalue !== undefined) {
+						attrib("defaultValue", param.defaultvalue, undefined, /* raw = */true);
+					}
+					if (param.since) {
+						attrib("since", extractVersion(param.since));
+					}
+
+					writeParameterProperties(param.name, symbol.params);
+					tag("description", normalizeWS(param.description), true);
+					tagWithSince("experimental", param.experimental);
+					tagWithSince("deprecated", param.deprecated);
+					closeTag("parameter");
+				}
+				endCollection("parameters");
+			}
+			exceptions(symbol);
+			tag("description", normalizeWS(symbol.description), true);
+			// tagWithSince("experimental", symbol.experimental); // TODO repeat from class?
+			// tagWithSince("deprecated", symbol.deprecated); // TODO repeat from class?
+			examples(symbol); // TODO here or for class?
+			referencesList(symbol); // TODO here or for class?
+			// secTags(symbol); // TODO repeat from class?
+			closeTag("constructor");
+
+		}
+	} else if ( kind === 'namespace' ) {
+		if ( symbol.__ui5.stereotype || symbol.__ui5.metadata ) {
+			tag("ui5-metadata");
+
+			if ( symbol.__ui5.stereotype ) {
+				attrib("stereotype", symbol.__ui5.stereotype);
+			}
+
+			if ( symbol.__ui5.metadata && symbol.__ui5.metadata.basetype ) {
+				attrib("basetype", symbol.__ui5.metadata.basetype);
+			}
+
+			if ( symbol.__ui5.metadata && symbol.__ui5.metadata.pattern ) {
+				attrib("pattern", symbol.__ui5.metadata.pattern);
+			}
+
+			if ( symbol.__ui5.metadata && symbol.__ui5.metadata.range ) {
+				attrib("range", symbol.__ui5.metadata.range, null, /* raw = */ true);
+			}
+
+			closeTag("ui5-metadata");
+		}
+	}
+
+	var ownProperties = childrenOfKind(symbol, "property").own.sort(sortByAlias);
+	if ( ownProperties.length > 0 ) {
+		collection("properties");
+		for ( i = 0; i < ownProperties.length; i++ ) {
+			member = ownProperties[i];
+			tag("property");
+			attrib("name", member.name);
+			if ( member.__ui5.module && member.__ui5.module !== symbol.__ui5.module ) {
+				attrib("module", member.__ui5.module);
+				attrib("export", undefined, '', true);
+			}
+			attrib("visibility", visibility(member), 'public');
+			if ( member.scope === 'static' ) {
+				attrib("static", true, false, /* raw = */true);
+			}
+			if ( member.since ) {
+				attrib("since", extractVersion(member.since));
+			}
+			attrib("type", listTypes(member.type));
+			tag("description", normalizeWS(member.description), true);
+			tagWithSince("experimental", member.experimental);
+			tagWithSince("deprecated", member.deprecated);
+			examples(member);
+			referencesList(member);
+			if ( member.__ui5.resource && member.__ui5.resource !== symbol.__ui5.resource ) {
+				attrib("resource", member.__ui5.resource);
+			}
+			closeTag("property");
+		}
+		endCollection("properties");
+	}
+
+	var ownEvents = childrenOfKind(symbol, 'event').own.sort(sortByAlias);
+	if ( ownEvents.length > 0 ) {
+		collection("events");
+		for (i = 0; i < ownEvents.length; i++ ) {
+			member = ownEvents[i];
+			tag("event");
+			attrib("name", member.name);
+			if ( member.__ui5.module && member.__ui5.module !== symbol.__ui5.module ) {
+				attrib("module", member.__ui5.module);
+				attrib("export", undefined, '', true);
+			}
+			attrib("visibility", visibility(member), 'public');
+			if ( member.scope === 'static' ) {
+				attrib("static", true, false, /* raw = */true);
+			}
+			if ( member.since ) {
+				attrib("since", extractVersion(member.since));
+			}
+
+			if ( member.params && member.params.length > 0 ) {
+				collection("parameters");
+				for (j = 0; j < member.params.length; j++) {
+					param = member.params[j];
+					if ( param.name.indexOf('.') >= 0 ) {
+						continue;
+					}
+
+					tag("parameter");
+					attrib("name", param.name);
+					attrib("type", listTypes(param.type));
+					if ( param.since ) {
+						attrib("since", extractVersion(param.since));
+					}
+					writeParameterProperties(param.name, member.params);
+					tag("description", normalizeWS(param.description), true);
+					tagWithSince("experimental", param.experimental);
+					tagWithSince("deprecated", param.deprecated);
+					closeTag("parameter");
+				}
+				endCollection("parameters");
+			}
+			tag("description", normalizeWS(member.description), true);
+			tagWithSince("deprecated", member.deprecated);
+			tagWithSince("experimental", member.experimental);
+			examples(member);
+			referencesList(member);
+			//secTags(member);
+			if ( member.__ui5.resource && member.__ui5.resource !== symbol.__ui5.resource ) {
+				attrib("resource", member.__ui5.resource);
+			}
+			closeTag("event");
+		}
+		endCollection("events");
+	}
+
+	var ownMethods = childrenOfKind(symbol, 'method').own.sort(sortByAlias);
+	if ( ownMethods.length > 0 ) {
+		collection("methods");
+		for ( i = 0; i < ownMethods.length; i++ ) {
+			member = ownMethods[i];
+			tag("method");
+			attrib("name", member.name);
+			if ( member.__ui5.module && member.__ui5.module !== symbol.__ui5.module ) {
+				attrib("module", member.__ui5.module);
+				attrib("export", undefined, '', true);
+			}
+			attrib("visibility", visibility(member), 'public');
+			if ( member.scope === 'static' ) {
+				attrib("static", true, false, /* raw = */true);
+			}
+			if ( member.tags && member.tags.some(function(tag) { return tag.title === 'ui5-metamodel'; }) ) {
+				attrib('ui5-metamodel', true, false, /* raw = */true);
+			}
+
+			var returns = member.returns && member.returns.length && member.returns[0];
+			var type = member.type || (returns && returns.type);
+			type = listTypes(type);
+			//if ( type && type !== 'void' ) {
+			//	attrib("type", type, 'void');
+			//}
+			if ( type && type !== 'void' || returns && returns.description ) {
+				tag("returnValue");
+				if ( type && type !== 'void' ) {
+					attrib("type", type);
+				}
+				if ( returns && returns.description ) {
+					attrib("description", normalizeWS(returns.description));
+				}
+				closeTag("returnValue");
+			}
+			if ( member.since ) {
+				attrib("since", extractVersion(member.since));
+			}
+
+			if ( member.params && member.params.length > 0 ) {
+				collection("parameters");
+				for ( j = 0; j < member.params.length; j++) {
+					param = member.params[j];
+					if ( param.name.indexOf('.') >= 0 ) {
+						continue;
+					}
+					tag("parameter");
+					attrib("name", param.name);
+					attrib("type", listTypes(param.type));
+					attrib("optional", !!param.optional, false, /* raw = */true);
+					if ( param.defaultvalue !== undefined ) {
+						attrib("defaultValue", param.defaultvalue, undefined, /* raw = */true);
+					}
+					if ( param.since ) {
+						attrib("since", extractVersion(param.since));
+					}
+					writeParameterProperties(param.name, member.params);
+					tag("description", normalizeWS(param.description), true);
+					tagWithSince("experimental", param.experimental);
+					tagWithSince("deprecated", param.deprecated);
+					closeTag("parameter");
+				}
+				endCollection("parameters");
+			}
+			exceptions(member);
+			tag("description", normalizeWS(member.description), true);
+			tagWithSince("experimental", member.experimental);
+			tagWithSince("deprecated", member.deprecated);
+			examples(member);
+			referencesList(member);
+			//secTags(member);
+			if ( member.__ui5.resource && member.__ui5.resource !== symbol.__ui5.resource ) {
+				attrib("resource", member.__ui5.resource);
+			}
+			closeTag("method");
+		}
+		endCollection("methods");
+	}
+
+//	if ( roots && symbol.__ui5.children && symbol.__ui5.children.length ) {
+//		collection("children", "kind");
+//		symbol.__ui5.children.forEach(writeSymbol);
+//		endCollection("children");
+//	}
+
+	closeTag(kind);
+
+	return obj[0];
+}
+
+function postProcessAPIJSON(api) {
+	var modules = {};
+	var symbols = api.symbols;
+	var i,j,n,symbol,defaultExport;
+	
+	// collect modules and the symbols that refer to them 
+	for ( i = 0; i < symbols.length; i++) {
+		symbol = symbols[i];
+		if ( symbol.module ) {
+			modules[symbol.module] = modules[symbol.module] || [];
+			modules[symbol.module].push({
+				name: symbol.name,
+				symbol: symbol
+			});
+		}
+		if ( symbol.properties ) {
+			for ( j = 0; j < symbol.properties.length; j++ ) {
+				if ( symbol.properties[j].static && symbol.properties[j].module ) {
+					modules[symbol.properties[j].module] = modules[symbol.properties[j].module] || [];
+					modules[symbol.properties[j].module].push({
+						name: symbol.name + "." + symbol.properties[j].name,
+						symbol: symbol.properties[j]
+					});
+				}
+			}
+		}
+		if ( symbol.methods ) {
+			for ( j = 0; j < symbol.methods.length; j++ ) {
+				if ( symbol.methods[j].static && symbol.methods[j].module ) {
+					modules[symbol.methods[j].module] = modules[symbol.methods[j].module] || [];
+					modules[symbol.methods[j].module].push({
+						name: symbol.name + "." + symbol.methods[j].name,
+						symbol: symbol.methods[j]
+					});
+				}
+			}
+		}
+	}
+	
+	function guessExport(defaultExport, symbol) {
+		if ( symbol.name === defaultExport ) {
+			// default export equals the symbol name
+			symbol.symbol.export = ""; 
+			//console.log("    (default):" + defaultExport);
+		} else if ( symbol.name.lastIndexOf(defaultExport + ".", 0) === 0 ) {
+			// default export is a prefix of the symbol name
+			symbol.symbol.export = symbol.name.slice(defaultExport.length + 1); 
+			//console.log("    " + symbol.name.slice(defaultExport.length + 1) + ":" + symbol.name);
+		} else {
+			// default export is not a prefix of the symbol name -> no way to access it in AMD 
+			symbol.symbol.export = undefined;
+			console.log("    **** could not identify module export for API " + symbol.name);
+		}
+	}
+	
+	for ( n in modules ) {
+		
+		symbols = modules[n].sort(function(a,b) {
+			if ( a.name === b.name ) {
+				return 0;
+			}
+			return a.name < b.name ? -1 : 1;
+		});
+		
+		// console.log('  resolved exports of ' + n + ": " + symbols.map(function(symbol) { return symbol.name; } ));
+		if ( /^jquery\.sap\./.test(n) ) {
+			// the jquery.sap.* modules all export 'jQuery'.
+			// any API from those modules is reachable via 'jQuery.*'
+			defaultExport = 'jQuery';
+			symbols.forEach(
+				guessExport.bind(this, defaultExport)
+			);
+		} else if ( /\/library$/.test(n) ) {
+			// library.js modules export the library namespace
+			defaultExport = n.replace(/\/library$/, "").replace(/\//g, ".");
+			if ( symbols.some(function(symbol) { return symbol.name === defaultExport; }) ) {
+				// if there is a symbol for the namespace, then all other symbols from the module should be sub-exports of that symbol
+				symbols.forEach(
+					guessExport.bind(this, defaultExport)
+				);
+			} else {
+				// otherwise, we don't know how to map it to an export
+				symbols.forEach(function(symbol) {
+					symbol.symbol.export = symbol.name;
+					console.log("    **** unresolved " + symbol.name + " in library.js (no export that matches module name)");
+				});
+			}
+		} else {
+			// for all other modules, the assumed default export is identical to the name of the module (converted to a 'dot' name)
+			defaultExport = n.replace(/\//g, ".");
+			if ( symbols.some(function(symbol) { return symbol.name === defaultExport; }) ) {
+				symbols.forEach(
+					guessExport.bind(this, defaultExport)
+				);
+			//} else if ( symbols.length === 1 && (symbols[0].symbol.kind === 'class' || symbols[0].symbol.kind === 'namespace') ) {
+				// if there is only one symbol and if that symbol is of type class or namespace, assume it is the default export
+				// TODO is that assumption safe? Was only done because of IBarPageEnabler (which maybe better should be fixed in the JSDoc)
+				//symbols[0].symbol.export = '';
+			} else {
+				symbols.forEach(function(symbol) {
+					symbol.symbol.export = undefined;
+					console.log("    **** unresolved " + symbol.name + " (no export that matches module name)");
+				});
+			}
+		}
+	}
+}
+
+//---- add on: API XML -----------------------------------------------------------------
+
+function createAPIXML(symbols, filename, options) {
+
+	options = options || {};
+	var roots = options.roots || null;
+	var legacyContent = !!options.legacyContent;
+	var omitDefaults = !!options.omitDefaults;
+	var addRedundancy = !!options.resolveInheritance;
+
+	var indent = 0;
+	var output = [];
+	var sIndent = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
+	var tags = [];
+	var ENUM = legacyContent ? "namespace" : "enum" ;
+	var BASETYPE = legacyContent ? "baseType" : "extends";
+	var PROPERTY = legacyContent ? "parameter" : "property";
+	var unclosedStartTag = false;
+
+	function getAPIJSON(name) {
+
+		var symbol = lookup(name);
+		if ( symbol && !symbol.synthetic ) {
+			return createAPIJSON4Symbol(symbol, false);
+		}
+		if ( addRedundancy && externalSymbols[name] ) {
+			debug("  using " + name + " from external dependency");
+			return externalSymbols[name];
+		}
+		return symbol;
+	}
+
+	function encode(s) {
+		return s ? s.replace(/&/g, "&").replace(/ 0 )
+			output.push(sIndent.slice(0,indent));
+		if ( arguments.length ) {
+			for (var i = 0; i < arguments.length; i++)
+				output.push(arguments[i]);
+		}
+		output.push("\n");
+	}
+
+	function rootTag(name) {
+		tags = [];
+		unclosedStartTag = false;
+		tag(name);
+	}
+
+	function closeRootTag(name) {
+		closeTag(name);
+	}
+
+	function namespace(alias, namespace) {
+		attrib(alias, namespace);
+	}
+
+	function tag(name, value, omitEmpty) {
+
+		if ( omitEmpty && !value ) {
+			return;
+		}
+		if ( unclosedStartTag ) {
+			unclosedStartTag = false;
+			write('>\n');
+		}
+		if ( arguments.length === 1 ) { // opening tag
+			if ( indent > 0 ) {
+				output.push(sIndent.slice(0,indent));
+			}
+			write("<", name);
+			unclosedStartTag = true;
+			if ( legacyContent ) {
+				unclosedStartTag = false;
+				write(">\n");
+			}
+			tags.push(name);
+			indent++;
+			return;
+		}
+		if ( value == null ) {
+			writeln("<", name, "/>");
+		} else {
+			writeln("<", name, ">", encode(String(value)), "");
+		}
+	}
+
+	function attrib(name, value, defaultValue) {
+		var emptyTag = arguments.length === 1;
+		if ( omitDefaults && arguments.length === 3 && value === defaultValue ) {
+			return;
+		}
+
+		if ( !legacyContent ) {
+			write(" " + name + "=\"");
+			write(emptyTag ? "true" : encode(String(value)).replace(/"/g, """));
+			write("\"");
+		} else {
+			if ( emptyTag ) {
+				writeln("<", name, "/>");
+			} else {
+				writeln("<", name, ">", encode(String(value)), "");
+			}
+		}
+	}
+
+	function closeTag(name, noIndent) {
+
+		indent--;
+		var top = tags.pop();
+		if ( top != name ) {
+			// ERROR?
+		}
+
+		if ( unclosedStartTag ) {
+			unclosedStartTag = false;
+			write("/>\n");
+		} else if ( noIndent ) {
+			write("\n");
+		} else {
+			writeln("");
+		}
+	}
+
+	function textContent(text) {
+		if ( unclosedStartTag ) {
+			unclosedStartTag = false;
+			write('>');
+		}
+		write(encode(text));
+	}
+
+	function tagWithSince(tagName, prop) {
+		if ( prop ) {
+			tag(tagName);
+			if ( prop.since ) {
+				attrib("since", prop.since);
+			}
+			if ( prop.text && prop.text.trim() ) {
+				textContent(prop.text);
+			}
+			closeTag(tagName, true);
+		}
+	}
+
+	function getAsString() {
+		return output.join("");
+	}
+
+	function writeMetadata(symbolAPI, inherited) {
+
+		var ui5Metadata = symbolAPI["ui5-metadata"];
+		if ( !ui5Metadata ) {
+			return;
+		}
+
+		if ( addRedundancy && symbolAPI["extends"] ) {
+			var baseSymbolAPI = getAPIJSON(symbolAPI["extends"]);
+			if ( baseSymbolAPI ) {
+				writeMetadata(baseSymbolAPI, true);
+			}
+		}
+
+		if ( ui5Metadata.specialSettings ) {
+			ui5Metadata.specialSettings.forEach(function(special) {
+				tag("specialSetting");
+				attrib("name", special.name);
+				attrib("type", special.type);
+				attrib("visibility", special.visibility, 'public');
+				if ( special.since ) {
+					attrib("since", special.since);
+				}
+				if ( inherited ) {
+					attrib("origin", symbolAPI.name);
+				}
+				tag("description", special.description, true);
+				tagWithSince("experimental", special.experimental);
+				tagWithSince("deprecated", special.deprecated);
+				tag("methods", special.methods);
+				closeTag("specialSetting");
+			});
+		}
+
+		if ( ui5Metadata.properties ) {
+			ui5Metadata.properties.forEach(function(prop) {
+				tag("property");
+				attrib("name", prop.name);
+				attrib("type", prop.type, 'string');
+				if ( prop.defaultValue !== null ) {
+					attrib("defaultValue", prop.defaultValue, null);
+				}
+				attrib("visibility", prop.visibility, 'public');
+				if ( prop.since ) {
+					attrib("since", prop.since);
+				}
+				if ( prop.bindable ) {
+					attrib("bindable", prop.bindable);
+				}
+				if ( inherited ) {
+					attrib("origin", symbolAPI.name);
+				}
+				tag("description", prop.description, true);
+				tagWithSince("experimental", prop.experimental);
+				tagWithSince("deprecated", prop.deprecated);
+				tag("methods", prop.methods);
+				closeTag("property");
+			});
+		}
+
+		if ( ui5Metadata.defaultProperty ) {
+			tag("defaultProperty", ui5Metadata.defaultProperty);
+		}
+
+		if ( ui5Metadata.aggregations ) {
+			ui5Metadata.aggregations.forEach(function(aggr) {
+				tag("aggregation");
+				attrib("name", aggr.name);
+				attrib("singularName", aggr.singularName); // TODO omit default?
+				attrib("type", aggr.type, 'sap.ui.core.Control');
+				if ( aggr.altTypes ) {
+					attrib("altTypes", aggr.altTypes.join(","));
+				}
+				attrib("cardinality", aggr.cardinality, '0..n');
+				attrib("visibility", aggr.visibility, 'public');
+				if ( aggr.since ) {
+					attrib("since", aggr.since);
+				}
+				if ( aggr.bindable ) {
+					attrib("bindable", aggr.bindable);
+				}
+				if ( inherited ) {
+					attrib("origin", symbolAPI.name);
+				}
+				tag("description", aggr.description, true);
+				tagWithSince("experimental", aggr.experimental);
+				tagWithSince("deprecated", aggr.deprecated);
+				tag("methods", aggr.methods);
+				closeTag("aggregation");
+			});
+		}
+
+		if ( ui5Metadata.defaultAggregation ) {
+			tag("defaultAggregation", ui5Metadata.defaultAggregation);
+		}
+
+		if ( ui5Metadata.associations ) {
+			ui5Metadata.associations.forEach(function(assoc) {
+				tag("association");
+				attrib("name", assoc.name);
+				attrib("singularName", assoc.singularName); // TODO omit default?
+				attrib("type", assoc.type, 'sap.ui.core.Control');
+				attrib("cardinality", assoc.cardinality, '0..1');
+				attrib("visibility", assoc.visibility, 'public');
+				if ( assoc.since ) {
+					attrib("since", assoc.since);
+				}
+				if ( inherited ) {
+					attrib("origin", symbolAPI.name);
+				}
+				tag("description", assoc.description, true);
+				tagWithSince("experimental", assoc.experimental);
+				tagWithSince("deprecated", assoc.deprecated);
+				tag("methods", assoc.methods);
+				closeTag("association");
+			});
+		}
+
+		if ( ui5Metadata.events ) {
+			ui5Metadata.events.forEach(function(event) {
+				tag("event");
+				attrib("name", event.name);
+				attrib("visibility", event.visibility, 'public');
+				if ( event.since ) {
+					attrib("since", event.since);
+				}
+				if ( inherited ) {
+					attrib("origin", symbolAPI.name);
+				}
+				tag("description", event.description, true);
+				tagWithSince("experimental", event.experimental);
+				tagWithSince("deprecated", event.deprecated);
+				if ( event.parameters ) {
+					tag("parameters");
+					for ( var pn in event.parameters ) {
+						if ( event.parameters.hasOwnProperty(pn) ) {
+							var param = event.parameters[pn];
+
+							tag("parameter");
+							attrib("name", param.name);
+							attrib("type", param.type);
+							if ( param.since ) {
+								attrib("since", param.since);
+							}
+							tag("description", param.description, true);
+							tagWithSince("experimental", param.experimental);
+							tagWithSince("deprecated", param.deprecated);
+							closeTag("parameter");
+						}
+					}
+					closeTag("parameters");
+				}
+				tag("methods", event.methods, true);
+				closeTag("event");
+			});
+		}
+
+		if ( ui5Metadata.annotations ) {
+			ui5Metadata.annotations.forEach(function(anno) {
+				tag("annotation");
+				attrib("name", anno.name);
+				attrib("namespace", anno.namespace); // TODO omit default?
+				attrib("target", anno.target);
+				attrib("annotation", anno.annotation);
+				attrib("appliesTo", anno.appliesTo);
+				if ( anno.since ) {
+					attrib("since", anno.since);
+				}
+				tag("description", anno.description, true);
+				tagWithSince("deprecated", anno.deprecated);
+				closeTag("annotation");
+			});
+		}
+
+	}
+
+	function writeParameterPropertiesForMSettings(symbolAPI, inherited) {
+
+		var ui5Metadata = symbolAPI["ui5-metadata"];
+		if ( !ui5Metadata ) {
+			return;
+		}
+
+		if ( symbolAPI["extends"] ) {
+			var baseSymbolAPI = getAPIJSON(symbolAPI["extends"]);
+			writeParameterPropertiesForMSettings(baseSymbolAPI, true);
+		}
+
+		if ( ui5Metadata.specialSettings ) {
+			ui5Metadata.specialSettings.forEach(function(special) {
+				if ( special.visibility !== 'hidden' ) {
+					tag("property");
+					attrib("name", special.name);
+					attrib("type", special.type);
+					attrib("optional");
+					if ( inherited ) {
+						attrib("origin", symbolAPI.name);
+					}
+					tag("description", special.description, true);
+					closeTag("property");
+				}
+			});
+		}
+
+		if ( ui5Metadata.properties ) {
+			ui5Metadata.properties.forEach(function(prop) {
+				tag("property");
+				attrib("name", prop.name);
+				attrib("type", prop.type);
+				attrib("group", prop.group, 'Misc');
+				if ( prop.defaultValue !== null ) {
+					attrib("defaultValue", typeof prop.defaultValue === 'string' ? "\"" + prop.defaultValue + "\"" : prop.defaultValue);
+				}
+				attrib("optional");
+				if ( inherited ) {
+					attrib("origin", symbolAPI.name);
+				}
+				tag("description", prop.description, true);
+				closeTag("property");
+			});
+		}
+
+		if ( ui5Metadata.aggregations ) {
+			ui5Metadata.aggregations.forEach(function(aggr) {
+				if ( aggr.visibility !== "hidden" ) {
+					tag("property");
+					attrib("name", aggr.name);
+					attrib("type", aggr.type + (aggr.cardinality === '0..1' ? "" : "[]"));
+					if ( aggr.altTypes ) {
+						attrib("altTypes", aggr.altTypes.join(","));
+					}
+					attrib("optional");
+					if ( inherited ) {
+						attrib("origin", symbolAPI.name);
+					}
+					tag("description", aggr.description, true);
+					closeTag("property");
+				}
+			});
+		}
+
+		if ( ui5Metadata.associations ) {
+			ui5Metadata.associations.forEach(function(assoc) {
+				if ( assoc.visibility !== "hidden" ) {
+					tag("property");
+					attrib("name", assoc.name);
+					attrib("type", "(" + assoc.type + "|" + "string)" + (assoc.cardinality === '0..1' ? "" : "[]"));
+					attrib("optional");
+					if ( inherited ) {
+						attrib("origin", symbolAPI.name);
+					}
+					tag("description", assoc.description, true);
+					closeTag("property");
+				}
+			});
+		}
+
+		if ( ui5Metadata.events ) {
+			ui5Metadata.events.forEach(function(event) {
+				tag("property");
+				attrib("name", event.name);
+				attrib("type", "function|array");
+				attrib("optional");
+				if ( inherited ) {
+					attrib("origin", symbolAPI.name);
+				}
+				tag("description", event.description, true);
+				closeTag("property");
+			});
+		}
+
+	}
+
+	function writeParameterProperties(param, paramName) {
+		var props = param.parameterProperties,
+			prefix = paramName + '.',
+			count = 0;
+
+		if ( props ) {
+			for (var n in props ) {
+				if ( props.hasOwnProperty(n) ) {
+
+					param = props[n];
+
+					if ( !legacyContent && count === 0 ) {
+						tag("parameterProperties");
+					}
+
+					count++;
+
+					tag(PROPERTY);
+					attrib("name", legacyContent ? prefix + n : n);
+					attrib("type", param.type);
+					if ( param.since ) {
+						attrib("since", param.since);
+					}
+					if ( param.optional ) {
+						attrib("optional", param.optional);
+					}
+
+					if ( !legacyContent ) {
+						writeParameterProperties(param, prefix + n);
+					}
+
+					tag("description", param.description, true);
+					tagWithSince("experimental", param.experimental);
+					tagWithSince("deprecated", param.deprecated);
+
+					closeTag(PROPERTY);
+
+					if ( legacyContent ) {
+						writeParameterProperties(param, prefix + n);
+					}
+				}
+			}
+		}
+
+		if ( !legacyContent && count > 0 ) {
+			closeTag("parameterProperties");
+		}
+	}
+
+	/*
+	var rSplitSecTag = /^\s*\{([^\}]*)\}/;
+
+	function secTags($) {
+		if ( !legacyContent ) {
+			return;
+		}
+		var aTags = $.tags;
+		if ( !aTags ) {
+			return;
+		}
+		for (var iTag = 0; iTag < A_SECURITY_TAGS.length; iTag++  ) {
+			var oTagDef = A_SECURITY_TAGS[iTag];
+			for (var j = 0; j < aTags.length; j++ ) {
+				if ( aTags[j].title.toLowerCase() === oTagDef.name.toLowerCase() ) {
+					tag(oTagDef.name);
+					var m = rSplitSecTag.exec(aTags[j].text);
+					if ( m && m[1].trim() ) {
+						var aParams = m[1].trim().split(/\s*\|\s* /); <-- remove the blank!
+						for (var iParam = 0; iParam < aParams.length; iParam++ ) {
+							tag(oTagDef.params[iParam], aParams[iParam]);
+						}
+					}
+					var sDesc = aTags[j].description;
+					tag("description", sDesc, true);
+					closeTag(oTagDef.name);
+				}
+			}
+		}
+	}
+	*/
+
+	function writeSymbol(symbol) {
+
+		var kind;
+
+		if ( isaClass(symbol) && (roots || !symbol.synthetic) ) { // dump a symbol if it as a class symbol and if either hierarchies are dumped or if it is not a synthetic symbol
+
+			// for the hierarchy we use only the local information
+			var symbolAPI = createAPIJSON4Symbol(symbol);
+
+			kind = symbolAPI.kind === 'enum' ? ENUM : symbolAPI.kind;
+
+			tag(kind);
+
+			attrib("name", symbolAPI.name);
+			attrib("basename", symbolAPI.basename);
+//			if ( symbolAPI["resource"] ) {
+//				attrib("resource");
+//			}
+			if ( symbolAPI["module"] ) {
+				attrib("module", symbolAPI["module"]);
+			}
+			if ( symbolAPI["abstract"] ) {
+				attrib("abstract");
+			}
+			if ( symbolAPI["final"] ) {
+				attrib("final");
+			}
+			if ( symbolAPI["static"] ) {
+				attrib("static");
+			}
+			attrib("visibility", symbolAPI.visibility, 'public');
+			if ( symbolAPI.since ) {
+				attrib("since", symbolAPI.since);
+			}
+			if ( symbolAPI["extends"] ) {
+				tag(BASETYPE, symbolAPI["extends"]); // TODO what about multiple inheritance?
+			}
+			tag("description", symbolAPI.description, true);
+			tagWithSince("experimental", symbolAPI.experimental);
+			tagWithSince("deprecated", symbolAPI.deprecated);
+
+			if ( kind === 'class' ) {
+
+				var hasSettings = symbolAPI["ui5-metadata"];
+
+				if ( !legacyContent && symbolAPI["ui5-metadata"] ) {
+
+					tag("ui5-metadata");
+
+					if ( symbolAPI["ui5-metadata"].stereotype ) {
+						attrib("stereotype", symbolAPI["ui5-metadata"].stereotype);
+					}
+
+					writeMetadata(symbolAPI);
+
+					closeTag("ui5-metadata");
+
+				}
+
+				tag("constructor");
+				if ( legacyContent ) {
+					attrib("name", symbolAPI.basename);
+				}
+				attrib("visibility", symbolAPI.visibility, 'public');
+				if ( symbolAPI.constructor.parameters ) {
+					symbolAPI.constructor.parameters.forEach(function(param, j) {
+
+						tag("parameter");
+						attrib("name", param.name);
+						attrib("type", param.type);
+						attrib("optional", param.optional, false);
+						if ( param.defaultValue !== undefined ) {
+							attrib("defaultValue", param.defaultValue);
+						}
+						if ( param.since ) {
+							attrib("since", param.since);
+						}
+
+						if ( !legacyContent ) {
+							if ( hasSettings && j == 1 && /setting/i.test(param.name) && /object/i.test(param.type) ) {
+								if ( addRedundancy ) {
+									tag("parameterProperties");
+									writeParameterPropertiesForMSettings(symbolAPI);
+									closeTag("parameterProperties");
+								}
+							} else {
+								writeParameterProperties(param, param.name);
+							}
+						}
+						tag("description", param.description, true);
+						tagWithSince("experimental", param.experimental);
+						tagWithSince("deprecated", param.deprecated);
+						closeTag("parameter");
+						if ( legacyContent ) {
+							writeParameterProperties(param, param.name);
+						}
+					});
+				}
+
+				tag("description", getConstructorDescription(symbol), true);
+				// tagWithSince("experimental", symbol.experimental); // TODO repeat from class?
+				// tagWithSince("deprecated", symbol.deprecated); // TODO repeat from class?
+				// secTags(symbol); // TODO repeat from class?
+				closeTag("constructor");
+			}
+
+			/* TODO MIGRATE or remove, if not needed
+			var ownSubspaces = ( symbol.__ui5.children || [] ).filter(function($) { return $.kind === 'namespace' }).sort(sortByAlias);
+			for (var i=0; i");
+	rootTag("api");
+	if ( !legacyContent ) {
+		namespace("xmlns", "http://www.sap.com/sap.ui.library.api.xsd");
+		attrib("_version", "1.0.0");
+		if ( templateConf.version ) {
+			attrib("version", templateConf.version.replace(/-SNAPSHOT$/,""));
+		}
+		if ( templateConf.uilib ) {
+			attrib("library", templateConf.uilib);
+		}
+	}
+
+	if ( roots ) {
+		roots.forEach(writeSymbol);
+	} else {
+		// sort only a copy(!) of the symbols, otherwise the SymbolSet lookup is broken
+		symbols.slice(0).sort(sortByAlias).forEach(writeSymbol);
+	}
+
+	closeRootTag("api");
+
+	fs.mkPath(path.dirname(filename));
+	fs.writeFileSync(filename, getAsString(), 'utf8');
+}
+
+//---- add on: API JS -----------------------------------------------------------------
+
+function createAPIJS(symbols, filename) {
+
+	var output = [];
+
+	var rkeywords = /^(?:abstract|as|boolean|break|byte|case|catch|char|class|continue|const|debugger|default|delete|do|double|else|enum|export|extends|false|final|finally|float|for|function|goto|if|implements|import|in|instanceof|int|interface|is|long|namespace|native|new|null|package|private|protected|public|return|short|static|super|switch|synchronized|this|throw|throws|transient|true|try|typeof|use|var|void|volatile|while|with)$/;
+
+	function isNoKeyword($) { return !rkeywords.test($.name); }
+
+	function isAPI($) { return $.access === 'public' || $.access === 'protected' || !$.access }
+
+	function writeln(args) {
+		if ( arguments.length ) {
+			for (var i = 0; i < arguments.length; i++)
+				output.push(arguments[i]);
+		}
+		output.push("\n");
+	}
+
+	function unwrap(docletSrc) {
+		if (!docletSrc) { return ''; }
+
+		// note: keep trailing whitespace for @examples
+		// extra opening/closing stars are ignored
+		// left margin is considered a star and a space
+		// use the /m flag on regex to avoid having to guess what this platform's newline is
+		docletSrc =
+			docletSrc.replace(/^\/\*\*+/, '') // remove opening slash+stars
+			.replace(/\**\*\/$/, "\\Z")       // replace closing star slash with end-marker
+			.replace(/^\s*(\* ?|\\Z)/gm, '')  // remove left margin like: spaces+star or spaces+end-marker
+			.replace(/\s*\\Z$/g, '');         // remove end-marker
+
+		return docletSrc;
+	}
+
+	function comment($, sMetaType) {
+
+		var s = unwrap($.comment.toString());
+
+		// remove the @desc tag
+		s = s.replace(/(\r\n|\r|\n)/gm, "\n");
+		s = s.replace(/^\s*@desc\s*/gm, "");
+		s = s.replace(/^\s*@alias[^\r\n]*(\r\n|\r|\n)?/gm, "");
+		s = s.replace(/^\s*@name[^\r\n]*(\r\n|\r|\n)?/gm, "");
+		s = s.replace(/^\s*@function[^\r\n]*(\r\n|\r|\n)?/gm, "");
+		s = s.replace(/^\s*@author[^\r\n]*(\r\n|\r|\n)?/gm, "");
+		s = s.replace(/^\s*@synthetic[^\r\n]*(\r\n|\r|\n)?/gm, "");
+		s = s.replace(/^\s*<\/p>

\s*(\r\n|\r|\n)?/gm, "\n"); + // skip empty documentation + if ( !s ) return; + + // for namespaces, enforce the @.memberof tag + if ( sMetaType === "namespace" && $.memberof && s.indexOf("@memberof") < 0 ) { + s = s + "\n@memberof " + $.memberof; + } + + writeln("/**\n * " + s.replace(/\n/g, "\n * ") + "\n */"); + + /* + writeln("/**"); + writeln(s.split(/\r\n|\r|\n/g).map(function($) { return " * " + $;}).join("\r\n")); + writeln(" * /"); + */ + + } + + function signature($) { + var p = $.params, + r = [], + i; + if ( p ) { + for (i = 0; i < p.length; i++) { + // ignore @param tags for 'virtual' params that are used to document members of config-like params + // (e.g. like "@param param1.key ...") + if (p[i].name && p[i].name.indexOf('.') < 0) { + r.push(p[i].name); + } + } + } + return r.join(','); + } + + function qname(member,parent) { + var r = member.memberof; + if ( member.scope !== 'static' ) { + r += ".prototype"; + } + return (r ? r + "." : "") + member.name; + } + + var mValues = { + "boolean" : "false", + "int" : "0", + "float" : "0.0", + "number" : "0.0", + "string" : "\"\"", + "object" : "new Object()", + "function" : "function() {}" + }; + + function valueForType(type) { + if ( type && type.names && type.names[0] ) { + type = type.names[0]; + if ( REGEXP_ARRAY_TYPE.test(type) || type.indexOf("[]") > 0 ) { + return "new Array()"; + } else if ( mValues[type] ) { + return mValues[type]; + } else if ( type.indexOf(".") > 0 ) { + return "new " + type + "()"; + } else { + // return "/* unsupported type: " + member.type + " */ null"; + return "null"; + } + } + } + + function value(member) { + return valueForType(member.type); + } + + function retvalue(member) { + //console.log(member); + var r = valueForType(member.type || (member.returns && member.returns.length && member.returns[0] && member.returns[0].type && member.returns[0].type)); + if ( r ) { + return "return " + r + ";"; + } + return ""; + } + + var sortedSymbols = symbols.slice(0).filter(function($) { return isaClass($) && isAPI($) && !$.synthetic; }).sort(sortByAlias); // sort only a copy(!) of the symbols, otherwise the SymbolSet lookup is broken + sortedSymbols.forEach(function(symbol) { + + var sMetaType = (symbol.kind === 'member' && symbol.isEnum) ? 'enum' : symbol.kind; + if ( sMetaType ) { + + writeln(""); + writeln("// ---- " + symbol.longname + " --------------------------------------------------------------------------"); + writeln(""); + + var memberId, member; + + var ownProperties = childrenOfKind(symbol, 'property').own.filter(isNoKeyword).sort(sortByAlias); + if ( sMetaType === "class" ) { + comment(symbol, sMetaType); + writeln(symbol.longname + " = function(" + signature(symbol) + ") {};"); + for ( memberId in ownProperties ) { + member = ownProperties[memberId]; + comment(member, sMetaType); + writeln(qname(member, symbol) + " = " + value(member)); + writeln(""); + } + } else if ( sMetaType === 'namespace' || sMetaType === 'enum' ) { + //console.log("found namespace " + symbol.longname); + //console.log(ownProperties); + if ( ownProperties.length ) { + writeln("// dummy function to make Eclipse aware of namespace"); + writeln(symbol.longname + ".toString = function() { return \"\"; };"); + } + } + + var ownEvents = childrenOfKind(symbol, 'event').own.filter(isNoKeyword).sort(sortByAlias); + if ( ownEvents.length ) { + for ( memberId in ownEvents ) { + member = ownEvents[memberId]; + comment(member, sMetaType); + writeln(qname(member, symbol) + " = function(" + signature(member) + ") { " + retvalue(member) + " };"); + writeln(""); + } + } + + var ownMethods = childrenOfKind(symbol, 'method').own.filter(isNoKeyword).sort(sortByAlias); + if ( ownMethods.length ) { + for ( memberId in ownMethods ) { + member = ownMethods[memberId]; + comment(member, sMetaType); + writeln(qname(member, symbol) + " = function(" + signature(member) + ") { " + retvalue(member) + " };"); + writeln(""); + } + } + + } + }); + + writeln("// ---- static fields of namespaces ---------------------------------------------------------------------"); + + sortedSymbols.forEach(function(symbol) { + + var sMetaType = (symbol.kind === 'member' && symbol.isEnum) ? 'enum' : symbol.kind; + + if ( sMetaType === 'namespace' || sMetaType === 'enum' ) { + + var ownProperties = childrenOfKind(symbol, 'property').own.filter(isNoKeyword).sort(sortByAlias); + if ( ownProperties.length ) { + writeln(""); + writeln("// ---- " + symbol.longname + " --------------------------------------------------------------------------"); + writeln(""); + + for (var memberId in ownProperties ) { + var member = ownProperties[memberId]; + comment(member, sMetaType); + writeln(qname(member, symbol) + " = " + value(member) + ";"); + writeln(""); + } + } + } + + }); + + fs.mkPath(path.dirname(filename)); + fs.writeFileSync(filename, output.join(""), 'utf8'); + info(" saved as " + filename); +} + +// Description + Settings + +function getConstructorDescription(symbol) { + var description = symbol.description; + var tags = symbol.tags; + if ( tags ) { + for (var i = 0; i < tags.length; i++) { + if ( tags[i].title === "ui5-settings" && tags[i].text) { + description += "\n

\n" + tags[i].text; + break; + } + } + } + return description; +} + + +// Example + +function makeExample(example) { + var result = { + caption: null, + example: example + }, + match = /^\s*([\s\S]+?)<\/caption>(?:[ \t]*[\n\r]*)([\s\S]+)$/i.exec(example); + + if ( match ) { + result.caption = match[1]; + result.example = match[2]; + } + + return result; +} + +/* ---- exports ---- */ + +exports.publish = publish; + diff --git a/lib/tasks/createJSDoc.js b/lib/tasks/createJSDoc.js new file mode 100644 index 000000000..f7e4d9c69 --- /dev/null +++ b/lib/tasks/createJSDoc.js @@ -0,0 +1,29 @@ +const runJSDoc = require("../processors/jsdoc/jsdoc"); + +/** + * Task to create dbg files. + * + * @module builder/tasks/createDebugFiles + * @param {Object} parameters Parameters + * @param {DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {Object} [parameters.options] Options + * @param {string} [parameters.options.pattern] Pattern to locate the files to be processed + * @returns {Promise} Promise resolving with undefined once data has been written + */ +module.exports = async function({workspace, options}) { + let allResources; + if (workspace.byGlobSource) { // API only available on duplex collections + allResources = await workspace.byGlobSource(options.pattern); + } else { + allResources = await workspace.byGlob(options.pattern); + } + return runJSDoc({ + resources: allResources, + options + }).then((createdResources) => { + console.log(createdResources); + return Promise.all(createdResources.map((resource) => { + return workspace.write(resource); + })); + }); +}; diff --git a/lib/tasks/taskRepository.js b/lib/tasks/taskRepository.js index 2b1285c5b..3afffd6e0 100644 --- a/lib/tasks/taskRepository.js +++ b/lib/tasks/taskRepository.js @@ -2,6 +2,7 @@ const tasks = { replaceCopyright: require("./replaceCopyright"), replaceVersion: require("./replaceVersion"), createDebugFiles: require("./createDebugFiles"), + createJSDoc: require("./createJSDoc"), uglify: require("./uglify"), buildThemes: require("./buildThemes"), transformBootstrapHtml: require("./transformBootstrapHtml"), diff --git a/lib/types/library/LibraryBuilder.js b/lib/types/library/LibraryBuilder.js index 98fb709b4..f38f6117a 100644 --- a/lib/types/library/LibraryBuilder.js +++ b/lib/types/library/LibraryBuilder.js @@ -8,6 +8,7 @@ const tasks = { // can't require index.js due to circular dependency generateStandaloneAppBundle: require("../../tasks/bundlers/generateStandaloneAppBundle"), buildThemes: require("../../tasks/buildThemes"), createDebugFiles: require("../../tasks/createDebugFiles"), + createJSDoc: require("../../tasks/createJSDoc"), generateLibraryManifest: require("../../tasks/generateLibraryManifest"), generateVersionInfo: require("../../tasks/generateVersionInfo"), replaceCopyright: require("../../tasks/replaceCopyright"), @@ -39,6 +40,20 @@ class LibraryBuilder extends AbstractBuilder { }); }); + this.addTask("createJSDoc", () => { + const createJSDoc = tasks.createJSDoc; + return createJSDoc({ + workspace: resourceCollections.workspace, + options: { + libraryName: project.metadata.name, + version: project.version, + pattern: "/resources/**/*.js" + } + }).then(() => { + console.log("createJSDOC done"); + }); + }); + const componentPreload = project.builder && project.builder.componentPreload; if (componentPreload) { const generateComponentPreload = tasks.generateComponentPreload; @@ -75,6 +90,9 @@ class LibraryBuilder extends AbstractBuilder { options: { projectName: project.metadata.name } + }).catch((err) => { + console.log("generateLibraryPreload failed:", err); + throw err; }); }); diff --git a/package-lock.json b/package-lock.json index bf9846c57..00dd8d9f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1376,8 +1376,7 @@ "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", - "dev": true + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" }, "boolbase": { "version": "1.0.0", @@ -1616,7 +1615,6 @@ "version": "0.8.9", "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.9.tgz", "integrity": "sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is=", - "dev": true, "requires": { "underscore-contrib": "~0.3.0" } @@ -2084,7 +2082,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, "requires": { "lru-cache": "^4.0.1", "shebang-command": "^1.2.0", @@ -2921,6 +2918,17 @@ "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" + }, + "dependencies": { + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + } } }, "extglob": { @@ -4528,8 +4536,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", @@ -4596,7 +4603,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-3.0.0.tgz", "integrity": "sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=", - "dev": true, "requires": { "xmlcreate": "^1.0.1" } @@ -4611,7 +4617,6 @@ "version": "3.5.5", "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.5.5.tgz", "integrity": "sha512-6PxB65TAU4WO0Wzyr/4/YhlGovXl0EVYfpKbpSroSj0qBxT4/xod/l40Opkm38dRHRdQgdeY836M0uVnJQG7kg==", - "dev": true, "requires": { "babylon": "7.0.0-beta.19", "bluebird": "~3.5.0", @@ -4630,14 +4635,12 @@ "babylon": { "version": "7.0.0-beta.19", "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.19.tgz", - "integrity": "sha512-Vg0C9s/REX6/WIXN37UKpv5ZhRi6A4pjHlpkE34+8/a6c2W1Q692n3hmc+SZG5lKRnaExLUbxtJ1SVT+KaCQ/A==", - "dev": true + "integrity": "sha512-Vg0C9s/REX6/WIXN37UKpv5ZhRi6A4pjHlpkE34+8/a6c2W1Q692n3hmc+SZG5lKRnaExLUbxtJ1SVT+KaCQ/A==" }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, "requires": { "minimist": "0.0.8" } @@ -4715,7 +4718,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/klaw/-/klaw-2.0.0.tgz", "integrity": "sha1-WcEo4Nxc5BAgEVEZTuucv4WGUPY=", - "dev": true, "requires": { "graceful-fs": "^4.1.9" } @@ -4960,7 +4962,6 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", - "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -4996,8 +4997,7 @@ "marked": { "version": "0.3.19", "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", - "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", - "dev": true + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==" }, "matcher": { "version": "1.1.1", @@ -5205,8 +5205,7 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minimist-options": { "version": "3.0.2", @@ -6700,8 +6699,7 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "p-finally": { "version": "1.0.0", @@ -7004,8 +7002,7 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "punycode": { "version": "1.4.1", @@ -7334,7 +7331,6 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.1.tgz", "integrity": "sha1-aUPDUwxNmn5G8c3dUcFY/GcM294=", - "dev": true, "requires": { "underscore": "~1.6.0" }, @@ -7342,8 +7338,7 @@ "underscore": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", - "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", - "dev": true + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" } } }, @@ -7504,7 +7499,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -7512,8 +7506,7 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "sift": { "version": "5.1.0", @@ -7878,8 +7871,7 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, "supertap": { "version": "1.0.0", @@ -8018,8 +8010,7 @@ "taffydb": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", - "dev": true + "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=" }, "tap-nyan": { "version": "1.1.0", @@ -8155,7 +8146,6 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, "requires": { "os-tmpdir": "~1.0.2" } @@ -8300,14 +8290,12 @@ "underscore": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", - "dev": true + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" }, "underscore-contrib": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/underscore-contrib/-/underscore-contrib-0.3.0.tgz", "integrity": "sha1-ZltmwkeD+PorGMn4y7Dix9SMJsc=", - "dev": true, "requires": { "underscore": "1.6.0" }, @@ -8315,8 +8303,7 @@ "underscore": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", - "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", - "dev": true + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" } } }, @@ -8519,7 +8506,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -8676,8 +8662,7 @@ "xmlcreate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz", - "integrity": "sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=", - "dev": true + "integrity": "sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=" }, "xtend": { "version": "4.0.1", @@ -8688,8 +8673,7 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "yargs-parser": { "version": "10.1.0", diff --git a/package.json b/package.json index 5fc6220ee..95e350a83 100644 --- a/package.json +++ b/package.json @@ -95,17 +95,20 @@ "@ui5/fs": "^1.0.1", "@ui5/logger": "^1.0.0", "cheerio": "^0.22.0", + "cross-spawn": "^5.1.0", "escodegen": "^1.11.0", "escope": "^3.6.0", "esprima": "^4.0.1", "estraverse": "^4.2.0", "globby": "^7.1.1", "graceful-fs": "^4.1.15", + "jsdoc": "^3.5.5", "less-openui5": "^0.6.0", "pretty-data": "^0.40.0", "pretty-hrtime": "^1.0.3", "replacestream": "^4.0.3", "semver": "^5.6.0", + "tmp": "0.0.33", "uglify-es": "^3.2.2", "xml2js": "^0.4.17", "yazl": "^2.5.1" @@ -121,7 +124,6 @@ "eslint-config-google": "^0.12.0", "eslint-plugin-jsdoc": "^4.1.1", "extract-zip": "^1.6.7", - "jsdoc": "^3.5.5", "mock-require": "^3.0.3", "nyc": "^13.3.0", "opn-cli": "^4.0.0", From 302e2dff8370639a63fe12c8141e3fb83a8fceba Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Wed, 27 Feb 2019 16:01:14 +0100 Subject: [PATCH 02/38] Refactoring based on concept work --- index.js | 2 + lib/processors/jsdoc/jsdoc.js | 128 ------------------------- lib/processors/jsdoc/jsdocGenerator.js | 118 +++++++++++++++++++++++ lib/tasks/createJSDoc.js | 29 ------ lib/tasks/generateJsdoc.js | 66 +++++++++++++ lib/tasks/taskRepository.js | 2 +- lib/types/library/LibraryBuilder.js | 8 +- 7 files changed, 191 insertions(+), 162 deletions(-) delete mode 100644 lib/processors/jsdoc/jsdoc.js create mode 100644 lib/processors/jsdoc/jsdocGenerator.js delete mode 100644 lib/tasks/createJSDoc.js create mode 100644 lib/tasks/generateJsdoc.js diff --git a/index.js b/index.js index 59f3eaf65..bb83bfcf8 100644 --- a/index.js +++ b/index.js @@ -13,6 +13,7 @@ module.exports = { flexChangesBundler: require("./lib/processors/bundlers/flexChangesBundler"), manifestBundler: require("./lib/processors/bundlers/manifestBundler"), moduleBundler: require("./lib/processors/bundlers/moduleBundler"), + jsdocGenerator: require("./lib/processors/jsdoc/jsdocGenerator"), bootstrapHtmlTransformer: require("./lib/processors/bootstrapHtmlTransformer"), debugFileCreator: require("./lib/processors/debugFileCreator"), resourceCopier: require("./lib/processors/resourceCopier"), @@ -35,6 +36,7 @@ module.exports = { generateBundle: require("./lib/tasks/bundlers/generateBundle"), buildThemes: require("./lib/tasks/buildThemes"), createDebugFiles: require("./lib/tasks/createDebugFiles"), + generateJsdoc: require("./lib/tasks/generateJsdoc"), generateVersionInfo: require("./lib/tasks/generateVersionInfo"), replaceCopyright: require("./lib/tasks/replaceCopyright"), replaceVersion: require("./lib/tasks/replaceVersion"), diff --git a/lib/processors/jsdoc/jsdoc.js b/lib/processors/jsdoc/jsdoc.js deleted file mode 100644 index 8cc13afd7..000000000 --- a/lib/processors/jsdoc/jsdoc.js +++ /dev/null @@ -1,128 +0,0 @@ -const spawn = require('cross-spawn').spawn; -const fs = require('fs'); -const path = require('path'); -const tmp = require('tmp'); -const resourceFactory = require("@ui5/fs").resourceFactory; - -function createJSDocConfig({source, target, namespace, libraryName, version}) { - // resolve path to the package.json to get the path to the jsdocext folder - const jsdocext = path.normalize(__dirname); - - const config = `{ - "plugins": ["${jsdocext}/ui5/plugin.js"], - "opts": { - "recurse": true, - "lenient": true, - "template": "${jsdocext}/ui5/template", - "ui5": { - "saveSymbols": true - } - }, - "templates": { - "ui5": { - "variants": [ "apijson", "fullapixml", "apijs", "api.xml"], - "version": "${version}", - "jsapiFile": "${target}/libraries/${libraryName}.js", - "apiJsonFolder": "${target}/dependency-apis", - "apiJsonFile": "${target}/test-resources/${namespace}/designtime/api.json" - } - } - }`; - console.log(config); - return config; -} - -function jsdoc({sources, target, namespace, libraryName, version}) { - - const tmpobj = tmp.fileSync(); - fs.writeFileSync(tmpobj.name, createJSDocConfig({target, namespace, libraryName, version}), 'utf8'); // TODO async + promise - - console.log("jsdoc called for ", sources); - var args = [ - require.resolve("jsdoc/jsdoc"), - '-c', - tmpobj.name, - '--verbose' - ]; - args = args.concat(sources); - - return new Promise((resolve, reject) => { - const child = spawn('node', args); - child.stdout.on('data', function(data) { - console.log(String(data)); - }); - child.stderr.on('data', function(data) { - console.error(String(data)); - }); - child.on('exit', function(code) { - var resolvedDest; - console.log("jsdoc exited with code ", code); - if (code === 0 || code === 1) { - resolve(code); - } else { - reject(code) - } - }); - }); -} - -/** - * Creates *-dbg.js files for all JavaScript-resources supplied and writes them to target locator. - * - * @module build/processors/dbg - * - * @param {Object} parameters Parameters - * @param {Array} parameters.resources List of resources to be processed - * @param {ResourceLocatorCollection} parameters.sourceLocator Source locator - * @param {ResourceLocator} parameters.targetLocator Target locator - * @param {Object} [parameters.config] Configuration - * @return {Promise} Promise resolving with undefined once data has been written to the target locator - */ -module.exports = function({resources, options}) { - if ( !options.libraryName ) { - throw new TypeError("Cannot execute JSDoc build without a library name"); - } - const namespace = options.libraryName.replace(/\./g, "/"); - const tmpDirObj = tmp.dirSync(); - const tmpSourceDir = path.join(tmpDirObj.name, 'src'); - const tmpTargetDir = path.join(tmpDirObj.name, 'target'); - - const fsSources = resourceFactory.createAdapter({ - fsBasePath: tmpSourceDir, - virBasePath: "/resources/" - }); - const fsTarget = resourceFactory.createAdapter({ - fsBasePath: tmpTargetDir, - virBasePath: "/" - }); - - //return Promise.resolve([]); - - return Promise.all( - // write all resources to the tmp folder - resources.map((resource) => fsSources.write(resource)) - // after this step, a follow-up step aborts silenty for an unknown reasons - // cloning the resources before writing them avoids the problem: - // resources.map((resource) => resource.clone().then((resource) => fsSources.write(resource))) - ).then(() => [], (err) => { - console.log(err); - return []; - }).then((files) => { - return jsdoc({ - sources: [tmpSourceDir], - target: tmpTargetDir, - namespace, - libraryName: options.libraryName, - version: options.version - }); - }).then(() => { - // create resources from the output files - return Promise.all([ - fsTarget.byPath(`/test-resources/${namespace}/designtime/api.json`) - //,fsTarget.byPath(`/libraries/${options.libraryName}.js`) - ]).then((res) => res.filter($=>$)); - }).then((result) => { - // TODO cleanup tmp dir - return result; - }); -}; diff --git a/lib/processors/jsdoc/jsdocGenerator.js b/lib/processors/jsdoc/jsdocGenerator.js new file mode 100644 index 000000000..6b90357ce --- /dev/null +++ b/lib/processors/jsdoc/jsdocGenerator.js @@ -0,0 +1,118 @@ +const spawn = require("cross-spawn").spawn; +const fs = require("fs"); +const path = require("path"); +const {promisify} = require("util"); +const writeFile = promisify(fs.writeFile); +const {resourceFactory} = require("@ui5/fs"); + +async function createJSDocConfig({sourcePath, targetPath, namespace, libraryName, version, variants}) { + // Resolve path to this script to get the path to the JSDoc extensions folder + const jsdocPath = path.normalize(__dirname); + + const config = `{ + "plugins": ["${jsdocPath}/ui5/plugin.js"], + "opts": { + "recurse": true, + "lenient": true, + "template": "${jsdocPath}/ui5/template", + "ui5": { + "saveSymbols": true + } + }, + "templates": { + "ui5": { + "variants": ${JSON.stringify(variants)}, + "version": "${version}", + "jsapiFile": "${targetPath}/libraries/${libraryName}.js", + "apiJsonFolder": "${targetPath}/dependency-apis", + "apiJsonFile": "${targetPath}/test-resources/${namespace}/designtime/api.json" + } + } + }`; + console.log(config); + const configPath = path.join(sourcePath, "jsdoc-config.json"); + await writeFile(configPath, config); + return configPath; +} + +async function buildJsdoc({sourcePath, targetPath, namespace, libraryName, version, variants}) { + const configPath = await createJSDocConfig({ + sourcePath, targetPath, namespace, libraryName, version, variants + }); + + console.log("jsdoc called for ", sourcePath); + const args = [ + require.resolve("jsdoc/jsdoc"), + "-c", + configPath, + "--verbose", + sourcePath + ]; + + return new Promise((resolve, reject) => { + const child = spawn("node", args); + child.stdout.on("data", function(data) { + console.log(String(data)); + }); + child.stderr.on("data", function(data) { + console.error(String(data)); + }); + child.on("exit", function(code) { + console.log("jsdoc exited with code ", code); + if (code === 0 || code === 1) { + resolve(code); + } else { + reject(code); + } + }); + }); +} + + +/** + * JSDoc generator + * + * @public + * @alias module:@ui5/builder.processors.jsdoc.jsdocGenerator + * @param {Object} parameters Parameters + * @param {string} parameters.sourcePath Resources + * @param {string} parameters.targetPath Resources + * @param {Object} parameters.options Options + * @param {string} parameters.options.libraryName Library name + * @param {string} parameters.options.version Library version + * @param {Array} [parameters.options.variants=["apijson"]] JSDoc variants to be built + * @param {boolean} [parameters.options.sdkBuild=true] Whether additional SDK specific api.json resources shall be generated + * @returns {Promise} Promise resolving with + */ +module.exports = async function({sourcePath, targetPath, options}) { + if ( !options.libraryName ) { + throw new TypeError("Cannot execute JSDoc build without a library name"); + } + + if (!options.variants || options.variants.length === 0) { + options.variants = ["apijson"]; + } + const namespace = options.libraryName.replace(/\./g, "/"); + + await buildJsdoc({ + sourcePath, + targetPath, + namespace, + libraryName: options.libraryName, + version: options.version, + variants: options.variants + }); + + const fsTarget = resourceFactory.createAdapter({ + fsBasePath: targetPath, + virBasePath: "/" + }); + + + // create resources from the output files + return Promise.all([ + fsTarget.byPath(`/test-resources/${namespace}/designtime/api.json`) + // fsTarget.byPath(`/libraries/${options.libraryName}.js`) + // ]).then((res) => res.filter($=>$)); + ]); +}; diff --git a/lib/tasks/createJSDoc.js b/lib/tasks/createJSDoc.js deleted file mode 100644 index f7e4d9c69..000000000 --- a/lib/tasks/createJSDoc.js +++ /dev/null @@ -1,29 +0,0 @@ -const runJSDoc = require("../processors/jsdoc/jsdoc"); - -/** - * Task to create dbg files. - * - * @module builder/tasks/createDebugFiles - * @param {Object} parameters Parameters - * @param {DuplexCollection} parameters.workspace DuplexCollection to read and write files - * @param {Object} [parameters.options] Options - * @param {string} [parameters.options.pattern] Pattern to locate the files to be processed - * @returns {Promise} Promise resolving with undefined once data has been written - */ -module.exports = async function({workspace, options}) { - let allResources; - if (workspace.byGlobSource) { // API only available on duplex collections - allResources = await workspace.byGlobSource(options.pattern); - } else { - allResources = await workspace.byGlob(options.pattern); - } - return runJSDoc({ - resources: allResources, - options - }).then((createdResources) => { - console.log(createdResources); - return Promise.all(createdResources.map((resource) => { - return workspace.write(resource); - })); - }); -}; diff --git a/lib/tasks/generateJsdoc.js b/lib/tasks/generateJsdoc.js new file mode 100644 index 000000000..2e9a8dee0 --- /dev/null +++ b/lib/tasks/generateJsdoc.js @@ -0,0 +1,66 @@ +const path = require("path"); +const tmp = require("tmp"); +tmp.setGracefulCleanup(); +const jsdocGenerator = require("../processors/jsdoc/jsdocGenerator"); +const {resourceFactory} = require("@ui5/fs"); + +/** + * Task to create dbg files. + * + * @module builder/tasks/createDebugFiles + * @param {Object} parameters Parameters + * @param {DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {Object} [parameters.options] Options + * @param {string} [parameters.options.pattern] Pattern to locate the files to be processed + * @returns {Promise} Promise resolving with undefined once data has been written + */ +module.exports = async function({workspace, options}) { + let allResources; + if (workspace.byGlobSource) { // API only available on duplex collections + allResources = await workspace.byGlobSource(options.pattern); + } else { + allResources = await workspace.byGlob(options.pattern); + } + + const {path: tmpDirPath, cleanupCallback} = await createTmpWorkDir(); + const tmpSourcePath = path.join(tmpDirPath, "src"); + const tmpTargetPath = path.join(tmpDirPath, "target"); + + const fsSource = resourceFactory.createAdapter({ + fsBasePath: tmpSourcePath, + virBasePath: "/resources/" + }); + + // write all resources to the tmp folder + await Promise.all(allResources.map((resource) => fsSource.write(resource))); + + const createdResources = await jsdocGenerator({ + sourcePath: tmpSourcePath, + targetPath: tmpTargetPath, + options + }); + + console.log(createdResources); + return Promise.all(createdResources.map((resource) => { + return workspace.write(resource); + // TODO: cleanupCallback + })); +}; + +function createTmpWorkDir() { + return new Promise((resolve, reject) => { + tmp.dir({ + prefix: "ui5-tooling-jsdoc-tmp-" + }, (err, path, cleanupCallback) => { + if (err) { + reject(err); + return; + } + + resolve({ + path, + cleanupCallback + }); + }); + }); +} diff --git a/lib/tasks/taskRepository.js b/lib/tasks/taskRepository.js index 3afffd6e0..1fd975b1a 100644 --- a/lib/tasks/taskRepository.js +++ b/lib/tasks/taskRepository.js @@ -2,7 +2,7 @@ const tasks = { replaceCopyright: require("./replaceCopyright"), replaceVersion: require("./replaceVersion"), createDebugFiles: require("./createDebugFiles"), - createJSDoc: require("./createJSDoc"), + generateJsdoc: require("./generateJsdoc"), uglify: require("./uglify"), buildThemes: require("./buildThemes"), transformBootstrapHtml: require("./transformBootstrapHtml"), diff --git a/lib/types/library/LibraryBuilder.js b/lib/types/library/LibraryBuilder.js index f38f6117a..defc23518 100644 --- a/lib/types/library/LibraryBuilder.js +++ b/lib/types/library/LibraryBuilder.js @@ -8,7 +8,7 @@ const tasks = { // can't require index.js due to circular dependency generateStandaloneAppBundle: require("../../tasks/bundlers/generateStandaloneAppBundle"), buildThemes: require("../../tasks/buildThemes"), createDebugFiles: require("../../tasks/createDebugFiles"), - createJSDoc: require("../../tasks/createJSDoc"), + generateJsdoc: require("../../tasks/generateJsdoc"), generateLibraryManifest: require("../../tasks/generateLibraryManifest"), generateVersionInfo: require("../../tasks/generateVersionInfo"), replaceCopyright: require("../../tasks/replaceCopyright"), @@ -40,9 +40,9 @@ class LibraryBuilder extends AbstractBuilder { }); }); - this.addTask("createJSDoc", () => { - const createJSDoc = tasks.createJSDoc; - return createJSDoc({ + this.addTask("generateJsdoc", () => { + const generateJsdoc = tasks.generateJsdoc; + return generateJsdoc({ workspace: resourceCollections.workspace, options: { libraryName: project.metadata.name, From 7f74ab7c64fd0810fac51827724ac4cd4ccfe8b4 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Wed, 27 Feb 2019 17:13:54 +0100 Subject: [PATCH 03/38] Remove cross spawn + add tests --- lib/builder/builder.js | 1 + lib/processors/jsdoc/jsdocGenerator.js | 47 ++++----- lib/tasks/generateJsdoc.js | 2 +- package-lock.json | 16 ++- package.json | 1 - test/lib/processors/jsdoc/jsdocGenerator.js | 102 ++++++++++++++++++++ 6 files changed, 140 insertions(+), 29 deletions(-) create mode 100644 test/lib/processors/jsdoc/jsdocGenerator.js diff --git a/lib/builder/builder.js b/lib/builder/builder.js index 01307c6e0..fdecdaa13 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -50,6 +50,7 @@ function composeTaskList({dev, selfContained, includedTasks, excludedTasks}) { selectedTasks.generateManifestBundle = false; selectedTasks.generateStandaloneAppBundle = false; selectedTasks.transformBootstrapHtml = false; + selectedTasks.generateJsdoc = false; if (selfContained) { // No preloads, bundle only diff --git a/lib/processors/jsdoc/jsdocGenerator.js b/lib/processors/jsdoc/jsdocGenerator.js index 6b90357ce..5f27adf53 100644 --- a/lib/processors/jsdoc/jsdocGenerator.js +++ b/lib/processors/jsdoc/jsdocGenerator.js @@ -1,11 +1,11 @@ -const spawn = require("cross-spawn").spawn; +const spawn = require("child_process").spawn; const fs = require("fs"); const path = require("path"); const {promisify} = require("util"); const writeFile = promisify(fs.writeFile); const {resourceFactory} = require("@ui5/fs"); -async function createJSDocConfig({sourcePath, targetPath, namespace, libraryName, version, variants}) { +async function generateJsdocConfig({targetPath, namespace, libraryName, version, variants}) { // Resolve path to this script to get the path to the JSDoc extensions folder const jsdocPath = path.normalize(__dirname); @@ -29,18 +29,16 @@ async function createJSDocConfig({sourcePath, targetPath, namespace, libraryName } } }`; - console.log(config); + return config; +} + +async function writeJsdocConfig(sourcePath, config) { const configPath = path.join(sourcePath, "jsdoc-config.json"); await writeFile(configPath, config); return configPath; } -async function buildJsdoc({sourcePath, targetPath, namespace, libraryName, version, variants}) { - const configPath = await createJSDocConfig({ - sourcePath, targetPath, namespace, libraryName, version, variants - }); - - console.log("jsdoc called for ", sourcePath); +async function buildJsdoc({sourcePath, configPath}) { const args = [ require.resolve("jsdoc/jsdoc"), "-c", @@ -48,21 +46,15 @@ async function buildJsdoc({sourcePath, targetPath, namespace, libraryName, versi "--verbose", sourcePath ]; - return new Promise((resolve, reject) => { - const child = spawn("node", args); - child.stdout.on("data", function(data) { - console.log(String(data)); - }); - child.stderr.on("data", function(data) { - console.error(String(data)); + const child = spawn("node", args, { + stdio: ["ignore", "ignore", process.stderr] }); - child.on("exit", function(code) { - console.log("jsdoc exited with code ", code); + child.on("close", function(code) { if (code === 0 || code === 1) { - resolve(code); + resolve(); } else { - reject(code); + reject(new Error(`JSDoc child process closed with code ${code}`)); } }); }); @@ -94,8 +86,7 @@ module.exports = async function({sourcePath, targetPath, options}) { } const namespace = options.libraryName.replace(/\./g, "/"); - await buildJsdoc({ - sourcePath, + const config = await generateJsdocConfig({ targetPath, namespace, libraryName: options.libraryName, @@ -103,12 +94,18 @@ module.exports = async function({sourcePath, targetPath, options}) { variants: options.variants }); + const configPath = await writeJsdocConfig(sourcePath, config); + + await buildJsdoc({ + sourcePath, + configPath + }); + const fsTarget = resourceFactory.createAdapter({ fsBasePath: targetPath, virBasePath: "/" }); - // create resources from the output files return Promise.all([ fsTarget.byPath(`/test-resources/${namespace}/designtime/api.json`) @@ -116,3 +113,7 @@ module.exports = async function({sourcePath, targetPath, options}) { // ]).then((res) => res.filter($=>$)); ]); }; + +module.exports._generateJsdocConfig = generateJsdocConfig; +module.exports._writeJsdocConfig = writeJsdocConfig; +module.exports._buildJsdoc = buildJsdoc; diff --git a/lib/tasks/generateJsdoc.js b/lib/tasks/generateJsdoc.js index 2e9a8dee0..7661d4e32 100644 --- a/lib/tasks/generateJsdoc.js +++ b/lib/tasks/generateJsdoc.js @@ -41,7 +41,7 @@ module.exports = async function({workspace, options}) { }); console.log(createdResources); - return Promise.all(createdResources.map((resource) => { + await Promise.all(createdResources.map((resource) => { return workspace.write(resource); // TODO: cleanupCallback })); diff --git a/package-lock.json b/package-lock.json index 00dd8d9f3..068d39c00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2082,6 +2082,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, "requires": { "lru-cache": "^4.0.1", "shebang-command": "^1.2.0", @@ -4536,7 +4537,8 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, "isobject": { "version": "3.0.1", @@ -4962,6 +4964,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -7002,7 +7005,8 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true }, "punycode": { "version": "1.4.1", @@ -7499,6 +7503,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -7506,7 +7511,8 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true }, "sift": { "version": "5.1.0", @@ -8506,6 +8512,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, "requires": { "isexe": "^2.0.0" } @@ -8673,7 +8680,8 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true }, "yargs-parser": { "version": "10.1.0", diff --git a/package.json b/package.json index 95e350a83..3d17575b0 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,6 @@ "@ui5/fs": "^1.0.1", "@ui5/logger": "^1.0.0", "cheerio": "^0.22.0", - "cross-spawn": "^5.1.0", "escodegen": "^1.11.0", "escope": "^3.6.0", "esprima": "^4.0.1", diff --git a/test/lib/processors/jsdoc/jsdocGenerator.js b/test/lib/processors/jsdoc/jsdocGenerator.js new file mode 100644 index 000000000..0dfa0b270 --- /dev/null +++ b/test/lib/processors/jsdoc/jsdocGenerator.js @@ -0,0 +1,102 @@ +const path = require("path"); +const {test} = require("ava"); +const sinon = require("sinon"); +const mock = require("mock-require"); +const jsdocGenerator = require("../../../../lib/processors/jsdoc/jsdocGenerator"); + +test("generateJsdocConfig", async (t) => { + const res = await jsdocGenerator._generateJsdocConfig({ + sourcePath: "/some/source/path", + targetPath: "/some/target/path", + namespace: "some/namespace", + libraryName: "some.namespace", + version: "1.0.0", + variants: ["apijson"] + }); + + const jsdocGeneratorPath = path.resolve(__dirname, "..", "..", "..", "..", "lib", "processors", + "jsdoc"); + + t.deepEqual(res, `{ + "plugins": ["${jsdocGeneratorPath}/ui5/plugin.js"], + "opts": { + "recurse": true, + "lenient": true, + "template": "${jsdocGeneratorPath}/ui5/template", + "ui5": { + "saveSymbols": true + } + }, + "templates": { + "ui5": { + "variants": ["apijson"], + "version": "1.0.0", + "jsapiFile": "/some/target/path/libraries/some.namespace.js", + "apiJsonFolder": "/some/target/path/dependency-apis", + "apiJsonFile": "/some/target/path/test-resources/some/namespace/designtime/api.json" + } + } + }`, "Correct config generated"); +}); + +test.serial("writeJsdocConfig", async (t) => { + mock("fs", { + writeFile: (configPath, configContent, callback) => { + t.deepEqual(configPath, "/some/path/jsdoc-config.json", "Correct config path supplied"); + t.deepEqual(configContent, "some config", "Correct config content supplied"); + callback(); + } + }); + mock.reRequire("fs"); + + // Re-require tested module + const jsdocGenerator = mock.reRequire("../../../../lib/processors/jsdoc/jsdocGenerator"); + const res = await jsdocGenerator._writeJsdocConfig("/some/path", "some config"); + + t.deepEqual(res, "/some/path/jsdoc-config.json", "Correct config path returned"); + + mock.stop("fs"); +}); + +test.serial("buildJsdoc", async (t) => { + const childProcess = require("child_process"); + let exitCode = 0; + const cpStub = sinon.stub(childProcess, "spawn").returns({ + on: (event, callback) => { + callback(exitCode); + } + }); + const jsdocGenerator = mock.reRequire("../../../../lib/processors/jsdoc/jsdocGenerator"); + + await jsdocGenerator._buildJsdoc({ + sourcePath: "/some/path", + configPath: "/some/config/path/jsdoc-config.json" + }); + t.deepEqual(cpStub.callCount, 1, "Spawn got called"); + + const firstCallArgs = cpStub.getCall(0).args; + t.deepEqual(firstCallArgs[0], "node", "Spawn got called with correct process argument"); + t.deepEqual(firstCallArgs[1], [ + path.resolve(__dirname, "..", "..", "..", "..", "node_modules", "jsdoc", "jsdoc.js"), + "-c", + "/some/config/path/jsdoc-config.json", + "--verbose", + "/some/path" + ], "Spawn got called with correct arguments"); + + + // Re-execute with exit code 1 + exitCode = 1; + await t.notThrows(jsdocGenerator._buildJsdoc({ + sourcePath: "/some/path", + configPath: "/some/config/path/jsdoc-config.json" + })); + + // Re-execute with exit code 2 + exitCode = 2; + const error = await t.throws(jsdocGenerator._buildJsdoc({ + sourcePath: "/some/path", + configPath: "/some/config/path/jsdoc-config.json" + })); + t.deepEqual(error.message, "JSDoc child process closed with code 2"); +}); From e0d23257cd2c54c3f68a0d64e8e7a0b2bc7ca723 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Thu, 28 Feb 2019 15:29:04 +0100 Subject: [PATCH 04/38] JSDoc: configure destination path + refactor API signatures --- lib/processors/jsdoc/jsdocGenerator.js | 36 +++++---- lib/tasks/generateJsdoc.js | 33 ++++++-- lib/types/library/LibraryBuilder.js | 4 +- package-lock.json | 85 ++++++++++++++++++++- package.json | 1 + test/lib/processors/jsdoc/jsdocGenerator.js | 12 +-- 6 files changed, 136 insertions(+), 35 deletions(-) diff --git a/lib/processors/jsdoc/jsdocGenerator.js b/lib/processors/jsdoc/jsdocGenerator.js index 5f27adf53..602f603a4 100644 --- a/lib/processors/jsdoc/jsdocGenerator.js +++ b/lib/processors/jsdoc/jsdocGenerator.js @@ -1,11 +1,11 @@ const spawn = require("child_process").spawn; -const fs = require("fs"); +const fs = require("graceful-fs"); const path = require("path"); const {promisify} = require("util"); const writeFile = promisify(fs.writeFile); const {resourceFactory} = require("@ui5/fs"); -async function generateJsdocConfig({targetPath, namespace, libraryName, version, variants}) { +async function generateJsdocConfig({targetPath, tmpPath, namespace, projectName, version, variants}) { // Resolve path to this script to get the path to the JSDoc extensions folder const jsdocPath = path.normalize(__dirname); @@ -17,13 +17,14 @@ async function generateJsdocConfig({targetPath, namespace, libraryName, version, "template": "${jsdocPath}/ui5/template", "ui5": { "saveSymbols": true - } + }, + "destination": "${tmpPath}" }, "templates": { "ui5": { "variants": ${JSON.stringify(variants)}, "version": "${version}", - "jsapiFile": "${targetPath}/libraries/${libraryName}.js", + "jsapiFile": "${targetPath}/libraries/${projectName}.js", "apiJsonFolder": "${targetPath}/dependency-apis", "apiJsonFile": "${targetPath}/test-resources/${namespace}/designtime/api.json" } @@ -67,34 +68,39 @@ async function buildJsdoc({sourcePath, configPath}) { * @public * @alias module:@ui5/builder.processors.jsdoc.jsdocGenerator * @param {Object} parameters Parameters - * @param {string} parameters.sourcePath Resources - * @param {string} parameters.targetPath Resources + * @param {string} parameters.sourcePath Path of the source files to be processed + * @param {string} parameters.targetPath Path to write any output files + * @param {string} parameters.tmpPath Path to write temporary and debug files * @param {Object} parameters.options Options - * @param {string} parameters.options.libraryName Library name - * @param {string} parameters.options.version Library version + * @param {string} parameters.options.projectName Project name + * @param {string} parameters.options.version Project version * @param {Array} [parameters.options.variants=["apijson"]] JSDoc variants to be built * @param {boolean} [parameters.options.sdkBuild=true] Whether additional SDK specific api.json resources shall be generated * @returns {Promise} Promise resolving with */ -module.exports = async function({sourcePath, targetPath, options}) { - if ( !options.libraryName ) { - throw new TypeError("Cannot execute JSDoc build without a library name"); +module.exports = async function({sourcePath, targetPath, tmpPath, options}) { + if (!sourcePath || !targetPath || !tmpPath || !options.projectName || !options.version) { + throw new Error("[jsdocGenerator]: One or more mandatory options not provided"); } if (!options.variants || options.variants.length === 0) { options.variants = ["apijson"]; } - const namespace = options.libraryName.replace(/\./g, "/"); + if (options.sdkBuild === undefined) { + options.sdkBuild = true; + } + const namespace = options.projectName.replace(/\./g, "/"); const config = await generateJsdocConfig({ targetPath, + tmpPath, namespace, - libraryName: options.libraryName, + projectName: options.projectName, version: options.version, variants: options.variants }); - const configPath = await writeJsdocConfig(sourcePath, config); + const configPath = await writeJsdocConfig(tmpPath, config); await buildJsdoc({ sourcePath, @@ -109,7 +115,7 @@ module.exports = async function({sourcePath, targetPath, options}) { // create resources from the output files return Promise.all([ fsTarget.byPath(`/test-resources/${namespace}/designtime/api.json`) - // fsTarget.byPath(`/libraries/${options.libraryName}.js`) + // fsTarget.byPath(`/libraries/${options.projectName}.js`) // ]).then((res) => res.filter($=>$)); ]); }; diff --git a/lib/tasks/generateJsdoc.js b/lib/tasks/generateJsdoc.js index 7661d4e32..f5e245016 100644 --- a/lib/tasks/generateJsdoc.js +++ b/lib/tasks/generateJsdoc.js @@ -1,4 +1,6 @@ const path = require("path"); +const makeDir = require("make-dir"); +const fs = require("graceful-fs"); const tmp = require("tmp"); tmp.setGracefulCleanup(); const jsdocGenerator = require("../processors/jsdoc/jsdocGenerator"); @@ -10,11 +12,18 @@ const {resourceFactory} = require("@ui5/fs"); * @module builder/tasks/createDebugFiles * @param {Object} parameters Parameters * @param {DuplexCollection} parameters.workspace DuplexCollection to read and write files - * @param {Object} [parameters.options] Options - * @param {string} [parameters.options.pattern] Pattern to locate the files to be processed + * @param {Object} parameters.options Options + * @param {string} parameters.options.pattern Pattern to locate the files to be processed + * @param {string} parameters.options.projectName Project name + * @param {string} parameters.options.version Project version + * @param {boolean} [parameters.options.sdkBuild=true] Whether additional SDK specific api.json resources shall be generated * @returns {Promise} Promise resolving with undefined once data has been written */ module.exports = async function({workspace, options}) { + if (!options.projectName || !options.version || !options.pattern) { + throw new Error("[generateJsdoc]: One or more mandatory options not provided"); + } + let allResources; if (workspace.byGlobSource) { // API only available on duplex collections allResources = await workspace.byGlobSource(options.pattern); @@ -22,9 +31,12 @@ module.exports = async function({workspace, options}) { allResources = await workspace.byGlob(options.pattern); } - const {path: tmpDirPath, cleanupCallback} = await createTmpWorkDir(); - const tmpSourcePath = path.join(tmpDirPath, "src"); - const tmpTargetPath = path.join(tmpDirPath, "target"); + const {path: tmpDirPath, cleanupCallback} = await createTmpWorkDir(options.projectName); + const tmpSourcePath = path.join(tmpDirPath, "src"); // dir will be created by writing project resources below + const tmpTargetPath = path.join(tmpDirPath, "target"); // dir will be created by jsdoc itself + const tmpTmpPath = path.join(tmpDirPath, "tmp"); // dir needs to be created by us + + await makeDir(tmpTmpPath, {fs}); const fsSource = resourceFactory.createAdapter({ fsBasePath: tmpSourcePath, @@ -37,7 +49,12 @@ module.exports = async function({workspace, options}) { const createdResources = await jsdocGenerator({ sourcePath: tmpSourcePath, targetPath: tmpTargetPath, - options + tmpPath: tmpTmpPath, + options: { + projectName: options.projectName, + version: options.version, + variants: ["apijson"] + } }); console.log(createdResources); @@ -47,10 +64,10 @@ module.exports = async function({workspace, options}) { })); }; -function createTmpWorkDir() { +function createTmpWorkDir(projectName) { return new Promise((resolve, reject) => { tmp.dir({ - prefix: "ui5-tooling-jsdoc-tmp-" + prefix: `ui5-tooling-tmp-jsdoc-${projectName}-` }, (err, path, cleanupCallback) => { if (err) { reject(err); diff --git a/lib/types/library/LibraryBuilder.js b/lib/types/library/LibraryBuilder.js index defc23518..b4d643518 100644 --- a/lib/types/library/LibraryBuilder.js +++ b/lib/types/library/LibraryBuilder.js @@ -45,12 +45,10 @@ class LibraryBuilder extends AbstractBuilder { return generateJsdoc({ workspace: resourceCollections.workspace, options: { - libraryName: project.metadata.name, + projectName: project.metadata.name, version: project.version, pattern: "/resources/**/*.js" } - }).then(() => { - console.log("createJSDOC done"); }); }); diff --git a/package-lock.json b/package-lock.json index 068d39c00..3cbc3945a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -331,6 +331,16 @@ "minimatch": "^3.0.3", "pretty-hrtime": "^1.0.3", "random-int": "^1.0.0" + }, + "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "requires": { + "pify": "^3.0.0" + } + } } }, "@ui5/logger": { @@ -790,6 +800,23 @@ "pinkie-promise": "^2.0.0" } }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -1977,6 +2004,17 @@ "unique-string": "^1.0.0", "write-file-atomic": "^2.0.0", "xdg-basedir": "^3.0.0" + }, + "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + } } }, "console-control-strings": { @@ -3109,6 +3147,17 @@ "commondir": "^1.0.1", "make-dir": "^1.0.0", "pkg-dir": "^2.0.0" + }, + "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + } } }, "find-up": { @@ -4971,11 +5020,19 @@ } }, "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.0.0.tgz", + "integrity": "sha512-DCZvJtCxpfY3a0Onp57Jm0PY9ggZENfVtBMsPdXFZDrMSHU5kYCMJkJesLr0/UrFdJKuDUYoGxCpc93n4F3Z8g==", "requires": { - "pify": "^3.0.0" + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + } } }, "map-cache": { @@ -8103,6 +8160,17 @@ "pify": "^3.0.0", "temp-dir": "^1.0.0", "uuid": "^3.0.1" + }, + "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + } } }, "term-size": { @@ -8633,6 +8701,15 @@ "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", "dev": true + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } } } }, diff --git a/package.json b/package.json index 3d17575b0..aabab40fd 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "graceful-fs": "^4.1.15", "jsdoc": "^3.5.5", "less-openui5": "^0.6.0", + "make-dir": "^2.0.0", "pretty-data": "^0.40.0", "pretty-hrtime": "^1.0.3", "replacestream": "^4.0.3", diff --git a/test/lib/processors/jsdoc/jsdocGenerator.js b/test/lib/processors/jsdoc/jsdocGenerator.js index 0dfa0b270..d4b74a909 100644 --- a/test/lib/processors/jsdoc/jsdocGenerator.js +++ b/test/lib/processors/jsdoc/jsdocGenerator.js @@ -8,8 +8,9 @@ test("generateJsdocConfig", async (t) => { const res = await jsdocGenerator._generateJsdocConfig({ sourcePath: "/some/source/path", targetPath: "/some/target/path", + tmpPath: "/some/tmp/path", namespace: "some/namespace", - libraryName: "some.namespace", + projectName: "some.namespace", version: "1.0.0", variants: ["apijson"] }); @@ -25,7 +26,8 @@ test("generateJsdocConfig", async (t) => { "template": "${jsdocGeneratorPath}/ui5/template", "ui5": { "saveSymbols": true - } + }, + "destination": "/some/tmp/path" }, "templates": { "ui5": { @@ -40,14 +42,14 @@ test("generateJsdocConfig", async (t) => { }); test.serial("writeJsdocConfig", async (t) => { - mock("fs", { + mock("graceful-fs", { writeFile: (configPath, configContent, callback) => { t.deepEqual(configPath, "/some/path/jsdoc-config.json", "Correct config path supplied"); t.deepEqual(configContent, "some config", "Correct config content supplied"); callback(); } }); - mock.reRequire("fs"); + mock.reRequire("graceful-fs"); // Re-require tested module const jsdocGenerator = mock.reRequire("../../../../lib/processors/jsdoc/jsdocGenerator"); @@ -55,7 +57,7 @@ test.serial("writeJsdocConfig", async (t) => { t.deepEqual(res, "/some/path/jsdoc-config.json", "Correct config path returned"); - mock.stop("fs"); + mock.stop("graceful-fs"); }); test.serial("buildJsdoc", async (t) => { From 454211be2d37ad5bed7d84286cd827fa899ba86b Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Thu, 28 Feb 2019 16:16:16 +0100 Subject: [PATCH 05/38] Fix tmp dir cleanup --- lib/tasks/generateJsdoc.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/tasks/generateJsdoc.js b/lib/tasks/generateJsdoc.js index f5e245016..425d4027e 100644 --- a/lib/tasks/generateJsdoc.js +++ b/lib/tasks/generateJsdoc.js @@ -31,7 +31,7 @@ module.exports = async function({workspace, options}) { allResources = await workspace.byGlob(options.pattern); } - const {path: tmpDirPath, cleanupCallback} = await createTmpWorkDir(options.projectName); + const {path: tmpDirPath} = await createTmpWorkDir(options.projectName); const tmpSourcePath = path.join(tmpDirPath, "src"); // dir will be created by writing project resources below const tmpTargetPath = path.join(tmpDirPath, "target"); // dir will be created by jsdoc itself const tmpTmpPath = path.join(tmpDirPath, "tmp"); // dir needs to be created by us @@ -58,25 +58,25 @@ module.exports = async function({workspace, options}) { }); console.log(createdResources); - await Promise.all(createdResources.map((resource) => { - return workspace.write(resource); - // TODO: cleanupCallback + await Promise.all(createdResources.map(async (resource) => { + await workspace.write(resource); })); }; function createTmpWorkDir(projectName) { return new Promise((resolve, reject) => { tmp.dir({ - prefix: `ui5-tooling-tmp-jsdoc-${projectName}-` - }, (err, path, cleanupCallback) => { + prefix: `ui5-tooling-tmp-jsdoc-${projectName}-`, + // keep: true, + unsafeCleanup: true + }, (err, path) => { if (err) { reject(err); return; } resolve({ - path, - cleanupCallback + path }); }); }); From 965d5d2ccec2eb71f4891a5bc400718c75827756 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Wed, 27 Feb 2019 17:35:41 +0100 Subject: [PATCH 06/38] Fix ESLint errors --- lib/processors/jsdoc/create-api-index.js | 106 +- lib/processors/jsdoc/dummy-child.js | 5 - lib/processors/jsdoc/jsdocGenerator.js | 3 +- .../jsdoc/transform-apijson-for-sdk.js | 942 +++++---- lib/processors/jsdoc/ui5/plugin.js | 1118 +++++----- lib/processors/jsdoc/ui5/template/publish.js | 1824 +++++++++-------- lib/tasks/generateJsdoc.js | 4 +- lib/types/library/LibraryBuilder.js | 3 - 8 files changed, 2051 insertions(+), 1954 deletions(-) delete mode 100644 lib/processors/jsdoc/dummy-child.js diff --git a/lib/processors/jsdoc/create-api-index.js b/lib/processors/jsdoc/create-api-index.js index b68357dde..33ccd20da 100644 --- a/lib/processors/jsdoc/create-api-index.js +++ b/lib/processors/jsdoc/create-api-index.js @@ -8,20 +8,21 @@ "use strict"; const fs = require("fs"); const path = require("path"); - -function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetFileDeprecated, targetFileExperimental, targetFileSince) { - - console.log("[INFO] creating API index files"); - console.log("[INFO] sap-ui-version.json: " + versionInfoFile); - console.log("[INFO] unpacked test-resources: " + unpackedTestresourcesRoot); - console.log("[INFO] target file: " + targetFile); - console.log("[INFO] target file deprecated: " + targetFileDeprecated); - console.log("[INFO] target file experimental: " + targetFileExperimental); - console.log("[INFO] target file since: " + targetFileSince); - console.log("[INFO]"); +const log = require("@ui5/logger").getLogger("builder:processors:jsdoc:"); + +function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetFileDeprecated, + targetFileExperimental, targetFileSince) { + log.info("creating API index files"); + log.info(" sap-ui-version.json: " + versionInfoFile); + log.info(" unpacked test-resources: " + unpackedTestresourcesRoot); + log.info(" target file: " + targetFile); + log.info(" target file deprecated: " + targetFileDeprecated); + log.info(" target file experimental: " + targetFileExperimental); + log.info(" target file since: " + targetFileSince); + log.info(""); // Deprecated, Experimental and Since collections - let oListCollection = { + const oListCollection = { deprecated: { noVersion: { apis: [] @@ -40,8 +41,8 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF }; function readJSONFile(file) { - return new Promise(function (resolve, reject) { - fs.readFile(file, 'utf8', function (err, data) { + return new Promise(function(resolve, reject) { + fs.readFile(file, "utf8", function(err, data) { if (err) { reject(err); } else { @@ -64,7 +65,7 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF } function writeJSON(file, content) { - return new Promise(function(resolve,reject) { + return new Promise(function(resolve, reject) { // Create dir if it does not exist mkdirSync( path.dirname(file) ); fs.writeFile(file, JSON.stringify(content), "utf-8", function(err) { @@ -83,14 +84,14 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF * Returns a promise that resolves with an array of symbols. */ function createSymbolSummaryForLib(lib) { - let file = path.join(unpackedTestresourcesRoot, lib.replace(/\./g, "/"), "designtime/api.json"); + const file = path.join(unpackedTestresourcesRoot, lib.replace(/\./g, "/"), "designtime/api.json"); - return readJSONFile(file).then(function (apijson) { + return readJSONFile(file).then(function(apijson) { if (!apijson.hasOwnProperty("symbols") || !Array.isArray(apijson.symbols)) { // Ignore libraries with invalid api.json content like empty object or non-array "symbols" property. return []; } - return apijson.symbols.map(symbol => { + return apijson.symbols.map((symbol) => { collectLists(symbol); return { name: symbol.name, @@ -101,7 +102,7 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF lib: lib }; }); - }) + }); } /* @@ -109,16 +110,17 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF * including symbol itself, methods and events. */ function collectLists(oSymbol) { - function addData(oDataType, oEntityObject, sObjectType, sSymbolName) { - let sSince = oDataType !== "since" ? oEntityObject[oDataType].since : oEntityObject.since, - oData = { - control: sSymbolName, - text: oEntityObject[oDataType].text || oEntityObject.description, - type: sObjectType, - "static": !!oEntityObject.static, - visibility: oEntityObject.visibility - }; + const sSince = oDataType !== "since" ? oEntityObject[oDataType].since : oEntityObject.since; + + + const oData = { + "control": sSymbolName, + "text": oEntityObject[oDataType].text || oEntityObject.description, + "type": sObjectType, + "static": !!oEntityObject.static, + "visibility": oEntityObject.visibility + }; // For class we skip entityName if (sObjectType !== "class") { @@ -127,7 +129,7 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF if (sSince) { // take only major and minor versions - let sVersion = sSince.split(".").slice(0, 2).join("."); + const sVersion = sSince.split(".").slice(0, 2).join("."); oData.since = sSince; @@ -158,7 +160,7 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF } // Methods - oSymbol.methods && oSymbol.methods.forEach(oMethod => { + oSymbol.methods && oSymbol.methods.forEach((oMethod) => { if (oMethod.deprecated) { addData("deprecated", oMethod, "methods", oSymbol.name); } @@ -173,7 +175,7 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF }); // Events - oSymbol.events && oSymbol.events.forEach(oEvent => { + oSymbol.events && oSymbol.events.forEach((oEvent) => { if (oEvent.deprecated) { addData("deprecated", oEvent, "events", oSymbol.name); } @@ -186,32 +188,31 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF addData("since", oEvent, "events", oSymbol.name); } }); - } function deepMerge(arrayOfArrays) { return arrayOfArrays.reduce((array, items) => { - array.push.apply(array, items); + array.push(...items); return array; }, []); } function expandHierarchyInfo(symbols) { - let byName = new Map(); - symbols.forEach(symbol => { + const byName = new Map(); + symbols.forEach((symbol) => { byName.set(symbol.name, symbol); }); - symbols.forEach(symbol => { - let parent = symbol.extends && byName.get(symbol.extends); + symbols.forEach((symbol) => { + const parent = symbol.extends && byName.get(symbol.extends); if (parent) { - parent.extendedBy = parent.extendedBy ||  []; + parent.extendedBy = parent.extendedBy || []; parent.extendedBy.push(symbol.name); } if (symbol.implements) { - symbol.implements.forEach(intfName => { - let intf = byName.get(intfName); + symbol.implements.forEach((intfName) => { + const intf = byName.get(intfName); if (intf) { - intf.implementedBy = intf.implementedBy ||  []; + intf.implementedBy = intf.implementedBy || []; intf.implementedBy.push(symbol.name); } }); @@ -223,14 +224,14 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF function createOverallIndex() { let version = "0.0.0"; - var p = readJSONFile(versionInfoFile) - .then(versionInfo => { + const p = readJSONFile(versionInfoFile) + .then((versionInfo) => { version = versionInfo.version; return Promise.all( versionInfo.libraries.map( - lib => createSymbolSummaryForLib(lib.name).catch(err => { + (lib) => createSymbolSummaryForLib(lib.name).catch((err) => { // ignore 'file not found' errors as some libs don't have an api.json (themes, server libs) - if (err.code === 'ENOENT') { + if (err.code === "ENOENT") { return []; } throw err; @@ -240,12 +241,12 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF }) .then(deepMerge) .then(expandHierarchyInfo) - .then(symbols => { - let result = { + .then((symbols) => { + const result = { "$schema-ref": "http://schemas.sap.com/sapui5/designtime/api.json/1.0", - version: version, - library: "*", - symbols: symbols + "version": version, + "library": "*", + "symbols": symbols }; return writeJSON(targetFile, result); }) @@ -255,8 +256,8 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF writeJSON(targetFileExperimental, oListCollection.experimental), writeJSON(targetFileSince, oListCollection.since) ])) - .catch(err => { - console.error("**** failed to create API index for libraries:", err) + .catch((err) => { + log.error("**** failed to create API index for libraries:", err); throw err; }); @@ -264,7 +265,6 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF } return createOverallIndex(); - } module.exports = process; diff --git a/lib/processors/jsdoc/dummy-child.js b/lib/processors/jsdoc/dummy-child.js deleted file mode 100644 index 64a4a6727..000000000 --- a/lib/processors/jsdoc/dummy-child.js +++ /dev/null @@ -1,5 +0,0 @@ -console.log('child executing'); -setTimeout(function() { - console.log("child done"); - //process.exit(0); -},20000); diff --git a/lib/processors/jsdoc/jsdocGenerator.js b/lib/processors/jsdoc/jsdocGenerator.js index 602f603a4..a13fc4db4 100644 --- a/lib/processors/jsdoc/jsdocGenerator.js +++ b/lib/processors/jsdoc/jsdocGenerator.js @@ -75,7 +75,8 @@ async function buildJsdoc({sourcePath, configPath}) { * @param {string} parameters.options.projectName Project name * @param {string} parameters.options.version Project version * @param {Array} [parameters.options.variants=["apijson"]] JSDoc variants to be built - * @param {boolean} [parameters.options.sdkBuild=true] Whether additional SDK specific api.json resources shall be generated + * @param {boolean} [parameters.options.sdkBuild=true] Whether additional SDK specific + * api.json resources shall be generated * @returns {Promise} Promise resolving with */ module.exports = async function({sourcePath, targetPath, tmpPath, options}) { diff --git a/lib/processors/jsdoc/transform-apijson-for-sdk.js b/lib/processors/jsdoc/transform-apijson-for-sdk.js index f4f3a8544..8dfca4bdc 100644 --- a/lib/processors/jsdoc/transform-apijson-for-sdk.js +++ b/lib/processors/jsdoc/transform-apijson-for-sdk.js @@ -4,24 +4,26 @@ * (c) Copyright 2009-2018 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ +/* eslint max-len: ["warn", 180], no-useless-escape: "off" */ "use strict"; const fs = require("fs"); const cheerio = require("cheerio"); -const path = require('path'); +const path = require("path"); +const log = require("@ui5/logger").getLogger("builder:processors:jsdoc:transform-apijson-for-sdk"); module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { - - console.log("[INFO] Transform API index files for sap.ui.documentation"); - console.log("[INFO] original file: " + sInputFile); - console.log("[INFO] output file: " + sOutputFile); - console.log("[INFO]"); + log.info("Transform API index files for sap.ui.documentation"); + log.info(" original file: " + sInputFile); + log.info(" output file: " + sOutputFile); + log.info(""); /** * Transforms api.json file - * @param {object} oChainObject chain object + * + * @param {Object} oChainObject chain object */ - let transformApiJson = function (oChainObject) { + const transformApiJson = function(oChainObject) { function isBuiltInType(type) { return formatters._baseTypes.indexOf(type) >= 0; } @@ -29,6 +31,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Heuristically determining if there is a possibility the given input string * to be a UI5 symbol + * * @param {string} sName * @returns {boolean} */ @@ -42,13 +45,14 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { sPattern = sPattern.toLowerCase(); return ( sModuleName === sPattern - || sPattern.match(/\*$/) && sModuleName.indexOf(sPattern.slice(0,-1)) === 0 // simple prefix match - || sPattern.match(/\.\*$/) && sModuleName === sPattern.slice(0,-2) // directory pattern also matches directory itself + || sPattern.match(/\*$/) && sModuleName.indexOf(sPattern.slice(0, -1)) === 0 // simple prefix match + || sPattern.match(/\.\*$/) && + sModuleName === sPattern.slice(0, -2) // directory pattern also matches directory itself ); } // Transform to object - let oData = JSON.parse(oChainObject.fileData); + const oData = JSON.parse(oChainObject.fileData); // Attach default component for the library if available if (oChainObject.defaultComponent) { @@ -61,7 +65,6 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Apply formatter's and modify data as needed oData.symbols.forEach((oSymbol) => { - // when the module name starts with the library name, then we apply the default component if (oSymbol.name.indexOf(oData.library) === 0) { oSymbol.component = oChainObject.defaultComponent; @@ -70,7 +73,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Attach symbol specific component if available (special cases) // Note: Last hit wins as there may be a more specific component pattern if (oChainObject.customSymbolComponents) { - Object.keys(oChainObject.customSymbolComponents).forEach(sComponent => { + Object.keys(oChainObject.customSymbolComponents).forEach((sComponent) => { if (matchComponent(oSymbol.name, sComponent)) { oSymbol.component = oChainObject.customSymbolComponents[sComponent]; } @@ -91,7 +94,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { oSymbol.subTitle = formatters.formatSubtitle(oSymbol.deprecated); // Symbol children - let aControlChildren = methods._getControlChildren(oSymbol.name); + const aControlChildren = methods._getControlChildren(oSymbol.name); if (aControlChildren) { oSymbol.nodes = aControlChildren; methods._addChildrenDescription(oData.symbols, oSymbol.nodes); @@ -99,7 +102,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Constructor if (oSymbol.constructor) { - let oConstructor = oSymbol.constructor; + const oConstructor = oSymbol.constructor; // Description if (oConstructor.description) { @@ -131,13 +134,12 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { if (oConstructor.parameters) { oConstructor.parameters = methods.buildConstructorParameters(oConstructor.parameters); - let aParameters = oConstructor.parameters; - aParameters.forEach(oParameter => { - + const aParameters = oConstructor.parameters; + aParameters.forEach((oParameter) => { // Types oParameter.types = []; if (oParameter.type) { - let aTypes = oParameter.type.split("|"); + const aTypes = oParameter.type.split("|"); for (let i = 0; i < aTypes.length; i++) { oParameter.types.push({ @@ -157,14 +159,12 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { if (oParameter.description) { oParameter.description = formatters.formatDescription(oParameter.description); } - - }) + }); } // Throws if (oConstructor.throws) { - oConstructor.throws.forEach(oThrows => { - + oConstructor.throws.forEach((oThrows) => { // Description if (oThrows.description) { oThrows.description = formatters.formatDescription(oThrows.description); @@ -174,14 +174,14 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { if (oThrows.type) { oThrows.linkEnabled = formatters.formatExceptionLink(oThrows.type); } - }); } } // Description if (oSymbol.description) { - oSymbol.description = formatters.formatOverviewDescription(oSymbol.description, oSymbol.constructor.references); + oSymbol.description = + formatters.formatOverviewDescription(oSymbol.description, oSymbol.constructor.references); } // Deprecated @@ -194,7 +194,6 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Properties if (oSymbol.properties) { oSymbol.properties.forEach((oProperty) => { - // Name oProperty.name = formatters.formatEntityName(oProperty.name, oSymbol.name, oProperty.static); @@ -218,18 +217,17 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { if (oProperty.type) { delete oProperty.type; } - }); } // UI5 Metadata if (oSymbol["ui5-metadata"]) { - let oMeta = oSymbol["ui5-metadata"]; + const oMeta = oSymbol["ui5-metadata"]; // Properties if (oMeta.properties) { // Sort - oMeta.properties.sort(function (a, b) { + oMeta.properties.sort(function(a, b) { if (a.name < b.name) { return -1; } else if (a.name > b.name) { @@ -245,7 +243,8 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { oProperty.name = formatters.formatEntityName(oProperty.name, oSymbol.name, oProperty.static); // Description - oProperty.description = formatters.formatDescriptionSince(oProperty.description, oProperty.since); + oProperty.description = + formatters.formatDescriptionSince(oProperty.description, oProperty.since); // Link Enabled if (!isBuiltInType(oProperty.type)) { @@ -269,7 +268,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Aggregations if (oMeta.aggregations) { // Sort - oMeta.aggregations.sort(function (a, b) { + oMeta.aggregations.sort(function(a, b) { if (a.name < b.name) { return -1; } else if (a.name > b.name) { @@ -303,7 +302,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { if (oMeta.associations) { // Sort - oMeta.associations.sort(function (a, b) { + oMeta.associations.sort(function(a, b) { if (a.name < b.name) { return -1; } else if (a.name > b.name) { @@ -338,8 +337,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Special Settings if (oMeta.specialSettings) { - oMeta.specialSettings.forEach(oSetting => { - + oMeta.specialSettings.forEach((oSetting) => { // Link Enabled if (!isBuiltInType(oSetting.type)) { oSetting.linkEnabled = true; @@ -352,14 +350,12 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { } else { oSetting.description = formatters.formatDescription(oSetting.description); } - }); } // Annotations if (oMeta.annotations) { - oMeta.annotations.forEach(oAnnotation => { - + oMeta.annotations.forEach((oAnnotation) => { // Description oAnnotation.description = formatters.formatAnnotationDescription(oAnnotation.description, oAnnotation.since); @@ -373,19 +369,15 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Applies to oAnnotation.appliesTo = formatters.formatAnnotationTarget(oAnnotation.appliesTo); - }); } - } if (oSymbol.events) { - // Pre-process events methods.buildEventsModel(oSymbol.events); - oSymbol.events.forEach(oEvent => { - + oSymbol.events.forEach((oEvent) => { // Description if (oEvent.description) { oEvent.description = formatters.formatDescriptionSince(oEvent.description, oEvent.since); @@ -399,8 +391,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Parameters if (oEvent.parameters && Array.isArray(oEvent.parameters)) { - oEvent.parameters.forEach(oParameter => { - + oEvent.parameters.forEach((oParameter) => { // Link Enabled if (!isBuiltInType(oParameter.type)) { oParameter.linkEnabled = true; @@ -413,22 +404,17 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { } else { oParameter.description = formatters.formatDescription(oParameter.description); } - }); } - }); - } // Methods if (oSymbol.methods) { - // Pre-process methods methods.buildMethodsModel(oSymbol.methods); - oSymbol.methods.forEach(oMethod => { - + oSymbol.methods.forEach((oMethod) => { // Name if (oMethod.name) { oMethod.name = formatters.formatEntityName(oMethod.name, oSymbol.name, oMethod.static); @@ -440,7 +426,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { } // Examples - oMethod.examples && oMethod.examples.forEach(oExample => { + oMethod.examples && oMethod.examples.forEach((oExample) => { oExample = formatters.formatExample(oExample.caption, oExample.text); }); @@ -455,18 +441,15 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Parameters if (oMethod.parameters) { - oMethod.parameters.forEach(oParameter => { - + oMethod.parameters.forEach((oParameter) => { // Types if (oParameter.types) { - oParameter.types.forEach(oType => { - + oParameter.types.forEach((oType) => { // Link Enabled if (!isBuiltInType(oType.value) && possibleUI5Symbol(oType.value)) { oType.linkEnabled = true; oType.href = "#/api/" + oType.value.replace("[]", ""); } - }); } @@ -480,34 +463,28 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { } else { oParameter.description = formatters.formatDescription(oParameter.description); } - }); } // Return value if (oMethod.returnValue) { - // Description oMethod.returnValue.description = formatters.formatDescription(oMethod.returnValue.description); // Types if (oMethod.returnValue.types) { - oMethod.returnValue.types.forEach(oType => { - + oMethod.returnValue.types.forEach((oType) => { // Link Enabled if (!isBuiltInType(oType.value)) { oType.linkEnabled = true; } - }); } - } // Throws if (oMethod.throws) { - oMethod.throws.forEach(oThrows => { - + oMethod.throws.forEach((oThrows) => { // Description if (oThrows.description) { oThrows.description = formatters.formatDescription(oThrows.description); @@ -517,7 +494,6 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { if (oThrows.type) { oThrows.linkEnabled = formatters.formatExceptionLink(oThrows.type); } - }); } @@ -534,13 +510,9 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { delete oExample.text; } }); - } - - }); } - }); oChainObject.parsedData = oData; @@ -550,10 +522,11 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Create api.json from parsed data - * @param oChainObject chain object + * + * @param {Object} oChainObject chain object */ function createApiRefApiJson(oChainObject) { - let sOutputDir = path.dirname(oChainObject.outputFile); + const sOutputDir = path.dirname(oChainObject.outputFile); // Create dir if it does not exist if (!fs.existsSync(sOutputDir)) { @@ -561,17 +534,19 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { } // Write result to file - fs.writeFileSync(oChainObject.outputFile, JSON.stringify(oChainObject.parsedData) /* Transform back to string */, 'utf8'); + fs.writeFileSync(oChainObject.outputFile, + JSON.stringify(oChainObject.parsedData) /* Transform back to string */, "utf8"); } /** * Load .library file - * @param oChainObject chain return object + * + * @param {Object} oChainObject chain return object * @returns {Promise} library file promise */ function getLibraryPromise(oChainObject) { return new Promise(function(oResolve) { - fs.readFile(oChainObject.libraryFile, 'utf8', (oError, oData) => { + fs.readFile(oChainObject.libraryFile, "utf8", (oError, oData) => { oChainObject.libraryFileData = oData; oResolve(oChainObject); }); @@ -580,14 +555,15 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Extracts components list and docuindex.json relative path from .library file data - * @param {object} oChainObject chain object - * @returns {object} chain object + * + * @param {Object} oChainObject chain object + * @returns {Object} chain object */ function extractComponentAndDocuindexUrl(oChainObject) { oChainObject.modules = []; if (oChainObject.libraryFileData) { - let $ = cheerio.load(oChainObject.libraryFileData, { + const $ = cheerio.load(oChainObject.libraryFileData, { ignoreWhitespace: true, xmlMode: true, lowerCaseTags: false @@ -598,13 +574,12 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Extract components $("ownership > component").each((i, oComponent) => { - if (oComponent.children) { if (oComponent.children.length === 1) { oChainObject.defaultComponent = $(oComponent).text(); } else { - let sCurrentComponentName = $(oComponent).find("name").text(); - let aCurrentModules = []; + const sCurrentComponentName = $(oComponent).find("name").text(); + const aCurrentModules = []; $(oComponent).find("module").each((a, oC) => { aCurrentModules.push($(oC).text().replace(/\//g, ".")); }); @@ -615,9 +590,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { }); } } - }); - } return oChainObject; @@ -626,15 +599,16 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Adds to the passed object custom symbol component map generated from the extracted components list * to be easily searchable later - * @param {object} oChainObject chain object - * @returns {object} chain object + * + * @param {Object} oChainObject chain object + * @returns {Object} chain object */ function flattenComponents(oChainObject) { if (oChainObject.modules && oChainObject.modules.length > 0) { oChainObject.customSymbolComponents = {}; - oChainObject.modules.forEach(oComponent => { - let sCurrentComponent = oComponent.componentName; - oComponent.modules.forEach(sModule => { + oChainObject.modules.forEach((oComponent) => { + const sCurrentComponent = oComponent.componentName; + oComponent.modules.forEach((sModule) => { oChainObject.customSymbolComponents[sModule] = sCurrentComponent; }); }); @@ -645,8 +619,9 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Adds to the passed object array with entities which have explored samples - * @param {object} oChainObject chain object - * @returns {object} chain object + * + * @param {Object} oChainObject chain object + * @returns {Object} chain object */ function extractSamplesFromDocuIndex(oChainObject) { // If we have not extracted docuPath we return early @@ -659,12 +634,12 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Normalize path to resolve relative path sPath = path.normalize(sPath); - fs.readFile(sPath, 'utf8', (oError, oFileData) => { + fs.readFile(sPath, "utf8", (oError, oFileData) => { if (!oError) { oFileData = JSON.parse(oFileData); if (oFileData.explored && oFileData.explored.entities && oFileData.explored.entities.length > 0) { oChainObject.entitiesWithSamples = []; - oFileData.explored.entities.forEach(oEntity => { + oFileData.explored.entities.forEach((oEntity) => { oChainObject.entitiesWithSamples.push(oEntity.id); }); } @@ -672,18 +647,18 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // We aways resolve as this data is not mandatory oResolve(oChainObject); }); - }); } /** * Load api.json file - * @param {object} oChainObject chain object - * @returns {object} chain object + * + * @param {Object} oChainObject chain object + * @returns {Object} chain object */ function getAPIJSONPromise(oChainObject) { return new Promise(function(oResolve, oReject) { - fs.readFile(sInputFile, 'utf8', (oError, sFileData) => { + fs.readFile(sInputFile, "utf8", (oError, sFileData) => { if (oError) { oReject(oError); } else { @@ -695,12 +670,12 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { } /* - * ===================================================================================================================== - * IMPORTANT NOTE: Formatter code is a copy from APIDetail.controller.js with a very little modification and mocking and - * code can be significantly improved - * ===================================================================================================================== + * ================================================================================================================ + * IMPORTANT NOTE: Formatter code is a copy from APIDetail.controller.js with a very little modification and + * mocking and code can be significantly improved + * ================================================================================================================ */ - let formatters = { + const formatters = { _sTopicId: "", _oTopicData: {}, @@ -746,16 +721,17 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { "TouchList", "undefined" ], - ANNOTATIONS_LINK: 'http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part3-csdl.html', - ANNOTATIONS_NAMESPACE_LINK: 'http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/vocabularies/', + ANNOTATIONS_LINK: "http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part3-csdl.html", + ANNOTATIONS_NAMESPACE_LINK: "http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/vocabularies/", /** * Adds "deprecated" information if such exists to the header area - * @param deprecated - object containing information about deprecation + * + * @param {Object} deprecated - object containing information about deprecation * @returns {string} - the deprecated text to display */ - formatSubtitle: function (deprecated) { - var result = ""; + formatSubtitle: function(deprecated) { + let result = ""; if (deprecated) { result += "Deprecated in version: " + deprecated.since; @@ -766,15 +742,16 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Formats the target and applies to texts of annotations - * @param target - the array of texts to be formatted - * @returns string - the formatted text + * + * @param {Array} target - the array of texts to be formatted + * @returns {string} - the formatted text */ - formatAnnotationTarget: function (target) { - var result = ""; + formatAnnotationTarget: function(target) { + let result = ""; if (target) { - target.forEach(function (element) { - result += element + '
'; + target.forEach(function(element) { + result += element + "
"; }); } @@ -784,15 +761,18 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Formats the namespace of annotations - * @param namespace - the namespace to be formatted - * @returns string - the formatted text + * + * @param {string} namespace - the namespace to be formatted + * @returns {string} - the formatted text */ - formatAnnotationNamespace: function (namespace) { - var result, - aNamespaceParts = namespace.split("."); + formatAnnotationNamespace: function(namespace) { + let result; + + + const aNamespaceParts = namespace.split("."); if (aNamespaceParts[0] === "Org" && aNamespaceParts[1] === "OData") { - result = '' + namespace + ''; + result = "" + namespace + ""; } else { result = namespace; } @@ -803,55 +783,56 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Formats the description of annotations - * @param description - the description of the annotation - * @param since - the since version information of the annotation - * @returns string - the formatted description + * + * @param {string} description - the description of the annotation + * @param {string} since - the since version information of the annotation + * @returns {string} - the formatted description */ - formatAnnotationDescription: function (description, since) { - var result = description || ""; + formatAnnotationDescription: function(description, since) { + let result = description || ""; - result += '
For more information, see ' + 'OData v4 Annotations'; + result += "
For more information, see " + "OData v4 Annotations"; if (since) { - result += '

Since: ' + since + '.'; + result += "

Since: " + since + "."; } result = this._preProcessLinksInTextBlock(result); return result; }, - formatExceptionLink: function (linkText) { - linkText = linkText || ''; - return linkText.indexOf('sap.ui.') !== -1; + formatExceptionLink: function(linkText) { + linkText = linkText || ""; + return linkText.indexOf("sap.ui.") !== -1; }, - formatMethodCode: function (sName, aParams, aReturnValue) { - var result = '

' + sName + '(';
+		formatMethodCode: function(sName, aParams, aReturnValue) {
+			let result = "
" + sName + "(";
 
 			if (aParams && aParams.length > 0) {
 				/* We consider only root level parameters so we get rid of all that are not on the root level */
-				aParams = aParams.filter(oElem => {
+				aParams = aParams.filter((oElem) => {
 					return oElem.depth === undefined;
 				});
-				aParams.forEach(function (element, index, array) {
+				aParams.forEach(function(element, index, array) {
 					result += element.name;
 
 					if (element.optional) {
-						result += '?';
+						result += "?";
 					}
 
 					if (index < array.length - 1) {
-						result += ', ';
+						result += ", ";
 					}
 				});
 			}
 
-			result += ') : ';
+			result += ") : ";
 
 			if (aReturnValue) {
 				result += aReturnValue.type;
 			} else {
-				result += 'void';
+				result += "void";
 			}
 
 			result += "
"; @@ -861,35 +842,38 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Formats method deprecation message and pre-process jsDoc link and code blocks + * * @param {string} sSince since text * @param {string} sDescription deprecation description text * @returns {string} formatted deprecation message */ - formatMethodDeprecated: function (sSince, sDescription) { + formatMethodDeprecated: function(sSince, sDescription) { return this.formatDeprecated(sSince, sDescription, "methods"); }, /** * Formats event deprecation message and pre-process jsDoc link and code blocks + * * @param {string} sSince since text * @param {string} sDescription deprecation description text * @returns {string} formatted deprecation message */ - formatEventDeprecated: function (sSince, sDescription) { + formatEventDeprecated: function(sSince, sDescription) { return this.formatDeprecated(sSince, sDescription, "events"); }, /** * Formats the description of control properties - * @param description - the description of the property - * @param since - the since version information of the property - * @returns string - the formatted description + * + * @param {string} description - the description of the property + * @param {string} since - the since version information of the property + * @returns {string} - the formatted description */ - formatDescriptionSince: function (description, since) { - var result = description || ""; + formatDescriptionSince: function(description, since) { + let result = description || ""; if (since) { - result += '

Since: ' + since + '.'; + result += "

Since: " + since + "."; } result = this._preProcessLinksInTextBlock(result); @@ -898,62 +882,64 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Formats the default value of the property as a string. - * @param defaultValue - the default value of the property - * @returns string - The default value of the property formatted as a string. + * + * @param {string} defaultValue - the default value of the property + * @returns {string} - The default value of the property formatted as a string. */ - formatDefaultValue: function (defaultValue) { - var sReturn; + formatDefaultValue: function(defaultValue) { + let 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; + return Array.isArray(sReturn) ? sReturn.join(", ") : sReturn; }, /** * Formats the constructor of the class - * @param name - * @param params - * @returns string - The code needed to create an object of that class + * + * @param {string} name + * @param {Array} params + * @returns {string} - The code needed to create an object of that class */ - formatConstructor: function (name, params) { - var result = '
new ';
+		formatConstructor: function(name, params) {
+			let result = "
new ";
 
 			if (name) {
-				result += name + '(';
+				result += name + "(";
 			}
 
 			if (params) {
-				params.forEach(function (element, index, array) {
+				params.forEach(function(element, index, array) {
 					result += element.name;
 
 					if (element.optional) {
-						result += '?';
+						result += "?";
 					}
 
 					if (index < array.length - 1) {
-						result += ', ';
+						result += ", ";
 					}
 				});
 			}
 
 			if (name) {
-				result += ')
'; + result += ")
"; } return result; }, - formatExample: function (sCaption, sText) { + formatExample: function(sCaption, sText) { return this.formatDescription( ["Example: ", sCaption, @@ -965,21 +951,21 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Formats the name of a property or a method depending on if it's static or not - * @param sName {string} - Name - * @param sClassName {string} - Name of the class - * @param bStatic {boolean} - If it's static + * + * @param {string} sName - Name + * @param {string} sClassName - Name of the class + * @param {boolean} bStatic - If it's static * @returns {string} - Formatted name */ - formatEntityName: function (sName, sClassName, bStatic) { + formatEntityName: function(sName, sClassName, bStatic) { return (bStatic === true) ? sClassName + "." + sName : sName; }, - JSDocUtil: function () { - - var rEscapeRegExp = /[[\]{}()*+?.\\^$|]/g; + getJSDocUtil: function() { + const rEscapeRegExp = /[[\]{}()*+?.\\^$|]/g; // Local mocked methods - var escapeRegExp = function escapeRegExp(sString) { + const escapeRegExp = function escapeRegExp(sString) { return sString.replace(rEscapeRegExp, "\\$&"); }; @@ -988,13 +974,15 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { } function format(src, options) { - options = options || {}; - var beforeParagraph = options.beforeParagraph === undefined ? '

' : options.beforeParagraph; - var afterParagraph = options.afterParagraph === undefined ? '

' : options.afterParagraph; - var beforeFirstParagraph = options.beforeFirstParagraph === undefined ? beforeParagraph : options.beforeFirstParagraph; - var afterLastParagraph = options.afterLastParagraph === undefined ? afterParagraph : options.afterLastParagraph; - var linkFormatter = typeof options.linkFormatter === 'function' ? options.linkFormatter : defaultLinkFormatter; + const beforeParagraph = options.beforeParagraph === undefined ? "

" : options.beforeParagraph; + const afterParagraph = options.afterParagraph === undefined ? "

" : options.afterParagraph; + const beforeFirstParagraph = + options.beforeFirstParagraph === undefined ? beforeParagraph : options.beforeFirstParagraph; + const afterLastParagraph = + options.afterLastParagraph === undefined ? afterParagraph : options.afterLastParagraph; + let linkFormatter = + typeof options.linkFormatter === "function" ? options.linkFormatter : defaultLinkFormatter; /* * regexp to recognize important places in the text @@ -1009,40 +997,42 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { * group 7: an empty line which implicitly starts a new paragraph * * [--
 block -] [---- some header ----] [---- an inline [@link ...} tag ----] [---------- an empty line ---------]  */
-				var r = /(
)|(<\/pre>)|()|(<\/h[\d+]>)|\{@link\s+([^}\s]+)(?:\s+([^\}]*))?\}|((?:\r\n|\r|\n)[ \t]*(?:\r\n|\r|\n))/gi;
-				var inpre = false;
+				const r = /(
)|(<\/pre>)|()|(<\/h[\d+]>)|\{@link\s+([^}\s]+)(?:\s+([^\}]*))?\}|((?:\r\n|\r|\n)[ \t]*(?:\r\n|\r|\n))/gi;
+				let inpre = false;
 
-				src = src || '';
+				src = src || "";
 				linkFormatter = linkFormatter || defaultLinkFormatter;
 
-				src = beforeFirstParagraph + src.replace(r, function(match, pre, endpre, header, endheader, linkTarget, linkText, emptyline) {
-					if ( pre ) {
-						inpre = true;
-					} else if ( endpre ) {
-						inpre = false;
-					} else if ( header ) {
-						if ( !inpre ) {
-							return afterParagraph + match;
-						}
-					} else if ( endheader ) {
-						if ( !inpre ) {
-							return match + beforeParagraph;
-						}
-					} else if ( emptyline ) {
-						if ( !inpre ) {
-							return afterParagraph + beforeParagraph;
-						}
-					} else if ( linkTarget ) {
-						if ( !inpre ) {
-							return linkFormatter(linkTarget, linkText);
+				src = beforeFirstParagraph +
+					src.replace(r, function(match, pre, endpre, header, endheader, linkTarget, linkText, emptyline) {
+						if ( pre ) {
+							inpre = true;
+						} else if ( endpre ) {
+							inpre = false;
+						} else if ( header ) {
+							if ( !inpre ) {
+								return afterParagraph + match;
+							}
+						} else if ( endheader ) {
+							if ( !inpre ) {
+								return match + beforeParagraph;
+							}
+						} else if ( emptyline ) {
+							if ( !inpre ) {
+								return afterParagraph + beforeParagraph;
+							}
+						} else if ( linkTarget ) {
+							if ( !inpre ) {
+								return linkFormatter(linkTarget, linkText);
+							}
 						}
-					}
-					return match;
-				}) + afterLastParagraph;
+						return match;
+					}) + afterLastParagraph;
 
 				// remove empty paragraphs
 				if (beforeParagraph !== "" && afterParagraph !== "") {
-					src = src.replace(new RegExp(escapeRegExp(beforeParagraph) + "\\s*" + escapeRegExp(afterParagraph), "g"), "");
+					src = src.replace(
+						new RegExp(escapeRegExp(beforeParagraph) + "\\s*" + escapeRegExp(afterParagraph), "g"), "");
 				}
 
 				return src;
@@ -1051,159 +1041,185 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 			return {
 				formatTextBlock: format
 			};
-
 		},
 
-		/**
+		/*
 		 * Pre-process links in text block
+   		 *
 		 * @param {string} sText text block
 		 * @returns {string} processed text block
 		 * @private
 		 */
-		_preProcessLinksInTextBlock: function (sText, bSkipParagraphs) {
-			var topicsData = this._oTopicData, //this.getModel('topics').oData,
-				topicName = topicsData.name || "",
-				topicMethods = topicsData.methods || [],
-				oOptions = {
-					linkFormatter: function (target, text) {
-						var iHashIndex, // indexOf('#')
-							iHashDotIndex, // indexOf('#.')
-							iHashEventIndex, // indexOf('#event:')
-							aMatched,
-							sRoute = "api",
-							sTargetBase,
-							sScrollHandlerClass = "scrollToMethod",
-							sEntityName,
-							aMatch,
-							sLink;
-
-						text = text || target; // keep the full target in the fallback text
-
-						// If the link has a protocol, do not modify, but open in a new window
-						if (target.match("://")) {
-							return '' + text + '';
-						}
+		_preProcessLinksInTextBlock: function(sText, bSkipParagraphs) {
+			const topicsData = this._oTopicData;
+			// this.getModel('topics').oData,
 
-						target = target.trim().replace(/\.prototype\./g, "#");
-
-						// Link matches the pattern of an static extend method sap.ui.core.Control.extend
-						// BCP: 1780477951
-						aMatch = target.match(/^([a-zA-Z0-9\.]*)\.extend$/);
-						if (aMatch) {
-							// In this case the link should be a link to a static method of the control like for example
-							// #/api/sap.ui.core.Control/methods/sap.ui.core.Control.extend
-							target = aMatch[1] + "/methods/" + aMatch[0];
-							sEntityName = aMatch[1];
-							sScrollHandlerClass = false; // No scroll handler needed
-						} else {
+			const topicName = topicsData.name || "";
+
+
+			const topicMethods = topicsData.methods || [];
+
+
+			const oOptions = {
+				linkFormatter: function(target, text) {
+					let iHashIndex;
+					// indexOf('#')
+
+					let iHashDotIndex;
+					// indexOf('#.')
+
+					let iHashEventIndex;
+					// indexOf('#event:')
+
+					let aMatched;
+
+
+					let sRoute = "api";
+
+
+					let sTargetBase;
+
+
+					let sScrollHandlerClass = "scrollToMethod";
+
+
+					let sEntityName;
+
+
+					let sLink;
+
+					text = text || target; // keep the full target in the fallback text
 
-							iHashIndex = target.indexOf('#');
-							iHashDotIndex = target.indexOf('#.');
-							iHashEventIndex = target.indexOf('#event:');
-
-							if (iHashIndex === -1) {
-								var lastDotIndex = target.lastIndexOf('.'),
-									entityName = sEntityName = target.substring(lastDotIndex + 1),
-									targetMethod = topicMethods.filter(function (method) {
-										if (method.name === entityName) {
-											return method;
-										}
-									})[0];
-
-								if (targetMethod) {
-									if (targetMethod.static === true) {
-										sEntityName = target;
-										// We need to handle links to static methods in a different way if static method is
-										// a child of the current or a different entity
-										sTargetBase = target.replace("." + entityName, "");
-										if (sTargetBase.length > 0 && sTargetBase !== topicName) {
-											// Different entity
-											target = sTargetBase + "/methods/" + target;
-											// We will navigate to a different entity so no scroll is needed
-											sScrollHandlerClass = false;
-										} else {
-											// Current entity
-											target = topicName + '/methods/' + target;
-										}
+					// If the link has a protocol, do not modify, but open in a new window
+					if (target.match("://")) {
+						return "" + text + "";
+					}
+
+					target = target.trim().replace(/\.prototype\./g, "#");
+
+					// Link matches the pattern of an static extend method sap.ui.core.Control.extend
+					// BCP: 1780477951
+					const aMatch = target.match(/^([a-zA-Z0-9\.]*)\.extend$/);
+					if (aMatch) {
+						// In this case the link should be a link to a static method of the control like for example
+						// #/api/sap.ui.core.Control/methods/sap.ui.core.Control.extend
+						target = aMatch[1] + "/methods/" + aMatch[0];
+						sEntityName = aMatch[1];
+						sScrollHandlerClass = false; // No scroll handler needed
+					} else {
+						iHashIndex = target.indexOf("#");
+						iHashDotIndex = target.indexOf("#.");
+						iHashEventIndex = target.indexOf("#event:");
+
+						if (iHashIndex === -1) {
+							const lastDotIndex = target.lastIndexOf(".");
+
+
+							const entityName = sEntityName = target.substring(lastDotIndex + 1);
+
+
+							const targetMethod = topicMethods.filter(function(method) {
+								if (method.name === entityName) {
+									return method;
+								}
+							})[0];
+
+							if (targetMethod) {
+								if (targetMethod.static === true) {
+									sEntityName = target;
+									// We need to handle links to static methods in a different way if static method is
+									// a child of the current or a different entity
+									sTargetBase = target.replace("." + entityName, "");
+									if (sTargetBase.length > 0 && sTargetBase !== topicName) {
+										// Different entity
+										target = sTargetBase + "/methods/" + target;
+										// We will navigate to a different entity so no scroll is needed
+										sScrollHandlerClass = false;
 									} else {
-										target = topicName + '/methods/' + entityName;
+										// Current entity
+										target = topicName + "/methods/" + target;
 									}
 								} else {
-									// Handle links to documentation
-									aMatched = target.match(/^topic:(\w{32})$/);
-									if (aMatched) {
-										target = sEntityName = aMatched[1];
-										sRoute = "topic";
-									}
+									target = topicName + "/methods/" + entityName;
+								}
+							} else {
+								// Handle links to documentation
+								aMatched = target.match(/^topic:(\w{32})$/);
+								if (aMatched) {
+									target = sEntityName = aMatched[1];
+									sRoute = "topic";
 								}
 							}
+						}
 
-							if (iHashDotIndex === 0) {
-								// clear '#.' from target string
-								target = target.slice(2);
-
-								target = topicName + '/methods/' + topicName + '.' + target;
-							} else if (iHashEventIndex >= 0) {
-								//format is 'className#event:eventName'  or  '#event:eventName'
-								var sClassName = target.substring(0, iHashIndex);
-								target = target.substring(iHashIndex);
-
-								// clear '#event:' from target string
-								target = target.slice('#event:'.length);
-
-								if (!sClassName) {
-									sClassName = topicName; // if no className => event is relative to current topicName
-									sScrollHandlerClass = "scrollToEvent"; // mark the element as relative link to the events section
-								}
+						if (iHashDotIndex === 0) {
+							// clear '#.' from target string
+							target = target.slice(2);
 
-								target = sClassName + '/events/' + target;
-								sEntityName = target;
+							target = topicName + "/methods/" + topicName + "." + target;
+						} else if (iHashEventIndex >= 0) {
+							// format is 'className#event:eventName'  or  '#event:eventName'
+							let sClassName = target.substring(0, iHashIndex);
+							target = target.substring(iHashIndex);
 
-							} else if (iHashIndex === 0) {
-								// clear '#' from target string
-								target = target.slice(1);
-								sEntityName = target;
+							// clear '#event:' from target string
+							target = target.slice("#event:".length);
 
-								target = topicName + '/methods/' + target;
-							} else if (iHashIndex > 0) {
-								target = target.replace('#', '/methods/');
-								sEntityName = target;
+							if (!sClassName) {
+								sClassName = topicName; // if no className => event is relative to current topicName
+								// mark the element as relative link to the events section
+								sScrollHandlerClass = "scrollToEvent";
 							}
 
+							target = sClassName + "/events/" + target;
+							sEntityName = target;
+						} else if (iHashIndex === 0) {
+							// clear '#' from target string
+							target = target.slice(1);
+							sEntityName = target;
+
+							target = topicName + "/methods/" + target;
+						} else if (iHashIndex > 0) {
+							target = target.replace("#", "/methods/");
+							sEntityName = target;
 						}
+					}
 
-						sLink = '' + text + '';
+					sLink = "" + text + "";
 
-					}
-				};
+					return sLink;
+				}
+			};
 
 			if (bSkipParagraphs) {
 				oOptions.beforeParagraph = "";
 				oOptions.afterParagraph = "";
 			}
 
-			return this.JSDocUtil().formatTextBlock(sText, oOptions);
+			return this.getJSDocUtil().formatTextBlock(sText, oOptions);
 		},
 
 		/**
 		 * Formatter for Overview section
+  		 *
 		 * @param {string} sDescription - Class about description
-		 * @param {array} aReferences - References
+		 * @param {Array} aReferences - References
 		 * @returns {string} - formatted text block
 		 */
-		formatOverviewDescription: function (sDescription, aReferences) {
-			var iLen,
-				i;
+		formatOverviewDescription: function(sDescription, aReferences) {
+			let iLen;
+
+
+			let i;
 
 			// format references
 			if (aReferences && aReferences.length > 0) {
@@ -1228,17 +1244,18 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 
 		/**
 		 * Formats the description of the property
-		 * @param description - the description of the property
-		 * @param deprecatedText - the text explaining this property is deprecated
-		 * @param deprecatedSince - the version when this property was deprecated
-		 * @returns string - the formatted description
+   		 *
+		 * @param {string} description - the description of the property
+		 * @param {string} deprecatedText - the text explaining this property is deprecated
+		 * @param {string} deprecatedSince - the version when this property was deprecated
+		 * @returns {string} - the formatted description
 		 */
-		formatDescription: function (description, deprecatedText, deprecatedSince) {
+		formatDescription: function(description, deprecatedText, deprecatedSince) {
 			if (!description && !deprecatedText && !deprecatedSince) {
 				return "";
 			}
 
-			var result = description || "";
+			let result = description || "";
 
 			if (deprecatedSince || deprecatedText) {
 				// Note: sapUiDocumentationDeprecated - transformed to sapUiDeprecated to keep json file size low
@@ -1255,25 +1272,24 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 
 		/**
 		 * Formats the entity deprecation message and pre-process jsDoc link and code blocks
+  		 *
 		 * @param {string} sSince since text
 		 * @param {string} sDescription deprecation description text
 		 * @param {string} sEntityType string representation of entity type
 		 * @returns {string} formatted deprecation message
 		 */
-		formatDeprecated: function (sSince, sDescription, sEntityType) {
-			var aResult;
-
+		formatDeprecated: function(sSince, sDescription, sEntityType) {
 			// Build deprecation message
 			// Note: there may be no since or no description text available
-			aResult = ["Deprecated"];
+			const aResult = ["Deprecated"];
 			if (sSince) {
 				aResult.push(" as of version " + sSince);
 			}
 			if (sDescription) {
 				// Evaluate code blocks - Handle ... pattern
-				sDescription = sDescription.replace(/(\S+)<\/code>/gi, function (sMatch, sCodeEntity) {
-						return ['', sCodeEntity, ''].join("");
-					}
+				sDescription = sDescription.replace(/(\S+)<\/code>/gi, function(sMatch, sCodeEntity) {
+					return ["", sCodeEntity, ""].join("");
+				}
 				);
 
 				// Evaluate links in the deprecation description
@@ -1283,30 +1299,32 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 			return aResult.join("");
 		},
 
-		_formatChildDescription: function (description) {
+		_formatChildDescription: function(description) {
 			if (description) {
 				return this._extractFirstSentence(description);
 			}
 		},
 
-		/** Just the first sentence (up to a full stop). Should not break on dotted variable names. */
+		/* Just the first sentence (up to a full stop). Should not break on dotted variable names. */
 		_extractFirstSentence: function(desc) {
 			if ( desc ) {
-				desc = String(desc).replace(/\s+/g, ' ').
-				replace(/^(<\/?p>||\w+<\/h\d>|\s)+/, '');
+				desc = String(desc).replace(/\s+/g, " ").
+					replace(/^(<\/?p>||\w+<\/h\d>|\s)+/, "");
 
-				var match = /([\w\W]+?\.)[^a-z0-9_$]/i.exec(desc);
+				const match = /([\w\W]+?\.)[^a-z0-9_$]/i.exec(desc);
 				return match ? match[1] : desc;
 			}
 			return "";
 		},
 
-		_sliceSpecialTags: function (descriptionCopy, startSymbol, endSymbol) {
-			var startIndex, endIndex;
-			while (descriptionCopy.indexOf(startSymbol) !== -1 && descriptionCopy.indexOf(startSymbol) < descriptionCopy.indexOf(".")) {
+		_sliceSpecialTags: function(descriptionCopy, startSymbol, endSymbol) {
+			let startIndex; let endIndex;
+			while (descriptionCopy.indexOf(startSymbol) !== -1 &&
+					descriptionCopy.indexOf(startSymbol) < descriptionCopy.indexOf(".")) {
 				startIndex = descriptionCopy.indexOf(startSymbol);
 				endIndex = descriptionCopy.indexOf(endSymbol);
-				descriptionCopy = descriptionCopy.slice(0, startIndex) + descriptionCopy.slice(endIndex + endSymbol.length, descriptionCopy.length);
+				descriptionCopy = descriptionCopy.slice(0, startIndex) +
+					descriptionCopy.slice(endIndex + endSymbol.length, descriptionCopy.length);
 			}
 			return descriptionCopy;
 		}
@@ -1314,25 +1332,30 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 	};
 
 	/* Methods direct copy from API Detail */
-	let methods = {
+	const methods = {
 
 		/**
 		 * Pre-process and modify references
-		 * @param {object} oSymbol control data object which will be modified
+   *
+		 * @param {Object} oSymbol control data object which will be modified
 		 * @private
 		 */
-		modifyReferences: function (oSymbol) {
-			var bHeaderDocuLinkFound = false,
-				bUXGuidelinesLinkFound = false,
-				aReferences = [];
+		modifyReferences: function(oSymbol) {
+			let bHeaderDocuLinkFound = false;
+
+
+			let bUXGuidelinesLinkFound = false;
+
+
+			const aReferences = [];
 
 			if (oSymbol.constructor.references && oSymbol.constructor.references.length > 0) {
-				oSymbol.constructor.references.forEach(function (sReference) {
-					var aParts;
+				oSymbol.constructor.references.forEach(function(sReference) {
+					let aParts;
 
-					// Docu link - For the header we take into account only the first link that matches one of the patterns
+					// Docu link - For the header we take into account only the first link that matches
+					//	one of the patterns
 					if (!bHeaderDocuLinkFound) {
-
 						// Handled patterns:
 						// * topic:59a0e11712e84a648bb990a1dba76bc7
 						// * {@link topic:59a0e11712e84a648bb990a1dba76bc7}
@@ -1357,11 +1380,11 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 					// Fiori link - Handled patterns:
 					// * fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/
 					// * {@link fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/}
-					// * {@link fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/ Flexible Column Layout}
+					// * {@link fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/
+					// 	Flexible Column Layout}
 					aParts = sReference.match(/^{@link\s+fiori:(\S+)(\s.+)?}$|^fiori:(\S+)$/);
 
 					if (aParts) {
-
 						if (!bUXGuidelinesLinkFound) {
 							// Extract first found UX Guidelines link as primary
 							if (aParts) {
@@ -1383,7 +1406,6 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 							// BCP: 1870155880 - Every consecutive "fiori:" link should be handled as a normal link
 							sReference = sReference.replace("fiori:", "");
 						}
-
 					}
 
 					aReferences.push(sReference);
@@ -1396,18 +1418,19 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 
 		/**
 		 * Adjusts methods info so that it can be easily displayed in a table
-		 * @param aMethods - the methods array initially coming from the server
+  		 *
+		 * @param {Array} aMethods - the methods array initially coming from the server
 		 */
-		buildMethodsModel: function (aMethods) {
-			var fnCreateTypesArr = function (sTypes) {
-				return sTypes.split("|").map(function (sType) {
-					return {value: sType}
+		buildMethodsModel: function(aMethods) {
+			const fnCreateTypesArr = function(sTypes) {
+				return sTypes.split("|").map(function(sType) {
+					return {value: sType};
 				});
 			};
-			var fnExtractParameterProperties = function (oParameter, aParameters, iDepth, aPhoneName) {
+			const fnExtractParameterProperties = function(oParameter, aParameters, iDepth, aPhoneName) {
 				if (oParameter.parameterProperties) {
-					Object.keys(oParameter.parameterProperties).forEach(function (sProperty) {
-						var oProperty = oParameter.parameterProperties[sProperty];
+					Object.keys(oParameter.parameterProperties).forEach(function(sProperty) {
+						const oProperty = oParameter.parameterProperties[sProperty];
 
 						oProperty.depth = iDepth;
 
@@ -1433,13 +1456,13 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 					delete oParameter.parameterProperties;
 				}
 			};
-			aMethods.forEach(function (oMethod) {
+			aMethods.forEach(function(oMethod) {
 				// New array to hold modified parameters
-				var aParameters = [];
+				const aParameters = [];
 
 				// Handle parameters
 				if (oMethod.parameters) {
-					oMethod.parameters.forEach(function (oParameter) {
+					oMethod.parameters.forEach(function(oParameter) {
 						if (oParameter.type) {
 							oParameter.types = fnCreateTypesArr(oParameter.type);
 						}
@@ -1453,7 +1476,6 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 						// Handle Parameter Properties
 						// Note: We flatten the structure
 						fnExtractParameterProperties(oParameter, aParameters, 1, [oParameter.name]);
-
 					});
 
 					// Override the old data
@@ -1465,25 +1487,24 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 					// Handle types
 					oMethod.returnValue.types = fnCreateTypesArr(oMethod.returnValue.type);
 				}
-
 			});
 		},
 
 		/**
 		 * Adjusts events info so that it can be easily displayed in a table
+   *
 		 * @param {Array} aEvents - the events array initially coming from the server
 		 */
-		buildEventsModel: function (aEvents) {
-			var fnExtractParameterProperties = function (oParameter, aParameters, iDepth, aPhoneName) {
+		buildEventsModel: function(aEvents) {
+			const fnExtractParameterProperties = function(oParameter, aParameters, iDepth, aPhoneName) {
 				if (oParameter.parameterProperties) {
-					Object.keys(oParameter.parameterProperties).forEach(function (sProperty) {
-						var oProperty = oParameter.parameterProperties[sProperty],
-							sPhoneTypeSuffix;
+					Object.keys(oParameter.parameterProperties).forEach(function(sProperty) {
+						const oProperty = oParameter.parameterProperties[sProperty];
 
 						oProperty.depth = iDepth;
 
 						// Phone name - available only for parameters
-						sPhoneTypeSuffix = oProperty.type === "array" ? "[]" : "";
+						const sPhoneTypeSuffix = oProperty.type === "array" ? "[]" : "";
 						oProperty.phoneName = [aPhoneName.join("."), (oProperty.name + sPhoneTypeSuffix)].join(".");
 
 						// Add property to parameter array as we need a simple structure
@@ -1498,13 +1519,13 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 					delete oParameter.parameterProperties;
 				}
 			};
-			aEvents.forEach(function (aEvents) {
+			aEvents.forEach(function(aEvents) {
 				// New array to hold modified parameters
-				var aParameters = [];
+				const aParameters = [];
 
 				// Handle parameters
 				if (aEvents.parameters) {
-					aEvents.parameters.forEach(function (oParameter) {
+					aEvents.parameters.forEach(function(oParameter) {
 						// Add the parameter before the properties
 						aParameters.push(oParameter);
 
@@ -1521,32 +1542,35 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 
 		/**
 		 * Adjusts constructor parameters info so that it can be easily displayed in a table
+   *
 		 * @param {Array} aParameters - the events array initially coming from the server
 		 */
-		buildConstructorParameters: function (aParameters) {
+		buildConstructorParameters: function(aParameters) {
 			// New array to hold modified parameters
-			var aNodes = [],
-				processNode = function (oNode, sPhoneName, iDepth, aNodes) {
-					// Handle phone name
-					oNode.phoneName = sPhoneName ? [sPhoneName, oNode.name].join(".") : oNode.name;
+			const aNodes = [];
 
-					// Depth
-					oNode.depth = iDepth;
 
-					// Add to array
-					aNodes.push(oNode);
+			const processNode = function(oNode, sPhoneName, iDepth, aNodes) {
+				// Handle phone name
+				oNode.phoneName = sPhoneName ? [sPhoneName, oNode.name].join(".") : oNode.name;
 
-					// Handle nesting
-					if (oNode.parameterProperties) {
-						Object.keys(oNode.parameterProperties).forEach(function (sNode) {
-							processNode(oNode.parameterProperties[sNode], oNode.phoneName, (iDepth + 1), aNodes);
-						});
-					}
+				// Depth
+				oNode.depth = iDepth;
 
-					delete oNode.parameterProperties;
-				};
+				// Add to array
+				aNodes.push(oNode);
+
+				// Handle nesting
+				if (oNode.parameterProperties) {
+					Object.keys(oNode.parameterProperties).forEach(function(sNode) {
+						processNode(oNode.parameterProperties[sNode], oNode.phoneName, (iDepth + 1), aNodes);
+					});
+				}
+
+				delete oNode.parameterProperties;
+			};
 
-			aParameters.forEach(function (oParameter) {
+			aParameters.forEach(function(oParameter) {
 				// Handle Parameter Properties
 				// Note: We flatten the structure
 				processNode(oParameter, undefined, 0, aNodes);
@@ -1558,35 +1582,45 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 		oLibsData: {},
 		aTreeContent: [],
 
-		_getControlChildren: function (sTopicId) {
+		_getControlChildren: function(sTopicId) {
 			// Find tree node
-			var findTreeNode = function (aNodes, sTopicId) {
-					var iLen,
-						oNode,
-						i;
-
-					for (i = 0, iLen = aNodes.length; i < iLen; i++) {
-						oNode = aNodes[i];
-						if (oNode.name === sTopicId) {
+			const findTreeNode = function(aNodes, sTopicId) {
+				let iLen;
+
+
+				let oNode;
+
+
+				let i;
+
+				for (i = 0, iLen = aNodes.length; i < iLen; i++) {
+					oNode = aNodes[i];
+					if (oNode.name === sTopicId) {
+						return oNode;
+					}
+					if (oNode.nodes) {
+						oNode = findTreeNode(aNodes[i].nodes, sTopicId);
+						if (oNode) {
 							return oNode;
 						}
-						if (oNode.nodes) {
-							oNode = findTreeNode(aNodes[i].nodes, sTopicId);
-							if (oNode) {
-								return oNode;
-							}
-						}
 					}
-				},
-				oNode = findTreeNode(this.aTreeContent, sTopicId);
+				}
+			};
+
+
+			const oNode = findTreeNode(this.aTreeContent, sTopicId);
 
 			return oNode.nodes ? oNode.nodes : false;
 		},
 
-		_parseLibraryElements : function (aLibraryElements) {
-			var oLibraryElement,
-				aNodes,
-				i;
+		_parseLibraryElements: function(aLibraryElements) {
+			let oLibraryElement;
+
+
+			let aNodes;
+
+
+			let i;
 
 			for (i = 0; i < aLibraryElements.length; i++) {
 				oLibraryElement = aLibraryElements[i];
@@ -1606,15 +1640,23 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 			return this.aTreeContent;
 		},
 
-		_addElementToTreeData : function (oJSONElement, aLibraryElements) {
-			var oNewNodeNamespace;
+		_addElementToTreeData: function(oJSONElement, aLibraryElements) {
+			let oNewNodeNamespace;
 
 			if (oJSONElement.kind !== "namespace") {
-				var aNameParts = oJSONElement.name.split("."),
-					sBaseName = aNameParts.pop(),
-					sNodeNamespace = aNameParts.join("."), // Note: Array.pop() on the previous line modifies the array itself
-					oTreeNode = this._createTreeNode(sBaseName, oJSONElement.name),
-					oExistingNodeNamespace = this._findNodeNamespaceInTreeStructure(sNodeNamespace);
+				const aNameParts = oJSONElement.name.split(".");
+
+
+				const sBaseName = aNameParts.pop();
+
+
+				const sNodeNamespace = aNameParts.join(".");
+				// Note: Array.pop() on the previous line modifies the array itself
+
+				const oTreeNode = this._createTreeNode(sBaseName, oJSONElement.name);
+
+
+				const oExistingNodeNamespace = this._findNodeNamespaceInTreeStructure(sNodeNamespace);
 
 				if (oExistingNodeNamespace) {
 					if (!oExistingNodeNamespace.nodes) {
@@ -1635,7 +1677,6 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 						name: sNodeNamespace,
 						ref: "#/api/" + sNodeNamespace
 					});
-
 				} else {
 					// Entities for which we can't resolve namespace we are shown in the root level
 					oNewNodeNamespace = this._createTreeNode(oJSONElement.name, oJSONElement.name);
@@ -1647,23 +1688,23 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 			}
 		},
 
-		_createTreeNode : function (text, name, sLib) {
-			var oTreeNode = {};
+		_createTreeNode: function(text, name, sLib) {
+			const oTreeNode = {};
 			oTreeNode.text = text;
 			oTreeNode.name = name;
 			oTreeNode.ref = "#/api/" + name;
 			return oTreeNode;
 		},
 
-		_findNodeNamespaceInTreeStructure : function (sNodeNamespace, aTreeStructure) {
+		_findNodeNamespaceInTreeStructure: function(sNodeNamespace, aTreeStructure) {
 			aTreeStructure = aTreeStructure || this.aTreeContent;
-			for (var i = 0; i < aTreeStructure.length; i++) {
-				var oTreeNode = aTreeStructure[i];
+			for (let i = 0; i < aTreeStructure.length; i++) {
+				const oTreeNode = aTreeStructure[i];
 				if (oTreeNode.name === sNodeNamespace) {
 					return oTreeNode;
 				}
 				if (oTreeNode.nodes) {
-					var oChildNode = this._findNodeNamespaceInTreeStructure(sNodeNamespace, oTreeNode.nodes);
+					const oChildNode = this._findNodeNamespaceInTreeStructure(sNodeNamespace, oTreeNode.nodes);
 					if (oChildNode) {
 						return oChildNode;
 					}
@@ -1671,8 +1712,8 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 			}
 		},
 
-		_removeNodeFromNamespace : function (sNode, oNamespace) {
-			for (var i = 0; i < oNamespace.nodes.length; i++) {
+		_removeNodeFromNamespace: function(sNode, oNamespace) {
+			for (let i = 0; i < oNamespace.nodes.length; i++) {
 				if (oNamespace.nodes[i].text === sNode) {
 					oNamespace.nodes.splice(i, 1);
 					return;
@@ -1680,18 +1721,20 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 			}
 		},
 
-		_removeDuplicatedNodeFromTree : function (sNodeFullName) {
+		_removeDuplicatedNodeFromTree: function(sNodeFullName) {
 			if (this.oLibsData[sNodeFullName]) {
-				var sNodeNamespace = sNodeFullName.substring(0, sNodeFullName.lastIndexOf("."));
-				var oNamespace = this._findNodeNamespaceInTreeStructure(sNodeNamespace);
-				var sNode = sNodeFullName.substring(sNodeFullName.lastIndexOf(".") + 1, sNodeFullName.lenght);
+				const sNodeNamespace = sNodeFullName.substring(0, sNodeFullName.lastIndexOf("."));
+				const oNamespace = this._findNodeNamespaceInTreeStructure(sNodeNamespace);
+				const sNode = sNodeFullName.substring(sNodeFullName.lastIndexOf(".") + 1, sNodeFullName.lenght);
 				this._removeNodeFromNamespace(sNode, oNamespace);
 			}
 		},
-		_addChildrenDescription: function (aLibsData, aControlChildren) {
-			function getDataByName (sName) {
-				var iLen,
-					i;
+		_addChildrenDescription: function(aLibsData, aControlChildren) {
+			function getDataByName(sName) {
+				let iLen;
+
+
+				let i;
 
 				for (i = 0, iLen = aLibsData.length; i < iLen; i++) {
 					if (aLibsData[i].name === sName) {
@@ -1700,7 +1743,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 				}
 				return false;
 			}
-			for (var i = 0; i < aControlChildren.length; i++) {
+			for (let i = 0; i < aControlChildren.length; i++) {
 				aControlChildren[i].description = formatters._formatChildDescription(getDataByName(aControlChildren[i].name).description);
 				aControlChildren[i].description = formatters._preProcessLinksInTextBlock(aControlChildren[i].description, true);
 
@@ -1713,14 +1756,14 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 	};
 
 	// Create the chain object
-	let oChainObject = {
+	const oChainObject = {
 		inputFile: sInputFile,
 		outputFile: sOutputFile,
 		libraryFile: sLibraryFile
 	};
 
 	// Start the work here
-	var p = getLibraryPromise(oChainObject)
+	const p = getLibraryPromise(oChainObject)
 		.then(extractComponentAndDocuindexUrl)
 		.then(flattenComponents)
 		.then(extractSamplesFromDocuIndex)
@@ -1728,5 +1771,4 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
 		.then(transformApiJson)
 		.then(createApiRefApiJson);
 	return p;
-
-}
+};
diff --git a/lib/processors/jsdoc/ui5/plugin.js b/lib/processors/jsdoc/ui5/plugin.js
index 9a323ea06..bcc9c1e2a 100644
--- a/lib/processors/jsdoc/ui5/plugin.js
+++ b/lib/processors/jsdoc/ui5/plugin.js
@@ -1,14 +1,14 @@
 /*
  * JSDoc3 plugin for UI5 documentation generation.
- * 
+ *
  * (c) Copyright 2009-2018 SAP SE or an SAP affiliate company.
  * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
  */
 
 /* global require, exports, env */
-/* eslint strict: [2, "global"]*/
+/* eslint strict: [2, "global"], max-len: ["warn", 180],  no-console: "off", guard-for-in: "off" */
 
-'use strict';
+"use strict";
 
 /**
  * UI5 plugin for JSDoc3 (3.3.0-alpha5)
@@ -52,11 +52,11 @@
  */
 
 /* imports */
-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) || {};
+const Syntax = require("jsdoc/src/syntax").Syntax;
+const Doclet = require("jsdoc/doclet").Doclet;
+const fs = require("jsdoc/fs");
+const path = require("jsdoc/path");
+const pluginConfig = (env.conf && env.conf.templates && env.conf.templates.ui5) || {};
 
 /* ---- global vars---- */
 
@@ -65,7 +65,7 @@ var pluginConfig = (env.conf && env.conf.templates && env.conf.templates.ui5) ||
  *
  * Will be determined in the handler for the parseBegin event
  */
-var pathPrefixes = [];
+let pathPrefixes = [];
 
 /**
  * Prefixes of the UI5 unified resource name for the source files is NOT part of the file name.
@@ -73,182 +73,180 @@ var pathPrefixes = [];
  *
  * The prefix will be prepended to all resource names.
  */
-var resourceNamePrefixes = [];
+let resourceNamePrefixes = [];
 
 /**
  * A UI5 specific unique Id for all doclets.
  */
-var docletUid = 0;
+let docletUid = 0;
 
-var currentProgram;
+let currentProgram;
 
 /**
- * Information about the current module. 
- * 
- * The info object is created in the 'fileBegin' event handler and the 'resource' and 'module' properties 
- * are derived from the filename provided by the event. The derived information is only correct, when the 
+ * Information about the current module.
+ *
+ * The info object is created in the 'fileBegin' event handler and the 'resource' and 'module' properties
+ * are derived from the filename provided by the event. The derived information is only correct, when the
  * resource name prefix is known for the directory from which a source is loaded (prefixes can be configured
  * via sapui5.resourceNamePrefixes, for UI5 libraries it is empty by default).
- * 
- * During AST visiting, the 'name' property and the 'localeNames' map will be filled. 
- * 'name' will be the name of the class defined by the module (assuming that there is only one). 
- * 'localNames' will contain information objects for each parameter of an AMD Factory function and for 
+ *
+ * During AST visiting, the 'name' property and the 'localeNames' map will be filled.
+ * 'name' will be the name of the class defined by the module (assuming that there is only one).
+ * 'localNames' will contain information objects for each parameter of an AMD Factory function and for
  * all shortcut variables that are defined top-level in the module factory function (e.g. something like
  *    var ButtonDesign = coreLibrary.ButtonDesign; ).
- * An info object for a local name either can have a 'value' property (simple, constant value) or it can 
- * have a 'module' and optionally a 'path' value. In that case, the local name represents an AMD 
+ * An info object for a local name either can have a 'value' property (simple, constant value) or it can
+ * 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}}
  */
-var currentModule;
+let currentModule;
 
-var currentSource;
+let currentSource;
 
 /**
  * Cached UI5 metadata for encountered UI5 classes.
- * 
+ *
  * The metadata is collected from the 'metadata' property of 'extend' calls. It is stored
- * in this map keyed by the name of the class (as defined in the first parameter of the extend call). 
+ * in this map keyed by the name of the class (as defined in the first parameter of the extend call).
  * Only after all files have been parsed, the collected information can be associated with the
- * corresponding JSDoc doclet (e.g. with the class documentation). 
+ * corresponding JSDoc doclet (e.g. with the class documentation).
  */
-var classInfos = Object.create(null);
+const classInfos = Object.create(null);
 
 /**
- * 
+ *
  */
-var typeInfos = Object.create(null);
+const typeInfos = Object.create(null);
 
 /**
  * Cached designtime info for encountered sources.
- * 
+ *
  * The designtime information is collected only for files named '*.designtime.js'.
  * It is stored in this map keyed by the corresponding module name (e.g. 'sap/m/designtime/Button.designtime').
- * Only after all files have been parsed, the collected information can be associated with runtime metadata 
+ * Only after all files have been parsed, the collected information can be associated with runtime metadata
  * that refers to that designtime module name.
  */
-var designtimeInfos = Object.create(null);
+const designtimeInfos = Object.create(null);
 
 /* ---- private functions ---- */
 
 function ui5data(doclet) {
-	return doclet.__ui5 || (doclet.__ui5 = { id: ++docletUid });
+	return doclet.__ui5 || (doclet.__ui5 = {id: ++docletUid});
 }
 
-var pendingMessageHeader;
+let pendingMessageHeader;
 
 function msgHeader(str) {
 	pendingMessageHeader = str;
 }
 
-function debug() {
+function debug(...args) {
 	if ( env.opts.debug ) {
-		console.log.apply(console, arguments);
+		console.log(...args);
 	}
 }
 
-function info() {
+function info(...args) {
 	if ( env.opts.verbose || env.opts.debug ) {
 		if ( pendingMessageHeader ) {
 			console.log("");
 			pendingMessageHeader = null;
 		}
-		console.log.apply(console, arguments);
+		console.log(...args);
 	}
 }
 
-function warning(msg) {
+function warning(...args) {
 	if ( pendingMessageHeader ) {
-		if ( !env.opts.verbose && !env.opts.debug  ) {
+		if ( !env.opts.verbose && !env.opts.debug ) {
 			console.log(pendingMessageHeader);
 		} else {
 			console.log("");
 		}
 		pendingMessageHeader = null;
 	}
-	var args = Array.prototype.slice.apply(arguments);
 	args[0] = "**** warning: " + args[0];
-	console.log.apply(console, args);
+	console.log(...args);
 }
 
-function error(msg) {
+function error(...args) {
 	if ( pendingMessageHeader && !env.opts.verbose && !env.opts.debug ) {
-		if ( !env.opts.verbose && !env.opts.debug  ) {
+		if ( !env.opts.verbose && !env.opts.debug ) {
 			console.log(pendingMessageHeader);
 		} else {
 			console.log("");
 		}
 		pendingMessageHeader = null;
 	}
-	var args = Array.prototype.slice.apply(arguments);
 	args[0] = "**** error: " + args[0];
-	console.log.apply(console, args);
+	console.log(...args);
 }
 
-//---- path handling ---------------------------------------------------------
+// ---- path handling ---------------------------------------------------------
 
 function ensureEndingSlash(path) {
-	path = path || '';
-	return path && path.slice(-1) !== '/' ? path + '/' : path;
+	path = path || "";
+	return path && path.slice(-1) !== "/" ? path + "/" : path;
 }
 
 function getRelativePath(filename) {
-	var relative = path.resolve(filename);
-	for ( var i = 0; i < pathPrefixes.length; i++ ) {
+	let relative = path.resolve(filename);
+	for ( let i = 0; i < pathPrefixes.length; i++ ) {
 		if ( relative.indexOf(pathPrefixes[i]) === 0 ) {
 			relative = relative.slice(pathPrefixes[i].length);
 			break;
 		}
 	}
-	return relative.replace(/\\/g, '/');
+	return relative.replace(/\\/g, "/");
 }
 
 function getResourceName(filename) {
-	var resource = path.resolve(filename);
-	for ( var i = 0; i < pathPrefixes.length; i++ ) {
+	let resource = path.resolve(filename);
+	for ( let i = 0; i < pathPrefixes.length; i++ ) {
 		if ( resource.indexOf(pathPrefixes[i]) === 0 ) {
 			resource = resourceNamePrefixes[i] + resource.slice(pathPrefixes[i].length);
 			break;
 		}
 	}
-	return resource.replace(/\\/g, '/');
+	return resource.replace(/\\/g, "/");
 }
 
 function getModuleName(resource) {
-	return resource.replace(/\.js$/,'');
+	return resource.replace(/\.js$/, "");
 }
 
 /*
  * resolves relative AMD module identifiers relative to a given base name
  */
 function resolveModuleName(base, name) {
-	var stack = base.split('/');
+	let stack = base.split("/");
 	stack.pop();
-	name.split('/').forEach(function(segment, i) {
-		if ( segment == '..' ) {
+	name.split("/").forEach(function(segment, i) {
+		if ( segment == ".." ) {
 			stack.pop();
-		} else if ( segment === '.' ) {
+		} else if ( segment === "." ) {
 			// ignore
-		} else { 
+		} else {
 			if ( i === 0 ) {
 				stack = [];
 			}
 			stack.push(segment);
 		}
 	});
-	return stack.join('/');
+	return stack.join("/");
 }
 
 // ---- AMD handling
 
 function analyzeModuleDefinition(node) {
-	var args = node.arguments; 
-	var arg = 0;
-	if ( arg < args.length 
-		 && args[arg].type === Syntax.Literal && typeof args[arg].value === 'string' ) {
+	const args = node.arguments;
+	let arg = 0;
+	if ( arg < args.length
+			&& args[arg].type === Syntax.Literal && typeof args[arg].value === "string" ) {
 		warning("module explicitly defined a module name '" + args[arg].value + "'");
 		currentModule.name = args[arg].value;
 		arg++;
@@ -262,9 +260,9 @@ function analyzeModuleDefinition(node) {
 		arg++;
 	}
 	if ( currentModule.dependencies && currentModule.factory ) {
-		for ( var i = 0; i < currentModule.dependencies.length && i < currentModule.factory.params.length; i++ ) {
-			var name = currentModule.factory.params[i].name;
-			var module = resolveModuleName(currentModule.module, currentModule.dependencies[i]);
+		for ( let i = 0; i < currentModule.dependencies.length && i < currentModule.factory.params.length; i++ ) {
+			const name = currentModule.factory.params[i].name;
+			const module = resolveModuleName(currentModule.module, currentModule.dependencies[i]);
 			debug("  import " + name + " from '" + module + "'");
 			currentModule.localNames[name] = {
 				module: module
@@ -281,40 +279,39 @@ 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)
  * or because they have a (design time) constant value.
- * 
+ *
  * @param {ASTNode} body
  */
 function collectShortcuts(body) {
-
 	function checkAssignment(name, valueNode) {
 		if ( valueNode.type === Syntax.Literal ) {
 			currentModule.localNames[name] = {
-				value: valueNode.value	
+				value: valueNode.value
 			};
 			debug("compile time constant found ", name, valueNode.value);
 		} else if ( valueNode.type === Syntax.MemberExpression ) {
-			var _import = getLeftmostName(valueNode);
-			var local = _import && currentModule.localNames[_import];
-			if ( typeof local === 'object' && local.module ) {
+			const _import = getLeftmostName(valueNode);
+			const local = _import && currentModule.localNames[_import];
+			if ( typeof local === "object" && local.module ) {
 				currentModule.localNames[name] = {
 					module: local.module,
-					path: getObjectName(valueNode).split('.').slice(1).join('.') // TODO chaining if local has path
+					path: getObjectName(valueNode).split(".").slice(1).join(".") // TODO chaining if local has path
 				};
 				debug("  found local shortcut: ", name, currentModule.localNames[name]);
 			}
 		}
-	}	
+	}
 
 	if ( body.type === Syntax.BlockStatement ) {
-		body.body.forEach(function ( stmt ) {
+		body.body.forEach(function( stmt ) {
 			// console.log(stmt);
 			if ( stmt.type === Syntax.VariableDeclaration ) {
 				stmt.declarations.forEach(function(decl) {
 					if ( decl.init ) {
 						checkAssignment(decl.id.name, decl.init);
 					}
-				})
-			} else if ( stmt.type === Syntax.ExpressionStatement 
+				});
+			} else if ( stmt.type === Syntax.ExpressionStatement
 						&& stmt.expression.type === Syntax.AssignmentExpression
 						&& stmt.expression.left.type === Syntax.Identifier ) {
 				checkAssignment(stmt.expression.left.name, stmt.expression.right);
@@ -325,13 +322,14 @@ function collectShortcuts(body) {
 
 // ---- text handling ---------------------------------------------------------
 
-var rPlural = /(children|ies|ves|oes|ses|ches|shes|xes|s)$/i;
-var mSingular = {'children' : -3, 'ies' : 'y', 'ves' : 'f', 'oes' : -2, 'ses' : -2, 'ches' : -2, 'shes' : -2, 'xes' : -2, 's' : -1 };
+const rPlural = /(children|ies|ves|oes|ses|ches|shes|xes|s)$/i;
+const mSingular =
+	{"children": -3, "ies": "y", "ves": "f", "oes": -2, "ses": -2, "ches": -2, "shes": -2, "xes": -2, "s": -1};
 
 function guessSingularName(sPluralName) {
-	return sPluralName.replace(rPlural, function($,sPlural) {
-		var vRepl = mSingular[sPlural.toLowerCase()];
-		return typeof vRepl === "string" ? vRepl : sPlural.slice(0,vRepl);
+	return sPluralName.replace(rPlural, function($, sPlural) {
+		const vRepl = mSingular[sPlural.toLowerCase()];
+		return typeof vRepl === "string" ? vRepl : sPlural.slice(0, vRepl);
 	});
 }
 
@@ -342,20 +340,19 @@ function guessSingularName(sPluralName) {
  * It would be more convenient to just return the values, but the property node is needed
  * to find the corresponding (preceding) documentation comment.
  *
- * @param node
- * @param defaultKey
+ * @param {Object} node
+ * @param {string} defaultKey
  * @returns {Map}
  */
 function createPropertyMap(node, defaultKey) {
-
-	var result;
+	let result;
 
 	if ( node != null ) {
-
-		// if, instead of an object literal only a literal is given and there is a defaultKey, then wrap the literal in a map
+		// if, instead of an object literal only a literal is given and there is a defaultKey,
+		// then wrap the literal in a map
 		if ( node.type === Syntax.Literal && defaultKey != null ) {
 			result = {};
-			result[defaultKey] = { type: Syntax.Property, value: node };
+			result[defaultKey] = {type: Syntax.Property, value: node};
 			return result;
 		}
 
@@ -368,10 +365,10 @@ function createPropertyMap(node, defaultKey) {
 
 		// invariant: node.type == Syntax.ObjectExpression
 		result = {};
-		for (var i = 0; i < node.properties.length; i++) {
-			var prop = node.properties[i];
-			var name;
-			//console.log("objectproperty " + prop.type);
+		for (let i = 0; i < node.properties.length; i++) {
+			const prop = node.properties[i];
+			let name;
+			// console.log("objectproperty " + prop.type);
 			if ( prop.key.type === Syntax.Identifier ) {
 				name = prop.key.name;
 			} else if ( prop.key.type === Syntax.Literal ) {
@@ -379,7 +376,7 @@ function createPropertyMap(node, defaultKey) {
 			} else {
 				name = prop.key.toSource();
 			}
-			//console.log("objectproperty " + prop.type + ":" + name);
+			// console.log("objectproperty " + prop.type + ":" + name);
 			result[name] = prop;
 		}
 	}
@@ -387,36 +384,32 @@ function createPropertyMap(node, defaultKey) {
 }
 
 function isExtendCall(node) {
-
 	return (
 		node
 		&& node.type === Syntax.CallExpression
 		&& node.callee.type === Syntax.MemberExpression
 		&& node.callee.property.type === Syntax.Identifier
-		&& node.callee.property.name === 'extend'
+		&& node.callee.property.name === "extend"
 		&& node.arguments.length >= 2
 		&& node.arguments[0].type === Syntax.Literal
 		&& typeof node.arguments[0].value === "string"
 		&& node.arguments[1].type === Syntax.ObjectExpression
 	);
-
 }
 
 function isSapUiDefineCall(node) {
-
 	return (
 		node
 		&& node.type === Syntax.CallExpression
 		&& node.callee.type === Syntax.MemberExpression
 		&& node.callee.object.type === Syntax.MemberExpression
 		&& node.callee.object.object.type === Syntax.Identifier
-		&& node.callee.object.object.name === 'sap'
+		&& node.callee.object.object.name === "sap"
 		&& node.callee.object.property.type === Syntax.Identifier
-		&& node.callee.object.property.name === 'ui'
+		&& node.callee.object.property.name === "ui"
 		&& node.callee.property.type === Syntax.Identifier
-		&& node.callee.property.name === 'define'
+		&& node.callee.property.name === "define"
 	);
-
 }
 
 function isCreateDataTypeCall(node) {
@@ -426,13 +419,13 @@ function isCreateDataTypeCall(node) {
 		&& node.callee.type === Syntax.MemberExpression
 		&& /^(sap\.ui\.base\.)?DataType$/.test(getObjectName(node.callee.object))
 		&& node.callee.property.type === Syntax.Identifier
-		&& node.callee.property.name === 'createType'
+		&& node.callee.property.name === "createType"
 	);
 }
 
 function getObjectName(node) {
 	if ( node.type === Syntax.MemberExpression && !node.computed && node.property.type === Syntax.Identifier ) {
-		var prefix = getObjectName(node.object);
+		const prefix = getObjectName(node.object);
 		return prefix ? prefix + "." + node.property.name : null;
 	} else if ( node.type === Syntax.Identifier ) {
 		return /* scope[node.name] ? scope[node.name] : */ node.name;
@@ -442,7 +435,7 @@ function getObjectName(node) {
 }
 
 /*
- * Checks whether the node is a qualified name (a.b.c) and if so, 
+ * Checks whether the node is a qualified name (a.b.c) and if so,
  * returns the leftmost identifier a
  */
 function getLeftmostName(node) {
@@ -456,16 +449,16 @@ function getLeftmostName(node) {
 }
 
 function getResolvedObjectName(node) {
-	var name = getObjectName(node);
-	var _import = getLeftmostName(node);
-	var local = _import && currentModule.localNames[_import];
+	const name = getObjectName(node);
+	const _import = getLeftmostName(node);
+	const local = _import && currentModule.localNames[_import];
 	if ( local && local.module ) {
-		var resolvedName = local.module.replace(/\//g, ".").replace(/\.library$/, "");
+		let resolvedName = local.module.replace(/\//g, ".").replace(/\.library$/, "");
 		if ( local.path ) {
 			resolvedName = resolvedName + "." + local.path;
 		}
-		if ( name.indexOf('.') > 0 ) {
-			resolvedName = resolvedName + name.slice(name.indexOf('.'));
+		if ( name.indexOf(".") > 0 ) {
+			resolvedName = resolvedName + name.slice(name.indexOf("."));
 		}
 		debug("resolved " + name + " to " + resolvedName);
 		return resolvedName;
@@ -474,76 +467,68 @@ function getResolvedObjectName(node) {
 }
 
 function convertValue(node, type, propertyName) {
-
-	var value;
+	let value;
 
 	if ( node.type === Syntax.Literal ) {
-
 		// 'string' or number or true or false
 		return node.value;
-
 	} else if ( node.type === Syntax.UnaryExpression
 		&& node.prefix
 		&& node.argument.type === Syntax.Literal
-		&& typeof node.argument.value === 'number'
-		&& ( node.operator === '-' || node.operator === '+' )) {
-
+		&& typeof node.argument.value === "number"
+		&& ( node.operator === "-" || node.operator === "+" )) {
 		// -n or +n
 		value = node.argument.value;
-		return node.operator === '-' ? -value : value;
-
+		return node.operator === "-" ? -value : value;
 	} else if ( node.type === Syntax.MemberExpression && type ) {
-
 		// enum value (a.b.c)
 		value = getResolvedObjectName(node);
 		if ( value.indexOf(type + ".") === 0 ) {
 			// starts with fully qualified enum name -> cut off name
 			return value.slice(type.length + 1);
-//		} else if ( value.indexOf(type.split(".").slice(-1)[0] + ".") === 0 ) {
-//			// unqualified name might be a local name (just a guess - would need static code analysis for proper solution)
-//			return value.slice(type.split(".").slice(-1)[0].length + 1);
+			// } else if ( value.indexOf(type.split(".").slice(-1)[0] + ".") === 0 ) {
+			// // unqualified name might be a local name (just a guess -
+			// //	would need static code analysis for proper solution)
+			// return value.slice(type.split(".").slice(-1)[0].length + 1);
 		} else {
-			warning("did not understand default value '%s'%s, falling back to source", value, propertyName ? " of property '" + propertyName + "'" : "");
+			warning("did not understand default value '%s'%s, falling back to source", value,
+				propertyName ? " of property '" + propertyName + "'" : "");
 			return value;
 		}
-
 	} else if ( node.type === Syntax.Identifier ) {
-		if ( node.name === 'undefined') {
+		if ( node.name === "undefined") {
 			// undefined
 			return undefined;
 		}
-		var local = currentModule.localNames[node.name];
-		if ( typeof local === 'object' && 'value' in local ) {
+		const local = currentModule.localNames[node.name];
+		if ( typeof local === "object" && "value" in local ) {
 			// TODO check type
 			return local.value;
 		}
 	} else if ( node.type === Syntax.ArrayExpression ) {
-
 		if ( node.elements.length === 0 ) {
 			// empty array literal
 			return "[]"; // TODO return this string or an empty array
 		}
-		
+
 		if ( type && type.slice(-2) === "[]" ) {
-			var componentType = type.slice(0,-2);
+			const componentType = type.slice(0, -2);
 			return node.elements.map( function(elem) {
 				return convertValue(elem, componentType, propertyName);
 			});
 		}
-
 	} else if ( node.type === Syntax.ObjectExpression ) {
-
-		if ( node.properties.length === 0 && (type === 'object' || type === 'any') ) {
+		if ( node.properties.length === 0 && (type === "object" || type === "any") ) {
 			return {};
 		}
-
 	}
 
-	value = '???';
+	value = "???";
 	if ( currentSource && node.range ) {
 		value = currentSource.slice( node.range[0], node.range[1] );
 	}
-	error("unexpected type of default value (type='%s', source='%s')%s, falling back to '%s'", node.type, node.toString(), propertyName ? " of property '" + propertyName + "'" : "", value);
+	error("unexpected type of default value (type='%s', source='%s')%s, falling back to '%s'",
+		node.type, node.toString(), propertyName ? " of property '" + propertyName + "'" : "", value);
 	return value;
 }
 
@@ -551,9 +536,9 @@ function convertStringArray(node) {
 	if ( node.type !== Syntax.ArrayExpression ) {
 		throw new Error("not an array");
 	}
-	var result = [];
-	for ( var i = 0; i < node.elements.length; i++ ) {
-		if ( node.elements[i].type !== Syntax.Literal || typeof node.elements[i].value !== 'string' ) {
+	const result = [];
+	for ( let i = 0; i < node.elements.length; i++ ) {
+		if ( node.elements[i].type !== Syntax.Literal || typeof node.elements[i].value !== "string" ) {
 			throw new Error("not a string literal");
 		}
 		result.push(node.elements[i].value);
@@ -563,17 +548,17 @@ function convertStringArray(node) {
 }
 
 function convertDragDropValue(node, cardinality) {
-	var mDragDropValue;
-	var mPossibleKeys = {draggable: 1, droppable: 1};
-	var mDefaults = {
-		"self" : { draggable : true, droppable: true },
-		"0..1" : { draggable : true, droppable: true },
-		"0..n" : { draggable : false, droppable: false }
+	let mDragDropValue;
+	const mPossibleKeys = {draggable: 1, droppable: 1};
+	const mDefaults = {
+		"self": {draggable: true, droppable: true},
+		"0..1": {draggable: true, droppable: true},
+		"0..n": {draggable: false, droppable: false}
 	};
 
 	if ( node.type === Syntax.ObjectExpression ) {
 		mDragDropValue = (node.properties || []).reduce(function(oObject, oProperty) {
-			var sKey = convertValue(oProperty.key);
+			const sKey = convertValue(oProperty.key);
 			if (mPossibleKeys[sKey]) {
 				oObject[sKey] = convertValue(oProperty.value);
 			}
@@ -581,8 +566,8 @@ function convertDragDropValue(node, cardinality) {
 		}, {});
 	} else if ( node.type === Syntax.Literal ) {
 		mDragDropValue = {
-			draggable : node.value,
-			droppable : node.value
+			draggable: node.value,
+			droppable: node.value
 		};
 	} else {
 		throw new Error("not a valid dnd node");
@@ -592,13 +577,12 @@ function convertDragDropValue(node, cardinality) {
 }
 
 function collectClassInfo(extendCall, classDoclet) {
-
-	var baseType;
+	let baseType;
 	if ( classDoclet && classDoclet.augments && classDoclet.augments.length === 1 ) {
 		baseType = classDoclet.augments[0];
 	}
 	if ( extendCall.callee.type === Syntax.MemberExpression ) {
-		var baseCandidate = getResolvedObjectName(extendCall.callee.object);
+		const baseCandidate = getResolvedObjectName(extendCall.callee.object);
 		if ( baseCandidate && baseType == null ) {
 			baseType = baseCandidate;
 		} else if ( baseCandidate !== baseType ) {
@@ -606,32 +590,32 @@ function collectClassInfo(extendCall, classDoclet) {
 		}
 	}
 
-	var oClassInfo = {
-		name : extendCall.arguments[0].value,
-		baseType : baseType,
-		interfaces : [],
-		doc : classDoclet && classDoclet.description,
-		deprecation : classDoclet && classDoclet.deprecated,
-		since : classDoclet && classDoclet.since,
-		experimental : classDoclet && classDoclet.experimental,
-		specialSettings : {},
-		properties : {},
-		aggregations : {},
-		associations : {},
-		events : {},
-		methods : {},
-		annotations : {},
+	const oClassInfo = {
+		name: extendCall.arguments[0].value,
+		baseType: baseType,
+		interfaces: [],
+		doc: classDoclet && classDoclet.description,
+		deprecation: classDoclet && classDoclet.deprecated,
+		since: classDoclet && classDoclet.since,
+		experimental: classDoclet && classDoclet.experimental,
+		specialSettings: {},
+		properties: {},
+		aggregations: {},
+		associations: {},
+		events: {},
+		methods: {},
+		annotations: {},
 		designtime: false
 	};
 
 	function upper(n) {
-		return n.slice(0,1).toUpperCase() + n.slice(1);
+		return n.slice(0, 1).toUpperCase() + n.slice(1);
 	}
 
 	function each(node, defaultKey, callback) {
-		var map,n,settings,doclet;
+		let n; let settings; let doclet;
 
-		map = node && createPropertyMap(node.value);
+		const map = node && createPropertyMap(node.value);
 		if ( map ) {
 			for (n in map ) {
 				if ( map.hasOwnProperty(n) ) {
@@ -648,16 +632,16 @@ function collectClassInfo(extendCall, classDoclet) {
 		}
 	}
 
-	var classInfoNode = extendCall.arguments[1];
-	var classInfoMap = createPropertyMap(classInfoNode);
+	const classInfoNode = extendCall.arguments[1];
+	const classInfoMap = createPropertyMap(classInfoNode);
 	if ( classInfoMap && classInfoMap.metadata && classInfoMap.metadata.value.type !== Syntax.ObjectExpression ) {
-		warning("class metadata exists but can't be analyzed. It is not of type 'ObjectExpression', but a '" + classInfoMap.metadata.value.type + "'.");
+		warning("class metadata exists but can't be analyzed. It is not of type 'ObjectExpression', but a '" +
+			classInfoMap.metadata.value.type + "'.");
 		return null;
 	}
 
-	var metadata = classInfoMap && classInfoMap.metadata && createPropertyMap(classInfoMap.metadata.value);
+	const metadata = classInfoMap && classInfoMap.metadata && createPropertyMap(classInfoMap.metadata.value);
 	if ( metadata ) {
-
 		debug("  analyzing metadata for '" + oClassInfo.name + "'");
 
 		oClassInfo["abstract"] = !!(metadata["abstract"] && metadata["abstract"].value.value);
@@ -670,33 +654,33 @@ function collectClassInfo(extendCall, classDoclet) {
 
 		each(metadata.specialSettings, "type", function(n, settings, doclet) {
 			oClassInfo.specialSettings[n] = {
-				name : n,
-				doc : doclet && doclet.description,
-				since : doclet && doclet.since,
-				deprecation : doclet && doclet.deprecated,
-				experimental : doclet && doclet.experimental,
-				visibility : (settings.visibility && settings.visibility.value.value) || "public",
-				type : settings.type ? settings.type.value.value : "any"
+				name: n,
+				doc: doclet && doclet.description,
+				since: doclet && doclet.since,
+				deprecation: doclet && doclet.deprecated,
+				experimental: doclet && doclet.experimental,
+				visibility: (settings.visibility && settings.visibility.value.value) || "public",
+				type: settings.type ? settings.type.value.value : "any"
 			};
 		});
 
 		oClassInfo.defaultProperty = (metadata.defaultProperty && metadata.defaultProperty.value.value) || undefined;
 
 		each(metadata.properties, "type", function(n, settings, doclet) {
-			var type;
-			var N = upper(n);
-			var methods;
+			let type;
+			const N = upper(n);
+			let methods;
 			oClassInfo.properties[n] = {
-				name : n,
-				doc : doclet && doclet.description,
-				since : doclet && doclet.since,
-				deprecation : doclet && doclet.deprecated,
-				experimental : doclet && doclet.experimental,
-				visibility : (settings.visibility && settings.visibility.value.value) || "public",
-				type : (type = settings.type ? settings.type.value.value : "string"),
-				defaultValue : settings.defaultValue ? convertValue(settings.defaultValue.value, type, n) : null,
-				group : settings.group ? settings.group.value.value : 'Misc',
-				bindable : settings.bindable ? !!convertValue(settings.bindable.value) : false,
+				name: n,
+				doc: doclet && doclet.description,
+				since: doclet && doclet.since,
+				deprecation: doclet && doclet.deprecated,
+				experimental: doclet && doclet.experimental,
+				visibility: (settings.visibility && settings.visibility.value.value) || "public",
+				type: (type = settings.type ? settings.type.value.value : "string"),
+				defaultValue: settings.defaultValue ? convertValue(settings.defaultValue.value, type, n) : null,
+				group: settings.group ? settings.group.value.value : "Misc",
+				bindable: settings.bindable ? !!convertValue(settings.bindable.value) : false,
 				methods: (methods = {
 					"get": "get" + N,
 					"set": "set" + N
@@ -708,29 +692,31 @@ function collectClassInfo(extendCall, classDoclet) {
 			}
 			// if ( !settings.defaultValue ) {
 			//	console.log("property without defaultValue: " + oClassInfo.name + "." + n);
-			//}
-			if ( oClassInfo.properties[n].visibility !== 'public' ) {
-				error("Property '" + n + "' uses visibility '" + oClassInfo.properties[n].visibility + "' which is not supported by the runtime");
+			// }
+			if ( oClassInfo.properties[n].visibility !== "public" ) {
+				error("Property '" + n + "' uses visibility '" + oClassInfo.properties[n].visibility +
+					"' which is not supported by the runtime");
 			}
 		});
 
-		oClassInfo.defaultAggregation = (metadata.defaultAggregation && metadata.defaultAggregation.value.value) || undefined;
+		oClassInfo.defaultAggregation =
+			(metadata.defaultAggregation && metadata.defaultAggregation.value.value) || undefined;
 
 		each(metadata.aggregations, "type", function(n, settings, doclet) {
-			var N = upper(n);
-			var methods;
-			var aggr = oClassInfo.aggregations[n] = {
+			const N = upper(n);
+			let methods;
+			const aggr = oClassInfo.aggregations[n] = {
 				name: n,
-				doc : doclet && doclet.description,
-				deprecation : doclet && doclet.deprecated,
-				since : doclet && doclet.since,
-				experimental : doclet && doclet.experimental,
-				visibility : (settings.visibility && settings.visibility.value.value) || "public",
-				type : settings.type ? settings.type.value.value : "sap.ui.core.Control",
+				doc: doclet && doclet.description,
+				deprecation: doclet && doclet.deprecated,
+				since: doclet && doclet.since,
+				experimental: doclet && doclet.experimental,
+				visibility: (settings.visibility && settings.visibility.value.value) || "public",
+				type: settings.type ? settings.type.value.value : "sap.ui.core.Control",
 				altTypes: settings.altTypes ? convertStringArray(settings.altTypes.value) : undefined,
-				singularName : settings.singularName ? settings.singularName.value.value : guessSingularName(n),
-				cardinality : (settings.multiple && !settings.multiple.value.value) ? "0..1" : "0..n",
-				bindable : settings.bindable ? !!convertValue(settings.bindable.value) : false,
+				singularName: settings.singularName ? settings.singularName.value.value : guessSingularName(n),
+				cardinality: (settings.multiple && !settings.multiple.value.value) ? "0..1" : "0..n",
+				bindable: settings.bindable ? !!convertValue(settings.bindable.value) : false,
 				methods: (methods = {
 					"get": "get" + N,
 					"destroy": "destroy" + N
@@ -742,7 +728,7 @@ function collectClassInfo(extendCall, classDoclet) {
 			if ( aggr.cardinality === "0..1" ) {
 				methods["set"] = "set" + N;
 			} else {
-				var N1 = upper(aggr.singularName);
+				const N1 = upper(aggr.singularName);
 				methods["insert"] = "insert" + N1;
 				methods["add"] = "add" + N1;
 				methods["remove"] = "remove" + N1;
@@ -756,18 +742,18 @@ function collectClassInfo(extendCall, classDoclet) {
 		});
 
 		each(metadata.associations, "type", function(n, settings, doclet) {
-			var N = upper(n);
-			var methods;
+			const N = upper(n);
+			let methods;
 			oClassInfo.associations[n] = {
 				name: n,
-				doc : doclet && doclet.description,
-				deprecation : doclet && doclet.deprecated,
-				since : doclet && doclet.since,
-				experimental : doclet && doclet.experimental,
-				visibility : (settings.visibility && settings.visibility.value.value) || "public",
-				type : settings.type ? settings.type.value.value : "sap.ui.core.Control",
-				singularName : settings.singularName ? settings.singularName.value.value : guessSingularName(n),
-				cardinality : (settings.multiple && settings.multiple.value.value) ? "0..n" : "0..1",
+				doc: doclet && doclet.description,
+				deprecation: doclet && doclet.deprecated,
+				since: doclet && doclet.since,
+				experimental: doclet && doclet.experimental,
+				visibility: (settings.visibility && settings.visibility.value.value) || "public",
+				type: settings.type ? settings.type.value.value : "sap.ui.core.Control",
+				singularName: settings.singularName ? settings.singularName.value.value : guessSingularName(n),
+				cardinality: (settings.multiple && settings.multiple.value.value) ? "0..n" : "0..1",
 				methods: (methods = {
 					"get": "get" + N
 				})
@@ -775,27 +761,28 @@ function collectClassInfo(extendCall, classDoclet) {
 			if ( oClassInfo.associations[n].cardinality === "0..1" ) {
 				methods["set"] = "set" + N;
 			} else {
-				var N1 = upper(oClassInfo.associations[n].singularName);
+				const N1 = upper(oClassInfo.associations[n].singularName);
 				methods["add"] = "add" + N1;
 				methods["remove"] = "remove" + N1;
 				methods["removeAll"] = "removeAll" + N;
 			}
-			if ( oClassInfo.associations[n].visibility !== 'public' ) {
-				error("Association '" + n + "' uses visibility '" + oClassInfo.associations[n].visibility + "' which is not supported by the runtime");
+			if ( oClassInfo.associations[n].visibility !== "public" ) {
+				error("Association '" + n + "' uses visibility '" +
+					oClassInfo.associations[n].visibility + "' which is not supported by the runtime");
 			}
 		});
 
 		each(metadata.events, null, function(n, settings, doclet) {
-			var N = upper(n);
-			var info = oClassInfo.events[n] = {
+			const N = upper(n);
+			const info = oClassInfo.events[n] = {
 				name: n,
-				doc : doclet && doclet.description,
-				deprecation : doclet && doclet.deprecated,
-				since : doclet && doclet.since,
-				experimental : doclet && doclet.experimental,
-				visibility : /* (settings.visibility && settings.visibility.value.value) || */ "public",
-				allowPreventDefault : !!(settings.allowPreventDefault && settings.allowPreventDefault.value.value),
-				parameters : {},
+				doc: doclet && doclet.description,
+				deprecation: doclet && doclet.deprecated,
+				since: doclet && doclet.since,
+				experimental: doclet && doclet.experimental,
+				visibility: /* (settings.visibility && settings.visibility.value.value) || */ "public",
+				allowPreventDefault: !!(settings.allowPreventDefault && settings.allowPreventDefault.value.value),
+				parameters: {},
 				methods: {
 					"attach": "attach" + N,
 					"detach": "detach" + N,
@@ -804,18 +791,19 @@ function collectClassInfo(extendCall, classDoclet) {
 			};
 			each(settings.parameters, "type", function(pName, pSettings, pDoclet) {
 				info.parameters[pName] = {
-					name : pName,
-					doc : pDoclet && pDoclet.description,
-					deprecation : pDoclet && pDoclet.deprecated,
-					since : pDoclet && pDoclet.since,
-					experimental : pDoclet && pDoclet.experimental,
-					type : pSettings && pSettings.type ? pSettings.type.value.value : ""
+					name: pName,
+					doc: pDoclet && pDoclet.description,
+					deprecation: pDoclet && pDoclet.deprecated,
+					since: pDoclet && pDoclet.since,
+					experimental: pDoclet && pDoclet.experimental,
+					type: pSettings && pSettings.type ? pSettings.type.value.value : ""
 				};
 			});
 		});
 
-		var designtime = (metadata.designtime && convertValue(metadata.designtime.value)) || (metadata.designTime && convertValue(metadata.designTime.value));
-		if ( typeof designtime === 'string' || typeof designtime === 'boolean' ) {
+		const designtime = (metadata.designtime && convertValue(metadata.designtime.value)) ||
+								(metadata.designTime && convertValue(metadata.designTime.value));
+		if ( typeof designtime === "string" || typeof designtime === "boolean" ) {
 			oClassInfo.designtime = designtime;
 		}
 		// console.log(oClassInfo.name + ":" + JSON.stringify(oClassInfo, null, "  "));
@@ -828,11 +816,10 @@ function collectClassInfo(extendCall, classDoclet) {
 }
 
 function collectDesigntimeInfo(dtNode) {
-
 	function each(node, defaultKey, callback) {
-		var map,n,settings,doclet;
+		let n; let settings; let doclet;
 
-		map = node && createPropertyMap(node.value);
+		const map = node && createPropertyMap(node.value);
 		if ( map ) {
 			for (n in map ) {
 				if ( map.hasOwnProperty(n) ) {
@@ -849,20 +836,23 @@ function collectDesigntimeInfo(dtNode) {
 		}
 	}
 
-	var oDesigntimeInfo;
+	let oDesigntimeInfo;
 
-	var map = createPropertyMap(dtNode.argument);
+	const map = createPropertyMap(dtNode.argument);
 
 	if (map.annotations) {
-
 		oDesigntimeInfo = {
 			annotations: {}
 		};
 
 		each(map.annotations, null, function(n, settings, doclet) {
-			var appliesTo = [],
-				targets = [],
-				i, oAnno, iPos;
+			const appliesTo = [];
+
+
+			const targets = [];
+
+
+			let i;
 
 			if (settings.appliesTo) {
 				for (i = 0; i < settings.appliesTo.value.elements.length; i++) {
@@ -878,9 +868,9 @@ function collectDesigntimeInfo(dtNode) {
 
 			oDesigntimeInfo.annotations[n] = {
 				name: n,
-				doc : doclet && doclet.description,
-				deprecation : doclet && doclet.deprecated,
-				since : doclet && doclet.since || settings.since && settings.since.value.value,
+				doc: doclet && doclet.description,
+				deprecation: doclet && doclet.deprecated,
+				since: doclet && doclet.since || settings.since && settings.since.value.value,
 				namespace: settings.namespace && settings.namespace.value.value,
 				annotation: settings.annotation && settings.annotation.value.value,
 				appliesTo: appliesTo,
@@ -889,14 +879,14 @@ function collectDesigntimeInfo(dtNode) {
 				defaultValue: settings.defaultValue && settings.defaultValue.value.value
 			};
 
-			oAnno = oDesigntimeInfo.annotations[n].annotation;
-			iPos = oAnno && oAnno.lastIndexOf(".");
+			const oAnno = oDesigntimeInfo.annotations[n].annotation;
+			const iPos = oAnno && oAnno.lastIndexOf(".");
 
 			if ( !oDesigntimeInfo.annotations[n].namespace && iPos > 0 ) {
 				oDesigntimeInfo.annotations[n].namespace = oAnno.slice(0, iPos);
 				oDesigntimeInfo.annotations[n].annotation = oAnno.slice(iPos + 1);
 			}
-		})
+		});
 	}
 
 	return oDesigntimeInfo;
@@ -904,29 +894,31 @@ function collectDesigntimeInfo(dtNode) {
 
 function determineValueRangeBorder(range, expression, varname, inverse) {
 	if ( expression.type === Syntax.BinaryExpression ) {
-		var value;
-		if ( expression.left.type === Syntax.Identifier && expression.left.name === varname && expression.right.type === Syntax.Literal ) {
+		let value;
+		if ( expression.left.type === Syntax.Identifier && expression.left.name === varname &&
+				expression.right.type === Syntax.Literal ) {
 			value = expression.right.value;
-		} else if ( expression.left.type === Syntax.Literal && expression.right.type === Syntax.Identifier && expression.right.name === varname ) {
+		} else if ( expression.left.type === Syntax.Literal && expression.right.type === Syntax.Identifier &&
+				expression.right.name === varname ) {
 			inverse = !inverse;
 			value = expression.left.value;
 		} else {
 			return false;
 		}
 		switch (expression.operator) {
-		case '<':
-			range[inverse ? 'minExclusive' : 'maxExclusive'] = value;
+		case "<":
+			range[inverse ? "minExclusive" : "maxExclusive"] = value;
 			break;
-		case '<=':
-			range[inverse ? 'minInclusive' : 'maxInclusive'] = value;
+		case "<=":
+			range[inverse ? "minInclusive" : "maxInclusive"] = value;
 			break;
-		case '>=':
-			range[inverse ? 'maxInclusive' : 'minInclusive'] = value;
+		case ">=":
+			range[inverse ? "maxInclusive" : "minInclusive"] = value;
 			break;
-		case '>':
-			range[inverse ? 'maxExclusive' : 'minExclusive'] = value;
+		case ">":
+			range[inverse ? "maxExclusive" : "minExclusive"] = value;
 			break;
-		default: 
+		default:
 			return false;
 		}
 		return true;
@@ -935,13 +927,13 @@ function determineValueRangeBorder(range, expression, varname, inverse) {
 }
 
 function determineValueRange(expression, varname, inverse) {
-	var range = {};
-	if ( expression.type === Syntax.LogicalExpression 
-		 && expression.operator === '&&'
-		 && expression.left.type === Syntax.BinaryExpression
-		 && expression.right.type === Syntax.BinaryExpression
-		 && determineValueRangeBorder(range, expression.left, varname, inverse)
-		 && determineValueRangeBorder(range, expression.right, varname, inverse) ) {
+	const range = {};
+	if ( expression.type === Syntax.LogicalExpression
+		&& expression.operator === "&&"
+		&& expression.left.type === Syntax.BinaryExpression
+		&& expression.right.type === Syntax.BinaryExpression
+		&& determineValueRangeBorder(range, expression.left, varname, inverse)
+		&& determineValueRangeBorder(range, expression.right, varname, inverse) ) {
 		return range;
 	} else if ( expression.type === Syntax.BinaryExpression
 				&& determineValueRangeBorder(range, expression, varname, inverse) ) {
@@ -951,27 +943,31 @@ function determineValueRange(expression, varname, inverse) {
 }
 
 function collectDataTypeInfo(extendCall, classDoclet) {
-	var args = extendCall.arguments,
-		i = 0,
-		name, def, base, pattern, range;
-	
-	if ( i < args.length && args[i].type === Syntax.Literal && typeof args[i].value === 'string' ) {
+	const args = extendCall.arguments;
+
+
+	let i = 0;
+
+
+	let name; let def; let base; let pattern; let range;
+
+	if ( i < args.length && args[i].type === Syntax.Literal && typeof args[i].value === "string" ) {
 		name = args[i++].value;
 	}
 	if ( i < args.length && args[i].type === Syntax.ObjectExpression ) {
 		def = createPropertyMap(args[i++]);
 	}
 	if ( i < args.length ) {
-		if ( args[i].type === Syntax.Literal && typeof args[i].value === 'string' ) {
+		if ( args[i].type === Syntax.Literal && typeof args[i].value === "string" ) {
 			base = args[i++].value;
-		} else if ( args[i].type === Syntax.CallExpression 
-					&& args[i].callee.type === Syntax.MemberExpression 
+		} else if ( args[i].type === Syntax.CallExpression
+					&& args[i].callee.type === Syntax.MemberExpression
 					&& /^(sap\.ui\.base\.)?DataType$/.test(getObjectName(args[i].callee.object))
 					&& args[i].callee.property.type === Syntax.Identifier
-					&& args[i].callee.property.name === 'getType'
-					&& args[i].arguments.length === 1 
+					&& args[i].callee.property.name === "getType"
+					&& args[i].arguments.length === 1
 					&& args[i].arguments[0].type === Syntax.Literal
-					&& typeof args[i].arguments[0].value === 'string' ) {
+					&& typeof args[i].arguments[0].value === "string" ) {
 			base = args[i++].arguments[0].value;
 		} else {
 			error("could not identify base type of data type '" + name + "'");
@@ -980,41 +976,41 @@ function collectDataTypeInfo(extendCall, classDoclet) {
 		base = "any";
 	}
 
-	if ( def 
-		 && def.isValid 
-		 && def.isValid.value.type === Syntax.FunctionExpression
-		 && def.isValid.value.params.length === 1
-		 && def.isValid.value.params[0].type === Syntax.Identifier
-		 && def.isValid.value.body.body.length === 1 ) {
-		var varname = def.isValid.value.params[0].name;
-		var stmt = def.isValid.value.body.body[0];
+	if ( def
+		&& def.isValid
+		&& def.isValid.value.type === Syntax.FunctionExpression
+		&& def.isValid.value.params.length === 1
+		&& def.isValid.value.params[0].type === Syntax.Identifier
+		&& def.isValid.value.body.body.length === 1 ) {
+		const varname = def.isValid.value.params[0].name;
+		const stmt = def.isValid.value.body.body[0];
 		if ( stmt.type === Syntax.ReturnStatement && stmt.argument ) {
-			if ( stmt.argument.type === Syntax.CallExpression 
-				 && stmt.argument.callee.type === Syntax.MemberExpression
-				 && stmt.argument.callee.object.type === Syntax.Literal
-				 && stmt.argument.callee.object.regex
-				 && stmt.argument.callee.property.type === Syntax.Identifier
-				 && stmt.argument.callee.property.name === 'test' ) {
+			if ( stmt.argument.type === Syntax.CallExpression
+				&& stmt.argument.callee.type === Syntax.MemberExpression
+				&& stmt.argument.callee.object.type === Syntax.Literal
+				&& stmt.argument.callee.object.regex
+				&& stmt.argument.callee.property.type === Syntax.Identifier
+				&& stmt.argument.callee.property.name === "test" ) {
 				pattern = stmt.argument.callee.object.regex.pattern;
 				// console.log(pattern);
 			} else {
 				range = determineValueRange(stmt.argument, varname, false);
 			}
-		} else if ( stmt.type === Syntax.IfStatement 
+		} else if ( stmt.type === Syntax.IfStatement
 					&& stmt.consequent.type === Syntax.BlockStatement
 					&& stmt.consequent.body.length === 1
 					&& stmt.consequent.body[0].type === Syntax.ReturnStatement
 					&& stmt.consequent.body[0].argument
 					&& stmt.consequent.body[0].argument.type === Syntax.Literal
-					&& typeof stmt.consequent.body[0].argument.value === 'boolean'
+					&& typeof stmt.consequent.body[0].argument.value === "boolean"
 					&& stmt.alternate.type === Syntax.BlockStatement
 					&& stmt.alternate.body.length === 1
 					&& stmt.alternate.body[0].type === Syntax.ReturnStatement
 					&& stmt.alternate.body[0].argument
 					&& stmt.alternate.body[0].argument.type === Syntax.Literal
-					&& typeof stmt.alternate.body[0].argument.value === 'boolean'
+					&& typeof stmt.alternate.body[0].argument.value === "boolean"
 					&& stmt.consequent.body[0].argument.value !== typeof stmt.alternate.body[0].argument.value ) {
-			var inverse = stmt.alternate.body[0].argument.value;
+			const inverse = stmt.alternate.body[0].argument.value;
 			range = determineValueRange(stmt.test, varname, inverse);
 		} else {
 			console.log(stmt);
@@ -1034,20 +1030,25 @@ function collectDataTypeInfo(extendCall, classDoclet) {
 	}
 }
 
-var rEmptyLine = /^\s*$/;
+const rEmptyLine = /^\s*$/;
 
 function createAutoDoc(oClassInfo, classComment, node, parser, filename, commentAlreadyProcessed) {
+	const newStyle = !!pluginConfig.newStyle;
 
-	var newStyle = !!pluginConfig.newStyle,
-		includeSettings = !!pluginConfig.includeSettingsInConstructor,
-		rawClassComment = getRawComment(classComment),
-		p,n,n1,pName,info,lines,link;
+
+	const includeSettings = !!pluginConfig.includeSettingsInConstructor;
+
+
+	const rawClassComment = getRawComment(classComment);
+
+
+	let n; let n1; let pName; let info; let lines; let link;
 
 	function isEmpty(obj) {
 		if ( !obj ) {
 			return true;
 		}
-		for (var n in obj) {
+		for (const n in obj) {
 			if ( obj.hasOwnProperty(n) ) {
 				return false;
 			}
@@ -1056,18 +1057,20 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment
 	}
 
 	function jsdocCommentFound(comment) {
-		parser.emit('jsdocCommentFound', {
-			event:'jsdocCommentFound',
-			comment : comment,
-			lineno : node.loc.start.line,
-			filename : filename,
-			range : [ node.range[0], node.range[0] ]
+		parser.emit("jsdocCommentFound", {
+			event: "jsdocCommentFound",
+			comment: comment,
+			lineno: node.loc.start.line,
+			filename: filename,
+			range: [node.range[0], node.range[0]]
 		}, parser);
 	}
 
 	function removeDuplicateEmptyLines(lines) {
-		var lastWasEmpty = false,
-			i,j,l,line;
+		let lastWasEmpty = false;
+
+
+		let i; let j; let l; let line;
 
 		for (i = 0, j = 0, l = lines.length; i < l; i++) {
 			line = lines[i];
@@ -1081,37 +1084,38 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment
 				lastWasEmpty = false;
 			}
 		}
-		return j < i ? lines.slice(0,j) : lines;
+		return j < i ? lines.slice(0, j) : lines;
 	}
 
 	function newJSDoc(lines) {
-		//console.log("add completely new jsdoc comment to prog " + node.type + ":" + node.nodeId + ":" + Object.keys(node));
+		// console.log("add completely new jsdoc comment to prog " +
+		//	node.type + ":" + node.nodeId + ":" + Object.keys(node));
 
 		lines = removeDuplicateEmptyLines(lines);
 		lines.push("@synthetic");
 
-		var comment = " * " + lines.join("\r\n * ");
-		jsdocCommentFound("/**\r\n" + comment + "\r\n */")
+		const comment = " * " + lines.join("\r\n * ");
+		jsdocCommentFound("/**\r\n" + comment + "\r\n */");
 
-		var m = /@name\s+([^\r\n\t ]+)/.exec(comment);
+		const m = /@name\s+([^\r\n\t ]+)/.exec(comment);
 		debug("  creating synthetic comment '" + (m && m[1]) + "'");
 	}
 
-	function rname(prefix,n,_static) {
-		return (_static ? "." : "#") + prefix + n.slice(0,1).toUpperCase() + n.slice(1);
+	function rname(prefix, n, _static) {
+		return (_static ? "." : "#") + prefix + n.slice(0, 1).toUpperCase() + n.slice(1);
 	}
 
-	function name(prefix,n,_static) {
-		return oClassInfo.name + rname(prefix,n,_static);
+	function name(prefix, n, _static) {
+		return oClassInfo.name + rname(prefix, n, _static);
 	}
-	
+
 	/*
 	 * creates a JSDoc type string from the given metadata info object.
 	 * It takes into account the type, the altTypes and the cardinality
 	 * (the latter only if componentTypeOnly is not set).
 	 */
 	function makeTypeString(aggr, componentTypeOnly) {
-		var s = aggr.type;
+		let s = aggr.type;
 		if ( aggr.altTypes ) {
 			s = s + "|" + aggr.altTypes.join("|");
 		}
@@ -1126,39 +1130,39 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment
 		return s;
 	}
 
-//	function shortname(s) {
-//		return s.slice(s.lastIndexOf('.') + 1);
-//	}
-
-	var HUNGARIAN_PREFIXES = {
-		'int' : 'i',
-		'boolean' : 'b',
-		'float' : 'f',
-		'string' : 's',
-		'function' : 'fn',
-		'object' : 'o',
-		'regexp' : 'r',
-		'jQuery' : '$',
-		'any' : 'o',
-		'variant' : 'v',
-		'map' : 'm'
+	//	function shortname(s) {
+	//		return s.slice(s.lastIndexOf('.') + 1);
+	//	}
+
+	const HUNGARIAN_PREFIXES = {
+		"int": "i",
+		"boolean": "b",
+		"float": "f",
+		"string": "s",
+		"function": "fn",
+		"object": "o",
+		"regexp": "r",
+		"jQuery": "$",
+		"any": "o",
+		"variant": "v",
+		"map": "m"
 	};
 
 	function varname(n, type, property) {
-		var prefix = HUNGARIAN_PREFIXES[type] || (property ? "s" : "o");
-		return prefix + n.slice(0,1).toUpperCase() + n.slice(1);
+		const prefix = HUNGARIAN_PREFIXES[type] || (property ? "s" : "o");
+		return prefix + n.slice(0, 1).toUpperCase() + n.slice(1);
 	}
 
 	// add a list of the possible settings if and only if
 	// - documentation for the constructor exists
 	// - no (generated) documentation for settings exists already
 	// - a suitable place for inserting the settings can be found
-	var m = /(?:^|\r\n|\n|\r)[ \t]*\**[ \t]*@[a-zA-Z]/.exec(rawClassComment);
-	p = m ? m.index : -1;
-	var hasSettingsDocs = rawClassComment.indexOf("The supported settings are:") >= 0;
+	const m = /(?:^|\r\n|\n|\r)[ \t]*\**[ \t]*@[a-zA-Z]/.exec(rawClassComment);
+	const p = m ? m.index : -1;
+	const hasSettingsDocs = rawClassComment.indexOf("The supported settings are:") >= 0;
 
 	// heuristic to recognize a ManagedObject
-	var isManagedObject = (
+	const isManagedObject = (
 		/@extends\s+sap\.ui\.(?:base\.ManagedObject|core\.(?:Element|Control|Component))(?:\s|$)/.test(rawClassComment)
 		|| oClassInfo.library
 		|| !isEmpty(oClassInfo.specialSettings)
@@ -1166,7 +1170,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment
 		|| !isEmpty(oClassInfo.aggregations)
 		|| !isEmpty(oClassInfo.associations)
 		|| !isEmpty(oClassInfo.events)
-		);
+	);
 
 	if ( p >= 0 && !hasSettingsDocs ) {
 		lines = [
@@ -1174,23 +1178,22 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment
 		];
 
 		if ( isManagedObject ) { // only a ManagedObject has settings
-
 			if ( oClassInfo.name !== "sap.ui.base.ManagedObject" ) {
 				// add the hint for the general description only when the current class is not ManagedObject itself
 				lines.push(
 					"",
 					"Accepts an object literal mSettings that defines initial",
 					"property values, aggregated and associated objects as well as event handlers.",
-					"See {@link sap.ui.base.ManagedObject#constructor} for a general description of the syntax of the settings object."
+					"See {@link sap.ui.base.ManagedObject#constructor} for a general description of " +
+						"the syntax of the settings object."
 				);
 			}
 
 			// add the settings section only if there are any settings
 			if ( !isEmpty(oClassInfo.properties)
-				 || !isEmpty(oClassInfo.aggregations)
-				 || !isEmpty(oClassInfo.associations)
-				 || !isEmpty(oClassInfo.events) ) {
-
+				|| !isEmpty(oClassInfo.aggregations)
+				|| !isEmpty(oClassInfo.associations)
+				|| !isEmpty(oClassInfo.events) ) {
 				lines.push(
 					"",
 					includeSettings ? "" : "@ui5-settings",
@@ -1201,7 +1204,10 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment
 					lines.push("
  • Properties"); lines.push("
      "); for (n in oClassInfo.properties) { - lines.push("
    • {@link " + rname("get", n) + " " + n + "} : " + oClassInfo.properties[n].type + (oClassInfo.properties[n].defaultValue !== null ? " (default: " + oClassInfo.properties[n].defaultValue + ")" : "") + (oClassInfo.defaultProperty == n ? " (default)" : "") + "
    • "); + lines.push("
    • {@link " + rname("get", n) + " " + n + "} : " + + oClassInfo.properties[n].type + (oClassInfo.properties[n].defaultValue !== null ? + " (default: " + oClassInfo.properties[n].defaultValue + ")" : "") + + (oClassInfo.defaultProperty == n ? " (default)" : "") + "
    • "); } lines.push("
    "); lines.push("
  • "); @@ -1211,7 +1217,8 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment lines.push("
      "); for (n in oClassInfo.aggregations) { if ( oClassInfo.aggregations[n].visibility !== "hidden" ) { - lines.push("
    • {@link " + rname("get", n) + " " + n + "} : " + makeTypeString(oClassInfo.aggregations[n]) + (oClassInfo.defaultAggregation == n ? " (default)" : "") + "
    • "); + lines.push("
    • {@link " + rname("get", n) + " " + n + "} : " + + makeTypeString(oClassInfo.aggregations[n]) + (oClassInfo.defaultAggregation == n ? " (default)" : "") + "
    • "); } } lines.push("
    "); @@ -1221,7 +1228,8 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment lines.push("
  • Associations"); lines.push("
      "); for (n in oClassInfo.associations) { - lines.push("
    • {@link " + rname("get", n) + " " + n + "} : (sap.ui.core.ID | " + oClassInfo.associations[n].type + ")" + (oClassInfo.associations[n].cardinality === "0..n" ? "[]" : "") + "
    • "); + lines.push("
    • {@link " + rname("get", n) + " " + n + "} : (sap.ui.core.ID | " + + oClassInfo.associations[n].type + ")" + (oClassInfo.associations[n].cardinality === "0..n" ? "[]" : "") + "
    • "); } lines.push("
    "); lines.push("
  • "); @@ -1230,7 +1238,8 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment lines.push("
  • Events"); lines.push("
      "); for (n in oClassInfo.events) { - lines.push("
    • {@link " + "#event:" + n + " " + n + "} : fnListenerFunction or [fnListenerFunction, oListenerObject] or [oData, fnListenerFunction, oListenerObject]
    • "); + lines.push("
    • {@link " + "#event:" + n + " " + n + + "} : fnListenerFunction or [fnListenerFunction, oListenerObject] or [oData, fnListenerFunction, oListenerObject]
    • "); } lines.push("
    "); lines.push("
  • "); @@ -1246,9 +1255,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment ); } lines.push(""); - } else if ( oClassInfo.name !== "sap.ui.base.ManagedObject" && oClassInfo.baseType && oClassInfo.hasOwnProperty("abstract") ) { - // if a class has no settings, but metadata, point at least to the base class - if it makes sense lines.push( "", @@ -1256,24 +1263,22 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment "This class does not have its own settings, but all settings applicable to the base type", "{@link " + oClassInfo.baseType + "#constructor " + oClassInfo.baseType + "} can be used." ); - } } debug(" enhancing constructor documentation with settings"); - var enhancedComment = - rawClassComment.slice(0,p) + + let enhancedComment = + rawClassComment.slice(0, p) + "\n * " + removeDuplicateEmptyLines(lines).join("\n * ") + (commentAlreadyProcessed ? "@ui5-updated-doclet\n * " : "") + rawClassComment.slice(p); - enhancedComment = preprocessComment({ comment : enhancedComment, lineno : classComment.lineno }); + enhancedComment = preprocessComment({comment: enhancedComment, lineno: classComment.lineno}); if ( commentAlreadyProcessed ) { jsdocCommentFound(enhancedComment); } else { setRawComment(classComment, enhancedComment); } - } newJSDoc([ @@ -1291,7 +1296,8 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment "Creates a new subclass of class " + oClassInfo.name + " with name sClassName", "and enriches it with the information contained in oClassInfo.", "", - "oClassInfo might contain the same kind of information as described in {@link " + (oClassInfo.baseType ? oClassInfo.baseType + ".extend" : "sap.ui.base.Object.extend Object.extend") + "}.", + "oClassInfo might contain the same kind of information as described in {@link " + + (oClassInfo.baseType ? oClassInfo.baseType + ".extend" : "sap.ui.base.Object.extend Object.extend") + "}.", "", "@param {string} sClassName Name of the class being created", "@param {object} [oClassInfo] Object literal with information about the class", @@ -1306,11 +1312,11 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment for (n in oClassInfo.properties ) { info = oClassInfo.properties[n]; - if ( info.visibility === 'hidden' ) { + if ( info.visibility === "hidden" ) { continue; } // link = newStyle ? "{@link #setting:" + n + " " + n + "}" : "" + n + ""; - link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; + link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; newJSDoc([ "Gets current value of property " + link + ".", "", @@ -1322,7 +1328,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("get",n), + "@name " + name("get", n), "@function" ]); newJSDoc([ @@ -1333,13 +1339,13 @@ 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 + "", + "@param {" + info.type + "} " + varname(n, info.type, true) + " New value for property " + n + "", "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", info.since ? "@since " + info.since : "", info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("set",n), + "@name " + name("set", n), "@function" ]); if ( info.bindable ) { @@ -1354,7 +1360,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("bind",n), + "@name " + name("bind", n), "@function" ]); newJSDoc([ @@ -1364,7 +1370,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("unbind",n), + "@name " + name("unbind", n), "@function" ]); } @@ -1372,11 +1378,11 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment for (n in oClassInfo.aggregations ) { info = oClassInfo.aggregations[n]; - if ( info.visibility === 'hidden' ) { + if ( info.visibility === "hidden" ) { continue; } // link = newStyle ? "{@link #setting:" + n + " " + n + "}" : "" + n + ""; - link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; + link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; newJSDoc([ "Gets content of aggregation " + link + ".", "", @@ -1388,7 +1394,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("get",n), + "@name " + name("get", n), "@function" ]); if ( info.cardinality == "0..n" ) { @@ -1397,7 +1403,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment "Inserts a " + n1 + " into the aggregation " + link + ".", "", "@param {" + makeTypeString(info, true) + "}", - " " + varname(n1,info.altTypes ? "variant" : info.type) + " The " + n1 + " to insert; if empty, nothing is inserted", + " " + varname(n1, info.altTypes ? "variant" : info.type) + " The " + n1 + " to insert; if empty, nothing is inserted", "@param {int}", " iIndex The 0-based index the " + n1 + " should be inserted at; for", " a negative value of iIndex, the " + n1 + " is inserted at position 0; for a value", @@ -1408,26 +1414,26 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("insert",n1), + "@name " + name("insert", n1), "@function" ]); newJSDoc([ "Adds some " + n1 + " to the aggregation " + link + ".", "@param {" + makeTypeString(info, true) + "}", - " " + varname(n1,info.altTypes ? "variant" : info.type) + " The " + n1 + " to add; if empty, nothing is inserted", + " " + varname(n1, info.altTypes ? "variant" : info.type) + " The " + n1 + " to add; if empty, nothing is inserted", "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", info.since ? "@since " + info.since : "", info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("add",n1), + "@name " + name("add", n1), "@function" ]); newJSDoc([ "Removes a " + n1 + " from the aggregation " + link + ".", "", - "@param {int | string | " + makeTypeString(info, true) + "} " + varname(n1,"variant") + " The " + n1 + " to remove or its index or id", + "@param {int | string | " + makeTypeString(info, true) + "} " + varname(n1, "variant") + " The " + n1 + " to remove or its index or id", "@returns {" + makeTypeString(info, true) + "} The removed " + n1 + " or null", info.since ? "@since " + info.since : "", info.deprecation ? "@deprecated " + info.deprecation : "", @@ -1496,7 +1502,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("bind",n), + "@name " + name("bind", n), "@function" ]); newJSDoc([ @@ -1506,7 +1512,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("unbind",n), + "@name " + name("unbind", n), "@function" ]); } @@ -1514,11 +1520,11 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment for (n in oClassInfo.associations ) { info = oClassInfo.associations[n]; - if ( info.visibility === 'hidden' ) { + if ( info.visibility === "hidden" ) { continue; } // link = newStyle ? "{@link #setting:" + n + " " + n + "}" : "" + n + ""; - link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; + link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; newJSDoc([ info.cardinality === "0..n" ? "Returns array of IDs of the elements which are the current targets of the association " + link + "." : @@ -1531,7 +1537,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("get",n), + "@name " + name("get", n), "@function" ]); if ( info.cardinality === "0..n" ) { @@ -1539,18 +1545,20 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment newJSDoc([ "Adds some " + n1 + " into the association " + link + ".", "", - "@param {sap.ui.core.ID | " + info.type + "} " + varname(n1, "variant") + " The " + n + " to add; if empty, nothing is inserted", + "@param {sap.ui.core.ID | " + info.type + "} " + varname(n1, "variant") + " The " + n + + " to add; if empty, nothing is inserted", "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", info.since ? "@since " + info.since : "", info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("add",n1), + "@name " + name("add", n1), "@function" ]); newJSDoc([ "Removes an " + n1 + " from the association named " + link + ".", - "@param {int | sap.ui.core.ID | " + info.type + "} " + varname(n1,"variant") + " The " + n1 + " to be removed or its index or ID", + "@param {int | sap.ui.core.ID | " + info.type + "} " + varname(n1, "variant") + " The " + n1 + + " to be removed or its index or ID", "@returns {sap.ui.core.ID} The removed " + n1 + " or null", info.since ? "@since " + info.since : "", info.deprecation ? "@deprecated " + info.deprecation : "", @@ -1572,7 +1580,9 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment } else { newJSDoc([ "Sets the associated " + link + ".", - "@param {sap.ui.core.ID | " + info.type + "} " + varname(n, info.type) + " ID of an element which becomes the new target of this " + n + " association; alternatively, an element instance may be given", + "@param {sap.ui.core.ID | " + info.type + "} " + varname(n, info.type) + + " ID of an element which becomes the new target of this " + n + + " association; alternatively, an element instance may be given", "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", info.since ? "@since " + info.since : "", info.deprecation ? "@deprecated " + info.deprecation : "", @@ -1586,7 +1596,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment for (n in oClassInfo.events ) { info = oClassInfo.events[n]; - //link = newStyle ? "{@link #event:" + n + " " + n + "}" : "" + n + ""; + // link = newStyle ? "{@link #event:" + n + " " + n + "}" : "" + n + ""; link = "{@link #event:" + n + " " + n + "}"; lines = [ @@ -1610,19 +1620,23 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment newJSDoc(lines); newJSDoc([ - "Attaches event handler fnFunction to the " + link + " event of this " + oClassInfo.name + ".", + "Attaches event handler fnFunction to the " + link + " event of this " + + oClassInfo.name + ".", "", - "When called, the context of the event handler (its this) will be bound to oListener if specified, ", + "When called, the context of the event handler (its this) will be bound to " + + "oListener if specified, ", "otherwise it will be bound to this " + oClassInfo.name + " itself.", "", !newStyle && info.doc ? info.doc : "", "", "@param {object}", - " [oData] An application-specific payload object that will be passed to the event handler along with the event object when firing the event", + " [oData] An application-specific payload object that will be passed to the event handler " + + "along with the event object when firing the event", "@param {function}", " fnFunction The function to be called when the event occurs", "@param {object}", - " [oListener] Context object to call the event handler with. Defaults to this " + oClassInfo.name + " itself", + " [oListener] Context object to call the event handler with. Defaults to this " + + oClassInfo.name + " itself", "", "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", "@public", @@ -1633,7 +1647,8 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment "@function" ]); newJSDoc([ - "Detaches event handler fnFunction from the " + link + " event of this " + oClassInfo.name + ".", + "Detaches event handler fnFunction from the " + link + " event of this " + + oClassInfo.name + ".", "", "The passed function and listener object must match the ones used for event registration.", "", @@ -1656,9 +1671,10 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment ]; if ( info.allowPreventDefault ) { lines.push( - "", - "Listeners may prevent the default action of this event by using the preventDefault-method on the event object.", - ""); + "", + "Listeners may prevent the default action of this event by using the " + + "preventDefault-method on the event object.", + ""); } lines.push( "", @@ -1667,7 +1683,8 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment if ( !isEmpty(info.parameters) ) { for (pName in info.parameters) { lines.push( - "@param {" + (info.parameters[pName].type || "any") + "} [mParameters." + pName + "] " + (info.parameters[pName].doc || "") + "@param {" + (info.parameters[pName].type || "any") + "} [mParameters." + pName + "] " + + (info.parameters[pName].doc || "") ); } lines.push(""); @@ -1675,7 +1692,8 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment if ( info.allowPreventDefault ) { lines.push("@returns {boolean} Whether or not to prevent the default action"); } else { - lines.push("@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining"); + lines.push("@returns {" + oClassInfo.name + + "} Reference to this in order to allow method chaining"); } lines.push( "@protected", @@ -1687,7 +1705,6 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment ); newJSDoc(lines); } - } function createDataTypeAutoDoc(oTypeInfo, classComment, node, parser, filename) { @@ -1695,11 +1712,12 @@ function createDataTypeAutoDoc(oTypeInfo, classComment, node, parser, filename) /** * Creates a human readable location info for a given doclet. - * @param doclet - * @returns {String} + * + * @param {Object} doclet + * @returns {string} */ function location(doclet) { - var filename = (doclet.meta && doclet.meta.filename) || "unknown"; + const filename = (doclet.meta && doclet.meta.filename) || "unknown"; return " #" + ui5data(doclet).id + "@" + filename + (doclet.meta.lineno != null ? ":" + doclet.meta.lineno : "") + (doclet.synthetic ? "(synthetic)" : ""); } @@ -1707,23 +1725,22 @@ function location(doclet) { // --- comment related functions that depend on the JSdoc version (e.g. on the used parser) -var isDocComment; -var getLeadingCommentNode; +let isDocComment; +let getLeadingCommentNode; -// JSDoc added the node type Syntax.File with the same change that activated Babylon +// JSDoc added the node type Syntax.File with the same change that activated Babylon // See https://github.com/jsdoc3/jsdoc/commit/ffec4a42291de6d68e6240f304b68d6abb82a869 -if ( Syntax.File === 'File' ) { - +if ( Syntax.File === "File" ) { // JSDoc starting with version 3.5.0 isDocComment = function isDocCommentBabylon(comment) { - return comment && comment.type === 'CommentBlock' && comment.value && comment.value.charAt(0) === '*'; - } + return comment && comment.type === "CommentBlock" && comment.value && comment.value.charAt(0) === "*"; + }; getLeadingCommentNode = function getLeadingCommentNodeBabylon(node, longname) { - var leadingComments = node.leadingComments; + let leadingComments = node.leadingComments; if ( Array.isArray(leadingComments) ) { - // in babylon, all comments are already attached to the node + // in babylon, all comments are already attached to the node // and the last one is the closest one and should win // non-block comments have to be filtered out leadingComments = leadingComments.filter(isDocComment); @@ -1731,19 +1748,19 @@ if ( Syntax.File === 'File' ) { return leadingComments[leadingComments.length - 1]; } } - } - + }; } else { - // JSDoc versions before 3.5.0 isDocComment = function isDoccommentEsprima(comment) { - return comment && comment.type === 'Block'; + return comment && comment.type === "Block"; }; getLeadingCommentNode = function getLeadingCommentNodeEsprima(node, longname) { - var comment, - leadingComments = node.leadingComments; + let comment; + + + let leadingComments = node.leadingComments; // when espree is used, JSDOc attached the leading comment and the first one was picked if (Array.isArray(leadingComments) && leadingComments.length && leadingComments[0].raw) { @@ -1751,48 +1768,50 @@ if ( Syntax.File === 'File' ) { } // also check all comments attached to the Program node (if found) whether they refer to the same longname - // TODO check why any matches here override the direct leading comment from above + // TODO check why any matches here override the direct leading comment from above if ( longname && currentProgram && currentProgram.leadingComments && currentProgram.leadingComments.length ) { leadingComments = currentProgram.leadingComments; - var rLongname = new RegExp("@(name|alias|class|namespace)\\s+" + longname.replace(/\./g, '\\.')); - for ( var i = 0; i < leadingComments.length; i++ ) { - var raw = getRawComment(leadingComments[i]); + const rLongname = new RegExp("@(name|alias|class|namespace)\\s+" + longname.replace(/\./g, "\\.")); + for ( let i = 0; i < leadingComments.length; i++ ) { + const raw = getRawComment(leadingComments[i]); if ( /^\/\*\*[\s\S]*\*\/$/.test(raw) && rLongname.test(raw) ) { comment = leadingComments[i]; - // console.log("\n\n**** alternative comment found for " + longname + " on program level\n\n", comment); + // console.log("\n\n**** alternative comment found for " + longname + + // " on program level\n\n", comment); break; } } } return comment; - } + }; } -//--- comment related functions that are independent from the JSdoc version +// --- comment related functions that are independent from the JSdoc version function getLeadingComment(node) { - var comment = getLeadingCommentNode(node); + const comment = getLeadingCommentNode(node); return comment ? getRawComment(comment) : null; } function getLeadingDoclet(node, preprocess) { - var comment = getLeadingComment(node) + let comment = getLeadingComment(node); if ( comment && preprocess ) { - comment = preprocessComment({comment:comment, lineno: node.loc.start.line }); + comment = preprocessComment({comment: comment, lineno: node.loc.start.line}); } return comment ? new Doclet(comment, {}) : null; } /** - * Determines the raw comment string (source code form, including leading and trailing comment markers / *...* /) from a comment node. + * Determines the raw comment string (source code form, including leading and trailing comment markers / *...* /) from a comment node. * Works for Esprima and Babylon based JSDoc versions. - * @param commentNode - * @returns + * + * @param {Object} commentNode + * @returns {string} */ function getRawComment(commentNode) { - // in esprima, there's a 'raw' property, in babylon, the 'raw' string has to be reconstructed from the 'value' by adding the markers - return commentNode ? commentNode.raw || '/*' + commentNode.value + '*/' : ''; + // in esprima, there's a 'raw' property, in babylon, the 'raw' string has to be reconstructed from the 'value' by adding the markers + return commentNode ? commentNode.raw || "/*" + commentNode.value + "*/" : ""; } function setRawComment(commentNode, newRawComment) { @@ -1803,53 +1822,57 @@ function setRawComment(commentNode, newRawComment) { } /** - * Removes the mandatory comment markers and the optional but common asterisks at the beginning of each JSDoc comment line. + * Removes the mandatory comment markers and the optional but common asterisks at the beginning of + * each JSDoc comment line. * * The result is easier to parse/analyze. * * Implementation is a 1:1 copy from JSDoc's lib/jsdoc/doclet.js (closure function, not directly reusable) - * + * * @param {string} docletSrc the source comment with or without block comment markers * @returns {string} the unwrapped content of the JSDoc comment - * + * */ function unwrap(docletSrc) { - if (!docletSrc) { return ''; } + if (!docletSrc) { + return ""; + } // note: keep trailing whitespace for @examples // extra opening/closing stars are ignored // left margin is considered a star and a space // use the /m flag on regex to avoid having to guess what this platform's newline is docletSrc = - docletSrc.replace(/^\/\*\*+/, '') // remove opening slash+stars - .replace(/\**\*\/$/, "\\Z") // replace closing star slash with end-marker - .replace(/^\s*(\* ?|\\Z)/gm, '') // remove left margin like: spaces+star or spaces+end-marker - .replace(/\s*\\Z$/g, ''); // remove end-marker + docletSrc.replace(/^\/\*\*+/, "") // remove opening slash+stars + .replace(/\**\*\/$/, "\\Z") // replace closing star slash with end-marker + .replace(/^\s*(\* ?|\\Z)/gm, "") // remove left margin like: spaces+star or spaces+end-marker + .replace(/\s*\\Z$/g, ""); // remove end-marker return docletSrc; } /** * Inverse operation of unwrap. - * + * * The prefix for lines is fixed to be " * ", lines are separated with '\n', independent from the platform. + * + * @param {string|Array} lines */ function wrap(lines) { if ( typeof lines === "string" ) { lines = lines.split(/\r\n?|\n/); } - return "/**\n * " + lines.join('\n * ') + "\n */"; + return "/**\n * " + lines.join("\n * ") + "\n */"; } /** * Preprocesses a JSDoc comment string to ensure some UI5 standards. - * - * @param {event} e Event for the new comment + * + * @param {event} e Event for the new comment * @returns {event} */ function preprocessComment(e) { - - var src = e.comment; + let src = e.comment; // add a default visibility if ( !/@private|@public|@protected|@sap-restricted|@ui5-restricted/.test(src) ) { @@ -1860,15 +1883,15 @@ function preprocessComment(e) { } if ( /@class/.test(src) && /@static/.test(src) ) { - warning("combination of @class and @static is no longer supported with jsdoc3, converting it to @namespace and @classdesc: (line " + e.lineno + ")"); + warning("combination of @class and @static is no longer supported with jsdoc3, converting it to @namespace " + + "and @classdesc: (line " + e.lineno + ")"); src = unwrap(src); src = src.replace(/@class/, "@classdesc").replace(/@static/, "@namespace"); src = wrap(src); - //console.log(src); + // console.log(src); } return src; - } // ---- other functionality --------------------------------------------------------------------------- @@ -1876,7 +1899,7 @@ function preprocessComment(e) { // HACK: override cli.exit() to avoid that JSDoc3 exits the VM if ( pluginConfig.noExit ) { info("disabling exit() call"); - require( path.join(global.env.dirname, 'cli') ).exit = function(retval) { + require( path.join(global.env.dirname, "cli") ).exit = function(retval) { info("cli.exit(): do nothing (ret val=" + retval + ")"); }; } @@ -1885,19 +1908,20 @@ if ( pluginConfig.noExit ) { // ---- exports ---------------------------------------------------------------------------------------- exports.defineTags = function(dictionary) { - /** * a special value that is not 'falsy' but results in an empty string when output * Used for the disclaimer and experimental tag */ - var EMPTY = { - toString: function() { return ""; } + const EMPTY = { + toString: function() { + return ""; + } }; /** * A sapui5 specific tag to add a disclaimer to a symbol */ - dictionary.defineTag('disclaimer', { + dictionary.defineTag("disclaimer", { // value is optional onTagged: function(doclet, tag) { doclet.disclaimer = tag.value || EMPTY; @@ -1907,7 +1931,7 @@ exports.defineTags = function(dictionary) { /** * A sapui5 specific tag to mark a symbol as experimental. */ - dictionary.defineTag('experimental', { + dictionary.defineTag("experimental", { // value is optional onTagged: function(doclet, tag) { doclet.experimental = tag.value || EMPTY; @@ -1915,9 +1939,10 @@ exports.defineTags = function(dictionary) { }); /** - * Re-introduce the deprecated 'final tag. JSDoc used it as a synonym for readonly, but we use it to mark classes as final + * Re-introduce the deprecated 'final tag. JSDoc used it as a synonym for readonly, + * but we use it to mark classes as final */ - dictionary.defineTag('final', { + dictionary.defineTag("final", { mustNotHaveValue: true, onTagged: function(doclet, tag) { doclet.final_ = true; @@ -1929,11 +1954,11 @@ exports.defineTags = function(dictionary) { * 'interface' is like 'class', but without a constructor. * Support for 'interface' might not be complete (only standard UI5 use cases tested) */ - dictionary.defineTag('interface', { - //mustNotHaveValue: true, + dictionary.defineTag("interface", { + // mustNotHaveValue: true, onTagged: function(doclet, tag) { // debug("setting kind of " + doclet.name + " to 'interface'"); - doclet.kind = 'interface'; + doclet.kind = "interface"; if ( tag.value ) { doclet.classdesc = tag.value; } @@ -1943,7 +1968,7 @@ exports.defineTags = function(dictionary) { /** * Classes can declare that they implement a set of interfaces */ - dictionary.defineTag('implements', { + dictionary.defineTag("implements", { mustHaveValue: true, onTagged: function(doclet, tag) { // console.log("setting implements of " + doclet.name + " to 'interface'"); @@ -1959,25 +1984,25 @@ exports.defineTags = function(dictionary) { }); /** - * Set the visibility of a doclet to 'restricted'. + * Set the visibility of a doclet to 'restricted'. */ - dictionary.defineTag('ui5-restricted', { + dictionary.defineTag("ui5-restricted", { onTagged: function(doclet, tag) { - doclet.access = 'restricted'; + doclet.access = "restricted"; if ( tag.value ) { ui5data(doclet).stakeholders = tag.value.trim().split(/(?:\s*,\s*|\s+)/); } } }); - dictionary.defineSynonym('ui5-restricted', 'sap-restricted'); + dictionary.defineSynonym("ui5-restricted", "sap-restricted"); /** * Mark a doclet as synthetic. - * + * * Used for doclets that the autodoc generation creates. This helps the template * later to recognize such doclets and maybe filter them out. */ - dictionary.defineTag('synthetic', { + dictionary.defineTag("synthetic", { mustNotHaveValue: true, onTagged: function(doclet, tag) { doclet.synthetic = true; @@ -1987,7 +2012,7 @@ exports.defineTags = function(dictionary) { /** * Mark a doclet that intentionally updates a previous doclet */ - dictionary.defineTag('ui5-updated-doclet', { + dictionary.defineTag("ui5-updated-doclet", { mustNotHaveValue: true, onTagged: function(doclet, tag) { ui5data(doclet).updatedDoclet = true; @@ -1995,25 +2020,26 @@ exports.defineTags = function(dictionary) { }); /** - * The @hideconstructor tag tells JSDoc that the generated documentation should not display the constructor for a class. + * The @hideconstructor tag tells JSDoc that the generated documentation should not display + * the constructor for a class. * Note: this tag will be natively available in JSDoc >= 3.5.0 */ - dictionary.defineTag('hideconstructor', { + dictionary.defineTag("hideconstructor", { mustNotHaveValue: true, onTagged: function(doclet, tag) { doclet.hideconstructor = true; } }); - }; exports.handlers = { /** * Before all files are parsed, determine the common path prefix of all filenames + * + * @param {Any} e */ - parseBegin : function(e) { - + parseBegin: function(e) { pathPrefixes = env.opts._.reduce(function(result, fileOrDir) { fileOrDir = path.resolve( path.normalize(fileOrDir) ); if ( fs.statSync(fileOrDir).isDirectory() ) { @@ -2030,7 +2056,7 @@ exports.handlers = { } resourceNamePrefixes.forEach(ensureEndingSlash); while ( resourceNamePrefixes.length < pathPrefixes.length ) { - resourceNamePrefixes.push(''); + resourceNamePrefixes.push(""); } debug("path prefixes " + JSON.stringify(pathPrefixes)); @@ -2039,8 +2065,10 @@ exports.handlers = { /** * Log each file before it is parsed + * + * @param {Any} e */ - fileBegin: function (e) { + fileBegin: function(e) { currentProgram = undefined; currentModule = { name: null, @@ -2051,7 +2079,7 @@ exports.handlers = { debug(currentModule); }, - fileComplete: function (e) { + fileComplete: function(e) { currentSource = undefined; currentProgram = undefined; currentModule = undefined; @@ -2067,15 +2095,15 @@ exports.handlers = { }, newDoclet: function(e) { - - var _ui5data = ui5data(e.doclet); + const _ui5data = ui5data(e.doclet); // remove code: this is a try to reduce the required heap size if ( e.doclet.meta ) { if ( e.doclet.meta.code ) { e.doclet.meta.code = {}; } - var filepath = (e.doclet.meta.path && e.doclet.meta.path !== 'null' ) ? path.join(e.doclet.meta.path, e.doclet.meta.filename) : e.doclet.meta.filename; + const filepath = (e.doclet.meta.path && e.doclet.meta.path !== "null" ) ? + path.join(e.doclet.meta.path, e.doclet.meta.filename) : e.doclet.meta.filename; e.doclet.meta.__shortpath = getRelativePath(filepath); _ui5data.resource = currentModule.resource; _ui5data.module = currentModule.name || currentModule.module; @@ -2087,7 +2115,8 @@ exports.handlers = { if ( !e.doclet.longname ) { if ( e.doclet.memberof ) { e.doclet.longname = e.doclet.memberof + "." + e.doclet.name; // TODO '.' depends on scope? - warning("found doclet without longname, derived longname: " + e.doclet.longname + " " + location(e.doclet)); + warning("found doclet without longname, derived longname: " + + e.doclet.longname + " " + location(e.doclet)); } else { error("found doclet without longname, could not derive longname " + location(e.doclet)); } @@ -2097,36 +2126,34 @@ exports.handlers = { // try to detect misused memberof if ( e.doclet.memberof && e.doclet.longname.indexOf(e.doclet.memberof) !== 0 ) { warning("potentially unsupported use of @name and @memberof " + location(e.doclet)); - //console.log(e.doclet); + // console.log(e.doclet); } if ( e.doclet.returns - && e.doclet.returns.length > 0 - && e.doclet.returns[0] - && e.doclet.returns[0].type - && e.doclet.returns[0].type.names - && e.doclet.returns[0].type.names[0] === 'this' - && e.doclet.memberof ) { + && e.doclet.returns.length > 0 + && e.doclet.returns[0] + && e.doclet.returns[0].type + && e.doclet.returns[0].type.names + && e.doclet.returns[0].type.names[0] === "this" + && e.doclet.memberof ) { warning("fixing return type 'this' with " + e.doclet.memberof); e.doclet.returns[0].type.names[0] = e.doclet.memberof; } }, - beforeParse : function(e) { + beforeParse: function(e) { msgHeader("parsing " + getRelativePath(e.filename)); currentSource = e.source; }, - parseComplete : function(e) { - - var doclets = e.doclets; - var l = doclets.length,i,j,doclet; - //var noprivate = !env.opts.private; - var rAnonymous = /^(~|$)/; + parseComplete: function(e) { + const doclets = e.doclets; + let l = doclets.length; let i; let j; let doclet; + // var noprivate = !env.opts.private; + const rAnonymous = /^(~|$)/; // remove undocumented symbols, ignored symbols, anonymous functions and their members, scope members for (i = 0, j = 0; i < l; i++) { - doclet = doclets[i]; if ( !doclet.undocumented && !doclet.ignore && @@ -2144,7 +2171,7 @@ exports.handlers = { // sort doclets by name, synthetic, lineno, uid // 'ignore' is a combination of criteria, see function above debug("sorting doclets by name"); - doclets.sort(function(a,b) { + doclets.sort(function(a, b) { if ( a.longname === b.longname ) { if ( a.synthetic === b.synthetic ) { if ( a.meta && b.meta && a.meta.filename == b.meta.filename ) { @@ -2161,7 +2188,6 @@ exports.handlers = { debug("sorting doclets by name done."); for (i = 0, j = 0; i < l; i++) { - doclet = doclets[i]; // add metadata to symbol @@ -2169,8 +2195,8 @@ exports.handlers = { doclet.__ui5.metadata = classInfos[doclet.longname]; // add designtime infos, if configured - var designtimeModule = doclet.__ui5.metadata.designtime; - if ( designtimeModule && typeof designtimeModule !== 'string' ) { + let designtimeModule = doclet.__ui5.metadata.designtime; + if ( designtimeModule && typeof designtimeModule !== "string" ) { designtimeModule = doclet.__ui5.module + ".designtime"; } if ( designtimeModule && designtimeInfos[designtimeModule] ) { @@ -2180,27 +2206,28 @@ exports.handlers = { } // derive extends from UI5 APIs - if ( doclet.__ui5.metadata.baseType - && !(doclet.augments && doclet.augments.length > 0) ) { + if ( doclet.__ui5.metadata.baseType + && !(doclet.augments && doclet.augments.length > 0) ) { doclet.augments = doclet.augments || []; - info(" @extends " + doclet.__ui5.metadata.baseType + " derived from UI5 APIs (" + doclet.longname + ")"); + info(" @extends " + doclet.__ui5.metadata.baseType + + " derived from UI5 APIs (" + doclet.longname + ")"); doclet.augments.push(doclet.__ui5.metadata.baseType); } - // derive interface implementations from UI5 metadata + // derive interface implementations from UI5 metadata if ( doclet.__ui5.metadata.interfaces && doclet.__ui5.metadata.interfaces.length ) { doclet.__ui5.metadata.interfaces.forEach(function(intf) { - doclet.implements = doclet.implements || []; + doclet.implements = doclet.implements || []; if ( doclet.implements.indexOf(intf) < 0 ) { info(" @implements " + intf + " derived from UI5 metadata (" + doclet.longname + ")"); doclet.implements.push(intf); } - }) + }); } } if ( typeInfos[doclet.longname] ) { - doclet.__ui5.stereotype = 'datatype'; + doclet.__ui5.stereotype = "datatype"; doclet.__ui5.metadata = { basetype: typeInfos[doclet.longname].base, pattern: typeInfos[doclet.longname].pattern, @@ -2211,8 +2238,10 @@ exports.handlers = { // check for duplicates: last one wins if ( j > 0 && doclets[j - 1].longname === doclet.longname ) { if ( !doclets[j - 1].synthetic && !doclet.__ui5.updatedDoclet ) { - // replacing synthetic comments or updating comments are trivial case. Just log non-trivial duplicates - debug("ignoring duplicate doclet for " + doclet.longname + ":" + location(doclet) + " overrides " + location(doclets[j - 1])); + // replacing synthetic comments or updating comments are trivial case. Just log non-trivial + // duplicates + debug("ignoring duplicate doclet for " + doclet.longname + ":" + location(doclet) + " overrides " + + location(doclets[j - 1])); } doclets[j - 1] = doclet; } else { @@ -2226,36 +2255,33 @@ exports.handlers = { } if ( pluginConfig.saveSymbols ) { - fs.mkPath(env.opts.destination); - fs.writeFileSync(path.join(env.opts.destination, "symbols-parseComplete.json"), JSON.stringify(e.doclets, null, "\t"), 'utf8'); - + fs.writeFileSync(path.join(env.opts.destination, "symbols-parseComplete.json"), + JSON.stringify(e.doclets, null, "\t"), "utf8"); } - } }; exports.astNodeVisitor = { visitNode: function(node, e, parser, currentSourceName) { - - var comment; + let comment; if ( node.type === Syntax.Program ) { currentProgram = node; } function processExtendCall(extendCall, comment, commentAlreadyProcessed) { - var doclet = comment && new Doclet(getRawComment(comment), {}); - var classInfo = collectClassInfo(extendCall, doclet); + const doclet = comment && new Doclet(getRawComment(comment), {}); + const classInfo = collectClassInfo(extendCall, doclet); if ( classInfo ) { createAutoDoc(classInfo, comment, extendCall, parser, currentSourceName, commentAlreadyProcessed); } } function processDataType(createCall, comment) { - var doclet = comment && new Doclet(getRawComment(comment), {}); - var typeInfo = collectDataTypeInfo(createCall, doclet); + const doclet = comment && new Doclet(getRawComment(comment), {}); + const typeInfo = collectDataTypeInfo(createCall, doclet); if ( typeInfo ) { createDataTypeAutoDoc(typeInfo, comment, createCall, parser, currentSourceName); } @@ -2272,47 +2298,41 @@ exports.astNodeVisitor = { warning("module has explicit module name " + node.expression.arguments[0].value); */ } - } - if (node.type === Syntax.ReturnStatement && node.argument && node.argument.type === Syntax.ObjectExpression && /\.designtime\.js$/.test(currentSourceName) ) { - + if (node.type === Syntax.ReturnStatement && node.argument && + node.argument.type === Syntax.ObjectExpression && /\.designtime\.js$/.test(currentSourceName) ) { // assume this node to return designtime metadata. Collect it and remember it by its module name - var oDesigntimeInfo = collectDesigntimeInfo(node); + const oDesigntimeInfo = collectDesigntimeInfo(node); if ( oDesigntimeInfo ) { designtimeInfos[currentModule.module] = oDesigntimeInfo; info("collected designtime info " + currentModule.module); } - } else if ( node.type === Syntax.ExpressionStatement && isExtendCall(node.expression) ) { - // Something.extend(...) -- return value (new class) is not used in an assignment // className = node.expression.arguments[0].value; comment = getLeadingCommentNode(node) || getLeadingCommentNode(node.expression); // console.log("ast node with comment " + comment); processExtendCall(node.expression, comment); - - } else if ( node.type === Syntax.VariableDeclaration && node.declarations.length == 1 && isExtendCall(node.declarations[0].init) ) { - + } else if ( node.type === Syntax.VariableDeclaration && node.declarations.length == 1 && + isExtendCall(node.declarations[0].init) ) { // var NewClass = Something.extend(...) // className = node.declarations[0].init.arguments[0].value; comment = getLeadingCommentNode(node) || getLeadingCommentNode(node.declarations[0]); // console.log("ast node with comment " + comment); processExtendCall(node.declarations[0].init, comment); - } else if ( node.type === Syntax.ReturnStatement && isExtendCall(node.argument) ) { - // return Something.extend(...) - var className = node.argument.arguments[0].value; + const className = node.argument.arguments[0].value; comment = getLeadingCommentNode(node, className) || getLeadingCommentNode(node.argument, className); // console.log("ast node with comment " + comment); processExtendCall(node.argument, comment, true); - } else if ( node.type === Syntax.ExpressionStatement && node.expression.type === Syntax.AssignmentExpression && isCreateDataTypeCall(node.expression.right) ) { - - // thisLib.TypeName = DataType.createType( ... ) + } else if ( node.type === Syntax.ExpressionStatement && node.expression.type === Syntax.AssignmentExpression && + isCreateDataTypeCall(node.expression.right) ) { + // thisLib.TypeName = DataType.createType( ... ) comment = getLeadingCommentNode(node) || getLeadingCommentNode(node.expression); processDataType(node.expression.right); } diff --git a/lib/processors/jsdoc/ui5/template/publish.js b/lib/processors/jsdoc/ui5/template/publish.js index 5a766771a..f4cbc9d17 100644 --- a/lib/processors/jsdoc/ui5/template/publish.js +++ b/lib/processors/jsdoc/ui5/template/publish.js @@ -1,126 +1,130 @@ /* * JSDoc3 template for UI5 documentation generation. - * - * (c) Copyright 2009-2018 SAP SE or an SAP affiliate company. - * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ -/*global env: true */ -/*eslint strict: [2, "global"]*/ +/* global env: true */ +/* eslint strict: [2, "global"], max-len: ["warn", 180], no-console: "off", guard-for-in: "off", no-invalid-this: "off", no-useless-escape: "off" */ "use strict"; /* imports */ -var template = require('jsdoc/template'), - helper = require('jsdoc/util/templateHelper'), - fs = require('jsdoc/fs'), - doclet = require('jsdoc/doclet'), - path = require('jsdoc/path'); +const template = require("jsdoc/template"); +const helper = require("jsdoc/util/templateHelper"); +const fs = require("jsdoc/fs"); +const doclet = require("jsdoc/doclet"); +const path = require("jsdoc/path"); /* globals, constants */ -var MY_TEMPLATE_NAME = "ui5", - ANONYMOUS_LONGNAME = doclet.ANONYMOUS_LONGNAME, - A_SECURITY_TAGS = [ - { - name : "SecSource", - caption : "Taint Source", - description : "APIs that might introduce tainted data into an application, e.g. due to user input or network access", - params : ["out","flags"] - }, - { - name : "SecEntryPoint", - caption : "Taint Entry Point", - description: "APIs that are called implicitly by a framework or server and trigger execution of application logic", - params : ["in","flags"] - }, - { - name : "SecSink", - caption : "Taint Sink", - description : "APIs that pose a security risk when they receive tainted data", - params : ["in","flags"] - }, - { - name : "SecPassthrough", - caption : "Taint Passthrough", - description : "APIs that might propagate tainted data when they receive it as input", - params : ["in","out","flags"] - }, - { - name : "SecValidate", - caption : "Validation", - description : "APIs that (partially) cleanse tainted data so that it no longer poses a security risk in the further data flow of an application", - params : ["in","out","flags"] - } - ]; +const MY_TEMPLATE_NAME = "ui5"; +const ANONYMOUS_LONGNAME = doclet.ANONYMOUS_LONGNAME; +const A_SECURITY_TAGS = [ + { + name: "SecSource", + caption: "Taint Source", + description: "APIs that might introduce tainted data into an application, e.g. due to user input or network access", + params: ["out", "flags"] + }, + { + name: "SecEntryPoint", + caption: "Taint Entry Point", + description: "APIs that are called implicitly by a framework or server and trigger execution of application logic", + params: ["in", "flags"] + }, + { + name: "SecSink", + caption: "Taint Sink", + description: "APIs that pose a security risk when they receive tainted data", + params: ["in", "flags"] + }, + { + name: "SecPassthrough", + caption: "Taint Passthrough", + description: "APIs that might propagate tainted data when they receive it as input", + params: ["in", "out", "flags"] + }, + { + name: "SecValidate", + caption: "Validation", + description: "APIs that (partially) cleanse tainted data so that it no longer poses a security risk " + + "in the further data flow of an application", + params: ["in", "out", "flags"] + } +]; + +const rSecurityTags = new RegExp(A_SECURITY_TAGS.map(function($) { + return $.name.toLowerCase(); +}).join("|"), "i"); +// debug(A_SECURITY_TAGS.map(function($) {return $.name; }).join('|')); + +const templateConf = (env.conf.templates || {})[MY_TEMPLATE_NAME] || {}; + + +const pluginConf = templateConf; + -var rSecurityTags = new RegExp(A_SECURITY_TAGS.map(function($) {return $.name.toLowerCase(); }).join('|'), "i"); - //debug(A_SECURITY_TAGS.map(function($) {return $.name; }).join('|')); +let conf = {}; -var templateConf = (env.conf.templates || {})[MY_TEMPLATE_NAME] || {}, - pluginConf = templateConf, - conf = {}, - view; -var __db; -var __longnames; -var __missingLongnames = {}; +let view; + +let __db; +let __longnames; +const __missingLongnames = {}; /** * Maps the symbol 'longname's to the unique filename that contains the documentation of that symbol. - * This map is maintained to deal with names that only differ in case (e.g. the namespace sap.ui.model.type and the class sap.ui.model.Type). + * This map is maintained to deal with names that only differ in case (e.g. the namespace sap.ui.model.type + * and the class sap.ui.model.Type). */ -var __uniqueFilenames = {}; +let __uniqueFilenames = {}; -function info() { +function info(...args) { if ( env.opts.verbose || env.opts.debug ) { - console.log.apply(console, arguments); + console.log(...args); } } -function warning(msg) { - var args = Array.prototype.slice.apply(arguments); +function warning(...args) { args[0] = "**** warning: " + args[0]; - console.log.apply(console, args); + console.log(...args); } -function error(msg) { - var args = Array.prototype.slice.apply(arguments); +function error(...args) { args[0] = "**** error: " + args[0]; - console.log.apply(console, args); + console.log(...args); } -function debug() { +function debug(...args) { if ( env.opts.debug ) { - console.log.apply(console, arguments); + console.log(...args); } } -function merge(target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; +function merge(target, ...args) { + for (let i = 0; i < args.length; i++) { + const source = args[i]; Object.keys(source).forEach(function(p) { - var v = source[p]; + const v = source[p]; target[p] = ( v.constructor === Object ) ? merge(target[p] || {}, v) : v; }); } return target; } -function lookup(longname /*, variant*/) { - var key = longname; // variant ? longname + "|" + variant : longname; +function lookup(longname /* , variant*/) { + const key = longname; // variant ? longname + "|" + variant : longname; if ( !Object.prototype.hasOwnProperty.call(__longnames, key) ) { __missingLongnames[key] = (__missingLongnames[key] || 0) + 1; - var oResult = __db({longname: longname /*, variant: variant ? variant : {isUndefined: true}*/}); + const oResult = __db({longname: longname}); __longnames[key] = oResult.first(); } return __longnames[key]; } -var externalSymbols = {}; +const externalSymbols = {}; function loadExternalSymbols(apiJsonFolder) { - - var files; + let files; try { files = fs.readdirSync(templateConf.apiJsonFolder); @@ -132,9 +136,9 @@ function loadExternalSymbols(apiJsonFolder) { if ( files && files.length ) { files.forEach(function(localFileName) { try { - var file = path.join(templateConf.apiJsonFolder, localFileName); - var sJSON = fs.readFileSync(file, 'UTF-8'); - var data = JSON.parse(sJSON); + const file = path.join(templateConf.apiJsonFolder, localFileName); + const sJSON = fs.readFileSync(file, "UTF-8"); + const data = JSON.parse(sJSON); if ( !Array.isArray(data.symbols) ) { throw new TypeError("api.json does not contain a 'symbols' array"); } @@ -143,25 +147,24 @@ function loadExternalSymbols(apiJsonFolder) { externalSymbols[symbol.name] = symbol; }); } catch (e) { - error("failed to load symbols from " + file + ": " + (e.message || e)); + error("failed to load symbols from " + localFileName + ": " + (e.message || e)); } }); } } function isaClass($) { - return /^(namespace|interface|class|typedef)$/.test($.kind) || ($.kind === 'member' && $.isEnum) /* isNonEmptyNamespace($) */; + return /^(namespace|interface|class|typedef)$/.test($.kind) || ($.kind === "member" && $.isEnum); } -var REGEXP_ARRAY_TYPE = /^Array\.<(.*)>$/; +const REGEXP_ARRAY_TYPE = /^Array\.<(.*)>$/; -// ---- Version class ----------------------------------------------------------------------------------------------------------------------------------------------------------- +// ---- Version class --------------------------------------------------------------------------------------- -var Version = (function() { +const Version = (function() { + const rVersion = /^[0-9]+(?:\.([0-9]+)(?:\.([0-9]+))?)?(.*)$/; - var rVersion = /^[0-9]+(?:\.([0-9]+)(?:\.([0-9]+))?)?(.*)$/; - - /** + /* * Returns a Version instance created from the given parameters. * * This function can either be called as a constructor (using new) or as a normal function. @@ -169,38 +172,42 @@ var Version = (function() { * * The parts of the version number (major, minor, patch, suffix) can be provided in several ways: *
      - *
    • Version("1.2.3-SNAPSHOT") - as a dot-separated string. Any non-numerical char or a dot followed by a non-numerical char starts the suffix portion. + *
    • Version("1.2.3-SNAPSHOT") - as a dot-separated string. Any non-numerical char or a dot + * followed by a non-numerical char starts the suffix portion. * Any missing major, minor or patch versions will be set to 0.
    • - *
    • Version(1,2,3,"-SNAPSHOT") - as individual parameters. Major, minor and patch must be integer numbers or empty, suffix must be a string not starting with digits.
    • - *
    • Version([1,2,3,"-SNAPSHOT"]) - as an array with the individual parts. The same type restrictions apply as before.
    • - *
    • Version(otherVersion) - as a Version instance (cast operation). Returns the given instance instead of creating a new one.
    • + *
    • Version(1,2,3,"-SNAPSHOT") - as individual parameters. Major, minor and patch must be integer numbers or + * empty, suffix must be a string not starting with digits.
    • + *
    • Version([1,2,3,"-SNAPSHOT"]) - as an array with the individual parts. The same type restrictions + * apply as before.
    • + *
    • Version(otherVersion) - as a Version instance (cast operation). Returns the given instance instead of + * creating a new one.
    • *
    * * To keep the code size small, this implementation mainly validates the single string variant. * All other variants are only validated to some degree. It is the responsibility of the caller to * provide proper parts. * - * @param {int|string|any[]|jQuery.sap.Version} vMajor the major part of the version (int) or any of the single parameter variants explained above. + * @param {int|string|any[]|jQuery.sap.Version} vMajor the major part of the version (int) or any of the + * single parameter variants explained above. * @param {int} iMinor the minor part of the version number * @param {int} iPatch the patch part of the version number * @param {string} sSuffix the suffix part of the version number - * @return {jQuery.sap.Version} the version object as determined from the parameters + * @returns {jQuery.sap.Version} the version object as determined from the parameters * * @class Represents a version consisting of major, minor, patch version and suffix, e.g. '1.2.7-SNAPSHOT'. * * @author SAP SE * @version ${version} - * @constructor + * @class * @public * @since 1.15.0 * @name jQuery.sap.Version */ function Version(versionStr) { - - var match = rVersion.exec(versionStr) || []; + const match = rVersion.exec(versionStr) || []; function norm(v) { - v = parseInt(v,10); + v = parseInt(v, 10); return isNaN(v) ? 0 : v; } @@ -220,7 +227,6 @@ var Version = (function() { enumerable: true, value: String(match[3] || "") }); - } Version.prototype.toMajorMinor = function() { @@ -232,23 +238,21 @@ var Version = (function() { }; Version.prototype.compareTo = function(other) { - return this.major - other.major || + return this.major - other.major || this.minor - other.minor || this.patch - other.patch || ((this.suffix < other.suffix) ? -1 : (this.suffix === other.suffix) ? 0 : 1); }; return Version; - }()); // ---- Link class -------------------------------------------------------------------------------------------------------------------------------------------------------------- -//TODO move to separate module - -var Link = (function() { +// TODO move to separate module - var Link = function() { +const Link = (function() { + const Link = function() { }; Link.prototype.toSymbol = function(longname) { @@ -256,7 +260,7 @@ var Link = (function() { longname = String(longname); if ( /#constructor$/.test(longname) ) { if ( !this.innerName ) { - this.innerName = 'constructor'; + this.innerName = "constructor"; } longname = longname.slice(0, -"#constructor".length); } @@ -281,19 +285,20 @@ var Link = (function() { }; function _makeLink(href, target, tooltip, text) { - return '' + text + ''; + return "" + text + ""; } Link.prototype.toString = function() { - var longname = this.longname, - linkString; + let longname = this.longname; - if (longname) { + let linkString; + + if (longname) { if ( /^(?:(?:ftp|https?):\/\/|\.\.?\/)/.test(longname) ) { // handle real hyperlinks (TODO should be handled with a different "to" method linkString = _makeLink(longname, this.targetName, this.tooltip, this.text || longname); @@ -304,7 +309,6 @@ var Link = (function() { } else { linkString = this._makeSymbolLink(longname); } - } else if (this.file) { linkString = _makeLink(Link.base + this.file, this.targetName, null, this.text || this.file); } @@ -312,53 +316,47 @@ var Link = (function() { return linkString; }; - var missingTypes = {}; + const missingTypes = {}; Link.getMissingTypes = function() { return Object.keys(missingTypes); }; - + Link.prototype._makeSymbolLink = function(longname) { - // normalize .prototype. and # - longname = longname.replace(/\.prototype\./g, '#'); + longname = longname.replace(/\.prototype\./g, "#"); // if it is an internal reference, then don't validate against symbols, just create a link - if ( longname.charAt(0) == "#" ) { - - return _makeLink(longname + (this.innerName ? "#" + this.innerName : ""), this.targetName, this.tooltip, this.text || longname.slice(1)); - + if ( longname.charAt(0) == "#" ) { + return _makeLink(longname + (this.innerName ? "#" + this.innerName : ""), this.targetName, this.tooltip, + this.text || longname.slice(1)); } - var linkTo = lookup(longname); - // if there is no symbol by that name just return the name unaltered - if ( !linkTo ) { - + const linkTo = lookup(longname); + // if there is no symbol by that name just return the name unaltered + if ( !linkTo ) { missingTypes[longname] = true; - - return this.text || longname; + return this.text || longname; } - - // it's a full symbol reference (potentially to another file) - var mainSymbol, anchor; - if ( (linkTo.kind === 'member' && !linkTo.isEnum) || linkTo.kind === 'constant' || linkTo.kind === 'function' || linkTo.kind === 'event' ) { // it's a method or property + // it's a full symbol reference (potentially to another file) + let mainSymbol; let anchor; + if ( (linkTo.kind === "member" && !linkTo.isEnum) || linkTo.kind === "constant" || linkTo.kind === "function" || + linkTo.kind === "event" ) { // it's a method or property mainSymbol = linkTo.memberof; - anchor = ( linkTo.kind === 'event' ? "event:" : "") + Link.symbolNameToLinkName(linkTo); - + anchor = ( linkTo.kind === "event" ? "event:" : "") + Link.symbolNameToLinkName(linkTo); } else { - mainSymbol = linkTo.longname; anchor = this.innerName; - } - return _makeLink(Link.baseSymbols + __uniqueFilenames[mainSymbol] + conf.ext + (anchor ? "#" + anchor : ""), this.targetName, this.tooltip, this.text || longname); - } + return _makeLink(Link.baseSymbols + __uniqueFilenames[mainSymbol] + conf.ext + (anchor ? "#" + anchor : ""), + this.targetName, this.tooltip, this.text || longname); + }; Link.symbolNameToLinkName = function(symbol) { - var linker = ""; - if ( symbol.scope === 'static' ) { + let linker = ""; + if ( symbol.scope === "static" ) { linker = "."; } else if (symbol.isInner) { linker = "-"; // TODO-migrate? @@ -367,26 +365,24 @@ var Link = (function() { }; return Link; - }()); - // ---- publish() - main entry point for JSDoc templates ------------------------------------------------------------------------------------------------------- -/** Called automatically by JsDoc Toolkit. */ +/* Called automatically by JsDoc Toolkit. */ function publish(symbolSet) { - info("entering sapui5 template"); // create output dir fs.mkPath(env.opts.destination); -// if ( symbolSet().count() < 20000 ) { -// info("writing raw symbols to " + path.join(env.opts.destination, "symbols-unpruned-ui5.json")); -// fs.writeFileSync(path.join(env.opts.destination, "symbols-unpruned-ui5.json"), JSON.stringify(symbolSet().get(), filter, "\t"), 'utf8'); -// } - + // if ( symbolSet().count() < 20000 ) { + // info("writing raw symbols to " + path.join(env.opts.destination, "symbols-unpruned-ui5.json")); + // fs.writeFileSync(path.join(env.opts.destination, "symbols-unpruned-ui5.json"), + // JSON.stringify(symbolSet().get(), filter, "\t"), 'utf8'); + // } + info("before prune: " + symbolSet().count() + " symbols."); symbolSet = helper.prune(symbolSet); info("after prune: " + symbolSet().count() + " symbols."); @@ -401,35 +397,41 @@ function publish(symbolSet) { info("loading external apis from folder '" + templateConf.apiJsonFolder + "'"); loadExternalSymbols(templateConf.apiJsonFolder); } - - var templatePath = path.join(env.opts.template, 'tmpl/'); + + const templatePath = path.join(env.opts.template, "tmpl/"); info("using templates from '" + templatePath + "'"); view = new template.Template(templatePath); - function filter(key,value) { - if ( key === 'meta' ) { - //return; + function filter(key, value) { + if ( key === "meta" ) { + // return; } - if ( key === '__ui5' && value ) { - var v = { + if ( key === "__ui5" && value ) { + const v = { resource: value.resource, module: value.module, stakeholders: value.stakeholders }; if ( value.derived ) { - v.derived = value.derived.map(function($) { return $.longname }); + v.derived = value.derived.map(function($) { + return $.longname; + }); } if ( value.base ) { v.base = value.base.longname; } if ( value.implementations ) { - v.base = value.implementations.map(function($) { return $.longname }); + v.base = value.implementations.map(function($) { + return $.longname; + }); } if ( value.parent ) { v.parent = value.parent.longname; } if ( value.children ) { - v.children = value.children.map(function($) { return $.longname }); + v.children = value.children.map(function($) { + return $.longname; + }); } return v; } @@ -437,60 +439,61 @@ function publish(symbolSet) { } // now resolve relationships - var aRootNamespaces = createNamespaceTree(); - var hierarchyRoots = createInheritanceTree(); + const aRootNamespaces = createNamespaceTree(); + const hierarchyRoots = createInheritanceTree(); collectMembers(); mergeEventDocumentation(); if ( symbolSet().count() < 20000 ) { info("writing raw symbols to " + path.join(env.opts.destination, "symbols-pruned-ui5.json")); - fs.writeFileSync(path.join(env.opts.destination, "symbols-pruned-ui5.json"), JSON.stringify(symbolSet().get(), filter, "\t"), 'utf8'); + fs.writeFileSync(path.join(env.opts.destination, "symbols-pruned-ui5.json"), JSON.stringify(symbolSet().get(), + filter, "\t"), "utf8"); } // used to allow Link to check the details of things being linked to Link.symbolSet = symbolSet; // get an array version of the symbol set, useful for filtering - var symbols = symbolSet().get(); + const symbols = symbolSet().get(); // ----- - var PUBLISHING_VARIANTS = { + const PUBLISHING_VARIANTS = { - "apixml" : { - defaults : { + "apixml": { + defaults: { apiXmlFile: path.join(env.opts.destination, "jsapi.xml") }, - processor : function(conf) { + processor: function(conf) { createAPIXML(symbols, conf.apiXmlFile, { legacyContent: true }); } }, - "apijson" : { - defaults : { + "apijson": { + defaults: { apiJsonFile: path.join(env.opts.destination, "api.json") }, - processor : function(conf) { + processor: function(conf) { createAPIJSON(symbols, conf.apiJsonFile); } }, - "fullapixml" : { - defaults : { + "fullapixml": { + defaults: { fullXmlFile: path.join(env.opts.destination, "fulljsapi.xml") }, - processor : function(conf) { + processor: function(conf) { createAPIXML(symbols, conf.fullXmlFile, { roots: aRootNamespaces, - omitDefaults : conf.omitDefaultsInFullXml, + omitDefaults: conf.omitDefaultsInFullXml, resolveInheritance: true }); } }, - "apijs" : { + "apijs": { defaults: { jsapiFile: path.join(env.opts.destination, "api.js") }, @@ -499,8 +502,8 @@ function publish(symbolSet) { } }, - "full" : { - defaults : { + "full": { + defaults: { outdir: path.join(env.opts.destination, "full/"), contentOnly: false, hierarchyIndex: true @@ -510,10 +513,12 @@ function publish(symbolSet) { } }, - "public" : { + "public": { defaults: { outdir: path.join(env.opts.destination, "public/"), - filter: function($) { return $.access === 'public' || $.access === 'protected' || $.access == null; }, + filter: function($) { + return $.access === "public" || $.access === "protected" || $.access == null; + }, contentOnly: false, hierarchyIndex: true }, @@ -522,10 +527,12 @@ function publish(symbolSet) { } }, - "demokit" : { + "demokit": { defaults: { outdir: path.join(env.opts.destination, "demokit/"), - filter: function($) { return $.access === 'public' || $.access === 'protected' || $.access == null; }, + filter: function($) { + return $.access === "public" || $.access === "protected" || $.access == null; + }, contentOnly: true, modulePages: true, hierarchyIndex: false, @@ -541,10 +548,11 @@ function publish(symbolSet) { } }, - "demokit-internal" : { + "demokit-internal": { defaults: { outdir: path.join(env.opts.destination, "demokit-internal/"), - // filter: function($) { return $.access === 'public' || $.access === 'protected' || $.access === 'restricted' || $.access == null; }, + // filter: function($) { return $.access === 'public' || $.access === 'protected' || $.access === + // 'restricted' || $.access == null; }, contentOnly: true, modulePages: true, hierarchyIndex: false, @@ -562,20 +570,18 @@ function publish(symbolSet) { }; - var now = new Date(); + const now = new Date(); info("start publishing"); - for (var i = 0; i < templateConf.variants.length; i++) { - - var vVariant = templateConf.variants[i]; + for (let i = 0; i < templateConf.variants.length; i++) { + let vVariant = templateConf.variants[i]; if ( typeof vVariant === "string" ) { - vVariant = { variant : vVariant }; + 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 @@ -586,13 +592,16 @@ function publish(symbolSet) { // Note: trailing slash expected for dirs conf = merge({ ext: ".html", - filter: function($) { return true; }, + 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(), + creationDate: now.getFullYear() + "-" + (now.getMonth() + 1) + "-" + now.getDay() + " " + + now.getHours() + ":" + now.getMinutes(), outdir: env.opts.destination }, PUBLISHING_VARIANTS[vVariant.variant].defaults, templateConf, vVariant); @@ -603,15 +612,12 @@ function publish(symbolSet) { PUBLISHING_VARIANTS[vVariant.variant].processor(conf); info("done with variant " + vVariant.variant); - } else { - info("cannot publish unknown variant '" + vVariant.variant + "' (ignored)"); - } } - var builtinSymbols = templateConf.builtinSymbols; + const builtinSymbols = templateConf.builtinSymbols; if ( builtinSymbols ) { Link.getMissingTypes().filter(function($) { return builtinSymbols.indexOf($) < 0; @@ -620,28 +626,26 @@ function publish(symbolSet) { }); } info("publishing done."); - } -//---- namespace tree -------------------------------------------------------------------------------- +// ---- namespace tree -------------------------------------------------------------------------------- /** * Completes the tree of namespaces. Namespaces for which content is available * but which have not been documented are created as dummy without documentation. */ function createNamespaceTree() { - info("create namespace tree (" + __db().count() + " symbols)"); - var aRootNamespaces = []; - var aTypes = __db(function() { return isaClass(this); }).get(); - - for (var i = 0; i < aTypes.length; i++) { // loop with a for-loop as it can handle concurrent modifications + const aRootNamespaces = []; + const aTypes = __db(function() { + return isaClass(this); + }).get(); - var symbol = aTypes[i]; + for (let i = 0; i < aTypes.length; i++) { // loop with a for-loop as it can handle concurrent modifications + const symbol = aTypes[i]; if ( symbol.memberof ) { - - var parent = lookup(symbol.memberof); + let parent = lookup(symbol.memberof); if ( !parent ) { warning("create missing namespace '" + symbol.memberof + "' (referenced by " + symbol.longname + ")"); parent = makeNamespace(symbol.memberof); @@ -652,11 +656,8 @@ function createNamespaceTree() { symbol.__ui5.parent = parent; parent.__ui5.children = parent.__ui5.children || []; parent.__ui5.children.push(symbol); - } else if ( symbol.longname !== ANONYMOUS_LONGNAME ) { - aRootNamespaces.push(symbol); - } } @@ -664,23 +665,22 @@ function createNamespaceTree() { } function makeNamespace(memberof) { - info("adding synthetic namespace symbol " + memberof); - var comment = [ + const comment = [ "@name " + memberof, "@namespace", "@synthetic", "@public" ]; - var symbol = new doclet.Doclet("/**\n * " + comment.join("\n * ") + "\n */", {}); + const symbol = new doclet.Doclet("/**\n * " + comment.join("\n * ") + "\n */", {}); symbol.__ui5 = {}; return symbol; } -//---- inheritance hierarchy ---------------------------------------------------------------------------- +// ---- inheritance hierarchy ---------------------------------------------------------------------------- /** * Calculates the inheritance hierarchy for all class/interface/namespace symbols. @@ -695,10 +695,9 @@ function makeNamespace(memberof) { * */ function createInheritanceTree() { - function makeDoclet(longname, lines) { lines.push("@name " + longname); - var newDoclet = new doclet.Doclet("/**\n * " + lines.join("\n * ") + "\n */", {}); + const newDoclet = new doclet.Doclet("/**\n * " + lines.join("\n * ") + "\n */", {}); newDoclet.__ui5 = {}; __longnames[longname] = newDoclet; __db.insert(newDoclet); @@ -707,10 +706,12 @@ function createInheritanceTree() { info("create inheritance tree (" + __db().count() + " symbols)"); - var oTypes = __db(function() { return isaClass(this); }); - var aRootTypes = []; + const oTypes = __db(function() { + return isaClass(this); + }); + const aRootTypes = []; - var oObject = lookup("Object"); + let oObject = lookup("Object"); if ( !oObject ) { oObject = makeDoclet("Object", [ "@class", @@ -719,16 +720,16 @@ function createInheritanceTree() { ]); aRootTypes.push(oObject); } - + function getOrCreateClass(sClass, sExtendingClass) { - var oClass = lookup(sClass); + let oClass = lookup(sClass); if ( !oClass ) { warning("create missing class " + sClass + " (extended by " + sExtendingClass + ")"); - var sBaseClass = 'Object'; + let sBaseClass = "Object"; if ( externalSymbols[sClass] ) { - sBaseClass = externalSymbols[sClass].extends || sBaseClass; + sBaseClass = externalSymbols[sClass].extends || sBaseClass; } - var oBaseClass = getOrCreateClass(sBaseClass, sClass); + const oBaseClass = getOrCreateClass(sBaseClass, sClass); oClass = makeDoclet(sClass, [ "@extends " + sBaseClass, "@class", @@ -744,12 +745,11 @@ function createInheritanceTree() { // link them according to the inheritance infos oTypes.each(function(oClass) { - - if ( oClass.longname === 'Object') { + if ( oClass.longname === "Object") { return; } - var sBaseClass = "Object"; + let sBaseClass = "Object"; if ( oClass.augments && oClass.augments.length > 0 ) { if ( oClass.augments.length > 1 ) { warning("multiple inheritance detected in " + oClass.longname); @@ -759,14 +759,14 @@ function createInheritanceTree() { aRootTypes.push(oClass); } - var oBaseClass = getOrCreateClass(sBaseClass, oClass.longname); + const oBaseClass = getOrCreateClass(sBaseClass, oClass.longname); oClass.__ui5.base = oBaseClass; oBaseClass.__ui5.derived = oBaseClass.__ui5.derived || []; oBaseClass.__ui5.derived.push(oClass); if ( oClass.implements ) { - for (var j = 0; j < oClass.implements.length; j++) { - var oInterface = lookup(oClass.implements[j]); + for (let j = 0; j < oClass.implements.length; j++) { + let oInterface = lookup(oClass.implements[j]); if ( !oInterface ) { warning("create missing interface " + oClass.implements[j]); oInterface = makeDoclet(oClass.implements[j], [ @@ -790,9 +790,9 @@ function createInheritanceTree() { return; } oSymbol.__ui5.stereotype = sStereotype; - var derived = oSymbol.__ui5.derived; + const derived = oSymbol.__ui5.derived; if ( derived ) { - for (var i = 0; i < derived.length; i++ ) { + for (let i = 0; i < derived.length; i++ ) { if ( !derived[i].__ui5.stereotype ) { setStereotype(derived[i], sStereotype); } @@ -808,7 +808,7 @@ function createInheritanceTree() { // check for cyclic inheritance (not supported) // Note: the check needs to run bottom up, not top down as a typical cyclic dependency never will end at the root node oTypes.each(function(oStartClass) { - var visited = {}; + const visited = {}; function visit(oClass) { if ( visited[oClass.longname] ) { throw new Error("cyclic inheritance detected: " + JSON.stringify(Object.keys(visited))); @@ -834,7 +834,7 @@ function createInheritanceTree() { function collectMembers() { __db().each(function($) { if ( $.memberof ) { - var parent = lookup($.memberof); + const parent = lookup($.memberof); if ( parent && isaClass(parent) ) { parent.__ui5.members = parent.__ui5.members || []; parent.__ui5.members.push($); @@ -844,40 +844,39 @@ function collectMembers() { } function mergeEventDocumentation() { - console.log("merging JSDoc event documentation into UI5 metadata"); - var oTypes = __db(function() { return isaClass(this); }); + const oTypes = __db(function() { + return isaClass(this); + }); oTypes.each(function(symbol) { + const metadata = symbol.__ui5.metadata; + const members = symbol.__ui5.members; - var metadata = symbol.__ui5.metadata; - var members = symbol.__ui5.members; - - if ( !metadata || !metadata.events || Object.keys(metadata.events).length <= 0 || !members ) { + if ( !metadata || !metadata.events || Object.keys(metadata.events).length <= 0 || !members ) { return; } // console.log('mergeing events for ' + symbol.longname); members.forEach(function($) { - if ( $.kind === 'event' && !$.inherited - && ($.access === 'public' || $.access === 'protected' || $.access == null) - && metadata.events[$.name] - && Array.isArray($.params) - && !$.synthetic ) { - - var event = metadata.events[$.name]; - var modified = false; - //console.log("<<<<<<<"); - //console.log(event); - //console.log("======="); - //console.log($); + if ( $.kind === "event" && !$.inherited + && ($.access === "public" || $.access === "protected" || $.access == null) + && metadata.events[$.name] + && Array.isArray($.params) + && !$.synthetic ) { + const event = metadata.events[$.name]; + let modified = false; + // console.log("<<<<<<<"); + // console.log(event); + // console.log("======="); + // console.log($); $.params.forEach(function(param) { - var m = /^\w+\.getParameters\.(.*)$/.exec(param.name); + const m = /^\w+\.getParameters\.(.*)$/.exec(param.name); if ( m ) { - var pname = m[1]; - var ui5param = event.parameters[pname] || ( event.parameters[pname] = {}); + const pname = m[1]; + const ui5param = event.parameters[pname] || ( event.parameters[pname] = {}); if ( ui5param.type == null ) { ui5param.type = listTypes(param.type); modified = true; @@ -893,37 +892,37 @@ function mergeEventDocumentation() { console.log(" merged documentation for managed event " + symbol.longname + "#" + $.name); } - //console.log("======="); - //console.log(JSON.stringify(event, null, '\t')); - //console.log(">>>>>>>"); + // console.log("======="); + // console.log(JSON.stringify(event, null, '\t')); + // console.log(">>>>>>>"); } }); - }); - } // ---- publishing ----------------------------------------------------------------------- function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { - // create output dir fs.mkPath(path.join(conf.outdir, conf.symbolsDir)); // get a list of all the classes in the symbolset - var classes = symbols(function() { + let classes = symbols(function() { return isaClass(this) && conf.filter(this); }).order("longname"); // create unique file names __uniqueFilenames = {}; - var filenames = {}; + let filenames = {}; classes.get().sort(sortByAlias).forEach(function(symbol) { - var filename = escape(symbol.longname); - if ( filenames.hasOwnProperty(filename.toUpperCase()) && (filenames[filename.toUpperCase()].longname !== symbol.longname) ) { + let filename = escape(symbol.longname); + if ( filenames.hasOwnProperty(filename.toUpperCase()) && + (filenames[filename.toUpperCase()].longname !== symbol.longname) ) { // find an unused filename by appending "-n" where n is an integer > 0 - for (var j = 1; filenames.hasOwnProperty(filename.toUpperCase() + "-" + j); j++); - warning("duplicate symbol names " + filenames[filename.toUpperCase()].longname + " and " + symbol.longname + ", renaming the latter to " + filename + "-" + j); + let j; + for (j = 1; filenames.hasOwnProperty(filename.toUpperCase() + "-" + j); j++); + warning("duplicate symbol names " + filenames[filename.toUpperCase()].longname + " and " + + symbol.longname + ", renaming the latter to " + filename + "-" + j); filename = filename + "-" + j; } filenames[filename.toUpperCase()] = symbol; @@ -932,21 +931,21 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { filenames = null; // create a class index, displayed in the left-hand column of every class page - var classTemplate; + let classTemplate; if ( !conf.contentOnly ) { info("create embedded class index"); Link.base = "../"; Link.baseSymbols = ""; - classTemplate = 'classWithIndex.html.tmpl'; + classTemplate = "classWithIndex.html.tmpl"; publish.header = processTemplate("_header.tmpl", classes); publish.footer = processTemplate("_footer.tmpl", classes); publish.classesIndex = processTemplate("_navIndex.tmpl", classes); // kept in memory } else { - var newStyle = !!pluginConf.newStyle; + const newStyle = !!pluginConf.newStyle; classTemplate = newStyle ? "class-new.html.tmpl" : "class.html.tmpl"; - publish.header = ''; - publish.footer = ''; - publish.classesIndex = ''; + publish.header = ""; + publish.footer = ""; + publish.classesIndex = ""; // instead create an index as XML Link.base = ""; @@ -959,7 +958,7 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { Link.base = "../"; Link.baseSymbols = ""; classes.each(function(symbol) { - var sOutName = path.join(conf.symbolsDir, __uniqueFilenames[symbol.longname]) + conf.ext; + const sOutName = path.join(conf.symbolsDir, __uniqueFilenames[symbol.longname]) + conf.ext; processTemplateAndSave(classTemplate, symbol, sOutName); }); @@ -969,7 +968,7 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { Link.baseSymbols = "../" + conf.symbolsDir; fs.mkPath(path.join(conf.outdir, conf.modulesDir)); groupByModule(classes.get()).forEach(function(module) { - var sOutName = path.join(conf.modulesDir, module.name.replace(/\//g, '_')) + conf.ext; + const sOutName = path.join(conf.modulesDir, module.name.replace(/\//g, "_")) + conf.ext; processTemplateAndSave("module.html.tmpl", module, sOutName); }); } @@ -997,10 +996,10 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { info("create API by version index"); Link.base = ""; Link.baseSymbols = conf.symbolsDir; - var sinceSymbols = symbols(function() { - var r = !!this.since && !this.inherited && conf.filter(this); + const sinceSymbols = symbols(function() { + let r = !!this.since && !this.inherited && conf.filter(this); if ( r && this.memberof ) { - var parent = lookup(this.memberof); + const parent = lookup(this.memberof); // filter out symbol when parent is filtered out if ( !parent || !conf.filter(parent) ) { debug("since index: filtering out " + this.longname + ", member of " + this.memberof); @@ -1019,7 +1018,7 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { info("create deprecated API index"); Link.base = ""; Link.baseSymbols = conf.symbolsDir; - var deprecatedSymbols = symbols(function() { + const deprecatedSymbols = symbols(function() { return !!this.deprecated && !this.inherited && conf.filter(this); }).order("longname"); processTemplateAndSave("deprecation.html.tmpl", deprecatedSymbols, "deprecation" + conf.ext); @@ -1029,7 +1028,7 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { info("create experimental API index"); Link.base = ""; Link.baseSymbols = conf.symbolsDir; - var experimentalSymbols = symbols(function() { + const experimentalSymbols = symbols(function() { return !!this.experimental && !this.inherited && conf.filter(this); }).order("longname"); processTemplateAndSave("experimental.html.tmpl", experimentalSymbols, "experimental" + conf.ext); @@ -1038,16 +1037,16 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { if ( conf.securityIndex ) { info("create Security Relevant API index"); - var securityRelevantSymbols = {}; + const securityRelevantSymbols = {}; A_SECURITY_TAGS.forEach(function(oTagDef) { - securityRelevantSymbols[oTagDef.name.toLowerCase()] = { tag : oTagDef, symbols: [] }; + securityRelevantSymbols[oTagDef.name.toLowerCase()] = {tag: oTagDef, symbols: []}; }); symbols().each(function($) { - var tags = $.tags; - if ( !$.inherited && conf.filter($) && tags ) { - for (var i = 0; i < tags.length; i++) { + const tags = $.tags; + if ( !$.inherited && conf.filter($) && tags ) { + for (let i = 0; i < tags.length; i++) { if ( rSecurityTags.test(tags[i].title) ) { - securityRelevantSymbols[tags[i].title.toLowerCase()].symbols.push({ symbol: $, tag : tags[i]}); + securityRelevantSymbols[tags[i].title.toLowerCase()].symbols.push({symbol: $, tag: tags[i]}); } } } @@ -1063,11 +1062,11 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { // copy needed mimes info("copy mimes"); // copy the template's static files to outdir - var templatePath = env.opts.template; - var fromDir = path.join(templatePath, 'static'); - var staticFiles = fs.ls(fromDir, 3); + const templatePath = env.opts.template; + const fromDir = path.join(templatePath, "static"); + const staticFiles = fs.ls(fromDir, 3); staticFiles.forEach(function(fileName) { - var toDir = fs.toDir( fileName.replace(fromDir, conf.outdir) ); + const toDir = fs.toDir( fileName.replace(fromDir, conf.outdir) ); fs.mkPath(toDir); fs.copyFileSync(fileName, toDir); }); @@ -1079,70 +1078,70 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { // ---- helper functions for the templates ---- -var rSinceVersion = /^([0-9]+(?:\.[0-9]+(?:\.[0-9]+)?)?([-.][0-9A-Z]+)?)(?:\s|$)/i; +const rSinceVersion = /^([0-9]+(?:\.[0-9]+(?:\.[0-9]+)?)?([-.][0-9A-Z]+)?)(?:\s|$)/i; function extractVersion(value) { - if ( !value ) { return; } if ( value === true ) { - value = ''; + value = ""; } else { value = String(value); } - var m = rSinceVersion.exec(value); + const m = rSinceVersion.exec(value); return m ? m[1] : undefined; - } -var rSince = /^(?:as\s+of|since)(?:\s+version)?\s*([0-9]+(?:\.[0-9]+(?:\.[0-9]+)?)?([-.][0-9A-Z]+)?)(?:\.$|\.\s+|[,:]\s*|\s-\s*|\s|$)/i; +const rSince = /^(?:as\s+of|since)(?:\s+version)?\s*([0-9]+(?:\.[0-9]+(?:\.[0-9]+)?)?([-.][0-9A-Z]+)?)(?:\.$|\.\s+|[,:]\s*|\s-\s*|\s|$)/i; function extractSince(value) { - if ( !value ) { return; } if ( value === true ) { - value = ''; + value = ""; } else { value = String(value); } - var m = rSince.exec(value); + const m = rSince.exec(value); if ( m ) { return { - since : m[1], - pos : m[0].length, - value : value.slice(m[0].length).trim() - } + since: m[1], + pos: m[0].length, + value: value.slice(m[0].length).trim() + }; } return { - pos : 0, + pos: 0, value: value.trim() }; - } function sortByAlias(a, b) { - var partsA = a.longname.split(/[.#]/); - var partsB = b.longname.split(/[.#]/); - var i = 0; + const partsA = a.longname.split(/[.#]/); + const partsB = b.longname.split(/[.#]/); + let i = 0; while ( i < partsA.length && i < partsB.length ) { - if ( partsA[i].toLowerCase() < partsB[i].toLowerCase() ) + if ( partsA[i].toLowerCase() < partsB[i].toLowerCase() ) { return -1; - if ( partsA[i].toLowerCase() > partsB[i].toLowerCase() ) + } + if ( partsA[i].toLowerCase() > partsB[i].toLowerCase() ) { return 1; + } i++; } - if ( partsA.length < partsB.length ) + if ( partsA.length < partsB.length ) { return -1; - if ( partsA.length > partsB.length ) + } + if ( partsA.length > partsB.length ) { return 1; + } // as a last resort, try to compare the aliases case sensitive in case we have aliases that only // differ in case like with "sap.ui.model.type" and "sap.ui.model.Type" if ( a.longname < b.longname ) { @@ -1163,50 +1162,58 @@ function isNonEmptyNamespace($) { ($.children && $.children.length > 0)); };*/ -/** Just the first sentence (up to a full stop). Should not break on dotted variable names. */ +/* Just the first sentence (up to a full stop). Should not break on dotted variable names. */ function summarize(desc) { if ( desc != null ) { - desc = String(desc).replace(/\s+/g, ' '). - replace(/"'/g, '"'). - replace(/^(<\/?p>||\s)+/, ''); + desc = String(desc).replace(/\s+/g, " "). + replace(/"'/g, """). + replace(/^(<\/?p>||\s)+/, ""); - var match = /([\w\W]+?\.)[^a-z0-9_$]/i.exec(desc); + const match = /([\w\W]+?\.)[^a-z0-9_$]/i.exec(desc); return match ? match[1] : desc; } } -/** Make a symbol sorter by some attribute. */ -function makeSortby(/* fields ...*/) { - var aFields = Array.prototype.slice.apply(arguments), - aNorms = [], - aFuncs = []; - for (var i = 0; i < arguments.length; i++) { +/* Make a symbol sorter by some attribute. */ +function makeSortby(...aFields) { + const aNorms = []; + + const aFuncs = []; + for (let i = 0; i < arguments.length; i++) { aNorms[i] = 1; - if ( typeof aFields[i] === 'function' ) { + if ( typeof aFields[i] === "function" ) { aFuncs[i] = aFields[i]; continue; } - aFuncs[i] = function($,n) { return $[n]; }; + aFuncs[i] = function($, n) { + return $[n]; + }; if ( aFields[i].indexOf("!") === 0 ) { aNorms[i] = -1; aFields[i] = aFields[i].slice(1); } - if ( aFields[i] === 'deprecated' ) { - aFuncs[i] = function($,n) { return !!$[n]; }; - } else if ( aFields[i] === 'static' ) { - aFields[i] = 'scope'; - aFuncs[i] = function($,n) { return $[n] === 'static'; }; + if ( aFields[i] === "deprecated" ) { + aFuncs[i] = function($, n) { + return !!$[n]; + }; + } else if ( aFields[i] === "static" ) { + aFields[i] = "scope"; + aFuncs[i] = function($, n) { + return $[n] === "static"; + }; } else if ( aFields[i].indexOf("#") === 0 ) { aFields[i] = aFields[i].slice(1); - aFuncs[i] = function($,n) { return $.comment.getTag(n).length > 0; }; + aFuncs[i] = function($, n) { + return $.comment.getTag(n).length > 0; + }; } } return function(a, b) { // info("compare " + a.longname + " : " + b.longname); - var r = 0,i,va,vb; + let r = 0; let i; let va; let vb; for (i = 0; r === 0 && i < aFields.length; i++) { - va = aFuncs[i](a,aFields[i]); - vb = aFuncs[i](b,aFields[i]); + va = aFuncs[i](a, aFields[i]); + vb = aFuncs[i](b, aFields[i]); if ( va && !vb ) { r = -aNorms[i]; } else if ( !va && vb ) { @@ -1220,19 +1227,19 @@ function makeSortby(/* fields ...*/) { // debug(" " + aFields[i] + ": " + va + " ? " + vb + " = " + r); } return r; - } + }; } /** Pull in the contents of an external file at the given path. */ function processTemplateAndSave(sTemplateName, oData, sOutputName) { - var sResult = processTemplate(sTemplateName, oData); + let sResult = processTemplate(sTemplateName, oData); if ( conf.normalizeWhitespace && /\.html$/.test(sOutputName) ) { sResult = normalizeWhitespace(sResult); } - var sOutpath = path.join(conf.outdir, sOutputName); + const sOutpath = path.join(conf.outdir, sOutputName); try { - fs.writeFileSync(sOutpath, sResult, 'utf8'); + fs.writeFileSync(sOutpath, sResult, "utf8"); } catch (e) { error("failed to write generated file '" + sOutpath + "':" + (e.message || String(e))); } @@ -1240,40 +1247,40 @@ function processTemplateAndSave(sTemplateName, oData, sOutputName) { function processTemplate(sTemplateName, data) { debug("processing template '" + sTemplateName + "' for " + data.longname); - + let result; try { - var result = view.render(sTemplateName, { - asPlainSummary: asPlainSummary, - bySimpleName: bySimpleName, - childrenOfKind: childrenOfKind, - conf: conf, - data: data, - getConstructorDescription : getConstructorDescription, - getNSClass: getNSClass, - groupByVersion: groupByVersion, - extractSince: extractSince, - include: processTemplate, - Link: Link, - listTypes: listTypes, - linkTypes: linkTypes, - makeExample: makeExample, - makeLinkList: makeLinkList, - makeLinkToSymbolFile: makeLinkToSymbolFile, - makeSignature: makeSignature, - makeSortby: makeSortby, - publish : publish, - formatText: formatText, - simpleNameOf: simpleNameOf, - sortByAlias: sortByAlias, - summarize: summarize, - Version : Version - }); + result = view.render(sTemplateName, { + asPlainSummary: asPlainSummary, + bySimpleName: bySimpleName, + childrenOfKind: childrenOfKind, + conf: conf, + data: data, + getConstructorDescription: getConstructorDescription, + getNSClass: getNSClass, + groupByVersion: groupByVersion, + extractSince: extractSince, + include: processTemplate, + Link: Link, + listTypes: listTypes, + linkTypes: linkTypes, + makeExample: makeExample, + makeLinkList: makeLinkList, + makeLinkToSymbolFile: makeLinkToSymbolFile, + makeSignature: makeSignature, + makeSortby: makeSortby, + publish: publish, + formatText: formatText, + simpleNameOf: simpleNameOf, + sortByAlias: sortByAlias, + summarize: summarize, + Version: Version + }); } catch (e) { if ( e.source ) { - var filename = path.join(env.opts.destination, sTemplateName + ".js"); + const filename = path.join(env.opts.destination, sTemplateName + ".js"); console.log("**** failed to process template, source written to " + filename); fs.mkPath(path.dirname(filename)); - fs.writeFileSync(filename, e.source, 'utf8'); + fs.writeFileSync(filename, e.source, "utf8"); } console.log("error while processing " + sTemplateName); throw e; @@ -1283,24 +1290,25 @@ function processTemplate(sTemplateName, data) { } function groupByVersion(symbols, extractVersion) { - - var map = {}; + const map = {}; symbols.forEach(function(symbol) { + const version = extractVersion(symbol); + - var version = extractVersion(symbol), - key = String(version); + const key = String(version); if ( !map[key] ) { - map[key] = { version: version, symbols : [] }; + map[key] = {version: version, symbols: []}; } map[key].symbols.push(symbol); - }); - var groups = Object.keys(map).map(function(key) { return map[key]; }); + const groups = Object.keys(map).map(function(key) { + return map[key]; + }); - return groups.sort(function(a,b) { + return groups.sort(function(a, b) { if ( !a.version && b.version ) { return -1; } else if ( a.version && !b.version ) { @@ -1313,12 +1321,11 @@ function groupByVersion(symbols, extractVersion) { } function groupByModule(symbols) { - - var map = {}; + const map = {}; function add(key, symbol) { if ( !map[key] ) { - map[key] = { name: key, symbols : [] }; + map[key] = {name: key, symbols: []}; } if ( map[key].symbols.indexOf(symbol) < 0 ) { map[key].symbols.push(symbol); @@ -1326,8 +1333,7 @@ function groupByModule(symbols) { } symbols.forEach(function(symbol) { - - var key = symbol.__ui5.module; + const key = symbol.__ui5.module; if ( key ) { add(key, symbol); @@ -1339,16 +1345,17 @@ function groupByModule(symbols) { }); } } - }); - var groups = Object.keys(map).map(function(key) { return map[key]; }); + const groups = Object.keys(map).map(function(key) { + return map[key]; + }); return groups; } -var REGEXP_TAG = /<(\/?(?:[A-Z][A-Z0-9_-]*:)?[A-Z][A-Z0-9_-]*)(?:\s[^>]*)?>/gi; +const REGEXP_TAG = /<(\/?(?:[A-Z][A-Z0-9_-]*:)?[A-Z][A-Z0-9_-]*)(?:\s[^>]*)?>/gi; /** * Removes unnecessary whitespace from an HTML document: @@ -1357,13 +1364,18 @@ var REGEXP_TAG = /<(\/?(?:[A-Z][A-Z0-9_-]*:)?[A-Z][A-Z0-9_-]*)(?:\s[^>]*)?>/gi; * - inside a
     tag, whitespace is preserved
      *
      * Whitespace inside an element tag is not touched (although it could be normalized as well)
    + *
      * @param {string} content raw HTML file
      * @returns {string} HTML file with normalized whitespace
      */
     function normalizeWhitespace(content) {
    -	var compressed = '',
    -		preformatted = 0,
    -		p = 0, m, text;
    +	let compressed = "";
    +
    +
    +	let preformatted = 0;
    +
    +
    +	let p = 0; let m; let text;
     
     	REGEXP_TAG.lastIndex = 0;
     	while ( m = REGEXP_TAG.exec(content) ) {
    @@ -1373,7 +1385,7 @@ function normalizeWhitespace(content) {
     				compressed += text;
     				// console.log('  "' + text + '" (preformatted)');
     			} else {
    -				text = text.replace(/\s+/g,' ');
    +				text = text.replace(/\s+/g, " ");
     				if ( text.trim() ) {
     					compressed += text;
     				}
    @@ -1390,7 +1402,6 @@ function normalizeWhitespace(content) {
     		} else if ( /^\/pre$/i.test(m[1]) && preformatted ) {
     			preformatted--;
     		}
    -
     	}
     
     	if ( content.length > p ) {
    @@ -1399,7 +1410,7 @@ function normalizeWhitespace(content) {
     			compressed += text;
     			// console.log('  "' + text + '" (preformatted)');
     		} else {
    -			text = text.replace(/\s+/g,' ');
    +			text = text.replace(/\s+/g, " ");
     			if ( text.trim() ) {
     				compressed += text;
     			}
    @@ -1416,16 +1427,16 @@ function makeLinkToSymbolFile(longname) {
     
     function simpleNameOf(longname) {
     	longname = String(longname);
    -	var p = longname.lastIndexOf('.');
    +	const p = longname.lastIndexOf(".");
     	return p < 0 ? longname : longname.slice(p + 1);
     }
     
    -function bySimpleName(a,b) {
    +function bySimpleName(a, b) {
     	if ( a === b ) {
     		return 0;
     	}
    -	var simpleA = simpleNameOf(a);
    -	var simpleB = simpleNameOf(b);
    +	const simpleA = simpleNameOf(a);
    +	const simpleB = simpleNameOf(b);
     	if ( simpleA === simpleB ) {
     		return a < b ? -1 : 1;
     	} else {
    @@ -1433,41 +1444,43 @@ function bySimpleName(a,b) {
     	}
     }
     
    -/** Build output for displaying function parameters. */
    +/* Build output for displaying function parameters. */
     function makeSignature(params) {
    -	var r = ['('], desc;
    +	const r = ["("]; let desc;
     	if ( params ) {
    -		for (var i = 0, p; p = params[i]; i++) {
    +		for (let i = 0, p; p = params[i]; i++) {
     			// ignore @param tags for 'virtual' params that are used to document members of config-like params
     			// (e.g. like "@param param1.key ...")
    -			if (p.name && p.name.indexOf('.') == -1) {
    -				if (i > 0)
    -					r.push(', ');
    +			if (p.name && p.name.indexOf(".") == -1) {
    +				if (i > 0) {
    +					r.push(", ");
    +				}
     
    -				r.push('');
    +				r.push(">");
     				r.push(p.name);
    -				r.push('');
    -				if ( p.optional )
    -					r.push('?');
    +				r.push("");
    +				if ( p.optional ) {
    +					r.push("?");
    +				}
     			}
     		}
     	}
    -	r.push(')');
    -	return r.join('');
    +	r.push(")");
    +	return r.join("");
     }
     
     
    @@ -1484,110 +1497,120 @@ function makeSignature(params) {
      *   group 7: an empty line which implicitly starts a new paragraph
      *
      *                 [------- 
     block -------] [----------------------- some flow content -----------------------] [---- an inline {@link ...} tag ----] [---------- an empty line ---------]  */
    -var rFormatText = /(]*)?>)|(<\/pre>)|(<(?:h[\d+]|ul|ol|table)(?:\s[^>]*)?>)|(<\/(?:h[\d+]|ul|ol|table)>)|\{@link\s+([^}\s]+)(?:\s+([^\}]*))?\}|((?:\r\n|\r|\n)[ \t]*(?:\r\n|\r|\n))/gi;
    +const rFormatText = /(]*)?>)|(<\/pre>)|(<(?:h[\d+]|ul|ol|table)(?:\s[^>]*)?>)|(<\/(?:h[\d+]|ul|ol|table)>)|\{@link\s+([^}\s]+)(?:\s+([^\}]*))?\}|((?:\r\n|\r|\n)[ \t]*(?:\r\n|\r|\n))/gi;
     
     function formatText(text) {
    -
     	if ( !text ) {
    -		return '';
    +		return "";
     	}
     
    -	var inpre = false,
    -		paragraphs = 0;
    -	
    -	text = String(text).replace(rFormatText, function(match, pre, endpre, flow, endflow, linkTarget, linkText, emptyline) {
    -		if ( pre ) {
    -			inpre = true;
    -			return pre.replace(/
    /gi, "
    ").replace(//gi, "
    ");
    -		} else if ( endpre ) {
    -			inpre = false;
    -		} else if ( flow ) {
    -			if ( !inpre ) {
    -				paragraphs++;
    -				return '

    ' + match; - } - } else if ( endflow ) { - if ( !inpre ) { - paragraphs++; - return match + '

    '; - } - } else if ( emptyline ) { - if ( !inpre ) { - paragraphs++; - return '

    '; - } - } else if ( linkTarget ) { - if ( !inpre ) { - // convert to a hyperlink - var link = new Link().toSymbol(linkTarget); - // if link tag contained a replacement text, use it - if ( linkText && linkText.trim()) { - link = link.withText(linkText.trim()); + let inpre = false; + + + let paragraphs = 0; + + text = String(text).replace(rFormatText, + function(match, pre, endpre, flow, endflow, linkTarget, linkText, emptyline) { + if ( pre ) { + inpre = true; + return pre.replace(/

    /gi, "
    ").replace(//gi,
    +					"
    ");
    +			} else if ( endpre ) {
    +				inpre = false;
    +			} else if ( flow ) {
    +				if ( !inpre ) {
    +					paragraphs++;
    +					return "

    " + match; + } + } else if ( endflow ) { + if ( !inpre ) { + paragraphs++; + return match + "

    "; + } + } else if ( emptyline ) { + if ( !inpre ) { + paragraphs++; + return "

    "; + } + } else if ( linkTarget ) { + if ( !inpre ) { + // convert to a hyperlink + let link = new Link().toSymbol(linkTarget); + // if link tag contained a replacement text, use it + if ( linkText && linkText.trim()) { + link = link.withText(linkText.trim()); + } + return link.toString(); } - return link.toString(); } - } - return match; - }); + return match; + }); if ( paragraphs > 0 ) { - text = '

    ' + text + '

    '; + text = "

    " + text + "

    "; } // remove empty paragraphs - text = text.replace(/

    \s*<\/p>/g, ''); + text = text.replace(/

    \s*<\/p>/g, ""); return text; } -//console.log("#### samples"); -//console.log(formatText(summarize("This is a first\n\nparagraph with empty \n \n \nlines in it. This is the remainder."))); +// console.log("#### samples"); +// console.log(formatText( +// summarize("This is a first\n\nparagraph with empty \n \n \nlines in it. This is the remainder."))); function childrenOfKind(data, kind) { /* old version based on TaffyDB (slow) - var oChildren = symbolSet({kind: kind, memberof: data.longname === GLOBAL_LONGNAME ? {isUndefined: true} : data.longname}).filter(function() { return conf.filter(this); }); + var oChildren = symbolSet({ + kind: kind, + memberof: data.longname === GLOBAL_LONGNAME ? {isUndefined: true} : data.longname}).filter(function() { + return conf.filter(this); + }); return { own : oChildren.filter({inherited: {isUndefined:true}}).get().sort(makeSortby("!deprecated","static","name")), borrowed : groupByContributors(data, oChildren.filter({inherited: true}).get().sort(makeSortby("name"))) } */ - var oResult = { + const oResult = { own: [], borrowed: [] }; - //console.log("calculating kind " + kind + " from " + data.longname); - //console.log(data); - var fnFilter; + // console.log("calculating kind " + kind + " from " + data.longname); + // console.log(data); + let fnFilter; switch (kind) { - case 'property': + case "property": fnFilter = function($) { - return $.kind === 'constant' || ($.kind === 'member' && !$.isEnum); - } + return $.kind === "constant" || ($.kind === "member" && !$.isEnum); + }; break; - case 'event': + case "event": fnFilter = function($) { - return $.kind === 'event'; - } + return $.kind === "event"; + }; break; - case 'method': + case "method": fnFilter = function($) { - return $.kind === 'function'; - } + return $.kind === "function"; + }; break; default: // default: none - fnFilter = function($) { return false; }; + fnFilter = function($) { + return false; + }; break; } if ( data.__ui5.members ) { data.__ui5.members.forEach(function($) { if ( fnFilter($) && conf.filter($) ) { - oResult[$.inherited ? 'borrowed' : 'own'].push($); + oResult[$.inherited ? "borrowed" : "own"].push($); } }); } - oResult.own.sort(makeSortby("!deprecated","static","name")); + oResult.own.sort(makeSortby("!deprecated", "static", "name")); oResult.borrowed = groupByContributors(data, oResult.borrowed); return oResult; @@ -1600,21 +1623,26 @@ function childrenOfKind(data, kind) { * Any contributors that can not be found in the hierarchy are appended * to the set. * - * @param symbol of which these are the members - * @param borrowedMembers set of borrowed members to determine the contributors for - * @return sorted array of contributors + * @param {Any} symbol of which these are the members + * @param {Array} aBorrowedMembers set of borrowed members to determine the contributors for + * @returns {Array} sorted array of contributors */ function groupByContributors(symbol, aBorrowedMembers) { + const MAX_ORDER = 1000; + // a sufficiently large number + + const mContributors = {}; + + + const aSortedContributors = []; - var MAX_ORDER = 1000, // a sufficiently large number - mContributors = {}, - aSortedContributors = [], - i,order; + + let i; let order; aBorrowedMembers.forEach(function($) { $ = lookup($.inherits); if ($ && mContributors[$.memberof] == null) { - mContributors[$.memberof] = { order : MAX_ORDER, items : [$] }; + mContributors[$.memberof] = {order: MAX_ORDER, items: [$]}; } else { mContributors[$.memberof].items.push($); } @@ -1623,12 +1651,13 @@ function groupByContributors(symbol, aBorrowedMembers) { // order contributors according to their distance in the inheritance hierarchy order = 0; (function handleAugments(oSymbol) { - var i,oTarget,aParentsToVisit; + let i; let oTarget; let aParentsToVisit; if ( oSymbol.augments ) { aParentsToVisit = []; // first assign an order for (i = 0; i < oSymbol.augments.length; i++) { - if ( mContributors[oSymbol.augments[i]] != null && mContributors[oSymbol.augments[i]].order === MAX_ORDER ) { + if ( mContributors[oSymbol.augments[i]] != null && + mContributors[oSymbol.augments[i]].order === MAX_ORDER ) { mContributors[oSymbol.augments[i]].order = ++order; aParentsToVisit.push(oSymbol.augments[i]); } @@ -1645,169 +1674,185 @@ function groupByContributors(symbol, aBorrowedMembers) { // convert to an array and sort by order for (i in mContributors) { - aSortedContributors.push(mContributors[i]); + if (mContributors.hasOwnProperty(i)) { + aSortedContributors.push(mContributors[i]); + } } - aSortedContributors.sort(function (a,b) { return a.order - b.order; }); + aSortedContributors.sort(function(a, b) { + return a.order - b.order; + }); return aSortedContributors; - } function makeLinkList(aSymbols) { return aSymbols .sort(makeSortby("name")) - .map(function($) { return new Link().toSymbol($.longname).withText($.name); }) + .map(function($) { + return new Link().toSymbol($.longname).withText($.name); + }) .join(", "); } // ---- type parsing --------------------------------------------------------------------------------------------- function TypeParser(defaultBuilder) { - - /* TODO + /* TODO * - function(this:) // type of this * - function(new:) // constructor */ - var rLexer = /\s*(Array\.?<|Object\.?<|Set\.?<|Promise\.?<|function\(|\{|:|\(|\||\}|>|\)|,|\[\]|\*|\?|!|\.\.\.)|\s*(\w+(?:[.#~]\w+)*)|./g; + const rLexer = /\s*(Array\.?<|Object\.?<|Set\.?<|Promise\.?<|function\(|\{|:|\(|\||\}|>|\)|,|\[\]|\*|\?|!|\.\.\.)|\s*(\w+(?:[.#~]\w+)*)|./g; + + let input; + + + let builder; - var input, - builder, - token, - tokenStr; + + let token; + + + let tokenStr; function next(expected) { if ( expected !== undefined && token !== expected ) { - throw new SyntaxError("TypeParser: expected '" + expected + "', but found '" + tokenStr + "' (pos: " + rLexer.lastIndex + ", input='" + input + "')"); + throw new SyntaxError("TypeParser: expected '" + expected + "', but found '" + tokenStr + "' (pos: " + + rLexer.lastIndex + ", input='" + input + "')"); } - var match = rLexer.exec(input); + const match = rLexer.exec(input); if ( match ) { - tokenStr = match[1] || match[2]; - token = match[1] || (match[2] && 'symbol'); + tokenStr = match[1] || match[2]; + token = match[1] || (match[2] && "symbol"); if ( !token ) { - throw new SyntaxError("TypeParser: unexpected '" + tokenStr + "' (pos: " + match.index + ", input='" + input + "')"); + throw new SyntaxError("TypeParser: unexpected '" + tokenStr + "' (pos: " + match.index + ", input='" + + input + "')"); } } else { tokenStr = token = null; } } - + function parseType() { - var nullable = false; - var mandatory = false; - if ( token === '?' ) { + let nullable = false; + let mandatory = false; + if ( token === "?" ) { next(); nullable = true; - } else if ( token === '!' ) { + } else if ( token === "!" ) { next(); mandatory = true; } - var type; - - if ( token === 'Array.<' || token === 'Array<' ) { + let type; + + if ( token === "Array.<" || token === "Array<" ) { next(); - var componentType = parseType(); - next('>'); + const componentType = parseType(); + next(">"); type = builder.array(componentType); - } else if ( token === 'Object.<' || token === 'Object<' ) { + } else if ( token === "Object.<" || token === "Object<" ) { next(); - var keyType; - var valueType = parseType(); - if ( token === ',' ) { + let keyType; + let valueType = parseType(); + if ( token === "," ) { next(); keyType = valueType; valueType = parseType(); } else { - keyType = builder.synthetic(builder.simpleType('string')); + keyType = builder.synthetic(builder.simpleType("string")); } - next('>'); + next(">"); type = builder.object(keyType, valueType); - } else if ( token === 'Set.<' || token === 'Set<' ) { + } else if ( token === "Set.<" || token === "Set<" ) { next(); - var elementType = parseType(); - next('>'); + const elementType = parseType(); + next(">"); type = builder.set(elementType); - } else if ( token === 'Promise.<' || token === 'Promise<' ) { + } else if ( token === "Promise.<" || token === "Promise<" ) { next(); - var elementType = parseType(); - next('>'); + const elementType = parseType(); + next(">"); type = builder.promise(elementType); - } else if ( token === 'function(' ) { + } else if ( token === "function(" ) { next(); - var thisType, constructorType, paramTypes = [], returnType; - if ( tokenStr === 'this' ) { + let thisType; let constructorType; const paramTypes = []; let returnType; + if ( tokenStr === "this" ) { next(); - next(':'); + next(":"); thisType = parseType(); - if ( token === ',' ) { + if ( token === "," ) { next(); } - } else if ( tokenStr === 'new' ) { + } else if ( tokenStr === "new" ) { next(); - next(':'); + next(":"); constructorType = parseType(); - if ( token === ',' ) { + if ( token === "," ) { next(); } } - while ( token === 'symbol' || token === '...' ) { - var repeatable = token === '...'; + while ( token === "symbol" || token === "..." ) { + const repeatable = token === "..."; if ( repeatable) { - next(); + next(); } - var paramType = parseType(); + let paramType = parseType(); if ( repeatable ) { paramType = builder.repeatable(paramType); } paramTypes.push(paramType); - if ( token === ',' ) { + if ( token === "," ) { if ( repeatable ) { - throw new SyntaxError("TypeParser: only the last parameter of a function can be repeatable (pos: " + rLexer.lastIndex + ", input='" + input + "')"); + throw new SyntaxError( + "TypeParser: only the last parameter of a function can be repeatable (pos: " + + rLexer.lastIndex + ", input='" + input + "')"); } next(); } } - next(')'); - if ( token === ':' ) { - next(':'); + next(")"); + if ( token === ":" ) { + next(":"); returnType = parseType(); } type = builder.function(paramTypes, returnType, thisType, constructorType); - } else if ( token === '{' ) { - var structure = Object.create(null); - var propName,propType; + } else if ( token === "{" ) { + const structure = Object.create(null); + let propName; let propType; next(); do { propName = tokenStr; if ( !/^\w+$/.test(propName) ) { - throw new SyntaxError("TypeParser: structure field must have a simple name (pos: " + rLexer.lastIndex + ", input='" + input + "', field:'" + propName + "')"); + throw new SyntaxError( + "TypeParser: structure field must have a simple name (pos: " + rLexer.lastIndex + + ", input='" + input + "', field:'" + propName + "')"); } - next('symbol'); - if ( token === ':' ) { + next("symbol"); + if ( token === ":" ) { next(); propType = parseType(); } else { - propType = builder.synthetic(builder.simpleType('any')); + propType = builder.synthetic(builder.simpleType("any")); } structure[propName] = propType; - if ( token === '}' ) { + if ( token === "}" ) { break; } - next(','); + next(","); } while (token); - next('}'); + next("}"); type = builder.structure(structure); - } else if ( token === '(' ) { + } else if ( token === "(" ) { next(); type = parseTypes(); - next(')'); - } else if ( token === '*' ) { + next(")"); + } else if ( token === "*" ) { next(); - type = builder.simpleType('*'); + type = builder.simpleType("*"); } else { type = builder.simpleType(tokenStr); - next('symbol'); - while ( token === '[]' ) { + next("symbol"); + while ( token === "[]" ) { next(); type = builder.array(type); } @@ -1822,10 +1867,10 @@ function TypeParser(defaultBuilder) { } function parseTypes() { - var types = []; + const types = []; do { types.push(parseType()); - if ( token !== '|' ) { + if ( token !== "|" ) { break; } next(); @@ -1834,52 +1879,51 @@ function TypeParser(defaultBuilder) { } this.parse = function(typeStr, tempBuilder) { - builder = tempBuilder || defaultBuilder || TypeParser.ASTBuilder; + builder = tempBuilder || defaultBuilder || TypeParser.ASTBuilder; input = String(typeStr); rLexer.lastIndex = 0; next(); - var type = parseTypes(); + const type = parseTypes(); next(null); return type; - } - -} + }; +} TypeParser.ASTBuilder = { simpleType: function(type) { return { - type: 'simpleType', + type: "simpleType", name: type }; }, array: function(componentType) { return { - type: 'array', + type: "array", component: componentType }; }, object: function(keyType, valueType) { return { - type: 'object', + type: "object", key: keyType, value: valueType }; }, set: function(elementType) { return { - type: 'set', + type: "set", element: elementType }; }, promise: function(fulfillmentType) { return { - type: 'promise', + type: "promise", fulfill: fulfillmentType }; }, function: function(paramTypes, returnType, thisType, constructorType) { return { - type: 'function', + type: "function", params: paramTypes, return: returnType, this: thisType, @@ -1888,13 +1932,13 @@ TypeParser.ASTBuilder = { }, structure: function(structure) { return { - type: 'structure', + type: "structure", fields: structure }; }, union: function(types) { return { - type: 'union', + type: "union", types: types }; }, @@ -1926,13 +1970,13 @@ TypeParser.LinkBuilder.prototype = { return type.needsParenthesis ? "(" + type.str + ")" : type.str; }, simpleType: function(type) { - if ( this.linkStyle === 'text' ) { + if ( this.linkStyle === "text" ) { return { str: type }; } - var link = new Link().toSymbol(type); - if ( this.linkStyle === 'short' ) { + const link = new Link().toSymbol(type); + if ( this.linkStyle === "short" ) { link.withText(simpleNameOf(type)).withTooltip(type); } return { @@ -1961,22 +2005,24 @@ TypeParser.LinkBuilder.prototype = { }, set: function(elementType) { return { - str: 'Set.' + this.lt + elementType.str + this.gt + str: "Set." + this.lt + elementType.str + this.gt }; }, promise: function(fulfillmentType) { return { - str: 'Promise.' + this.lt + fulfillmentType.str + this.gt + str: "Promise." + this.lt + fulfillmentType.str + this.gt }; }, function: function(paramTypes, returnType) { return { - str: "function(" + paramTypes.map(function(type) { return type.str; }).join(',') + ")" + ( returnType ? " : " + this.safe(returnType) : "") + str: "function(" + paramTypes.map(function(type) { + return type.str; + }).join(",") + ")" + ( returnType ? " : " + this.safe(returnType) : "") }; }, structure: function(structure) { - var r = []; - for ( var fieldName in structure ) { + const r = []; + for ( const fieldName in structure ) { if ( structure[fieldName].synthetic ) { r.push(fieldName); } else { @@ -1990,7 +2036,7 @@ TypeParser.LinkBuilder.prototype = { union: function(types) { return { needsParenthesis: true, - str: types.map( this.safe.bind(this) ).join('|') + str: types.map( this.safe.bind(this) ).join("|") }; }, synthetic: function(type) { @@ -2011,11 +2057,11 @@ TypeParser.LinkBuilder.prototype = { } }; -var typeParser = new TypeParser(); -var _SHORT_BUILDER = new TypeParser.LinkBuilder('short', true); -var _LONG_BUILDER = new TypeParser.LinkBuilder('long', true); -var _TEXT_BUILDER = new TypeParser.LinkBuilder('text', false); -var _TEXT_BUILDER_ENCODED = new TypeParser.LinkBuilder('text', true); +const typeParser = new TypeParser(); +const _SHORT_BUILDER = new TypeParser.LinkBuilder("short", true); +const _LONG_BUILDER = new TypeParser.LinkBuilder("long", true); +const _TEXT_BUILDER = new TypeParser.LinkBuilder("text", false); +const _TEXT_BUILDER_ENCODED = new TypeParser.LinkBuilder("text", true); /* function testTypeParser(type) { @@ -2037,12 +2083,12 @@ testTypeParser("{a:int,b,c:float,d,e}"); function _processTypeString(type, builder) { if ( type && Array.isArray(type.names) ) { - type = type.names.join('|'); + type = type.names.join("|"); } if ( type ) { try { return typeParser.parse( type, builder ).str; - } catch (e) { + } catch (e) { error("failed to parse type string '" + type + "': " + e); return type; } @@ -2060,21 +2106,23 @@ function linkTypes(type, short) { /** * Reduces the given text to a summary and removes all tags links etc. and escapes double quotes. * The result therefore should be suitable as content for an HTML tag attribute (e.g. title). - * @param sText - * @return summarized, plain attribute + * + * @param {string} sText + * @returns {string} summarized, plain attribute */ function asPlainSummary(sText) { - return sText ? summarize(sText).replace(/<.*?>/g, '').replace(/\{\@link\s*(.*?)\}/g, '$1').replace(/"/g,""") : ''; + return sText ? summarize(sText).replace(/<.*?>/g, "").replace(/\{\@link\s*(.*?)\}/g, "$1") + .replace(/"/g, """) : ""; } function getNSClass(item) { - if (item.kind === 'interface') { + if (item.kind === "interface") { return " interface"; - } else if (item.kind === 'namespace') { + } else if (item.kind === "namespace") { return " namespace"; - } else if (item.kind === 'typedef' ) { + } else if (item.kind === "typedef" ) { return " typedef"; - } else if (item.kind === 'member' && item.isEnum ) { + } else if (item.kind === "member" && item.isEnum ) { return " enum"; } else { return ""; @@ -2091,14 +2139,14 @@ function getNSClass(item) { * group 4: an isolated line feed + surrounding whitespace * * [-------

     block -------] [---- an empty line and surrounding whitespace ----] [---- new line or whitespaces ----] */
    -var rNormalizeText = /(]*)?>)|(<\/pre>)|([ \t]*(?:\r\n|\r|\n)[ \t]*(?:\r\n|\r|\n)[ \t\r\n]*)|([ \t]*(?:\r\n|\r|\n)[ \t]*|[ \t]+)/gi;
    +const rNormalizeText = /(]*)?>)|(<\/pre>)|([ \t]*(?:\r\n|\r|\n)[ \t]*(?:\r\n|\r|\n)[ \t\r\n]*)|([ \t]*(?:\r\n|\r|\n)[ \t]*|[ \t]+)/gi;
     
     function normalizeWS(text) {
     	if ( text == null ) {
     		return text;
     	}
     
    -	var inpre = false;
    +	let inpre = false;
     	return String(text).replace(rNormalizeText, function(match, pre, endpre, emptyline, ws) {
     		if ( pre ) {
     			inpre = true;
    @@ -2107,25 +2155,23 @@ function normalizeWS(text) {
     			inpre = false;
     			return endpre;
     		} else if ( emptyline ) {
    -			return inpre ? emptyline : '\n\n';
    +			return inpre ? emptyline : "\n\n";
     		} else if ( ws ) {
    -			return inpre ? ws : ' ';
    +			return inpre ? ws : " ";
     		}
     		return match;
     	});
    -
     }
     
    -//---- add on: API JSON -----------------------------------------------------------------
    +// ---- add on: API JSON -----------------------------------------------------------------
     
     function createAPIJSON(symbols, filename) {
    -
    -	var api = {
    +	const api = {
     		"$schema-ref": "http://schemas.sap.com/sapui5/designtime/api.json/1.0"
    -	}
    +	};
     
     	if ( templateConf.version ) {
    -		api.version = templateConf.version.replace(/-SNAPSHOT$/,"");
    +		api.version = templateConf.version.replace(/-SNAPSHOT$/, "");
     	}
     	if ( templateConf.uilib ) {
     		api.library = templateConf.uilib;
    @@ -2134,7 +2180,8 @@ function createAPIJSON(symbols, filename) {
     	api.symbols = [];
     	// sort only a copy(!) of the symbols, otherwise the SymbolSet lookup is broken
     	symbols.slice(0).sort(sortByAlias).forEach(function(symbol) {
    -		if ( isaClass(symbol) && !symbol.synthetic ) { // dump a symbol if it as a class symbol and if it is not a synthetic symbol
    +		if ( isaClass(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));
     		}
     	});
    @@ -2142,22 +2189,21 @@ function createAPIJSON(symbols, filename) {
     	postProcessAPIJSON(api);
     
     	fs.mkPath(path.dirname(filename));
    -	fs.writeFileSync(filename, JSON.stringify(api), 'utf8');
    +	fs.writeFileSync(filename, JSON.stringify(api), "utf8");
     	info("  apiJson saved as " + filename);
     }
     
     function createAPIJSON4Symbol(symbol, omitDefaults) {
    -
    -	var obj = [];
    -	var curr = obj;
    -	var attribForKind = 'kind';
    -	var stack = [];
    +	const obj = [];
    +	let curr = obj;
    +	let attribForKind = "kind";
    +	const stack = [];
     
     	function isEmpty(obj) {
     		if ( !obj ) {
     			return true;
     		}
    -		for (var n in obj) {
    +		for (const n in obj) {
     			if ( obj.hasOwnProperty(n) ) {
     				return false;
     			}
    @@ -2166,14 +2212,13 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     
     	function tag(name, value, omitEmpty) {
    -
     		if ( omitEmpty && !value ) {
     			return;
     		}
     		if ( arguments.length === 1 ) { // opening tag
     			stack.push(curr);
     			stack.push(attribForKind);
    -			var obj = {};
    +			const obj = {};
     			if ( Array.isArray(curr) ) {
     				if ( attribForKind != null ) {
     					obj[attribForKind] = name;
    @@ -2194,7 +2239,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     
     	function attrib(name, value, defaultValue, raw) {
    -		var emptyTag = arguments.length === 1;
    +		const emptyTag = arguments.length === 1;
     		if ( omitDefaults && arguments.length >= 3 && value === defaultValue ) {
     			return;
     		}
    @@ -2203,7 +2248,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     
     	function closeTag(name, noIndent) {
     		attribForKind = stack.pop();
    -		curr  = stack.pop();
    +		curr = stack.pop();
     	}
     
     	function collection(name, attribForKind) {
    @@ -2216,16 +2261,15 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     
     	function endCollection(name) {
     		attribForKind = stack.pop();
    -		curr  = stack.pop();
    +		curr = stack.pop();
     	}
     
     	function tagWithSince(name, value) {
    -
     		if ( !value ) {
     			return;
     		}
     
    -		var info = extractSince(value);
    +		const info = extractSince(value);
     
     		tag(name);
     		if ( info.since ) {
    @@ -2235,11 +2279,10 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     			curr["text"] = normalizeWS(info.value);
     		}
     		closeTag(name, true);
    -
     	}
     
     	function examples(symbol) {
    -		var j, example;
    +		let j; let example;
     
     		if ( symbol.examples && symbol.examples.length ) {
     			collection("examples");
    @@ -2263,11 +2306,11 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     
     	function visibility($) {
    -		if ( $.access === 'protected' ) {
    +		if ( $.access === "protected" ) {
     			return "protected";
    -		} else if ( $.access === 'restricted' ) {
    +		} else if ( $.access === "restricted" ) {
     			return "restricted";
    -		} else if ( $.access === 'private' ) {
    +		} else if ( $.access === "private" ) {
     			return "private";
     		} else {
     			return "public";
    @@ -2275,18 +2318,20 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     
     	function exceptions(symbol) {
    -		var array = symbol.exceptions,
    -			j, exception;
    -		
    +		let array = symbol.exceptions;
    +
    +
    +		let j; let exception;
    +
     		if ( Array.isArray(array) ) {
    -			array = array.filter( function (ex) {
    +			array = array.filter( function(ex) {
     				return (ex.type && listTypes(ex.type)) || (ex.description && ex.description.trim());
     			});
    -		} 
    -		if ( array == null || array.length === 0 ) {
    +		}
    +		if ( array == null || array.length === 0 ) {
     			return;
     		}
    -		
    +
     		collection("throws");
     		for (j = 0; j < array.length; j++) {
     			exception = array[j];
    @@ -2301,7 +2346,9 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     
     	function methodList(tagname, methods) {
    -		methods = methods && Object.keys(methods).map(function(key) { return methods[key]; });
    +		methods = methods && Object.keys(methods).map(function(key) {
    +			return methods[key];
    +		});
     		if ( methods != null && methods.length > 0 ) {
     			curr[tagname] = methods;
     		}
    @@ -2314,23 +2361,22 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     
     	function hasSettings($, visited) {
    -
     		visited = visited || {};
     
     		if ( $.augments && $.augments.length > 0 ) {
    -			var baseSymbol = $.augments[0];
    +			let baseSymbol = $.augments[0];
     			if ( visited.hasOwnProperty(baseSymbol) ) {
     				error("detected cyclic inheritance when looking at " + $.longname + ": " + JSON.stringify(visited));
     				return false;
     			}
     			visited[baseSymbol] = true;
    -			baseSymbol = lookup(baseSymbol) ;
    +			baseSymbol = lookup(baseSymbol);
     			if ( hasSettings(baseSymbol, visited) ) {
     				return true;
     			}
     		}
     
    -		var metadata = $.__ui5.metadata;
    +		const metadata = $.__ui5.metadata;
     		return metadata &&
     			(
     				!isEmpty(metadata.specialSettings)
    @@ -2343,22 +2389,21 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     
     	function writeMetadata($) {
    -
    -		var metadata = $.__ui5.metadata;
    +		const metadata = $.__ui5.metadata;
     		if ( !metadata ) {
     			return;
     		}
     
    -		var n;
    +		let n;
     
     		if ( metadata.specialSettings && Object.keys(metadata.specialSettings).length > 0 ) {
     			collection("specialSettings");
     			for ( n in metadata.specialSettings ) {
    -				var special = metadata.specialSettings[n];
    +				const special = metadata.specialSettings[n];
     				tag("specialSetting");
     				attrib("name", special.name);
     				attrib("type", special.type);
    -				attrib("visibility", special.visibility, 'public');
    +				attrib("visibility", special.visibility, "public");
     				if ( special.since ) {
     					attrib("since", extractVersion(special.since));
     				}
    @@ -2374,13 +2419,13 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     		if ( metadata.properties && Object.keys(metadata.properties).length > 0 ) {
     			collection("properties");
     			for ( n in metadata.properties ) {
    -				var prop = metadata.properties[n];
    +				const prop = metadata.properties[n];
     				tag("property");
     				attrib("name", prop.name);
    -				attrib("type", prop.type, 'string');
    +				attrib("type", prop.type, "string");
     				attrib("defaultValue", prop.defaultValue, null, /* raw = */true);
    -				attrib("group", prop.group, 'Misc');
    -				attrib("visibility", prop.visibility, 'public');
    +				attrib("group", prop.group, "Misc");
    +				attrib("visibility", prop.visibility, "public");
     				if ( prop.since ) {
     					attrib("since", extractVersion(prop.since));
     				}
    @@ -2407,16 +2452,16 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     		if ( metadata.aggregations && Object.keys(metadata.aggregations).length > 0 ) {
     			collection("aggregations");
     			for ( n in metadata.aggregations ) {
    -				var aggr = metadata.aggregations[n];
    +				const aggr = metadata.aggregations[n];
     				tag("aggregation");
     				attrib("name", aggr.name);
     				attrib("singularName", aggr.singularName); // TODO omit default?
    -				attrib("type", aggr.type, 'sap.ui.core.Control');
    +				attrib("type", aggr.type, "sap.ui.core.Control");
     				if ( aggr.altTypes ) {
     					curr.altTypes = aggr.altTypes.slice();
     				}
    -				attrib("cardinality", aggr.cardinality, '0..n');
    -				attrib("visibility", aggr.visibility, 'public');
    +				attrib("cardinality", aggr.cardinality, "0..n");
    +				attrib("visibility", aggr.visibility, "public");
     				if ( aggr.since ) {
     					attrib("since", extractVersion(aggr.since));
     				}
    @@ -2442,13 +2487,13 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     		if ( metadata.associations && Object.keys(metadata.associations).length > 0 ) {
     			collection("associations");
     			for ( n in metadata.associations ) {
    -				var assoc = metadata.associations[n];
    +				const assoc = metadata.associations[n];
     				tag("association");
     				attrib("name", assoc.name);
     				attrib("singularName", assoc.singularName); // TODO omit default?
    -				attrib("type", assoc.type, 'sap.ui.core.Control');
    -				attrib("cardinality", assoc.cardinality, '0..1');
    -				attrib("visibility", assoc.visibility, 'public');
    +				attrib("type", assoc.type, "sap.ui.core.Control");
    +				attrib("cardinality", assoc.cardinality, "0..1");
    +				attrib("visibility", assoc.visibility, "public");
     				if ( assoc.since ) {
     					attrib("since", extractVersion(assoc.since));
     				}
    @@ -2464,10 +2509,10 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     		if ( metadata.events && Object.keys(metadata.events).length > 0 ) {
     			collection("events");
     			for ( n in metadata.events ) {
    -				var event = metadata.events[n];
    +				const event = metadata.events[n];
     				tag("event");
     				attrib("name", event.name);
    -				attrib("visibility", event.visibility, 'public');
    +				attrib("visibility", event.visibility, "public");
     				if ( event.since ) {
     					attrib("since", extractVersion(event.since));
     				}
    @@ -2476,9 +2521,9 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     				tagWithSince("deprecated", event.deprecation);
     				if ( event.parameters && Object.keys(event.parameters).length > 0 ) {
     					tag("parameters");
    -					for ( var pn in event.parameters ) {
    +					for ( const pn in event.parameters ) {
     						if ( event.parameters.hasOwnProperty(pn) ) {
    -							var param = event.parameters[pn];
    +							const param = event.parameters[pn];
     							tag(pn);
     							attrib("name", pn);
     							attrib("type", param.type);
    @@ -2502,7 +2547,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     		if ( metadata.annotations && Object.keys(metadata.annotations).length > 0 ) {
     			collection("annotations");
     			for ( n in metadata.annotations ) {
    -				var anno = metadata.annotations[n];
    +				const anno = metadata.annotations[n];
     				tag("annotation");
     				attrib("name", anno.name);
     				attrib("namespace", anno.namespace);
    @@ -2523,26 +2568,28 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     			}
     			endCollection("annotations");
     		}
    -		
    +
     		if ( metadata.designtime ) { // don't write falsy values
     			tag("designtime", metadata.designtime);
     		}
    -
     	}
     
     	function writeParameterProperties(paramName, params) {
    -		var prefix = paramName + '.',
    -			count = 0,
    -			i;
    +		const prefix = paramName + ".";
     
    -		for ( i = 0; i < params.length; i++ ) {
     
    -			var name = params[i].name;
    +		let count = 0;
    +
    +
    +		let i;
    +
    +		for ( i = 0; i < params.length; i++ ) {
    +			let name = params[i].name;
     			if ( name.lastIndexOf(prefix, 0) !== 0 ) { // startsWith
     				continue;
     			}
     			name = name.slice(prefix.length);
    -			if ( name.indexOf('.') >= 0 ) {
    +			if ( name.indexOf(".") >= 0 ) {
     				continue;
     			}
     
    @@ -2609,7 +2656,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     	*/
     
    -	var kind = (symbol.kind === 'member' && symbol.isEnum) ? "enum" : symbol.kind; // handle pseudo-kind 'enum'
    +	const kind = (symbol.kind === "member" && symbol.isEnum) ? "enum" : symbol.kind; // handle pseudo-kind 'enum'
     
     	tag(kind);
     
    @@ -2620,7 +2667,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     	if ( symbol.__ui5.module ) {
     		attrib("module", symbol.__ui5.module);
    -		attrib("export", undefined, '', true);
    +		attrib("export", undefined, "", true);
     	}
     	if ( symbol.virtual ) {
     		attrib("abstract", true, false, /* raw = */true);
    @@ -2628,10 +2675,10 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	if ( symbol.final_ ) {
     		attrib("final", true, false, /* raw = */true);
     	}
    -	if ( symbol.scope === 'static' ) {
    +	if ( symbol.scope === "static" ) {
     		attrib("static", true, false, /* raw = */true);
     	}
    -	attrib("visibility", visibility(symbol), 'public');
    +	attrib("visibility", visibility(symbol), "public");
     	if ( symbol.since ) {
     		attrib("since", extractVersion(symbol.since));
     	}
    @@ -2639,19 +2686,19 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     		tag("extends", symbol.augments.sort().join(",")); // TODO what about multiple inheritance?
     	}
     	interfaceList("implements", symbol.implements);
    -	tag("description", normalizeWS(symbol.classdesc || (symbol.kind === 'class' ? '' : symbol.description)), true);
    +	tag("description", normalizeWS(symbol.classdesc || (symbol.kind === "class" ? "" : symbol.description)), true);
     	tagWithSince("experimental", symbol.experimental);
     	tagWithSince("deprecated", symbol.deprecated);
    -	if ( symbol.tags && symbol.tags.some(function(tag) { return tag.title === 'ui5-metamodel'; }) ) {
    -		attrib('ui5-metamodel', true, false, /* raw = */true);
    +	if ( symbol.tags && symbol.tags.some(function(tag) {
    +		return tag.title === "ui5-metamodel";
    +	}) ) {
    +		attrib("ui5-metamodel", true, false, /* raw = */true);
     	}
     
    -	var i, j, member, param;
    -
    -	if ( kind === 'class' ) {
    +	let i; let j; let member; let param;
     
    +	if ( kind === "class" ) {
     		if ( symbol.__ui5.stereotype || hasSettings(symbol) ) {
    -
     			tag("ui5-metadata");
     
     			if ( symbol.__ui5.stereotype ) {
    @@ -2666,14 +2713,13 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     
     		// IF @hideconstructor tag is present we omit the whole constructor
     		if ( !symbol.hideconstructor ) {
    -
     			tag("constructor");
     			attrib("visibility", visibility(symbol));
     			if (symbol.params && symbol.params.length > 0) {
     				collection("parameters");
     				for (j = 0; j < symbol.params.length; j++) {
     					param = symbol.params[j];
    -					if (param.name.indexOf('.') >= 0) {
    +					if (param.name.indexOf(".") >= 0) {
     						continue;
     					}
     					tag("parameter");
    @@ -2703,9 +2749,8 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     			referencesList(symbol); // TODO here or for class?
     			// secTags(symbol); // TODO repeat from class?
     			closeTag("constructor");
    -
     		}
    -	} else if ( kind === 'namespace' ) {
    +	} else if ( kind === "namespace" ) {
     		if ( symbol.__ui5.stereotype || symbol.__ui5.metadata ) {
     			tag("ui5-metadata");
     
    @@ -2729,7 +2774,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     		}
     	}
     
    -	var ownProperties = childrenOfKind(symbol, "property").own.sort(sortByAlias);
    +	const ownProperties = childrenOfKind(symbol, "property").own.sort(sortByAlias);
     	if ( ownProperties.length > 0 ) {
     		collection("properties");
     		for ( i = 0; i < ownProperties.length; i++ ) {
    @@ -2738,10 +2783,10 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     			attrib("name", member.name);
     			if ( member.__ui5.module && member.__ui5.module !== symbol.__ui5.module ) {
     				attrib("module", member.__ui5.module);
    -				attrib("export", undefined, '', true);
    +				attrib("export", undefined, "", true);
     			}
    -			attrib("visibility", visibility(member), 'public');
    -			if ( member.scope === 'static' ) {
    +			attrib("visibility", visibility(member), "public");
    +			if ( member.scope === "static" ) {
     				attrib("static", true, false, /* raw = */true);
     			}
     			if ( member.since ) {
    @@ -2761,7 +2806,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     		endCollection("properties");
     	}
     
    -	var ownEvents = childrenOfKind(symbol, 'event').own.sort(sortByAlias);
    +	const ownEvents = childrenOfKind(symbol, "event").own.sort(sortByAlias);
     	if ( ownEvents.length > 0 ) {
     		collection("events");
     		for (i = 0; i < ownEvents.length; i++ ) {
    @@ -2770,10 +2815,10 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     			attrib("name", member.name);
     			if ( member.__ui5.module && member.__ui5.module !== symbol.__ui5.module ) {
     				attrib("module", member.__ui5.module);
    -				attrib("export", undefined, '', true);
    +				attrib("export", undefined, "", true);
     			}
    -			attrib("visibility", visibility(member), 'public');
    -			if ( member.scope === 'static' ) {
    +			attrib("visibility", visibility(member), "public");
    +			if ( member.scope === "static" ) {
     				attrib("static", true, false, /* raw = */true);
     			}
     			if ( member.since ) {
    @@ -2784,7 +2829,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     				collection("parameters");
     				for (j = 0; j < member.params.length; j++) {
     					param = member.params[j];
    -					if ( param.name.indexOf('.') >= 0 ) {
    +					if ( param.name.indexOf(".") >= 0 ) {
     						continue;
     					}
     
    @@ -2807,7 +2852,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     			tagWithSince("experimental", member.experimental);
     			examples(member);
     			referencesList(member);
    -			//secTags(member);
    +			// secTags(member);
     			if ( member.__ui5.resource && member.__ui5.resource !== symbol.__ui5.resource ) {
     				attrib("resource", member.__ui5.resource);
     			}
    @@ -2816,7 +2861,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     		endCollection("events");
     	}
     
    -	var ownMethods = childrenOfKind(symbol, 'method').own.sort(sortByAlias);
    +	const ownMethods = childrenOfKind(symbol, "method").own.sort(sortByAlias);
     	if ( ownMethods.length > 0 ) {
     		collection("methods");
     		for ( i = 0; i < ownMethods.length; i++ ) {
    @@ -2825,25 +2870,27 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     			attrib("name", member.name);
     			if ( member.__ui5.module && member.__ui5.module !== symbol.__ui5.module ) {
     				attrib("module", member.__ui5.module);
    -				attrib("export", undefined, '', true);
    +				attrib("export", undefined, "", true);
     			}
    -			attrib("visibility", visibility(member), 'public');
    -			if ( member.scope === 'static' ) {
    +			attrib("visibility", visibility(member), "public");
    +			if ( member.scope === "static" ) {
     				attrib("static", true, false, /* raw = */true);
     			}
    -			if ( member.tags && member.tags.some(function(tag) { return tag.title === 'ui5-metamodel'; }) ) {
    -				attrib('ui5-metamodel', true, false, /* raw = */true);
    +			if ( member.tags && member.tags.some(function(tag) {
    +				return tag.title === "ui5-metamodel";
    +			}) ) {
    +				attrib("ui5-metamodel", true, false, /* raw = */true);
     			}
     
    -			var returns = member.returns && member.returns.length && member.returns[0];
    -			var type = member.type || (returns && returns.type);
    +			const returns = member.returns && member.returns.length && member.returns[0];
    +			let type = member.type || (returns && returns.type);
     			type = listTypes(type);
    -			//if ( type && type !== 'void' ) {
    +			// if ( type && type !== 'void' ) {
     			//	attrib("type", type, 'void');
    -			//}
    -			if ( type && type !== 'void' || returns && returns.description ) {
    +			// }
    +			if ( type && type !== "void" || returns && returns.description ) {
     				tag("returnValue");
    -				if ( type && type !== 'void' ) {
    +				if ( type && type !== "void" ) {
     					attrib("type", type);
     				}
     				if ( returns && returns.description ) {
    @@ -2859,7 +2906,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     				collection("parameters");
     				for ( j = 0; j < member.params.length; j++) {
     					param = member.params[j];
    -					if ( param.name.indexOf('.') >= 0 ) {
    +					if ( param.name.indexOf(".") >= 0 ) {
     						continue;
     					}
     					tag("parameter");
    @@ -2886,7 +2933,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     			tagWithSince("deprecated", member.deprecated);
     			examples(member);
     			referencesList(member);
    -			//secTags(member);
    +			// secTags(member);
     			if ( member.__ui5.resource && member.__ui5.resource !== symbol.__ui5.resource ) {
     				attrib("resource", member.__ui5.resource);
     			}
    @@ -2895,11 +2942,11 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     		endCollection("methods");
     	}
     
    -//	if ( roots && symbol.__ui5.children && symbol.__ui5.children.length ) {
    -//		collection("children", "kind");
    -//		symbol.__ui5.children.forEach(writeSymbol);
    -//		endCollection("children");
    -//	}
    +	//	if ( roots && symbol.__ui5.children && symbol.__ui5.children.length ) {
    +	//		collection("children", "kind");
    +	//		symbol.__ui5.children.forEach(writeSymbol);
    +	//		endCollection("children");
    +	//	}
     
     	closeTag(kind);
     
    @@ -2907,15 +2954,15 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     }
     
     function postProcessAPIJSON(api) {
    -	var modules = {};
    -	var symbols = api.symbols;
    -	var i,j,n,symbol,defaultExport;
    -	
    -	// collect modules and the symbols that refer to them 
    +	const modules = {};
    +	let symbols = api.symbols;
    +	let i; let j; let n; let symbol; let defaultExport;
    +
    +	// collect modules and the symbols that refer to them
     	for ( i = 0; i < symbols.length; i++) {
     		symbol = symbols[i];
     		if ( symbol.module ) {
    -			modules[symbol.module] = modules[symbol.module] || [];
    +			modules[symbol.module] = modules[symbol.module] || [];
     			modules[symbol.module].push({
     				name: symbol.name,
     				symbol: symbol
    @@ -2924,7 +2971,7 @@ function postProcessAPIJSON(api) {
     		if ( symbol.properties ) {
     			for ( j = 0; j < symbol.properties.length; j++ ) {
     				if ( symbol.properties[j].static && symbol.properties[j].module ) {
    -					modules[symbol.properties[j].module] = modules[symbol.properties[j].module] || [];
    +					modules[symbol.properties[j].module] = modules[symbol.properties[j].module] || [];
     					modules[symbol.properties[j].module].push({
     						name: symbol.name + "." + symbol.properties[j].name,
     						symbol: symbol.properties[j]
    @@ -2935,7 +2982,7 @@ function postProcessAPIJSON(api) {
     		if ( symbol.methods ) {
     			for ( j = 0; j < symbol.methods.length; j++ ) {
     				if ( symbol.methods[j].static && symbol.methods[j].module ) {
    -					modules[symbol.methods[j].module] = modules[symbol.methods[j].module] || [];
    +					modules[symbol.methods[j].module] = modules[symbol.methods[j].module] || [];
     					modules[symbol.methods[j].module].push({
     						name: symbol.name + "." + symbol.methods[j].name,
     						symbol: symbol.methods[j]
    @@ -2944,44 +2991,45 @@ function postProcessAPIJSON(api) {
     			}
     		}
     	}
    -	
    +
     	function guessExport(defaultExport, symbol) {
     		if ( symbol.name === defaultExport ) {
     			// default export equals the symbol name
    -			symbol.symbol.export = ""; 
    -			//console.log("    (default):" + defaultExport);
    -		} else if ( symbol.name.lastIndexOf(defaultExport + ".", 0) === 0 ) {
    +			symbol.symbol.export = "";
    +			// console.log("    (default):" + defaultExport);
    +		} else if ( symbol.name.lastIndexOf(defaultExport + ".", 0) === 0 ) {
     			// default export is a prefix of the symbol name
    -			symbol.symbol.export = symbol.name.slice(defaultExport.length + 1); 
    -			//console.log("    " + symbol.name.slice(defaultExport.length + 1) + ":" + symbol.name);
    +			symbol.symbol.export = symbol.name.slice(defaultExport.length + 1);
    +			// console.log("    " + symbol.name.slice(defaultExport.length + 1) + ":" + symbol.name);
     		} else {
    -			// default export is not a prefix of the symbol name -> no way to access it in AMD 
    +			// default export is not a prefix of the symbol name -> no way to access it in AMD
     			symbol.symbol.export = undefined;
     			console.log("    **** could not identify module export for API " + symbol.name);
     		}
     	}
    -	
    +
     	for ( n in modules ) {
    -		
    -		symbols = modules[n].sort(function(a,b) {
    +		symbols = modules[n].sort(function(a, b) {
     			if ( a.name === b.name ) {
     				return 0;
     			}
     			return a.name < b.name ? -1 : 1;
     		});
    -		
    +
     		// console.log('  resolved exports of ' + n + ": " + symbols.map(function(symbol) { return symbol.name; } ));
     		if ( /^jquery\.sap\./.test(n) ) {
     			// the jquery.sap.* modules all export 'jQuery'.
     			// any API from those modules is reachable via 'jQuery.*'
    -			defaultExport = 'jQuery';
    +			defaultExport = "jQuery";
     			symbols.forEach(
     				guessExport.bind(this, defaultExport)
     			);
     		} else if ( /\/library$/.test(n) ) {
     			// library.js modules export the library namespace
     			defaultExport = n.replace(/\/library$/, "").replace(/\//g, ".");
    -			if ( symbols.some(function(symbol) { return symbol.name === defaultExport; }) ) {
    +			if ( symbols.some(function(symbol) {
    +				return symbol.name === defaultExport;
    +			}) ) {
     				// if there is a symbol for the namespace, then all other symbols from the module should be sub-exports of that symbol
     				symbols.forEach(
     					guessExport.bind(this, defaultExport)
    @@ -2996,14 +3044,16 @@ function postProcessAPIJSON(api) {
     		} else {
     			// for all other modules, the assumed default export is identical to the name of the module (converted to a 'dot' name)
     			defaultExport = n.replace(/\//g, ".");
    -			if ( symbols.some(function(symbol) { return symbol.name === defaultExport; }) ) {
    +			if ( symbols.some(function(symbol) {
    +				return symbol.name === defaultExport;
    +			}) ) {
     				symbols.forEach(
     					guessExport.bind(this, defaultExport)
     				);
    -			//} else if ( symbols.length === 1 && (symbols[0].symbol.kind === 'class' || symbols[0].symbol.kind === 'namespace') ) {
    +			// } else if ( symbols.length === 1 && (symbols[0].symbol.kind === 'class' || symbols[0].symbol.kind === 'namespace') ) {
     				// if there is only one symbol and if that symbol is of type class or namespace, assume it is the default export
     				// TODO is that assumption safe? Was only done because of IBarPageEnabler (which maybe better should be fixed in the JSDoc)
    -				//symbols[0].symbol.export = '';
    +				// symbols[0].symbol.export = '';
     			} else {
     				symbols.forEach(function(symbol) {
     					symbol.symbol.export = undefined;
    @@ -3014,28 +3064,26 @@ function postProcessAPIJSON(api) {
     	}
     }
     
    -//---- add on: API XML -----------------------------------------------------------------
    +// ---- add on: API XML -----------------------------------------------------------------
     
     function createAPIXML(symbols, filename, options) {
    -
     	options = options || {};
    -	var roots = options.roots || null;
    -	var legacyContent = !!options.legacyContent;
    -	var omitDefaults = !!options.omitDefaults;
    -	var addRedundancy = !!options.resolveInheritance;
    -
    -	var indent = 0;
    -	var output = [];
    -	var sIndent = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
    -	var tags = [];
    -	var ENUM = legacyContent ? "namespace" : "enum" ;
    -	var BASETYPE = legacyContent ? "baseType" : "extends";
    -	var PROPERTY = legacyContent ? "parameter" : "property";
    -	var unclosedStartTag = false;
    +	const roots = options.roots || null;
    +	const legacyContent = !!options.legacyContent;
    +	const omitDefaults = !!options.omitDefaults;
    +	const addRedundancy = !!options.resolveInheritance;
    +
    +	let indent = 0;
    +	const output = [];
    +	const sIndent = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
    +	let tags = [];
    +	const ENUM = legacyContent ? "namespace" : "enum";
    +	const BASETYPE = legacyContent ? "baseType" : "extends";
    +	const PROPERTY = legacyContent ? "parameter" : "property";
    +	let unclosedStartTag = false;
     
     	function getAPIJSON(name) {
    -
    -		var symbol = lookup(name);
    +		const symbol = lookup(name);
     		if ( symbol && !symbol.synthetic ) {
     			return createAPIJSON4Symbol(symbol, false);
     		}
    @@ -3050,19 +3098,22 @@ function createAPIXML(symbols, filename, options) {
     		return s ? s.replace(/&/g, "&").replace(/ 0 )
    -			output.push(sIndent.slice(0,indent));
    -		if ( arguments.length ) {
    -			for (var i = 0; i < arguments.length; i++)
    -				output.push(arguments[i]);
    +	function writeln(...args) {
    +		if ( indent > 0 ) {
    +			output.push(sIndent.slice(0, indent));
    +		}
    +		if ( args.length ) {
    +			for (let i = 0; i < args.length; i++) {
    +				output.push(args[i]);
    +			}
     		}
     		output.push("\n");
     	}
    @@ -3082,17 +3133,16 @@ function createAPIXML(symbols, filename, options) {
     	}
     
     	function tag(name, value, omitEmpty) {
    -
     		if ( omitEmpty && !value ) {
     			return;
     		}
     		if ( unclosedStartTag ) {
     			unclosedStartTag = false;
    -			write('>\n');
    +			write(">\n");
     		}
     		if ( arguments.length === 1 ) { // opening tag
     			if ( indent > 0 ) {
    -				output.push(sIndent.slice(0,indent));
    +				output.push(sIndent.slice(0, indent));
     			}
     			write("<", name);
     			unclosedStartTag = true;
    @@ -3112,7 +3162,7 @@ function createAPIXML(symbols, filename, options) {
     	}
     
     	function attrib(name, value, defaultValue) {
    -		var emptyTag = arguments.length === 1;
    +		const emptyTag = arguments.length === 1;
     		if ( omitDefaults && arguments.length === 3 && value === defaultValue ) {
     			return;
     		}
    @@ -3131,9 +3181,8 @@ function createAPIXML(symbols, filename, options) {
     	}
     
     	function closeTag(name, noIndent) {
    -
     		indent--;
    -		var top = tags.pop();
    +		const top = tags.pop();
     		if ( top != name ) {
     			// ERROR?
     		}
    @@ -3151,7 +3200,7 @@ function createAPIXML(symbols, filename, options) {
     	function textContent(text) {
     		if ( unclosedStartTag ) {
     			unclosedStartTag = false;
    -			write('>');
    +			write(">");
     		}
     		write(encode(text));
     	}
    @@ -3174,14 +3223,13 @@ function createAPIXML(symbols, filename, options) {
     	}
     
     	function writeMetadata(symbolAPI, inherited) {
    -
    -		var ui5Metadata = symbolAPI["ui5-metadata"];
    +		const ui5Metadata = symbolAPI["ui5-metadata"];
     		if ( !ui5Metadata ) {
     			return;
     		}
     
     		if ( addRedundancy && symbolAPI["extends"] ) {
    -			var baseSymbolAPI = getAPIJSON(symbolAPI["extends"]);
    +			const baseSymbolAPI = getAPIJSON(symbolAPI["extends"]);
     			if ( baseSymbolAPI ) {
     				writeMetadata(baseSymbolAPI, true);
     			}
    @@ -3192,7 +3240,7 @@ function createAPIXML(symbols, filename, options) {
     				tag("specialSetting");
     				attrib("name", special.name);
     				attrib("type", special.type);
    -				attrib("visibility", special.visibility, 'public');
    +				attrib("visibility", special.visibility, "public");
     				if ( special.since ) {
     					attrib("since", special.since);
     				}
    @@ -3211,11 +3259,11 @@ function createAPIXML(symbols, filename, options) {
     			ui5Metadata.properties.forEach(function(prop) {
     				tag("property");
     				attrib("name", prop.name);
    -				attrib("type", prop.type, 'string');
    +				attrib("type", prop.type, "string");
     				if ( prop.defaultValue !== null ) {
     					attrib("defaultValue", prop.defaultValue, null);
     				}
    -				attrib("visibility", prop.visibility, 'public');
    +				attrib("visibility", prop.visibility, "public");
     				if ( prop.since ) {
     					attrib("since", prop.since);
     				}
    @@ -3242,12 +3290,12 @@ function createAPIXML(symbols, filename, options) {
     				tag("aggregation");
     				attrib("name", aggr.name);
     				attrib("singularName", aggr.singularName); // TODO omit default?
    -				attrib("type", aggr.type, 'sap.ui.core.Control');
    +				attrib("type", aggr.type, "sap.ui.core.Control");
     				if ( aggr.altTypes ) {
     					attrib("altTypes", aggr.altTypes.join(","));
     				}
    -				attrib("cardinality", aggr.cardinality, '0..n');
    -				attrib("visibility", aggr.visibility, 'public');
    +				attrib("cardinality", aggr.cardinality, "0..n");
    +				attrib("visibility", aggr.visibility, "public");
     				if ( aggr.since ) {
     					attrib("since", aggr.since);
     				}
    @@ -3274,9 +3322,9 @@ function createAPIXML(symbols, filename, options) {
     				tag("association");
     				attrib("name", assoc.name);
     				attrib("singularName", assoc.singularName); // TODO omit default?
    -				attrib("type", assoc.type, 'sap.ui.core.Control');
    -				attrib("cardinality", assoc.cardinality, '0..1');
    -				attrib("visibility", assoc.visibility, 'public');
    +				attrib("type", assoc.type, "sap.ui.core.Control");
    +				attrib("cardinality", assoc.cardinality, "0..1");
    +				attrib("visibility", assoc.visibility, "public");
     				if ( assoc.since ) {
     					attrib("since", assoc.since);
     				}
    @@ -3295,7 +3343,7 @@ function createAPIXML(symbols, filename, options) {
     			ui5Metadata.events.forEach(function(event) {
     				tag("event");
     				attrib("name", event.name);
    -				attrib("visibility", event.visibility, 'public');
    +				attrib("visibility", event.visibility, "public");
     				if ( event.since ) {
     					attrib("since", event.since);
     				}
    @@ -3307,9 +3355,9 @@ function createAPIXML(symbols, filename, options) {
     				tagWithSince("deprecated", event.deprecated);
     				if ( event.parameters ) {
     					tag("parameters");
    -					for ( var pn in event.parameters ) {
    +					for ( const pn in event.parameters ) {
     						if ( event.parameters.hasOwnProperty(pn) ) {
    -							var param = event.parameters[pn];
    +							const param = event.parameters[pn];
     
     							tag("parameter");
     							attrib("name", param.name);
    @@ -3346,24 +3394,22 @@ function createAPIXML(symbols, filename, options) {
     				closeTag("annotation");
     			});
     		}
    -
     	}
     
     	function writeParameterPropertiesForMSettings(symbolAPI, inherited) {
    -
    -		var ui5Metadata = symbolAPI["ui5-metadata"];
    +		const ui5Metadata = symbolAPI["ui5-metadata"];
     		if ( !ui5Metadata ) {
     			return;
     		}
     
     		if ( symbolAPI["extends"] ) {
    -			var baseSymbolAPI = getAPIJSON(symbolAPI["extends"]);
    +			const baseSymbolAPI = getAPIJSON(symbolAPI["extends"]);
     			writeParameterPropertiesForMSettings(baseSymbolAPI, true);
     		}
     
     		if ( ui5Metadata.specialSettings ) {
     			ui5Metadata.specialSettings.forEach(function(special) {
    -				if ( special.visibility !== 'hidden' ) {
    +				if ( special.visibility !== "hidden" ) {
     					tag("property");
     					attrib("name", special.name);
     					attrib("type", special.type);
    @@ -3382,9 +3428,9 @@ function createAPIXML(symbols, filename, options) {
     				tag("property");
     				attrib("name", prop.name);
     				attrib("type", prop.type);
    -				attrib("group", prop.group, 'Misc');
    +				attrib("group", prop.group, "Misc");
     				if ( prop.defaultValue !== null ) {
    -					attrib("defaultValue", typeof prop.defaultValue === 'string' ? "\"" + prop.defaultValue + "\"" : prop.defaultValue);
    +					attrib("defaultValue", typeof prop.defaultValue === "string" ? "\"" + prop.defaultValue + "\"" : prop.defaultValue);
     				}
     				attrib("optional");
     				if ( inherited ) {
    @@ -3400,7 +3446,7 @@ function createAPIXML(symbols, filename, options) {
     				if ( aggr.visibility !== "hidden" ) {
     					tag("property");
     					attrib("name", aggr.name);
    -					attrib("type", aggr.type + (aggr.cardinality === '0..1' ? "" : "[]"));
    +					attrib("type", aggr.type + (aggr.cardinality === "0..1" ? "" : "[]"));
     					if ( aggr.altTypes ) {
     						attrib("altTypes", aggr.altTypes.join(","));
     					}
    @@ -3419,7 +3465,7 @@ function createAPIXML(symbols, filename, options) {
     				if ( assoc.visibility !== "hidden" ) {
     					tag("property");
     					attrib("name", assoc.name);
    -					attrib("type", "(" + assoc.type + "|" + "string)" + (assoc.cardinality === '0..1' ? "" : "[]"));
    +					attrib("type", "(" + assoc.type + "|" + "string)" + (assoc.cardinality === "0..1" ? "" : "[]"));
     					attrib("optional");
     					if ( inherited ) {
     						attrib("origin", symbolAPI.name);
    @@ -3443,18 +3489,20 @@ function createAPIXML(symbols, filename, options) {
     				closeTag("property");
     			});
     		}
    -
     	}
     
     	function writeParameterProperties(param, paramName) {
    -		var props = param.parameterProperties,
    -			prefix = paramName + '.',
    -			count = 0;
    +		const props = param.parameterProperties;
    +
    +
    +		const prefix = paramName + ".";
    +
    +
    +		let count = 0;
     
     		if ( props ) {
    -			for (var n in props ) {
    +			for (const n in props ) {
     				if ( props.hasOwnProperty(n) ) {
    -
     					param = props[n];
     
     					if ( !legacyContent && count === 0 ) {
    @@ -3528,23 +3576,21 @@ function createAPIXML(symbols, filename, options) {
     	*/
     
     	function writeSymbol(symbol) {
    -
    -		var kind;
    +		let kind;
     
     		if ( isaClass(symbol) && (roots || !symbol.synthetic) ) { // dump a symbol if it as a class symbol and if either hierarchies are dumped or if it is not a synthetic symbol
    -
     			// for the hierarchy we use only the local information
    -			var symbolAPI = createAPIJSON4Symbol(symbol);
    +			const symbolAPI = createAPIJSON4Symbol(symbol);
     
    -			kind = symbolAPI.kind === 'enum' ? ENUM : symbolAPI.kind;
    +			kind = symbolAPI.kind === "enum" ? ENUM : symbolAPI.kind;
     
     			tag(kind);
     
     			attrib("name", symbolAPI.name);
     			attrib("basename", symbolAPI.basename);
    -//			if ( symbolAPI["resource"] ) {
    -//				attrib("resource");
    -//			}
    +			//			if ( symbolAPI["resource"] ) {
    +			//				attrib("resource");
    +			//			}
     			if ( symbolAPI["module"] ) {
     				attrib("module", symbolAPI["module"]);
     			}
    @@ -3557,7 +3603,7 @@ function createAPIXML(symbols, filename, options) {
     			if ( symbolAPI["static"] ) {
     				attrib("static");
     			}
    -			attrib("visibility", symbolAPI.visibility, 'public');
    +			attrib("visibility", symbolAPI.visibility, "public");
     			if ( symbolAPI.since ) {
     				attrib("since", symbolAPI.since);
     			}
    @@ -3568,12 +3614,10 @@ function createAPIXML(symbols, filename, options) {
     			tagWithSince("experimental", symbolAPI.experimental);
     			tagWithSince("deprecated", symbolAPI.deprecated);
     
    -			if ( kind === 'class' ) {
    -
    -				var hasSettings = symbolAPI["ui5-metadata"];
    +			if ( kind === "class" ) {
    +				const hasSettings = symbolAPI["ui5-metadata"];
     
     				if ( !legacyContent && symbolAPI["ui5-metadata"] ) {
    -
     					tag("ui5-metadata");
     
     					if ( symbolAPI["ui5-metadata"].stereotype ) {
    @@ -3583,17 +3627,15 @@ function createAPIXML(symbols, filename, options) {
     					writeMetadata(symbolAPI);
     
     					closeTag("ui5-metadata");
    -
     				}
     
     				tag("constructor");
     				if ( legacyContent ) {
     					attrib("name", symbolAPI.basename);
     				}
    -				attrib("visibility", symbolAPI.visibility, 'public');
    +				attrib("visibility", symbolAPI.visibility, "public");
     				if ( symbolAPI.constructor.parameters ) {
     					symbolAPI.constructor.parameters.forEach(function(param, j) {
    -
     						tag("parameter");
     						attrib("name", param.name);
     						attrib("type", param.type);
    @@ -3650,7 +3692,7 @@ function createAPIXML(symbols, filename, options) {
     					if ( member.module ) {
     						attrib("module", member.module);
     					}
    -					attrib("visibility", member.visibility, 'public');
    +					attrib("visibility", member.visibility, "public");
     					if ( member["static"] ) {
     						attrib("static");
     					}
    @@ -3672,7 +3714,7 @@ function createAPIXML(symbols, filename, options) {
     					if ( member.module ) {
     						attrib("module", member.module);
     					}
    -					attrib("visibility", member.visibility, 'public');
    +					attrib("visibility", member.visibility, "public");
     					if ( member["static"] ) {
     						attrib("static");
     					}
    @@ -3682,7 +3724,6 @@ function createAPIXML(symbols, filename, options) {
     
     					if ( member.parameters ) {
     						member.parameters.forEach(function(param) {
    -
     							tag("parameter");
     							attrib("name", param.name);
     							attrib("type", param.type);
    @@ -3711,18 +3752,17 @@ function createAPIXML(symbols, filename, options) {
     
     			if ( symbolAPI.methods ) {
     				symbolAPI.methods.forEach(function(member) {
    -
     					tag("method");
     					attrib("name", member.name);
     					if ( member.module ) {
     						attrib("module", member.module);
     					}
    -					attrib("visibility", member.visibility, 'public');
    +					attrib("visibility", member.visibility, "public");
     					if ( member["static"] ) {
     						attrib("static");
     					}
    -					if ( member.returnValue && member.returnValue.type  ) {
    -						attrib("type", member.returnValue.type, 'void');
    +					if ( member.returnValue && member.returnValue.type ) {
    +						attrib("type", member.returnValue.type, "void");
     					}
     					if ( member.since ) {
     						attrib("since", member.since);
    @@ -3730,7 +3770,6 @@ function createAPIXML(symbols, filename, options) {
     
     					if ( member.parameters ) {
     						member.parameters.forEach(function(param) {
    -
     							tag("parameter");
     							attrib("name", param.name);
     							attrib("type", param.type);
    @@ -3760,7 +3799,6 @@ function createAPIXML(symbols, filename, options) {
     					tagWithSince("deprecated", member.deprecated);
     					// TODO secTags(member);
     					closeTag("method");
    -
     				});
     			}
     
    @@ -3771,9 +3809,7 @@ function createAPIXML(symbols, filename, options) {
     			}
     
     			closeTag(kind);
    -
     		}
    -
     	}
     
     	writeln("");
    @@ -3782,7 +3818,7 @@ function createAPIXML(symbols, filename, options) {
     		namespace("xmlns", "http://www.sap.com/sap.ui.library.api.xsd");
     		attrib("_version", "1.0.0");
     		if ( templateConf.version ) {
    -			attrib("version", templateConf.version.replace(/-SNAPSHOT$/,""));
    +			attrib("version", templateConf.version.replace(/-SNAPSHOT$/, ""));
     		}
     		if ( templateConf.uilib ) {
     			attrib("library", templateConf.uilib);
    @@ -3799,48 +3835,53 @@ function createAPIXML(symbols, filename, options) {
     	closeRootTag("api");
     
     	fs.mkPath(path.dirname(filename));
    -	fs.writeFileSync(filename, getAsString(), 'utf8');
    +	fs.writeFileSync(filename, getAsString(), "utf8");
     }
     
    -//---- add on: API JS -----------------------------------------------------------------
    +// ---- add on: API JS -----------------------------------------------------------------
     
     function createAPIJS(symbols, filename) {
    +	const output = [];
     
    -	var output = [];
    +	const rkeywords = /^(?:abstract|as|boolean|break|byte|case|catch|char|class|continue|const|debugger|default|delete|do|double|else|enum|export|extends|false|final|finally|float|for|function|goto|if|implements|import|in|instanceof|int|interface|is|long|namespace|native|new|null|package|private|protected|public|return|short|static|super|switch|synchronized|this|throw|throws|transient|true|try|typeof|use|var|void|volatile|while|with)$/;
     
    -	var rkeywords = /^(?:abstract|as|boolean|break|byte|case|catch|char|class|continue|const|debugger|default|delete|do|double|else|enum|export|extends|false|final|finally|float|for|function|goto|if|implements|import|in|instanceof|int|interface|is|long|namespace|native|new|null|package|private|protected|public|return|short|static|super|switch|synchronized|this|throw|throws|transient|true|try|typeof|use|var|void|volatile|while|with)$/;
    -
    -	function isNoKeyword($) { return !rkeywords.test($.name); }
    +	function isNoKeyword($) {
    +		return !rkeywords.test($.name);
    +	}
     
    -	function isAPI($) { return $.access === 'public' || $.access === 'protected' || !$.access }
    +	function isAPI($) {
    +		return $.access === "public" || $.access === "protected" || !$.access;
    +	}
     
    -	function writeln(args) {
    -		if ( arguments.length ) {
    -			for (var i = 0; i < arguments.length; i++)
    -				output.push(arguments[i]);
    +	function writeln(...args) {
    +		if ( args.length ) {
    +			for (let i = 0; i < args.length; i++) {
    +				output.push(args[i]);
    +			}
     		}
     		output.push("\n");
     	}
     
     	function unwrap(docletSrc) {
    -		if (!docletSrc) { return ''; }
    +		if (!docletSrc) {
    +			return "";
    +		}
     
     		// note: keep trailing whitespace for @examples
     		// extra opening/closing stars are ignored
     		// left margin is considered a star and a space
     		// use the /m flag on regex to avoid having to guess what this platform's newline is
     		docletSrc =
    -			docletSrc.replace(/^\/\*\*+/, '') // remove opening slash+stars
    -			.replace(/\**\*\/$/, "\\Z")       // replace closing star slash with end-marker
    -			.replace(/^\s*(\* ?|\\Z)/gm, '')  // remove left margin like: spaces+star or spaces+end-marker
    -			.replace(/\s*\\Z$/g, '');         // remove end-marker
    +			docletSrc.replace(/^\/\*\*+/, "") // remove opening slash+stars
    +				.replace(/\**\*\/$/, "\\Z") // replace closing star slash with end-marker
    +				.replace(/^\s*(\* ?|\\Z)/gm, "") // remove left margin like: spaces+star or spaces+end-marker
    +				.replace(/\s*\\Z$/g, ""); // remove end-marker
     
     		return docletSrc;
     	}
     
     	function comment($, sMetaType) {
    -
    -		var s = unwrap($.comment.toString());
    +		let s = unwrap($.comment.toString());
     
     		// remove the @desc tag
     		s = s.replace(/(\r\n|\r|\n)/gm, "\n");
    @@ -3866,41 +3907,44 @@ function createAPIJS(symbols, filename) {
     		writeln(s.split(/\r\n|\r|\n/g).map(function($) { return " * " + $;}).join("\r\n"));
     		writeln(" * /");
     		*/
    -
     	}
     
     	function signature($) {
    -		var p = $.params,
    -			r = [],
    -			i;
    +		const p = $.params;
    +
    +
    +		const r = [];
    +
    +
    +		let i;
     		if ( p ) {
     			for (i = 0; i < p.length; i++) {
     				// ignore @param tags for 'virtual' params that are used to document members of config-like params
     				// (e.g. like "@param param1.key ...")
    -				if (p[i].name && p[i].name.indexOf('.') < 0) {
    +				if (p[i].name && p[i].name.indexOf(".") < 0) {
     					r.push(p[i].name);
     				}
     			}
     		}
    -		return r.join(',');
    +		return r.join(",");
     	}
     
    -	function qname(member,parent) {
    -		var r = member.memberof;
    -		if ( member.scope !== 'static' ) {
    +	function qname(member, parent) {
    +		let r = member.memberof;
    +		if ( member.scope !== "static" ) {
     			r += ".prototype";
     		}
     		return (r ? r + "." : "") + member.name;
     	}
     
    -	var mValues = {
    -		"boolean"  : "false",
    -		"int"      : "0",
    -		"float"    : "0.0",
    -		"number"   : "0.0",
    -		"string"   : "\"\"",
    -		"object"   : "new Object()",
    -		"function" : "function() {}"
    +	const mValues = {
    +		"boolean": "false",
    +		"int": "0",
    +		"float": "0.0",
    +		"number": "0.0",
    +		"string": "\"\"",
    +		"object": "new Object()",
    +		"function": "function() {}"
     	};
     
     	function valueForType(type) {
    @@ -3924,46 +3968,46 @@ function createAPIJS(symbols, filename) {
     	}
     
     	function retvalue(member) {
    -		//console.log(member);
    -		var r = valueForType(member.type || (member.returns && member.returns.length && member.returns[0] && member.returns[0].type && member.returns[0].type));
    +		// console.log(member);
    +		const r = valueForType(member.type || (member.returns && member.returns.length && member.returns[0] && member.returns[0].type && member.returns[0].type));
     		if ( r ) {
     			return "return " + r + ";";
     		}
     		return "";
     	}
     
    -	var sortedSymbols = symbols.slice(0).filter(function($) { return isaClass($) && isAPI($) && !$.synthetic; }).sort(sortByAlias); // sort only a copy(!) of the symbols, otherwise the SymbolSet lookup is broken
    +	const sortedSymbols = symbols.slice(0).filter(function($) {
    +		return isaClass($) && isAPI($) && !$.synthetic;
    +	}).sort(sortByAlias); // sort only a copy(!) of the symbols, otherwise the SymbolSet lookup is broken
     	sortedSymbols.forEach(function(symbol) {
    -
    -		var sMetaType = (symbol.kind === 'member' && symbol.isEnum) ? 'enum' : symbol.kind;
    +		const sMetaType = (symbol.kind === "member" && symbol.isEnum) ? "enum" : symbol.kind;
     		if ( sMetaType ) {
    -
     			writeln("");
     			writeln("// ---- " + symbol.longname + " --------------------------------------------------------------------------");
     			writeln("");
     
    -			var memberId, member;
    +			let memberId; let member;
     
    -			var ownProperties = childrenOfKind(symbol, 'property').own.filter(isNoKeyword).sort(sortByAlias);
    -				if ( sMetaType === "class" ) {
    -					comment(symbol, sMetaType);
    -					writeln(symbol.longname + " = function(" + signature(symbol) + ") {};");
    +			const ownProperties = childrenOfKind(symbol, "property").own.filter(isNoKeyword).sort(sortByAlias);
    +			if ( sMetaType === "class" ) {
    +				comment(symbol, sMetaType);
    +				writeln(symbol.longname + " = function(" + signature(symbol) + ") {};");
     				for ( memberId in ownProperties ) {
     					member = ownProperties[memberId];
     					comment(member, sMetaType);
     					writeln(qname(member, symbol) + " = " + value(member));
     					writeln("");
     				}
    -				} else if ( sMetaType === 'namespace' || sMetaType === 'enum' ) {
    -				//console.log("found namespace " + symbol.longname);
    -				//console.log(ownProperties);
    -					if ( ownProperties.length ) {
    -						writeln("// dummy function to make Eclipse aware of namespace");
    -						writeln(symbol.longname + ".toString = function() { return \"\"; };");
    -					}
    +			} else if ( sMetaType === "namespace" || sMetaType === "enum" ) {
    +				// console.log("found namespace " + symbol.longname);
    +				// console.log(ownProperties);
    +				if ( ownProperties.length ) {
    +					writeln("// dummy function to make Eclipse aware of namespace");
    +					writeln(symbol.longname + ".toString = function() { return \"\"; };");
     				}
    +			}
     
    -			var ownEvents = childrenOfKind(symbol, 'event').own.filter(isNoKeyword).sort(sortByAlias);
    +			const ownEvents = childrenOfKind(symbol, "event").own.filter(isNoKeyword).sort(sortByAlias);
     			if ( ownEvents.length ) {
     				for ( memberId in ownEvents ) {
     					member = ownEvents[memberId];
    @@ -3973,7 +4017,7 @@ function createAPIJS(symbols, filename) {
     				}
     			}
     
    -			var ownMethods = childrenOfKind(symbol, 'method').own.filter(isNoKeyword).sort(sortByAlias);
    +			const ownMethods = childrenOfKind(symbol, "method").own.filter(isNoKeyword).sort(sortByAlias);
     			if ( ownMethods.length ) {
     				for ( memberId in ownMethods ) {
     					member = ownMethods[memberId];
    @@ -3982,47 +4026,43 @@ function createAPIJS(symbols, filename) {
     					writeln("");
     				}
     			}
    -
     		}
     	});
     
     	writeln("// ---- static fields of namespaces ---------------------------------------------------------------------");
     
     	sortedSymbols.forEach(function(symbol) {
    +		const sMetaType = (symbol.kind === "member" && symbol.isEnum) ? "enum" : symbol.kind;
     
    -		var sMetaType = (symbol.kind === 'member' && symbol.isEnum) ? 'enum' : symbol.kind;
    -
    -		if ( sMetaType === 'namespace' || sMetaType === 'enum' ) {
    -
    -			var ownProperties = childrenOfKind(symbol, 'property').own.filter(isNoKeyword).sort(sortByAlias);
    +		if ( sMetaType === "namespace" || sMetaType === "enum" ) {
    +			const ownProperties = childrenOfKind(symbol, "property").own.filter(isNoKeyword).sort(sortByAlias);
     			if ( ownProperties.length ) {
     				writeln("");
     				writeln("// ---- " + symbol.longname + " --------------------------------------------------------------------------");
     				writeln("");
     
    -				for (var memberId in ownProperties ) {
    -					var member = ownProperties[memberId];
    +				for (const memberId in ownProperties ) {
    +					const member = ownProperties[memberId];
     					comment(member, sMetaType);
     					writeln(qname(member, symbol) + " = " + value(member) + ";");
     					writeln("");
     				}
     			}
     		}
    -
     	});
     
     	fs.mkPath(path.dirname(filename));
    -	fs.writeFileSync(filename, output.join(""), 'utf8');
    +	fs.writeFileSync(filename, output.join(""), "utf8");
     	info("  saved as " + filename);
     }
     
     // Description + Settings
     
     function getConstructorDescription(symbol) {
    -	var description = symbol.description;
    -	var tags = symbol.tags;
    +	let description = symbol.description;
    +	const tags = symbol.tags;
     	if ( tags ) {
    -		for (var i = 0; i < tags.length; i++) {
    +		for (let i = 0; i < tags.length; i++) {
     			if ( tags[i].title === "ui5-settings" && tags[i].text) {
     				description += "\n

    \n" + tags[i].text; break; @@ -4036,11 +4076,13 @@ function getConstructorDescription(symbol) { // Example function makeExample(example) { - var result = { - caption: null, - example: example - }, - match = /^\s*([\s\S]+?)<\/caption>(?:[ \t]*[\n\r]*)([\s\S]+)$/i.exec(example); + const result = { + caption: null, + example: example + }; + + + const match = /^\s*([\s\S]+?)<\/caption>(?:[ \t]*[\n\r]*)([\s\S]+)$/i.exec(example); if ( match ) { result.caption = match[1]; diff --git a/lib/tasks/generateJsdoc.js b/lib/tasks/generateJsdoc.js index 425d4027e..3d1a699e9 100644 --- a/lib/tasks/generateJsdoc.js +++ b/lib/tasks/generateJsdoc.js @@ -16,7 +16,8 @@ const {resourceFactory} = require("@ui5/fs"); * @param {string} parameters.options.pattern Pattern to locate the files to be processed * @param {string} parameters.options.projectName Project name * @param {string} parameters.options.version Project version - * @param {boolean} [parameters.options.sdkBuild=true] Whether additional SDK specific api.json resources shall be generated + * @param {boolean} [parameters.options.sdkBuild=true] Whether additional SDK specific api.json + * resources shall be generated * @returns {Promise} Promise resolving with undefined once data has been written */ module.exports = async function({workspace, options}) { @@ -57,7 +58,6 @@ module.exports = async function({workspace, options}) { } }); - console.log(createdResources); await Promise.all(createdResources.map(async (resource) => { await workspace.write(resource); })); diff --git a/lib/types/library/LibraryBuilder.js b/lib/types/library/LibraryBuilder.js index b4d643518..f80cbb815 100644 --- a/lib/types/library/LibraryBuilder.js +++ b/lib/types/library/LibraryBuilder.js @@ -88,9 +88,6 @@ class LibraryBuilder extends AbstractBuilder { options: { projectName: project.metadata.name } - }).catch((err) => { - console.log("generateLibraryPreload failed:", err); - throw err; }); }); From 7fcce6938fc0ebc1e32ba47717117fc4dd75508d Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Fri, 1 Mar 2019 13:43:30 +0100 Subject: [PATCH 07/38] Add jsdocGenerater processor unit tests --- lib/processors/jsdoc/jsdocGenerator.js | 10 +-- test/lib/processors/jsdoc/jsdocGenerator.js | 93 +++++++++++++++++++++ 2 files changed, 98 insertions(+), 5 deletions(-) diff --git a/lib/processors/jsdoc/jsdocGenerator.js b/lib/processors/jsdoc/jsdocGenerator.js index a13fc4db4..f9b306a77 100644 --- a/lib/processors/jsdoc/jsdocGenerator.js +++ b/lib/processors/jsdoc/jsdocGenerator.js @@ -79,9 +79,9 @@ async function buildJsdoc({sourcePath, configPath}) { * api.json resources shall be generated * @returns {Promise} Promise resolving with */ -module.exports = async function({sourcePath, targetPath, tmpPath, options}) { +const jsdocGenerator = module.exports = async function({sourcePath, targetPath, tmpPath, options} = {}) { if (!sourcePath || !targetPath || !tmpPath || !options.projectName || !options.version) { - throw new Error("[jsdocGenerator]: One or more mandatory options not provided"); + throw new Error("[jsdocGenerator]: One or more mandatory parameters not provided"); } if (!options.variants || options.variants.length === 0) { @@ -92,7 +92,7 @@ module.exports = async function({sourcePath, targetPath, tmpPath, options}) { } const namespace = options.projectName.replace(/\./g, "/"); - const config = await generateJsdocConfig({ + const config = await jsdocGenerator._generateJsdocConfig({ targetPath, tmpPath, namespace, @@ -101,9 +101,9 @@ module.exports = async function({sourcePath, targetPath, tmpPath, options}) { variants: options.variants }); - const configPath = await writeJsdocConfig(tmpPath, config); + const configPath = await jsdocGenerator._writeJsdocConfig(tmpPath, config); - await buildJsdoc({ + await jsdocGenerator._buildJsdoc({ sourcePath, configPath }); diff --git a/test/lib/processors/jsdoc/jsdocGenerator.js b/test/lib/processors/jsdoc/jsdocGenerator.js index d4b74a909..694c7c9aa 100644 --- a/test/lib/processors/jsdoc/jsdocGenerator.js +++ b/test/lib/processors/jsdoc/jsdocGenerator.js @@ -102,3 +102,96 @@ test.serial("buildJsdoc", async (t) => { })); t.deepEqual(error.message, "JSDoc child process closed with code 2"); }); + +test.serial("jsdocGenerator", async (t) => { + const generateJsdocConfigStub = sinon.stub(jsdocGenerator, "_generateJsdocConfig").resolves("some config"); + const writeJsdocConfigStub = sinon.stub(jsdocGenerator, "_writeJsdocConfig").resolves("/some/config/path"); + const buildJsdocStub = sinon.stub(jsdocGenerator, "_buildJsdoc").resolves(); + const byPathStub = sinon.stub().resolves("some resource"); + const createAdapterStub = sinon.stub(require("@ui5/fs").resourceFactory, "createAdapter").returns({ + byPath: byPathStub + }); + + const res = await jsdocGenerator({ + sourcePath: "/some/source/path", + targetPath: "/some/target/path", + tmpPath: "/some/tmp/path", + options: { + projectName: "some.project.name", + version: "1.0.0" + } + }); + + t.deepEqual(res.length, 1, "Returned 1 resource"); + t.deepEqual(res[0], "some resource", "Returned 1 resource"); + + t.deepEqual(generateJsdocConfigStub.callCount, 1, "generateJsdocConfig called once"); + t.deepEqual(generateJsdocConfigStub.getCall(0).args[0], { + targetPath: "/some/target/path", + tmpPath: "/some/tmp/path", + namespace: "some/project/name", + projectName: "some.project.name", + version: "1.0.0", + variants: ["apijson"] + }, "generateJsdocConfig called with correct arguments"); + + t.deepEqual(writeJsdocConfigStub.callCount, 1, "writeJsdocConfig called once"); + t.deepEqual(writeJsdocConfigStub.getCall(0).args[0], "/some/tmp/path", + "writeJsdocConfig called with correct tmpPath argument"); + t.deepEqual(writeJsdocConfigStub.getCall(0).args[1], "some config", + "writeJsdocConfig called with correct config argument"); + + t.deepEqual(buildJsdocStub.callCount, 1, "buildJsdoc called once"); + t.deepEqual(buildJsdocStub.getCall(0).args[0], { + sourcePath: "/some/source/path", + configPath: "/some/config/path" + }, "buildJsdoc called with correct arguments"); + + t.deepEqual(createAdapterStub.getCall(0).args[0], { + fsBasePath: "/some/target/path", + virBasePath: "/" + }, "createAdapter called with correct arguments"); + t.deepEqual(byPathStub.getCall(0).args[0], "/test-resources/some/project/name/designtime/api.json", + "byPath called with correct path for api.json"); + + + /* Test branch: empty variants array*/ + await jsdocGenerator({ + sourcePath: "/some/source/path", + targetPath: "/some/target/path", + tmpPath: "/some/tmp/path", + options: { + projectName: "some.project.name", + version: "1.0.0", + variants: [] + } + }); + + t.deepEqual(generateJsdocConfigStub.getCall(1).args[0].variants, ["apijson"], + "generateJsdocConfig called with correct variants arguments"); + + + /* Test branch: variants array set + sdkBuild requested*/ + await jsdocGenerator({ + sourcePath: "/some/source/path", + targetPath: "/some/target/path", + tmpPath: "/some/tmp/path", + options: { + projectName: "some.project.name", + version: "1.0.0", + variants: ["pony"], + sdkBuild: true + } + }); + + t.deepEqual(generateJsdocConfigStub.getCall(2).args[0].variants, ["pony"], + "generateJsdocConfig called with correct variants arguments"); + + sinon.restore(); +}); + +test("jsdocGenerator missing parameters", async (t) => { + const error = await t.throws(jsdocGenerator()); + t.deepEqual(error.message, "[jsdocGenerator]: One or more mandatory parameters not provided", + "Correct error message thrown"); +}); From c0e3c38057fe968354d8f60a083691086d5dbaf8 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Fri, 1 Mar 2019 14:11:43 +0100 Subject: [PATCH 08/38] Refactor jsdocGenerator: Move main function to top + add JSDoc for private functions --- lib/processors/jsdoc/jsdocGenerator.js | 149 +++++++++++++++---------- 1 file changed, 90 insertions(+), 59 deletions(-) diff --git a/lib/processors/jsdoc/jsdocGenerator.js b/lib/processors/jsdoc/jsdocGenerator.js index f9b306a77..864aad5c0 100644 --- a/lib/processors/jsdoc/jsdocGenerator.js +++ b/lib/processors/jsdoc/jsdocGenerator.js @@ -5,68 +5,11 @@ const {promisify} = require("util"); const writeFile = promisify(fs.writeFile); const {resourceFactory} = require("@ui5/fs"); -async function generateJsdocConfig({targetPath, tmpPath, namespace, projectName, version, variants}) { - // Resolve path to this script to get the path to the JSDoc extensions folder - const jsdocPath = path.normalize(__dirname); - - const config = `{ - "plugins": ["${jsdocPath}/ui5/plugin.js"], - "opts": { - "recurse": true, - "lenient": true, - "template": "${jsdocPath}/ui5/template", - "ui5": { - "saveSymbols": true - }, - "destination": "${tmpPath}" - }, - "templates": { - "ui5": { - "variants": ${JSON.stringify(variants)}, - "version": "${version}", - "jsapiFile": "${targetPath}/libraries/${projectName}.js", - "apiJsonFolder": "${targetPath}/dependency-apis", - "apiJsonFile": "${targetPath}/test-resources/${namespace}/designtime/api.json" - } - } - }`; - return config; -} - -async function writeJsdocConfig(sourcePath, config) { - const configPath = path.join(sourcePath, "jsdoc-config.json"); - await writeFile(configPath, config); - return configPath; -} - -async function buildJsdoc({sourcePath, configPath}) { - const args = [ - require.resolve("jsdoc/jsdoc"), - "-c", - configPath, - "--verbose", - sourcePath - ]; - return new Promise((resolve, reject) => { - const child = spawn("node", args, { - stdio: ["ignore", "ignore", process.stderr] - }); - child.on("close", function(code) { - if (code === 0 || code === 1) { - resolve(); - } else { - reject(new Error(`JSDoc child process closed with code ${code}`)); - } - }); - }); -} - - /** * JSDoc generator * * @public - * @alias module:@ui5/builder.processors.jsdoc.jsdocGenerator + * @alias module:@ui5/builder.processors.jsdocGenerator * @param {Object} parameters Parameters * @param {string} parameters.sourcePath Path of the source files to be processed * @param {string} parameters.targetPath Path to write any output files @@ -79,7 +22,7 @@ async function buildJsdoc({sourcePath, configPath}) { * api.json resources shall be generated * @returns {Promise} Promise resolving with */ -const jsdocGenerator = module.exports = async function({sourcePath, targetPath, tmpPath, options} = {}) { +const jsdocGenerator = async function({sourcePath, targetPath, tmpPath, options} = {}) { if (!sourcePath || !targetPath || !tmpPath || !options.projectName || !options.version) { throw new Error("[jsdocGenerator]: One or more mandatory parameters not provided"); } @@ -121,6 +64,94 @@ const jsdocGenerator = module.exports = async function({sourcePath, targetPath, ]); }; + +/** + * Generate jsdoc-config.json content + * + * @private + * @param {Object} parameters Parameters + * @param {string} parameters.targetPath Path to write any output files + * @param {string} parameters.tmpPath Path to write temporary and debug files + * @param {string} parameters.projectName Project name + * @param {string} parameters.version Project version + * @param {Array} parameters.variants JSDoc variants to be built + * @returns {string} jsdoc-config.json content string + */ +async function generateJsdocConfig({targetPath, tmpPath, namespace, projectName, version, variants}) { + // Resolve path to this script to get the path to the JSDoc extensions folder + const jsdocPath = path.normalize(__dirname); + + const config = `{ + "plugins": ["${jsdocPath}/ui5/plugin.js"], + "opts": { + "recurse": true, + "lenient": true, + "template": "${jsdocPath}/ui5/template", + "ui5": { + "saveSymbols": true + }, + "destination": "${tmpPath}" + }, + "templates": { + "ui5": { + "variants": ${JSON.stringify(variants)}, + "version": "${version}", + "jsapiFile": "${targetPath}/libraries/${projectName}.js", + "apiJsonFolder": "${targetPath}/dependency-apis", + "apiJsonFile": "${targetPath}/test-resources/${namespace}/designtime/api.json" + } + } + }`; + return config; +} + +/** + * Write jsdoc-config.json to file system + * + * @private + * @param {string} targetDirPath Directory Path to write the jsdoc-config.json file to + * @param {string} config jsdoc-config.json content + * @returns {string} Full path to the written jsdoc-config.json file + */ +async function writeJsdocConfig(targetDirPath, config) { + const configPath = path.join(targetDirPath, "jsdoc-config.json"); + await writeFile(configPath, config); + return configPath; +} + + +/** + * Execute JSDoc build by spawning JSDoc as an external process + * + * @private + * @param {Object} parameters Parameters + * @param {string} parameters.sourcePath Project resources (input for JSDoc generation) + * @param {string} parameters.configPath Full path to jsdoc-config.json file + * @returns {Promise} + */ +async function buildJsdoc({sourcePath, configPath}) { + const args = [ + require.resolve("jsdoc/jsdoc"), + "-c", + configPath, + "--verbose", + sourcePath + ]; + return new Promise((resolve, reject) => { + const child = spawn("node", args, { + stdio: ["ignore", "ignore", process.stderr] + }); + child.on("close", function(code) { + if (code === 0 || code === 1) { + resolve(); + } else { + reject(new Error(`JSDoc child process closed with code ${code}`)); + } + }); + }); +} + +module.exports = jsdocGenerator; module.exports._generateJsdocConfig = generateJsdocConfig; module.exports._writeJsdocConfig = writeJsdocConfig; module.exports._buildJsdoc = buildJsdoc; From 130a9c9c366f361b56f4acfb53d3545aa447b656 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Fri, 1 Mar 2019 14:40:44 +0100 Subject: [PATCH 09/38] Refactor generateJsdoc task --- lib/tasks/generateJsdoc.js | 101 +++++++++++++++++++++++++++---------- 1 file changed, 75 insertions(+), 26 deletions(-) diff --git a/lib/tasks/generateJsdoc.js b/lib/tasks/generateJsdoc.js index 3d1a699e9..817f7039a 100644 --- a/lib/tasks/generateJsdoc.js +++ b/lib/tasks/generateJsdoc.js @@ -9,9 +9,10 @@ const {resourceFactory} = require("@ui5/fs"); /** * Task to create dbg files. * - * @module builder/tasks/createDebugFiles + * @public + * @alias module:@ui5/builder.tasks.generateJsdoc * @param {Object} parameters Parameters - * @param {DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files * @param {Object} parameters.options Options * @param {string} parameters.options.pattern Pattern to locate the files to be processed * @param {string} parameters.options.projectName Project name @@ -20,37 +21,23 @@ const {resourceFactory} = require("@ui5/fs"); * resources shall be generated * @returns {Promise} Promise resolving with undefined once data has been written */ -module.exports = async function({workspace, options}) { +const generateJsdoc = async function({workspace, options}) { if (!options.projectName || !options.version || !options.pattern) { throw new Error("[generateJsdoc]: One or more mandatory options not provided"); } - let allResources; - if (workspace.byGlobSource) { // API only available on duplex collections - allResources = await workspace.byGlobSource(options.pattern); - } else { - allResources = await workspace.byGlob(options.pattern); - } - - const {path: tmpDirPath} = await createTmpWorkDir(options.projectName); - const tmpSourcePath = path.join(tmpDirPath, "src"); // dir will be created by writing project resources below - const tmpTargetPath = path.join(tmpDirPath, "target"); // dir will be created by jsdoc itself - const tmpTmpPath = path.join(tmpDirPath, "tmp"); // dir needs to be created by us - - await makeDir(tmpTmpPath, {fs}); + const {sourcePath: resourcePath, targetPath, tmpPath} = await createTmpDirs(options.projectName); - const fsSource = resourceFactory.createAdapter({ - fsBasePath: tmpSourcePath, - virBasePath: "/resources/" + await writeResourcesToDir({ + workspace, + pattern: options.pattern, + targetPath: resourcePath }); - // write all resources to the tmp folder - await Promise.all(allResources.map((resource) => fsSource.write(resource))); - const createdResources = await jsdocGenerator({ - sourcePath: tmpSourcePath, - targetPath: tmpTargetPath, - tmpPath: tmpTmpPath, + sourcePath: resourcePath, + targetPath, + tmpPath, options: { projectName: options.projectName, version: options.version, @@ -63,7 +50,38 @@ module.exports = async function({workspace, options}) { })); }; -function createTmpWorkDir(projectName) { + +/** + * Create temporary directories for JSDoc generation processor + * + * @private + * @param {string} projectName Project name used for naming the temporary working directory + * @returns {Promise} Promise resolving with sourcePath, targetPath and tmpPath strings + */ +async function createTmpDirs(projectName) { + const {path: tmpDirPath} = await createTmpDir(projectName); + + const sourcePath = path.join(tmpDirPath, "src"); // dir will be created by writing project resources below + const targetPath = path.join(tmpDirPath, "target"); // dir will be created by jsdoc itself + + const tmpPath = path.join(tmpDirPath, "tmp"); // dir needs to be created by us + await makeDir(tmpPath, {fs}); + + return { + sourcePath, + targetPath, + tmpPath + }; +} + +/** + * Create a temporary directory on the host system + * + * @private + * @param {string} projectName Project name used for naming the temporary directory + * @returns {Promise} Promise resolving with path of the temporary directory + */ +function createTmpDir(projectName) { return new Promise((resolve, reject) => { tmp.dir({ prefix: `ui5-tooling-tmp-jsdoc-${projectName}-`, @@ -81,3 +99,34 @@ function createTmpWorkDir(projectName) { }); }); } + +/** + * Write resources from workspace matching the given pattern to the given fs destination + * + * @private + * @param {Object} parameters Parameters + * @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {string} parameters.pattern Pattern to match resources in workspace against + * @param {string} parameters.targetPath Path to write the resources to + * @returns {Promise} Promise resolving with undefined once data has been written + */ +async function writeResourcesToDir({workspace, pattern, targetPath}) { + const fsSource = resourceFactory.createAdapter({ + fsBasePath: targetPath, + virBasePath: "/resources/" + }); + + let allResources; + if (workspace.byGlobSource) { // API only available on duplex collections + allResources = await workspace.byGlobSource(pattern); + } else { + allResources = await workspace.byGlob(pattern); + } + // write all resources to the tmp folder + await Promise.all(allResources.map((resource) => fsSource.write(resource))); +} + +module.exports = generateJsdoc; +module.exports._createTmpDirs = createTmpDirs; +module.exports._createTmpDir = createTmpDir; +module.exports._writeResourcesToDir = writeResourcesToDir; From c26f2dee68e9b514a7497458f4210163f0568613 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Fri, 1 Mar 2019 15:07:48 +0100 Subject: [PATCH 10/38] Add generateJsdoc test file --- test/lib/tasks/generateJsdoc.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 test/lib/tasks/generateJsdoc.js diff --git a/test/lib/tasks/generateJsdoc.js b/test/lib/tasks/generateJsdoc.js new file mode 100644 index 000000000..21e4bbcdd --- /dev/null +++ b/test/lib/tasks/generateJsdoc.js @@ -0,0 +1,9 @@ +const {test} = require("ava"); +const sinon = require("sinon"); +const mock = require("mock-require"); + +const generateJsdoc = require("../../../lib/tasks/generateJsdoc"); + +test("jsdocGenerator", async (t) => { + +}); From 4eb28260ea5e3caf46f0c0c5771a46ffdd2d26f8 Mon Sep 17 00:00:00 2001 From: Tommy Vinh Lam Date: Mon, 4 Mar 2019 17:13:42 +0100 Subject: [PATCH 11/38] Add generateJsdoc task unit tests --- lib/tasks/generateJsdoc.js | 4 +- test/lib/tasks/generateJsdoc.js | 77 ++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/lib/tasks/generateJsdoc.js b/lib/tasks/generateJsdoc.js index 817f7039a..9da4fb0c5 100644 --- a/lib/tasks/generateJsdoc.js +++ b/lib/tasks/generateJsdoc.js @@ -21,8 +21,8 @@ const {resourceFactory} = require("@ui5/fs"); * resources shall be generated * @returns {Promise} Promise resolving with undefined once data has been written */ -const generateJsdoc = async function({workspace, options}) { - if (!options.projectName || !options.version || !options.pattern) { +const generateJsdoc = async function({workspace, options} = {}) { + if (!options || !options.projectName || !options.version || !options.pattern) { throw new Error("[generateJsdoc]: One or more mandatory options not provided"); } diff --git a/test/lib/tasks/generateJsdoc.js b/test/lib/tasks/generateJsdoc.js index 21e4bbcdd..5c67b4b9d 100644 --- a/test/lib/tasks/generateJsdoc.js +++ b/test/lib/tasks/generateJsdoc.js @@ -1,9 +1,84 @@ const {test} = require("ava"); const sinon = require("sinon"); +const tmp = require("tmp"); + const mock = require("mock-require"); const generateJsdoc = require("../../../lib/tasks/generateJsdoc"); -test("jsdocGenerator", async (t) => { +test.beforeEach((t) => { + t.context.tmpStub = sinon.stub(tmp, "dir"); +}); + +test.afterEach.always((t) => { + t.context.tmpStub.restore(); +}); + +test.serial("createTmpDir successful", async (t) => { + t.context.tmpStub.callsArgWithAsync(1, undefined, "some/path"); + + const res = await generateJsdoc._createTmpDir("some.namespace"); + + t.deepEqual(t.context.tmpStub.callCount, 1, "Tmp dir is called once"); + t.deepEqual(t.context.tmpStub.getCall(0).args[0].prefix, "ui5-tooling-tmp-jsdoc-some.namespace-"); + t.deepEqual(res, {path: "some/path"}, "Correct path returned"); +}); + +test.serial("createTmpDir error", async (t) => { + t.context.tmpStub.callsArgWithAsync(1, {message: "Dir creation failed"}, "some/path"); + + const res = await t.throws(generateJsdoc._createTmpDir("some.namespace")); + + t.deepEqual(t.context.tmpStub.callCount, 1, "Tmp dir is called once"); + t.deepEqual(t.context.tmpStub.getCall(0).args[0].prefix, "ui5-tooling-tmp-jsdoc-some.namespace-"); + t.deepEqual(res, {message: "Dir creation failed"}, "Dir creation failed"); +}); + +test.serial("createTempDirs", async (t) => { + mock("make-dir", function() { + return Promise.resolve(); + }); + mock.reRequire("make-dir"); + + t.context.tmpStub.callsArgWithAsync(1, undefined, "some/path"); + + const res = await generateJsdoc._createTmpDirs("some.namespace"); + + t.deepEqual(res, {sourcePath: "some/path/src", targetPath: "some/path/target", tmpPath: "some/path/tmp"}, + "Correct temporary directories returned"); + + mock.stop("make-dir"); +}); + +test.serial("writeResourcesToDir", async (t) => { + const writeStub = sinon.stub().resolves("some resource"); + const createAdapterStub = sinon.stub(require("@ui5/fs").resourceFactory, "createAdapter").returns({ + write: writeStub + }); + + generateJsdoc._writeResourcesToDir({ + workspace: { + // stub byGlobSource + byGlobSource: (pattern) => { + return Promise.resolve([]); + } + }, + pattern: "", + targetPath: "/some/target/path" + }); + + t.deepEqual(createAdapterStub.getCall(0).args[0], { + fsBasePath: "/some/target/path", + virBasePath: "/resources/" + }, "createAdapter called with correct arguments"); +}); + +// test.serial("generateJsdoc", async (t) => { + +// }); +test.serial("generateJsdoc missing parameters", async (t) => { + const error = await t.throws(generateJsdoc()); + t.deepEqual(error.message, "[generateJsdoc]: One or more mandatory options not provided", + "Correct error message thrown"); }); From 3e537a0db929ef15dcd32084539f7c0d0640c65b Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Tue, 5 Mar 2019 14:43:41 +0100 Subject: [PATCH 12/38] Fix make-dir mock in task tests --- test/lib/tasks/generateJsdoc.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test/lib/tasks/generateJsdoc.js b/test/lib/tasks/generateJsdoc.js index 5c67b4b9d..86b137ca4 100644 --- a/test/lib/tasks/generateJsdoc.js +++ b/test/lib/tasks/generateJsdoc.js @@ -34,18 +34,19 @@ test.serial("createTmpDir error", async (t) => { t.deepEqual(res, {message: "Dir creation failed"}, "Dir creation failed"); }); -test.serial("createTempDirs", async (t) => { - mock("make-dir", function() { - return Promise.resolve(); - }); - mock.reRequire("make-dir"); +test.serial("createTmpDirs", async (t) => { + const makeDirStub = sinon.stub().resolves(); + mock("make-dir", makeDirStub); + const generateJsdoc = mock.reRequire("../../../lib/tasks/generateJsdoc"); - t.context.tmpStub.callsArgWithAsync(1, undefined, "some/path"); + t.context.tmpStub.callsArgWithAsync(1, undefined, "/some/path"); const res = await generateJsdoc._createTmpDirs("some.namespace"); - t.deepEqual(res, {sourcePath: "some/path/src", targetPath: "some/path/target", tmpPath: "some/path/tmp"}, + t.deepEqual(res, {sourcePath: "/some/path/src", targetPath: "/some/path/target", tmpPath: "/some/path/tmp"}, "Correct temporary directories returned"); + t.deepEqual(makeDirStub.callCount, 1, "One directory got created"); + t.deepEqual(makeDirStub.getCall(0).args[0], "/some/path/tmp", "Correct dir path got created"); mock.stop("make-dir"); }); From 28fe45290a81f712d1e865244baf1c456c084e20 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Tue, 5 Mar 2019 16:28:39 +0100 Subject: [PATCH 13/38] Add tests for generateJsdoc task --- lib/tasks/generateJsdoc.js | 4 +-- test/lib/tasks/generateJsdoc.js | 55 ++++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/lib/tasks/generateJsdoc.js b/lib/tasks/generateJsdoc.js index 9da4fb0c5..2993e3d14 100644 --- a/lib/tasks/generateJsdoc.js +++ b/lib/tasks/generateJsdoc.js @@ -26,9 +26,9 @@ const generateJsdoc = async function({workspace, options} = {}) { throw new Error("[generateJsdoc]: One or more mandatory options not provided"); } - const {sourcePath: resourcePath, targetPath, tmpPath} = await createTmpDirs(options.projectName); + const {sourcePath: resourcePath, targetPath, tmpPath} = await generateJsdoc._createTmpDirs(options.projectName); - await writeResourcesToDir({ + await generateJsdoc._writeResourcesToDir({ workspace, pattern: options.pattern, targetPath: resourcePath diff --git a/test/lib/tasks/generateJsdoc.js b/test/lib/tasks/generateJsdoc.js index 86b137ca4..fa1cd8663 100644 --- a/test/lib/tasks/generateJsdoc.js +++ b/test/lib/tasks/generateJsdoc.js @@ -11,7 +11,7 @@ test.beforeEach((t) => { }); test.afterEach.always((t) => { - t.context.tmpStub.restore(); + sinon.restore(); }); test.serial("createTmpDir successful", async (t) => { @@ -52,7 +52,7 @@ test.serial("createTmpDirs", async (t) => { }); test.serial("writeResourcesToDir", async (t) => { - const writeStub = sinon.stub().resolves("some resource"); + const writeStub = sinon.stub().resolves(); const createAdapterStub = sinon.stub(require("@ui5/fs").resourceFactory, "createAdapter").returns({ write: writeStub }); @@ -74,9 +74,56 @@ test.serial("writeResourcesToDir", async (t) => { }, "createAdapter called with correct arguments"); }); -// test.serial("generateJsdoc", async (t) => { +test.serial("generateJsdoc", async (t) => { + const jsdocGeneratorStub = sinon.stub().resolves(["some resource 1", "some resource 2"]); + mock("../../../lib/processors/jsdoc/jsdocGenerator", jsdocGeneratorStub); + const generateJsdoc = mock.reRequire("../../../lib/tasks/generateJsdoc"); + + const createTmpDirsStub = sinon.stub(generateJsdoc, "_createTmpDirs").resolves({ + sourcePath: "/some/source/path", + targetPath: "/some/target/path", + tmpPath: "/some/tmp/path", + }); + const writeResourcesToDirStub = sinon.stub(generateJsdoc, "_writeResourcesToDir").resolves(); -// }); + const writeStub = sinon.stub().resolves(); + const workspace = { + write: writeStub + }; + await generateJsdoc({ + workspace, + options: { + pattern: "some pattern", + projectName: "some.project", + version: "some version" + } + }); + + t.deepEqual(createTmpDirsStub.callCount, 1, "createTmpDirs got called once"); + t.deepEqual(createTmpDirsStub.getCall(0).args[0], "some.project", + "createTmpDirs got called with correct arguments"); + + t.deepEqual(writeResourcesToDirStub.callCount, 1, "writeResourcesToDir got called once"); + t.deepEqual(writeResourcesToDirStub.getCall(0).args[0], { + workspace, + pattern: "some pattern", + targetPath: "/some/source/path" // one's target is another one's source + }, "writeResourcesToDir got called with correct arguments"); + + t.deepEqual(jsdocGeneratorStub.callCount, 1, "jsdocGenerator processor got called once"); + t.deepEqual(jsdocGeneratorStub.getCall(0).args[0], { + sourcePath: "/some/source/path", + targetPath: "/some/target/path", + tmpPath: "/some/tmp/path", + options: { + projectName: "some.project", + version: "some version", + variants: ["apijson"] + } + }, "jsdocGenerator got called with correct arguments"); + + mock.stop("../../../lib/processors/jsdoc/jsdocGenerator"); +}); test.serial("generateJsdoc missing parameters", async (t) => { const error = await t.throws(generateJsdoc()); From 9c1885f049c9102450cca90a7ae5fb929f938425 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Wed, 6 Mar 2019 13:02:30 +0100 Subject: [PATCH 14/38] Add basic integration test for JSDoc generation --- .../dest/resources/library/j/.library | 11 +++++ .../dest/resources/library/j/some.js | 16 ++++++ .../library/j/designtime/api.json | 1 + .../library.j/main/src/library/j/.library | 11 +++++ .../library.j/main/src/library/j/some.js | 16 ++++++ test/fixtures/library.j/package.json | 9 ++++ test/fixtures/library.j/ui5.yaml | 10 ++++ test/lib/builder/builder.js | 49 ++++++++++++++++++- 8 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 test/expected/build/library.j/dest/resources/library/j/.library create mode 100644 test/expected/build/library.j/dest/resources/library/j/some.js create mode 100644 test/expected/build/library.j/dest/test-resources/library/j/designtime/api.json create mode 100644 test/fixtures/library.j/main/src/library/j/.library create mode 100644 test/fixtures/library.j/main/src/library/j/some.js create mode 100644 test/fixtures/library.j/package.json create mode 100644 test/fixtures/library.j/ui5.yaml diff --git a/test/expected/build/library.j/dest/resources/library/j/.library b/test/expected/build/library.j/dest/resources/library/j/.library new file mode 100644 index 000000000..1919da37e --- /dev/null +++ b/test/expected/build/library.j/dest/resources/library/j/.library @@ -0,0 +1,11 @@ + + + + library.j + SAP SE + Some fancy copyright + ${version} + + Library J + + diff --git a/test/expected/build/library.j/dest/resources/library/j/some.js b/test/expected/build/library.j/dest/resources/library/j/some.js new file mode 100644 index 000000000..8fd81bb68 --- /dev/null +++ b/test/expected/build/library.j/dest/resources/library/j/some.js @@ -0,0 +1,16 @@ +/*! + * ${copyright} + */ + +sap.ui.define([], + function() { + "use strict"; + + /** + * @alias library.j + * @namespace + * @public + */ + var SomeFunction = function() {}; + +}, /* bExport= */ true); diff --git a/test/expected/build/library.j/dest/test-resources/library/j/designtime/api.json b/test/expected/build/library.j/dest/test-resources/library/j/designtime/api.json new file mode 100644 index 000000000..37b7d11d9 --- /dev/null +++ b/test/expected/build/library.j/dest/test-resources/library/j/designtime/api.json @@ -0,0 +1 @@ +{"$schema-ref":"http://schemas.sap.com/sapui5/designtime/api.json/1.0","version":"1.0.0","symbols":[{"kind":"namespace","name":"library.j","basename":"j","resource":"library/j/some.js","module":"library/j/some","static":true,"visibility":"public"}]} \ No newline at end of file diff --git a/test/fixtures/library.j/main/src/library/j/.library b/test/fixtures/library.j/main/src/library/j/.library new file mode 100644 index 000000000..1919da37e --- /dev/null +++ b/test/fixtures/library.j/main/src/library/j/.library @@ -0,0 +1,11 @@ + + + + library.j + SAP SE + Some fancy copyright + ${version} + + Library J + + diff --git a/test/fixtures/library.j/main/src/library/j/some.js b/test/fixtures/library.j/main/src/library/j/some.js new file mode 100644 index 000000000..8fd81bb68 --- /dev/null +++ b/test/fixtures/library.j/main/src/library/j/some.js @@ -0,0 +1,16 @@ +/*! + * ${copyright} + */ + +sap.ui.define([], + function() { + "use strict"; + + /** + * @alias library.j + * @namespace + * @public + */ + var SomeFunction = function() {}; + +}, /* bExport= */ true); diff --git a/test/fixtures/library.j/package.json b/test/fixtures/library.j/package.json new file mode 100644 index 000000000..5961b3664 --- /dev/null +++ b/test/fixtures/library.j/package.json @@ -0,0 +1,9 @@ +{ + "name": "library.j", + "version": "1.0.0", + "description": "Simple SAPUI5 based library for testing JSDoc builds", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/test/fixtures/library.j/ui5.yaml b/test/fixtures/library.j/ui5.yaml new file mode 100644 index 000000000..eae34d176 --- /dev/null +++ b/test/fixtures/library.j/ui5.yaml @@ -0,0 +1,10 @@ +--- +specVersion: "0.1" +type: library +metadata: + name: library.j +resources: + configuration: + paths: + src: main/src + test: main/test diff --git a/test/lib/builder/builder.js b/test/lib/builder/builder.js index d25a8a966..a5a6d8364 100644 --- a/test/lib/builder/builder.js +++ b/test/lib/builder/builder.js @@ -14,8 +14,9 @@ const applicationGPath = path.join(__dirname, "..", "..", "fixtures", "applicati const applicationHPath = path.join(__dirname, "..", "..", "fixtures", "application.h"); const libraryDPath = path.join(__dirname, "..", "..", "fixtures", "library.d"); const libraryEPath = path.join(__dirname, "..", "..", "fixtures", "library.e"); -const libraryIPath = path.join(__dirname, "..", "..", "fixtures", "library.i"); const libraryHPath = path.join(__dirname, "..", "..", "fixtures", "library.h"); +const libraryIPath = path.join(__dirname, "..", "..", "fixtures", "library.i"); +const libraryJPath = path.join(__dirname, "..", "..", "fixtures", "library.j"); const libraryCore = path.join(__dirname, "..", "..", "fixtures", "sap.ui.core-evo"); const themeJPath = path.join(__dirname, "..", "..", "fixtures", "theme.j"); @@ -248,6 +249,28 @@ test("Build library.i with manifest info taken from .library and library.js", (t }); }); +test("Build library.j with JSDoc build only", (t) => { + const destPath = path.join("test", "tmp", "build", "library.j", "dest"); + const expectedPath = path.join("test", "expected", "build", "library.j", "dest"); + + return builder.build({ + tree: libraryJTree, + destPath, + includedTasks: ["generateJsdoc"], + excludedTasks: ["*"] + }).then(() => { + return findFiles(expectedPath); + }).then((expectedFiles) => { + // Check for all directories and files + assert.directoryDeepEqual(destPath, expectedPath); + + // Check for all file contents + return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath); + }).then(() => { + t.pass(); + }); +}); + test("Build theme.j even without an library", (t) => { const destPath = "./test/tmp/build/theme.j/dest"; const expectedPath = "./test/expected/build/theme.j/dest"; @@ -753,6 +776,30 @@ const libraryITree = { } }; +const libraryJTree = { + "id": "library.j", + "version": "1.0.0", + "path": libraryJPath, + "dependencies": [], + "_level": 0, + "specVersion": "0.1", + "type": "library", + "metadata": { + "name": "library.j", + "copyright": "Some fancy copyright" + }, + "resources": { + "configuration": { + "paths": { + "src": "main/src" + } + }, + "pathMappings": { + "/resources/": "main/src" + } + } +}; + const themeJTree = { "id": "library.i", "version": "1.0.0", From 1ebd6c0e0fee1ab7bb6d0e66920b021564bd9f6f Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Wed, 6 Mar 2019 14:25:55 +0100 Subject: [PATCH 15/38] Sanitize project name before using it in directory name --- lib/tasks/generateJsdoc.js | 4 +++- test/lib/tasks/generateJsdoc.js | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/tasks/generateJsdoc.js b/lib/tasks/generateJsdoc.js index 2993e3d14..8c30c2e19 100644 --- a/lib/tasks/generateJsdoc.js +++ b/lib/tasks/generateJsdoc.js @@ -82,9 +82,11 @@ async function createTmpDirs(projectName) { * @returns {Promise} Promise resolving with path of the temporary directory */ function createTmpDir(projectName) { + // Remove all non alpha-num characters from project name + const sanitizedProjectName = projectName.replace(/[^A-Za-z0-9]/g, ""); return new Promise((resolve, reject) => { tmp.dir({ - prefix: `ui5-tooling-tmp-jsdoc-${projectName}-`, + prefix: `ui5-tooling-tmp-jsdoc-${sanitizedProjectName}-`, // keep: true, unsafeCleanup: true }, (err, path) => { diff --git a/test/lib/tasks/generateJsdoc.js b/test/lib/tasks/generateJsdoc.js index fa1cd8663..a781399a8 100644 --- a/test/lib/tasks/generateJsdoc.js +++ b/test/lib/tasks/generateJsdoc.js @@ -17,10 +17,10 @@ test.afterEach.always((t) => { test.serial("createTmpDir successful", async (t) => { t.context.tmpStub.callsArgWithAsync(1, undefined, "some/path"); - const res = await generateJsdoc._createTmpDir("some.namespace"); + const res = await generateJsdoc._createTmpDir("som$e.nam3/space"); // non alphanum characters get removed t.deepEqual(t.context.tmpStub.callCount, 1, "Tmp dir is called once"); - t.deepEqual(t.context.tmpStub.getCall(0).args[0].prefix, "ui5-tooling-tmp-jsdoc-some.namespace-"); + t.deepEqual(t.context.tmpStub.getCall(0).args[0].prefix, "ui5-tooling-tmp-jsdoc-somenam3space-"); t.deepEqual(res, {path: "some/path"}, "Correct path returned"); }); @@ -30,7 +30,7 @@ test.serial("createTmpDir error", async (t) => { const res = await t.throws(generateJsdoc._createTmpDir("some.namespace")); t.deepEqual(t.context.tmpStub.callCount, 1, "Tmp dir is called once"); - t.deepEqual(t.context.tmpStub.getCall(0).args[0].prefix, "ui5-tooling-tmp-jsdoc-some.namespace-"); + t.deepEqual(t.context.tmpStub.getCall(0).args[0].prefix, "ui5-tooling-tmp-jsdoc-somenamespace-"); t.deepEqual(res, {message: "Dir creation failed"}, "Dir creation failed"); }); From d633cdbec601946cfa3c9efb308e4ec95d741b7c Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Wed, 6 Mar 2019 15:35:32 +0100 Subject: [PATCH 16/38] Improve generateJsdoc unit tests --- test/lib/tasks/generateJsdoc.js | 41 +++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/test/lib/tasks/generateJsdoc.js b/test/lib/tasks/generateJsdoc.js index a781399a8..0781509bc 100644 --- a/test/lib/tasks/generateJsdoc.js +++ b/test/lib/tasks/generateJsdoc.js @@ -51,20 +51,49 @@ test.serial("createTmpDirs", async (t) => { mock.stop("make-dir"); }); -test.serial("writeResourcesToDir", async (t) => { +test.serial("writeResourcesToDir with byGlobSource", async (t) => { const writeStub = sinon.stub().resolves(); const createAdapterStub = sinon.stub(require("@ui5/fs").resourceFactory, "createAdapter").returns({ write: writeStub }); - generateJsdoc._writeResourcesToDir({ + await generateJsdoc._writeResourcesToDir({ workspace: { // stub byGlobSource byGlobSource: (pattern) => { - return Promise.resolve([]); + t.deepEqual(pattern, "some pattern", "Glob with correct pattern"); + return Promise.resolve(["resource A", "resource B"]); } }, - pattern: "", + pattern: "some pattern", + targetPath: "/some/target/path" + }); + + t.deepEqual(createAdapterStub.getCall(0).args[0], { + fsBasePath: "/some/target/path", + virBasePath: "/resources/" + }, "createAdapter called with correct arguments"); + + t.deepEqual(writeStub.callCount, 2, "Write got called twice"); + t.deepEqual(writeStub.getCall(0).args[0], "resource A", "Write got called with correct arguments"); + t.deepEqual(writeStub.getCall(1).args[0], "resource B", "Write got called with correct arguments"); +}); + +test.serial("writeResourcesToDir with byGlob", async (t) => { + const writeStub = sinon.stub().resolves(); + const createAdapterStub = sinon.stub(require("@ui5/fs").resourceFactory, "createAdapter").returns({ + write: writeStub + }); + + await generateJsdoc._writeResourcesToDir({ + workspace: { + // stub byGlobSource + byGlob: (pattern) => { + t.deepEqual(pattern, "some pattern", "Glob with correct pattern"); + return Promise.resolve(["resource A", "resource B"]); + } + }, + pattern: "some pattern", targetPath: "/some/target/path" }); @@ -72,6 +101,10 @@ test.serial("writeResourcesToDir", async (t) => { fsBasePath: "/some/target/path", virBasePath: "/resources/" }, "createAdapter called with correct arguments"); + + t.deepEqual(writeStub.callCount, 2, "Write got called twice"); + t.deepEqual(writeStub.getCall(0).args[0], "resource A", "Write got called with correct arguments"); + t.deepEqual(writeStub.getCall(1).args[0], "resource B", "Write got called with correct arguments"); }); test.serial("generateJsdoc", async (t) => { From 006702c24e8f73feae24521c1fc7c515238ba42c Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Thu, 7 Mar 2019 10:24:06 +0100 Subject: [PATCH 17/38] Refactor jsdoc processor internal files into lib dir --- .../jsdoc/jsdoc-config-template.json | 23 ------------------- lib/processors/jsdoc/jsdocGenerator.js | 6 ++--- .../jsdoc/{ => lib}/create-api-index.js | 0 .../{ => lib}/transform-apijson-for-sdk.js | 0 lib/processors/jsdoc/{ => lib}/ui5/plugin.js | 0 .../jsdoc/{ => lib}/ui5/template/publish.js | 0 test/lib/processors/jsdoc/jsdocGenerator.js | 4 ++-- 7 files changed, 5 insertions(+), 28 deletions(-) delete mode 100644 lib/processors/jsdoc/jsdoc-config-template.json rename lib/processors/jsdoc/{ => lib}/create-api-index.js (100%) rename lib/processors/jsdoc/{ => lib}/transform-apijson-for-sdk.js (100%) rename lib/processors/jsdoc/{ => lib}/ui5/plugin.js (100%) rename lib/processors/jsdoc/{ => lib}/ui5/template/publish.js (100%) diff --git a/lib/processors/jsdoc/jsdoc-config-template.json b/lib/processors/jsdoc/jsdoc-config-template.json deleted file mode 100644 index 52569ef4c..000000000 --- a/lib/processors/jsdoc/jsdoc-config-template.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "source": { - "excludePattern": "(/|\\\\)library-all\\.js|(/|\\\\).*-preload\\.js|^jquery-.*\\.js|^sap-.*\\.js" - }, - "opts" : { - "recurse": true, - "template" : "lib/jsdoc/ui5/template" - }, - "plugins": [ - "lib/jsdoc/ui5/plugin.js" - ], - "templates" : { - "ui5" : { - "variants": [ - "apijson" - ], - "version": "${version}", - "apiJsonFolder": "${apiJsonFolder}", - "apiJsonFile": "${apiJsonFile}", - "includeSettingsInConstructor": false - } - } -} diff --git a/lib/processors/jsdoc/jsdocGenerator.js b/lib/processors/jsdoc/jsdocGenerator.js index 864aad5c0..40dbcfb8f 100644 --- a/lib/processors/jsdoc/jsdocGenerator.js +++ b/lib/processors/jsdoc/jsdocGenerator.js @@ -20,7 +20,7 @@ const {resourceFactory} = require("@ui5/fs"); * @param {Array} [parameters.options.variants=["apijson"]] JSDoc variants to be built * @param {boolean} [parameters.options.sdkBuild=true] Whether additional SDK specific * api.json resources shall be generated - * @returns {Promise} Promise resolving with + * @returns {Promise} Promise resolving with newly created resources */ const jsdocGenerator = async function({sourcePath, targetPath, tmpPath, options} = {}) { if (!sourcePath || !targetPath || !tmpPath || !options.projectName || !options.version) { @@ -82,11 +82,11 @@ async function generateJsdocConfig({targetPath, tmpPath, namespace, projectName, const jsdocPath = path.normalize(__dirname); const config = `{ - "plugins": ["${jsdocPath}/ui5/plugin.js"], + "plugins": ["${jsdocPath}/lib/ui5/plugin.js"], "opts": { "recurse": true, "lenient": true, - "template": "${jsdocPath}/ui5/template", + "template": "${jsdocPath}/lib/ui5/template", "ui5": { "saveSymbols": true }, diff --git a/lib/processors/jsdoc/create-api-index.js b/lib/processors/jsdoc/lib/create-api-index.js similarity index 100% rename from lib/processors/jsdoc/create-api-index.js rename to lib/processors/jsdoc/lib/create-api-index.js diff --git a/lib/processors/jsdoc/transform-apijson-for-sdk.js b/lib/processors/jsdoc/lib/transform-apijson-for-sdk.js similarity index 100% rename from lib/processors/jsdoc/transform-apijson-for-sdk.js rename to lib/processors/jsdoc/lib/transform-apijson-for-sdk.js diff --git a/lib/processors/jsdoc/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js similarity index 100% rename from lib/processors/jsdoc/ui5/plugin.js rename to lib/processors/jsdoc/lib/ui5/plugin.js diff --git a/lib/processors/jsdoc/ui5/template/publish.js b/lib/processors/jsdoc/lib/ui5/template/publish.js similarity index 100% rename from lib/processors/jsdoc/ui5/template/publish.js rename to lib/processors/jsdoc/lib/ui5/template/publish.js diff --git a/test/lib/processors/jsdoc/jsdocGenerator.js b/test/lib/processors/jsdoc/jsdocGenerator.js index 694c7c9aa..a49604f0b 100644 --- a/test/lib/processors/jsdoc/jsdocGenerator.js +++ b/test/lib/processors/jsdoc/jsdocGenerator.js @@ -19,11 +19,11 @@ test("generateJsdocConfig", async (t) => { "jsdoc"); t.deepEqual(res, `{ - "plugins": ["${jsdocGeneratorPath}/ui5/plugin.js"], + "plugins": ["${jsdocGeneratorPath}/lib/ui5/plugin.js"], "opts": { "recurse": true, "lenient": true, - "template": "${jsdocGeneratorPath}/ui5/template", + "template": "${jsdocGeneratorPath}/lib/ui5/template", "ui5": { "saveSymbols": true }, From ef0c52e0e341f17fa7e6b1cf9477760971889611 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Thu, 7 Mar 2019 16:16:50 +0100 Subject: [PATCH 18/38] Collect dependency APIs --- lib/builder/builder.js | 16 +++- lib/processors/jsdoc/jsdocGenerator.js | 2 +- lib/tasks/generateJsdoc.js | 52 ++++++++++--- lib/types/library/LibraryBuilder.js | 1 + test/lib/processors/jsdoc/jsdocGenerator.js | 2 +- test/lib/tasks/generateJsdoc.js | 86 +++++++++++++++++++-- 6 files changed, 136 insertions(+), 23 deletions(-) diff --git a/lib/builder/builder.js b/lib/builder/builder.js index fdecdaa13..b801e8a24 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -1,5 +1,6 @@ const log = require("@ui5/logger").getGroupLogger("builder:builder"); const resourceFactory = require("@ui5/fs").resourceFactory; +const MemAdapter = require("@ui5/fs").adapters.Memory; const typeRepository = require("../types/typeRepository"); const taskRepository = require("../tasks/taskRepository"); @@ -148,6 +149,7 @@ module.exports = { const projects = {}; // Unique project index to prevent building the same project multiple times + const projectWriters = {}; // Collection of memory adapters of already built libraries const projectCountMarker = {}; function projectCount(project, count = 0) { @@ -188,10 +190,21 @@ module.exports = { const projectType = typeRepository.getType(project.type); const resourceCollections = resourceFactory.createCollectionsForTree(project, { - useNamespaces: true + useNamespaces: true, + virtualReaders: projectWriters }); + const writer = new MemAdapter({ + virBasePath: "/" + }); + // Store project writer as virtual reader for parent projects + // so they can access the build results of this project + projectWriters[project.metadata.name] = writer; + + // TODO: Add getter for writer of DuplexColection const workspace = resourceFactory.createWorkspace({ + virBasePath: "/", + writer, reader: resourceCollections.source, name: project.metadata.name }); @@ -199,6 +212,7 @@ module.exports = { if (dev && devExcludeProject.indexOf(project.metadata.name) !== -1) { projectTasks = composeTaskList({dev: false, selfContained, includedTasks, excludedTasks}); } + return projectType.build({ resourceCollections: { workspace, diff --git a/lib/processors/jsdoc/jsdocGenerator.js b/lib/processors/jsdoc/jsdocGenerator.js index 40dbcfb8f..4e2be64f0 100644 --- a/lib/processors/jsdoc/jsdocGenerator.js +++ b/lib/processors/jsdoc/jsdocGenerator.js @@ -97,7 +97,7 @@ async function generateJsdocConfig({targetPath, tmpPath, namespace, projectName, "variants": ${JSON.stringify(variants)}, "version": "${version}", "jsapiFile": "${targetPath}/libraries/${projectName}.js", - "apiJsonFolder": "${targetPath}/dependency-apis", + "apiJsonFolder": "${tmpPath}/dependency-apis", "apiJsonFile": "${targetPath}/test-resources/${namespace}/designtime/api.json" } } diff --git a/lib/tasks/generateJsdoc.js b/lib/tasks/generateJsdoc.js index 8c30c2e19..7b7d0cf50 100644 --- a/lib/tasks/generateJsdoc.js +++ b/lib/tasks/generateJsdoc.js @@ -13,6 +13,7 @@ const {resourceFactory} = require("@ui5/fs"); * @alias module:@ui5/builder.tasks.generateJsdoc * @param {Object} parameters Parameters * @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {module:@ui5/fs.ReaderCollection} parameters.dependencies DuplexCollection to read and write files * @param {Object} parameters.options Options * @param {string} parameters.options.pattern Pattern to locate the files to be processed * @param {string} parameters.options.projectName Project name @@ -21,18 +22,25 @@ const {resourceFactory} = require("@ui5/fs"); * resources shall be generated * @returns {Promise} Promise resolving with undefined once data has been written */ -const generateJsdoc = async function({workspace, options} = {}) { +const generateJsdoc = async function({workspace, dependencies, options} = {}) { if (!options || !options.projectName || !options.version || !options.pattern) { throw new Error("[generateJsdoc]: One or more mandatory options not provided"); } const {sourcePath: resourcePath, targetPath, tmpPath} = await generateJsdoc._createTmpDirs(options.projectName); - await generateJsdoc._writeResourcesToDir({ - workspace, - pattern: options.pattern, - targetPath: resourcePath - }); + await Promise.all([ + generateJsdoc._writeResourcesToDir({ + workspace, + dependencies, + pattern: options.pattern, + targetPath: resourcePath + }), + generateJsdoc._writeDependencyApisToDir({ + dependencies, + targetPath: path.posix.join(tmpPath, "dependency-apis") + }) + ]); const createdResources = await jsdocGenerator({ sourcePath: resourcePath, @@ -45,8 +53,8 @@ const generateJsdoc = async function({workspace, options} = {}) { } }); - await Promise.all(createdResources.map(async (resource) => { - await workspace.write(resource); + await Promise.all(createdResources.map((resource) => { + return workspace.write(resource); })); }; @@ -79,15 +87,16 @@ async function createTmpDirs(projectName) { * * @private * @param {string} projectName Project name used for naming the temporary directory + * @param {boolean} [keep=false] Whether to keep the temporary directory * @returns {Promise} Promise resolving with path of the temporary directory */ -function createTmpDir(projectName) { +function createTmpDir(projectName, keep = false) { // Remove all non alpha-num characters from project name const sanitizedProjectName = projectName.replace(/[^A-Za-z0-9]/g, ""); return new Promise((resolve, reject) => { tmp.dir({ prefix: `ui5-tooling-tmp-jsdoc-${sanitizedProjectName}-`, - // keep: true, + keep, unsafeCleanup: true }, (err, path) => { if (err) { @@ -113,7 +122,7 @@ function createTmpDir(projectName) { * @returns {Promise} Promise resolving with undefined once data has been written */ async function writeResourcesToDir({workspace, pattern, targetPath}) { - const fsSource = resourceFactory.createAdapter({ + const fsTarget = resourceFactory.createAdapter({ fsBasePath: targetPath, virBasePath: "/resources/" }); @@ -124,11 +133,30 @@ async function writeResourcesToDir({workspace, pattern, targetPath}) { } else { allResources = await workspace.byGlob(pattern); } + // write all resources to the tmp folder - await Promise.all(allResources.map((resource) => fsSource.write(resource))); + await Promise.all(allResources.map((resource) => fsTarget.write(resource))); +} + +async function writeDependencyApisToDir({dependencies, targetPath}) { + const depApis = await dependencies.byGlob("/test-resources/**/designtime/api.json"); + + // Clone resources before changing their path + const apis = await Promise.all(depApis.map((resource) => resource.clone())); + + for (let i = 0; i < apis.length; i++) { + apis[i].setPath(`/api-${i}.json`); + } + + const fsTarget = resourceFactory.createAdapter({ + fsBasePath: targetPath, + virBasePath: "/" + }); + await Promise.all(apis.map((resource) => fsTarget.write(resource))); } module.exports = generateJsdoc; module.exports._createTmpDirs = createTmpDirs; module.exports._createTmpDir = createTmpDir; module.exports._writeResourcesToDir = writeResourcesToDir; +module.exports._writeDependencyApisToDir = writeDependencyApisToDir; diff --git a/lib/types/library/LibraryBuilder.js b/lib/types/library/LibraryBuilder.js index f80cbb815..ea7d5d0bb 100644 --- a/lib/types/library/LibraryBuilder.js +++ b/lib/types/library/LibraryBuilder.js @@ -44,6 +44,7 @@ class LibraryBuilder extends AbstractBuilder { const generateJsdoc = tasks.generateJsdoc; return generateJsdoc({ workspace: resourceCollections.workspace, + dependencies: resourceCollections.dependencies, options: { projectName: project.metadata.name, version: project.version, diff --git a/test/lib/processors/jsdoc/jsdocGenerator.js b/test/lib/processors/jsdoc/jsdocGenerator.js index a49604f0b..b4227a55b 100644 --- a/test/lib/processors/jsdoc/jsdocGenerator.js +++ b/test/lib/processors/jsdoc/jsdocGenerator.js @@ -34,7 +34,7 @@ test("generateJsdocConfig", async (t) => { "variants": ["apijson"], "version": "1.0.0", "jsapiFile": "/some/target/path/libraries/some.namespace.js", - "apiJsonFolder": "/some/target/path/dependency-apis", + "apiJsonFolder": "/some/tmp/path/dependency-apis", "apiJsonFile": "/some/target/path/test-resources/some/namespace/designtime/api.json" } } diff --git a/test/lib/tasks/generateJsdoc.js b/test/lib/tasks/generateJsdoc.js index 0781509bc..525a9ace3 100644 --- a/test/lib/tasks/generateJsdoc.js +++ b/test/lib/tasks/generateJsdoc.js @@ -74,9 +74,9 @@ test.serial("writeResourcesToDir with byGlobSource", async (t) => { virBasePath: "/resources/" }, "createAdapter called with correct arguments"); - t.deepEqual(writeStub.callCount, 2, "Write got called twice"); - t.deepEqual(writeStub.getCall(0).args[0], "resource A", "Write got called with correct arguments"); - t.deepEqual(writeStub.getCall(1).args[0], "resource B", "Write got called with correct arguments"); + t.deepEqual(writeStub.callCount, 2, "Write got called four times"); + t.deepEqual(writeStub.getCall(0).args[0], "resource A", "Write got called for resource A"); + t.deepEqual(writeStub.getCall(1).args[0], "resource B", "Write got called for resource B"); }); test.serial("writeResourcesToDir with byGlob", async (t) => { @@ -87,7 +87,6 @@ test.serial("writeResourcesToDir with byGlob", async (t) => { await generateJsdoc._writeResourcesToDir({ workspace: { - // stub byGlobSource byGlob: (pattern) => { t.deepEqual(pattern, "some pattern", "Glob with correct pattern"); return Promise.resolve(["resource A", "resource B"]); @@ -102,13 +101,71 @@ test.serial("writeResourcesToDir with byGlob", async (t) => { virBasePath: "/resources/" }, "createAdapter called with correct arguments"); - t.deepEqual(writeStub.callCount, 2, "Write got called twice"); - t.deepEqual(writeStub.getCall(0).args[0], "resource A", "Write got called with correct arguments"); - t.deepEqual(writeStub.getCall(1).args[0], "resource B", "Write got called with correct arguments"); + t.deepEqual(writeStub.callCount, 2, "Write got called four times"); + t.deepEqual(writeStub.getCall(0).args[0], "resource A", "Write got called for resource A"); + t.deepEqual(writeStub.getCall(1).args[0], "resource B", "Write got called for resource B"); +}); + +test.serial("writeDependencyApisToDir with byGlob", async (t) => { + const writeStub = sinon.stub().resolves(); + const createAdapterStub = sinon.stub(require("@ui5/fs").resourceFactory, "createAdapter").returns({ + write: writeStub + }); + + const setPathStubA = sinon.stub(); + const setPathStubB = sinon.stub(); + + const cloneStubA = sinon.stub().resolves({ + // Cloned resource + id: "resource A", + setPath: setPathStubA + }); + const cloneStubB = sinon.stub().resolves({ + // Cloned resource + id: "resource B", + setPath: setPathStubB + }); + const initialResourceA = { + // Globbed resource + clone: cloneStubA + }; + const initialResourceB = { + // Globbed resource + clone: cloneStubB + }; + + await generateJsdoc._writeDependencyApisToDir({ + dependencies: { + byGlob: (pattern) => { + t.deepEqual(pattern, "/test-resources/**/designtime/api.json", + "Dependency api.json glob with correct pattern"); + return Promise.resolve([initialResourceA, initialResourceB]); + } + }, + targetPath: "/some/target/path" + }); + + t.deepEqual(cloneStubA.callCount, 1, "resource A got cloned once"); + t.deepEqual(cloneStubB.callCount, 1, "resource B got cloned once"); + + t.deepEqual(setPathStubA.callCount, 1, "Path of cloned resource A got changed"); + t.deepEqual(setPathStubA.getCall(0).args[0], "/api-0.json", "Path of cloned resource A got changed correctly"); + + t.deepEqual(setPathStubB.callCount, 1, "Path of cloned resource B got changed"); + t.deepEqual(setPathStubB.getCall(0).args[0], "/api-1.json", "Path of cloned resource B got changed correctly"); + + t.deepEqual(createAdapterStub.getCall(0).args[0], { + fsBasePath: "/some/target/path", + virBasePath: "/" + }, "createAdapter called with correct arguments"); + + t.deepEqual(writeStub.callCount, 2, "Write got called four times"); + t.deepEqual(writeStub.getCall(0).args[0].id, "resource A", "Write got called for resource A"); + t.deepEqual(writeStub.getCall(1).args[0].id, "resource B", "Write got called for resource B"); }); test.serial("generateJsdoc", async (t) => { - const jsdocGeneratorStub = sinon.stub().resolves(["some resource 1", "some resource 2"]); + const jsdocGeneratorStub = sinon.stub().resolves(["resource A", "resource B"]); mock("../../../lib/processors/jsdoc/jsdocGenerator", jsdocGeneratorStub); const generateJsdoc = mock.reRequire("../../../lib/tasks/generateJsdoc"); @@ -118,6 +175,7 @@ test.serial("generateJsdoc", async (t) => { tmpPath: "/some/tmp/path", }); const writeResourcesToDirStub = sinon.stub(generateJsdoc, "_writeResourcesToDir").resolves(); + const writeDependencyApisToDirStub = sinon.stub(generateJsdoc, "_writeDependencyApisToDir").resolves(); const writeStub = sinon.stub().resolves(); const workspace = { @@ -125,6 +183,7 @@ test.serial("generateJsdoc", async (t) => { }; await generateJsdoc({ workspace, + dependencies: "dependencies", options: { pattern: "some pattern", projectName: "some.project", @@ -139,10 +198,17 @@ test.serial("generateJsdoc", async (t) => { t.deepEqual(writeResourcesToDirStub.callCount, 1, "writeResourcesToDir got called once"); t.deepEqual(writeResourcesToDirStub.getCall(0).args[0], { workspace, + dependencies: "dependencies", pattern: "some pattern", targetPath: "/some/source/path" // one's target is another one's source }, "writeResourcesToDir got called with correct arguments"); + t.deepEqual(writeDependencyApisToDirStub.callCount, 1, "writeDependencyApisToDir got called once"); + t.deepEqual(writeDependencyApisToDirStub.getCall(0).args[0], { + dependencies: "dependencies", + targetPath: "/some/tmp/path/dependency-apis" + }, "writeDependencyApisToDir got called with correct arguments"); + t.deepEqual(jsdocGeneratorStub.callCount, 1, "jsdocGenerator processor got called once"); t.deepEqual(jsdocGeneratorStub.getCall(0).args[0], { sourcePath: "/some/source/path", @@ -155,6 +221,10 @@ test.serial("generateJsdoc", async (t) => { } }, "jsdocGenerator got called with correct arguments"); + t.deepEqual(writeStub.callCount, 2, "Write got called twice"); + t.deepEqual(writeStub.getCall(0).args[0], "resource A", "Write got called with correct arguments"); + t.deepEqual(writeStub.getCall(1).args[0], "resource B", "Write got called with correct arguments"); + mock.stop("../../../lib/processors/jsdoc/jsdocGenerator"); }); From 2e2a663278aee3eac3ff4de445e2f981f2be1fc3 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Thu, 7 Mar 2019 17:44:22 +0100 Subject: [PATCH 19/38] Add handling for exclude patterns defined in project configuration --- lib/tasks/generateJsdoc.js | 13 +++++++++++-- lib/types/library/LibraryBuilder.js | 13 ++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/tasks/generateJsdoc.js b/lib/tasks/generateJsdoc.js index 7b7d0cf50..693de9ed6 100644 --- a/lib/tasks/generateJsdoc.js +++ b/lib/tasks/generateJsdoc.js @@ -13,9 +13,9 @@ const {resourceFactory} = require("@ui5/fs"); * @alias module:@ui5/builder.tasks.generateJsdoc * @param {Object} parameters Parameters * @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files - * @param {module:@ui5/fs.ReaderCollection} parameters.dependencies DuplexCollection to read and write files + * @param {module:@ui5/fs.AbstractReader} parameters.dependencies Reader or Collection to read dependency files * @param {Object} parameters.options Options - * @param {string} parameters.options.pattern Pattern to locate the files to be processed + * @param {string|Array} parameters.options.pattern Pattern to locate the files to be processed * @param {string} parameters.options.projectName Project name * @param {string} parameters.options.version Project version * @param {boolean} [parameters.options.sdkBuild=true] Whether additional SDK specific api.json @@ -138,6 +138,15 @@ async function writeResourcesToDir({workspace, pattern, targetPath}) { await Promise.all(allResources.map((resource) => fsTarget.write(resource))); } +/** + * Write api.json files of dependencies to given target path in a flat structure + * + * @private + * @param {Object} parameters Parameters + * @param {module:@ui5/fs.AbstractReader} parameters.dependencies Reader or Collection to read dependency files + * @param {string} parameters.targetPath Path to write the resources to + * @returns {Promise} Promise resolving with undefined once data has been written + */ async function writeDependencyApisToDir({dependencies, targetPath}) { const depApis = await dependencies.byGlob("/test-resources/**/designtime/api.json"); diff --git a/lib/types/library/LibraryBuilder.js b/lib/types/library/LibraryBuilder.js index ea7d5d0bb..396a60655 100644 --- a/lib/types/library/LibraryBuilder.js +++ b/lib/types/library/LibraryBuilder.js @@ -42,13 +42,24 @@ class LibraryBuilder extends AbstractBuilder { this.addTask("generateJsdoc", () => { const generateJsdoc = tasks.generateJsdoc; + + const patterns = ["/resources/**/*.js"]; + // Add excludes + if (project.builder && project.builder.jsdoc && project.builder.jsdoc.excludes) { + const excludes = project.builder.jsdoc.excludes.map((pattern) => { + return `!/resources/${pattern}`; + }); + + patterns.push(...excludes); + } + return generateJsdoc({ workspace: resourceCollections.workspace, dependencies: resourceCollections.dependencies, options: { projectName: project.metadata.name, version: project.version, - pattern: "/resources/**/*.js" + pattern: patterns } }); }); From a156d614393ee9fe4997ef37723763be47301dd4 Mon Sep 17 00:00:00 2001 From: Frank Weigel Date: Thu, 7 Mar 2019 23:40:41 +0100 Subject: [PATCH 20/38] Update JSDoc template and plugin from grunt tooling (openui5 master) As these resources are shared with the jsdoc-maven-plugin which still allows to use an older Node.JS version, all modernization (es6+) as well as eslint fixes had to be reverted for now. To compensate for this, the shared resources are excluded from linting. --- .eslintignore | 3 + lib/processors/jsdoc/lib/create-api-index.js | 275 +- .../jsdoc/lib/transform-apijson-for-sdk.js | 1487 ++++++----- lib/processors/jsdoc/lib/ui5/plugin.js | 1136 +++++---- .../jsdoc/lib/ui5/template/publish.js | 2269 +++++++++-------- 5 files changed, 2806 insertions(+), 2364 deletions(-) diff --git a/.eslintignore b/.eslintignore index 5c9eafbbe..5f3b1d6e9 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,8 @@ # /node_modules/* and /bower_components/* ignored by default +# Exclude shared resources that can't (yet) follow our conventions +lib/processors/jsdoc/lib + # Exclude coverage folder coverage/ diff --git a/lib/processors/jsdoc/lib/create-api-index.js b/lib/processors/jsdoc/lib/create-api-index.js index 33ccd20da..0866f8852 100644 --- a/lib/processors/jsdoc/lib/create-api-index.js +++ b/lib/processors/jsdoc/lib/create-api-index.js @@ -1,28 +1,27 @@ /* * Node script to create cross-library API index files for use in the UI5 SDKs. * - * (c) Copyright 2009-2018 SAP SE or an SAP affiliate company. + * (c) Copyright 2009-2019 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ "use strict"; const fs = require("fs"); const path = require("path"); -const log = require("@ui5/logger").getLogger("builder:processors:jsdoc:"); - -function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetFileDeprecated, - targetFileExperimental, targetFileSince) { - log.info("creating API index files"); - log.info(" sap-ui-version.json: " + versionInfoFile); - log.info(" unpacked test-resources: " + unpackedTestresourcesRoot); - log.info(" target file: " + targetFile); - log.info(" target file deprecated: " + targetFileDeprecated); - log.info(" target file experimental: " + targetFileExperimental); - log.info(" target file since: " + targetFileSince); - log.info(""); + +function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetFileDeprecated, targetFileExperimental, targetFileSince) { + + console.log("[INFO] creating API index files"); + console.log("[INFO] sap-ui-version.json: " + versionInfoFile); + console.log("[INFO] unpacked test-resources: " + unpackedTestresourcesRoot); + console.log("[INFO] target file: " + targetFile); + console.log("[INFO] target file deprecated: " + targetFileDeprecated); + console.log("[INFO] target file experimental: " + targetFileExperimental); + console.log("[INFO] target file since: " + targetFileSince); + console.log("[INFO]"); // Deprecated, Experimental and Since collections - const oListCollection = { + let oListCollection = { deprecated: { noVersion: { apis: [] @@ -33,16 +32,12 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF apis: [] } }, - since: { - noVersion: { - apis: [] - } - } + since: {} }; function readJSONFile(file) { - return new Promise(function(resolve, reject) { - fs.readFile(file, "utf8", function(err, data) { + return new Promise(function (resolve, reject) { + fs.readFile(file, 'utf8', function (err, data) { if (err) { reject(err); } else { @@ -65,7 +60,7 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF } function writeJSON(file, content) { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve,reject) { // Create dir if it does not exist mkdirSync( path.dirname(file) ); fs.writeFile(file, JSON.stringify(content), "utf-8", function(err) { @@ -84,16 +79,15 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF * Returns a promise that resolves with an array of symbols. */ function createSymbolSummaryForLib(lib) { - const file = path.join(unpackedTestresourcesRoot, lib.replace(/\./g, "/"), "designtime/api.json"); + let file = path.join(unpackedTestresourcesRoot, lib.replace(/\./g, "/"), "designtime/api.json"); - return readJSONFile(file).then(function(apijson) { + return readJSONFile(file).then(function (apijson) { if (!apijson.hasOwnProperty("symbols") || !Array.isArray(apijson.symbols)) { // Ignore libraries with invalid api.json content like empty object or non-array "symbols" property. return []; } - return apijson.symbols.map((symbol) => { - collectLists(symbol); - return { + return apijson.symbols.map(symbol => { + let oReturn = { name: symbol.name, kind: symbol.kind, visibility: symbol.visibility, @@ -101,8 +95,14 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF implements: symbol.implements, lib: lib }; + // We add deprecated member only when the control is deprecated to keep file size at check + if (symbol.deprecated) { + oReturn.deprecated = true; + } + collectLists(symbol); + return oReturn; }); - }); + }) } /* @@ -110,26 +110,25 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF * including symbol itself, methods and events. */ function collectLists(oSymbol) { - function addData(oDataType, oEntityObject, sObjectType, sSymbolName) { - const sSince = oDataType !== "since" ? oEntityObject[oDataType].since : oEntityObject.since; - - const oData = { - "control": sSymbolName, - "text": oEntityObject[oDataType].text || oEntityObject.description, - "type": sObjectType, - "static": !!oEntityObject.static, - "visibility": oEntityObject.visibility - }; + function addData(oDataType, oEntityObject, sObjectType, sSymbolName) { + let sSince = oDataType !== "since" ? oEntityObject[oDataType].since : oEntityObject.since, + oData = { + control: sSymbolName, + text: oEntityObject[oDataType].text || oEntityObject.description, + type: sObjectType, + "static": !!oEntityObject.static, + visibility: oEntityObject.visibility + }; // For class we skip entityName if (sObjectType !== "class") { oData.entityName = oEntityObject.name; } - if (sSince) { + if (sSince && sSince !== "undefined" /* Sometimes sSince comes as string "undefined" */) { // take only major and minor versions - const sVersion = sSince.split(".").slice(0, 2).join("."); + let sVersion = sSince.split(".").slice(0, 2).join("."); oData.since = sSince; @@ -141,7 +140,7 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF } oListCollection[oDataType][sVersion].apis.push(oData); - } else { + } else if (oDataType !== "since" /* noVersion does not make sense for since and will fail */) { oListCollection[oDataType].noVersion.apis.push(oData); } } @@ -155,12 +154,12 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF addData("experimental", oSymbol, "class", oSymbol.name); } - if (oSymbol.since) { + if (oSymbol.since && oSymbol.since !== "undefined" /* Sometimes sSince comes as string "undefined" */) { addData("since", oSymbol, "class", oSymbol.name); } // Methods - oSymbol.methods && oSymbol.methods.forEach((oMethod) => { + oSymbol.methods && oSymbol.methods.forEach(oMethod => { if (oMethod.deprecated) { addData("deprecated", oMethod, "methods", oSymbol.name); } @@ -175,7 +174,7 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF }); // Events - oSymbol.events && oSymbol.events.forEach((oEvent) => { + oSymbol.events && oSymbol.events.forEach(oEvent => { if (oEvent.deprecated) { addData("deprecated", oEvent, "events", oSymbol.name); } @@ -188,31 +187,32 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF addData("since", oEvent, "events", oSymbol.name); } }); + } function deepMerge(arrayOfArrays) { return arrayOfArrays.reduce((array, items) => { - array.push(...items); + array.push.apply(array, items); return array; }, []); } function expandHierarchyInfo(symbols) { - const byName = new Map(); - symbols.forEach((symbol) => { + let byName = new Map(); + symbols.forEach(symbol => { byName.set(symbol.name, symbol); }); - symbols.forEach((symbol) => { - const parent = symbol.extends && byName.get(symbol.extends); + symbols.forEach(symbol => { + let parent = symbol.extends && byName.get(symbol.extends); if (parent) { - parent.extendedBy = parent.extendedBy || []; + parent.extendedBy = parent.extendedBy ||  []; parent.extendedBy.push(symbol.name); } if (symbol.implements) { - symbol.implements.forEach((intfName) => { - const intf = byName.get(intfName); + symbol.implements.forEach(intfName => { + let intf = byName.get(intfName); if (intf) { - intf.implementedBy = intf.implementedBy || []; + intf.implementedBy = intf.implementedBy ||  []; intf.implementedBy.push(symbol.name); } }); @@ -221,17 +221,111 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF return symbols; } + function convertListToTree(symbols) { + let aTree = []; + + // Filter out black list libraries + symbols = symbols.filter(({lib}) => ["sap.ui.demokit", "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 + symbols.forEach(oSymbol => { + oSymbol.treeName.split(".").forEach((sPart, i, a) => { + let sName = a.slice(0, (i + 1)).join("."); + + if (!symbols.find(o => o.treeName === sName)) { + symbols.push({ + name: sName, + treeName: sName, + displayName: sPart, + lib: oSymbol.lib, + kind: "namespace", + bIsDeprecated: false // Virtual namespace can't be deprecated + }); + } + }); + }); + + // Discover parents + symbols.forEach(oSymbol => { + let aParent = oSymbol.treeName.split("."), + sParent; + + // Extract parent name + aParent.pop(); + sParent = aParent.join("."); + + // Mark parent + if (symbols.find(({treeName}) => treeName === sParent)) { + oSymbol.parent = sParent; + } + }); + + // Sort the list before building the tree + symbols.sort((a, b) => { + let sA = a.treeName.toUpperCase(), + sB = b.treeName.toUpperCase(); + + if (sA < sB) return -1; + if (sA > sB) return 1; + return 0; + }); + + // Build tree + symbols.forEach(oSymbol => { + if (oSymbol.parent) { + let oParent = symbols.find(({treeName}) => treeName === oSymbol.parent); + + if (!oParent.nodes) oParent.nodes = []; + oParent.nodes.push(oSymbol); + } else { + aTree.push(oSymbol); + } + }); + + // Custom sort first level tree items - "sap" namespace should be on top + aTree.sort((a, b) => { + let sA = a.displayName.toUpperCase(), + sB = b.displayName.toUpperCase(); + + if (sA === "SAP") return -1; + if (sB === "SAP") return 1; + if (sA < sB) return -1; + if (sA > sB) return 1; + + return 0; + }); + + // Clean tree - keep file size down + function cleanTree (oSymbol) { + delete oSymbol.treeName; + delete oSymbol.parent; + if (oSymbol.children) { + oSymbol.children.forEach(o => cleanTree(o)); + } + } + aTree.forEach(o => cleanTree(o)); + + return aTree; + } + function createOverallIndex() { let version = "0.0.0"; - const p = readJSONFile(versionInfoFile) - .then((versionInfo) => { + var p = readJSONFile(versionInfoFile) + .then(versionInfo => { version = versionInfo.version; return Promise.all( versionInfo.libraries.map( - (lib) => createSymbolSummaryForLib(lib.name).catch((err) => { + lib => createSymbolSummaryForLib(lib.name).catch(err => { // ignore 'file not found' errors as some libs don't have an api.json (themes, server libs) - if (err.code === "ENOENT") { + if (err.code === 'ENOENT') { return []; } throw err; @@ -241,23 +335,73 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF }) .then(deepMerge) .then(expandHierarchyInfo) - .then((symbols) => { - const result = { - "$schema-ref": "http://schemas.sap.com/sapui5/designtime/api.json/1.0", - "version": version, - "library": "*", - "symbols": symbols + .then(convertListToTree) + .then(symbols => { + let result = { + "$schema-ref": "http://schemas.sap.com/sapui5/designtime/api-index.json/1.0", + version: version, + library: "*", + symbols: symbols }; return writeJSON(targetFile, result); }) + .then(() => { + /* Lists - modify and cleanup */ + let sortList = function (oList) { + /* Sorting since records */ + let aKeys = Object.keys(oList), + oSorted = {}; + + aKeys.sort((a, b) => { + let aA = a.split("."), + aB = b.split("."); + + if (a === "noVersion") { + return 1; /* No version always at end of list */ + } + + if (b === "noVersion") { + return -1; /* No version always at end of list */ + } + + // Normalize old versions 1.4 to 1.04 for proper sorting + a = [aA[0], ('0' + aA[1]).slice(-2)].join(""); + b = [aB[0], ('0' + aB[1]).slice(-2)].join(""); + + // Sort descending + return parseInt(b, 10) - parseInt(a, 10); + }); + + aKeys.forEach((sKey) => { + oSorted[sKey] = oList[sKey]; + }); + + return oSorted; + }; + + /* Since */ + oListCollection.since = sortList(oListCollection.since); + + /* Deprecated */ + oListCollection.deprecated = sortList(oListCollection.deprecated); + if (!oListCollection.deprecated.noVersion.apis.length) { + delete oListCollection.deprecated.noVersion; + } + + /* Experimental */ + oListCollection.experimental = sortList(oListCollection.experimental); + if (!oListCollection.experimental.noVersion.apis.length) { + delete oListCollection.experimental.noVersion; + } + }) .then(() => Promise.all([ // write deprecated, experimental and since collections in the respective index files writeJSON(targetFileDeprecated, oListCollection.deprecated), writeJSON(targetFileExperimental, oListCollection.experimental), writeJSON(targetFileSince, oListCollection.since) ])) - .catch((err) => { - log.error("**** failed to create API index for libraries:", err); + .catch(err => { + console.error("**** failed to create API index for libraries:", err) throw err; }); @@ -265,6 +409,7 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF } return createOverallIndex(); + } module.exports = process; diff --git a/lib/processors/jsdoc/lib/transform-apijson-for-sdk.js b/lib/processors/jsdoc/lib/transform-apijson-for-sdk.js index 8dfca4bdc..58739116c 100644 --- a/lib/processors/jsdoc/lib/transform-apijson-for-sdk.js +++ b/lib/processors/jsdoc/lib/transform-apijson-for-sdk.js @@ -1,29 +1,57 @@ /* * Node script to preprocess api.json files for use in the UI5 SDKs. * - * (c) Copyright 2009-2018 SAP SE or an SAP affiliate company. + * (c) Copyright 2009-2019 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ -/* eslint max-len: ["warn", 180], no-useless-escape: "off" */ "use strict"; const fs = require("fs"); const cheerio = require("cheerio"); -const path = require("path"); -const log = require("@ui5/logger").getLogger("builder:processors:jsdoc:transform-apijson-for-sdk"); +const path = require('path'); +const log = (function() { + try { + return require("@ui5/logger").getLogger("builder:processors:jsdoc:transform-apijson-for-sdk"); + } catch (error) { + /* eslint-disable no-console */ + return { + info: function info(...msg) { + console.log("[INFO]", ...msg); + }, + error: function error(...msg) { + console.error(...msg); + } + }; + /* eslint-enable no-console */ + } +}()); + +/* + * Transforms the api.json as created by the JSDoc build into a pre-processed api.json file suitable for the SDK. + * + * The pre-processing includes formatting of type references, rewriting of links and other time consuming calculations. + * + * @param {string} sInputFile Path of the original api.json file that should be transformed + * @param {string} sOutputFile Path that the transformed api.json file should should be written to + * @param {string} sLibraryFile Path to the .library file of the library, used to extract further documentation information + * @param {string|string[]} vDependencyAPIFiles Path of folder that contains api.json files of predecessor libs or + * an array of paths of those files + * @returns {Promise} A Promise that resolves after the transformation has been completed + */ +function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles) { -module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { log.info("Transform API index files for sap.ui.documentation"); log.info(" original file: " + sInputFile); log.info(" output file: " + sOutputFile); + log.info(" library file: " + sLibraryFile); + log.info(" dependency dir: " + vDependencyAPIFiles); log.info(""); /** * Transforms api.json file - * - * @param {Object} oChainObject chain object + * @param {object} oChainObject chain object */ - const transformApiJson = function(oChainObject) { + let transformApiJson = function (oChainObject) { function isBuiltInType(type) { return formatters._baseTypes.indexOf(type) >= 0; } @@ -31,7 +59,6 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Heuristically determining if there is a possibility the given input string * to be a UI5 symbol - * * @param {string} sName * @returns {boolean} */ @@ -45,26 +72,93 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { sPattern = sPattern.toLowerCase(); return ( sModuleName === sPattern - || sPattern.match(/\*$/) && sModuleName.indexOf(sPattern.slice(0, -1)) === 0 // simple prefix match - || sPattern.match(/\.\*$/) && - sModuleName === sPattern.slice(0, -2) // directory pattern also matches directory itself + || sPattern.match(/\*$/) && sModuleName.indexOf(sPattern.slice(0,-1)) === 0 // simple prefix match + || sPattern.match(/\.\*$/) && sModuleName === sPattern.slice(0,-2) // directory pattern also matches directory itself ); } + /** + * Pre-processes the symbols list - creating virtual namespace records and attaching children list to namespace + * records. + * @param {object} symbols list + */ + function preProcessSymbols(symbols) { + // Create treeName and modify module names + symbols.forEach(oSymbol => { + let sModuleClearName = oSymbol.name.replace(/^module:/, ""); + oSymbol.displayName = sModuleClearName; + oSymbol.treeName = sModuleClearName.replace(/\//g, "."); + }); + + // Create missing - virtual namespaces + symbols.forEach(oSymbol => { + oSymbol.treeName.split(".").forEach((sPart, i, a) => { + let sName = a.slice(0, (i + 1)).join("."); + + if (!symbols.find(o => o.treeName === sName)) { + symbols.push({ + name: sName, + displayName: sName, + treeName: sName, + lib: oSymbol.lib, + kind: "namespace" + }); + } + }); + }); + + // Discover parents + symbols.forEach(oSymbol => { + let aParent = oSymbol.treeName.split("."), + sParent; + + // Extract parent name + aParent.pop(); + sParent = aParent.join("."); + + // Mark parent + if (symbols.find(({treeName}) => treeName === sParent)) { + oSymbol.parent = sParent; + } + }); + + // Attach children info + symbols.forEach(oSymbol => { + if (oSymbol.parent) { + let oParent = symbols.find(({treeName}) => treeName === oSymbol.parent); + + if (!oParent.nodes) oParent.nodes = []; + oParent.nodes.push({ + name: oSymbol.displayName, + description: formatters._preProcessLinksInTextBlock(oSymbol.description), + href: "#/api/" + encodeURIComponent(oSymbol.name) + }); + } + }); + + // Clean list - keep file size down + symbols.forEach(o => { + delete o.treeName; + delete o.parent; + }); + } + // Transform to object - const oData = JSON.parse(oChainObject.fileData); + let oData = oChainObject.fileData; // Attach default component for the library if available if (oChainObject.defaultComponent) { oData.defaultComponent = oChainObject.defaultComponent; } - // Populate methods.aTreeContent for later use for symbol children if applicable - // NOTE: This will inject missing root sub_namespace entries in oData.symbols array!!! - methods._parseLibraryElements(oData.symbols); + formatters._oOwnLibrary = oData; + + // Pre process symbols + preProcessSymbols(oData.symbols); // Apply formatter's and modify data as needed oData.symbols.forEach((oSymbol) => { + // when the module name starts with the library name, then we apply the default component if (oSymbol.name.indexOf(oData.library) === 0) { oSymbol.component = oChainObject.defaultComponent; @@ -73,7 +167,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Attach symbol specific component if available (special cases) // Note: Last hit wins as there may be a more specific component pattern if (oChainObject.customSymbolComponents) { - Object.keys(oChainObject.customSymbolComponents).forEach((sComponent) => { + Object.keys(oChainObject.customSymbolComponents).forEach(sComponent => { if (matchComponent(oSymbol.name, sComponent)) { oSymbol.component = oChainObject.customSymbolComponents[sComponent]; } @@ -90,19 +184,12 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { formatters._oTopicData = oSymbol; // Format Page Title - oSymbol.title = (oSymbol.abstract ? "abstract " : "") + oSymbol.kind + " " + oSymbol.name; + oSymbol.title = (oSymbol.abstract ? "abstract " : "") + oSymbol.kind + " " + oSymbol.displayName; oSymbol.subTitle = formatters.formatSubtitle(oSymbol.deprecated); - // Symbol children - const aControlChildren = methods._getControlChildren(oSymbol.name); - if (aControlChildren) { - oSymbol.nodes = aControlChildren; - methods._addChildrenDescription(oData.symbols, oSymbol.nodes); - } - // Constructor if (oSymbol.constructor) { - const oConstructor = oSymbol.constructor; + let oConstructor = oSymbol.constructor; // Description if (oConstructor.description) { @@ -110,7 +197,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { } // References - methods.modifyReferences(oSymbol); + formatters.modifyReferences(oSymbol, true); // Examples if (oConstructor.examples) { @@ -134,12 +221,13 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { if (oConstructor.parameters) { oConstructor.parameters = methods.buildConstructorParameters(oConstructor.parameters); - const aParameters = oConstructor.parameters; - aParameters.forEach((oParameter) => { + let aParameters = oConstructor.parameters; + aParameters.forEach(oParameter => { + // Types oParameter.types = []; if (oParameter.type) { - const aTypes = oParameter.type.split("|"); + let aTypes = oParameter.type.split("|"); for (let i = 0; i < aTypes.length; i++) { oParameter.types.push({ @@ -159,12 +247,14 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { if (oParameter.description) { oParameter.description = formatters.formatDescription(oParameter.description); } - }); + + }) } // Throws if (oConstructor.throws) { - oConstructor.throws.forEach((oThrows) => { + oConstructor.throws.forEach(oThrows => { + // Description if (oThrows.description) { oThrows.description = formatters.formatDescription(oThrows.description); @@ -174,14 +264,14 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { if (oThrows.type) { oThrows.linkEnabled = formatters.formatExceptionLink(oThrows.type); } + }); } } // Description if (oSymbol.description) { - oSymbol.description = - formatters.formatOverviewDescription(oSymbol.description, oSymbol.constructor.references); + oSymbol.description = formatters.formatOverviewDescription(oSymbol.description, oSymbol.constructor.references); } // Deprecated @@ -194,6 +284,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Properties if (oSymbol.properties) { oSymbol.properties.forEach((oProperty) => { + // Name oProperty.name = formatters.formatEntityName(oProperty.name, oSymbol.name, oProperty.static); @@ -206,28 +297,31 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { } // Link Enabled - if (!isBuiltInType(oProperty.type)) { + if (oSymbol.kind !== "enum" && !isBuiltInType(oProperty.type) && possibleUI5Symbol(oProperty.type)) { oProperty.linkEnabled = true; + oProperty.href = "#/api/" + oProperty.type.replace("[]", ""); } // Keep file size in check if (oProperty.static) { delete oProperty.static; } - if (oProperty.type) { - delete oProperty.type; + + if (oSymbol.kind === "enum" || oProperty.type === "undefined") { + delete oProperty.type; } + }); } // UI5 Metadata if (oSymbol["ui5-metadata"]) { - const oMeta = oSymbol["ui5-metadata"]; + let oMeta = oSymbol["ui5-metadata"]; // Properties if (oMeta.properties) { // Sort - oMeta.properties.sort(function(a, b) { + oMeta.properties.sort(function (a, b) { if (a.name < b.name) { return -1; } else if (a.name > b.name) { @@ -243,8 +337,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { oProperty.name = formatters.formatEntityName(oProperty.name, oSymbol.name, oProperty.static); // Description - oProperty.description = - formatters.formatDescriptionSince(oProperty.description, oProperty.since); + oProperty.description = formatters.formatDescriptionSince(oProperty.description, oProperty.since); // Link Enabled if (!isBuiltInType(oProperty.type)) { @@ -268,7 +361,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Aggregations if (oMeta.aggregations) { // Sort - oMeta.aggregations.sort(function(a, b) { + oMeta.aggregations.sort(function (a, b) { if (a.name < b.name) { return -1; } else if (a.name > b.name) { @@ -302,7 +395,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { if (oMeta.associations) { // Sort - oMeta.associations.sort(function(a, b) { + oMeta.associations.sort(function (a, b) { if (a.name < b.name) { return -1; } else if (a.name > b.name) { @@ -337,7 +430,8 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Special Settings if (oMeta.specialSettings) { - oMeta.specialSettings.forEach((oSetting) => { + oMeta.specialSettings.forEach(oSetting => { + // Link Enabled if (!isBuiltInType(oSetting.type)) { oSetting.linkEnabled = true; @@ -350,12 +444,14 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { } else { oSetting.description = formatters.formatDescription(oSetting.description); } + }); } // Annotations if (oMeta.annotations) { - oMeta.annotations.forEach((oAnnotation) => { + oMeta.annotations.forEach(oAnnotation => { + // Description oAnnotation.description = formatters.formatAnnotationDescription(oAnnotation.description, oAnnotation.since); @@ -369,15 +465,19 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Applies to oAnnotation.appliesTo = formatters.formatAnnotationTarget(oAnnotation.appliesTo); + }); } + } if (oSymbol.events) { + // Pre-process events methods.buildEventsModel(oSymbol.events); - oSymbol.events.forEach((oEvent) => { + oSymbol.events.forEach(oEvent => { + // Description if (oEvent.description) { oEvent.description = formatters.formatDescriptionSince(oEvent.description, oEvent.since); @@ -391,7 +491,8 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Parameters if (oEvent.parameters && Array.isArray(oEvent.parameters)) { - oEvent.parameters.forEach((oParameter) => { + oEvent.parameters.forEach(oParameter => { + // Link Enabled if (!isBuiltInType(oParameter.type)) { oParameter.linkEnabled = true; @@ -404,29 +505,40 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { } else { oParameter.description = formatters.formatDescription(oParameter.description); } + }); } + }); + } // Methods if (oSymbol.methods) { + // Pre-process methods methods.buildMethodsModel(oSymbol.methods); - oSymbol.methods.forEach((oMethod) => { - // Name + oSymbol.methods.forEach(oMethod => { + + // Name and link if (oMethod.name) { oMethod.name = formatters.formatEntityName(oMethod.name, oSymbol.name, oMethod.static); + + // Link + oMethod.href = "#/api/" + encodeURIComponent(oSymbol.name) + + "/methods/" + encodeURIComponent(oMethod.name); } + formatters.formatReferencesInDescription(oMethod); + // Description if (oMethod.description) { oMethod.description = formatters.formatDescription(oMethod.description); } // Examples - oMethod.examples && oMethod.examples.forEach((oExample) => { + oMethod.examples && oMethod.examples.forEach(oExample => { oExample = formatters.formatExample(oExample.caption, oExample.text); }); @@ -441,15 +553,18 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Parameters if (oMethod.parameters) { - oMethod.parameters.forEach((oParameter) => { + oMethod.parameters.forEach(oParameter => { + // Types if (oParameter.types) { - oParameter.types.forEach((oType) => { + oParameter.types.forEach(oType => { + // Link Enabled if (!isBuiltInType(oType.value) && possibleUI5Symbol(oType.value)) { oType.linkEnabled = true; oType.href = "#/api/" + oType.value.replace("[]", ""); } + }); } @@ -463,28 +578,35 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { } else { oParameter.description = formatters.formatDescription(oParameter.description); } + }); } // Return value if (oMethod.returnValue) { + // Description oMethod.returnValue.description = formatters.formatDescription(oMethod.returnValue.description); // Types if (oMethod.returnValue.types) { - oMethod.returnValue.types.forEach((oType) => { + oMethod.returnValue.types.forEach(oType => { + // Link Enabled if (!isBuiltInType(oType.value)) { + oType.href = "#/api/" + encodeURIComponent(oType.value.replace("[]", "")); oType.linkEnabled = true; } + }); } + } // Throws if (oMethod.throws) { - oMethod.throws.forEach((oThrows) => { + oMethod.throws.forEach(oThrows => { + // Description if (oThrows.description) { oThrows.description = formatters.formatDescription(oThrows.description); @@ -494,6 +616,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { if (oThrows.type) { oThrows.linkEnabled = formatters.formatExceptionLink(oThrows.type); } + }); } @@ -510,9 +633,41 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { delete oExample.text; } }); + } + + }); } + + // Formatting namespaces, functions and enums, which may contain examples + // or references with links to process them similar to methods and constructors of classes + + if (oSymbol.kind !== "class") { + + if (oSymbol.examples) { + oSymbol.examples.forEach((oExample) => { + oExample.data = formatters.formatExample(oExample.caption, oExample.text); + // Keep file size in check + if (oExample.caption) { + delete oExample.caption; + } + if (oExample.text) { + delete oExample.text; + } + }); + } + + if (oSymbol.references) { + formatters.modifyReferences(oSymbol); + formatters.formatReferencesInDescription(oSymbol); + } + + // Description + if (oSymbol.description) { + oSymbol.description = formatters.formatDescription(oSymbol.description); + } + } }); oChainObject.parsedData = oData; @@ -520,13 +675,70 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { return oChainObject; }; + function getDependencyLibraryFilesList(oChainObject) { + // if vDependencyAPIFiles is an array, it contains the file paths of api.json files + if ( Array.isArray(vDependencyAPIFiles) ) { + return oChainObject; + } + + // otherwise, it names a directory that has to be scanned for the files + return new Promise(oResolve => { + fs.readdir(vDependencyAPIFiles, function (oError, aItems) { + if (!oError && aItems && aItems.length) { + let aFiles = []; + aItems.forEach(sItem => { + aFiles.push(path.join(vDependencyAPIFiles, sItem)); + }) + oChainObject.aDependentLibraryFiles = aFiles; + } + oResolve(oChainObject); // We don't fail if no dependency library files are available + }); + }); + } + + function loadDependencyLibraryFiles (oChainObject) { + if (!oChainObject.aDependentLibraryFiles) { + return oChainObject; + } + let aPromises = []; + oChainObject.aDependentLibraryFiles.forEach(sFile => { + aPromises.push(new Promise(oResolve => { + fs.readFile(sFile, 'utf8', (oError, oData) => { + oResolve(oError ? false : oData); + }); + })); + }); + return Promise.all(aPromises).then(aValues => { + let oDependentAPIs = {}; + + aValues.forEach(sData => { + let oData; + + try { + oData = JSON.parse(sData); + } catch (e) { + // Silence possible dependency library invalid json errors as they are not critical + // and we should not fail BCP: 1870560058 + } + + // OpenUI5 build specific - own library can be listed as dependency library also. + // In this case we don't add it to the dependency list to skip double iteration. + if (oData && oChainObject.fileData.library !== oData.library) { + oDependentAPIs[oData.library] = oData.symbols; + } + }); + + oChainObject.oDependentAPIs = oDependentAPIs; + return oChainObject; + }) + } + /** * Create api.json from parsed data - * - * @param {Object} oChainObject chain object + * @param oChainObject chain object */ function createApiRefApiJson(oChainObject) { - const sOutputDir = path.dirname(oChainObject.outputFile); + let sOutputDir = path.dirname(oChainObject.outputFile); // Create dir if it does not exist if (!fs.existsSync(sOutputDir)) { @@ -534,19 +746,17 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { } // Write result to file - fs.writeFileSync(oChainObject.outputFile, - JSON.stringify(oChainObject.parsedData) /* Transform back to string */, "utf8"); + fs.writeFileSync(oChainObject.outputFile, JSON.stringify(oChainObject.parsedData) /* Transform back to string */, 'utf8'); } /** * Load .library file - * - * @param {Object} oChainObject chain return object + * @param oChainObject chain return object * @returns {Promise} library file promise */ function getLibraryPromise(oChainObject) { return new Promise(function(oResolve) { - fs.readFile(oChainObject.libraryFile, "utf8", (oError, oData) => { + fs.readFile(oChainObject.libraryFile, 'utf8', (oError, oData) => { oChainObject.libraryFileData = oData; oResolve(oChainObject); }); @@ -555,15 +765,14 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Extracts components list and docuindex.json relative path from .library file data - * - * @param {Object} oChainObject chain object - * @returns {Object} chain object + * @param {object} oChainObject chain object + * @returns {object} chain object */ function extractComponentAndDocuindexUrl(oChainObject) { oChainObject.modules = []; if (oChainObject.libraryFileData) { - const $ = cheerio.load(oChainObject.libraryFileData, { + let $ = cheerio.load(oChainObject.libraryFileData, { ignoreWhitespace: true, xmlMode: true, lowerCaseTags: false @@ -574,12 +783,13 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Extract components $("ownership > component").each((i, oComponent) => { + if (oComponent.children) { if (oComponent.children.length === 1) { oChainObject.defaultComponent = $(oComponent).text(); } else { - const sCurrentComponentName = $(oComponent).find("name").text(); - const aCurrentModules = []; + let sCurrentComponentName = $(oComponent).find("name").text(); + let aCurrentModules = []; $(oComponent).find("module").each((a, oC) => { aCurrentModules.push($(oC).text().replace(/\//g, ".")); }); @@ -590,7 +800,9 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { }); } } + }); + } return oChainObject; @@ -599,16 +811,15 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Adds to the passed object custom symbol component map generated from the extracted components list * to be easily searchable later - * - * @param {Object} oChainObject chain object - * @returns {Object} chain object + * @param {object} oChainObject chain object + * @returns {object} chain object */ function flattenComponents(oChainObject) { if (oChainObject.modules && oChainObject.modules.length > 0) { oChainObject.customSymbolComponents = {}; - oChainObject.modules.forEach((oComponent) => { - const sCurrentComponent = oComponent.componentName; - oComponent.modules.forEach((sModule) => { + oChainObject.modules.forEach(oComponent => { + let sCurrentComponent = oComponent.componentName; + oComponent.modules.forEach(sModule => { oChainObject.customSymbolComponents[sModule] = sCurrentComponent; }); }); @@ -619,9 +830,8 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Adds to the passed object array with entities which have explored samples - * - * @param {Object} oChainObject chain object - * @returns {Object} chain object + * @param {object} oChainObject chain object + * @returns {object} chain object */ function extractSamplesFromDocuIndex(oChainObject) { // If we have not extracted docuPath we return early @@ -634,12 +844,12 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Normalize path to resolve relative path sPath = path.normalize(sPath); - fs.readFile(sPath, "utf8", (oError, oFileData) => { + fs.readFile(sPath, 'utf8', (oError, oFileData) => { if (!oError) { oFileData = JSON.parse(oFileData); if (oFileData.explored && oFileData.explored.entities && oFileData.explored.entities.length > 0) { oChainObject.entitiesWithSamples = []; - oFileData.explored.entities.forEach((oEntity) => { + oFileData.explored.entities.forEach(oEntity => { oChainObject.entitiesWithSamples.push(oEntity.id); }); } @@ -647,22 +857,22 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // We aways resolve as this data is not mandatory oResolve(oChainObject); }); + }); } /** * Load api.json file - * - * @param {Object} oChainObject chain object - * @returns {Object} chain object + * @param {object} oChainObject chain object + * @returns {object} chain object */ function getAPIJSONPromise(oChainObject) { return new Promise(function(oResolve, oReject) { - fs.readFile(sInputFile, "utf8", (oError, sFileData) => { + fs.readFile(sInputFile, 'utf8', (oError, sFileData) => { if (oError) { oReject(oError); } else { - oChainObject.fileData = sFileData; + oChainObject.fileData = JSON.parse(sFileData); oResolve(oChainObject); } }); @@ -670,12 +880,12 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { } /* - * ================================================================================================================ - * IMPORTANT NOTE: Formatter code is a copy from APIDetail.controller.js with a very little modification and - * mocking and code can be significantly improved - * ================================================================================================================ + * ===================================================================================================================== + * IMPORTANT NOTE: Formatter code is a copy from APIDetail.controller.js with a very little modification and mocking and + * code can be significantly improved + * ===================================================================================================================== */ - const formatters = { + let formatters = { _sTopicId: "", _oTopicData: {}, @@ -721,17 +931,16 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { "TouchList", "undefined" ], - ANNOTATIONS_LINK: "http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part3-csdl.html", - ANNOTATIONS_NAMESPACE_LINK: "http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/vocabularies/", + ANNOTATIONS_LINK: 'http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part3-csdl.html', + ANNOTATIONS_NAMESPACE_LINK: 'http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/vocabularies/', /** * Adds "deprecated" information if such exists to the header area - * - * @param {Object} deprecated - object containing information about deprecation + * @param deprecated - object containing information about deprecation * @returns {string} - the deprecated text to display */ - formatSubtitle: function(deprecated) { - let result = ""; + formatSubtitle: function (deprecated) { + var result = ""; if (deprecated) { result += "Deprecated in version: " + deprecated.since; @@ -742,16 +951,15 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Formats the target and applies to texts of annotations - * - * @param {Array} target - the array of texts to be formatted - * @returns {string} - the formatted text + * @param target - the array of texts to be formatted + * @returns string - the formatted text */ - formatAnnotationTarget: function(target) { - let result = ""; + formatAnnotationTarget: function (target) { + var result = ""; if (target) { - target.forEach(function(element) { - result += element + "
    "; + target.forEach(function (element) { + result += element + '
    '; }); } @@ -761,18 +969,15 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Formats the namespace of annotations - * - * @param {string} namespace - the namespace to be formatted - * @returns {string} - the formatted text + * @param namespace - the namespace to be formatted + * @returns string - the formatted text */ - formatAnnotationNamespace: function(namespace) { - let result; - - - const aNamespaceParts = namespace.split("."); + formatAnnotationNamespace: function (namespace) { + var result, + aNamespaceParts = namespace.split("."); if (aNamespaceParts[0] === "Org" && aNamespaceParts[1] === "OData") { - result = "" + namespace + ""; + result = '' + namespace + ''; } else { result = namespace; } @@ -783,56 +988,55 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Formats the description of annotations - * - * @param {string} description - the description of the annotation - * @param {string} since - the since version information of the annotation - * @returns {string} - the formatted description + * @param description - the description of the annotation + * @param since - the since version information of the annotation + * @returns string - the formatted description */ - formatAnnotationDescription: function(description, since) { - let result = description || ""; + formatAnnotationDescription: function (description, since) { + var result = description || ""; - result += "
    For more information, see " + "OData v4 Annotations"; + result += '
    For more information, see ' + this.handleExternalUrl(this.ANNOTATIONS_LINK, "OData v4 Annotations"); if (since) { - result += "

    Since: " + since + "."; + result += '

    Since: ' + since + '.'; } result = this._preProcessLinksInTextBlock(result); return result; }, - formatExceptionLink: function(linkText) { - linkText = linkText || ""; - return linkText.indexOf("sap.ui.") !== -1; + formatExceptionLink: function (linkText) { + linkText = linkText || ''; + return linkText.indexOf('sap.ui.') !== -1; }, - formatMethodCode: function(sName, aParams, aReturnValue) { - let result = "
    " + sName + "(";
    +		formatMethodCode: function (sName, aParams, aReturnValue) {
    +			var result = '
    ' + sName + '(';
     
     			if (aParams && aParams.length > 0) {
     				/* We consider only root level parameters so we get rid of all that are not on the root level */
    -				aParams = aParams.filter((oElem) => {
    +				aParams = aParams.filter(oElem => {
     					return oElem.depth === undefined;
     				});
    -				aParams.forEach(function(element, index, array) {
    +				aParams.forEach(function (element, index, array) {
     					result += element.name;
     
     					if (element.optional) {
    -						result += "?";
    +						result += '?';
     					}
     
     					if (index < array.length - 1) {
    -						result += ", ";
    +						result += ', ';
     					}
     				});
     			}
     
    -			result += ") : ";
    +			result += ') : ';
     
     			if (aReturnValue) {
     				result += aReturnValue.type;
     			} else {
    -				result += "void";
    +				result += 'void';
     			}
     
     			result += "
    "; @@ -842,38 +1046,35 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Formats method deprecation message and pre-process jsDoc link and code blocks - * * @param {string} sSince since text * @param {string} sDescription deprecation description text * @returns {string} formatted deprecation message */ - formatMethodDeprecated: function(sSince, sDescription) { + formatMethodDeprecated: function (sSince, sDescription) { return this.formatDeprecated(sSince, sDescription, "methods"); }, /** * Formats event deprecation message and pre-process jsDoc link and code blocks - * * @param {string} sSince since text * @param {string} sDescription deprecation description text * @returns {string} formatted deprecation message */ - formatEventDeprecated: function(sSince, sDescription) { + formatEventDeprecated: function (sSince, sDescription) { return this.formatDeprecated(sSince, sDescription, "events"); }, /** * Formats the description of control properties - * - * @param {string} description - the description of the property - * @param {string} since - the since version information of the property - * @returns {string} - the formatted description + * @param description - the description of the property + * @param since - the since version information of the property + * @returns string - the formatted description */ - formatDescriptionSince: function(description, since) { - let result = description || ""; + formatDescriptionSince: function (description, since) { + var result = description || ""; if (since) { - result += "

    Since: " + since + "."; + result += '

    Since: ' + since + '.'; } result = this._preProcessLinksInTextBlock(result); @@ -882,64 +1083,62 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Formats the default value of the property as a string. - * - * @param {string} defaultValue - the default value of the property - * @returns {string} - The default value of the property formatted as a string. + * @param defaultValue - the default value of the property + * @returns string - The default value of the property formatted as a string. */ - formatDefaultValue: function(defaultValue) { - let sReturn; + formatDefaultValue: function (defaultValue) { + 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; + return Array.isArray(sReturn) ? sReturn.join(', ') : sReturn; }, /** * Formats the constructor of the class - * - * @param {string} name - * @param {Array} params - * @returns {string} - The code needed to create an object of that class + * @param name + * @param params + * @returns string - The code needed to create an object of that class */ - formatConstructor: function(name, params) { - let result = "
    new ";
    +		formatConstructor: function (name, params) {
    +			var result = '
    new ';
     
     			if (name) {
    -				result += name + "(";
    +				result += name + '(';
     			}
     
     			if (params) {
    -				params.forEach(function(element, index, array) {
    +				params.forEach(function (element, index, array) {
     					result += element.name;
     
     					if (element.optional) {
    -						result += "?";
    +						result += '?';
     					}
     
     					if (index < array.length - 1) {
    -						result += ", ";
    +						result += ', ';
     					}
     				});
     			}
     
     			if (name) {
    -				result += ")
    "; + result += ')
    '; } return result; }, - formatExample: function(sCaption, sText) { + formatExample: function (sCaption, sText) { return this.formatDescription( ["Example: ", sCaption, @@ -951,21 +1150,21 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Formats the name of a property or a method depending on if it's static or not - * - * @param {string} sName - Name - * @param {string} sClassName - Name of the class - * @param {boolean} bStatic - If it's static + * @param sName {string} - Name + * @param sClassName {string} - Name of the class + * @param bStatic {boolean} - If it's static * @returns {string} - Formatted name */ - formatEntityName: function(sName, sClassName, bStatic) { + formatEntityName: function (sName, sClassName, bStatic) { return (bStatic === true) ? sClassName + "." + sName : sName; }, - getJSDocUtil: function() { - const rEscapeRegExp = /[[\]{}()*+?.\\^$|]/g; + JSDocUtil: function () { + + var rEscapeRegExp = /[[\]{}()*+?.\\^$|]/g; // Local mocked methods - const escapeRegExp = function escapeRegExp(sString) { + var escapeRegExp = function escapeRegExp(sString) { return sString.replace(rEscapeRegExp, "\\$&"); }; @@ -974,15 +1173,13 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { } function format(src, options) { + options = options || {}; - const beforeParagraph = options.beforeParagraph === undefined ? "

    " : options.beforeParagraph; - const afterParagraph = options.afterParagraph === undefined ? "

    " : options.afterParagraph; - const beforeFirstParagraph = - options.beforeFirstParagraph === undefined ? beforeParagraph : options.beforeFirstParagraph; - const afterLastParagraph = - options.afterLastParagraph === undefined ? afterParagraph : options.afterLastParagraph; - let linkFormatter = - typeof options.linkFormatter === "function" ? options.linkFormatter : defaultLinkFormatter; + var beforeParagraph = options.beforeParagraph === undefined ? '

    ' : options.beforeParagraph; + var afterParagraph = options.afterParagraph === undefined ? '

    ' : options.afterParagraph; + var beforeFirstParagraph = options.beforeFirstParagraph === undefined ? beforeParagraph : options.beforeFirstParagraph; + var afterLastParagraph = options.afterLastParagraph === undefined ? afterParagraph : options.afterLastParagraph; + var linkFormatter = typeof options.linkFormatter === 'function' ? options.linkFormatter : defaultLinkFormatter; /* * regexp to recognize important places in the text @@ -997,42 +1194,40 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { * group 7: an empty line which implicitly starts a new paragraph * * [--
     block -] [---- some header ----] [---- an inline [@link ...} tag ----] [---------- an empty line ---------]  */
    -				const r = /(
    )|(<\/pre>)|()|(<\/h[\d+]>)|\{@link\s+([^}\s]+)(?:\s+([^\}]*))?\}|((?:\r\n|\r|\n)[ \t]*(?:\r\n|\r|\n))/gi;
    -				let inpre = false;
    +				var r = /(
    )|(<\/pre>)|()|(<\/h[\d+]>)|\{@link\s+([^}\s]+)(?:\s+([^\}]*))?\}|((?:\r\n|\r|\n)[ \t]*(?:\r\n|\r|\n))/gi;
    +				var inpre = false;
     
    -				src = src || "";
    +				src = src || '';
     				linkFormatter = linkFormatter || defaultLinkFormatter;
     
    -				src = beforeFirstParagraph +
    -					src.replace(r, function(match, pre, endpre, header, endheader, linkTarget, linkText, emptyline) {
    -						if ( pre ) {
    -							inpre = true;
    -						} else if ( endpre ) {
    -							inpre = false;
    -						} else if ( header ) {
    -							if ( !inpre ) {
    -								return afterParagraph + match;
    -							}
    -						} else if ( endheader ) {
    -							if ( !inpre ) {
    -								return match + beforeParagraph;
    -							}
    -						} else if ( emptyline ) {
    -							if ( !inpre ) {
    -								return afterParagraph + beforeParagraph;
    -							}
    -						} else if ( linkTarget ) {
    -							if ( !inpre ) {
    -								return linkFormatter(linkTarget, linkText);
    -							}
    +				src = beforeFirstParagraph + src.replace(r, function(match, pre, endpre, header, endheader, linkTarget, linkText, emptyline) {
    +					if ( pre ) {
    +						inpre = true;
    +					} else if ( endpre ) {
    +						inpre = false;
    +					} else if ( header ) {
    +						if ( !inpre ) {
    +							return afterParagraph + match;
    +						}
    +					} else if ( endheader ) {
    +						if ( !inpre ) {
    +							return match + beforeParagraph;
    +						}
    +					} else if ( emptyline ) {
    +						if ( !inpre ) {
    +							return afterParagraph + beforeParagraph;
    +						}
    +					} else if ( linkTarget ) {
    +						if ( !inpre ) {
    +							return linkFormatter(linkTarget, linkText);
     						}
    -						return match;
    -					}) + afterLastParagraph;
    +					}
    +					return match;
    +				}) + afterLastParagraph;
     
     				// remove empty paragraphs
     				if (beforeParagraph !== "" && afterParagraph !== "") {
    -					src = src.replace(
    -						new RegExp(escapeRegExp(beforeParagraph) + "\\s*" + escapeRegExp(afterParagraph), "g"), "");
    +					src = src.replace(new RegExp(escapeRegExp(beforeParagraph) + "\\s*" + escapeRegExp(afterParagraph), "g"), "");
     				}
     
     				return src;
    @@ -1041,185 +1236,369 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
     			return {
     				formatTextBlock: format
     			};
    +
     		},
     
    -		/*
    -		 * Pre-process links in text block
    -   		 *
    -		 * @param {string} sText text block
    -		 * @returns {string} processed text block
    -		 * @private
    -		 */
    -		_preProcessLinksInTextBlock: function(sText, bSkipParagraphs) {
    -			const topicsData = this._oTopicData;
    -			// this.getModel('topics').oData,
    +		handleExternalUrl: function (sTarget, sText) {
    +			// Check if the external domain is SAP hosted
    +			let bSAPHosted = /^https?:\/\/(?:www.)?[\w.]*(?:sap|hana\.ondemand|sapfioritrial)\.com/.test(sTarget);
     
    -			const topicName = topicsData.name || "";
    +		return `${sText}
    +`;
    +		},
     
    +	/**
    +	 * Discover possible target type by looking at symbols from own and depending libraries
    +	 * @param {string} target
    +	 * @param {object} self
    +	 * @param {object} ownLibrary
    +	 * @param {object} dependencyLibs
    +	 * @param {boolean} module
    +	 * @returns {string}
    +	 */
    +	createLinkFromTargetType: function ({className, methodName, target, self, ownLibrary, dependencyLibs, text}) {
    +		let sResult;
     
    -			const topicMethods = topicsData.methods || [];
    +		function searchInSymbol(oSymbol) {
    +			function findProperty(oEntity, sName, sTarget) {
    +				if (!oEntity || !oEntity.properties) {
    +					return;
    +				}
    +				return oEntity.properties.find(({name}) => name === sName || name === sTarget);
    +			}
     
    +			function findMethod(oEntity, sName, sTarget) {
    +				if (!oEntity || !oEntity.methods) {
    +					return;
    +				}
    +				return oEntity.methods.find(({name}) => name === sName || name === sTarget);
    +			}
     
    -			const oOptions = {
    -				linkFormatter: function(target, text) {
    -					let iHashIndex;
    -					// indexOf('#')
    +			function findEvent(oEntity, sName) {
    +				if (!oEntity || !oEntity.events) {
    +					return;
    +				}
    +				return oEntity.events.find(({name}) => name === sName);
    +			}
     
    -					let iHashDotIndex;
    -					// indexOf('#.')
    +			if (oSymbol.name === target) {
    +				sResult = this.createLink({
    +					name: oSymbol.name,
    +					text: text
    +				});
    +				return true;
    +			}
    +			if (oSymbol.name === className) {
    +				let oProperty = findProperty(oSymbol, methodName, target);
    +				if (oProperty) {
    +					sResult = this.createLink({
    +						name: oProperty.name,
    +						text: text
    +					});
    +					return true;
    +				}
    +				let oMethod = findMethod(oSymbol, methodName, target);
    +				if (oMethod) {
    +					sResult = this.createLink({
    +						name: oMethod.static ? target : oMethod.name,
    +						type: "methods",
    +						text: text,
    +						className: className
    +					});
    +					return true;
    +				}
     
    -					let iHashEventIndex;
    -					// indexOf('#event:')
    +				let oEvent = findEvent(oSymbol, methodName);
    +				if (oEvent) {
    +					sResult = this.createLink({
    +						name: oEvent.name,
    +						type: "events",
    +						text: text,
    +						className: className
    +					});
    +					return true;
    +				}
    +			}
     
    -					let aMatched;
    +			return false;
    +		}
     
    +		// Self link
    +		if (self.name === target) {
    +			return this.createLink({
    +				name: target,
    +				text: text
    +			});
    +		}
     
    -					let sRoute = "api";
    +		// Own methods search
    +		if (self.name === className && self.methods) {
    +			let oResult = self.methods.find(({name}) => name === methodName);
    +			if (oResult) {
    +				return this.createLink({
    +					name: oResult.static ? [self.name, oResult.name].join(".") : oResult.name,
    +					type: "methods",
    +					className: className,
    +					text: text,
    +					local: true
    +				});
    +			}
    +		}
     
    +		// Local library symbols
    +		ownLibrary.symbols.find(oSymbol => {
    +			return searchInSymbol.call(this, oSymbol);
    +		});
     
    -					let sTargetBase;
    +		if (sResult) return sResult;
     
    +		// Dependent library symbols
    +		dependencyLibs && Object.keys(dependencyLibs).find(sLib => {
    +			if (sLib === target) {
    +				sResult = this.createLink({
    +					name: sLib,
    +					text: text
    +				});
    +				return true;
    +			}
    +			return dependencyLibs[sLib].find(oSymbol => {
    +				return searchInSymbol.call(this, oSymbol);
    +			});
    +		});
     
    -					let sScrollHandlerClass = "scrollToMethod";
    +		return sResult;
    +	},
     
    +	/**
    +	 * Creates a html link
    +	 * @param {string} name
    +	 * @param {string} type
    +	 * @param {string} className
    +	 * @param {string=name} text by default if no text is provided the name will be used
    +	 * @param {boolean=false} local
    +	 * @param {string=""} hrefAppend
    +	 * @returns {string} link
    +	 */
    +	createLink: function ({name, type, className, text=name, local=false, hrefAppend=""}) {
    +		let sLink;
     
    -					let sEntityName;
    +		// handling module's
    +		if (className !== undefined && (/^module:/.test(name) || /^module:/.test(className))) {
    +			name = name.replace(/^module:/, "");
    +		}
     
    +		name = encodeURIComponent(name);
    +		className = encodeURIComponent(className);
     
    -					let sLink;
    +		// Build the link
    + 		sLink = type ? `${className}/${type}/${name}` : name;
     
    -					text = text || target; // keep the full target in the fallback text
    +		if (hrefAppend) {
    +			sLink += hrefAppend;
    +		}
     
    -					// If the link has a protocol, do not modify, but open in a new window
    -					if (target.match("://")) {
    -						return "" + text + "";
    -					}
    +		if (local) {
    +			return `${text}`;
    +		}
     
    -					target = target.trim().replace(/\.prototype\./g, "#");
    +		return `${text}`;
    +	},
     
    -					// Link matches the pattern of an static extend method sap.ui.core.Control.extend
    -					// BCP: 1780477951
    -					const aMatch = target.match(/^([a-zA-Z0-9\.]*)\.extend$/);
    -					if (aMatch) {
    -						// In this case the link should be a link to a static method of the control like for example
    -						// #/api/sap.ui.core.Control/methods/sap.ui.core.Control.extend
    -						target = aMatch[1] + "/methods/" + aMatch[0];
    -						sEntityName = aMatch[1];
    -						sScrollHandlerClass = false; // No scroll handler needed
    -					} else {
    -						iHashIndex = target.indexOf("#");
    -						iHashDotIndex = target.indexOf("#.");
    -						iHashEventIndex = target.indexOf("#event:");
    +		/**
    +		 * Pre-process links in text block
    +		 * @param {string} sText text block
    +	 * @param {boolean} bSkipParagraphs skip paragraphs
    +		 * @returns {string} processed text block
    +		 * @private
    +		 */
    +		_preProcessLinksInTextBlock: function (sText, bSkipParagraphs) {
    +		let oSelf = this._oTopicData,
    +			oOwnLibrary = this._oOwnLibrary,
    +			oDependencyLibs = oChainObject.oDependentAPIs,
    +				oOptions = {
    +				linkFormatter: function (sTarget, sText) {
    +					let aMatch,
    +						aTarget;
    +
    +					// keep the full target in the fallback text
    +					sText = sText || sTarget;
    +
    +						// If the link has a protocol, do not modify, but open in a new window
    +					if (/:\/\//.test(sTarget)) {
    +						return this.handleExternalUrl(sTarget, sText);
    +						}
     
    -						if (iHashIndex === -1) {
    -							const lastDotIndex = target.lastIndexOf(".");
    +					// topic:xxx Topic
    +					aMatch = sTarget.match(/^topic:(\w{32})$/);
    +					if (aMatch) {
    +						return '' + sText + '';
    +					}
     
    +					// sap.x.Xxx.prototype.xxx - In case of prototype we have a link to method
    +					aMatch = sTarget.match(/([a-zA-Z0-9.$_]+?)\.prototype\.([a-zA-Z0-9.$_]+)$/);
    +						if (aMatch) {
    +						return this.createLink({
    +							name: aMatch[2],
    +							type: "methods",
    +							className: aMatch[1],
    +							text: sText
    +						});
    +					}
     
    -							const entityName = sEntityName = target.substring(lastDotIndex + 1);
    +					// Heuristics: Extend is always a static method
    +					// sap.x.Xxx.extend
    +					// module:sap/x/Xxx.extend
    +					aMatch = sTarget.match(/^(module:)?([a-zA-Z0-9.$_\/]+?)\.extend$/);
    +					if (aMatch) {
    +						let [, sModule, sClass] = aMatch;
    +						return this.createLink({
    +							name: sTarget.replace(/^module:/, ""),
    +							type: "methods",
    +							className: (sModule ? sModule : "") + sClass,
    +							text: sText
    +						});
    +					}
     
    +					// Constructor links are handled in special manner by the SDK
    +					// sap.x.Xxx.constructor
    +					// sap.x.Xxx#constructor
    +					// module:sap/x/Xxx.constructor
    +					// #constructor
    +					aMatch = sTarget.match(/^(module:)?([a-zA-Z0-9.$_\/]+?)?[\.#]constructor$/i);
    +					if (aMatch) {
    +						let [, sModule, sClass] = aMatch,
    +							sName;
     
    -							const targetMethod = topicMethods.filter(function(method) {
    -								if (method.name === entityName) {
    -									return method;
    -								}
    -							})[0];
    -
    -							if (targetMethod) {
    -								if (targetMethod.static === true) {
    -									sEntityName = target;
    -									// We need to handle links to static methods in a different way if static method is
    -									// a child of the current or a different entity
    -									sTargetBase = target.replace("." + entityName, "");
    -									if (sTargetBase.length > 0 && sTargetBase !== topicName) {
    -										// Different entity
    -										target = sTargetBase + "/methods/" + target;
    -										// We will navigate to a different entity so no scroll is needed
    -										sScrollHandlerClass = false;
    -									} else {
    -										// Current entity
    -										target = topicName + "/methods/" + target;
    -									}
    -								} else {
    -									target = topicName + "/methods/" + entityName;
    -								}
    -							} else {
    -								// Handle links to documentation
    -								aMatched = target.match(/^topic:(\w{32})$/);
    -								if (aMatched) {
    -									target = sEntityName = aMatched[1];
    -									sRoute = "topic";
    -								}
    -							}
    -						}
    +						if (sClass) {
    +							sName = (sModule ? sModule : "") + sClass;
    +						} else {
    +							sName = oSelf.name
    +										}
     
    -						if (iHashDotIndex === 0) {
    -							// clear '#.' from target string
    -							target = target.slice(2);
    +						return this.createLink({
    +							name: sName,
    +							hrefAppend: "/constructor",
    +							text: sText
    +						});
    +										}
     
    -							target = topicName + "/methods/" + topicName + "." + target;
    -						} else if (iHashEventIndex >= 0) {
    -							// format is 'className#event:eventName'  or  '#event:eventName'
    -							let sClassName = target.substring(0, iHashIndex);
    -							target = target.substring(iHashIndex);
    +					// #.setText - local static method
    +					// #setText - local instance method
    +					// #.setText.from - local nested method
    +					aMatch = sTarget.match(/^#(\.)?([a-zA-Z0-9.$_]+)$/);
    +					if (aMatch) {
    +						return this.createLink({
    +							name: aMatch[1] ? `${oSelf.name}.${aMatch[2]}` : aMatch[2],
    +							type: "methods",
    +							className: oSelf.name,
    +							local: true,
    +							text: sText
    +						});
    +					}
     
    -							// clear '#event:' from target string
    -							target = target.slice("#event:".length);
    +					// #event:press - local event
    +					aMatch = sTarget.match(/^#event:([a-zA-Z0-9$_]+)$/);
    +					if (aMatch) {
    +						return this.createLink({
    +							name: aMatch[1],
    +							type: "events",
    +							className: oSelf.name,
    +							local: true,
    +							text: sText
    +						});
    +					}
    +					// Event links
    +					// sap.m.Button#event:press
    +					// sap.m.Button.event:press
    +					// module:sap/m/Button.event:press
    +					// module:sap/m/Button#event:press
    +					aMatch = sTarget.match(/^(module:)?([a-zA-Z0-9.$_\/]+?)[.#]event:([a-zA-Z0-9$_]+)$/);
    +					if (aMatch) {
    +						let [, sModule, sClass, sEvent] = aMatch;
    +						return this.createLink({
    +							name: sEvent,
    +							type: "events",
    +							className: (sModule ? sModule : "") + sClass,
    +							text: sText
    +						});
    +					}
     
    -							if (!sClassName) {
    -								sClassName = topicName; // if no className => event is relative to current topicName
    -								// mark the element as relative link to the events section
    -								sScrollHandlerClass = "scrollToEvent";
    -							}
    +					// sap.m.Button#setText - instance method
    +					// module:sap/m/Button#setText
    +					aMatch = sTarget.match(/^(module:)?([a-zA-Z0-9.$_\/]+)#([a-zA-Z0-9.$_]+)$/);
    +					if (aMatch) {
    +						let [, sModule, sClass, sMethod] = aMatch;
    +						return this.createLink({
    +							name: sMethod,
    +							type: "methods",
    +							className: (sModule ? sModule : "") + sClass,
    +							text: sText
    +						});
    +					}
     
    -							target = sClassName + "/events/" + target;
    -							sEntityName = target;
    -						} else if (iHashIndex === 0) {
    -							// clear '#' from target string
    -							target = target.slice(1);
    -							sEntityName = target;
    -
    -							target = topicName + "/methods/" + target;
    -						} else if (iHashIndex > 0) {
    -							target = target.replace("#", "/methods/");
    -							sEntityName = target;
    -						}
    +					// Unresolved type - try to discover target type
    +					// sap.x.Xxx.xxx
    +					// module:sap/x/Xxx.xxx
    +					if (/^(?:module:)?([a-zA-Z0-9.$_\/]+?)\.([a-zA-Z0-9$_]+)$/.test(sTarget)) {
    +						let [,sClass, sName] = sTarget.match(/^((?:module:)?[a-zA-Z0-9.$_\/]+?)\.([a-zA-Z0-9$_]+)$/),
    +							sResult = this.createLinkFromTargetType({
    +								className: sClass,
    +								methodName: sName,
    +								target: sTarget,
    +								self: oSelf,
    +								ownLibrary: oOwnLibrary,
    +								dependencyLibs: oDependencyLibs,
    +								text: sText
    +							});
    +						if (sResult) {
    +							return sResult;
    +								}
     					}
     
    -					sLink = "= 3) {
    +						let sResult = this.createLinkFromTargetType({
    +							methodName: aTarget.splice(-2).join("."),
    +							className: aTarget.join("."),
    +							target: sTarget,
    +							self: oSelf,
    +							ownLibrary: oOwnLibrary,
    +							dependencyLibs: oDependencyLibs,
    +							text: sText
    +						});
    +						if (sResult) {
    +							return sResult;
    +						}
     					}
     
    -					// TODO: have to check if data-sap-ui-target is really needed here -
    -					//	maybe we can strip some more bites
    -					// from json file size by removing it ...
    -					sLink += "\" target=\"_self\" href=\"#/" + sRoute + "/" + target +
    -							"\" data-sap-ui-target=\"" + sEntityName + "\">" + text + "";
    +					// Possible forward reference - we will treat them as symbol link
    +					return this.createLink({
    +						name: sTarget,
    +						text: sText
    +					});
     
    -					return sLink;
    -				}
    -			};
    +					}.bind(this)
    +				};
     
     			if (bSkipParagraphs) {
     				oOptions.beforeParagraph = "";
     				oOptions.afterParagraph = "";
     			}
     
    -			return this.getJSDocUtil().formatTextBlock(sText, oOptions);
    +			return this.JSDocUtil().formatTextBlock(sText, oOptions);
     		},
     
     		/**
     		 * Formatter for Overview section
    -  		 *
     		 * @param {string} sDescription - Class about description
    -		 * @param {Array} aReferences - References
    +		 * @param {array} aReferences - References
     		 * @returns {string} - formatted text block
     		 */
    -		formatOverviewDescription: function(sDescription, aReferences) {
    -			let iLen;
    -
    -
    -			let i;
    +		formatOverviewDescription: function (sDescription, aReferences) {
    +			var iLen,
    +				i;
     
     			// format references
     			if (aReferences && aReferences.length > 0) {
    @@ -1232,6 +1611,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) {
     						sDescription += "
  • " + aReferences[i] + "
  • "; } else { sDescription += "
  • {@link " + aReferences[i] + "}
  • "; + } } @@ -1244,18 +1624,17 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Formats the description of the property - * - * @param {string} description - the description of the property - * @param {string} deprecatedText - the text explaining this property is deprecated - * @param {string} deprecatedSince - the version when this property was deprecated - * @returns {string} - the formatted description + * @param description - the description of the property + * @param deprecatedText - the text explaining this property is deprecated + * @param deprecatedSince - the version when this property was deprecated + * @returns string - the formatted description */ - formatDescription: function(description, deprecatedText, deprecatedSince) { + formatDescription: function (description, deprecatedText, deprecatedSince) { if (!description && !deprecatedText && !deprecatedSince) { return ""; } - let result = description || ""; + var result = description || ""; if (deprecatedSince || deprecatedText) { // Note: sapUiDocumentationDeprecated - transformed to sapUiDeprecated to keep json file size low @@ -1272,24 +1651,25 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Formats the entity deprecation message and pre-process jsDoc link and code blocks - * * @param {string} sSince since text * @param {string} sDescription deprecation description text * @param {string} sEntityType string representation of entity type * @returns {string} formatted deprecation message */ - formatDeprecated: function(sSince, sDescription, sEntityType) { + formatDeprecated: function (sSince, sDescription, sEntityType) { + var aResult; + // Build deprecation message // Note: there may be no since or no description text available - const aResult = ["Deprecated"]; + aResult = ["Deprecated"]; if (sSince) { aResult.push(" as of version " + sSince); } if (sDescription) { // Evaluate code blocks - Handle ... pattern - sDescription = sDescription.replace(/(\S+)<\/code>/gi, function(sMatch, sCodeEntity) { - return ["", sCodeEntity, ""].join(""); - } + sDescription = sDescription.replace(/(\S+)<\/code>/gi, function (sMatch, sCodeEntity) { + return ['', sCodeEntity, ''].join(""); + } ); // Evaluate links in the deprecation description @@ -1299,63 +1679,26 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { return aResult.join(""); }, - _formatChildDescription: function(description) { - if (description) { - return this._extractFirstSentence(description); - } - }, - - /* Just the first sentence (up to a full stop). Should not break on dotted variable names. */ - _extractFirstSentence: function(desc) { - if ( desc ) { - desc = String(desc).replace(/\s+/g, " "). - replace(/^(<\/?p>||\w+<\/h\d>|\s)+/, ""); - - const match = /([\w\W]+?\.)[^a-z0-9_$]/i.exec(desc); - return match ? match[1] : desc; - } - return ""; - }, - - _sliceSpecialTags: function(descriptionCopy, startSymbol, endSymbol) { - let startIndex; let endIndex; - while (descriptionCopy.indexOf(startSymbol) !== -1 && - descriptionCopy.indexOf(startSymbol) < descriptionCopy.indexOf(".")) { - startIndex = descriptionCopy.indexOf(startSymbol); - endIndex = descriptionCopy.indexOf(endSymbol); - descriptionCopy = descriptionCopy.slice(0, startIndex) + - descriptionCopy.slice(endIndex + endSymbol.length, descriptionCopy.length); - } - return descriptionCopy; - } - - }; - - /* Methods direct copy from API Detail */ - const methods = { - /** * Pre-process and modify references - * - * @param {Object} oSymbol control data object which will be modified + * @param {object} oSymbol control data object which will be modified * @private */ - modifyReferences: function(oSymbol) { - let bHeaderDocuLinkFound = false; - - - let bUXGuidelinesLinkFound = false; + modifyReferences: function (oSymbol, bCalledOnConstructor) { + var bHeaderDocuLinkFound = false, + bUXGuidelinesLinkFound = false, + aReferences = [], + entity = bCalledOnConstructor? oSymbol.constructor.references : oSymbol.references; + const UX_GUIDELINES_BASE_URL = "https://experience.sap.com/fiori-design-web/"; - const aReferences = []; + if (entity && entity.length > 0) { + entity.forEach(function (sReference) { + var aParts; - if (oSymbol.constructor.references && oSymbol.constructor.references.length > 0) { - oSymbol.constructor.references.forEach(function(sReference) { - let aParts; - - // Docu link - For the header we take into account only the first link that matches - // one of the patterns + // Docu link - For the header we take into account only the first link that matches one of the patterns if (!bHeaderDocuLinkFound) { + // Handled patterns: // * topic:59a0e11712e84a648bb990a1dba76bc7 // * {@link topic:59a0e11712e84a648bb990a1dba76bc7} @@ -1378,59 +1721,77 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { } // Fiori link - Handled patterns: + // * fiori:flexible-column-layout + // * fiori:/flexible-column-layout/ // * fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/ + // * {@link fiori:flexible-column-layout} + // * {@link fiori:/flexible-column-layout/} + // * {@link fiori:/flexible-column-layout/ Flexible Column Layout} // * {@link fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/} - // * {@link fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/ - // Flexible Column Layout} - aParts = sReference.match(/^{@link\s+fiori:(\S+)(\s.+)?}$|^fiori:(\S+)$/); + // * {@link fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/ Flexible Column Layout} + aParts = sReference.match(/^(?:{@link\s)?fiori:(?:https:\/\/experience\.sap\.com\/fiori-design-web\/)?\/?(\S+\b)\/?\s?(.*[^\s}])?}?$/); if (aParts) { - if (!bUXGuidelinesLinkFound) { + let [, sTarget, sTargetName] = aParts; + + if (bCalledOnConstructor && !bUXGuidelinesLinkFound) { // Extract first found UX Guidelines link as primary - if (aParts) { - if (aParts[3]) { - // String of type: "fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/" - oSymbol.uxGuidelinesLink = aParts[3]; - oSymbol.uxGuidelinesLinkText = oSymbol.basename; - } else if (aParts[1]) { - // String of type: "{@link fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/}" - // or - // String of type: "{@link fiori:https://experience.sap.com/fiori-design-web/flexible-column-layout/ Flexible Column Layout}" - oSymbol.uxGuidelinesLink = aParts[1]; - oSymbol.uxGuidelinesLinkText = aParts[2] ? aParts[2] : oSymbol.basename; - } + oSymbol.uxGuidelinesLink = UX_GUIDELINES_BASE_URL + sTarget; + oSymbol.uxGuidelinesLinkText = sTargetName ? sTargetName : oSymbol.basename; bUXGuidelinesLinkFound = true; return; - } } else { // BCP: 1870155880 - Every consecutive "fiori:" link should be handled as a normal link - sReference = sReference.replace("fiori:", ""); + sReference = "{@link " + UX_GUIDELINES_BASE_URL + sTarget + (sTargetName ? " " + sTargetName : "") + "}"; } } aReferences.push(sReference); }); - oSymbol.constructor.references = aReferences; + bCalledOnConstructor? oSymbol.constructor.references = aReferences : oSymbol.references = aReferences; } else { - oSymbol.constructor.references = []; + bCalledOnConstructor? oSymbol.constructor.references = [] : oSymbol.references = []; } }, + /** + * Manage References, to apply as an unordered list in the description + * @param {object} oEntity control data object which will be modified + * @private + */ + formatReferencesInDescription: function(oEntity) { + if (oEntity.references && Array.isArray(oEntity.references)) { + oEntity.references = oEntity.references.map(sReference => { + return `
  • ${sReference}
  • `; + }); + if (!oEntity.description) { + // If there is no method description - references should be the first line of it + oEntity.description = ''; + } else { + oEntity.description += '

    '; + } + oEntity.description += `References:
      ${oEntity.references.join("")}
    `; + } + } + }; + + /* Methods direct copy from API Detail */ + let methods = { + /** * Adjusts methods info so that it can be easily displayed in a table - * - * @param {Array} aMethods - the methods array initially coming from the server + * @param aMethods - the methods array initially coming from the server */ - buildMethodsModel: function(aMethods) { - const fnCreateTypesArr = function(sTypes) { - return sTypes.split("|").map(function(sType) { - return {value: sType}; + buildMethodsModel: function (aMethods) { + var fnCreateTypesArr = function (sTypes) { + return sTypes.split("|").map(function (sType) { + return {value: sType} }); }; - const fnExtractParameterProperties = function(oParameter, aParameters, iDepth, aPhoneName) { + var fnExtractParameterProperties = function (oParameter, aParameters, iDepth, aPhoneName) { if (oParameter.parameterProperties) { - Object.keys(oParameter.parameterProperties).forEach(function(sProperty) { - const oProperty = oParameter.parameterProperties[sProperty]; + Object.keys(oParameter.parameterProperties).forEach(function (sProperty) { + var oProperty = oParameter.parameterProperties[sProperty]; oProperty.depth = iDepth; @@ -1456,13 +1817,13 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { delete oParameter.parameterProperties; } }; - aMethods.forEach(function(oMethod) { + aMethods.forEach(function (oMethod) { // New array to hold modified parameters - const aParameters = []; + var aParameters = []; // Handle parameters if (oMethod.parameters) { - oMethod.parameters.forEach(function(oParameter) { + oMethod.parameters.forEach(function (oParameter) { if (oParameter.type) { oParameter.types = fnCreateTypesArr(oParameter.type); } @@ -1476,6 +1837,7 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Handle Parameter Properties // Note: We flatten the structure fnExtractParameterProperties(oParameter, aParameters, 1, [oParameter.name]); + }); // Override the old data @@ -1487,24 +1849,25 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { // Handle types oMethod.returnValue.types = fnCreateTypesArr(oMethod.returnValue.type); } + }); }, /** * Adjusts events info so that it can be easily displayed in a table - * * @param {Array} aEvents - the events array initially coming from the server */ - buildEventsModel: function(aEvents) { - const fnExtractParameterProperties = function(oParameter, aParameters, iDepth, aPhoneName) { + buildEventsModel: function (aEvents) { + var fnExtractParameterProperties = function (oParameter, aParameters, iDepth, aPhoneName) { if (oParameter.parameterProperties) { - Object.keys(oParameter.parameterProperties).forEach(function(sProperty) { - const oProperty = oParameter.parameterProperties[sProperty]; + Object.keys(oParameter.parameterProperties).forEach(function (sProperty) { + var oProperty = oParameter.parameterProperties[sProperty], + sPhoneTypeSuffix; oProperty.depth = iDepth; // Phone name - available only for parameters - const sPhoneTypeSuffix = oProperty.type === "array" ? "[]" : ""; + sPhoneTypeSuffix = oProperty.type === "array" ? "[]" : ""; oProperty.phoneName = [aPhoneName.join("."), (oProperty.name + sPhoneTypeSuffix)].join("."); // Add property to parameter array as we need a simple structure @@ -1519,13 +1882,13 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { delete oParameter.parameterProperties; } }; - aEvents.forEach(function(aEvents) { + aEvents.forEach(function (aEvents) { // New array to hold modified parameters - const aParameters = []; + var aParameters = []; // Handle parameters if (aEvents.parameters) { - aEvents.parameters.forEach(function(oParameter) { + aEvents.parameters.forEach(function (oParameter) { // Add the parameter before the properties aParameters.push(oParameter); @@ -1542,35 +1905,32 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { /** * Adjusts constructor parameters info so that it can be easily displayed in a table - * * @param {Array} aParameters - the events array initially coming from the server */ - buildConstructorParameters: function(aParameters) { + buildConstructorParameters: function (aParameters) { // New array to hold modified parameters - const aNodes = []; - - - const processNode = function(oNode, sPhoneName, iDepth, aNodes) { - // Handle phone name - oNode.phoneName = sPhoneName ? [sPhoneName, oNode.name].join(".") : oNode.name; + var aNodes = [], + processNode = function (oNode, sPhoneName, iDepth, aNodes) { + // Handle phone name + oNode.phoneName = sPhoneName ? [sPhoneName, oNode.name].join(".") : oNode.name; - // Depth - oNode.depth = iDepth; + // Depth + oNode.depth = iDepth; - // Add to array - aNodes.push(oNode); + // Add to array + aNodes.push(oNode); - // Handle nesting - if (oNode.parameterProperties) { - Object.keys(oNode.parameterProperties).forEach(function(sNode) { - processNode(oNode.parameterProperties[sNode], oNode.phoneName, (iDepth + 1), aNodes); - }); - } + // Handle nesting + if (oNode.parameterProperties) { + Object.keys(oNode.parameterProperties).forEach(function (sNode) { + processNode(oNode.parameterProperties[sNode], oNode.phoneName, (iDepth + 1), aNodes); + }); + } - delete oNode.parameterProperties; - }; + delete oNode.parameterProperties; + }; - aParameters.forEach(function(oParameter) { + aParameters.forEach(function (oParameter) { // Handle Parameter Properties // Note: We flatten the structure processNode(oParameter, undefined, 0, aNodes); @@ -1580,195 +1940,44 @@ module.exports = function transformer(sInputFile, sOutputFile, sLibraryFile) { }, oLibsData: {}, - aTreeContent: [], - - _getControlChildren: function(sTopicId) { - // Find tree node - const findTreeNode = function(aNodes, sTopicId) { - let iLen; - - - let oNode; - - - let i; - - for (i = 0, iLen = aNodes.length; i < iLen; i++) { - oNode = aNodes[i]; - if (oNode.name === sTopicId) { - return oNode; - } - if (oNode.nodes) { - oNode = findTreeNode(aNodes[i].nodes, sTopicId); - if (oNode) { - return oNode; - } - } - } - }; - - - const oNode = findTreeNode(this.aTreeContent, sTopicId); - - return oNode.nodes ? oNode.nodes : false; - }, - - _parseLibraryElements: function(aLibraryElements) { - let oLibraryElement; - - - let aNodes; - - - let i; - - for (i = 0; i < aLibraryElements.length; i++) { - oLibraryElement = aLibraryElements[i]; - aNodes = oLibraryElement.nodes; - - if (!aNodes) { - this.oLibsData[oLibraryElement.name] = oLibraryElement; - } - - this._addElementToTreeData(oLibraryElement, aLibraryElements); - - if (aNodes) { - this._parseLibraryElements(aNodes, true); - } - } - - return this.aTreeContent; - }, - - _addElementToTreeData: function(oJSONElement, aLibraryElements) { - let oNewNodeNamespace; - - if (oJSONElement.kind !== "namespace") { - const aNameParts = oJSONElement.name.split("."); - - - const sBaseName = aNameParts.pop(); - - - const sNodeNamespace = aNameParts.join("."); - // Note: Array.pop() on the previous line modifies the array itself - const oTreeNode = this._createTreeNode(sBaseName, oJSONElement.name); - - - const oExistingNodeNamespace = this._findNodeNamespaceInTreeStructure(sNodeNamespace); - - if (oExistingNodeNamespace) { - if (!oExistingNodeNamespace.nodes) { - oExistingNodeNamespace.nodes = []; - } - oExistingNodeNamespace.nodes.push(oTreeNode); - } else if (sNodeNamespace) { - oNewNodeNamespace = this._createTreeNode(sNodeNamespace, sNodeNamespace); - oNewNodeNamespace.nodes = []; - oNewNodeNamespace.nodes.push(oTreeNode); - this.aTreeContent.push(oNewNodeNamespace); - - this._removeDuplicatedNodeFromTree(sNodeNamespace); - - // Inject missing new root namespace in main collection - aLibraryElements.push({ - kind: "namespace", // Note: we show this elements as namespaces - name: sNodeNamespace, - ref: "#/api/" + sNodeNamespace - }); - } else { - // Entities for which we can't resolve namespace we are shown in the root level - oNewNodeNamespace = this._createTreeNode(oJSONElement.name, oJSONElement.name); - this.aTreeContent.push(oNewNodeNamespace); - } - } else { - oNewNodeNamespace = this._createTreeNode(oJSONElement.name, oJSONElement.name); - this.aTreeContent.push(oNewNodeNamespace); - } - }, - - _createTreeNode: function(text, name, sLib) { - const oTreeNode = {}; - oTreeNode.text = text; - oTreeNode.name = name; - oTreeNode.ref = "#/api/" + name; - return oTreeNode; - }, - - _findNodeNamespaceInTreeStructure: function(sNodeNamespace, aTreeStructure) { - aTreeStructure = aTreeStructure || this.aTreeContent; - for (let i = 0; i < aTreeStructure.length; i++) { - const oTreeNode = aTreeStructure[i]; - if (oTreeNode.name === sNodeNamespace) { - return oTreeNode; - } - if (oTreeNode.nodes) { - const oChildNode = this._findNodeNamespaceInTreeStructure(sNodeNamespace, oTreeNode.nodes); - if (oChildNode) { - return oChildNode; - } - } - } - }, - - _removeNodeFromNamespace: function(sNode, oNamespace) { - for (let i = 0; i < oNamespace.nodes.length; i++) { - if (oNamespace.nodes[i].text === sNode) { - oNamespace.nodes.splice(i, 1); - return; - } - } - }, - - _removeDuplicatedNodeFromTree: function(sNodeFullName) { - if (this.oLibsData[sNodeFullName]) { - const sNodeNamespace = sNodeFullName.substring(0, sNodeFullName.lastIndexOf(".")); - const oNamespace = this._findNodeNamespaceInTreeStructure(sNodeNamespace); - const sNode = sNodeFullName.substring(sNodeFullName.lastIndexOf(".") + 1, sNodeFullName.lenght); - this._removeNodeFromNamespace(sNode, oNamespace); - } - }, - _addChildrenDescription: function(aLibsData, aControlChildren) { - function getDataByName(sName) { - let iLen; - - - let i; - - for (i = 0, iLen = aLibsData.length; i < iLen; i++) { - if (aLibsData[i].name === sName) { - return aLibsData[i]; - } - } - return false; - } - for (let i = 0; i < aControlChildren.length; i++) { - aControlChildren[i].description = formatters._formatChildDescription(getDataByName(aControlChildren[i].name).description); - aControlChildren[i].description = formatters._preProcessLinksInTextBlock(aControlChildren[i].description, true); - - // Handle nesting - if (aControlChildren[i].nodes) { - this._addChildrenDescription(aLibsData, aControlChildren[i].nodes); - } - } - } }; // Create the chain object - const oChainObject = { + let oChainObject = { inputFile: sInputFile, outputFile: sOutputFile, - libraryFile: sLibraryFile + libraryFile: sLibraryFile, + aDependentLibraryFiles: Array.isArray(vDependencyAPIFiles) ? vDependencyAPIFiles : null }; // Start the work here - const p = getLibraryPromise(oChainObject) + let p = getLibraryPromise(oChainObject) .then(extractComponentAndDocuindexUrl) .then(flattenComponents) .then(extractSamplesFromDocuIndex) + .then(getDependencyLibraryFilesList) .then(getAPIJSONPromise) + .then(loadDependencyLibraryFiles) .then(transformApiJson) .then(createApiRefApiJson); return p; + }; + +module.exports = transformer; + +// auto execute the transformer, if this module is the executed script (not used in grunt tooling) +if ( process.argv.length > 1 && /transform-apijson-for-sdk.js$/.test(process.argv[1]) ) { + + let sInputFile = process.argv[2]; + let sOutputFile = process.argv[3]; + let sLibraryFile = process.argv[4]; + let sAPIJSonDependencyDir = process.argv[5]; + + transformer(sInputFile, sOutputFile, sLibraryFile, sAPIJSonDependencyDir) + .catch(oError => { + log.error(oError); + }); + +} diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index bcc9c1e2a..0cba05cf7 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -1,14 +1,14 @@ /* * JSDoc3 plugin for UI5 documentation generation. * - * (c) Copyright 2009-2018 SAP SE or an SAP affiliate company. + * (c) Copyright 2009-2019 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ -/* global require, exports, env */ -/* eslint strict: [2, "global"], max-len: ["warn", 180], no-console: "off", guard-for-in: "off" */ +/* global global, require, exports, env */ +/* eslint strict: [2, "global"]*/ -"use strict"; +'use strict'; /** * UI5 plugin for JSDoc3 (3.3.0-alpha5) @@ -52,11 +52,11 @@ */ /* imports */ -const Syntax = require("jsdoc/src/syntax").Syntax; -const Doclet = require("jsdoc/doclet").Doclet; -const fs = require("jsdoc/fs"); -const path = require("jsdoc/path"); -const pluginConfig = (env.conf && env.conf.templates && env.conf.templates.ui5) || {}; +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) || {}; /* ---- global vars---- */ @@ -65,7 +65,7 @@ const pluginConfig = (env.conf && env.conf.templates && env.conf.templates.ui5) * * Will be determined in the handler for the parseBegin event */ -let pathPrefixes = []; +var pathPrefixes = []; /** * Prefixes of the UI5 unified resource name for the source files is NOT part of the file name. @@ -73,14 +73,14 @@ let pathPrefixes = []; * * The prefix will be prepended to all resource names. */ -let resourceNamePrefixes = []; +var resourceNamePrefixes = []; /** * A UI5 specific unique Id for all doclets. */ -let docletUid = 0; +var docletUid = 0; -let currentProgram; +var currentProgram; /** * Information about the current module. @@ -103,9 +103,9 @@ let currentProgram; * * @type {{name:string,resource:string,module:string,localName:Object}} */ -let currentModule; +var currentModule; -let currentSource; +var currentSource; /** * Cached UI5 metadata for encountered UI5 classes. @@ -115,12 +115,12 @@ let currentSource; * Only after all files have been parsed, the collected information can be associated with the * corresponding JSDoc doclet (e.g. with the class documentation). */ -const classInfos = Object.create(null); +var classInfos = Object.create(null); /** * */ -const typeInfos = Object.create(null); +var typeInfos = Object.create(null); /** * Cached designtime info for encountered sources. @@ -130,105 +130,109 @@ const typeInfos = Object.create(null); * Only after all files have been parsed, the collected information can be associated with runtime metadata * that refers to that designtime module name. */ -const designtimeInfos = Object.create(null); +var designtimeInfos = Object.create(null); /* ---- private functions ---- */ function ui5data(doclet) { - return doclet.__ui5 || (doclet.__ui5 = {id: ++docletUid}); + return doclet.__ui5 || (doclet.__ui5 = { id: ++docletUid }); } -let pendingMessageHeader; +var pendingMessageHeader; function msgHeader(str) { pendingMessageHeader = str; } -function debug(...args) { +/* eslint-disable no-console */ +function debug() { if ( env.opts.debug ) { - console.log(...args); + console.log.apply(console, arguments); } } -function info(...args) { +function info() { if ( env.opts.verbose || env.opts.debug ) { if ( pendingMessageHeader ) { console.log(""); pendingMessageHeader = null; } - console.log(...args); + console.log.apply(console, arguments); } } -function warning(...args) { +function warning(msg) { if ( pendingMessageHeader ) { - if ( !env.opts.verbose && !env.opts.debug ) { + if ( !env.opts.verbose && !env.opts.debug ) { console.log(pendingMessageHeader); } else { console.log(""); } pendingMessageHeader = null; } + var args = Array.prototype.slice.apply(arguments); args[0] = "**** warning: " + args[0]; - console.log(...args); + console.log.apply(console, args); } -function error(...args) { +function error(msg) { if ( pendingMessageHeader && !env.opts.verbose && !env.opts.debug ) { - if ( !env.opts.verbose && !env.opts.debug ) { + if ( !env.opts.verbose && !env.opts.debug ) { console.log(pendingMessageHeader); } else { console.log(""); } pendingMessageHeader = null; } + var args = Array.prototype.slice.apply(arguments); args[0] = "**** error: " + args[0]; - console.log(...args); + console.log.apply(console, args); } +/* eslint-enable no-console */ -// ---- path handling --------------------------------------------------------- +//---- path handling --------------------------------------------------------- function ensureEndingSlash(path) { - path = path || ""; - return path && path.slice(-1) !== "/" ? path + "/" : path; + path = path || ''; + return path && path.slice(-1) !== '/' ? path + '/' : path; } function getRelativePath(filename) { - let relative = path.resolve(filename); - for ( let i = 0; i < pathPrefixes.length; i++ ) { + var relative = path.resolve(filename); + for ( var i = 0; i < pathPrefixes.length; i++ ) { if ( relative.indexOf(pathPrefixes[i]) === 0 ) { relative = relative.slice(pathPrefixes[i].length); break; } } - return relative.replace(/\\/g, "/"); + return relative.replace(/\\/g, '/'); } function getResourceName(filename) { - let resource = path.resolve(filename); - for ( let i = 0; i < pathPrefixes.length; i++ ) { + var resource = path.resolve(filename); + for ( var i = 0; i < pathPrefixes.length; i++ ) { if ( resource.indexOf(pathPrefixes[i]) === 0 ) { resource = resourceNamePrefixes[i] + resource.slice(pathPrefixes[i].length); break; } } - return resource.replace(/\\/g, "/"); + return resource.replace(/\\/g, '/'); } function getModuleName(resource) { - return resource.replace(/\.js$/, ""); + return resource.replace(/\.js$/,''); } /* * resolves relative AMD module identifiers relative to a given base name */ function resolveModuleName(base, name) { - let stack = base.split("/"); + var stack = base.split('/'); stack.pop(); - name.split("/").forEach(function(segment, i) { - if ( segment == ".." ) { + name.split('/').forEach(function(segment, i) { + if ( segment == '..' ) { stack.pop(); - } else if ( segment === "." ) { + } else if ( segment === '.' ) { // ignore } else { if ( i === 0 ) { @@ -237,16 +241,16 @@ function resolveModuleName(base, name) { stack.push(segment); } }); - return stack.join("/"); + return stack.join('/'); } // ---- AMD handling function analyzeModuleDefinition(node) { - const args = node.arguments; - let arg = 0; + var args = node.arguments; + var arg = 0; if ( arg < args.length - && args[arg].type === Syntax.Literal && typeof args[arg].value === "string" ) { + && args[arg].type === Syntax.Literal && typeof args[arg].value === 'string' ) { warning("module explicitly defined a module name '" + args[arg].value + "'"); currentModule.name = args[arg].value; arg++; @@ -260,9 +264,9 @@ function analyzeModuleDefinition(node) { arg++; } if ( currentModule.dependencies && currentModule.factory ) { - for ( let i = 0; i < currentModule.dependencies.length && i < currentModule.factory.params.length; i++ ) { - const name = currentModule.factory.params[i].name; - const module = resolveModuleName(currentModule.module, currentModule.dependencies[i]); + for ( var i = 0; i < currentModule.dependencies.length && i < currentModule.factory.params.length; i++ ) { + var name = currentModule.factory.params[i].name; + var module = resolveModuleName(currentModule.module, currentModule.dependencies[i]); debug(" import " + name + " from '" + module + "'"); currentModule.localNames[name] = { module: module @@ -280,9 +284,10 @@ function analyzeModuleDefinition(node) { * either because they refer to known AMD modukle imports (e.g. shortcut varialbes) * or because they have a (design time) constant value. * - * @param {ASTNode} body + * @param {ASTNode} body AST node of a function body that shall be searched for shortcuts */ function collectShortcuts(body) { + function checkAssignment(name, valueNode) { if ( valueNode.type === Syntax.Literal ) { currentModule.localNames[name] = { @@ -290,20 +295,36 @@ function collectShortcuts(body) { }; debug("compile time constant found ", name, valueNode.value); } else if ( valueNode.type === Syntax.MemberExpression ) { - const _import = getLeftmostName(valueNode); - const local = _import && currentModule.localNames[_import]; - if ( typeof local === "object" && local.module ) { + var _import = getLeftmostName(valueNode); + var local = _import && currentModule.localNames[_import]; + if ( typeof local === 'object' && local.module ) { currentModule.localNames[name] = { module: local.module, - path: getObjectName(valueNode).split(".").slice(1).join(".") // TODO chaining if local has path + path: getObjectName(valueNode).split('.').slice(1).join('.') // TODO chaining if local has path }; debug(" found local shortcut: ", name, currentModule.localNames[name]); } + } else if ( isRequireSyncCall(valueNode) || isProbingRequireCall(valueNode) ) { + if ( valueNode.arguments[0] + && valueNode.arguments[0].type === Syntax.Literal + && typeof valueNode.arguments[0].value === 'string' ) { + currentModule.localNames[name] = { + module: valueNode.arguments[0].value + // no (or empty) path + }; + debug(" found local import: %s = %s('%s')", name, valueNode.callee.property.name, valueNode.arguments[0].value); + } + } else if ( isExtendCall(valueNode) ) { + currentModule.localNames[name] = { + "class": valueNode.arguments[0].value + // no (or empty) path + }; + debug(" found local class definition: %s = .extend('%s', ...)", name, valueNode.arguments[0].value); } } if ( body.type === Syntax.BlockStatement ) { - body.body.forEach(function( stmt ) { + body.body.forEach(function ( stmt ) { // console.log(stmt); if ( stmt.type === Syntax.VariableDeclaration ) { stmt.declarations.forEach(function(decl) { @@ -322,17 +343,26 @@ function collectShortcuts(body) { // ---- text handling --------------------------------------------------------- -const rPlural = /(children|ies|ves|oes|ses|ches|shes|xes|s)$/i; -const mSingular = - {"children": -3, "ies": "y", "ves": "f", "oes": -2, "ses": -2, "ches": -2, "shes": -2, "xes": -2, "s": -1}; +var rPlural = /(children|ies|ves|oes|ses|ches|shes|xes|s)$/i; +var mSingular = {'children' : -3, 'ies' : 'y', 'ves' : 'f', 'oes' : -2, 'ses' : -2, 'ches' : -2, 'shes' : -2, 'xes' : -2, 's' : -1 }; function guessSingularName(sPluralName) { - return sPluralName.replace(rPlural, function($, sPlural) { - const vRepl = mSingular[sPlural.toLowerCase()]; - return typeof vRepl === "string" ? vRepl : sPlural.slice(0, vRepl); + return sPluralName.replace(rPlural, function($,sPlural) { + var vRepl = mSingular[sPlural.toLowerCase()]; + return typeof vRepl === "string" ? vRepl : sPlural.slice(0,vRepl); }); } +function getPropertyKey(prop) { + if ( prop.key.type === Syntax.Identifier ) { + return prop.key.name; + } else if ( prop.key.type === Syntax.Literal ) { + return String(prop.key.value); + } else { + return prop.key.toSource(); + } +} + /** * Creates a map of property values from an AST 'object literal' node. * @@ -340,19 +370,30 @@ function guessSingularName(sPluralName) { * It would be more convenient to just return the values, but the property node is needed * to find the corresponding (preceding) documentation comment. * - * @param {Object} node - * @param {string} defaultKey - * @returns {Map} + * If a defaultKey is given and if the node is not an object literal + * but another simple type literal, the value is treated as a shortcut for + *
    + *   {
    + *     [defaultKey]: node.value
    + *   }
    + * 
    + * This is used in ManagedObjectMetadata to allow a simpler declaration of properties by + * specifying a type name only. + * + * @param {ASTNode} node AST node for an object literal or simple literal + * @param {string} [defaultKey=undefined] A default key to use for simple values + * @returns {Map} Map of AST nodes of type 'Property', keyed by their property name */ function createPropertyMap(node, defaultKey) { - let result; + + var result; if ( node != null ) { - // if, instead of an object literal only a literal is given and there is a defaultKey, - // then wrap the literal in a map + + // if, instead of an object literal only a literal is given and there is a defaultKey, then wrap the literal in a map if ( node.type === Syntax.Literal && defaultKey != null ) { result = {}; - result[defaultKey] = {type: Syntax.Property, value: node}; + result[defaultKey] = { type: Syntax.Property, value: node }; return result; } @@ -365,18 +406,11 @@ function createPropertyMap(node, defaultKey) { // invariant: node.type == Syntax.ObjectExpression result = {}; - for (let i = 0; i < node.properties.length; i++) { - const prop = node.properties[i]; - let name; - // console.log("objectproperty " + prop.type); - if ( prop.key.type === Syntax.Identifier ) { - name = prop.key.name; - } else if ( prop.key.type === Syntax.Literal ) { - name = String(prop.key.value); - } else { - name = prop.key.toSource(); - } - // console.log("objectproperty " + prop.type + ":" + name); + for (var i = 0; i < node.properties.length; i++) { + var prop = node.properties[i]; + //console.log("objectproperty " + prop.type); + var name = getPropertyKey(prop); + //console.log("objectproperty " + prop.type + ":" + name); result[name] = prop; } } @@ -384,32 +418,36 @@ function createPropertyMap(node, defaultKey) { } function isExtendCall(node) { + return ( node && node.type === Syntax.CallExpression && node.callee.type === Syntax.MemberExpression && node.callee.property.type === Syntax.Identifier - && node.callee.property.name === "extend" + && node.callee.property.name === 'extend' && node.arguments.length >= 2 && node.arguments[0].type === Syntax.Literal && typeof node.arguments[0].value === "string" && node.arguments[1].type === Syntax.ObjectExpression ); + } function isSapUiDefineCall(node) { + return ( node && node.type === Syntax.CallExpression && node.callee.type === Syntax.MemberExpression && node.callee.object.type === Syntax.MemberExpression && node.callee.object.object.type === Syntax.Identifier - && node.callee.object.object.name === "sap" + && node.callee.object.object.name === 'sap' && node.callee.object.property.type === Syntax.Identifier - && node.callee.object.property.name === "ui" + && node.callee.object.property.name === 'ui' && node.callee.property.type === Syntax.Identifier - && node.callee.property.name === "define" + && node.callee.property.name === 'define' ); + } function isCreateDataTypeCall(node) { @@ -419,13 +457,46 @@ function isCreateDataTypeCall(node) { && node.callee.type === Syntax.MemberExpression && /^(sap\.ui\.base\.)?DataType$/.test(getObjectName(node.callee.object)) && node.callee.property.type === Syntax.Identifier - && node.callee.property.name === "createType" + && node.callee.property.name === 'createType' + ); +} + +function isRequireSyncCall(node) { + return ( + node + && node.type === Syntax.CallExpression + && node.callee.type === Syntax.MemberExpression + && node.callee.object.type === Syntax.MemberExpression + && node.callee.object.object.type === Syntax.Identifier + && node.callee.object.object.name === 'sap' + && node.callee.object.property.type === Syntax.Identifier + && node.callee.object.property.name === 'ui' + && node.callee.property.type === Syntax.Identifier + && node.callee.property.name === 'requireSync' + ); +} + +function isProbingRequireCall(node) { + return ( + node + && node.type === Syntax.CallExpression + && node.callee.type === Syntax.MemberExpression + && node.callee.object.type === Syntax.MemberExpression + && node.callee.object.object.type === Syntax.Identifier + && node.callee.object.object.name === 'sap' + && node.callee.object.property.type === Syntax.Identifier + && node.callee.object.property.name === 'ui' + && node.callee.property.type === Syntax.Identifier + && node.callee.property.name === 'require' + && node.arguments.length === 1 + && node.arguments[0].type === Syntax.Literal + && typeof node.arguments[0].value === 'string' // TODO generalize to statically analyzable constants ); } function getObjectName(node) { if ( node.type === Syntax.MemberExpression && !node.computed && node.property.type === Syntax.Identifier ) { - const prefix = getObjectName(node.object); + var prefix = getObjectName(node.object); return prefix ? prefix + "." + node.property.name : null; } else if ( node.type === Syntax.Identifier ) { return /* scope[node.name] ? scope[node.name] : */ node.name; @@ -449,16 +520,21 @@ function getLeftmostName(node) { } function getResolvedObjectName(node) { - const name = getObjectName(node); - const _import = getLeftmostName(node); - const local = _import && currentModule.localNames[_import]; - if ( local && local.module ) { - let resolvedName = local.module.replace(/\//g, ".").replace(/\.library$/, ""); - if ( local.path ) { - resolvedName = resolvedName + "." + local.path; + var name = getObjectName(node); + var _import = getLeftmostName(node); + var local = _import && currentModule.localNames[_import]; + if ( local && (local.class || local.module) ) { + var resolvedName; + if ( local.class ) { + resolvedName = local.class; + } else { + resolvedName = local.module.replace(/\//g, ".").replace(/\.library$/, ""); + if ( local.path ) { + resolvedName = resolvedName + "." + local.path; + } } - if ( name.indexOf(".") > 0 ) { - resolvedName = resolvedName + name.slice(name.indexOf(".")); + if ( name.indexOf('.') > 0 ) { + resolvedName = resolvedName + name.slice(name.indexOf('.')); } debug("resolved " + name + " to " + resolvedName); return resolvedName; @@ -467,68 +543,76 @@ function getResolvedObjectName(node) { } function convertValue(node, type, propertyName) { - let value; + + var value; if ( node.type === Syntax.Literal ) { + // 'string' or number or true or false return node.value; + } else if ( node.type === Syntax.UnaryExpression && node.prefix && node.argument.type === Syntax.Literal - && typeof node.argument.value === "number" - && ( node.operator === "-" || node.operator === "+" )) { + && typeof node.argument.value === 'number' + && ( node.operator === '-' || node.operator === '+' )) { + // -n or +n value = node.argument.value; - return node.operator === "-" ? -value : value; + return node.operator === '-' ? -value : value; + } else if ( node.type === Syntax.MemberExpression && type ) { + // enum value (a.b.c) value = getResolvedObjectName(node); if ( value.indexOf(type + ".") === 0 ) { // starts with fully qualified enum name -> cut off name return value.slice(type.length + 1); - // } else if ( value.indexOf(type.split(".").slice(-1)[0] + ".") === 0 ) { - // // unqualified name might be a local name (just a guess - - // // would need static code analysis for proper solution) - // return value.slice(type.split(".").slice(-1)[0].length + 1); +// } else if ( value.indexOf(type.split(".").slice(-1)[0] + ".") === 0 ) { +// // unqualified name might be a local name (just a guess - would need static code analysis for proper solution) +// return value.slice(type.split(".").slice(-1)[0].length + 1); } else { - warning("did not understand default value '%s'%s, falling back to source", value, - propertyName ? " of property '" + propertyName + "'" : ""); + warning("did not understand default value '%s'%s, falling back to source", value, propertyName ? " of property '" + propertyName + "'" : ""); return value; } + } else if ( node.type === Syntax.Identifier ) { - if ( node.name === "undefined") { + if ( node.name === 'undefined') { // undefined return undefined; } - const local = currentModule.localNames[node.name]; - if ( typeof local === "object" && "value" in local ) { + var local = currentModule.localNames[node.name]; + if ( typeof local === 'object' && 'value' in local ) { // TODO check type return local.value; } } else if ( node.type === Syntax.ArrayExpression ) { + if ( node.elements.length === 0 ) { // empty array literal return "[]"; // TODO return this string or an empty array } if ( type && type.slice(-2) === "[]" ) { - const componentType = type.slice(0, -2); + var componentType = type.slice(0,-2); return node.elements.map( function(elem) { return convertValue(elem, componentType, propertyName); }); } + } else if ( node.type === Syntax.ObjectExpression ) { - if ( node.properties.length === 0 && (type === "object" || type === "any") ) { + + if ( node.properties.length === 0 && (type === 'object' || type === 'any') ) { return {}; } + } - value = "???"; + value = '???'; if ( currentSource && node.range ) { value = currentSource.slice( node.range[0], node.range[1] ); } - error("unexpected type of default value (type='%s', source='%s')%s, falling back to '%s'", - node.type, node.toString(), propertyName ? " of property '" + propertyName + "'" : "", value); + error("unexpected type of default value (type='%s', source='%s')%s, falling back to '%s'", node.type, node.toString(), propertyName ? " of property '" + propertyName + "'" : "", value); return value; } @@ -536,9 +620,9 @@ function convertStringArray(node) { if ( node.type !== Syntax.ArrayExpression ) { throw new Error("not an array"); } - const result = []; - for ( let i = 0; i < node.elements.length; i++ ) { - if ( node.elements[i].type !== Syntax.Literal || typeof node.elements[i].value !== "string" ) { + var result = []; + for ( var i = 0; i < node.elements.length; i++ ) { + if ( node.elements[i].type !== Syntax.Literal || typeof node.elements[i].value !== 'string' ) { throw new Error("not a string literal"); } result.push(node.elements[i].value); @@ -548,41 +632,37 @@ function convertStringArray(node) { } function convertDragDropValue(node, cardinality) { - let mDragDropValue; - const mPossibleKeys = {draggable: 1, droppable: 1}; - const mDefaults = { - "self": {draggable: true, droppable: true}, - "0..1": {draggable: true, droppable: true}, - "0..n": {draggable: false, droppable: false} - }; + var mDragDropValue; + var mDefaults = { draggable : false, droppable: false }; if ( node.type === Syntax.ObjectExpression ) { mDragDropValue = (node.properties || []).reduce(function(oObject, oProperty) { - const sKey = convertValue(oProperty.key); - if (mPossibleKeys[sKey]) { + var sKey = getPropertyKey(oProperty); + if (mDefaults.hasOwnProperty(sKey)) { oObject[sKey] = convertValue(oProperty.value); } return oObject; }, {}); } else if ( node.type === Syntax.Literal ) { mDragDropValue = { - draggable: node.value, - droppable: node.value + draggable : node.value, + droppable : node.value }; } else { throw new Error("not a valid dnd node"); } - return Object.assign(mDefaults[cardinality || "self"], mDragDropValue); + return Object.assign(mDefaults, mDragDropValue); } function collectClassInfo(extendCall, classDoclet) { - let baseType; + + var baseType; if ( classDoclet && classDoclet.augments && classDoclet.augments.length === 1 ) { baseType = classDoclet.augments[0]; } if ( extendCall.callee.type === Syntax.MemberExpression ) { - const baseCandidate = getResolvedObjectName(extendCall.callee.object); + var baseCandidate = getResolvedObjectName(extendCall.callee.object); if ( baseCandidate && baseType == null ) { baseType = baseCandidate; } else if ( baseCandidate !== baseType ) { @@ -590,32 +670,32 @@ function collectClassInfo(extendCall, classDoclet) { } } - const oClassInfo = { - name: extendCall.arguments[0].value, - baseType: baseType, - interfaces: [], - doc: classDoclet && classDoclet.description, - deprecation: classDoclet && classDoclet.deprecated, - since: classDoclet && classDoclet.since, - experimental: classDoclet && classDoclet.experimental, - specialSettings: {}, - properties: {}, - aggregations: {}, - associations: {}, - events: {}, - methods: {}, - annotations: {}, + var oClassInfo = { + name : extendCall.arguments[0].value, + baseType : baseType, + interfaces : [], + doc : classDoclet && classDoclet.description, + deprecation : classDoclet && classDoclet.deprecated, + since : classDoclet && classDoclet.since, + experimental : classDoclet && classDoclet.experimental, + specialSettings : {}, + properties : {}, + aggregations : {}, + associations : {}, + events : {}, + methods : {}, + annotations : {}, designtime: false }; function upper(n) { - return n.slice(0, 1).toUpperCase() + n.slice(1); + return n.slice(0,1).toUpperCase() + n.slice(1); } function each(node, defaultKey, callback) { - let n; let settings; let doclet; + var map,n,settings,doclet; - const map = node && createPropertyMap(node.value); + map = node && createPropertyMap(node.value); if ( map ) { for (n in map ) { if ( map.hasOwnProperty(n) ) { @@ -632,16 +712,16 @@ function collectClassInfo(extendCall, classDoclet) { } } - const classInfoNode = extendCall.arguments[1]; - const classInfoMap = createPropertyMap(classInfoNode); + var classInfoNode = extendCall.arguments[1]; + var classInfoMap = createPropertyMap(classInfoNode); if ( classInfoMap && classInfoMap.metadata && classInfoMap.metadata.value.type !== Syntax.ObjectExpression ) { - warning("class metadata exists but can't be analyzed. It is not of type 'ObjectExpression', but a '" + - classInfoMap.metadata.value.type + "'."); + warning("class metadata exists but can't be analyzed. It is not of type 'ObjectExpression', but a '" + classInfoMap.metadata.value.type + "'."); return null; } - const metadata = classInfoMap && classInfoMap.metadata && createPropertyMap(classInfoMap.metadata.value); + var metadata = classInfoMap && classInfoMap.metadata && createPropertyMap(classInfoMap.metadata.value); if ( metadata ) { + debug(" analyzing metadata for '" + oClassInfo.name + "'"); oClassInfo["abstract"] = !!(metadata["abstract"] && metadata["abstract"].value.value); @@ -654,33 +734,33 @@ function collectClassInfo(extendCall, classDoclet) { each(metadata.specialSettings, "type", function(n, settings, doclet) { oClassInfo.specialSettings[n] = { - name: n, - doc: doclet && doclet.description, - since: doclet && doclet.since, - deprecation: doclet && doclet.deprecated, - experimental: doclet && doclet.experimental, - visibility: (settings.visibility && settings.visibility.value.value) || "public", - type: settings.type ? settings.type.value.value : "any" + name : n, + doc : doclet && doclet.description, + since : doclet && doclet.since, + deprecation : doclet && doclet.deprecated, + experimental : doclet && doclet.experimental, + visibility : (settings.visibility && settings.visibility.value.value) || "public", + type : settings.type ? settings.type.value.value : "any" }; }); oClassInfo.defaultProperty = (metadata.defaultProperty && metadata.defaultProperty.value.value) || undefined; each(metadata.properties, "type", function(n, settings, doclet) { - let type; - const N = upper(n); - let methods; + var type; + var N = upper(n); + var methods; oClassInfo.properties[n] = { - name: n, - doc: doclet && doclet.description, - since: doclet && doclet.since, - deprecation: doclet && doclet.deprecated, - experimental: doclet && doclet.experimental, - visibility: (settings.visibility && settings.visibility.value.value) || "public", - type: (type = settings.type ? settings.type.value.value : "string"), - defaultValue: settings.defaultValue ? convertValue(settings.defaultValue.value, type, n) : null, - group: settings.group ? settings.group.value.value : "Misc", - bindable: settings.bindable ? !!convertValue(settings.bindable.value) : false, + name : n, + doc : doclet && doclet.description, + since : doclet && doclet.since, + deprecation : doclet && doclet.deprecated, + experimental : doclet && doclet.experimental, + visibility : (settings.visibility && settings.visibility.value.value) || "public", + type : (type = settings.type ? settings.type.value.value : "string"), + defaultValue : settings.defaultValue ? convertValue(settings.defaultValue.value, type, n) : null, + group : settings.group ? settings.group.value.value : 'Misc', + bindable : settings.bindable ? !!convertValue(settings.bindable.value) : false, methods: (methods = { "get": "get" + N, "set": "set" + N @@ -692,31 +772,26 @@ function collectClassInfo(extendCall, classDoclet) { } // if ( !settings.defaultValue ) { // console.log("property without defaultValue: " + oClassInfo.name + "." + n); - // } - if ( oClassInfo.properties[n].visibility !== "public" ) { - error("Property '" + n + "' uses visibility '" + oClassInfo.properties[n].visibility + - "' which is not supported by the runtime"); - } + //} }); - oClassInfo.defaultAggregation = - (metadata.defaultAggregation && metadata.defaultAggregation.value.value) || undefined; + oClassInfo.defaultAggregation = (metadata.defaultAggregation && metadata.defaultAggregation.value.value) || undefined; each(metadata.aggregations, "type", function(n, settings, doclet) { - const N = upper(n); - let methods; - const aggr = oClassInfo.aggregations[n] = { + var N = upper(n); + var methods; + var aggr = oClassInfo.aggregations[n] = { name: n, - doc: doclet && doclet.description, - deprecation: doclet && doclet.deprecated, - since: doclet && doclet.since, - experimental: doclet && doclet.experimental, - visibility: (settings.visibility && settings.visibility.value.value) || "public", - type: settings.type ? settings.type.value.value : "sap.ui.core.Control", + doc : doclet && doclet.description, + deprecation : doclet && doclet.deprecated, + since : doclet && doclet.since, + experimental : doclet && doclet.experimental, + visibility : (settings.visibility && settings.visibility.value.value) || "public", + type : settings.type ? settings.type.value.value : "sap.ui.core.Control", altTypes: settings.altTypes ? convertStringArray(settings.altTypes.value) : undefined, - singularName: settings.singularName ? settings.singularName.value.value : guessSingularName(n), - cardinality: (settings.multiple && !settings.multiple.value.value) ? "0..1" : "0..n", - bindable: settings.bindable ? !!convertValue(settings.bindable.value) : false, + singularName : settings.singularName ? settings.singularName.value.value : guessSingularName(n), + cardinality : (settings.multiple && !settings.multiple.value.value) ? "0..1" : "0..n", + bindable : settings.bindable ? !!convertValue(settings.bindable.value) : false, methods: (methods = { "get": "get" + N, "destroy": "destroy" + N @@ -728,7 +803,7 @@ function collectClassInfo(extendCall, classDoclet) { if ( aggr.cardinality === "0..1" ) { methods["set"] = "set" + N; } else { - const N1 = upper(aggr.singularName); + var N1 = upper(aggr.singularName); methods["insert"] = "insert" + N1; methods["add"] = "add" + N1; methods["remove"] = "remove" + N1; @@ -742,18 +817,18 @@ function collectClassInfo(extendCall, classDoclet) { }); each(metadata.associations, "type", function(n, settings, doclet) { - const N = upper(n); - let methods; + var N = upper(n); + var methods; oClassInfo.associations[n] = { name: n, - doc: doclet && doclet.description, - deprecation: doclet && doclet.deprecated, - since: doclet && doclet.since, - experimental: doclet && doclet.experimental, - visibility: (settings.visibility && settings.visibility.value.value) || "public", - type: settings.type ? settings.type.value.value : "sap.ui.core.Control", - singularName: settings.singularName ? settings.singularName.value.value : guessSingularName(n), - cardinality: (settings.multiple && settings.multiple.value.value) ? "0..n" : "0..1", + doc : doclet && doclet.description, + deprecation : doclet && doclet.deprecated, + since : doclet && doclet.since, + experimental : doclet && doclet.experimental, + visibility : (settings.visibility && settings.visibility.value.value) || "public", + type : settings.type ? settings.type.value.value : "sap.ui.core.Control", + singularName : settings.singularName ? settings.singularName.value.value : guessSingularName(n), + cardinality : (settings.multiple && settings.multiple.value.value) ? "0..n" : "0..1", methods: (methods = { "get": "get" + N }) @@ -761,28 +836,24 @@ function collectClassInfo(extendCall, classDoclet) { if ( oClassInfo.associations[n].cardinality === "0..1" ) { methods["set"] = "set" + N; } else { - const N1 = upper(oClassInfo.associations[n].singularName); + var N1 = upper(oClassInfo.associations[n].singularName); methods["add"] = "add" + N1; methods["remove"] = "remove" + N1; methods["removeAll"] = "removeAll" + N; } - if ( oClassInfo.associations[n].visibility !== "public" ) { - error("Association '" + n + "' uses visibility '" + - oClassInfo.associations[n].visibility + "' which is not supported by the runtime"); - } }); each(metadata.events, null, function(n, settings, doclet) { - const N = upper(n); - const info = oClassInfo.events[n] = { + var N = upper(n); + var info = oClassInfo.events[n] = { name: n, - doc: doclet && doclet.description, - deprecation: doclet && doclet.deprecated, - since: doclet && doclet.since, - experimental: doclet && doclet.experimental, - visibility: /* (settings.visibility && settings.visibility.value.value) || */ "public", - allowPreventDefault: !!(settings.allowPreventDefault && settings.allowPreventDefault.value.value), - parameters: {}, + doc : doclet && doclet.description, + deprecation : doclet && doclet.deprecated, + since : doclet && doclet.since, + experimental : doclet && doclet.experimental, + visibility : /* (settings.visibility && settings.visibility.value.value) || */ "public", + allowPreventDefault : !!(settings.allowPreventDefault && settings.allowPreventDefault.value.value), + parameters : {}, methods: { "attach": "attach" + N, "detach": "detach" + N, @@ -791,19 +862,18 @@ function collectClassInfo(extendCall, classDoclet) { }; each(settings.parameters, "type", function(pName, pSettings, pDoclet) { info.parameters[pName] = { - name: pName, - doc: pDoclet && pDoclet.description, - deprecation: pDoclet && pDoclet.deprecated, - since: pDoclet && pDoclet.since, - experimental: pDoclet && pDoclet.experimental, - type: pSettings && pSettings.type ? pSettings.type.value.value : "" + name : pName, + doc : pDoclet && pDoclet.description, + deprecation : pDoclet && pDoclet.deprecated, + since : pDoclet && pDoclet.since, + experimental : pDoclet && pDoclet.experimental, + type : pSettings && pSettings.type ? pSettings.type.value.value : "" }; }); }); - const designtime = (metadata.designtime && convertValue(metadata.designtime.value)) || - (metadata.designTime && convertValue(metadata.designTime.value)); - if ( typeof designtime === "string" || typeof designtime === "boolean" ) { + var designtime = (metadata.designtime && convertValue(metadata.designtime.value)) || (metadata.designTime && convertValue(metadata.designTime.value)); + if ( typeof designtime === 'string' || typeof designtime === 'boolean' ) { oClassInfo.designtime = designtime; } // console.log(oClassInfo.name + ":" + JSON.stringify(oClassInfo, null, " ")); @@ -816,10 +886,11 @@ function collectClassInfo(extendCall, classDoclet) { } function collectDesigntimeInfo(dtNode) { + function each(node, defaultKey, callback) { - let n; let settings; let doclet; + var map,n,settings,doclet; - const map = node && createPropertyMap(node.value); + map = node && createPropertyMap(node.value); if ( map ) { for (n in map ) { if ( map.hasOwnProperty(n) ) { @@ -836,23 +907,20 @@ function collectDesigntimeInfo(dtNode) { } } - let oDesigntimeInfo; + var oDesigntimeInfo; - const map = createPropertyMap(dtNode.argument); + var map = createPropertyMap(dtNode.argument); if (map.annotations) { + oDesigntimeInfo = { annotations: {} }; each(map.annotations, null, function(n, settings, doclet) { - const appliesTo = []; - - - const targets = []; - - - let i; + var appliesTo = [], + targets = [], + i, oAnno, iPos; if (settings.appliesTo) { for (i = 0; i < settings.appliesTo.value.elements.length; i++) { @@ -868,9 +936,9 @@ function collectDesigntimeInfo(dtNode) { oDesigntimeInfo.annotations[n] = { name: n, - doc: doclet && doclet.description, - deprecation: doclet && doclet.deprecated, - since: doclet && doclet.since || settings.since && settings.since.value.value, + doc : doclet && doclet.description, + deprecation : doclet && doclet.deprecated, + since : doclet && doclet.since || settings.since && settings.since.value.value, namespace: settings.namespace && settings.namespace.value.value, annotation: settings.annotation && settings.annotation.value.value, appliesTo: appliesTo, @@ -879,8 +947,8 @@ function collectDesigntimeInfo(dtNode) { defaultValue: settings.defaultValue && settings.defaultValue.value.value }; - const oAnno = oDesigntimeInfo.annotations[n].annotation; - const iPos = oAnno && oAnno.lastIndexOf("."); + oAnno = oDesigntimeInfo.annotations[n].annotation; + iPos = oAnno && oAnno.lastIndexOf("."); if ( !oDesigntimeInfo.annotations[n].namespace && iPos > 0 ) { oDesigntimeInfo.annotations[n].namespace = oAnno.slice(0, iPos); @@ -894,29 +962,27 @@ function collectDesigntimeInfo(dtNode) { function determineValueRangeBorder(range, expression, varname, inverse) { if ( expression.type === Syntax.BinaryExpression ) { - let value; - if ( expression.left.type === Syntax.Identifier && expression.left.name === varname && - expression.right.type === Syntax.Literal ) { + var value; + if ( expression.left.type === Syntax.Identifier && expression.left.name === varname && expression.right.type === Syntax.Literal ) { value = expression.right.value; - } else if ( expression.left.type === Syntax.Literal && expression.right.type === Syntax.Identifier && - expression.right.name === varname ) { + } else if ( expression.left.type === Syntax.Literal && expression.right.type === Syntax.Identifier && expression.right.name === varname ) { inverse = !inverse; value = expression.left.value; } else { return false; } switch (expression.operator) { - case "<": - range[inverse ? "minExclusive" : "maxExclusive"] = value; + case '<': + range[inverse ? 'minExclusive' : 'maxExclusive'] = value; break; - case "<=": - range[inverse ? "minInclusive" : "maxInclusive"] = value; + case '<=': + range[inverse ? 'minInclusive' : 'maxInclusive'] = value; break; - case ">=": - range[inverse ? "maxInclusive" : "minInclusive"] = value; + case '>=': + range[inverse ? 'maxInclusive' : 'minInclusive'] = value; break; - case ">": - range[inverse ? "maxExclusive" : "minExclusive"] = value; + case '>': + range[inverse ? 'maxExclusive' : 'minExclusive'] = value; break; default: return false; @@ -927,13 +993,13 @@ function determineValueRangeBorder(range, expression, varname, inverse) { } function determineValueRange(expression, varname, inverse) { - const range = {}; + var range = {}; if ( expression.type === Syntax.LogicalExpression - && expression.operator === "&&" - && expression.left.type === Syntax.BinaryExpression - && expression.right.type === Syntax.BinaryExpression - && determineValueRangeBorder(range, expression.left, varname, inverse) - && determineValueRangeBorder(range, expression.right, varname, inverse) ) { + && expression.operator === '&&' + && expression.left.type === Syntax.BinaryExpression + && expression.right.type === Syntax.BinaryExpression + && determineValueRangeBorder(range, expression.left, varname, inverse) + && determineValueRangeBorder(range, expression.right, varname, inverse) ) { return range; } else if ( expression.type === Syntax.BinaryExpression && determineValueRangeBorder(range, expression, varname, inverse) ) { @@ -943,31 +1009,27 @@ function determineValueRange(expression, varname, inverse) { } function collectDataTypeInfo(extendCall, classDoclet) { - const args = extendCall.arguments; - - - let i = 0; + var args = extendCall.arguments, + i = 0, + name, def, base, pattern, range; - - let name; let def; let base; let pattern; let range; - - if ( i < args.length && args[i].type === Syntax.Literal && typeof args[i].value === "string" ) { + if ( i < args.length && args[i].type === Syntax.Literal && typeof args[i].value === 'string' ) { name = args[i++].value; } if ( i < args.length && args[i].type === Syntax.ObjectExpression ) { def = createPropertyMap(args[i++]); } if ( i < args.length ) { - if ( args[i].type === Syntax.Literal && typeof args[i].value === "string" ) { + if ( args[i].type === Syntax.Literal && typeof args[i].value === 'string' ) { base = args[i++].value; } else if ( args[i].type === Syntax.CallExpression && args[i].callee.type === Syntax.MemberExpression && /^(sap\.ui\.base\.)?DataType$/.test(getObjectName(args[i].callee.object)) && args[i].callee.property.type === Syntax.Identifier - && args[i].callee.property.name === "getType" + && args[i].callee.property.name === 'getType' && args[i].arguments.length === 1 && args[i].arguments[0].type === Syntax.Literal - && typeof args[i].arguments[0].value === "string" ) { + && typeof args[i].arguments[0].value === 'string' ) { base = args[i++].arguments[0].value; } else { error("could not identify base type of data type '" + name + "'"); @@ -977,20 +1039,20 @@ function collectDataTypeInfo(extendCall, classDoclet) { } if ( def - && def.isValid - && def.isValid.value.type === Syntax.FunctionExpression - && def.isValid.value.params.length === 1 - && def.isValid.value.params[0].type === Syntax.Identifier - && def.isValid.value.body.body.length === 1 ) { - const varname = def.isValid.value.params[0].name; - const stmt = def.isValid.value.body.body[0]; + && def.isValid + && def.isValid.value.type === Syntax.FunctionExpression + && def.isValid.value.params.length === 1 + && def.isValid.value.params[0].type === Syntax.Identifier + && def.isValid.value.body.body.length === 1 ) { + var varname = def.isValid.value.params[0].name; + var stmt = def.isValid.value.body.body[0]; if ( stmt.type === Syntax.ReturnStatement && stmt.argument ) { if ( stmt.argument.type === Syntax.CallExpression - && stmt.argument.callee.type === Syntax.MemberExpression - && stmt.argument.callee.object.type === Syntax.Literal - && stmt.argument.callee.object.regex - && stmt.argument.callee.property.type === Syntax.Identifier - && stmt.argument.callee.property.name === "test" ) { + && stmt.argument.callee.type === Syntax.MemberExpression + && stmt.argument.callee.object.type === Syntax.Literal + && stmt.argument.callee.object.regex + && stmt.argument.callee.property.type === Syntax.Identifier + && stmt.argument.callee.property.name === 'test' ) { pattern = stmt.argument.callee.object.regex.pattern; // console.log(pattern); } else { @@ -1002,18 +1064,18 @@ function collectDataTypeInfo(extendCall, classDoclet) { && stmt.consequent.body[0].type === Syntax.ReturnStatement && stmt.consequent.body[0].argument && stmt.consequent.body[0].argument.type === Syntax.Literal - && typeof stmt.consequent.body[0].argument.value === "boolean" + && typeof stmt.consequent.body[0].argument.value === 'boolean' && stmt.alternate.type === Syntax.BlockStatement && stmt.alternate.body.length === 1 && stmt.alternate.body[0].type === Syntax.ReturnStatement && stmt.alternate.body[0].argument && stmt.alternate.body[0].argument.type === Syntax.Literal - && typeof stmt.alternate.body[0].argument.value === "boolean" + && typeof stmt.alternate.body[0].argument.value === 'boolean' && stmt.consequent.body[0].argument.value !== typeof stmt.alternate.body[0].argument.value ) { - const inverse = stmt.alternate.body[0].argument.value; + var inverse = stmt.alternate.body[0].argument.value; range = determineValueRange(stmt.test, varname, inverse); } else { - console.log(stmt); + debug("unexpected implementation of a DataType's isValid() implementation: ", stmt); } } @@ -1030,25 +1092,20 @@ function collectDataTypeInfo(extendCall, classDoclet) { } } -const rEmptyLine = /^\s*$/; +var rEmptyLine = /^\s*$/; function createAutoDoc(oClassInfo, classComment, node, parser, filename, commentAlreadyProcessed) { - const newStyle = !!pluginConfig.newStyle; - - - const includeSettings = !!pluginConfig.includeSettingsInConstructor; - - const rawClassComment = getRawComment(classComment); - - - let n; let n1; let pName; let info; let lines; let link; + var newStyle = !!pluginConfig.newStyle, + includeSettings = !!pluginConfig.includeSettingsInConstructor, + rawClassComment = getRawComment(classComment), + p,n,n1,pName,info,lines,link; function isEmpty(obj) { if ( !obj ) { return true; } - for (const n in obj) { + for (var n in obj) { if ( obj.hasOwnProperty(n) ) { return false; } @@ -1057,20 +1114,18 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment } function jsdocCommentFound(comment) { - parser.emit("jsdocCommentFound", { - event: "jsdocCommentFound", - comment: comment, - lineno: node.loc.start.line, - filename: filename, - range: [node.range[0], node.range[0]] + parser.emit('jsdocCommentFound', { + event:'jsdocCommentFound', + comment : comment, + lineno : node.loc.start.line, + filename : filename, + range : [ node.range[0], node.range[0] ] }, parser); } function removeDuplicateEmptyLines(lines) { - let lastWasEmpty = false; - - - let i; let j; let l; let line; + var lastWasEmpty = false, + i,j,l,line; for (i = 0, j = 0, l = lines.length; i < l; i++) { line = lines[i]; @@ -1084,29 +1139,28 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment lastWasEmpty = false; } } - return j < i ? lines.slice(0, j) : lines; + return j < i ? lines.slice(0,j) : lines; } function newJSDoc(lines) { - // console.log("add completely new jsdoc comment to prog " + - // node.type + ":" + node.nodeId + ":" + Object.keys(node)); + //console.log("add completely new jsdoc comment to prog " + node.type + ":" + node.nodeId + ":" + Object.keys(node)); lines = removeDuplicateEmptyLines(lines); lines.push("@synthetic"); - const comment = " * " + lines.join("\r\n * "); + var comment = " * " + lines.join("\r\n * "); jsdocCommentFound("/**\r\n" + comment + "\r\n */"); - const m = /@name\s+([^\r\n\t ]+)/.exec(comment); + var m = /@name\s+([^\r\n\t ]+)/.exec(comment); debug(" creating synthetic comment '" + (m && m[1]) + "'"); } - function rname(prefix, n, _static) { - return (_static ? "." : "#") + prefix + n.slice(0, 1).toUpperCase() + n.slice(1); + function rname(prefix,n,_static) { + return (_static ? "." : "#") + prefix + n.slice(0,1).toUpperCase() + n.slice(1); } - function name(prefix, n, _static) { - return oClassInfo.name + rname(prefix, n, _static); + function name(prefix,n,_static) { + return oClassInfo.name + rname(prefix,n,_static); } /* @@ -1115,7 +1169,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment * (the latter only if componentTypeOnly is not set). */ function makeTypeString(aggr, componentTypeOnly) { - let s = aggr.type; + var s = aggr.type; if ( aggr.altTypes ) { s = s + "|" + aggr.altTypes.join("|"); } @@ -1130,39 +1184,39 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment return s; } - // function shortname(s) { - // return s.slice(s.lastIndexOf('.') + 1); - // } - - const HUNGARIAN_PREFIXES = { - "int": "i", - "boolean": "b", - "float": "f", - "string": "s", - "function": "fn", - "object": "o", - "regexp": "r", - "jQuery": "$", - "any": "o", - "variant": "v", - "map": "m" +// function shortname(s) { +// return s.slice(s.lastIndexOf('.') + 1); +// } + + var HUNGARIAN_PREFIXES = { + 'int' : 'i', + 'boolean' : 'b', + 'float' : 'f', + 'string' : 's', + 'function' : 'fn', + 'object' : 'o', + 'regexp' : 'r', + 'jQuery' : '$', + 'any' : 'o', + 'variant' : 'v', + 'map' : 'm' }; function varname(n, type, property) { - const prefix = HUNGARIAN_PREFIXES[type] || (property ? "s" : "o"); - return prefix + n.slice(0, 1).toUpperCase() + n.slice(1); + var prefix = HUNGARIAN_PREFIXES[type] || (property ? "s" : "o"); + return prefix + n.slice(0,1).toUpperCase() + n.slice(1); } // add a list of the possible settings if and only if // - documentation for the constructor exists // - no (generated) documentation for settings exists already // - a suitable place for inserting the settings can be found - const m = /(?:^|\r\n|\n|\r)[ \t]*\**[ \t]*@[a-zA-Z]/.exec(rawClassComment); - const p = m ? m.index : -1; - const hasSettingsDocs = rawClassComment.indexOf("The supported settings are:") >= 0; + var m = /(?:^|\r\n|\n|\r)[ \t]*\**[ \t]*@[a-zA-Z]/.exec(rawClassComment); + p = m ? m.index : -1; + var hasSettingsDocs = rawClassComment.indexOf("The supported settings are:") >= 0; // heuristic to recognize a ManagedObject - const isManagedObject = ( + var isManagedObject = ( /@extends\s+sap\.ui\.(?:base\.ManagedObject|core\.(?:Element|Control|Component))(?:\s|$)/.test(rawClassComment) || oClassInfo.library || !isEmpty(oClassInfo.specialSettings) @@ -1170,7 +1224,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment || !isEmpty(oClassInfo.aggregations) || !isEmpty(oClassInfo.associations) || !isEmpty(oClassInfo.events) - ); + ); if ( p >= 0 && !hasSettingsDocs ) { lines = [ @@ -1178,22 +1232,23 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment ]; if ( isManagedObject ) { // only a ManagedObject has settings + if ( oClassInfo.name !== "sap.ui.base.ManagedObject" ) { // add the hint for the general description only when the current class is not ManagedObject itself lines.push( "", "Accepts an object literal mSettings that defines initial", "property values, aggregated and associated objects as well as event handlers.", - "See {@link sap.ui.base.ManagedObject#constructor} for a general description of " + - "the syntax of the settings object." + "See {@link sap.ui.base.ManagedObject#constructor} for a general description of the syntax of the settings object." ); } // add the settings section only if there are any settings if ( !isEmpty(oClassInfo.properties) - || !isEmpty(oClassInfo.aggregations) - || !isEmpty(oClassInfo.associations) - || !isEmpty(oClassInfo.events) ) { + || !isEmpty(oClassInfo.aggregations) + || !isEmpty(oClassInfo.associations) + || !isEmpty(oClassInfo.events) ) { + lines.push( "", includeSettings ? "" : "@ui5-settings", @@ -1204,10 +1259,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment lines.push("
  • Properties"); lines.push("
      "); for (n in oClassInfo.properties) { - lines.push("
    • {@link " + rname("get", n) + " " + n + "} : " + - oClassInfo.properties[n].type + (oClassInfo.properties[n].defaultValue !== null ? - " (default: " + oClassInfo.properties[n].defaultValue + ")" : "") + - (oClassInfo.defaultProperty == n ? " (default)" : "") + "
    • "); + lines.push("
    • {@link " + rname("get", n) + " " + n + "} : " + oClassInfo.properties[n].type + (oClassInfo.properties[n].defaultValue !== null ? " (default: " + oClassInfo.properties[n].defaultValue + ")" : "") + (oClassInfo.defaultProperty == n ? " (default)" : "") + "
    • "); } lines.push("
    "); lines.push("
  • "); @@ -1217,8 +1269,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment lines.push("
      "); for (n in oClassInfo.aggregations) { if ( oClassInfo.aggregations[n].visibility !== "hidden" ) { - lines.push("
    • {@link " + rname("get", n) + " " + n + "} : " + - makeTypeString(oClassInfo.aggregations[n]) + (oClassInfo.defaultAggregation == n ? " (default)" : "") + "
    • "); + lines.push("
    • {@link " + rname("get", n) + " " + n + "} : " + makeTypeString(oClassInfo.aggregations[n]) + (oClassInfo.defaultAggregation == n ? " (default)" : "") + "
    • "); } } lines.push("
    "); @@ -1228,8 +1279,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment lines.push("
  • Associations"); lines.push("
      "); for (n in oClassInfo.associations) { - lines.push("
    • {@link " + rname("get", n) + " " + n + "} : (sap.ui.core.ID | " + - oClassInfo.associations[n].type + ")" + (oClassInfo.associations[n].cardinality === "0..n" ? "[]" : "") + "
    • "); + lines.push("
    • {@link " + rname("get", n) + " " + n + "} : (sap.ui.core.ID | " + oClassInfo.associations[n].type + ")" + (oClassInfo.associations[n].cardinality === "0..n" ? "[]" : "") + "
    • "); } lines.push("
    "); lines.push("
  • "); @@ -1238,8 +1288,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment lines.push("
  • Events"); lines.push("
      "); for (n in oClassInfo.events) { - lines.push("
    • {@link " + "#event:" + n + " " + n + - "} : fnListenerFunction or [fnListenerFunction, oListenerObject] or [oData, fnListenerFunction, oListenerObject]
    • "); + lines.push("
    • {@link " + "#event:" + n + " " + n + "} : fnListenerFunction or [fnListenerFunction, oListenerObject] or [oData, fnListenerFunction, oListenerObject]
    • "); } lines.push("
    "); lines.push("
  • "); @@ -1255,7 +1304,9 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment ); } lines.push(""); + } else if ( oClassInfo.name !== "sap.ui.base.ManagedObject" && oClassInfo.baseType && oClassInfo.hasOwnProperty("abstract") ) { + // if a class has no settings, but metadata, point at least to the base class - if it makes sense lines.push( "", @@ -1263,22 +1314,24 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment "This class does not have its own settings, but all settings applicable to the base type", "{@link " + oClassInfo.baseType + "#constructor " + oClassInfo.baseType + "} can be used." ); + } } debug(" enhancing constructor documentation with settings"); - let enhancedComment = - rawClassComment.slice(0, p) + + var enhancedComment = + rawClassComment.slice(0,p) + "\n * " + removeDuplicateEmptyLines(lines).join("\n * ") + (commentAlreadyProcessed ? "@ui5-updated-doclet\n * " : "") + rawClassComment.slice(p); - enhancedComment = preprocessComment({comment: enhancedComment, lineno: classComment.lineno}); + enhancedComment = preprocessComment({ comment : enhancedComment, lineno : classComment.lineno }); if ( commentAlreadyProcessed ) { jsdocCommentFound(enhancedComment); } else { setRawComment(classComment, enhancedComment); } + } newJSDoc([ @@ -1296,8 +1349,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment "Creates a new subclass of class " + oClassInfo.name + " with name sClassName", "and enriches it with the information contained in oClassInfo.", "", - "oClassInfo might contain the same kind of information as described in {@link " + - (oClassInfo.baseType ? oClassInfo.baseType + ".extend" : "sap.ui.base.Object.extend Object.extend") + "}.", + "oClassInfo might contain the same kind of information as described in {@link " + (oClassInfo.baseType ? oClassInfo.baseType + ".extend" : "sap.ui.base.Object.extend Object.extend") + "}.", "", "@param {string} sClassName Name of the class being created", "@param {object} [oClassInfo] Object literal with information about the class", @@ -1312,11 +1364,11 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment for (n in oClassInfo.properties ) { info = oClassInfo.properties[n]; - if ( info.visibility === "hidden" ) { + if ( info.visibility === 'hidden' ) { continue; } // link = newStyle ? "{@link #setting:" + n + " " + n + "}" : "" + n + ""; - link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; + link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; newJSDoc([ "Gets current value of property " + link + ".", "", @@ -1328,7 +1380,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("get", n), + "@name " + name("get",n), "@function" ]); newJSDoc([ @@ -1339,13 +1391,13 @@ 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 + "", + "@param {" + info.type + "} " + varname(n,info.type,true) + " New value for property " + n + "", "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", info.since ? "@since " + info.since : "", info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("set", n), + "@name " + name("set",n), "@function" ]); if ( info.bindable ) { @@ -1360,7 +1412,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("bind", n), + "@name " + name("bind",n), "@function" ]); newJSDoc([ @@ -1370,7 +1422,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("unbind", n), + "@name " + name("unbind",n), "@function" ]); } @@ -1378,11 +1430,11 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment for (n in oClassInfo.aggregations ) { info = oClassInfo.aggregations[n]; - if ( info.visibility === "hidden" ) { + if ( info.visibility === 'hidden' ) { continue; } // link = newStyle ? "{@link #setting:" + n + " " + n + "}" : "" + n + ""; - link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; + link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; newJSDoc([ "Gets content of aggregation " + link + ".", "", @@ -1394,7 +1446,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("get", n), + "@name " + name("get",n), "@function" ]); if ( info.cardinality == "0..n" ) { @@ -1403,7 +1455,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment "Inserts a " + n1 + " into the aggregation " + link + ".", "", "@param {" + makeTypeString(info, true) + "}", - " " + varname(n1, info.altTypes ? "variant" : info.type) + " The " + n1 + " to insert; if empty, nothing is inserted", + " " + varname(n1,info.altTypes ? "variant" : info.type) + " The " + n1 + " to insert; if empty, nothing is inserted", "@param {int}", " iIndex The 0-based index the " + n1 + " should be inserted at; for", " a negative value of iIndex, the " + n1 + " is inserted at position 0; for a value", @@ -1414,26 +1466,26 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("insert", n1), + "@name " + name("insert",n1), "@function" ]); newJSDoc([ "Adds some " + n1 + " to the aggregation " + link + ".", "@param {" + makeTypeString(info, true) + "}", - " " + varname(n1, info.altTypes ? "variant" : info.type) + " The " + n1 + " to add; if empty, nothing is inserted", + " " + varname(n1,info.altTypes ? "variant" : info.type) + " The " + n1 + " to add; if empty, nothing is inserted", "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", info.since ? "@since " + info.since : "", info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("add", n1), + "@name " + name("add",n1), "@function" ]); newJSDoc([ "Removes a " + n1 + " from the aggregation " + link + ".", "", - "@param {int | string | " + makeTypeString(info, true) + "} " + varname(n1, "variant") + " The " + n1 + " to remove or its index or id", + "@param {int | string | " + makeTypeString(info, true) + "} " + varname(n1,"variant") + " The " + n1 + " to remove or its index or id", "@returns {" + makeTypeString(info, true) + "} The removed " + n1 + " or null", info.since ? "@since " + info.since : "", info.deprecation ? "@deprecated " + info.deprecation : "", @@ -1502,7 +1554,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("bind", n), + "@name " + name("bind",n), "@function" ]); newJSDoc([ @@ -1512,7 +1564,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("unbind", n), + "@name " + name("unbind",n), "@function" ]); } @@ -1520,11 +1572,11 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment for (n in oClassInfo.associations ) { info = oClassInfo.associations[n]; - if ( info.visibility === "hidden" ) { + if ( info.visibility === 'hidden' ) { continue; } // link = newStyle ? "{@link #setting:" + n + " " + n + "}" : "" + n + ""; - link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; + link = "{@link " + (newStyle ? "#setting:" + n : rname("get", n)) + " " + n + "}"; newJSDoc([ info.cardinality === "0..n" ? "Returns array of IDs of the elements which are the current targets of the association " + link + "." : @@ -1537,7 +1589,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("get", n), + "@name " + name("get",n), "@function" ]); if ( info.cardinality === "0..n" ) { @@ -1545,20 +1597,18 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment newJSDoc([ "Adds some " + n1 + " into the association " + link + ".", "", - "@param {sap.ui.core.ID | " + info.type + "} " + varname(n1, "variant") + " The " + n + - " to add; if empty, nothing is inserted", + "@param {sap.ui.core.ID | " + info.type + "} " + varname(n1, "variant") + " The " + n + " to add; if empty, nothing is inserted", "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", info.since ? "@since " + info.since : "", info.deprecation ? "@deprecated " + info.deprecation : "", info.experimental ? "@experimental " + info.experimental : "", "@public", - "@name " + name("add", n1), + "@name " + name("add",n1), "@function" ]); newJSDoc([ "Removes an " + n1 + " from the association named " + link + ".", - "@param {int | sap.ui.core.ID | " + info.type + "} " + varname(n1, "variant") + " The " + n1 + - " to be removed or its index or ID", + "@param {int | sap.ui.core.ID | " + info.type + "} " + varname(n1,"variant") + " The " + n1 + " to be removed or its index or ID", "@returns {sap.ui.core.ID} The removed " + n1 + " or null", info.since ? "@since " + info.since : "", info.deprecation ? "@deprecated " + info.deprecation : "", @@ -1580,9 +1630,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment } else { newJSDoc([ "Sets the associated " + link + ".", - "@param {sap.ui.core.ID | " + info.type + "} " + varname(n, info.type) + - " ID of an element which becomes the new target of this " + n + - " association; alternatively, an element instance may be given", + "@param {sap.ui.core.ID | " + info.type + "} " + varname(n, info.type) + " ID of an element which becomes the new target of this " + n + " association; alternatively, an element instance may be given", "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", info.since ? "@since " + info.since : "", info.deprecation ? "@deprecated " + info.deprecation : "", @@ -1596,7 +1644,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment for (n in oClassInfo.events ) { info = oClassInfo.events[n]; - // link = newStyle ? "{@link #event:" + n + " " + n + "}" : "" + n + ""; + //link = newStyle ? "{@link #event:" + n + " " + n + "}" : "" + n + ""; link = "{@link #event:" + n + " " + n + "}"; lines = [ @@ -1620,23 +1668,19 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment newJSDoc(lines); newJSDoc([ - "Attaches event handler fnFunction to the " + link + " event of this " + - oClassInfo.name + ".", + "Attaches event handler fnFunction to the " + link + " event of this " + oClassInfo.name + ".", "", - "When called, the context of the event handler (its this) will be bound to " + - "oListener if specified, ", + "When called, the context of the event handler (its this) will be bound to oListener if specified, ", "otherwise it will be bound to this " + oClassInfo.name + " itself.", "", !newStyle && info.doc ? info.doc : "", "", "@param {object}", - " [oData] An application-specific payload object that will be passed to the event handler " + - "along with the event object when firing the event", + " [oData] An application-specific payload object that will be passed to the event handler along with the event object when firing the event", "@param {function}", " fnFunction The function to be called when the event occurs", "@param {object}", - " [oListener] Context object to call the event handler with. Defaults to this " + - oClassInfo.name + " itself", + " [oListener] Context object to call the event handler with. Defaults to this " + oClassInfo.name + " itself", "", "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", "@public", @@ -1647,15 +1691,14 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment "@function" ]); newJSDoc([ - "Detaches event handler fnFunction from the " + link + " event of this " + - oClassInfo.name + ".", + "Detaches event handler fnFunction from the " + link + " event of this " + oClassInfo.name + ".", "", "The passed function and listener object must match the ones used for event registration.", "", "@param {function}", " fnFunction The function to be called, when the event occurs", "@param {object}", - " oListener Context object on which the given function had to be called", + " [oListener] Context object on which the given function had to be called", "@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining", info.since ? "@since " + info.since : "", info.deprecation ? "@deprecated " + info.deprecation : "", @@ -1671,10 +1714,9 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment ]; if ( info.allowPreventDefault ) { lines.push( - "", - "Listeners may prevent the default action of this event by using the " + - "preventDefault-method on the event object.", - ""); + "", + "Listeners may prevent the default action of this event by using the preventDefault-method on the event object.", + ""); } lines.push( "", @@ -1683,8 +1725,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment if ( !isEmpty(info.parameters) ) { for (pName in info.parameters) { lines.push( - "@param {" + (info.parameters[pName].type || "any") + "} [mParameters." + pName + "] " + - (info.parameters[pName].doc || "") + "@param {" + (info.parameters[pName].type || "any") + "} [mParameters." + pName + "] " + (info.parameters[pName].doc || "") ); } lines.push(""); @@ -1692,8 +1733,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment if ( info.allowPreventDefault ) { lines.push("@returns {boolean} Whether or not to prevent the default action"); } else { - lines.push("@returns {" + oClassInfo.name + - "} Reference to this in order to allow method chaining"); + lines.push("@returns {" + oClassInfo.name + "} Reference to this in order to allow method chaining"); } lines.push( "@protected", @@ -1705,6 +1745,7 @@ function createAutoDoc(oClassInfo, classComment, node, parser, filename, comment ); newJSDoc(lines); } + } function createDataTypeAutoDoc(oTypeInfo, classComment, node, parser, filename) { @@ -1712,12 +1753,11 @@ function createDataTypeAutoDoc(oTypeInfo, classComment, node, parser, filename) /** * Creates a human readable location info for a given doclet. - * - * @param {Object} doclet - * @returns {string} + * @param {Doclet} doclet Doclet to get a location info for + * @returns {string} A human readable location info */ function location(doclet) { - const filename = (doclet.meta && doclet.meta.filename) || "unknown"; + var filename = (doclet.meta && doclet.meta.filename) || "unknown"; return " #" + ui5data(doclet).id + "@" + filename + (doclet.meta.lineno != null ? ":" + doclet.meta.lineno : "") + (doclet.synthetic ? "(synthetic)" : ""); } @@ -1725,20 +1765,21 @@ function location(doclet) { // --- comment related functions that depend on the JSdoc version (e.g. on the used parser) -let isDocComment; -let getLeadingCommentNode; +var isDocComment; +var getLeadingCommentNode; // JSDoc added the node type Syntax.File with the same change that activated Babylon // See https://github.com/jsdoc3/jsdoc/commit/ffec4a42291de6d68e6240f304b68d6abb82a869 -if ( Syntax.File === "File" ) { +if ( Syntax.File === 'File' ) { + // JSDoc starting with version 3.5.0 isDocComment = function isDocCommentBabylon(comment) { - return comment && comment.type === "CommentBlock" && comment.value && comment.value.charAt(0) === "*"; + return comment && comment.type === 'CommentBlock' && comment.value && comment.value.charAt(0) === '*'; }; getLeadingCommentNode = function getLeadingCommentNodeBabylon(node, longname) { - let leadingComments = node.leadingComments; + var leadingComments = node.leadingComments; if ( Array.isArray(leadingComments) ) { // in babylon, all comments are already attached to the node // and the last one is the closest one and should win @@ -1749,18 +1790,18 @@ if ( Syntax.File === "File" ) { } } }; + } else { + // JSDoc versions before 3.5.0 isDocComment = function isDoccommentEsprima(comment) { - return comment && comment.type === "Block"; + return comment && comment.type === 'Block'; }; getLeadingCommentNode = function getLeadingCommentNodeEsprima(node, longname) { - let comment; - - - let leadingComments = node.leadingComments; + var comment, + leadingComments = node.leadingComments; // when espree is used, JSDOc attached the leading comment and the first one was picked if (Array.isArray(leadingComments) && leadingComments.length && leadingComments[0].raw) { @@ -1771,13 +1812,12 @@ if ( Syntax.File === "File" ) { // TODO check why any matches here override the direct leading comment from above if ( longname && currentProgram && currentProgram.leadingComments && currentProgram.leadingComments.length ) { leadingComments = currentProgram.leadingComments; - const rLongname = new RegExp("@(name|alias|class|namespace)\\s+" + longname.replace(/\./g, "\\.")); - for ( let i = 0; i < leadingComments.length; i++ ) { - const raw = getRawComment(leadingComments[i]); + var rLongname = new RegExp("@(name|alias|class|namespace)\\s+" + longname.replace(/\./g, '\\.')); + for ( var i = 0; i < leadingComments.length; i++ ) { + var raw = getRawComment(leadingComments[i]); if ( /^\/\*\*[\s\S]*\*\/$/.test(raw) && rLongname.test(raw) ) { comment = leadingComments[i]; - // console.log("\n\n**** alternative comment found for " + longname + - // " on program level\n\n", comment); + // console.log("\n\n**** alternative comment found for " + longname + " on program level\n\n", comment); break; } } @@ -1787,17 +1827,17 @@ if ( Syntax.File === "File" ) { }; } -// --- comment related functions that are independent from the JSdoc version +//--- comment related functions that are independent from the JSdoc version function getLeadingComment(node) { - const comment = getLeadingCommentNode(node); + var comment = getLeadingCommentNode(node); return comment ? getRawComment(comment) : null; } function getLeadingDoclet(node, preprocess) { - let comment = getLeadingComment(node); + var comment = getLeadingComment(node); if ( comment && preprocess ) { - comment = preprocessComment({comment: comment, lineno: node.loc.start.line}); + comment = preprocessComment({comment:comment, lineno: node.loc.start.line }); } return comment ? new Doclet(comment, {}) : null; } @@ -1805,13 +1845,12 @@ function getLeadingDoclet(node, preprocess) { /** * Determines the raw comment string (source code form, including leading and trailing comment markers / *...* /) from a comment node. * Works for Esprima and Babylon based JSDoc versions. - * - * @param {Object} commentNode - * @returns {string} + * @param {ASTNode} commentNode Node that contains the comment. + * @returns {string} Comment string as written in the source */ function getRawComment(commentNode) { // in esprima, there's a 'raw' property, in babylon, the 'raw' string has to be reconstructed from the 'value' by adding the markers - return commentNode ? commentNode.raw || "/*" + commentNode.value + "*/" : ""; + return commentNode ? commentNode.raw || '/*' + commentNode.value + '*/' : ''; } function setRawComment(commentNode, newRawComment) { @@ -1822,8 +1861,7 @@ function setRawComment(commentNode, newRawComment) { } /** - * Removes the mandatory comment markers and the optional but common asterisks at the beginning of - * each JSDoc comment line. + * Removes the mandatory comment markers and the optional but common asterisks at the beginning of each JSDoc comment line. * * The result is easier to parse/analyze. * @@ -1834,19 +1872,17 @@ function setRawComment(commentNode, newRawComment) { * */ function unwrap(docletSrc) { - if (!docletSrc) { - return ""; - } + if (!docletSrc) { return ''; } // note: keep trailing whitespace for @examples // extra opening/closing stars are ignored // left margin is considered a star and a space // use the /m flag on regex to avoid having to guess what this platform's newline is docletSrc = - docletSrc.replace(/^\/\*\*+/, "") // remove opening slash+stars - .replace(/\**\*\/$/, "\\Z") // replace closing star slash with end-marker - .replace(/^\s*(\* ?|\\Z)/gm, "") // remove left margin like: spaces+star or spaces+end-marker - .replace(/\s*\\Z$/g, ""); // remove end-marker + docletSrc.replace(/^\/\*\*+/, '') // remove opening slash+stars + .replace(/\**\*\/$/, "\\Z") // replace closing star slash with end-marker + .replace(/^\s*(\* ?|\\Z)/gm, '') // remove left margin like: spaces+star or spaces+end-marker + .replace(/\s*\\Z$/g, ''); // remove end-marker return docletSrc; } @@ -1855,24 +1891,25 @@ function unwrap(docletSrc) { * Inverse operation of unwrap. * * The prefix for lines is fixed to be " * ", lines are separated with '\n', independent from the platform. - * - * @param {string|Array} lines + * @param {string|string[]} lines Multiline string or an array of lines + * @returns {string} Full comment string created from the line(s) */ function wrap(lines) { if ( typeof lines === "string" ) { lines = lines.split(/\r\n?|\n/); } - return "/**\n * " + lines.join("\n * ") + "\n */"; + return "/**\n * " + lines.join('\n * ') + "\n */"; } /** - * Preprocesses a JSDoc comment string to ensure some UI5 standards. + * Pre-processes a JSDoc comment string to ensure some UI5 standards. * * @param {event} e Event for the new comment - * @returns {event} + * @returns {event} Returns the modified event */ function preprocessComment(e) { - let src = e.comment; + + var src = e.comment; // add a default visibility if ( !/@private|@public|@protected|@sap-restricted|@ui5-restricted/.test(src) ) { @@ -1883,15 +1920,15 @@ function preprocessComment(e) { } if ( /@class/.test(src) && /@static/.test(src) ) { - warning("combination of @class and @static is no longer supported with jsdoc3, converting it to @namespace " + - "and @classdesc: (line " + e.lineno + ")"); + warning("combination of @class and @static is no longer supported with jsdoc3, converting it to @namespace and @classdesc: (line " + e.lineno + ")"); src = unwrap(src); src = src.replace(/@class/, "@classdesc").replace(/@static/, "@namespace"); src = wrap(src); - // console.log(src); + //console.log(src); } return src; + } // ---- other functionality --------------------------------------------------------------------------- @@ -1899,7 +1936,7 @@ function preprocessComment(e) { // HACK: override cli.exit() to avoid that JSDoc3 exits the VM if ( pluginConfig.noExit ) { info("disabling exit() call"); - require( path.join(global.env.dirname, "cli") ).exit = function(retval) { + require( path.join(global.env.dirname, 'cli') ).exit = function(retval) { info("cli.exit(): do nothing (ret val=" + retval + ")"); }; } @@ -1908,20 +1945,19 @@ if ( pluginConfig.noExit ) { // ---- exports ---------------------------------------------------------------------------------------- exports.defineTags = function(dictionary) { + /** * a special value that is not 'falsy' but results in an empty string when output * Used for the disclaimer and experimental tag */ - const EMPTY = { - toString: function() { - return ""; - } + var EMPTY = { + toString: function() { return ""; } }; /** * A sapui5 specific tag to add a disclaimer to a symbol */ - dictionary.defineTag("disclaimer", { + dictionary.defineTag('disclaimer', { // value is optional onTagged: function(doclet, tag) { doclet.disclaimer = tag.value || EMPTY; @@ -1931,7 +1967,7 @@ exports.defineTags = function(dictionary) { /** * A sapui5 specific tag to mark a symbol as experimental. */ - dictionary.defineTag("experimental", { + dictionary.defineTag('experimental', { // value is optional onTagged: function(doclet, tag) { doclet.experimental = tag.value || EMPTY; @@ -1939,10 +1975,9 @@ exports.defineTags = function(dictionary) { }); /** - * Re-introduce the deprecated 'final tag. JSDoc used it as a synonym for readonly, - * but we use it to mark classes as final + * Re-introduce the deprecated 'final tag. JSDoc used it as a synonym for readonly, but we use it to mark classes as final */ - dictionary.defineTag("final", { + dictionary.defineTag('final', { mustNotHaveValue: true, onTagged: function(doclet, tag) { doclet.final_ = true; @@ -1954,11 +1989,11 @@ exports.defineTags = function(dictionary) { * 'interface' is like 'class', but without a constructor. * Support for 'interface' might not be complete (only standard UI5 use cases tested) */ - dictionary.defineTag("interface", { - // mustNotHaveValue: true, + dictionary.defineTag('interface', { + //mustNotHaveValue: true, onTagged: function(doclet, tag) { // debug("setting kind of " + doclet.name + " to 'interface'"); - doclet.kind = "interface"; + doclet.kind = 'interface'; if ( tag.value ) { doclet.classdesc = tag.value; } @@ -1968,7 +2003,7 @@ exports.defineTags = function(dictionary) { /** * Classes can declare that they implement a set of interfaces */ - dictionary.defineTag("implements", { + dictionary.defineTag('implements', { mustHaveValue: true, onTagged: function(doclet, tag) { // console.log("setting implements of " + doclet.name + " to 'interface'"); @@ -1986,15 +2021,15 @@ exports.defineTags = function(dictionary) { /** * Set the visibility of a doclet to 'restricted'. */ - dictionary.defineTag("ui5-restricted", { + dictionary.defineTag('ui5-restricted', { onTagged: function(doclet, tag) { - doclet.access = "restricted"; + doclet.access = 'restricted'; if ( tag.value ) { ui5data(doclet).stakeholders = tag.value.trim().split(/(?:\s*,\s*|\s+)/); } } }); - dictionary.defineSynonym("ui5-restricted", "sap-restricted"); + dictionary.defineSynonym('ui5-restricted', 'sap-restricted'); /** * Mark a doclet as synthetic. @@ -2002,7 +2037,7 @@ exports.defineTags = function(dictionary) { * Used for doclets that the autodoc generation creates. This helps the template * later to recognize such doclets and maybe filter them out. */ - dictionary.defineTag("synthetic", { + dictionary.defineTag('synthetic', { mustNotHaveValue: true, onTagged: function(doclet, tag) { doclet.synthetic = true; @@ -2012,7 +2047,7 @@ exports.defineTags = function(dictionary) { /** * Mark a doclet that intentionally updates a previous doclet */ - dictionary.defineTag("ui5-updated-doclet", { + dictionary.defineTag('ui5-updated-doclet', { mustNotHaveValue: true, onTagged: function(doclet, tag) { ui5data(doclet).updatedDoclet = true; @@ -2020,30 +2055,31 @@ exports.defineTags = function(dictionary) { }); /** - * The @hideconstructor tag tells JSDoc that the generated documentation should not display - * the constructor for a class. + * The @hideconstructor tag tells JSDoc that the generated documentation should not display the constructor for a class. * Note: this tag will be natively available in JSDoc >= 3.5.0 */ - dictionary.defineTag("hideconstructor", { + dictionary.defineTag('hideconstructor', { mustNotHaveValue: true, onTagged: function(doclet, tag) { doclet.hideconstructor = true; } }); + }; exports.handlers = { /** * Before all files are parsed, determine the common path prefix of all filenames - * - * @param {Any} e + * @param {object} e Event info object */ - parseBegin: function(e) { + parseBegin : function(e) { + pathPrefixes = env.opts._.reduce(function(result, fileOrDir) { fileOrDir = path.resolve( path.normalize(fileOrDir) ); if ( fs.statSync(fileOrDir).isDirectory() ) { - if ( !fileOrDir.endsWith(path.sep) ) { + // ensure a trailing path separator + if ( fileOrDir.indexOf(path.sep, fileOrDir.length - path.sep.length) < 0 ) { fileOrDir += path.sep; } result.push(fileOrDir); @@ -2056,7 +2092,7 @@ exports.handlers = { } resourceNamePrefixes.forEach(ensureEndingSlash); while ( resourceNamePrefixes.length < pathPrefixes.length ) { - resourceNamePrefixes.push(""); + resourceNamePrefixes.push(''); } debug("path prefixes " + JSON.stringify(pathPrefixes)); @@ -2065,10 +2101,9 @@ exports.handlers = { /** * Log each file before it is parsed - * - * @param {Any} e + * @param {object} e Event info object */ - fileBegin: function(e) { + fileBegin: function (e) { currentProgram = undefined; currentModule = { name: null, @@ -2076,10 +2111,10 @@ exports.handlers = { module: getModuleName(getResourceName(e.filename)), localNames: Object.create(null) }; - debug(currentModule); }, - fileComplete: function(e) { + fileComplete: function (e) { + // debug("module info after parsing: ", currentModule); currentSource = undefined; currentProgram = undefined; currentModule = undefined; @@ -2095,15 +2130,15 @@ exports.handlers = { }, newDoclet: function(e) { - const _ui5data = ui5data(e.doclet); + + var _ui5data = ui5data(e.doclet); // remove code: this is a try to reduce the required heap size if ( e.doclet.meta ) { if ( e.doclet.meta.code ) { e.doclet.meta.code = {}; } - const filepath = (e.doclet.meta.path && e.doclet.meta.path !== "null" ) ? - path.join(e.doclet.meta.path, e.doclet.meta.filename) : e.doclet.meta.filename; + var filepath = (e.doclet.meta.path && e.doclet.meta.path !== 'null' ) ? path.join(e.doclet.meta.path, e.doclet.meta.filename) : e.doclet.meta.filename; e.doclet.meta.__shortpath = getRelativePath(filepath); _ui5data.resource = currentModule.resource; _ui5data.module = currentModule.name || currentModule.module; @@ -2115,8 +2150,7 @@ exports.handlers = { if ( !e.doclet.longname ) { if ( e.doclet.memberof ) { e.doclet.longname = e.doclet.memberof + "." + e.doclet.name; // TODO '.' depends on scope? - warning("found doclet without longname, derived longname: " + - e.doclet.longname + " " + location(e.doclet)); + warning("found doclet without longname, derived longname: " + e.doclet.longname + " " + location(e.doclet)); } else { error("found doclet without longname, could not derive longname " + location(e.doclet)); } @@ -2126,34 +2160,36 @@ exports.handlers = { // try to detect misused memberof if ( e.doclet.memberof && e.doclet.longname.indexOf(e.doclet.memberof) !== 0 ) { warning("potentially unsupported use of @name and @memberof " + location(e.doclet)); - // console.log(e.doclet); + //console.log(e.doclet); } if ( e.doclet.returns - && e.doclet.returns.length > 0 - && e.doclet.returns[0] - && e.doclet.returns[0].type - && e.doclet.returns[0].type.names - && e.doclet.returns[0].type.names[0] === "this" - && e.doclet.memberof ) { + && e.doclet.returns.length > 0 + && e.doclet.returns[0] + && e.doclet.returns[0].type + && e.doclet.returns[0].type.names + && e.doclet.returns[0].type.names[0] === 'this' + && e.doclet.memberof ) { warning("fixing return type 'this' with " + e.doclet.memberof); e.doclet.returns[0].type.names[0] = e.doclet.memberof; } }, - beforeParse: function(e) { + beforeParse : function(e) { msgHeader("parsing " + getRelativePath(e.filename)); currentSource = e.source; }, - parseComplete: function(e) { - const doclets = e.doclets; - let l = doclets.length; let i; let j; let doclet; - // var noprivate = !env.opts.private; - const rAnonymous = /^(~|$)/; + parseComplete : function(e) { + + var doclets = e.doclets; + var l = doclets.length,i,j,doclet; + //var noprivate = !env.opts.private; + var rAnonymous = /^(~|$)/; // remove undocumented symbols, ignored symbols, anonymous functions and their members, scope members for (i = 0, j = 0; i < l; i++) { + doclet = doclets[i]; if ( !doclet.undocumented && !doclet.ignore && @@ -2171,7 +2207,7 @@ exports.handlers = { // sort doclets by name, synthetic, lineno, uid // 'ignore' is a combination of criteria, see function above debug("sorting doclets by name"); - doclets.sort(function(a, b) { + doclets.sort(function(a,b) { if ( a.longname === b.longname ) { if ( a.synthetic === b.synthetic ) { if ( a.meta && b.meta && a.meta.filename == b.meta.filename ) { @@ -2188,6 +2224,7 @@ exports.handlers = { debug("sorting doclets by name done."); for (i = 0, j = 0; i < l; i++) { + doclet = doclets[i]; // add metadata to symbol @@ -2195,8 +2232,8 @@ exports.handlers = { doclet.__ui5.metadata = classInfos[doclet.longname]; // add designtime infos, if configured - let designtimeModule = doclet.__ui5.metadata.designtime; - if ( designtimeModule && typeof designtimeModule !== "string" ) { + var designtimeModule = doclet.__ui5.metadata.designtime; + if ( designtimeModule && typeof designtimeModule !== 'string' ) { designtimeModule = doclet.__ui5.module + ".designtime"; } if ( designtimeModule && designtimeInfos[designtimeModule] ) { @@ -2207,15 +2244,15 @@ exports.handlers = { // derive extends from UI5 APIs if ( doclet.__ui5.metadata.baseType - && !(doclet.augments && doclet.augments.length > 0) ) { + && !(doclet.augments && doclet.augments.length > 0) ) { doclet.augments = doclet.augments || []; - info(" @extends " + doclet.__ui5.metadata.baseType + - " derived from UI5 APIs (" + doclet.longname + ")"); + info(" @extends " + doclet.__ui5.metadata.baseType + " derived from UI5 APIs (" + doclet.longname + ")"); doclet.augments.push(doclet.__ui5.metadata.baseType); } // derive interface implementations from UI5 metadata if ( doclet.__ui5.metadata.interfaces && doclet.__ui5.metadata.interfaces.length ) { + /* eslint-disable no-loop-func */ doclet.__ui5.metadata.interfaces.forEach(function(intf) { doclet.implements = doclet.implements || []; if ( doclet.implements.indexOf(intf) < 0 ) { @@ -2223,11 +2260,12 @@ exports.handlers = { doclet.implements.push(intf); } }); + /* eslint-enable no-loop-func */ } } if ( typeInfos[doclet.longname] ) { - doclet.__ui5.stereotype = "datatype"; + doclet.__ui5.stereotype = 'datatype'; doclet.__ui5.metadata = { basetype: typeInfos[doclet.longname].base, pattern: typeInfos[doclet.longname].pattern, @@ -2238,10 +2276,8 @@ exports.handlers = { // check for duplicates: last one wins if ( j > 0 && doclets[j - 1].longname === doclet.longname ) { if ( !doclets[j - 1].synthetic && !doclet.__ui5.updatedDoclet ) { - // replacing synthetic comments or updating comments are trivial case. Just log non-trivial - // duplicates - debug("ignoring duplicate doclet for " + doclet.longname + ":" + location(doclet) + " overrides " + - location(doclets[j - 1])); + // replacing synthetic comments or updating comments are trivial case. Just log non-trivial duplicates + debug("ignoring duplicate doclet for " + doclet.longname + ":" + location(doclet) + " overrides " + location(doclets[j - 1])); } doclets[j - 1] = doclet; } else { @@ -2255,33 +2291,36 @@ exports.handlers = { } if ( pluginConfig.saveSymbols ) { + fs.mkPath(env.opts.destination); - fs.writeFileSync(path.join(env.opts.destination, "symbols-parseComplete.json"), - JSON.stringify(e.doclets, null, "\t"), "utf8"); + fs.writeFileSync(path.join(env.opts.destination, "symbols-parseComplete.json"), JSON.stringify(e.doclets, null, "\t"), 'utf8'); + } + } }; exports.astNodeVisitor = { visitNode: function(node, e, parser, currentSourceName) { - let comment; + + var comment; if ( node.type === Syntax.Program ) { currentProgram = node; } function processExtendCall(extendCall, comment, commentAlreadyProcessed) { - const doclet = comment && new Doclet(getRawComment(comment), {}); - const classInfo = collectClassInfo(extendCall, doclet); + var doclet = comment && new Doclet(getRawComment(comment), {}); + var classInfo = collectClassInfo(extendCall, doclet); if ( classInfo ) { createAutoDoc(classInfo, comment, extendCall, parser, currentSourceName, commentAlreadyProcessed); } } function processDataType(createCall, comment) { - const doclet = comment && new Doclet(getRawComment(comment), {}); - const typeInfo = collectDataTypeInfo(createCall, doclet); + var doclet = comment && new Doclet(getRawComment(comment), {}); + var typeInfo = collectDataTypeInfo(createCall, doclet); if ( typeInfo ) { createDataTypeAutoDoc(typeInfo, comment, createCall, parser, currentSourceName); } @@ -2298,43 +2337,58 @@ exports.astNodeVisitor = { warning("module has explicit module name " + node.expression.arguments[0].value); */ } + } - if (node.type === Syntax.ReturnStatement && node.argument && - node.argument.type === Syntax.ObjectExpression && /\.designtime\.js$/.test(currentSourceName) ) { + if (node.type === Syntax.ReturnStatement && node.argument && node.argument.type === Syntax.ObjectExpression && /\.designtime\.js$/.test(currentSourceName) ) { + // assume this node to return designtime metadata. Collect it and remember it by its module name - const oDesigntimeInfo = collectDesigntimeInfo(node); + var oDesigntimeInfo = collectDesigntimeInfo(node); if ( oDesigntimeInfo ) { designtimeInfos[currentModule.module] = oDesigntimeInfo; info("collected designtime info " + currentModule.module); } + } else if ( node.type === Syntax.ExpressionStatement && isExtendCall(node.expression) ) { + // Something.extend(...) -- return value (new class) is not used in an assignment // className = node.expression.arguments[0].value; comment = getLeadingCommentNode(node) || getLeadingCommentNode(node.expression); // console.log("ast node with comment " + comment); processExtendCall(node.expression, comment); - } else if ( node.type === Syntax.VariableDeclaration && node.declarations.length == 1 && - isExtendCall(node.declarations[0].init) ) { - // var NewClass = Something.extend(...) - // className = node.declarations[0].init.arguments[0].value; - comment = getLeadingCommentNode(node) || getLeadingCommentNode(node.declarations[0]); - // console.log("ast node with comment " + comment); - processExtendCall(node.declarations[0].init, comment); + } else if ( node.type === Syntax.VariableDeclaration ) { + node.declarations.forEach(function(decl, idx) { + if ( isExtendCall(decl.init) ) { + // var NewClass = Something.extend(...) + + // className = node.declarations[0].init.arguments[0].value; + comment = (idx === 0 ? getLeadingCommentNode(node) : undefined) || getLeadingCommentNode(decl); + // console.log("ast node with comment " + comment); + processExtendCall(decl.init, comment); + } + }); + } else if ( node.type === Syntax.ReturnStatement && isExtendCall(node.argument) ) { + // return Something.extend(...) - const className = node.argument.arguments[0].value; + var className = node.argument.arguments[0].value; comment = getLeadingCommentNode(node, className) || getLeadingCommentNode(node.argument, className); // console.log("ast node with comment " + comment); processExtendCall(node.argument, comment, true); - } else if ( node.type === Syntax.ExpressionStatement && node.expression.type === Syntax.AssignmentExpression && - isCreateDataTypeCall(node.expression.right) ) { - // thisLib.TypeName = DataType.createType( ... ) - comment = getLeadingCommentNode(node) || getLeadingCommentNode(node.expression); - processDataType(node.expression.right); + } else if ( node.type === Syntax.ExpressionStatement && node.expression.type === Syntax.AssignmentExpression ) { + + if ( isCreateDataTypeCall(node.expression.right) ) { + + // thisLib.TypeName = DataType.createType( ... ) + comment = getLeadingCommentNode(node) || getLeadingCommentNode(node.expression); + processDataType(node.expression.right); + // TODO remember knowledge about type and its name (left hand side of assignment) + + } + } } diff --git a/lib/processors/jsdoc/lib/ui5/template/publish.js b/lib/processors/jsdoc/lib/ui5/template/publish.js index f4cbc9d17..4ea98a10c 100644 --- a/lib/processors/jsdoc/lib/ui5/template/publish.js +++ b/lib/processors/jsdoc/lib/ui5/template/publish.js @@ -1,130 +1,143 @@ /* * JSDoc3 template for UI5 documentation generation. + * + * (c) Copyright 2009-2019 SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ -/* global env: true */ -/* eslint strict: [2, "global"], max-len: ["warn", 180], no-console: "off", guard-for-in: "off", no-invalid-this: "off", no-useless-escape: "off" */ +/*global env: true, require, exports */ +/*eslint strict: [2, "global"]*/ "use strict"; /* imports */ -const template = require("jsdoc/template"); -const helper = require("jsdoc/util/templateHelper"); -const fs = require("jsdoc/fs"); -const doclet = require("jsdoc/doclet"); -const path = require("jsdoc/path"); +var template = require('jsdoc/template'), + helper = require('jsdoc/util/templateHelper'), + fs = require('jsdoc/fs'), + doclet = require('jsdoc/doclet'), + path = require('jsdoc/path'); /* globals, constants */ -const MY_TEMPLATE_NAME = "ui5"; -const ANONYMOUS_LONGNAME = doclet.ANONYMOUS_LONGNAME; -const A_SECURITY_TAGS = [ - { - name: "SecSource", - caption: "Taint Source", - description: "APIs that might introduce tainted data into an application, e.g. due to user input or network access", - params: ["out", "flags"] - }, - { - name: "SecEntryPoint", - caption: "Taint Entry Point", - description: "APIs that are called implicitly by a framework or server and trigger execution of application logic", - params: ["in", "flags"] - }, - { - name: "SecSink", - caption: "Taint Sink", - description: "APIs that pose a security risk when they receive tainted data", - params: ["in", "flags"] - }, - { - name: "SecPassthrough", - caption: "Taint Passthrough", - description: "APIs that might propagate tainted data when they receive it as input", - params: ["in", "out", "flags"] - }, - { - name: "SecValidate", - caption: "Validation", - description: "APIs that (partially) cleanse tainted data so that it no longer poses a security risk " + - "in the further data flow of an application", - params: ["in", "out", "flags"] - } -]; - -const rSecurityTags = new RegExp(A_SECURITY_TAGS.map(function($) { - return $.name.toLowerCase(); -}).join("|"), "i"); -// debug(A_SECURITY_TAGS.map(function($) {return $.name; }).join('|')); - -const templateConf = (env.conf.templates || {})[MY_TEMPLATE_NAME] || {}; - - -const pluginConf = templateConf; - - -let conf = {}; +var MY_TEMPLATE_NAME = "ui5", + ANONYMOUS_LONGNAME = doclet.ANONYMOUS_LONGNAME, + A_SECURITY_TAGS = [ + { + name : "SecSource", + caption : "Taint Source", + description : "APIs that might introduce tainted data into an application, e.g. due to user input or network access", + params : ["out","flags"] + }, + { + name : "SecEntryPoint", + caption : "Taint Entry Point", + description: "APIs that are called implicitly by a framework or server and trigger execution of application logic", + params : ["in","flags"] + }, + { + name : "SecSink", + caption : "Taint Sink", + description : "APIs that pose a security risk when they receive tainted data", + params : ["in","flags"] + }, + { + name : "SecPassthrough", + caption : "Taint Passthrough", + description : "APIs that might propagate tainted data when they receive it as input", + params : ["in","out","flags"] + }, + { + name : "SecValidate", + caption : "Validation", + description : "APIs that (partially) cleanse tainted data so that it no longer poses a security risk in the further data flow of an application", + params : ["in","out","flags"] + } + ]; +var rSecurityTags = new RegExp(A_SECURITY_TAGS.map(function($) {return $.name.toLowerCase(); }).join('|'), "i"); + //debug(A_SECURITY_TAGS.map(function($) {return $.name; }).join('|')); -let view; +var templateConf = (env.conf.templates || {})[MY_TEMPLATE_NAME] || {}, + pluginConf = templateConf, + conf = {}, + view; -let __db; -let __longnames; -const __missingLongnames = {}; +var __db; +var __longnames; +var __missingLongnames = {}; /** * Maps the symbol 'longname's to the unique filename that contains the documentation of that symbol. - * This map is maintained to deal with names that only differ in case (e.g. the namespace sap.ui.model.type - * and the class sap.ui.model.Type). + * This map is maintained to deal with names that only differ in case (e.g. the namespace sap.ui.model.type and the class sap.ui.model.Type). */ -let __uniqueFilenames = {}; +var __uniqueFilenames = {}; -function info(...args) { +/* eslint-disable no-console */ +function info() { if ( env.opts.verbose || env.opts.debug ) { - console.log(...args); + console.log.apply(console, arguments); } } -function warning(...args) { +function warning(msg) { + var args = Array.prototype.slice.apply(arguments); args[0] = "**** warning: " + args[0]; - console.log(...args); + console.log.apply(console, args); } -function error(...args) { +function error(msg) { + var args = Array.prototype.slice.apply(arguments); args[0] = "**** error: " + args[0]; - console.log(...args); + console.log.apply(console, args); } -function debug(...args) { +function debug() { if ( env.opts.debug ) { - console.log(...args); + console.log.apply(console, arguments); + } +} +/* eslint-disable no-console */ + +function compare(v1, v2) { + if ( v1 !== v2 ) { + return v1 < v2 ? -1 : 1; } + return 0; } -function merge(target, ...args) { - for (let i = 0; i < args.length; i++) { - const source = args[i]; - Object.keys(source).forEach(function(p) { - const v = source[p]; - target[p] = ( v.constructor === Object ) ? merge(target[p] || {}, v) : v; +function merge(target, source) { + if ( source != null ) { + // simple single source merge + Object.keys(source).forEach(function(prop) { + var value = source[prop]; + if ( value != null && value.constructor === Object ) { + merge(target[prop] || {}, value); + } else { + target[prop] = value; + } }); } + // if there are more sources, merge them, too + for (var i = 2; i < arguments.length; i++) { + merge(target, arguments[i]); + } return target; } -function lookup(longname /* , variant*/) { - const key = longname; // variant ? longname + "|" + variant : longname; +function lookup(longname /*, variant*/) { + var key = longname; // variant ? longname + "|" + variant : longname; if ( !Object.prototype.hasOwnProperty.call(__longnames, key) ) { __missingLongnames[key] = (__missingLongnames[key] || 0) + 1; - const oResult = __db({longname: longname}); + var oResult = __db({longname: longname /*, variant: variant ? variant : {isUndefined: true}*/}); __longnames[key] = oResult.first(); } return __longnames[key]; } -const externalSymbols = {}; +var externalSymbols = {}; function loadExternalSymbols(apiJsonFolder) { - let files; + + var files; try { files = fs.readdirSync(templateConf.apiJsonFolder); @@ -135,10 +148,10 @@ function loadExternalSymbols(apiJsonFolder) { if ( files && files.length ) { files.forEach(function(localFileName) { + var file = path.join(templateConf.apiJsonFolder, localFileName); try { - const file = path.join(templateConf.apiJsonFolder, localFileName); - const sJSON = fs.readFileSync(file, "UTF-8"); - const data = JSON.parse(sJSON); + var sJSON = fs.readFileSync(file, 'UTF-8'); + var data = JSON.parse(sJSON); if ( !Array.isArray(data.symbols) ) { throw new TypeError("api.json does not contain a 'symbols' array"); } @@ -147,67 +160,60 @@ function loadExternalSymbols(apiJsonFolder) { externalSymbols[symbol.name] = symbol; }); } catch (e) { - error("failed to load symbols from " + localFileName + ": " + (e.message || e)); + error("failed to load symbols from " + file + ": " + (e.message || e)); } }); } } +function isModuleExport($) { + return $.longname.startsWith("module:") && $.longname.search(/[.#~]/) < 0; +} + function isaClass($) { - return /^(namespace|interface|class|typedef)$/.test($.kind) || ($.kind === "member" && $.isEnum); + return /^(namespace|interface|class|typedef)$/.test($.kind) || ($.kind === 'member' && $.isEnum )/* isNonEmptyNamespace($) */; } -const REGEXP_ARRAY_TYPE = /^Array\.<(.*)>$/; +function supportsInheritance($) { + return /^(interface|class|typedef)$/.test($.kind); +} -// ---- Version class --------------------------------------------------------------------------------------- +/* + * Returns true for any symbol that should appear in the API reference index of the SDK. + * + * In a perfect world, such symbols would be + * - default exports of AMD modules (named 'module:some/module) + * - classes, interfaces, enums, typedefs and namespaces, all with global names whose parents are all namespaces + * In the less perfect documentation build, the criterion 'whose parents are all namespaces' is ignored + */ +function isFirstClassSymbol($) { + return /^(namespace|interface|class|typedef)$/.test($.kind) || ($.kind === 'member' && $.isEnum || isModuleExport($) )/* isNonEmptyNamespace($) */; +} -const Version = (function() { - const rVersion = /^[0-9]+(?:\.([0-9]+)(?:\.([0-9]+))?)?(.*)$/; - /* - * Returns a Version instance created from the given parameters. - * - * This function can either be called as a constructor (using new) or as a normal function. - * It always returns an immutable Version instance. - * - * The parts of the version number (major, minor, patch, suffix) can be provided in several ways: - *
      - *
    • Version("1.2.3-SNAPSHOT") - as a dot-separated string. Any non-numerical char or a dot - * followed by a non-numerical char starts the suffix portion. - * Any missing major, minor or patch versions will be set to 0.
    • - *
    • Version(1,2,3,"-SNAPSHOT") - as individual parameters. Major, minor and patch must be integer numbers or - * empty, suffix must be a string not starting with digits.
    • - *
    • Version([1,2,3,"-SNAPSHOT"]) - as an array with the individual parts. The same type restrictions - * apply as before.
    • - *
    • Version(otherVersion) - as a Version instance (cast operation). Returns the given instance instead of - * creating a new one.
    • - *
    - * - * To keep the code size small, this implementation mainly validates the single string variant. - * All other variants are only validated to some degree. It is the responsibility of the caller to - * provide proper parts. - * - * @param {int|string|any[]|jQuery.sap.Version} vMajor the major part of the version (int) or any of the - * single parameter variants explained above. - * @param {int} iMinor the minor part of the version number - * @param {int} iPatch the patch part of the version number - * @param {string} sSuffix the suffix part of the version number - * @returns {jQuery.sap.Version} the version object as determined from the parameters +var REGEXP_ARRAY_TYPE = /^Array\.<(.*)>$/; + +// ---- Version class ----------------------------------------------------------------------------------------------------------------------------------------------------------- + +var Version = (function() { + + var rVersion = /^[0-9]+(?:\.([0-9]+)(?:\.([0-9]+))?)?(.*)$/; + + /** + * Creates a Version object from the given version string. * - * @class Represents a version consisting of major, minor, patch version and suffix, e.g. '1.2.7-SNAPSHOT'. + * @param {string} versionStr A dot-separated version string * - * @author SAP SE - * @version ${version} + * @classdesc Represents a version consisting of major, minor, patch version and suffix, + * e.g. '1.2.7-SNAPSHOT'. All parts after the major version are optional. * @class - * @public - * @since 1.15.0 - * @name jQuery.sap.Version */ function Version(versionStr) { - const match = rVersion.exec(versionStr) || []; + + var match = rVersion.exec(versionStr) || []; function norm(v) { - v = parseInt(v, 10); + v = parseInt(v); return isNaN(v) ? 0 : v; } @@ -227,6 +233,7 @@ const Version = (function() { enumerable: true, value: String(match[3] || "") }); + } Version.prototype.toMajorMinor = function() { @@ -238,21 +245,23 @@ const Version = (function() { }; Version.prototype.compareTo = function(other) { - return this.major - other.major || + return this.major - other.major || this.minor - other.minor || this.patch - other.patch || - ((this.suffix < other.suffix) ? -1 : (this.suffix === other.suffix) ? 0 : 1); + compare(this.suffix, other.suffix); }; return Version; + }()); // ---- Link class -------------------------------------------------------------------------------------------------------------------------------------------------------------- -// TODO move to separate module +//TODO move to separate module + +var Link = (function() { -const Link = (function() { - const Link = function() { + var Link = function() { }; Link.prototype.toSymbol = function(longname) { @@ -260,7 +269,7 @@ const Link = (function() { longname = String(longname); if ( /#constructor$/.test(longname) ) { if ( !this.innerName ) { - this.innerName = "constructor"; + this.innerName = 'constructor'; } longname = longname.slice(0, -"#constructor".length); } @@ -280,25 +289,26 @@ const Link = (function() { }; Link.prototype.toFile = function(file) { - if ( file != null ) this.file = file; + if ( file != null ) { + this.file = file; + } return this; }; function _makeLink(href, target, tooltip, text) { - return "" + text + ""; + return '' + text + ''; } Link.prototype.toString = function() { - let longname = this.longname; - - - let linkString; + var longname = this.longname, + linkString; if (longname) { + if ( /^(?:(?:ftp|https?):\/\/|\.\.?\/)/.test(longname) ) { // handle real hyperlinks (TODO should be handled with a different "to" method linkString = _makeLink(longname, this.targetName, this.tooltip, this.text || longname); @@ -309,6 +319,7 @@ const Link = (function() { } else { linkString = this._makeSymbolLink(longname); } + } else if (this.file) { linkString = _makeLink(Link.base + this.file, this.targetName, null, this.text || this.file); } @@ -316,47 +327,53 @@ const Link = (function() { return linkString; }; - const missingTypes = {}; + var missingTypes = {}; Link.getMissingTypes = function() { return Object.keys(missingTypes); }; Link.prototype._makeSymbolLink = function(longname) { + // normalize .prototype. and # - longname = longname.replace(/\.prototype\./g, "#"); + longname = longname.replace(/\.prototype\./g, '#'); // if it is an internal reference, then don't validate against symbols, just create a link if ( longname.charAt(0) == "#" ) { - return _makeLink(longname + (this.innerName ? "#" + this.innerName : ""), this.targetName, this.tooltip, - this.text || longname.slice(1)); + + return _makeLink(longname + (this.innerName ? "#" + this.innerName : ""), this.targetName, this.tooltip, this.text || longname.slice(1)); + } - const linkTo = lookup(longname); + var linkTo = lookup(longname); // if there is no symbol by that name just return the name unaltered if ( !linkTo ) { + missingTypes[longname] = true; return this.text || longname; + } // it's a full symbol reference (potentially to another file) - let mainSymbol; let anchor; - if ( (linkTo.kind === "member" && !linkTo.isEnum) || linkTo.kind === "constant" || linkTo.kind === "function" || - linkTo.kind === "event" ) { // it's a method or property + var mainSymbol, anchor; + if ( (linkTo.kind === 'member' && !linkTo.isEnum) || linkTo.kind === 'constant' || linkTo.kind === 'function' || linkTo.kind === 'event' ) { // it's a method or property + mainSymbol = linkTo.memberof; - anchor = ( linkTo.kind === "event" ? "event:" : "") + Link.symbolNameToLinkName(linkTo); + anchor = ( linkTo.kind === 'event' ? "event:" : "") + Link.symbolNameToLinkName(linkTo); + } else { + mainSymbol = linkTo.longname; anchor = this.innerName; + } - return _makeLink(Link.baseSymbols + __uniqueFilenames[mainSymbol] + conf.ext + (anchor ? "#" + anchor : ""), - this.targetName, this.tooltip, this.text || longname); + return _makeLink(Link.baseSymbols + __uniqueFilenames[mainSymbol] + conf.ext + (anchor ? "#" + anchor : ""), this.targetName, this.tooltip, this.text || longname); }; Link.symbolNameToLinkName = function(symbol) { - let linker = ""; - if ( symbol.scope === "static" ) { + var linker = ""; + if ( symbol.scope === 'static' ) { linker = "."; } else if (symbol.isInner) { linker = "-"; // TODO-migrate? @@ -365,23 +382,25 @@ const Link = (function() { }; return Link; + }()); + // ---- publish() - main entry point for JSDoc templates ------------------------------------------------------------------------------------------------------- /* Called automatically by JsDoc Toolkit. */ function publish(symbolSet) { + info("entering sapui5 template"); // create output dir fs.mkPath(env.opts.destination); - // if ( symbolSet().count() < 20000 ) { - // info("writing raw symbols to " + path.join(env.opts.destination, "symbols-unpruned-ui5.json")); - // fs.writeFileSync(path.join(env.opts.destination, "symbols-unpruned-ui5.json"), - // JSON.stringify(symbolSet().get(), filter, "\t"), 'utf8'); - // } +// if ( symbolSet().count() < 20000 ) { +// info("writing raw symbols to " + path.join(env.opts.destination, "symbols-unpruned-ui5.json")); +// fs.writeFileSync(path.join(env.opts.destination, "symbols-unpruned-ui5.json"), JSON.stringify(symbolSet().get(), filter, "\t"), 'utf8'); +// } info("before prune: " + symbolSet().count() + " symbols."); symbolSet = helper.prune(symbolSet); @@ -398,40 +417,34 @@ function publish(symbolSet) { loadExternalSymbols(templateConf.apiJsonFolder); } - const templatePath = path.join(env.opts.template, "tmpl/"); + var templatePath = path.join(env.opts.template, 'tmpl/'); info("using templates from '" + templatePath + "'"); view = new template.Template(templatePath); - function filter(key, value) { - if ( key === "meta" ) { - // return; + function filter(key,value) { + if ( key === 'meta' ) { + //return; } - if ( key === "__ui5" && value ) { - const v = { + if ( key === '__ui5' && value ) { + var v = { resource: value.resource, module: value.module, stakeholders: value.stakeholders }; if ( value.derived ) { - v.derived = value.derived.map(function($) { - return $.longname; - }); + v.derived = value.derived.map(function($) { return $.longname; }); } if ( value.base ) { v.base = value.base.longname; } if ( value.implementations ) { - v.base = value.implementations.map(function($) { - return $.longname; - }); + v.base = value.implementations.map(function($) { return $.longname; }); } if ( value.parent ) { v.parent = value.parent.longname; } if ( value.children ) { - v.children = value.children.map(function($) { - return $.longname; - }); + v.children = value.children.map(function($) { return $.longname; }); } return v; } @@ -439,61 +452,60 @@ function publish(symbolSet) { } // now resolve relationships - const aRootNamespaces = createNamespaceTree(); - const hierarchyRoots = createInheritanceTree(); + var aRootNamespaces = createNamespaceTree(); + var hierarchyRoots = createInheritanceTree(); collectMembers(); mergeEventDocumentation(); if ( symbolSet().count() < 20000 ) { info("writing raw symbols to " + path.join(env.opts.destination, "symbols-pruned-ui5.json")); - fs.writeFileSync(path.join(env.opts.destination, "symbols-pruned-ui5.json"), JSON.stringify(symbolSet().get(), - filter, "\t"), "utf8"); + fs.writeFileSync(path.join(env.opts.destination, "symbols-pruned-ui5.json"), JSON.stringify(symbolSet().get(), filter, "\t"), 'utf8'); } // used to allow Link to check the details of things being linked to Link.symbolSet = symbolSet; // get an array version of the symbol set, useful for filtering - const symbols = symbolSet().get(); + var symbols = symbolSet().get(); // ----- - const PUBLISHING_VARIANTS = { + var PUBLISHING_VARIANTS = { - "apixml": { - defaults: { + "apixml" : { + defaults : { apiXmlFile: path.join(env.opts.destination, "jsapi.xml") }, - processor: function(conf) { + processor : function(conf) { createAPIXML(symbols, conf.apiXmlFile, { legacyContent: true }); } }, - "apijson": { - defaults: { + "apijson" : { + defaults : { apiJsonFile: path.join(env.opts.destination, "api.json") }, - processor: function(conf) { + processor : function(conf) { createAPIJSON(symbols, conf.apiJsonFile); } }, - "fullapixml": { - defaults: { + "fullapixml" : { + defaults : { fullXmlFile: path.join(env.opts.destination, "fulljsapi.xml") }, - processor: function(conf) { + processor : function(conf) { createAPIXML(symbols, conf.fullXmlFile, { roots: aRootNamespaces, - omitDefaults: conf.omitDefaultsInFullXml, + omitDefaults : conf.omitDefaultsInFullXml, resolveInheritance: true }); } }, - "apijs": { + "apijs" : { defaults: { jsapiFile: path.join(env.opts.destination, "api.js") }, @@ -502,8 +514,8 @@ function publish(symbolSet) { } }, - "full": { - defaults: { + "full" : { + defaults : { outdir: path.join(env.opts.destination, "full/"), contentOnly: false, hierarchyIndex: true @@ -513,12 +525,10 @@ function publish(symbolSet) { } }, - "public": { + "public" : { defaults: { outdir: path.join(env.opts.destination, "public/"), - filter: function($) { - return $.access === "public" || $.access === "protected" || $.access == null; - }, + filter: function($) { return $.access === 'public' || $.access === 'protected' || $.access == null; }, contentOnly: false, hierarchyIndex: true }, @@ -527,12 +537,10 @@ function publish(symbolSet) { } }, - "demokit": { + "demokit" : { defaults: { outdir: path.join(env.opts.destination, "demokit/"), - filter: function($) { - return $.access === "public" || $.access === "protected" || $.access == null; - }, + filter: function($) { return $.access === 'public' || $.access === 'protected' || $.access == null; }, contentOnly: true, modulePages: true, hierarchyIndex: false, @@ -548,11 +556,10 @@ function publish(symbolSet) { } }, - "demokit-internal": { + "demokit-internal" : { defaults: { outdir: path.join(env.opts.destination, "demokit-internal/"), - // filter: function($) { return $.access === 'public' || $.access === 'protected' || $.access === - // 'restricted' || $.access == null; }, + // filter: function($) { return $.access === 'public' || $.access === 'protected' || $.access === 'restricted' || $.access == null; }, contentOnly: true, modulePages: true, hierarchyIndex: false, @@ -570,18 +577,20 @@ function publish(symbolSet) { }; - const now = new Date(); + var now = new Date(); info("start publishing"); - for (let i = 0; i < templateConf.variants.length; i++) { - let vVariant = templateConf.variants[i]; + for (var i = 0; i < templateConf.variants.length; i++) { + + var vVariant = templateConf.variants[i]; if ( typeof vVariant === "string" ) { - vVariant = {variant: vVariant}; + 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 @@ -592,16 +601,13 @@ function publish(symbolSet) { // Note: trailing slash expected for dirs conf = merge({ ext: ".html", - filter: function($) { - return true; - }, + 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(), + creationDate : now.getFullYear() + "-" + (now.getMonth() + 1) + "-" + now.getDay() + " " + now.getHours() + ":" + now.getMinutes(), outdir: env.opts.destination }, PUBLISHING_VARIANTS[vVariant.variant].defaults, templateConf, vVariant); @@ -612,40 +618,48 @@ function publish(symbolSet) { PUBLISHING_VARIANTS[vVariant.variant].processor(conf); info("done with variant " + vVariant.variant); + } else { + info("cannot publish unknown variant '" + vVariant.variant + "' (ignored)"); + } } - const builtinSymbols = templateConf.builtinSymbols; + var builtinSymbols = templateConf.builtinSymbols; if ( builtinSymbols ) { Link.getMissingTypes().filter(function($) { return builtinSymbols.indexOf($) < 0; }).sort().forEach(function($) { - error(" unresolved reference: " + $); + // TODO instead of filtering topic: and fiori: links out here, they should be correctly linked in the template + if ( !/\{@link (?:topic:|fiori:)/.test($) ) { + error(" unresolved reference: " + $); + } }); } info("publishing done."); + } -// ---- namespace tree -------------------------------------------------------------------------------- +//---- namespace tree -------------------------------------------------------------------------------- -/** +/* * Completes the tree of namespaces. Namespaces for which content is available * but which have not been documented are created as dummy without documentation. */ function createNamespaceTree() { + info("create namespace tree (" + __db().count() + " symbols)"); - const aRootNamespaces = []; - const aTypes = __db(function() { - return isaClass(this); - }).get(); + var aRootNamespaces = []; + var aTypes = __db(function() { return isFirstClassSymbol(this); }).get(); + + for (var i = 0; i < aTypes.length; i++) { // loop with a for-loop as it can handle concurrent modifications - for (let i = 0; i < aTypes.length; i++) { // loop with a for-loop as it can handle concurrent modifications - const symbol = aTypes[i]; + var symbol = aTypes[i]; if ( symbol.memberof ) { - let parent = lookup(symbol.memberof); + + var parent = lookup(symbol.memberof); if ( !parent ) { warning("create missing namespace '" + symbol.memberof + "' (referenced by " + symbol.longname + ")"); parent = makeNamespace(symbol.memberof); @@ -656,8 +670,11 @@ function createNamespaceTree() { symbol.__ui5.parent = parent; parent.__ui5.children = parent.__ui5.children || []; parent.__ui5.children.push(symbol); + } else if ( symbol.longname !== ANONYMOUS_LONGNAME ) { + aRootNamespaces.push(symbol); + } } @@ -665,24 +682,25 @@ function createNamespaceTree() { } function makeNamespace(memberof) { + info("adding synthetic namespace symbol " + memberof); - const comment = [ + var comment = [ "@name " + memberof, "@namespace", "@synthetic", "@public" ]; - const symbol = new doclet.Doclet("/**\n * " + comment.join("\n * ") + "\n */", {}); + var symbol = new doclet.Doclet("/**\n * " + comment.join("\n * ") + "\n */", {}); symbol.__ui5 = {}; return symbol; } -// ---- inheritance hierarchy ---------------------------------------------------------------------------- +//---- inheritance hierarchy ---------------------------------------------------------------------------- -/** +/* * Calculates the inheritance hierarchy for all class/interface/namespace symbols. * Each node in the tree has the content * @@ -695,9 +713,10 @@ function makeNamespace(memberof) { * */ function createInheritanceTree() { + function makeDoclet(longname, lines) { lines.push("@name " + longname); - const newDoclet = new doclet.Doclet("/**\n * " + lines.join("\n * ") + "\n */", {}); + var newDoclet = new doclet.Doclet("/**\n * " + lines.join("\n * ") + "\n */", {}); newDoclet.__ui5 = {}; __longnames[longname] = newDoclet; __db.insert(newDoclet); @@ -706,12 +725,10 @@ function createInheritanceTree() { info("create inheritance tree (" + __db().count() + " symbols)"); - const oTypes = __db(function() { - return isaClass(this); - }); - const aRootTypes = []; + var oTypes = __db(function() { return supportsInheritance(this); }); + var aRootTypes = []; - let oObject = lookup("Object"); + var oObject = lookup("Object"); if ( !oObject ) { oObject = makeDoclet("Object", [ "@class", @@ -722,14 +739,14 @@ function createInheritanceTree() { } function getOrCreateClass(sClass, sExtendingClass) { - let oClass = lookup(sClass); + var oClass = lookup(sClass); if ( !oClass ) { warning("create missing class " + sClass + " (extended by " + sExtendingClass + ")"); - let sBaseClass = "Object"; + var sBaseClass = 'Object'; if ( externalSymbols[sClass] ) { sBaseClass = externalSymbols[sClass].extends || sBaseClass; } - const oBaseClass = getOrCreateClass(sBaseClass, sClass); + var oBaseClass = getOrCreateClass(sBaseClass, sClass); oClass = makeDoclet(sClass, [ "@extends " + sBaseClass, "@class", @@ -745,11 +762,12 @@ function createInheritanceTree() { // link them according to the inheritance infos oTypes.each(function(oClass) { - if ( oClass.longname === "Object") { + + if ( oClass.longname === 'Object') { return; } - let sBaseClass = "Object"; + var sBaseClass = "Object"; if ( oClass.augments && oClass.augments.length > 0 ) { if ( oClass.augments.length > 1 ) { warning("multiple inheritance detected in " + oClass.longname); @@ -759,14 +777,14 @@ function createInheritanceTree() { aRootTypes.push(oClass); } - const oBaseClass = getOrCreateClass(sBaseClass, oClass.longname); + var oBaseClass = getOrCreateClass(sBaseClass, oClass.longname); oClass.__ui5.base = oBaseClass; oBaseClass.__ui5.derived = oBaseClass.__ui5.derived || []; oBaseClass.__ui5.derived.push(oClass); if ( oClass.implements ) { - for (let j = 0; j < oClass.implements.length; j++) { - let oInterface = lookup(oClass.implements[j]); + for (var j = 0; j < oClass.implements.length; j++) { + var oInterface = lookup(oClass.implements[j]); if ( !oInterface ) { warning("create missing interface " + oClass.implements[j]); oInterface = makeDoclet(oClass.implements[j], [ @@ -790,9 +808,9 @@ function createInheritanceTree() { return; } oSymbol.__ui5.stereotype = sStereotype; - const derived = oSymbol.__ui5.derived; + var derived = oSymbol.__ui5.derived; if ( derived ) { - for (let i = 0; i < derived.length; i++ ) { + for (var i = 0; i < derived.length; i++ ) { if ( !derived[i].__ui5.stereotype ) { setStereotype(derived[i], sStereotype); } @@ -808,7 +826,7 @@ function createInheritanceTree() { // check for cyclic inheritance (not supported) // Note: the check needs to run bottom up, not top down as a typical cyclic dependency never will end at the root node oTypes.each(function(oStartClass) { - const visited = {}; + var visited = {}; function visit(oClass) { if ( visited[oClass.longname] ) { throw new Error("cyclic inheritance detected: " + JSON.stringify(Object.keys(visited))); @@ -834,8 +852,8 @@ function createInheritanceTree() { function collectMembers() { __db().each(function($) { if ( $.memberof ) { - const parent = lookup($.memberof); - if ( parent && isaClass(parent) ) { + var parent = lookup($.memberof); + if ( parent /* && supportsInheritance(parent) */ ) { parent.__ui5.members = parent.__ui5.members || []; parent.__ui5.members.push($); } @@ -844,39 +862,36 @@ function collectMembers() { } function mergeEventDocumentation() { - console.log("merging JSDoc event documentation into UI5 metadata"); - const oTypes = __db(function() { - return isaClass(this); - }); + debug("merging JSDoc event documentation into UI5 metadata"); + + var oTypes = __db(function() { return isaClass(this); }); oTypes.each(function(symbol) { - const metadata = symbol.__ui5.metadata; - const members = symbol.__ui5.members; + + var metadata = symbol.__ui5.metadata; + var members = symbol.__ui5.members; if ( !metadata || !metadata.events || Object.keys(metadata.events).length <= 0 || !members ) { return; } - // console.log('mergeing events for ' + symbol.longname); + // debug('merging events for ' + symbol.longname); members.forEach(function($) { - if ( $.kind === "event" && !$.inherited - && ($.access === "public" || $.access === "protected" || $.access == null) - && metadata.events[$.name] - && Array.isArray($.params) - && !$.synthetic ) { - const event = metadata.events[$.name]; - let modified = false; - // console.log("<<<<<<<"); - // console.log(event); - // console.log("======="); - // console.log($); + if ( $.kind === 'event' && !$.inherited + && ($.access === 'public' || $.access === 'protected' || $.access == null) + && metadata.events[$.name] + && Array.isArray($.params) + && !$.synthetic ) { + + var event = metadata.events[$.name]; + var modified = false; $.params.forEach(function(param) { - const m = /^\w+\.getParameters\.(.*)$/.exec(param.name); + var m = /^\w+\.getParameters\.(.*)$/.exec(param.name); if ( m ) { - const pname = m[1]; - const ui5param = event.parameters[pname] || ( event.parameters[pname] = {}); + var pname = m[1]; + var ui5param = event.parameters[pname] || ( event.parameters[pname] = {}); if ( ui5param.type == null ) { ui5param.type = listTypes(param.type); modified = true; @@ -889,40 +904,40 @@ function mergeEventDocumentation() { }); if ( modified ) { - console.log(" merged documentation for managed event " + symbol.longname + "#" + $.name); + info(" merged documentation for managed event " + symbol.longname + "#" + $.name); } - // console.log("======="); - // console.log(JSON.stringify(event, null, '\t')); - // console.log(">>>>>>>"); } }); + }); + } // ---- publishing ----------------------------------------------------------------------- function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { + // create output dir fs.mkPath(path.join(conf.outdir, conf.symbolsDir)); - // get a list of all the classes in the symbolset - let classes = symbols(function() { - return isaClass(this) && conf.filter(this); + // get a list of all the first class symbols in the symbolset + var firstClassSymbols = symbols(function() { + return supportsInheritance(this) && conf.filter(this); }).order("longname"); // create unique file names __uniqueFilenames = {}; - let filenames = {}; - classes.get().sort(sortByAlias).forEach(function(symbol) { - let filename = escape(symbol.longname); - if ( filenames.hasOwnProperty(filename.toUpperCase()) && - (filenames[filename.toUpperCase()].longname !== symbol.longname) ) { + var filenames = {}; + firstClassSymbols.get().sort(sortByAlias).forEach(function(symbol) { + var filename = escape(symbol.longname.replace(/^module:/, "")).replace(/\//g, "%25"); + if ( filenames.hasOwnProperty(filename.toUpperCase()) && (filenames[filename.toUpperCase()].longname !== symbol.longname) ) { // find an unused filename by appending "-n" where n is an integer > 0 - let j; - for (j = 1; filenames.hasOwnProperty(filename.toUpperCase() + "-" + j); j++); - warning("duplicate symbol names " + filenames[filename.toUpperCase()].longname + " and " + - symbol.longname + ", renaming the latter to " + filename + "-" + j); + var j = 1; + while (filenames.hasOwnProperty(filename.toUpperCase() + "-" + j)) { + j++; + } + warning("duplicate symbol names " + filenames[filename.toUpperCase()].longname + " and " + symbol.longname + ", renaming the latter to " + filename + "-" + j); filename = filename + "-" + j; } filenames[filename.toUpperCase()] = symbol; @@ -931,21 +946,21 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { filenames = null; // create a class index, displayed in the left-hand column of every class page - let classTemplate; + var classTemplate; if ( !conf.contentOnly ) { info("create embedded class index"); Link.base = "../"; Link.baseSymbols = ""; - classTemplate = "classWithIndex.html.tmpl"; - publish.header = processTemplate("_header.tmpl", classes); - publish.footer = processTemplate("_footer.tmpl", classes); - publish.classesIndex = processTemplate("_navIndex.tmpl", classes); // kept in memory + classTemplate = 'classWithIndex.html.tmpl'; + publish.header = processTemplate("_header.tmpl", firstClassSymbols); + publish.footer = processTemplate("_footer.tmpl", firstClassSymbols); + publish.classesIndex = processTemplate("_navIndex.tmpl", firstClassSymbols); // kept in memory } else { - const newStyle = !!pluginConf.newStyle; + var newStyle = !!pluginConf.newStyle; classTemplate = newStyle ? "class-new.html.tmpl" : "class.html.tmpl"; - publish.header = ""; - publish.footer = ""; - publish.classesIndex = ""; + publish.header = ''; + publish.footer = ''; + publish.classesIndex = ''; // instead create an index as XML Link.base = ""; @@ -957,8 +972,8 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { info("create class/namespace pages"); Link.base = "../"; Link.baseSymbols = ""; - classes.each(function(symbol) { - const sOutName = path.join(conf.symbolsDir, __uniqueFilenames[symbol.longname]) + conf.ext; + firstClassSymbols.each(function(symbol) { + var sOutName = path.join(conf.symbolsDir, __uniqueFilenames[symbol.longname]) + conf.ext; processTemplateAndSave(classTemplate, symbol, sOutName); }); @@ -967,8 +982,8 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { Link.base = "../"; Link.baseSymbols = "../" + conf.symbolsDir; fs.mkPath(path.join(conf.outdir, conf.modulesDir)); - groupByModule(classes.get()).forEach(function(module) { - const sOutName = path.join(conf.modulesDir, module.name.replace(/\//g, "_")) + conf.ext; + groupByModule(firstClassSymbols.get()).forEach(function(module) { + var sOutName = path.join(conf.modulesDir, module.name.replace(/\//g, '_')) + conf.ext; processTemplateAndSave("module.html.tmpl", module, sOutName); }); } @@ -977,12 +992,12 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { info("create global class/namespace index"); Link.base = ""; Link.baseSymbols = conf.symbolsDir; - publish.header = processTemplate("_header.tmpl", classes); - publish.footer = processTemplate("_footer.tmpl", classes); - publish.classesIndex = processTemplate("_navIndex.tmpl", classes); + publish.header = processTemplate("_header.tmpl", firstClassSymbols); + publish.footer = processTemplate("_footer.tmpl", firstClassSymbols); + publish.classesIndex = processTemplate("_navIndex.tmpl", firstClassSymbols); // create the all classes index - processTemplateAndSave("index.html.tmpl", classes, "index" + conf.ext); + processTemplateAndSave("index.html.tmpl", firstClassSymbols, "index" + conf.ext); // create the class hierarchy page if ( conf.hierarchyIndex ) { @@ -996,10 +1011,10 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { info("create API by version index"); Link.base = ""; Link.baseSymbols = conf.symbolsDir; - const sinceSymbols = symbols(function() { - let r = !!this.since && !this.inherited && conf.filter(this); + var sinceSymbols = symbols(function() { + var r = !!this.since && !this.inherited && conf.filter(this); if ( r && this.memberof ) { - const parent = lookup(this.memberof); + var parent = lookup(this.memberof); // filter out symbol when parent is filtered out if ( !parent || !conf.filter(parent) ) { debug("since index: filtering out " + this.longname + ", member of " + this.memberof); @@ -1018,7 +1033,7 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { info("create deprecated API index"); Link.base = ""; Link.baseSymbols = conf.symbolsDir; - const deprecatedSymbols = symbols(function() { + var deprecatedSymbols = symbols(function() { return !!this.deprecated && !this.inherited && conf.filter(this); }).order("longname"); processTemplateAndSave("deprecation.html.tmpl", deprecatedSymbols, "deprecation" + conf.ext); @@ -1028,7 +1043,7 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { info("create experimental API index"); Link.base = ""; Link.baseSymbols = conf.symbolsDir; - const experimentalSymbols = symbols(function() { + var experimentalSymbols = symbols(function() { return !!this.experimental && !this.inherited && conf.filter(this); }).order("longname"); processTemplateAndSave("experimental.html.tmpl", experimentalSymbols, "experimental" + conf.ext); @@ -1037,16 +1052,16 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { if ( conf.securityIndex ) { info("create Security Relevant API index"); - const securityRelevantSymbols = {}; + var securityRelevantSymbols = {}; A_SECURITY_TAGS.forEach(function(oTagDef) { - securityRelevantSymbols[oTagDef.name.toLowerCase()] = {tag: oTagDef, symbols: []}; + securityRelevantSymbols[oTagDef.name.toLowerCase()] = { tag : oTagDef, symbols: [] }; }); symbols().each(function($) { - const tags = $.tags; - if ( !$.inherited && conf.filter($) && tags ) { - for (let i = 0; i < tags.length; i++) { + var tags = $.tags; + if ( !$.inherited && conf.filter($) && tags ) { + for (var i = 0; i < tags.length; i++) { if ( rSecurityTags.test(tags[i].title) ) { - securityRelevantSymbols[tags[i].title.toLowerCase()].symbols.push({symbol: $, tag: tags[i]}); + securityRelevantSymbols[tags[i].title.toLowerCase()].symbols.push({ symbol: $, tag : tags[i]}); } } } @@ -1057,16 +1072,16 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { processTemplateAndSave("security.html.tmpl", securityRelevantSymbols, "security" + conf.ext); } - classes = null; + firstClassSymbols = null; // copy needed mimes info("copy mimes"); // copy the template's static files to outdir - const templatePath = env.opts.template; - const fromDir = path.join(templatePath, "static"); - const staticFiles = fs.ls(fromDir, 3); + var templatePath = env.opts.template; + var fromDir = path.join(templatePath, 'static'); + var staticFiles = fs.ls(fromDir, 3); staticFiles.forEach(function(fileName) { - const toDir = fs.toDir( fileName.replace(fromDir, conf.outdir) ); + var toDir = fs.toDir( fileName.replace(fromDir, conf.outdir) ); fs.mkPath(toDir); fs.copyFileSync(fileName, toDir); }); @@ -1078,55 +1093,59 @@ function publishClasses(symbols, aRootNamespaces, hierarchyRoots) { // ---- helper functions for the templates ---- -const rSinceVersion = /^([0-9]+(?:\.[0-9]+(?:\.[0-9]+)?)?([-.][0-9A-Z]+)?)(?:\s|$)/i; +var rSinceVersion = /^([0-9]+(?:\.[0-9]+(?:\.[0-9]+)?)?([-.][0-9A-Z]+)?)(?:\s|$)/i; function extractVersion(value) { + if ( !value ) { - return; + return undefined; } if ( value === true ) { - value = ""; + value = ''; } else { value = String(value); } - const m = rSinceVersion.exec(value); + var m = rSinceVersion.exec(value); return m ? m[1] : undefined; + } -const rSince = /^(?:as\s+of|since)(?:\s+version)?\s*([0-9]+(?:\.[0-9]+(?:\.[0-9]+)?)?([-.][0-9A-Z]+)?)(?:\.$|\.\s+|[,:]\s*|\s-\s*|\s|$)/i; +var rSince = /^(?:as\s+of|since)(?:\s+version)?\s*([0-9]+(?:\.[0-9]+(?:\.[0-9]+)?)?([-.][0-9A-Z]+)?)(?:\.$|\.\s+|[,:]\s*|\s-\s*|\s|$)/i; function extractSince(value) { + if ( !value ) { - return; + return undefined; } if ( value === true ) { - value = ""; + value = ''; } else { value = String(value); } - const m = rSince.exec(value); + var m = rSince.exec(value); if ( m ) { return { - since: m[1], - pos: m[0].length, - value: value.slice(m[0].length).trim() + since : m[1], + pos : m[0].length, + value : value.slice(m[0].length).trim() }; } return { - pos: 0, + pos : 0, value: value.trim() }; + } function sortByAlias(a, b) { - const partsA = a.longname.split(/[.#]/); - const partsB = b.longname.split(/[.#]/); - let i = 0; + var partsA = a.longname.split(/[.#]/); + var partsB = b.longname.split(/[.#]/); + var i = 0; while ( i < partsA.length && i < partsB.length ) { if ( partsA[i].toLowerCase() < partsB[i].toLowerCase() ) { return -1; @@ -1165,55 +1184,47 @@ function isNonEmptyNamespace($) { /* Just the first sentence (up to a full stop). Should not break on dotted variable names. */ function summarize(desc) { if ( desc != null ) { - desc = String(desc).replace(/\s+/g, " "). - replace(/"'/g, """). - replace(/^(<\/?p>||\s)+/, ""); + desc = String(desc).replace(/\s+/g, ' '). + replace(/"'/g, '"'). + replace(/^(<\/?p>||\s)+/, ''); - const match = /([\w\W]+?\.)[^a-z0-9_$]/i.exec(desc); + var match = /([\w\W]+?\.)[^a-z0-9_$]/i.exec(desc); return match ? match[1] : desc; } } /* Make a symbol sorter by some attribute. */ -function makeSortby(...aFields) { - const aNorms = []; - - const aFuncs = []; - for (let i = 0; i < arguments.length; i++) { +function makeSortby(/* fields ...*/) { + var aFields = Array.prototype.slice.apply(arguments), + aNorms = [], + aFuncs = []; + for (var i = 0; i < arguments.length; i++) { aNorms[i] = 1; - if ( typeof aFields[i] === "function" ) { + if ( typeof aFields[i] === 'function' ) { aFuncs[i] = aFields[i]; continue; } - aFuncs[i] = function($, n) { - return $[n]; - }; + aFuncs[i] = function($,n) { return $[n]; }; if ( aFields[i].indexOf("!") === 0 ) { aNorms[i] = -1; aFields[i] = aFields[i].slice(1); } - if ( aFields[i] === "deprecated" ) { - aFuncs[i] = function($, n) { - return !!$[n]; - }; - } else if ( aFields[i] === "static" ) { - aFields[i] = "scope"; - aFuncs[i] = function($, n) { - return $[n] === "static"; - }; + if ( aFields[i] === 'deprecated' ) { + aFuncs[i] = function($,n) { return !!$[n]; }; + } else if ( aFields[i] === 'static' ) { + aFields[i] = 'scope'; + aFuncs[i] = function($,n) { return $[n] === 'static'; }; } else if ( aFields[i].indexOf("#") === 0 ) { aFields[i] = aFields[i].slice(1); - aFuncs[i] = function($, n) { - return $.comment.getTag(n).length > 0; - }; + aFuncs[i] = function($,n) { return $.comment.getTag(n).length > 0; }; } } return function(a, b) { // info("compare " + a.longname + " : " + b.longname); - let r = 0; let i; let va; let vb; + var r = 0,i,va,vb; for (i = 0; r === 0 && i < aFields.length; i++) { - va = aFuncs[i](a, aFields[i]); - vb = aFuncs[i](b, aFields[i]); + va = aFuncs[i](a,aFields[i]); + vb = aFuncs[i](b,aFields[i]); if ( va && !vb ) { r = -aNorms[i]; } else if ( !va && vb ) { @@ -1221,8 +1232,12 @@ function makeSortby(...aFields) { } else if ( va && vb ) { va = String(va).toLowerCase(); vb = String(vb).toLowerCase(); - if (va < vb) r = -aNorms[i]; - if (va > vb) r = aNorms[i]; + if (va < vb) { + r = -aNorms[i]; + } + if (va > vb) { + r = aNorms[i]; + } } // debug(" " + aFields[i] + ": " + va + " ? " + vb + " = " + r); } @@ -1233,13 +1248,14 @@ function makeSortby(...aFields) { /** Pull in the contents of an external file at the given path. */ function processTemplateAndSave(sTemplateName, oData, sOutputName) { - let sResult = processTemplate(sTemplateName, oData); + var sResult = processTemplate(sTemplateName, oData); if ( conf.normalizeWhitespace && /\.html$/.test(sOutputName) ) { sResult = normalizeWhitespace(sResult); } - const sOutpath = path.join(conf.outdir, sOutputName); + var sOutpath = path.join(conf.outdir, sOutputName); try { - fs.writeFileSync(sOutpath, sResult, "utf8"); + fs.mkPath( path.dirname(sOutpath) ); + fs.writeFileSync(sOutpath, sResult, 'utf8'); } catch (e) { error("failed to write generated file '" + sOutpath + "':" + (e.message || String(e))); } @@ -1247,7 +1263,9 @@ function processTemplateAndSave(sTemplateName, oData, sOutputName) { function processTemplate(sTemplateName, data) { debug("processing template '" + sTemplateName + "' for " + data.longname); - let result; + + var result; + try { result = view.render(sTemplateName, { asPlainSummary: asPlainSummary, @@ -1255,7 +1273,7 @@ function processTemplate(sTemplateName, data) { childrenOfKind: childrenOfKind, conf: conf, data: data, - getConstructorDescription: getConstructorDescription, + getConstructorDescription : getConstructorDescription, getNSClass: getNSClass, groupByVersion: groupByVersion, extractSince: extractSince, @@ -1268,21 +1286,21 @@ function processTemplate(sTemplateName, data) { makeLinkToSymbolFile: makeLinkToSymbolFile, makeSignature: makeSignature, makeSortby: makeSortby, - publish: publish, + publish : publish, formatText: formatText, simpleNameOf: simpleNameOf, sortByAlias: sortByAlias, summarize: summarize, - Version: Version + Version : Version }); } catch (e) { if ( e.source ) { - const filename = path.join(env.opts.destination, sTemplateName + ".js"); - console.log("**** failed to process template, source written to " + filename); + var filename = path.join(env.opts.destination, sTemplateName + ".js"); + error("**** failed to process template, source written to " + filename); fs.mkPath(path.dirname(filename)); - fs.writeFileSync(filename, e.source, "utf8"); + fs.writeFileSync(filename, e.source, 'utf8'); } - console.log("error while processing " + sTemplateName); + error("error while processing " + sTemplateName); throw e; } debug("processing template done."); @@ -1290,25 +1308,24 @@ function processTemplate(sTemplateName, data) { } function groupByVersion(symbols, extractVersion) { - const map = {}; - symbols.forEach(function(symbol) { - const version = extractVersion(symbol); + var map = {}; + symbols.forEach(function(symbol) { - const key = String(version); + var version = extractVersion(symbol), + key = String(version); if ( !map[key] ) { - map[key] = {version: version, symbols: []}; + map[key] = { version: version, symbols : [] }; } map[key].symbols.push(symbol); - }); - const groups = Object.keys(map).map(function(key) { - return map[key]; }); - return groups.sort(function(a, b) { + var groups = Object.keys(map).map(function(key) { return map[key]; }); + + return groups.sort(function(a,b) { if ( !a.version && b.version ) { return -1; } else if ( a.version && !b.version ) { @@ -1321,11 +1338,12 @@ function groupByVersion(symbols, extractVersion) { } function groupByModule(symbols) { - const map = {}; + + var map = {}; function add(key, symbol) { if ( !map[key] ) { - map[key] = {name: key, symbols: []}; + map[key] = { name: key, symbols : [] }; } if ( map[key].symbols.indexOf(symbol) < 0 ) { map[key].symbols.push(symbol); @@ -1333,7 +1351,8 @@ function groupByModule(symbols) { } symbols.forEach(function(symbol) { - const key = symbol.__ui5.module; + + var key = symbol.__ui5.module; if ( key ) { add(key, symbol); @@ -1345,17 +1364,16 @@ function groupByModule(symbols) { }); } } - }); - const groups = Object.keys(map).map(function(key) { - return map[key]; }); + var groups = Object.keys(map).map(function(key) { return map[key]; }); + return groups; } -const REGEXP_TAG = /<(\/?(?:[A-Z][A-Z0-9_-]*:)?[A-Z][A-Z0-9_-]*)(?:\s[^>]*)?>/gi; +var REGEXP_TAG = /<(\/?(?:[A-Z][A-Z0-9_-]*:)?[A-Z][A-Z0-9_-]*)(?:\s[^>]*)?>/gi; /** * Removes unnecessary whitespace from an HTML document: @@ -1364,37 +1382,32 @@ const REGEXP_TAG = /<(\/?(?:[A-Z][A-Z0-9_-]*:)?[A-Z][A-Z0-9_-]*)(?:\s[^>]*)?>/gi * - inside a
     tag, whitespace is preserved
      *
      * Whitespace inside an element tag is not touched (although it could be normalized as well)
    - *
      * @param {string} content raw HTML file
      * @returns {string} HTML file with normalized whitespace
      */
     function normalizeWhitespace(content) {
    -	let compressed = "";
    -
    -
    -	let preformatted = 0;
    -
    -
    -	let p = 0; let m; let text;
    +	var compressed = '',
    +		preformatted = 0,
    +		p = 0, m, text;
     
     	REGEXP_TAG.lastIndex = 0;
    -	while ( m = REGEXP_TAG.exec(content) ) {
    +	while ( (m = REGEXP_TAG.exec(content)) ) {
     		if ( m.index > p ) {
     			text = content.slice(p, m.index);
     			if ( preformatted ) {
     				compressed += text;
    -				// console.log('  "' + text + '" (preformatted)');
    +				// debug('  "' + text + '" (preformatted)');
     			} else {
    -				text = text.replace(/\s+/g, " ");
    +				text = text.replace(/\s+/g,' ');
     				if ( text.trim() ) {
     					compressed += text;
     				}
    -				// console.log('  "' + text + '" (trimmed)');
    +				// debug('  "' + text + '" (trimmed)');
     			}
     		}
     
     		compressed += m[0];
    -		// console.log('  "' + m[0] + '" (tag)');
    +		// debug('  "' + m[0] + '" (tag)');
     		p = m.index + m[0].length;
     
     		if ( /^pre$/i.test(m[1]) ) {
    @@ -1402,19 +1415,20 @@ function normalizeWhitespace(content) {
     		} else if ( /^\/pre$/i.test(m[1]) && preformatted ) {
     			preformatted--;
     		}
    +
     	}
     
     	if ( content.length > p ) {
     		text = content.slice(p, content.length);
     		if ( preformatted ) {
     			compressed += text;
    -			// console.log('  "' + text + '" (preformatted)');
    +			// debug('  "' + text + '" (preformatted)');
     		} else {
    -			text = text.replace(/\s+/g, " ");
    +			text = text.replace(/\s+/g,' ');
     			if ( text.trim() ) {
     				compressed += text;
     			}
    -			// console.log('  "' + text + '" (trimmed)');
    +			// debug('  "' + text + '" (trimmed)');
     		}
     	}
     
    @@ -1427,16 +1441,16 @@ function makeLinkToSymbolFile(longname) {
     
     function simpleNameOf(longname) {
     	longname = String(longname);
    -	const p = longname.lastIndexOf(".");
    +	var p = longname.lastIndexOf('.');
     	return p < 0 ? longname : longname.slice(p + 1);
     }
     
    -function bySimpleName(a, b) {
    +function bySimpleName(a,b) {
     	if ( a === b ) {
     		return 0;
     	}
    -	const simpleA = simpleNameOf(a);
    -	const simpleB = simpleNameOf(b);
    +	var simpleA = simpleNameOf(a);
    +	var simpleB = simpleNameOf(b);
     	if ( simpleA === simpleB ) {
     		return a < b ? -1 : 1;
     	} else {
    @@ -1446,41 +1460,41 @@ function bySimpleName(a, b) {
     
     /* Build output for displaying function parameters. */
     function makeSignature(params) {
    -	const r = ["("]; let desc;
    +	var r = ['('], desc;
     	if ( params ) {
    -		for (let i = 0, p; p = params[i]; i++) {
    +		for (var i = 0, p; (p = params[i]); i++) {
     			// ignore @param tags for 'virtual' params that are used to document members of config-like params
     			// (e.g. like "@param param1.key ...")
    -			if (p.name && p.name.indexOf(".") == -1) {
    +			if (p.name && p.name.indexOf('.') == -1) {
     				if (i > 0) {
    -					r.push(", ");
    +					r.push(', ');
     				}
     
    -				r.push("");
    +				r.push('>');
     				r.push(p.name);
    -				r.push("");
    +				r.push('');
     				if ( p.optional ) {
    -					r.push("?");
    +					r.push('?');
     				}
     			}
     		}
     	}
    -	r.push(")");
    -	return r.join("");
    +	r.push(')');
    +	return r.join('');
     }
     
     
    @@ -1497,120 +1511,107 @@ function makeSignature(params) {
      *   group 7: an empty line which implicitly starts a new paragraph
      *
      *                 [------- 
     block -------] [----------------------- some flow content -----------------------] [---- an inline {@link ...} tag ----] [---------- an empty line ---------]  */
    -const rFormatText = /(]*)?>)|(<\/pre>)|(<(?:h[\d+]|ul|ol|table)(?:\s[^>]*)?>)|(<\/(?:h[\d+]|ul|ol|table)>)|\{@link\s+([^}\s]+)(?:\s+([^\}]*))?\}|((?:\r\n|\r|\n)[ \t]*(?:\r\n|\r|\n))/gi;
    +var rFormatText = /(]*)?>)|(<\/pre>)|(<(?:h[\d+]|ul|ol|table)(?:\s[^>]*)?>)|(<\/(?:h[\d+]|ul|ol|table)>)|\{@link\s+([^}\s]+)(?:\s+([^\}]*))?\}|((?:\r\n|\r|\n)[ \t]*(?:\r\n|\r|\n))/gi;
     
     function formatText(text) {
    +
     	if ( !text ) {
    -		return "";
    +		return '';
     	}
     
    -	let inpre = false;
    +	var inpre = false,
    +		paragraphs = 0;
     
    -
    -	let paragraphs = 0;
    -
    -	text = String(text).replace(rFormatText,
    -		function(match, pre, endpre, flow, endflow, linkTarget, linkText, emptyline) {
    -			if ( pre ) {
    -				inpre = true;
    -				return pre.replace(/
    /gi, "
    ").replace(//gi,
    -					"
    ");
    -			} else if ( endpre ) {
    -				inpre = false;
    -			} else if ( flow ) {
    -				if ( !inpre ) {
    -					paragraphs++;
    -					return "

    " + match; - } - } else if ( endflow ) { - if ( !inpre ) { - paragraphs++; - return match + "

    "; - } - } else if ( emptyline ) { - if ( !inpre ) { - paragraphs++; - return "

    "; - } - } else if ( linkTarget ) { - if ( !inpre ) { - // convert to a hyperlink - let link = new Link().toSymbol(linkTarget); - // if link tag contained a replacement text, use it - if ( linkText && linkText.trim()) { - link = link.withText(linkText.trim()); - } - return link.toString(); + text = String(text).replace(rFormatText, function(match, pre, endpre, flow, endflow, linkTarget, linkText, emptyline) { + if ( pre ) { + inpre = true; + return pre.replace(/

    /gi, "
    ").replace(//gi, "
    ");
    +		} else if ( endpre ) {
    +			inpre = false;
    +		} else if ( flow ) {
    +			if ( !inpre ) {
    +				paragraphs++;
    +				return '

    ' + match; + } + } else if ( endflow ) { + if ( !inpre ) { + paragraphs++; + return match + '

    '; + } + } else if ( emptyline ) { + if ( !inpre ) { + paragraphs++; + return '

    '; + } + } else if ( linkTarget ) { + if ( !inpre ) { + // convert to a hyperlink + var link = new Link().toSymbol(linkTarget); + // if link tag contained a replacement text, use it + if ( linkText && linkText.trim()) { + link = link.withText(linkText.trim()); } + return link.toString(); } - return match; - }); + } + return match; + }); if ( paragraphs > 0 ) { - text = "

    " + text + "

    "; + text = '

    ' + text + '

    '; } // remove empty paragraphs - text = text.replace(/

    \s*<\/p>/g, ""); + text = text.replace(/

    \s*<\/p>/g, ''); return text; } -// console.log("#### samples"); -// console.log(formatText( -// summarize("This is a first\n\nparagraph with empty \n \n \nlines in it. This is the remainder."))); - function childrenOfKind(data, kind) { /* old version based on TaffyDB (slow) - var oChildren = symbolSet({ - kind: kind, - memberof: data.longname === GLOBAL_LONGNAME ? {isUndefined: true} : data.longname}).filter(function() { - return conf.filter(this); - }); + var oChildren = symbolSet({kind: kind, memberof: data.longname === GLOBAL_LONGNAME ? {isUndefined: true} : data.longname}).filter(function() { return conf.filter(this); }); return { own : oChildren.filter({inherited: {isUndefined:true}}).get().sort(makeSortby("!deprecated","static","name")), borrowed : groupByContributors(data, oChildren.filter({inherited: true}).get().sort(makeSortby("name"))) } */ - const oResult = { + var oResult = { own: [], borrowed: [] }; - // console.log("calculating kind " + kind + " from " + data.longname); - // console.log(data); - let fnFilter; + //debug("calculating kind " + kind + " from " + data.longname); + //console.log(data); + var fnFilter; switch (kind) { - case "property": + case 'property': fnFilter = function($) { - return $.kind === "constant" || ($.kind === "member" && !$.isEnum); + return $.kind === 'constant' || ($.kind === 'member' && !$.isEnum); }; break; - case "event": + case 'event': fnFilter = function($) { - return $.kind === "event"; + return $.kind === 'event'; }; break; - case "method": + case 'method': fnFilter = function($) { - return $.kind === "function"; + return $.kind === 'function'; }; break; default: // default: none - fnFilter = function($) { - return false; - }; + fnFilter = function($) { return false; }; break; } if ( data.__ui5.members ) { data.__ui5.members.forEach(function($) { if ( fnFilter($) && conf.filter($) ) { - oResult[$.inherited ? "borrowed" : "own"].push($); + oResult[$.inherited ? 'borrowed' : 'own'].push($); } }); } - oResult.own.sort(makeSortby("!deprecated", "static", "name")); + oResult.own.sort(makeSortby("!deprecated","static","name")); oResult.borrowed = groupByContributors(data, oResult.borrowed); return oResult; @@ -1623,26 +1624,21 @@ function childrenOfKind(data, kind) { * Any contributors that can not be found in the hierarchy are appended * to the set. * - * @param {Any} symbol of which these are the members - * @param {Array} aBorrowedMembers set of borrowed members to determine the contributors for - * @returns {Array} sorted array of contributors + * @param {Symbol} symbol of which these are the members + * @param {array} aBorrowedMembers set of borrowed members to determine the contributors for + * @return {array} sorted array of contributors */ function groupByContributors(symbol, aBorrowedMembers) { - const MAX_ORDER = 1000; - // a sufficiently large number - - const mContributors = {}; - - - const aSortedContributors = []; - - let i; let order; + var MAX_ORDER = 1000, // a sufficiently large number + mContributors = {}, + aSortedContributors = [], + i,order; aBorrowedMembers.forEach(function($) { $ = lookup($.inherits); if ($ && mContributors[$.memberof] == null) { - mContributors[$.memberof] = {order: MAX_ORDER, items: [$]}; + mContributors[$.memberof] = { order : MAX_ORDER, items : [$] }; } else { mContributors[$.memberof].items.push($); } @@ -1651,13 +1647,12 @@ function groupByContributors(symbol, aBorrowedMembers) { // order contributors according to their distance in the inheritance hierarchy order = 0; (function handleAugments(oSymbol) { - let i; let oTarget; let aParentsToVisit; + var i,oTarget,aParentsToVisit; if ( oSymbol.augments ) { aParentsToVisit = []; // first assign an order for (i = 0; i < oSymbol.augments.length; i++) { - if ( mContributors[oSymbol.augments[i]] != null && - mContributors[oSymbol.augments[i]].order === MAX_ORDER ) { + if ( mContributors[oSymbol.augments[i]] != null && mContributors[oSymbol.augments[i]].order === MAX_ORDER ) { mContributors[oSymbol.augments[i]].order = ++order; aParentsToVisit.push(oSymbol.augments[i]); } @@ -1674,58 +1669,46 @@ function groupByContributors(symbol, aBorrowedMembers) { // convert to an array and sort by order for (i in mContributors) { - if (mContributors.hasOwnProperty(i)) { - aSortedContributors.push(mContributors[i]); - } + aSortedContributors.push(mContributors[i]); } - aSortedContributors.sort(function(a, b) { - return a.order - b.order; - }); + aSortedContributors.sort(function (a,b) { return a.order - b.order; }); return aSortedContributors; + } function makeLinkList(aSymbols) { return aSymbols .sort(makeSortby("name")) - .map(function($) { - return new Link().toSymbol($.longname).withText($.name); - }) + .map(function($) { return new Link().toSymbol($.longname).withText($.name); }) .join(", "); } // ---- type parsing --------------------------------------------------------------------------------------------- function TypeParser(defaultBuilder) { + /* TODO * - function(this:) // type of this * - function(new:) // constructor */ - const rLexer = /\s*(Array\.?<|Object\.?<|Set\.?<|Promise\.?<|function\(|\{|:|\(|\||\}|>|\)|,|\[\]|\*|\?|!|\.\.\.)|\s*(\w+(?:[.#~]\w+)*)|./g; - - let input; + var rLexer = /\s*(Array\.?<|Object\.?<|Set\.?<|Promise\.?<|function\(|\{|:|\(|\||\}|>|\)|,|\[\]|\*|\?|!|\.\.\.)|\s*((?:module:)?\w+(?:[\/.#~]\w+)*)|./g; - - let builder; - - - let token; - - - let tokenStr; + var input, + builder, + token, + tokenStr; function next(expected) { if ( expected !== undefined && token !== expected ) { - throw new SyntaxError("TypeParser: expected '" + expected + "', but found '" + tokenStr + "' (pos: " + - rLexer.lastIndex + ", input='" + input + "')"); + throw new SyntaxError("TypeParser: expected '" + expected + "', but found '" + tokenStr + "' (pos: " + rLexer.lastIndex + ", input='" + input + "')"); } - const match = rLexer.exec(input); + var match = rLexer.exec(input); if ( match ) { tokenStr = match[1] || match[2]; - token = match[1] || (match[2] && "symbol"); + token = match[1] || (match[2] && 'symbol'); if ( !token ) { - throw new SyntaxError("TypeParser: unexpected '" + tokenStr + "' (pos: " + match.index + ", input='" + - input + "')"); + throw new SyntaxError("TypeParser: unexpected '" + tokenStr + "' (pos: " + match.index + ", input='" + input + "')"); } } else { tokenStr = token = null; @@ -1733,126 +1716,122 @@ function TypeParser(defaultBuilder) { } function parseType() { - let nullable = false; - let mandatory = false; - if ( token === "?" ) { + var nullable = false; + var mandatory = false; + if ( token === '?' ) { next(); nullable = true; - } else if ( token === "!" ) { + } else if ( token === '!' ) { next(); mandatory = true; } - let type; + var type; - if ( token === "Array.<" || token === "Array<" ) { + if ( token === 'Array.<' || token === 'Array<' ) { next(); - const componentType = parseType(); - next(">"); + var componentType = parseType(); + next('>'); type = builder.array(componentType); - } else if ( token === "Object.<" || token === "Object<" ) { + } else if ( token === 'Object.<' || token === 'Object<' ) { next(); - let keyType; - let valueType = parseType(); - if ( token === "," ) { + var keyType; + var valueType = parseType(); + if ( token === ',' ) { next(); keyType = valueType; valueType = parseType(); } else { - keyType = builder.synthetic(builder.simpleType("string")); + keyType = builder.synthetic(builder.simpleType('string')); } - next(">"); + next('>'); type = builder.object(keyType, valueType); - } else if ( token === "Set.<" || token === "Set<" ) { + } else if ( token === 'Set.<' || token === 'Set<' ) { next(); - const elementType = parseType(); - next(">"); + var elementType = parseType(); + next('>'); type = builder.set(elementType); - } else if ( token === "Promise.<" || token === "Promise<" ) { + } else if ( token === 'Promise.<' || token === 'Promise<' ) { next(); - const elementType = parseType(); - next(">"); - type = builder.promise(elementType); - } else if ( token === "function(" ) { + var resultType = parseType(); + next('>'); + type = builder.promise(resultType); + } else if ( token === 'function(' ) { next(); - let thisType; let constructorType; const paramTypes = []; let returnType; - if ( tokenStr === "this" ) { + var thisType, constructorType, paramTypes = [], returnType; + if ( tokenStr === 'this' ) { next(); - next(":"); + next(':'); thisType = parseType(); - if ( token === "," ) { + if ( token === ',' ) { next(); } - } else if ( tokenStr === "new" ) { + } else if ( tokenStr === 'new' ) { next(); - next(":"); + next(':'); constructorType = parseType(); - if ( token === "," ) { + if ( token === ',' ) { next(); } } - while ( token === "symbol" || token === "..." ) { - const repeatable = token === "..."; + while ( token === 'symbol' || token === '...' ) { + var repeatable = token === '...'; if ( repeatable) { next(); } - let paramType = parseType(); + var paramType = parseType(); if ( repeatable ) { paramType = builder.repeatable(paramType); } paramTypes.push(paramType); - if ( token === "," ) { + if ( token === ',' ) { if ( repeatable ) { - throw new SyntaxError( - "TypeParser: only the last parameter of a function can be repeatable (pos: " + - rLexer.lastIndex + ", input='" + input + "')"); + throw new SyntaxError("TypeParser: only the last parameter of a function can be repeatable (pos: " + rLexer.lastIndex + ", input='" + input + "')"); } next(); } } - next(")"); - if ( token === ":" ) { - next(":"); + next(')'); + if ( token === ':' ) { + next(':'); returnType = parseType(); } type = builder.function(paramTypes, returnType, thisType, constructorType); - } else if ( token === "{" ) { - const structure = Object.create(null); - let propName; let propType; + } else if ( token === '{' ) { + var structure = Object.create(null); + var propName,propType; next(); do { propName = tokenStr; if ( !/^\w+$/.test(propName) ) { - throw new SyntaxError( - "TypeParser: structure field must have a simple name (pos: " + rLexer.lastIndex + - ", input='" + input + "', field:'" + propName + "')"); + throw new SyntaxError("TypeParser: structure field must have a simple name (pos: " + rLexer.lastIndex + ", input='" + input + "', field:'" + propName + "')"); } - next("symbol"); - if ( token === ":" ) { + next('symbol'); + if ( token === ':' ) { next(); propType = parseType(); } else { - propType = builder.synthetic(builder.simpleType("any")); + propType = builder.synthetic(builder.simpleType('any')); } structure[propName] = propType; - if ( token === "}" ) { + if ( token === '}' ) { break; } - next(","); + next(','); } while (token); - next("}"); + next('}'); type = builder.structure(structure); - } else if ( token === "(" ) { + } else if ( token === '(' ) { next(); type = parseTypes(); - next(")"); - } else if ( token === "*" ) { + next(')'); + } else if ( token === '*' ) { next(); - type = builder.simpleType("*"); + type = builder.simpleType('*'); } else { type = builder.simpleType(tokenStr); - next("symbol"); - while ( token === "[]" ) { + next('symbol'); + while ( token === '[]' ) { next(); type = builder.array(type); } @@ -1867,10 +1846,10 @@ function TypeParser(defaultBuilder) { } function parseTypes() { - const types = []; + var types = []; do { types.push(parseType()); - if ( token !== "|" ) { + if ( token !== '|' ) { break; } next(); @@ -1883,62 +1862,63 @@ function TypeParser(defaultBuilder) { input = String(typeStr); rLexer.lastIndex = 0; next(); - const type = parseTypes(); + var type = parseTypes(); next(null); return type; }; + } TypeParser.ASTBuilder = { simpleType: function(type) { return { - type: "simpleType", + type: 'simpleType', name: type }; }, array: function(componentType) { return { - type: "array", + type: 'array', component: componentType }; }, object: function(keyType, valueType) { return { - type: "object", + type: 'object', key: keyType, value: valueType }; }, set: function(elementType) { return { - type: "set", + type: 'set', element: elementType }; }, promise: function(fulfillmentType) { return { - type: "promise", + type: 'promise', fulfill: fulfillmentType }; }, - function: function(paramTypes, returnType, thisType, constructorType) { + "function": function(paramTypes, returnType, thisType, constructorType) { return { - type: "function", + type: 'function', params: paramTypes, - return: returnType, - this: thisType, + "return": returnType, + "this": thisType, constructor: constructorType }; }, structure: function(structure) { return { - type: "structure", + type: 'structure', fields: structure }; }, union: function(types) { return { - type: "union", + type: 'union', types: types }; }, @@ -1970,13 +1950,13 @@ TypeParser.LinkBuilder.prototype = { return type.needsParenthesis ? "(" + type.str + ")" : type.str; }, simpleType: function(type) { - if ( this.linkStyle === "text" ) { + if ( this.linkStyle === 'text' ) { return { str: type }; } - const link = new Link().toSymbol(type); - if ( this.linkStyle === "short" ) { + var link = new Link().toSymbol(type); + if ( this.linkStyle === 'short' ) { link.withText(simpleNameOf(type)).withTooltip(type); } return { @@ -2005,24 +1985,22 @@ TypeParser.LinkBuilder.prototype = { }, set: function(elementType) { return { - str: "Set." + this.lt + elementType.str + this.gt + str: 'Set.' + this.lt + elementType.str + this.gt }; }, promise: function(fulfillmentType) { return { - str: "Promise." + this.lt + fulfillmentType.str + this.gt + str: 'Promise.' + this.lt + fulfillmentType.str + this.gt }; }, - function: function(paramTypes, returnType) { + "function": function(paramTypes, returnType) { return { - str: "function(" + paramTypes.map(function(type) { - return type.str; - }).join(",") + ")" + ( returnType ? " : " + this.safe(returnType) : "") + str: "function(" + paramTypes.map(function(type) { return type.str; }).join(',') + ")" + ( returnType ? " : " + this.safe(returnType) : "") }; }, structure: function(structure) { - const r = []; - for ( const fieldName in structure ) { + var r = []; + for ( var fieldName in structure ) { if ( structure[fieldName].synthetic ) { r.push(fieldName); } else { @@ -2036,7 +2014,7 @@ TypeParser.LinkBuilder.prototype = { union: function(types) { return { needsParenthesis: true, - str: types.map( this.safe.bind(this) ).join("|") + str: types.map( this.safe.bind(this) ).join('|') }; }, synthetic: function(type) { @@ -2057,19 +2035,19 @@ TypeParser.LinkBuilder.prototype = { } }; -const typeParser = new TypeParser(); -const _SHORT_BUILDER = new TypeParser.LinkBuilder("short", true); -const _LONG_BUILDER = new TypeParser.LinkBuilder("long", true); -const _TEXT_BUILDER = new TypeParser.LinkBuilder("text", false); -const _TEXT_BUILDER_ENCODED = new TypeParser.LinkBuilder("text", true); +var typeParser = new TypeParser(); +var _SHORT_BUILDER = new TypeParser.LinkBuilder('short', true); +var _LONG_BUILDER = new TypeParser.LinkBuilder('long', true); +var _TEXT_BUILDER = new TypeParser.LinkBuilder('text', false); +var _TEXT_BUILDER_ENCODED = new TypeParser.LinkBuilder('text', true); /* function testTypeParser(type) { - console.log("Type: '" + type + "' gives AST"); + debug("Type: '" + type + "' gives AST"); try { console.log(typeParser.parse(type)); } catch (e) { - console.log("**** throws: " + e); + error("**** throws: " + e); } } @@ -2083,7 +2061,7 @@ testTypeParser("{a:int,b,c:float,d,e}"); function _processTypeString(type, builder) { if ( type && Array.isArray(type.names) ) { - type = type.names.join("|"); + type = type.names.join('|'); } if ( type ) { try { @@ -2103,26 +2081,40 @@ function linkTypes(type, short) { return _processTypeString(type, short ? _SHORT_BUILDER : _LONG_BUILDER ); } +function isArrayType(type) { + if ( type && Array.isArray(type.names) ) { + type = type.names.join('|'); + } + if ( type ) { + try { + var ast = typeParser.parse( type, TypeParser.ASTBuilder ); + return ( ast.type === 'array' || (ast.type === 'union' && ast.types.some( function(subtype) { return subtype.type === 'array'; })) ); + } catch (e) { + error("failed to parse type string '" + type + "': " + e); + } + } + return false; +} + /** * Reduces the given text to a summary and removes all tags links etc. and escapes double quotes. * The result therefore should be suitable as content for an HTML tag attribute (e.g. title). * - * @param {string} sText + * @param {string} sText Text to extract a summary from * @returns {string} summarized, plain attribute */ function asPlainSummary(sText) { - return sText ? summarize(sText).replace(/<.*?>/g, "").replace(/\{\@link\s*(.*?)\}/g, "$1") - .replace(/"/g, """) : ""; + return sText ? summarize(sText).replace(/<.*?>/g, '').replace(/\{\@link\s*(.*?)\}/g, '$1').replace(/"/g,""") : ''; } function getNSClass(item) { - if (item.kind === "interface") { + if (item.kind === 'interface') { return " interface"; - } else if (item.kind === "namespace") { + } else if (item.kind === 'namespace') { return " namespace"; - } else if (item.kind === "typedef" ) { + } else if (item.kind === 'typedef' ) { return " typedef"; - } else if (item.kind === "member" && item.isEnum ) { + } else if (item.kind === 'member' && item.isEnum ) { return " enum"; } else { return ""; @@ -2139,14 +2131,14 @@ function getNSClass(item) { * group 4: an isolated line feed + surrounding whitespace * * [-------

     block -------] [---- an empty line and surrounding whitespace ----] [---- new line or whitespaces ----] */
    -const rNormalizeText = /(]*)?>)|(<\/pre>)|([ \t]*(?:\r\n|\r|\n)[ \t]*(?:\r\n|\r|\n)[ \t\r\n]*)|([ \t]*(?:\r\n|\r|\n)[ \t]*|[ \t]+)/gi;
    +var rNormalizeText = /(]*)?>)|(<\/pre>)|([ \t]*(?:\r\n|\r|\n)[ \t]*(?:\r\n|\r|\n)[ \t\r\n]*)|([ \t]*(?:\r\n|\r|\n)[ \t]*|[ \t]+)/gi;
     
     function normalizeWS(text) {
     	if ( text == null ) {
     		return text;
     	}
     
    -	let inpre = false;
    +	var inpre = false;
     	return String(text).replace(rNormalizeText, function(match, pre, endpre, emptyline, ws) {
     		if ( pre ) {
     			inpre = true;
    @@ -2155,23 +2147,25 @@ function normalizeWS(text) {
     			inpre = false;
     			return endpre;
     		} else if ( emptyline ) {
    -			return inpre ? emptyline : "\n\n";
    +			return inpre ? emptyline : '\n\n';
     		} else if ( ws ) {
    -			return inpre ? ws : " ";
    +			return inpre ? ws : ' ';
     		}
     		return match;
     	});
    +
     }
     
    -// ---- add on: API JSON -----------------------------------------------------------------
    +//---- add on: API JSON -----------------------------------------------------------------
     
     function createAPIJSON(symbols, filename) {
    -	const api = {
    +
    +	var api = {
     		"$schema-ref": "http://schemas.sap.com/sapui5/designtime/api.json/1.0"
     	};
     
     	if ( templateConf.version ) {
    -		api.version = templateConf.version.replace(/-SNAPSHOT$/, "");
    +		api.version = templateConf.version.replace(/-SNAPSHOT$/,"");
     	}
     	if ( templateConf.uilib ) {
     		api.library = templateConf.uilib;
    @@ -2180,8 +2174,7 @@ function createAPIJSON(symbols, filename) {
     	api.symbols = [];
     	// sort only a copy(!) of the symbols, otherwise the SymbolSet lookup is broken
     	symbols.slice(0).sort(sortByAlias).forEach(function(symbol) {
    -		if ( isaClass(symbol) && !symbol.synthetic ) {
    -			// dump a symbol if it as a class symbol and if it is not a synthetic 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));
     		}
     	});
    @@ -2189,21 +2182,22 @@ function createAPIJSON(symbols, filename) {
     	postProcessAPIJSON(api);
     
     	fs.mkPath(path.dirname(filename));
    -	fs.writeFileSync(filename, JSON.stringify(api), "utf8");
    +	fs.writeFileSync(filename, JSON.stringify(api), 'utf8');
     	info("  apiJson saved as " + filename);
     }
     
     function createAPIJSON4Symbol(symbol, omitDefaults) {
    -	const obj = [];
    -	let curr = obj;
    -	let attribForKind = "kind";
    -	const stack = [];
    +
    +	var obj = [];
    +	var curr = obj;
    +	var attribForKind = 'kind';
    +	var stack = [];
     
     	function isEmpty(obj) {
     		if ( !obj ) {
     			return true;
     		}
    -		for (const n in obj) {
    +		for (var n in obj) {
     			if ( obj.hasOwnProperty(n) ) {
     				return false;
     			}
    @@ -2212,13 +2206,14 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     
     	function tag(name, value, omitEmpty) {
    +
     		if ( omitEmpty && !value ) {
     			return;
     		}
     		if ( arguments.length === 1 ) { // opening tag
     			stack.push(curr);
     			stack.push(attribForKind);
    -			const obj = {};
    +			var obj = {};
     			if ( Array.isArray(curr) ) {
     				if ( attribForKind != null ) {
     					obj[attribForKind] = name;
    @@ -2239,16 +2234,19 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     
     	function attrib(name, value, defaultValue, raw) {
    -		const emptyTag = arguments.length === 1;
     		if ( omitDefaults && arguments.length >= 3 && value === defaultValue ) {
     			return;
     		}
    -		curr[name] = emptyTag ? true : (raw ? value : String(value));
    +		if ( arguments.length === 1 /* empty tag */ ) {
    +			curr[name] = true;
    +		} else {
    +			curr[name] = raw ? value : String(value);
    +		}
     	}
     
     	function closeTag(name, noIndent) {
     		attribForKind = stack.pop();
    -		curr = stack.pop();
    +		curr  = stack.pop();
     	}
     
     	function collection(name, attribForKind) {
    @@ -2261,15 +2259,16 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     
     	function endCollection(name) {
     		attribForKind = stack.pop();
    -		curr = stack.pop();
    +		curr  = stack.pop();
     	}
     
     	function tagWithSince(name, value) {
    +
     		if ( !value ) {
     			return;
     		}
     
    -		const info = extractSince(value);
    +		var info = extractSince(value);
     
     		tag(name);
     		if ( info.since ) {
    @@ -2279,10 +2278,11 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     			curr["text"] = normalizeWS(info.value);
     		}
     		closeTag(name, true);
    +
     	}
     
     	function examples(symbol) {
    -		let j; let example;
    +		var j, example;
     
     		if ( symbol.examples && symbol.examples.length ) {
     			collection("examples");
    @@ -2306,11 +2306,11 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     
     	function visibility($) {
    -		if ( $.access === "protected" ) {
    +		if ( $.access === 'protected' ) {
     			return "protected";
    -		} else if ( $.access === "restricted" ) {
    +		} else if ( $.access === 'restricted' ) {
     			return "restricted";
    -		} else if ( $.access === "private" ) {
    +		} else if ( $.access === 'private' ) {
     			return "private";
     		} else {
     			return "public";
    @@ -2318,13 +2318,11 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     
     	function exceptions(symbol) {
    -		let array = symbol.exceptions;
    -
    -
    -		let j; let exception;
    +		var array = symbol.exceptions,
    +			j, exception;
     
     		if ( Array.isArray(array) ) {
    -			array = array.filter( function(ex) {
    +			array = array.filter( function (ex) {
     				return (ex.type && listTypes(ex.type)) || (ex.description && ex.description.trim());
     			});
     		}
    @@ -2346,9 +2344,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     
     	function methodList(tagname, methods) {
    -		methods = methods && Object.keys(methods).map(function(key) {
    -			return methods[key];
    -		});
    +		methods = methods && Object.keys(methods).map(function(key) { return methods[key]; });
     		if ( methods != null && methods.length > 0 ) {
     			curr[tagname] = methods;
     		}
    @@ -2361,10 +2357,11 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     
     	function hasSettings($, visited) {
    +
     		visited = visited || {};
     
     		if ( $.augments && $.augments.length > 0 ) {
    -			let baseSymbol = $.augments[0];
    +			var baseSymbol = $.augments[0];
     			if ( visited.hasOwnProperty(baseSymbol) ) {
     				error("detected cyclic inheritance when looking at " + $.longname + ": " + JSON.stringify(visited));
     				return false;
    @@ -2376,7 +2373,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     			}
     		}
     
    -		const metadata = $.__ui5.metadata;
    +		var metadata = $.__ui5.metadata;
     		return metadata &&
     			(
     				!isEmpty(metadata.specialSettings)
    @@ -2389,21 +2386,22 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     
     	function writeMetadata($) {
    -		const metadata = $.__ui5.metadata;
    +
    +		var metadata = $.__ui5.metadata;
     		if ( !metadata ) {
     			return;
     		}
     
    -		let n;
    +		var n;
     
     		if ( metadata.specialSettings && Object.keys(metadata.specialSettings).length > 0 ) {
     			collection("specialSettings");
     			for ( n in metadata.specialSettings ) {
    -				const special = metadata.specialSettings[n];
    +				var special = metadata.specialSettings[n];
     				tag("specialSetting");
     				attrib("name", special.name);
     				attrib("type", special.type);
    -				attrib("visibility", special.visibility, "public");
    +				attrib("visibility", special.visibility, 'public');
     				if ( special.since ) {
     					attrib("since", extractVersion(special.since));
     				}
    @@ -2419,13 +2417,13 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     		if ( metadata.properties && Object.keys(metadata.properties).length > 0 ) {
     			collection("properties");
     			for ( n in metadata.properties ) {
    -				const prop = metadata.properties[n];
    +				var prop = metadata.properties[n];
     				tag("property");
     				attrib("name", prop.name);
    -				attrib("type", prop.type, "string");
    +				attrib("type", prop.type, 'string');
     				attrib("defaultValue", prop.defaultValue, null, /* raw = */true);
    -				attrib("group", prop.group, "Misc");
    -				attrib("visibility", prop.visibility, "public");
    +				attrib("group", prop.group, 'Misc');
    +				attrib("visibility", prop.visibility, 'public');
     				if ( prop.since ) {
     					attrib("since", extractVersion(prop.since));
     				}
    @@ -2452,16 +2450,16 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     		if ( metadata.aggregations && Object.keys(metadata.aggregations).length > 0 ) {
     			collection("aggregations");
     			for ( n in metadata.aggregations ) {
    -				const aggr = metadata.aggregations[n];
    +				var aggr = metadata.aggregations[n];
     				tag("aggregation");
     				attrib("name", aggr.name);
     				attrib("singularName", aggr.singularName); // TODO omit default?
    -				attrib("type", aggr.type, "sap.ui.core.Control");
    +				attrib("type", aggr.type, 'sap.ui.core.Control');
     				if ( aggr.altTypes ) {
     					curr.altTypes = aggr.altTypes.slice();
     				}
    -				attrib("cardinality", aggr.cardinality, "0..n");
    -				attrib("visibility", aggr.visibility, "public");
    +				attrib("cardinality", aggr.cardinality, '0..n');
    +				attrib("visibility", aggr.visibility, 'public');
     				if ( aggr.since ) {
     					attrib("since", extractVersion(aggr.since));
     				}
    @@ -2487,13 +2485,13 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     		if ( metadata.associations && Object.keys(metadata.associations).length > 0 ) {
     			collection("associations");
     			for ( n in metadata.associations ) {
    -				const assoc = metadata.associations[n];
    +				var assoc = metadata.associations[n];
     				tag("association");
     				attrib("name", assoc.name);
     				attrib("singularName", assoc.singularName); // TODO omit default?
    -				attrib("type", assoc.type, "sap.ui.core.Control");
    -				attrib("cardinality", assoc.cardinality, "0..1");
    -				attrib("visibility", assoc.visibility, "public");
    +				attrib("type", assoc.type, 'sap.ui.core.Control');
    +				attrib("cardinality", assoc.cardinality, '0..1');
    +				attrib("visibility", assoc.visibility, 'public');
     				if ( assoc.since ) {
     					attrib("since", extractVersion(assoc.since));
     				}
    @@ -2509,10 +2507,10 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     		if ( metadata.events && Object.keys(metadata.events).length > 0 ) {
     			collection("events");
     			for ( n in metadata.events ) {
    -				const event = metadata.events[n];
    +				var event = metadata.events[n];
     				tag("event");
     				attrib("name", event.name);
    -				attrib("visibility", event.visibility, "public");
    +				attrib("visibility", event.visibility, 'public');
     				if ( event.since ) {
     					attrib("since", extractVersion(event.since));
     				}
    @@ -2521,9 +2519,9 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     				tagWithSince("deprecated", event.deprecation);
     				if ( event.parameters && Object.keys(event.parameters).length > 0 ) {
     					tag("parameters");
    -					for ( const pn in event.parameters ) {
    +					for ( var pn in event.parameters ) {
     						if ( event.parameters.hasOwnProperty(pn) ) {
    -							const param = event.parameters[pn];
    +							var param = event.parameters[pn];
     							tag(pn);
     							attrib("name", pn);
     							attrib("type", param.type);
    @@ -2547,7 +2545,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     		if ( metadata.annotations && Object.keys(metadata.annotations).length > 0 ) {
     			collection("annotations");
     			for ( n in metadata.annotations ) {
    -				const anno = metadata.annotations[n];
    +				var anno = metadata.annotations[n];
     				tag("annotation");
     				attrib("name", anno.name);
     				attrib("namespace", anno.namespace);
    @@ -2572,24 +2570,30 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     		if ( metadata.designtime ) { // don't write falsy values
     			tag("designtime", metadata.designtime);
     		}
    -	}
    -
    -	function writeParameterProperties(paramName, params) {
    -		const prefix = paramName + ".";
    -
    -
    -		let count = 0;
     
    +	}
     
    -		let i;
    +	function writeParameterProperties(param, params) {
    +		var prefix = param.name + '.',
    +			altPrefix = isArrayType(param.type) ? param.name + '[].' : null,
    +			count = 0,
    +			i;
     
     		for ( i = 0; i < params.length; i++ ) {
    -			let name = params[i].name;
    -			if ( name.lastIndexOf(prefix, 0) !== 0 ) { // startsWith
    +
    +			var name = params[i].name;
    +			if ( altPrefix && name.lastIndexOf(altPrefix, 0) === 0 ) { // startsWith
    +				name = name.slice(altPrefix.length);
    +			} else if ( name.lastIndexOf(prefix, 0) === 0 ) { // startsWith
    +				if ( altPrefix ) {
    +					warning("Nested @param tag in the context of an array type is used without []-suffix", name);
    +				}
    +				name = name.slice(prefix.length);
    +			} else {
     				continue;
     			}
    -			name = name.slice(prefix.length);
    -			if ( name.indexOf(".") >= 0 ) {
    +
    +			if ( name.indexOf('.') >= 0 ) {
     				continue;
     			}
     
    @@ -2610,7 +2614,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     				attrib("since", extractVersion(params[i].since));
     			}
     
    -			writeParameterProperties(params[i].name, params);
    +			writeParameterProperties(params[i], params);
     
     			tag("description", normalizeWS(params[i].description), true);
     			tagWithSince("experimental", params[i].experimental);
    @@ -2624,6 +2628,90 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     		}
     	}
     
    +	function methodSignature(member, suppressReturnValue) {
    +
    +		if ( !suppressReturnValue ) {
    +			var returns = member.returns && member.returns.length && member.returns[0];
    +			var type = member.type || (returns && returns.type);
    +			type = listTypes(type);
    +			//if ( type && type !== 'void' ) {
    +			//	attrib("type", type, 'void');
    +			//}
    +			if ( type && type !== 'void' || returns && returns.description ) {
    +				tag("returnValue");
    +				if ( type && type !== 'void' ) {
    +					attrib("type", type);
    +				}
    +				if ( returns && returns.description ) {
    +					attrib("description", normalizeWS(returns.description));
    +				}
    +				closeTag("returnValue");
    +			}
    +		}
    +
    +		if ( member.params && member.params.length > 0 ) {
    +			collection("parameters");
    +			for ( var j = 0; j < member.params.length; j++) {
    +				var param = member.params[j];
    +				if ( param.name.indexOf('.') >= 0 ) {
    +					continue;
    +				}
    +				tag("parameter");
    +				attrib("name", param.name);
    +				attrib("type", listTypes(param.type));
    +				attrib("optional", !!param.optional, false, /* raw = */true);
    +				if ( param.defaultvalue !== undefined ) {
    +					attrib("defaultValue", param.defaultvalue, undefined, /* raw = */true);
    +				}
    +				if ( param.since ) {
    +					attrib("since", extractVersion(param.since));
    +				}
    +				writeParameterProperties(param, member.params);
    +				tag("description", normalizeWS(param.description), true);
    +				tagWithSince("experimental", param.experimental);
    +				tagWithSince("deprecated", param.deprecated);
    +				closeTag("parameter");
    +			}
    +			endCollection("parameters");
    +		}
    +
    +		exceptions(member);
    +
    +	}
    +
    +	function writeMethod(member, name) {
    +		tag("method");
    +		attrib("name", name || member.name);
    +		if ( member.__ui5.module && member.__ui5.module !== symbol.__ui5.module ) {
    +			attrib("module", member.__ui5.module);
    +			attrib("export", undefined, '', true);
    +		}
    +		attrib("visibility", visibility(member), 'public');
    +		if ( member.scope === 'static' ) {
    +			attrib("static", true, false, /* raw = */true);
    +		}
    +		if ( member.since ) {
    +			attrib("since", extractVersion(member.since));
    +		}
    +		if ( member.tags && member.tags.some(function(tag) { return tag.title === 'ui5-metamodel'; }) ) {
    +			attrib('ui5-metamodel', true, false, /* raw = */true);
    +		}
    +
    +		methodSignature(member);
    +
    +		tag("description", normalizeWS(member.description), true);
    +		tagWithSince("experimental", member.experimental);
    +		tagWithSince("deprecated", member.deprecated);
    +		examples(member);
    +		referencesList(member);
    +		//secTags(member);
    +		if ( member.__ui5.resource && member.__ui5.resource !== symbol.__ui5.resource ) {
    +			attrib("resource", member.__ui5.resource);
    +		}
    +		closeTag("method");
    +
    +	}
    +
     	/*
     	var rSplitSecTag = /^\s*\{([^\}]*)\}/;
     
    @@ -2656,7 +2744,7 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     	*/
     
    -	const kind = (symbol.kind === "member" && symbol.isEnum) ? "enum" : symbol.kind; // handle pseudo-kind 'enum'
    +	var kind = (symbol.kind === 'member' && symbol.isEnum) ? "enum" : symbol.kind; // handle pseudo-kind 'enum'
     
     	tag(kind);
     
    @@ -2667,38 +2755,49 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     	}
     	if ( symbol.__ui5.module ) {
     		attrib("module", symbol.__ui5.module);
    -		attrib("export", undefined, "", true);
    +		attrib("export", undefined, '', true);
     	}
    -	if ( symbol.virtual ) {
    +	if ( /* TODO (kind === 'class') && */ symbol.virtual ) {
    +		// Note reg. the TODO: only one unexpected occurrence found in DragSession (DragAndDrop.js)
     		attrib("abstract", true, false, /* raw = */true);
     	}
    -	if ( symbol.final_ ) {
    +	if ( /* TODO (kind === 'class' || kind === 'interface') && */ symbol.final_ ) {
    +		// Note reg. the TODO: enums are marked as final & namespace, they would loose the final with the addtl. check.
     		attrib("final", true, false, /* raw = */true);
     	}
    -	if ( symbol.scope === "static" ) {
    +	if ( symbol.scope === 'static' ) {
     		attrib("static", true, false, /* raw = */true);
     	}
    -	attrib("visibility", visibility(symbol), "public");
    +	attrib("visibility", visibility(symbol), 'public');
     	if ( symbol.since ) {
     		attrib("since", extractVersion(symbol.since));
     	}
    -	if ( symbol.augments && symbol.augments.length ) {
    -		tag("extends", symbol.augments.sort().join(",")); // TODO what about multiple inheritance?
    +	/* TODO if ( kind === 'class' || kind === 'interface' ) { */
    +		// Note reg. the TODO: some objects document that they extend other objects. JSDoc seems to support this use case
    +		// (properties show up as 'borrowed') and the borrowed entities also show up in the SDK (but not the 'extends' relationship itself)
    +		if ( symbol.augments && symbol.augments.length ) {
    +			tag("extends", symbol.augments.sort().join(",")); // TODO what about multiple inheritance?
    +		}
    +		interfaceList("implements", symbol.implements);
    +	/* } */
    +	tag("description", normalizeWS(symbol.classdesc || (symbol.kind === 'class' ? '' : symbol.description)), true);
    +	if ( kind !== 'class' ) {
    +		examples(symbol); // for a class, examples are added to the constructor
    +		referencesList(symbol); // for a class, references are added to the constructor
     	}
    -	interfaceList("implements", symbol.implements);
    -	tag("description", normalizeWS(symbol.classdesc || (symbol.kind === "class" ? "" : symbol.description)), true);
     	tagWithSince("experimental", symbol.experimental);
     	tagWithSince("deprecated", symbol.deprecated);
    -	if ( symbol.tags && symbol.tags.some(function(tag) {
    -		return tag.title === "ui5-metamodel";
    -	}) ) {
    -		attrib("ui5-metamodel", true, false, /* raw = */true);
    +	if ( symbol.tags && symbol.tags.some(function(tag) { return tag.title === 'ui5-metamodel'; }) ) {
    +		attrib('ui5-metamodel', true, false, /* raw = */true);
     	}
     
    -	let i; let j; let member; let param;
    +	var skipMembers = false;
    +	var i, j, member, param;
    +
    +	if ( kind === 'class' ) {
     
    -	if ( kind === "class" ) {
     		if ( symbol.__ui5.stereotype || hasSettings(symbol) ) {
    +
     			tag("ui5-metadata");
     
     			if ( symbol.__ui5.stereotype ) {
    @@ -2713,35 +2812,11 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     
     		// IF @hideconstructor tag is present we omit the whole constructor
     		if ( !symbol.hideconstructor ) {
    +
     			tag("constructor");
     			attrib("visibility", visibility(symbol));
    -			if (symbol.params && symbol.params.length > 0) {
    -				collection("parameters");
    -				for (j = 0; j < symbol.params.length; j++) {
    -					param = symbol.params[j];
    -					if (param.name.indexOf(".") >= 0) {
    -						continue;
    -					}
    -					tag("parameter");
    -					attrib("name", param.name);
    -					attrib("type", listTypes(param.type));
    -					attrib("optional", !!param.optional, false, /* raw = */true);
    -					if (param.defaultvalue !== undefined) {
    -						attrib("defaultValue", param.defaultvalue, undefined, /* raw = */true);
    -					}
    -					if (param.since) {
    -						attrib("since", extractVersion(param.since));
    -					}
    +			methodSignature(symbol, /* suppressReturnValue = */ true);
     
    -					writeParameterProperties(param.name, symbol.params);
    -					tag("description", normalizeWS(param.description), true);
    -					tagWithSince("experimental", param.experimental);
    -					tagWithSince("deprecated", param.deprecated);
    -					closeTag("parameter");
    -				}
    -				endCollection("parameters");
    -			}
    -			exceptions(symbol);
     			tag("description", normalizeWS(symbol.description), true);
     			// tagWithSince("experimental", symbol.experimental); // TODO repeat from class?
     			// tagWithSince("deprecated", symbol.deprecated); // TODO repeat from class?
    @@ -2749,8 +2824,9 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     			referencesList(symbol); // TODO here or for class?
     			// secTags(symbol); // TODO repeat from class?
     			closeTag("constructor");
    +
     		}
    -	} else if ( kind === "namespace" ) {
    +	} else if ( kind === 'namespace' ) {
     		if ( symbol.__ui5.stereotype || symbol.__ui5.metadata ) {
     			tag("ui5-metadata");
     
    @@ -2772,181 +2848,139 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     
     			closeTag("ui5-metadata");
     		}
    -	}
    -
    -	const ownProperties = childrenOfKind(symbol, "property").own.sort(sortByAlias);
    -	if ( ownProperties.length > 0 ) {
    -		collection("properties");
    -		for ( i = 0; i < ownProperties.length; i++ ) {
    -			member = ownProperties[i];
    -			tag("property");
    -			attrib("name", member.name);
    -			if ( member.__ui5.module && member.__ui5.module !== symbol.__ui5.module ) {
    -				attrib("module", member.__ui5.module);
    -				attrib("export", undefined, "", true);
    -			}
    -			attrib("visibility", visibility(member), "public");
    -			if ( member.scope === "static" ) {
    -				attrib("static", true, false, /* raw = */true);
    -			}
    -			if ( member.since ) {
    -				attrib("since", extractVersion(member.since));
    -			}
    -			attrib("type", listTypes(member.type));
    -			tag("description", normalizeWS(member.description), true);
    -			tagWithSince("experimental", member.experimental);
    -			tagWithSince("deprecated", member.deprecated);
    -			examples(member);
    -			referencesList(member);
    -			if ( member.__ui5.resource && member.__ui5.resource !== symbol.__ui5.resource ) {
    -				attrib("resource", member.__ui5.resource);
    -			}
    -			closeTag("property");
    +	} else if ( kind === 'typedef' ) {
    +		// typedefs have their own property structure
    +		skipMembers = true;
    +		if ( symbol.properties && symbol.properties.length > 0 ) {
    +			collection("properties");
    +			symbol.properties.forEach(function(prop) {
    +				tag("property");
    +				attrib("name", prop.name);
    +				attrib("type", listTypes(prop.type));
    +				attrib("visibility", visibility(symbol), 'public'); // properties inherit visibility of typedef
    +				tag("description", normalizeWS(prop.description), true);
    +				closeTag("property");
    +			});
    +			endCollection("properties");
     		}
    -		endCollection("properties");
    +	} else if ( kind === 'function' ) {
    +		methodSignature(symbol);
     	}
     
    -	const ownEvents = childrenOfKind(symbol, "event").own.sort(sortByAlias);
    -	if ( ownEvents.length > 0 ) {
    -		collection("events");
    -		for (i = 0; i < ownEvents.length; i++ ) {
    -			member = ownEvents[i];
    -			tag("event");
    -			attrib("name", member.name);
    -			if ( member.__ui5.module && member.__ui5.module !== symbol.__ui5.module ) {
    -				attrib("module", member.__ui5.module);
    -				attrib("export", undefined, "", true);
    -			}
    -			attrib("visibility", visibility(member), "public");
    -			if ( member.scope === "static" ) {
    -				attrib("static", true, false, /* raw = */true);
    -			}
    -			if ( member.since ) {
    -				attrib("since", extractVersion(member.since));
    -			}
    -
    -			if ( member.params && member.params.length > 0 ) {
    -				collection("parameters");
    -				for (j = 0; j < member.params.length; j++) {
    -					param = member.params[j];
    -					if ( param.name.indexOf(".") >= 0 ) {
    -						continue;
    -					}
    -
    -					tag("parameter");
    -					attrib("name", param.name);
    -					attrib("type", listTypes(param.type));
    -					if ( param.since ) {
    -						attrib("since", extractVersion(param.since));
    -					}
    -					writeParameterProperties(param.name, member.params);
    -					tag("description", normalizeWS(param.description), true);
    -					tagWithSince("experimental", param.experimental);
    -					tagWithSince("deprecated", param.deprecated);
    -					closeTag("parameter");
    +	if ( !skipMembers ) {
    +		var ownProperties = childrenOfKind(symbol, "property").own.sort(sortByAlias);
    +		if ( ownProperties.length > 0 ) {
    +			collection("properties");
    +			for ( i = 0; i < ownProperties.length; i++ ) {
    +				member = ownProperties[i];
    +				tag("property");
    +				attrib("name", member.name);
    +				if ( member.__ui5.module && member.__ui5.module !== symbol.__ui5.module ) {
    +					attrib("module", member.__ui5.module);
    +					attrib("export", undefined, '', true);
     				}
    -				endCollection("parameters");
    -			}
    -			tag("description", normalizeWS(member.description), true);
    -			tagWithSince("deprecated", member.deprecated);
    -			tagWithSince("experimental", member.experimental);
    -			examples(member);
    -			referencesList(member);
    -			// secTags(member);
    -			if ( member.__ui5.resource && member.__ui5.resource !== symbol.__ui5.resource ) {
    -				attrib("resource", member.__ui5.resource);
    +				attrib("visibility", visibility(member), 'public');
    +				if ( member.scope === 'static' ) {
    +					attrib("static", true, false, /* raw = */true);
    +				}
    +				if ( member.since ) {
    +					attrib("since", extractVersion(member.since));
    +				}
    +				attrib("type", listTypes(member.type));
    +				tag("description", normalizeWS(member.description), true);
    +				tagWithSince("experimental", member.experimental);
    +				tagWithSince("deprecated", member.deprecated);
    +				examples(member);
    +				referencesList(member);
    +				if ( member.__ui5.resource && member.__ui5.resource !== symbol.__ui5.resource ) {
    +					attrib("resource", member.__ui5.resource);
    +				}
    +				closeTag("property");
     			}
    -			closeTag("event");
    +			endCollection("properties");
     		}
    -		endCollection("events");
    -	}
    -
    -	const ownMethods = childrenOfKind(symbol, "method").own.sort(sortByAlias);
    -	if ( ownMethods.length > 0 ) {
    -		collection("methods");
    -		for ( i = 0; i < ownMethods.length; i++ ) {
    -			member = ownMethods[i];
    -			tag("method");
    -			attrib("name", member.name);
    -			if ( member.__ui5.module && member.__ui5.module !== symbol.__ui5.module ) {
    -				attrib("module", member.__ui5.module);
    -				attrib("export", undefined, "", true);
    -			}
    -			attrib("visibility", visibility(member), "public");
    -			if ( member.scope === "static" ) {
    -				attrib("static", true, false, /* raw = */true);
    -			}
    -			if ( member.tags && member.tags.some(function(tag) {
    -				return tag.title === "ui5-metamodel";
    -			}) ) {
    -				attrib("ui5-metamodel", true, false, /* raw = */true);
    -			}
     
    -			const returns = member.returns && member.returns.length && member.returns[0];
    -			let type = member.type || (returns && returns.type);
    -			type = listTypes(type);
    -			// if ( type && type !== 'void' ) {
    -			//	attrib("type", type, 'void');
    -			// }
    -			if ( type && type !== "void" || returns && returns.description ) {
    -				tag("returnValue");
    -				if ( type && type !== "void" ) {
    -					attrib("type", type);
    +		var ownEvents = childrenOfKind(symbol, 'event').own.sort(sortByAlias);
    +		if ( ownEvents.length > 0 ) {
    +			collection("events");
    +			for (i = 0; i < ownEvents.length; i++ ) {
    +				member = ownEvents[i];
    +				tag("event");
    +				attrib("name", member.name);
    +				if ( member.__ui5.module && member.__ui5.module !== symbol.__ui5.module ) {
    +					attrib("module", member.__ui5.module);
    +					attrib("export", undefined, '', true);
     				}
    -				if ( returns && returns.description ) {
    -					attrib("description", normalizeWS(returns.description));
    +				attrib("visibility", visibility(member), 'public');
    +				if ( member.scope === 'static' ) {
    +					attrib("static", true, false, /* raw = */true);
    +				}
    +				if ( member.since ) {
    +					attrib("since", extractVersion(member.since));
     				}
    -				closeTag("returnValue");
    -			}
    -			if ( member.since ) {
    -				attrib("since", extractVersion(member.since));
    -			}
     
    -			if ( member.params && member.params.length > 0 ) {
    -				collection("parameters");
    -				for ( j = 0; j < member.params.length; j++) {
    -					param = member.params[j];
    -					if ( param.name.indexOf(".") >= 0 ) {
    -						continue;
    -					}
    -					tag("parameter");
    -					attrib("name", param.name);
    -					attrib("type", listTypes(param.type));
    -					attrib("optional", !!param.optional, false, /* raw = */true);
    -					if ( param.defaultvalue !== undefined ) {
    -						attrib("defaultValue", param.defaultvalue, undefined, /* raw = */true);
    -					}
    -					if ( param.since ) {
    -						attrib("since", extractVersion(param.since));
    +				if ( member.params && member.params.length > 0 ) {
    +					collection("parameters");
    +					for (j = 0; j < member.params.length; j++) {
    +						param = member.params[j];
    +						if ( param.name.indexOf('.') >= 0 ) {
    +							continue;
    +						}
    +
    +						tag("parameter");
    +						attrib("name", param.name);
    +						attrib("type", listTypes(param.type));
    +						if ( param.since ) {
    +							attrib("since", extractVersion(param.since));
    +						}
    +						writeParameterProperties(param, member.params);
    +						tag("description", normalizeWS(param.description), true);
    +						tagWithSince("experimental", param.experimental);
    +						tagWithSince("deprecated", param.deprecated);
    +						closeTag("parameter");
     					}
    -					writeParameterProperties(param.name, member.params);
    -					tag("description", normalizeWS(param.description), true);
    -					tagWithSince("experimental", param.experimental);
    -					tagWithSince("deprecated", param.deprecated);
    -					closeTag("parameter");
    +					endCollection("parameters");
     				}
    -				endCollection("parameters");
    -			}
    -			exceptions(member);
    -			tag("description", normalizeWS(member.description), true);
    -			tagWithSince("experimental", member.experimental);
    -			tagWithSince("deprecated", member.deprecated);
    -			examples(member);
    -			referencesList(member);
    -			// secTags(member);
    -			if ( member.__ui5.resource && member.__ui5.resource !== symbol.__ui5.resource ) {
    -				attrib("resource", member.__ui5.resource);
    +				tag("description", normalizeWS(member.description), true);
    +				tagWithSince("deprecated", member.deprecated);
    +				tagWithSince("experimental", member.experimental);
    +				examples(member);
    +				referencesList(member);
    +				//secTags(member);
    +				if ( member.__ui5.resource && member.__ui5.resource !== symbol.__ui5.resource ) {
    +					attrib("resource", member.__ui5.resource);
    +				}
    +				closeTag("event");
     			}
    -			closeTag("method");
    +			endCollection("events");
    +		}
    +
    +		var ownMethods = childrenOfKind(symbol, 'method').own.sort(sortByAlias);
    +		if ( ownMethods.length > 0 ) {
    +			collection("methods");
    +			ownMethods.forEach(function(member) {
    +				writeMethod(member);
    +				if ( member.__ui5.members ) {
    +					// HACK: export nested static functions as siblings of the current function
    +					// A correct representation has to be discussed with the SDK / WebIDE
    +					member.__ui5.members.forEach(function($) {
    +						if ( $.kind === 'function' && $.scope === 'static'
    +							 && conf.filter($) && !$.inherited ) {
    +							error("exporting nested function '" + member.name + "." + $.name + "'");
    +							writeMethod($, member.name + "." + $.name);
    +						}
    +					});
    +				}
    +			});
    +			endCollection("methods");
     		}
    -		endCollection("methods");
    -	}
     
     	//	if ( roots && symbol.__ui5.children && symbol.__ui5.children.length ) {
     	//		collection("children", "kind");
     	//		symbol.__ui5.children.forEach(writeSymbol);
     	//		endCollection("children");
     	//	}
    +	}
     
     	closeTag(kind);
     
    @@ -2954,9 +2988,9 @@ function createAPIJSON4Symbol(symbol, omitDefaults) {
     }
     
     function postProcessAPIJSON(api) {
    -	const modules = {};
    -	let symbols = api.symbols;
    -	let i; let j; let n; let symbol; let defaultExport;
    +	var modules = {};
    +	var symbols = api.symbols;
    +	var i,j,n,symbol,defaultExport;
     
     	// collect modules and the symbols that refer to them
     	for ( i = 0; i < symbols.length; i++) {
    @@ -2992,98 +3026,85 @@ function postProcessAPIJSON(api) {
     		}
     	}
     
    -	function guessExport(defaultExport, symbol) {
    -		if ( symbol.name === defaultExport ) {
    -			// default export equals the symbol name
    -			symbol.symbol.export = "";
    -			// console.log("    (default):" + defaultExport);
    -		} else if ( symbol.name.lastIndexOf(defaultExport + ".", 0) === 0 ) {
    -			// default export is a prefix of the symbol name
    -			symbol.symbol.export = symbol.name.slice(defaultExport.length + 1);
    -			// console.log("    " + symbol.name.slice(defaultExport.length + 1) + ":" + symbol.name);
    -		} else {
    -			// default export is not a prefix of the symbol name -> no way to access it in AMD
    -			symbol.symbol.export = undefined;
    -			console.log("    **** could not identify module export for API " + symbol.name);
    -		}
    -	}
    +	function guessExports(moduleName, symbols) {
     
    -	for ( n in modules ) {
    -		symbols = modules[n].sort(function(a, b) {
    +		symbols = symbols.sort(function(a,b) {
     			if ( a.name === b.name ) {
     				return 0;
     			}
     			return a.name < b.name ? -1 : 1;
     		});
     
    -		// console.log('  resolved exports of ' + n + ": " + symbols.map(function(symbol) { return symbol.name; } ));
    -		if ( /^jquery\.sap\./.test(n) ) {
    +		// info('resolving exports of ' + n + ": " + symbols.map(function(symbol) { return symbol.name; } ));
    +		var moduleNamePath = "module:" + moduleName;
    +		if ( /^jquery\.sap\./.test(moduleName) ) {
     			// the jquery.sap.* modules all export 'jQuery'.
     			// any API from those modules is reachable via 'jQuery.*'
    -			defaultExport = "jQuery";
    -			symbols.forEach(
    -				guessExport.bind(this, defaultExport)
    -			);
    -		} else if ( /\/library$/.test(n) ) {
    -			// library.js modules export the library namespace
    -			defaultExport = n.replace(/\/library$/, "").replace(/\//g, ".");
    -			if ( symbols.some(function(symbol) {
    -				return symbol.name === defaultExport;
    -			}) ) {
    -				// if there is a symbol for the namespace, then all other symbols from the module should be sub-exports of that symbol
    -				symbols.forEach(
    -					guessExport.bind(this, defaultExport)
    -				);
    -			} else {
    -				// otherwise, we don't know how to map it to an export
    -				symbols.forEach(function(symbol) {
    -					symbol.symbol.export = symbol.name;
    -					console.log("    **** unresolved " + symbol.name + " in library.js (no export that matches module name)");
    -				});
    -			}
    +			defaultExport = 'jQuery';
     		} else {
    -			// for all other modules, the assumed default export is identical to the name of the module (converted to a 'dot' name)
    -			defaultExport = n.replace(/\//g, ".");
    -			if ( symbols.some(function(symbol) {
    -				return symbol.name === defaultExport;
    -			}) ) {
    -				symbols.forEach(
    -					guessExport.bind(this, defaultExport)
    -				);
    -			// } else if ( symbols.length === 1 && (symbols[0].symbol.kind === 'class' || symbols[0].symbol.kind === 'namespace') ) {
    -				// if there is only one symbol and if that symbol is of type class or namespace, assume it is the default export
    -				// TODO is that assumption safe? Was only done because of IBarPageEnabler (which maybe better should be fixed in the JSDoc)
    -				// symbols[0].symbol.export = '';
    +			// library.js modules export the library namespace; for all other modules, the assumed default export
    +			// is identical to the name of the module (converted to a 'dot' name)
    +			defaultExport = moduleName.replace(/\/library$/, "").replace(/\//g, ".");
    +		}
    +
    +		symbols.forEach(function(symbol) {
    +			// debug("check ", symbol.name, "against", defaultExport, "and", moduleNamePath);
    +			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 ) {
    +				// symbol name starts with the module namepath and a dot -> symbol is a named export (static)
    +				symbol.symbol.export = symbol.name.slice(moduleNamePath.length + 1);
    +			} else if ( symbol.name === defaultExport ) {
    +				// default export equals the symbol name
    +				symbol.symbol.export = "";
    +				//debug("    (default):" + defaultExport);
    +			} else if ( symbol.name.lastIndexOf(defaultExport + ".", 0) === 0 ) {
    +				// default export is a prefix of the symbol name
    +				symbol.symbol.export = symbol.name.slice(defaultExport.length + 1);
    +				//debug("    " + symbol.name.slice(defaultExport.length + 1) + ":" + symbol.name);
     			} else {
    -				symbols.forEach(function(symbol) {
    -					symbol.symbol.export = undefined;
    -					console.log("    **** unresolved " + symbol.name + " (no export that matches module name)");
    -				});
    +				// default export is not a prefix of the symbol name -> no way to access it in AMD
    +				symbol.symbol.export = undefined;
    +				if ( symbol.symbol.kind !== "namespace"
    +					 || (symbol.symbol.properties && symbol.symbol.properties.length > 0)
    +					 || (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 + "'");
    +				}
     			}
    -		}
    +		});
    +
    +	}
    +
    +	for ( n in modules ) {
    +		guessExports(n, modules[n]);
     	}
     }
     
    -// ---- add on: API XML -----------------------------------------------------------------
    +//---- add on: API XML -----------------------------------------------------------------
     
     function createAPIXML(symbols, filename, options) {
    +
     	options = options || {};
    -	const roots = options.roots || null;
    -	const legacyContent = !!options.legacyContent;
    -	const omitDefaults = !!options.omitDefaults;
    -	const addRedundancy = !!options.resolveInheritance;
    -
    -	let indent = 0;
    -	const output = [];
    -	const sIndent = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
    -	let tags = [];
    -	const ENUM = legacyContent ? "namespace" : "enum";
    -	const BASETYPE = legacyContent ? "baseType" : "extends";
    -	const PROPERTY = legacyContent ? "parameter" : "property";
    -	let unclosedStartTag = false;
    +	var roots = options.roots || null;
    +	var legacyContent = !!options.legacyContent;
    +	var omitDefaults = !!options.omitDefaults;
    +	var addRedundancy = !!options.resolveInheritance;
    +
    +	var indent = 0;
    +	var output = [];
    +	var sIndent = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
    +	var tags = [];
    +	var ENUM = legacyContent ? "namespace" : "enum";
    +	var BASETYPE = legacyContent ? "baseType" : "extends";
    +	var PROPERTY = legacyContent ? "parameter" : "property";
    +	var unclosedStartTag = false;
     
     	function getAPIJSON(name) {
    -		const symbol = lookup(name);
    +
    +		var symbol = lookup(name);
     		if ( symbol && !symbol.synthetic ) {
     			return createAPIJSON4Symbol(symbol, false);
     		}
    @@ -3098,21 +3119,21 @@ function createAPIXML(symbols, filename, options) {
     		return s ? s.replace(/&/g, "&").replace(/ 0 ) {
    -			output.push(sIndent.slice(0, indent));
    +			output.push(sIndent.slice(0,indent));
     		}
    -		if ( args.length ) {
    -			for (let i = 0; i < args.length; i++) {
    -				output.push(args[i]);
    +		if ( arguments.length ) {
    +			for (var i = 0; i < arguments.length; i++) {
    +				output.push(arguments[i]);
     			}
     		}
     		output.push("\n");
    @@ -3133,16 +3154,17 @@ function createAPIXML(symbols, filename, options) {
     	}
     
     	function tag(name, value, omitEmpty) {
    +
     		if ( omitEmpty && !value ) {
     			return;
     		}
     		if ( unclosedStartTag ) {
     			unclosedStartTag = false;
    -			write(">\n");
    +			write('>\n');
     		}
     		if ( arguments.length === 1 ) { // opening tag
     			if ( indent > 0 ) {
    -				output.push(sIndent.slice(0, indent));
    +				output.push(sIndent.slice(0,indent));
     			}
     			write("<", name);
     			unclosedStartTag = true;
    @@ -3162,7 +3184,7 @@ function createAPIXML(symbols, filename, options) {
     	}
     
     	function attrib(name, value, defaultValue) {
    -		const emptyTag = arguments.length === 1;
    +		var emptyTag = arguments.length === 1;
     		if ( omitDefaults && arguments.length === 3 && value === defaultValue ) {
     			return;
     		}
    @@ -3171,18 +3193,17 @@ function createAPIXML(symbols, filename, options) {
     			write(" " + name + "=\"");
     			write(emptyTag ? "true" : encode(String(value)).replace(/"/g, """));
     			write("\"");
    +		} else if ( emptyTag ) {
    +			writeln("<", name, "/>");
     		} else {
    -			if ( emptyTag ) {
    -				writeln("<", name, "/>");
    -			} else {
    -				writeln("<", name, ">", encode(String(value)), "");
    -			}
    +			writeln("<", name, ">", encode(String(value)), "");
     		}
     	}
     
     	function closeTag(name, noIndent) {
    +
     		indent--;
    -		const top = tags.pop();
    +		var top = tags.pop();
     		if ( top != name ) {
     			// ERROR?
     		}
    @@ -3200,7 +3221,7 @@ function createAPIXML(symbols, filename, options) {
     	function textContent(text) {
     		if ( unclosedStartTag ) {
     			unclosedStartTag = false;
    -			write(">");
    +			write('>');
     		}
     		write(encode(text));
     	}
    @@ -3223,13 +3244,14 @@ function createAPIXML(symbols, filename, options) {
     	}
     
     	function writeMetadata(symbolAPI, inherited) {
    -		const ui5Metadata = symbolAPI["ui5-metadata"];
    +
    +		var ui5Metadata = symbolAPI["ui5-metadata"];
     		if ( !ui5Metadata ) {
     			return;
     		}
     
     		if ( addRedundancy && symbolAPI["extends"] ) {
    -			const baseSymbolAPI = getAPIJSON(symbolAPI["extends"]);
    +			var baseSymbolAPI = getAPIJSON(symbolAPI["extends"]);
     			if ( baseSymbolAPI ) {
     				writeMetadata(baseSymbolAPI, true);
     			}
    @@ -3240,7 +3262,7 @@ function createAPIXML(symbols, filename, options) {
     				tag("specialSetting");
     				attrib("name", special.name);
     				attrib("type", special.type);
    -				attrib("visibility", special.visibility, "public");
    +				attrib("visibility", special.visibility, 'public');
     				if ( special.since ) {
     					attrib("since", special.since);
     				}
    @@ -3259,11 +3281,11 @@ function createAPIXML(symbols, filename, options) {
     			ui5Metadata.properties.forEach(function(prop) {
     				tag("property");
     				attrib("name", prop.name);
    -				attrib("type", prop.type, "string");
    +				attrib("type", prop.type, 'string');
     				if ( prop.defaultValue !== null ) {
     					attrib("defaultValue", prop.defaultValue, null);
     				}
    -				attrib("visibility", prop.visibility, "public");
    +				attrib("visibility", prop.visibility, 'public');
     				if ( prop.since ) {
     					attrib("since", prop.since);
     				}
    @@ -3290,12 +3312,12 @@ function createAPIXML(symbols, filename, options) {
     				tag("aggregation");
     				attrib("name", aggr.name);
     				attrib("singularName", aggr.singularName); // TODO omit default?
    -				attrib("type", aggr.type, "sap.ui.core.Control");
    +				attrib("type", aggr.type, 'sap.ui.core.Control');
     				if ( aggr.altTypes ) {
     					attrib("altTypes", aggr.altTypes.join(","));
     				}
    -				attrib("cardinality", aggr.cardinality, "0..n");
    -				attrib("visibility", aggr.visibility, "public");
    +				attrib("cardinality", aggr.cardinality, '0..n');
    +				attrib("visibility", aggr.visibility, 'public');
     				if ( aggr.since ) {
     					attrib("since", aggr.since);
     				}
    @@ -3322,9 +3344,9 @@ function createAPIXML(symbols, filename, options) {
     				tag("association");
     				attrib("name", assoc.name);
     				attrib("singularName", assoc.singularName); // TODO omit default?
    -				attrib("type", assoc.type, "sap.ui.core.Control");
    -				attrib("cardinality", assoc.cardinality, "0..1");
    -				attrib("visibility", assoc.visibility, "public");
    +				attrib("type", assoc.type, 'sap.ui.core.Control');
    +				attrib("cardinality", assoc.cardinality, '0..1');
    +				attrib("visibility", assoc.visibility, 'public');
     				if ( assoc.since ) {
     					attrib("since", assoc.since);
     				}
    @@ -3343,7 +3365,7 @@ function createAPIXML(symbols, filename, options) {
     			ui5Metadata.events.forEach(function(event) {
     				tag("event");
     				attrib("name", event.name);
    -				attrib("visibility", event.visibility, "public");
    +				attrib("visibility", event.visibility, 'public');
     				if ( event.since ) {
     					attrib("since", event.since);
     				}
    @@ -3355,9 +3377,9 @@ function createAPIXML(symbols, filename, options) {
     				tagWithSince("deprecated", event.deprecated);
     				if ( event.parameters ) {
     					tag("parameters");
    -					for ( const pn in event.parameters ) {
    +					for ( var pn in event.parameters ) {
     						if ( event.parameters.hasOwnProperty(pn) ) {
    -							const param = event.parameters[pn];
    +							var param = event.parameters[pn];
     
     							tag("parameter");
     							attrib("name", param.name);
    @@ -3394,22 +3416,24 @@ function createAPIXML(symbols, filename, options) {
     				closeTag("annotation");
     			});
     		}
    +
     	}
     
     	function writeParameterPropertiesForMSettings(symbolAPI, inherited) {
    -		const ui5Metadata = symbolAPI["ui5-metadata"];
    +
    +		var ui5Metadata = symbolAPI["ui5-metadata"];
     		if ( !ui5Metadata ) {
     			return;
     		}
     
     		if ( symbolAPI["extends"] ) {
    -			const baseSymbolAPI = getAPIJSON(symbolAPI["extends"]);
    +			var baseSymbolAPI = getAPIJSON(symbolAPI["extends"]);
     			writeParameterPropertiesForMSettings(baseSymbolAPI, true);
     		}
     
     		if ( ui5Metadata.specialSettings ) {
     			ui5Metadata.specialSettings.forEach(function(special) {
    -				if ( special.visibility !== "hidden" ) {
    +				if ( special.visibility !== 'hidden' ) {
     					tag("property");
     					attrib("name", special.name);
     					attrib("type", special.type);
    @@ -3428,9 +3452,9 @@ function createAPIXML(symbols, filename, options) {
     				tag("property");
     				attrib("name", prop.name);
     				attrib("type", prop.type);
    -				attrib("group", prop.group, "Misc");
    +				attrib("group", prop.group, 'Misc');
     				if ( prop.defaultValue !== null ) {
    -					attrib("defaultValue", typeof prop.defaultValue === "string" ? "\"" + prop.defaultValue + "\"" : prop.defaultValue);
    +					attrib("defaultValue", typeof prop.defaultValue === 'string' ? "\"" + prop.defaultValue + "\"" : prop.defaultValue);
     				}
     				attrib("optional");
     				if ( inherited ) {
    @@ -3446,7 +3470,7 @@ function createAPIXML(symbols, filename, options) {
     				if ( aggr.visibility !== "hidden" ) {
     					tag("property");
     					attrib("name", aggr.name);
    -					attrib("type", aggr.type + (aggr.cardinality === "0..1" ? "" : "[]"));
    +					attrib("type", aggr.type + (aggr.cardinality === '0..1' ? "" : "[]"));
     					if ( aggr.altTypes ) {
     						attrib("altTypes", aggr.altTypes.join(","));
     					}
    @@ -3465,7 +3489,7 @@ function createAPIXML(symbols, filename, options) {
     				if ( assoc.visibility !== "hidden" ) {
     					tag("property");
     					attrib("name", assoc.name);
    -					attrib("type", "(" + assoc.type + "|" + "string)" + (assoc.cardinality === "0..1" ? "" : "[]"));
    +					attrib("type", "(" + assoc.type + "|" + "string)" + (assoc.cardinality === '0..1' ? "" : "[]"));
     					attrib("optional");
     					if ( inherited ) {
     						attrib("origin", symbolAPI.name);
    @@ -3489,20 +3513,18 @@ function createAPIXML(symbols, filename, options) {
     				closeTag("property");
     			});
     		}
    +
     	}
     
     	function writeParameterProperties(param, paramName) {
    -		const props = param.parameterProperties;
    -
    -
    -		const prefix = paramName + ".";
    -
    -
    -		let count = 0;
    +		var props = param.parameterProperties,
    +			prefix = paramName + '.',
    +			count = 0;
     
     		if ( props ) {
    -			for (const n in props ) {
    +			for (var n in props ) {
     				if ( props.hasOwnProperty(n) ) {
    +
     					param = props[n];
     
     					if ( !legacyContent && count === 0 ) {
    @@ -3576,21 +3598,23 @@ function createAPIXML(symbols, filename, options) {
     	*/
     
     	function writeSymbol(symbol) {
    -		let kind;
     
    -		if ( isaClass(symbol) && (roots || !symbol.synthetic) ) { // dump a symbol if it as a class symbol and if either hierarchies are dumped or if it is not a synthetic symbol
    +		var kind;
    +
    +		if ( isFirstClassSymbol(symbol) && (roots || !symbol.synthetic) ) { // dump a symbol if it as a class symbol and if either hierarchies are dumped or if it is not a synthetic symbol
    +
     			// for the hierarchy we use only the local information
    -			const symbolAPI = createAPIJSON4Symbol(symbol);
    +			var symbolAPI = createAPIJSON4Symbol(symbol);
     
    -			kind = symbolAPI.kind === "enum" ? ENUM : symbolAPI.kind;
    +			kind = symbolAPI.kind === 'enum' ? ENUM : symbolAPI.kind;
     
     			tag(kind);
     
     			attrib("name", symbolAPI.name);
     			attrib("basename", symbolAPI.basename);
    -			//			if ( symbolAPI["resource"] ) {
    -			//				attrib("resource");
    -			//			}
    +//			if ( symbolAPI["resource"] ) {
    +//				attrib("resource");
    +//			}
     			if ( symbolAPI["module"] ) {
     				attrib("module", symbolAPI["module"]);
     			}
    @@ -3603,7 +3627,7 @@ function createAPIXML(symbols, filename, options) {
     			if ( symbolAPI["static"] ) {
     				attrib("static");
     			}
    -			attrib("visibility", symbolAPI.visibility, "public");
    +			attrib("visibility", symbolAPI.visibility, 'public');
     			if ( symbolAPI.since ) {
     				attrib("since", symbolAPI.since);
     			}
    @@ -3614,10 +3638,12 @@ function createAPIXML(symbols, filename, options) {
     			tagWithSince("experimental", symbolAPI.experimental);
     			tagWithSince("deprecated", symbolAPI.deprecated);
     
    -			if ( kind === "class" ) {
    -				const hasSettings = symbolAPI["ui5-metadata"];
    +			if ( kind === 'class' ) {
    +
    +				var hasSettings = symbolAPI["ui5-metadata"];
     
     				if ( !legacyContent && symbolAPI["ui5-metadata"] ) {
    +
     					tag("ui5-metadata");
     
     					if ( symbolAPI["ui5-metadata"].stereotype ) {
    @@ -3627,15 +3653,17 @@ function createAPIXML(symbols, filename, options) {
     					writeMetadata(symbolAPI);
     
     					closeTag("ui5-metadata");
    +
     				}
     
     				tag("constructor");
     				if ( legacyContent ) {
     					attrib("name", symbolAPI.basename);
     				}
    -				attrib("visibility", symbolAPI.visibility, "public");
    +				attrib("visibility", symbolAPI.visibility, 'public');
     				if ( symbolAPI.constructor.parameters ) {
     					symbolAPI.constructor.parameters.forEach(function(param, j) {
    +
     						tag("parameter");
     						attrib("name", param.name);
     						attrib("type", param.type);
    @@ -3692,7 +3720,7 @@ function createAPIXML(symbols, filename, options) {
     					if ( member.module ) {
     						attrib("module", member.module);
     					}
    -					attrib("visibility", member.visibility, "public");
    +					attrib("visibility", member.visibility, 'public');
     					if ( member["static"] ) {
     						attrib("static");
     					}
    @@ -3714,7 +3742,7 @@ function createAPIXML(symbols, filename, options) {
     					if ( member.module ) {
     						attrib("module", member.module);
     					}
    -					attrib("visibility", member.visibility, "public");
    +					attrib("visibility", member.visibility, 'public');
     					if ( member["static"] ) {
     						attrib("static");
     					}
    @@ -3724,6 +3752,7 @@ function createAPIXML(symbols, filename, options) {
     
     					if ( member.parameters ) {
     						member.parameters.forEach(function(param) {
    +
     							tag("parameter");
     							attrib("name", param.name);
     							attrib("type", param.type);
    @@ -3752,17 +3781,18 @@ function createAPIXML(symbols, filename, options) {
     
     			if ( symbolAPI.methods ) {
     				symbolAPI.methods.forEach(function(member) {
    +
     					tag("method");
     					attrib("name", member.name);
     					if ( member.module ) {
     						attrib("module", member.module);
     					}
    -					attrib("visibility", member.visibility, "public");
    +					attrib("visibility", member.visibility, 'public');
     					if ( member["static"] ) {
     						attrib("static");
     					}
    -					if ( member.returnValue && member.returnValue.type ) {
    -						attrib("type", member.returnValue.type, "void");
    +					if ( member.returnValue && member.returnValue.type  ) {
    +						attrib("type", member.returnValue.type, 'void');
     					}
     					if ( member.since ) {
     						attrib("since", member.since);
    @@ -3770,6 +3800,7 @@ function createAPIXML(symbols, filename, options) {
     
     					if ( member.parameters ) {
     						member.parameters.forEach(function(param) {
    +
     							tag("parameter");
     							attrib("name", param.name);
     							attrib("type", param.type);
    @@ -3799,6 +3830,7 @@ function createAPIXML(symbols, filename, options) {
     					tagWithSince("deprecated", member.deprecated);
     					// TODO secTags(member);
     					closeTag("method");
    +
     				});
     			}
     
    @@ -3809,7 +3841,9 @@ function createAPIXML(symbols, filename, options) {
     			}
     
     			closeTag(kind);
    +
     		}
    +
     	}
     
     	writeln("");
    @@ -3818,7 +3852,7 @@ function createAPIXML(symbols, filename, options) {
     		namespace("xmlns", "http://www.sap.com/sap.ui.library.api.xsd");
     		attrib("_version", "1.0.0");
     		if ( templateConf.version ) {
    -			attrib("version", templateConf.version.replace(/-SNAPSHOT$/, ""));
    +			attrib("version", templateConf.version.replace(/-SNAPSHOT$/,""));
     		}
     		if ( templateConf.uilib ) {
     			attrib("library", templateConf.uilib);
    @@ -3835,53 +3869,49 @@ function createAPIXML(symbols, filename, options) {
     	closeRootTag("api");
     
     	fs.mkPath(path.dirname(filename));
    -	fs.writeFileSync(filename, getAsString(), "utf8");
    +	fs.writeFileSync(filename, getAsString(), 'utf8');
     }
     
    -// ---- add on: API JS -----------------------------------------------------------------
    +//---- add on: API JS -----------------------------------------------------------------
     
     function createAPIJS(symbols, filename) {
    -	const output = [];
     
    -	const rkeywords = /^(?:abstract|as|boolean|break|byte|case|catch|char|class|continue|const|debugger|default|delete|do|double|else|enum|export|extends|false|final|finally|float|for|function|goto|if|implements|import|in|instanceof|int|interface|is|long|namespace|native|new|null|package|private|protected|public|return|short|static|super|switch|synchronized|this|throw|throws|transient|true|try|typeof|use|var|void|volatile|while|with)$/;
    +	var output = [];
     
    -	function isNoKeyword($) {
    -		return !rkeywords.test($.name);
    -	}
    +	var rkeywords = /^(?:abstract|as|boolean|break|byte|case|catch|char|class|continue|const|debugger|default|delete|do|double|else|enum|export|extends|false|final|finally|float|for|function|goto|if|implements|import|in|instanceof|int|interface|is|long|namespace|native|new|null|package|private|protected|public|return|short|static|super|switch|synchronized|this|throw|throws|transient|true|try|typeof|use|var|void|volatile|while|with)$/;
     
    -	function isAPI($) {
    -		return $.access === "public" || $.access === "protected" || !$.access;
    -	}
    +	function isNoKeyword($) { return !rkeywords.test($.name); }
    +
    +	function isAPI($) { return $.access === 'public' || $.access === 'protected' || !$.access; }
     
    -	function writeln(...args) {
    -		if ( args.length ) {
    -			for (let i = 0; i < args.length; i++) {
    -				output.push(args[i]);
    +	function writeln(args) {
    +		if ( arguments.length ) {
    +			for (var i = 0; i < arguments.length; i++) {
    +				output.push(arguments[i]);
     			}
     		}
     		output.push("\n");
     	}
     
     	function unwrap(docletSrc) {
    -		if (!docletSrc) {
    -			return "";
    -		}
    +		if (!docletSrc) { return ''; }
     
     		// note: keep trailing whitespace for @examples
     		// extra opening/closing stars are ignored
     		// left margin is considered a star and a space
     		// use the /m flag on regex to avoid having to guess what this platform's newline is
     		docletSrc =
    -			docletSrc.replace(/^\/\*\*+/, "") // remove opening slash+stars
    -				.replace(/\**\*\/$/, "\\Z") // replace closing star slash with end-marker
    -				.replace(/^\s*(\* ?|\\Z)/gm, "") // remove left margin like: spaces+star or spaces+end-marker
    -				.replace(/\s*\\Z$/g, ""); // remove end-marker
    +			docletSrc.replace(/^\/\*\*+/, '') // remove opening slash+stars
    +			.replace(/\**\*\/$/, "\\Z")       // replace closing star slash with end-marker
    +			.replace(/^\s*(\* ?|\\Z)/gm, '')  // remove left margin like: spaces+star or spaces+end-marker
    +			.replace(/\s*\\Z$/g, '');         // remove end-marker
     
     		return docletSrc;
     	}
     
     	function comment($, sMetaType) {
    -		let s = unwrap($.comment.toString());
    +
    +		var s = unwrap($.comment.toString());
     
     		// remove the @desc tag
     		s = s.replace(/(\r\n|\r|\n)/gm, "\n");
    @@ -3893,7 +3923,9 @@ function createAPIJS(symbols, filename) {
     		s = s.replace(/^\s*@synthetic[^\r\n]*(\r\n|\r|\n)?/gm, "");
     		s = s.replace(/^\s*<\/p>

    \s*(\r\n|\r|\n)?/gm, "\n"); // skip empty documentation - if ( !s ) return; + if ( !s ) { + return; + } // for namespaces, enforce the @.memberof tag if ( sMetaType === "namespace" && $.memberof && s.indexOf("@memberof") < 0 ) { @@ -3907,44 +3939,41 @@ function createAPIJS(symbols, filename) { writeln(s.split(/\r\n|\r|\n/g).map(function($) { return " * " + $;}).join("\r\n")); writeln(" * /"); */ + } function signature($) { - const p = $.params; - - - const r = []; - - - let i; + var p = $.params, + r = [], + i; if ( p ) { for (i = 0; i < p.length; i++) { // ignore @param tags for 'virtual' params that are used to document members of config-like params // (e.g. like "@param param1.key ...") - if (p[i].name && p[i].name.indexOf(".") < 0) { + if (p[i].name && p[i].name.indexOf('.') < 0) { r.push(p[i].name); } } } - return r.join(","); + return r.join(','); } - function qname(member, parent) { - let r = member.memberof; - if ( member.scope !== "static" ) { + function qname(member,parent) { + var r = member.memberof; + if ( member.scope !== 'static' ) { r += ".prototype"; } return (r ? r + "." : "") + member.name; } - const mValues = { - "boolean": "false", - "int": "0", - "float": "0.0", - "number": "0.0", - "string": "\"\"", - "object": "new Object()", - "function": "function() {}" + var mValues = { + "boolean" : "false", + "int" : "0", + "float" : "0.0", + "number" : "0.0", + "string" : "\"\"", + "object" : "new Object()", + "function" : "function() {}" }; function valueForType(type) { @@ -3968,46 +3997,46 @@ function createAPIJS(symbols, filename) { } function retvalue(member) { - // console.log(member); - const r = valueForType(member.type || (member.returns && member.returns.length && member.returns[0] && member.returns[0].type && member.returns[0].type)); + //debug(member); + var r = valueForType(member.type || (member.returns && member.returns.length && member.returns[0] && member.returns[0].type && member.returns[0].type)); if ( r ) { return "return " + r + ";"; } return ""; } - const sortedSymbols = symbols.slice(0).filter(function($) { - return isaClass($) && isAPI($) && !$.synthetic; - }).sort(sortByAlias); // sort only a copy(!) of the symbols, otherwise the SymbolSet lookup is broken + var sortedSymbols = symbols.slice(0).filter(function($) { return isaClass($) && isAPI($) && !$.synthetic; }).sort(sortByAlias); // sort only a copy(!) of the symbols, otherwise the SymbolSet lookup is broken sortedSymbols.forEach(function(symbol) { - const sMetaType = (symbol.kind === "member" && symbol.isEnum) ? "enum" : symbol.kind; + + var sMetaType = (symbol.kind === 'member' && symbol.isEnum) ? 'enum' : symbol.kind; if ( sMetaType ) { + writeln(""); writeln("// ---- " + symbol.longname + " --------------------------------------------------------------------------"); writeln(""); - let memberId; let member; + var memberId, member; - const ownProperties = childrenOfKind(symbol, "property").own.filter(isNoKeyword).sort(sortByAlias); - if ( sMetaType === "class" ) { - comment(symbol, sMetaType); - writeln(symbol.longname + " = function(" + signature(symbol) + ") {};"); + var ownProperties = childrenOfKind(symbol, 'property').own.filter(isNoKeyword).sort(sortByAlias); + if ( sMetaType === "class" ) { + comment(symbol, sMetaType); + writeln(symbol.longname + " = function(" + signature(symbol) + ") {};"); for ( memberId in ownProperties ) { member = ownProperties[memberId]; comment(member, sMetaType); writeln(qname(member, symbol) + " = " + value(member)); writeln(""); } - } else if ( sMetaType === "namespace" || sMetaType === "enum" ) { - // console.log("found namespace " + symbol.longname); - // console.log(ownProperties); - if ( ownProperties.length ) { - writeln("// dummy function to make Eclipse aware of namespace"); - writeln(symbol.longname + ".toString = function() { return \"\"; };"); + } else if ( sMetaType === 'namespace' || sMetaType === 'enum' ) { + //debug("found namespace " + symbol.longname); + //debug(ownProperties); + if ( ownProperties.length ) { + writeln("// dummy function to make Eclipse aware of namespace"); + writeln(symbol.longname + ".toString = function() { return \"\"; };"); + } } - } - const ownEvents = childrenOfKind(symbol, "event").own.filter(isNoKeyword).sort(sortByAlias); + var ownEvents = childrenOfKind(symbol, 'event').own.filter(isNoKeyword).sort(sortByAlias); if ( ownEvents.length ) { for ( memberId in ownEvents ) { member = ownEvents[memberId]; @@ -4017,7 +4046,7 @@ function createAPIJS(symbols, filename) { } } - const ownMethods = childrenOfKind(symbol, "method").own.filter(isNoKeyword).sort(sortByAlias); + var ownMethods = childrenOfKind(symbol, 'method').own.filter(isNoKeyword).sort(sortByAlias); if ( ownMethods.length ) { for ( memberId in ownMethods ) { member = ownMethods[memberId]; @@ -4026,43 +4055,47 @@ function createAPIJS(symbols, filename) { writeln(""); } } + } }); writeln("// ---- static fields of namespaces ---------------------------------------------------------------------"); sortedSymbols.forEach(function(symbol) { - const sMetaType = (symbol.kind === "member" && symbol.isEnum) ? "enum" : symbol.kind; - if ( sMetaType === "namespace" || sMetaType === "enum" ) { - const ownProperties = childrenOfKind(symbol, "property").own.filter(isNoKeyword).sort(sortByAlias); + var sMetaType = (symbol.kind === 'member' && symbol.isEnum) ? 'enum' : symbol.kind; + + if ( sMetaType === 'namespace' || sMetaType === 'enum' ) { + + var ownProperties = childrenOfKind(symbol, 'property').own.filter(isNoKeyword).sort(sortByAlias); if ( ownProperties.length ) { writeln(""); writeln("// ---- " + symbol.longname + " --------------------------------------------------------------------------"); writeln(""); - for (const memberId in ownProperties ) { - const member = ownProperties[memberId]; + for (var memberId in ownProperties ) { + var member = ownProperties[memberId]; comment(member, sMetaType); writeln(qname(member, symbol) + " = " + value(member) + ";"); writeln(""); } } } + }); fs.mkPath(path.dirname(filename)); - fs.writeFileSync(filename, output.join(""), "utf8"); + fs.writeFileSync(filename, output.join(""), 'utf8'); info(" saved as " + filename); } // Description + Settings function getConstructorDescription(symbol) { - let description = symbol.description; - const tags = symbol.tags; + var description = symbol.description; + var tags = symbol.tags; if ( tags ) { - for (let i = 0; i < tags.length; i++) { + for (var i = 0; i < tags.length; i++) { if ( tags[i].title === "ui5-settings" && tags[i].text) { description += "\n

    \n" + tags[i].text; break; @@ -4076,13 +4109,11 @@ function getConstructorDescription(symbol) { // Example function makeExample(example) { - const result = { - caption: null, - example: example - }; - - - const match = /^\s*([\s\S]+?)<\/caption>(?:[ \t]*[\n\r]*)([\s\S]+)$/i.exec(example); + var result = { + caption: null, + example: example + }, + match = /^\s*([\s\S]+?)<\/caption>(?:[ \t]*[\n\r]*)([\s\S]+)$/i.exec(example); if ( match ) { result.caption = match[1]; From ccfb255041680c4c8590c625556b2a53585719e4 Mon Sep 17 00:00:00 2001 From: Frank Weigel Date: Fri, 8 Mar 2019 00:00:47 +0100 Subject: [PATCH 21/38] Ignore non-existent output files When the JSDoc build is combined with --all, it might also run for themelibs which don't produce any JSDoc output. --- lib/processors/jsdoc/jsdocGenerator.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/processors/jsdoc/jsdocGenerator.js b/lib/processors/jsdoc/jsdocGenerator.js index 4e2be64f0..a7107e006 100644 --- a/lib/processors/jsdoc/jsdocGenerator.js +++ b/lib/processors/jsdoc/jsdocGenerator.js @@ -60,8 +60,7 @@ const jsdocGenerator = async function({sourcePath, targetPath, tmpPath, options} return Promise.all([ fsTarget.byPath(`/test-resources/${namespace}/designtime/api.json`) // fsTarget.byPath(`/libraries/${options.projectName}.js`) - // ]).then((res) => res.filter($=>$)); - ]); + ]).then((res) => res.filter($=>$)); }; From bea340c0f4bde805eac6c05f104642d9e3e61067 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Mon, 11 Mar 2019 10:45:10 +0100 Subject: [PATCH 22/38] Fix ESLint and JSDoc errors And trim some whitespaces --- lib/processors/jsdoc/jsdocGenerator.js | 2 +- .../jsdoc/lib/transform-apijson-for-sdk.js | 22 +++++++++---------- package.json | 3 ++- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/processors/jsdoc/jsdocGenerator.js b/lib/processors/jsdoc/jsdocGenerator.js index a7107e006..6821421d8 100644 --- a/lib/processors/jsdoc/jsdocGenerator.js +++ b/lib/processors/jsdoc/jsdocGenerator.js @@ -60,7 +60,7 @@ const jsdocGenerator = async function({sourcePath, targetPath, tmpPath, options} return Promise.all([ fsTarget.byPath(`/test-resources/${namespace}/designtime/api.json`) // fsTarget.byPath(`/libraries/${options.projectName}.js`) - ]).then((res) => res.filter($=>$)); + ]).then((res) => res.filter(($)=>$)); }; diff --git a/lib/processors/jsdoc/lib/transform-apijson-for-sdk.js b/lib/processors/jsdoc/lib/transform-apijson-for-sdk.js index 58739116c..91a4cbcf9 100644 --- a/lib/processors/jsdoc/lib/transform-apijson-for-sdk.js +++ b/lib/processors/jsdoc/lib/transform-apijson-for-sdk.js @@ -28,13 +28,13 @@ const log = (function() { /* * Transforms the api.json as created by the JSDoc build into a pre-processed api.json file suitable for the SDK. - * - * The pre-processing includes formatting of type references, rewriting of links and other time consuming calculations. - * + * + * The pre-processing includes formatting of type references, rewriting of links and other time consuming calculations. + * * @param {string} sInputFile Path of the original api.json file that should be transformed * @param {string} sOutputFile Path that the transformed api.json file should should be written to * @param {string} sLibraryFile Path to the .library file of the library, used to extract further documentation information - * @param {string|string[]} vDependencyAPIFiles Path of folder that contains api.json files of predecessor libs or + * @param {string|string[]} vDependencyAPIFiles Path of folder that contains api.json files of predecessor libs or * an array of paths of those files * @returns {Promise} A Promise that resolves after the transformation has been completed */ @@ -310,7 +310,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles) if (oSymbol.kind === "enum" || oProperty.type === "undefined") { delete oProperty.type; } - + }); } @@ -680,7 +680,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles) if ( Array.isArray(vDependencyAPIFiles) ) { return oChainObject; } - + // otherwise, it names a directory that has to be scanned for the files return new Promise(oResolve => { fs.readdir(vDependencyAPIFiles, function (oError, aItems) { @@ -1244,7 +1244,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles) let bSAPHosted = /^https?:\/\/(?:www.)?[\w.]*(?:sap|hana\.ondemand|sapfioritrial)\.com/.test(sTarget); return `${sText} -`; }, @@ -1375,9 +1375,9 @@ title="Information published on ${bSAPHosted ? '' : 'non '}SAP site" class="sapU * @param {string} name * @param {string} type * @param {string} className - * @param {string=name} text by default if no text is provided the name will be used - * @param {boolean=false} local - * @param {string=""} hrefAppend + * @param {string} [text=name] by default if no text is provided the name will be used + * @param {boolean} [local=false] + * @param {string} [hrefAppend=""] * @returns {string} link */ createLink: function ({name, type, className, text=name, local=false, hrefAppend=""}) { @@ -1948,7 +1948,7 @@ title="Information published on ${bSAPHosted ? '' : 'non '}SAP site" class="sapU inputFile: sInputFile, outputFile: sOutputFile, libraryFile: sLibraryFile, - aDependentLibraryFiles: Array.isArray(vDependencyAPIFiles) ? vDependencyAPIFiles : null + aDependentLibraryFiles: Array.isArray(vDependencyAPIFiles) ? vDependencyAPIFiles : null }; // Start the work here diff --git a/package.json b/package.json index aabab40fd..41f935e6d 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,8 @@ "docs/**", "jsdocs/**", "coverage/**", - "test/**" + "test/**", + "lib/processors/jsdoc/lib/**" ], "check-coverage": true, "statements": 85, From 606504cbfebe1f0ba29d56a38eb6fd783ca6622b Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Mon, 11 Mar 2019 11:13:11 +0100 Subject: [PATCH 23/38] Fix tests on Windows --- test/lib/processors/jsdoc/jsdocGenerator.js | 5 +++-- test/lib/tasks/generateJsdoc.js | 10 +++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/test/lib/processors/jsdoc/jsdocGenerator.js b/test/lib/processors/jsdoc/jsdocGenerator.js index b4227a55b..5a344c47c 100644 --- a/test/lib/processors/jsdoc/jsdocGenerator.js +++ b/test/lib/processors/jsdoc/jsdocGenerator.js @@ -44,7 +44,8 @@ test("generateJsdocConfig", async (t) => { test.serial("writeJsdocConfig", async (t) => { mock("graceful-fs", { writeFile: (configPath, configContent, callback) => { - t.deepEqual(configPath, "/some/path/jsdoc-config.json", "Correct config path supplied"); + t.deepEqual(configPath, path.join("/", "some", "path", "jsdoc-config.json"), + "Correct config path supplied"); t.deepEqual(configContent, "some config", "Correct config content supplied"); callback(); } @@ -55,7 +56,7 @@ test.serial("writeJsdocConfig", async (t) => { const jsdocGenerator = mock.reRequire("../../../../lib/processors/jsdoc/jsdocGenerator"); const res = await jsdocGenerator._writeJsdocConfig("/some/path", "some config"); - t.deepEqual(res, "/some/path/jsdoc-config.json", "Correct config path returned"); + t.deepEqual(res, path.join("/", "some", "path", "jsdoc-config.json"), "Correct config path returned"); mock.stop("graceful-fs"); }); diff --git a/test/lib/tasks/generateJsdoc.js b/test/lib/tasks/generateJsdoc.js index 525a9ace3..5df0e0508 100644 --- a/test/lib/tasks/generateJsdoc.js +++ b/test/lib/tasks/generateJsdoc.js @@ -1,6 +1,7 @@ const {test} = require("ava"); const sinon = require("sinon"); const tmp = require("tmp"); +const path = require("path"); const mock = require("mock-require"); @@ -43,10 +44,13 @@ test.serial("createTmpDirs", async (t) => { const res = await generateJsdoc._createTmpDirs("some.namespace"); - t.deepEqual(res, {sourcePath: "/some/path/src", targetPath: "/some/path/target", tmpPath: "/some/path/tmp"}, - "Correct temporary directories returned"); + t.deepEqual(res, { + sourcePath: path.join("/", "some", "path", "src"), + targetPath: path.join("/", "some", "path", "target"), + tmpPath: path.join("/", "some", "path", "tmp") + }, "Correct temporary directories returned"); t.deepEqual(makeDirStub.callCount, 1, "One directory got created"); - t.deepEqual(makeDirStub.getCall(0).args[0], "/some/path/tmp", "Correct dir path got created"); + t.deepEqual(makeDirStub.getCall(0).args[0], path.join("/", "some", "path", "tmp"), "Correct dir path got created"); mock.stop("make-dir"); }); From 68df5e7a7d732cf589f0411954d246fb6d4362f7 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Mon, 11 Mar 2019 13:43:06 +0100 Subject: [PATCH 24/38] JSDoc config JSON: Fix paths on Windows --- lib/processors/jsdoc/jsdocGenerator.js | 25 +++++++++++++---- test/lib/processors/jsdoc/jsdocGenerator.js | 31 ++++++++++++++++----- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/lib/processors/jsdoc/jsdocGenerator.js b/lib/processors/jsdoc/jsdocGenerator.js index 6821421d8..10c98a434 100644 --- a/lib/processors/jsdoc/jsdocGenerator.js +++ b/lib/processors/jsdoc/jsdocGenerator.js @@ -77,27 +77,40 @@ const jsdocGenerator = async function({sourcePath, targetPath, tmpPath, options} * @returns {string} jsdoc-config.json content string */ async function generateJsdocConfig({targetPath, tmpPath, namespace, projectName, version, variants}) { + // Backlash needs to be escaped as double-backslash + // This is not only relevant for win32 paths but also for + // Unix directory names that contain a backslash in their name + const backslashRegex = /\\/g; + // Resolve path to this script to get the path to the JSDoc extensions folder const jsdocPath = path.normalize(__dirname); + const pluginPath = path.join(jsdocPath, "lib", "ui5", "plugin.js").replace(backslashRegex, "\\\\"); + const templatePath = path.join(jsdocPath, "lib", "ui5", "template").replace(backslashRegex, "\\\\"); + const destinationPath = path.normalize(tmpPath).replace(backslashRegex, "\\\\"); + const jsapiFilePath = path.join(targetPath, "libraries", projectName + ".js").replace(backslashRegex, "\\\\"); + const apiJsonFolderPath = path.join(tmpPath, "dependency-apis").replace(backslashRegex, "\\\\"); + const apiJsonFilePath = + path.join(targetPath, "test-resources", path.normalize(namespace), "designtime", "api.json") + .replace(backslashRegex, "\\\\"); const config = `{ - "plugins": ["${jsdocPath}/lib/ui5/plugin.js"], + "plugins": ["${pluginPath}"], "opts": { "recurse": true, "lenient": true, - "template": "${jsdocPath}/lib/ui5/template", + "template": "${templatePath}", "ui5": { "saveSymbols": true }, - "destination": "${tmpPath}" + "destination": "${destinationPath}" }, "templates": { "ui5": { "variants": ${JSON.stringify(variants)}, "version": "${version}", - "jsapiFile": "${targetPath}/libraries/${projectName}.js", - "apiJsonFolder": "${tmpPath}/dependency-apis", - "apiJsonFile": "${targetPath}/test-resources/${namespace}/designtime/api.json" + "jsapiFile": "${jsapiFilePath}", + "apiJsonFolder": "${apiJsonFolderPath}", + "apiJsonFile": "${apiJsonFilePath}" } } }`; diff --git a/test/lib/processors/jsdoc/jsdocGenerator.js b/test/lib/processors/jsdoc/jsdocGenerator.js index 5a344c47c..d0e531c2e 100644 --- a/test/lib/processors/jsdoc/jsdocGenerator.js +++ b/test/lib/processors/jsdoc/jsdocGenerator.js @@ -8,7 +8,7 @@ test("generateJsdocConfig", async (t) => { const res = await jsdocGenerator._generateJsdocConfig({ sourcePath: "/some/source/path", targetPath: "/some/target/path", - tmpPath: "/some/tmp/path", + tmpPath: "/some/tm\\p/path", namespace: "some/namespace", projectName: "some.namespace", version: "1.0.0", @@ -18,24 +18,41 @@ test("generateJsdocConfig", async (t) => { const jsdocGeneratorPath = path.resolve(__dirname, "..", "..", "..", "..", "lib", "processors", "jsdoc"); + const backslashRegex = /\\/g; + + const pluginPath = path.join(jsdocGeneratorPath, "lib", "ui5", "plugin.js") + .replace(backslashRegex, "\\\\"); + const templatePath = path.join(jsdocGeneratorPath, "lib", "ui5", "template") + .replace(backslashRegex, "\\\\"); + const destinationPath = path.join("/", "some", "tm\\p", "path") + .replace(backslashRegex, "\\\\"); + const jsapiFilePath = path.join("/", "some", "target", "path", "libraries", "some.namespace.js") + .replace(backslashRegex, "\\\\"); + const apiJsonFolderPath = path.join("/", "some", "tm\\p", "path", "dependency-apis") + .replace(backslashRegex, "\\\\"); + const apiJsonFilePath = + path.join("/", "some", "target", "path", "test-resources", "some", "namespace", "designtime", "api.json") + .replace(backslashRegex, "\\\\"); + + t.deepEqual(res, `{ - "plugins": ["${jsdocGeneratorPath}/lib/ui5/plugin.js"], + "plugins": ["${pluginPath}"], "opts": { "recurse": true, "lenient": true, - "template": "${jsdocGeneratorPath}/lib/ui5/template", + "template": "${templatePath}", "ui5": { "saveSymbols": true }, - "destination": "/some/tmp/path" + "destination": "${destinationPath}" }, "templates": { "ui5": { "variants": ["apijson"], "version": "1.0.0", - "jsapiFile": "/some/target/path/libraries/some.namespace.js", - "apiJsonFolder": "/some/tmp/path/dependency-apis", - "apiJsonFile": "/some/target/path/test-resources/some/namespace/designtime/api.json" + "jsapiFile": "${jsapiFilePath}", + "apiJsonFolder": "${apiJsonFolderPath}", + "apiJsonFile": "${apiJsonFilePath}" } } }`, "Correct config generated"); From 0b1722793453251994d525479390215e6e2256f9 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Mon, 11 Mar 2019 14:40:38 +0100 Subject: [PATCH 25/38] Builder: Add JSDoc build option --- lib/builder/builder.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/builder/builder.js b/lib/builder/builder.js index b801e8a24..ac9ce7875 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -37,17 +37,18 @@ function getElapsedTime(startTime) { * @param {Object} parameters * @param {boolean} parameters.dev Sets development mode, which only runs essential tasks * @param {boolean} parameters.selfContained True if a the build should be self-contained or false for prelead build bundles + * @param {boolean} parameters.jsdoc True if a JSDoc build should be executed * @param {Array} parameters.includedTasks Task list to be included from build * @param {Array} parameters.excludedTasks Task list to be excluded from build * @returns {Array} Return a task list for the builder */ -function composeTaskList({dev, selfContained, includedTasks, excludedTasks}) { +function composeTaskList({dev, selfContained, jsdoc, includedTasks, excludedTasks}) { let selectedTasks = Object.keys(definedTasks).reduce((list, key) => { list[key] = true; return list; }, {}); - // Exclude tasks: manifestBundler + // Exclude non default tasks selectedTasks.generateManifestBundle = false; selectedTasks.generateStandaloneAppBundle = false; selectedTasks.transformBootstrapHtml = false; @@ -61,6 +62,19 @@ function composeTaskList({dev, selfContained, includedTasks, excludedTasks}) { selectedTasks.generateLibraryPreload = false; } + if (jsdoc) { + // Include JSDoc tasks + selectedTasks.generateJsdoc = true; + + // Exclude all tasks not relevant to JSDoc generation + selectedTasks.generateComponentPreload = false; + selectedTasks.generateLibraryPreload = false; + selectedTasks.generateLibraryManifest = false; + selectedTasks.buildThemes = false; + selectedTasks.createDebugFiles = false; + selectedTasks.uglify = false; + } + // Only run essential tasks in development mode, it is not desired to run time consuming tasks during development. if (dev) { // Overwrite all other tasks with noop promise @@ -125,6 +139,7 @@ module.exports = { * @param {boolean} [parameters.buildDependencies=false] Decides whether project dependencies are built as well * @param {boolean} [parameters.dev=false] Decides whether a development build should be activated (skips non-essential and time-intensive tasks) * @param {boolean} [parameters.selfContained=false] Flag to activate self contained build + * @param {boolean} [parameters.jsdoc=false] Flag to activate JSDoc build * @param {Array} [parameters.includedTasks=[]] List of tasks to be included * @param {Array} [parameters.excludedTasks=[]] List of tasks to be excluded. If the wildcard '*' is provided, only the included tasks will be executed. * @param {Array} [parameters.devExcludeProject=[]] List of projects to be excluded from development build @@ -132,7 +147,7 @@ module.exports = { */ build({ tree, destPath, - buildDependencies = false, dev = false, selfContained = false, + buildDependencies = false, dev = false, selfContained = false, jsdoc = false, includedTasks = [], excludedTasks = [], devExcludeProject = [] }) { const startTime = process.hrtime(); @@ -140,7 +155,7 @@ module.exports = { " including dependencies..." + (dev ? " [dev mode]" : "")); log.verbose(`Building to ${destPath}...`); - const selectedTasks = composeTaskList({dev, selfContained, includedTasks, excludedTasks}); + const selectedTasks = composeTaskList({dev, selfContained, jsdoc, includedTasks, excludedTasks}); const fsTarget = resourceFactory.createAdapter({ fsBasePath: destPath, From c63372cc021ba41884819a2ae970bfed6f51dee7 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Tue, 12 Mar 2019 15:44:18 +0100 Subject: [PATCH 26/38] Add SDK Transformation task and processor --- index.js | 3 +- lib/builder/builder.js | 2 + lib/processors/jsdoc/jsdocGenerator.js | 13 +--- .../jsdoc/lib/transform-apijson-for-sdk.js | 9 ++- lib/processors/jsdoc/sdkTransformer.js | 39 ++++++++++ .../jsdoc/executeJsdocSdkTransformation.js | 74 +++++++++++++++++++ lib/tasks/{ => jsdoc}/generateJsdoc.js | 10 +-- lib/tasks/taskRepository.js | 3 +- lib/types/library/LibraryBuilder.js | 18 ++++- test/lib/processors/jsdoc/jsdocGenerator.js | 3 + test/lib/tasks/generateJsdoc.js | 8 +- 11 files changed, 160 insertions(+), 22 deletions(-) create mode 100644 lib/processors/jsdoc/sdkTransformer.js create mode 100644 lib/tasks/jsdoc/executeJsdocSdkTransformation.js rename lib/tasks/{ => jsdoc}/generateJsdoc.js (93%) diff --git a/index.js b/index.js index bb83bfcf8..ef078852f 100644 --- a/index.js +++ b/index.js @@ -36,7 +36,8 @@ module.exports = { generateBundle: require("./lib/tasks/bundlers/generateBundle"), buildThemes: require("./lib/tasks/buildThemes"), createDebugFiles: require("./lib/tasks/createDebugFiles"), - generateJsdoc: require("./lib/tasks/generateJsdoc"), + generateJsdoc: require("./lib/tasks/jsdoc/generateJsdoc"), + executeJsdocSdkTransformation: require("./lib/tasks/jsdoc/executeJsdocSdkTransformation"), generateVersionInfo: require("./lib/tasks/generateVersionInfo"), replaceCopyright: require("./lib/tasks/replaceCopyright"), replaceVersion: require("./lib/tasks/replaceVersion"), diff --git a/lib/builder/builder.js b/lib/builder/builder.js index ac9ce7875..16ca364f7 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -53,6 +53,7 @@ function composeTaskList({dev, selfContained, jsdoc, includedTasks, excludedTask selectedTasks.generateStandaloneAppBundle = false; selectedTasks.transformBootstrapHtml = false; selectedTasks.generateJsdoc = false; + selectedTasks.executeJsdocSdkTransformation = false; if (selfContained) { // No preloads, bundle only @@ -65,6 +66,7 @@ function composeTaskList({dev, selfContained, jsdoc, includedTasks, excludedTask if (jsdoc) { // Include JSDoc tasks selectedTasks.generateJsdoc = true; + selectedTasks.executeJsdocSdkTransformation = true; // Exclude all tasks not relevant to JSDoc generation selectedTasks.generateComponentPreload = false; diff --git a/lib/processors/jsdoc/jsdocGenerator.js b/lib/processors/jsdoc/jsdocGenerator.js index 10c98a434..4c1d766c5 100644 --- a/lib/processors/jsdoc/jsdocGenerator.js +++ b/lib/processors/jsdoc/jsdocGenerator.js @@ -16,29 +16,24 @@ const {resourceFactory} = require("@ui5/fs"); * @param {string} parameters.tmpPath Path to write temporary and debug files * @param {Object} parameters.options Options * @param {string} parameters.options.projectName Project name + * @param {string} parameters.options.namespace Namespace to build (e.g. some/project/name) * @param {string} parameters.options.version Project version * @param {Array} [parameters.options.variants=["apijson"]] JSDoc variants to be built - * @param {boolean} [parameters.options.sdkBuild=true] Whether additional SDK specific - * api.json resources shall be generated * @returns {Promise} Promise resolving with newly created resources */ const jsdocGenerator = async function({sourcePath, targetPath, tmpPath, options} = {}) { - if (!sourcePath || !targetPath || !tmpPath || !options.projectName || !options.version) { + if (!sourcePath || !targetPath || !tmpPath || !options.projectName || !options.namespace || !options.version) { throw new Error("[jsdocGenerator]: One or more mandatory parameters not provided"); } if (!options.variants || options.variants.length === 0) { options.variants = ["apijson"]; } - if (options.sdkBuild === undefined) { - options.sdkBuild = true; - } - const namespace = options.projectName.replace(/\./g, "/"); const config = await jsdocGenerator._generateJsdocConfig({ targetPath, tmpPath, - namespace, + namespace: options.namespace, projectName: options.projectName, version: options.version, variants: options.variants @@ -58,7 +53,7 @@ const jsdocGenerator = async function({sourcePath, targetPath, tmpPath, options} // create resources from the output files return Promise.all([ - fsTarget.byPath(`/test-resources/${namespace}/designtime/api.json`) + fsTarget.byPath(`/test-resources/${options.namespace}/designtime/api.json`) // fsTarget.byPath(`/libraries/${options.projectName}.js`) ]).then((res) => res.filter(($)=>$)); }; diff --git a/lib/processors/jsdoc/lib/transform-apijson-for-sdk.js b/lib/processors/jsdoc/lib/transform-apijson-for-sdk.js index 91a4cbcf9..36d9dacde 100644 --- a/lib/processors/jsdoc/lib/transform-apijson-for-sdk.js +++ b/lib/processors/jsdoc/lib/transform-apijson-for-sdk.js @@ -6,7 +6,6 @@ */ "use strict"; -const fs = require("fs"); const cheerio = require("cheerio"); const path = require('path'); const log = (function() { @@ -38,7 +37,9 @@ const log = (function() { * an array of paths of those files * @returns {Promise} A Promise that resolves after the transformation has been completed */ -function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles) { +function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles, options) { + const fs = options && options.customFs || require("fs"); + const returnOutputFile = options && !!options.returnOutputFile; log.info("Transform API index files for sap.ui.documentation"); log.info(" original file: " + sInputFile); @@ -738,6 +739,10 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles) * @param oChainObject chain object */ function createApiRefApiJson(oChainObject) { + if (returnOutputFile) { + // If requested, return data instead of writing to FS (required by UI5 Tooling/UI5 Builder) + return JSON.stringify(oChainObject.parsedData); + } let sOutputDir = path.dirname(oChainObject.outputFile); // Create dir if it does not exist diff --git a/lib/processors/jsdoc/sdkTransformer.js b/lib/processors/jsdoc/sdkTransformer.js new file mode 100644 index 000000000..c8defa19b --- /dev/null +++ b/lib/processors/jsdoc/sdkTransformer.js @@ -0,0 +1,39 @@ +const resourceFactory = require("@ui5/fs").resourceFactory; +const transformer = require("./lib/transform-apijson-for-sdk"); + +/** + * Transform api.json as created by [jsdocGenerator]{@link module:@ui5/builder.processors.jsdocGenerator} + * for usage in a UI5 SDK + * + * @public + * @alias module:@ui5/builder.processors.sdkTransformer + * @param {Object} parameters Parameters + * @param {string} parameters.apiJsonPath Path to the projects api.json file as created by + * [jsdocGenerator]{@link module:@ui5/builder.processors.jsdoc.jsdocGenerator} + * @param {string} parameters.dotLibraryPath Path to the projects .library file + * @param {string[]} parameters.dependencyApiJsonPaths List of paths to the api.json files of all dependencies of + * the project as created by [jsdocGenerator]{@link module:@ui5/builder.processors.jsdoc.jsdocGenerator} + * @param {string} parameters.targetApiJsonPath Path to create the new, transformed api.json resource for + * @param {fs|module:@ui5/fs.fsInterface} parameters.fs Node fs or + * custom [fs interface]{@link module:resources/module:@ui5/fs.fsInterface} to use + * + * @returns {Promise} Promise resolving with created resources + */ +const sdkTransformer = async function({ + apiJsonPath, dotLibraryPath, dependencyApiJsonPaths, targetApiJsonPath, fs} = {} +) { + if (!apiJsonPath || !dotLibraryPath || !targetApiJsonPath || !dependencyApiJsonPaths || !fs) { + throw new Error("[sdkTransformer]: One or more mandatory parameters not provided"); + } + const fakeTargetPath = "/ignore/this/path/resource/will/be/returned"; + const apiJsonContent = await transformer(apiJsonPath, fakeTargetPath, dotLibraryPath, dependencyApiJsonPaths, { + customFs: fs, + returnOutputFile: true + }); + return [resourceFactory.createResource({ + path: targetApiJsonPath, + string: apiJsonContent + })]; +}; + +module.exports = sdkTransformer; diff --git a/lib/tasks/jsdoc/executeJsdocSdkTransformation.js b/lib/tasks/jsdoc/executeJsdocSdkTransformation.js new file mode 100644 index 000000000..b76d69394 --- /dev/null +++ b/lib/tasks/jsdoc/executeJsdocSdkTransformation.js @@ -0,0 +1,74 @@ +const log = require("@ui5/logger").getLogger("builder:tasks:executeJsdocSdkTransformation"); +const ReaderCollectionPrioritized = require("@ui5/fs").ReaderCollectionPrioritized; +const fsInterface = require("@ui5/fs").fsInterface; +const sdkTransformer = require("../../processors/jsdoc/sdkTransformer"); + +/** + * Task to transform the api.json file as created by the + * [generateJsdoc]{@link module:@ui5/builder.tasks.generateJsdoc} task into a pre-processed api.json + * file suitable for the SDK. + * + * @public + * @alias module:@ui5/builder.tasks.executeJsdocSdkTransformation + * @param {Object} parameters Parameters + * @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {module:@ui5/fs.AbstractReader} parameters.dependencies Reader or Collection to read dependency files + * @param {Object} parameters.options Options + * @param {string|Array} parameters.options.pattern Pattern to locate the files to be processed + * @param {string} parameters.options.projectName Project name + * @param {string} parameters.options.version Project version + * @returns {Promise} Promise resolving with undefined once data has been written + */ +const executeJsdocSdkTransformation = async function({workspace, dependencies, options} = {}) { + if (!options || !options.projectName || !options.librariesPattern) { + throw new Error("[executeJsdocSdkTransformation]: One or more mandatory options not provided"); + } + + const [apiJsons, dotLibraries, depApiJsons] = await Promise.all([ + workspace.byGlob("/test-resources/**/designtime/api.json"), + workspace.byGlob(options.librariesPattern), + dependencies.byGlob("/test-resources/**/designtime/api.json") + ]); + if (!apiJsons.length) { + throw new Error(`[executeJsdocSdkTransformation]: Failed to locate api.json resource for project ` + + `${options.projectName}.`); + } else if (apiJsons.length > 1) { + log.warn(`Found more than one api.json resources for project ${options.projectName}. ` + + `Will use the first resource, found at ${apiJsons[0].getPath()}`); + } + if (!dotLibraries.length) { + throw new Error(`[executeJsdocSdkTransformation]: Failed to locate .library resource for project ` + + `${options.projectName}.`); + } else if (dotLibraries.length > 1) { + log.warn(`Found more than one .library resources for project ${options.projectName}. ` + + `Will use the first resource, found at ${dotLibraries[0].getPath()}`); + } + + const combo = new ReaderCollectionPrioritized({ + name: `executeJsdocSdkTransformation - custom workspace + dependencies FS: ${options.projectName}`, + readers: [workspace, dependencies] + }); + + const apiJsonPath = apiJsons[0].getPath(); + const dotLibraryPath = dotLibraries[0].getPath(); + const dependencyApiJsonPaths = depApiJsons.map((res) => { + return res.getPath(); + }); + + // Target path is typically "/test-resources/${options.namespace}/designtime/apiref/api.json" + const targetApiJsonPath = apiJsonPath.replace(/\/api\.json$/i, "/apiref/api.json"); + + const createdResources = await sdkTransformer({ + apiJsonPath, + dotLibraryPath, + dependencyApiJsonPaths, + targetApiJsonPath, + fs: fsInterface(combo) + }); + + await Promise.all(createdResources.map((resource) => { + return workspace.write(resource); + })); +}; + +module.exports = executeJsdocSdkTransformation; diff --git a/lib/tasks/generateJsdoc.js b/lib/tasks/jsdoc/generateJsdoc.js similarity index 93% rename from lib/tasks/generateJsdoc.js rename to lib/tasks/jsdoc/generateJsdoc.js index 693de9ed6..df053feec 100644 --- a/lib/tasks/generateJsdoc.js +++ b/lib/tasks/jsdoc/generateJsdoc.js @@ -3,11 +3,11 @@ const makeDir = require("make-dir"); const fs = require("graceful-fs"); const tmp = require("tmp"); tmp.setGracefulCleanup(); -const jsdocGenerator = require("../processors/jsdoc/jsdocGenerator"); +const jsdocGenerator = require("../../processors/jsdoc/jsdocGenerator"); const {resourceFactory} = require("@ui5/fs"); /** - * Task to create dbg files. + * Task to execute a JSDoc build for UI5 projects * * @public * @alias module:@ui5/builder.tasks.generateJsdoc @@ -17,13 +17,12 @@ const {resourceFactory} = require("@ui5/fs"); * @param {Object} parameters.options Options * @param {string|Array} parameters.options.pattern Pattern to locate the files to be processed * @param {string} parameters.options.projectName Project name + * @param {string} parameters.options.namespace Namespace to build (e.g. some/project/name) * @param {string} parameters.options.version Project version - * @param {boolean} [parameters.options.sdkBuild=true] Whether additional SDK specific api.json - * resources shall be generated * @returns {Promise} Promise resolving with undefined once data has been written */ const generateJsdoc = async function({workspace, dependencies, options} = {}) { - if (!options || !options.projectName || !options.version || !options.pattern) { + if (!options || !options.projectName || !options.namespace || !options.version || !options.pattern) { throw new Error("[generateJsdoc]: One or more mandatory options not provided"); } @@ -48,6 +47,7 @@ const generateJsdoc = async function({workspace, dependencies, options} = {}) { tmpPath, options: { projectName: options.projectName, + namespace: options.namespace, version: options.version, variants: ["apijson"] } diff --git a/lib/tasks/taskRepository.js b/lib/tasks/taskRepository.js index 1fd975b1a..570e80408 100644 --- a/lib/tasks/taskRepository.js +++ b/lib/tasks/taskRepository.js @@ -2,7 +2,8 @@ const tasks = { replaceCopyright: require("./replaceCopyright"), replaceVersion: require("./replaceVersion"), createDebugFiles: require("./createDebugFiles"), - generateJsdoc: require("./generateJsdoc"), + generateJsdoc: require("./jsdoc/generateJsdoc"), + executeJsdocSdkTransformation: require("./jsdoc/executeJsdocSdkTransformation"), uglify: require("./uglify"), buildThemes: require("./buildThemes"), transformBootstrapHtml: require("./transformBootstrapHtml"), diff --git a/lib/types/library/LibraryBuilder.js b/lib/types/library/LibraryBuilder.js index 396a60655..c8c9ada1d 100644 --- a/lib/types/library/LibraryBuilder.js +++ b/lib/types/library/LibraryBuilder.js @@ -8,7 +8,8 @@ const tasks = { // can't require index.js due to circular dependency generateStandaloneAppBundle: require("../../tasks/bundlers/generateStandaloneAppBundle"), buildThemes: require("../../tasks/buildThemes"), createDebugFiles: require("../../tasks/createDebugFiles"), - generateJsdoc: require("../../tasks/generateJsdoc"), + generateJsdoc: require("../../tasks/jsdoc/generateJsdoc"), + executeJsdocSdkTransformation: require("../../tasks/jsdoc/executeJsdocSdkTransformation"), generateLibraryManifest: require("../../tasks/generateLibraryManifest"), generateVersionInfo: require("../../tasks/generateVersionInfo"), replaceCopyright: require("../../tasks/replaceCopyright"), @@ -52,18 +53,33 @@ class LibraryBuilder extends AbstractBuilder { patterns.push(...excludes); } + const namespace = project.metadata.name.replace(/\./g, "/"); return generateJsdoc({ workspace: resourceCollections.workspace, dependencies: resourceCollections.dependencies, options: { projectName: project.metadata.name, + namespace, version: project.version, pattern: patterns } }); }); + this.addTask("executeJsdocSdkTransformation", () => { + const executeJsdocSdkTransformation = tasks.executeJsdocSdkTransformation; + + return executeJsdocSdkTransformation({ + workspace: resourceCollections.workspace, + dependencies: resourceCollections.dependencies, + options: { + projectName: project.metadata.name, + librariesPattern: "/resources/**/*.library", + } + }); + }); + const componentPreload = project.builder && project.builder.componentPreload; if (componentPreload) { const generateComponentPreload = tasks.generateComponentPreload; diff --git a/test/lib/processors/jsdoc/jsdocGenerator.js b/test/lib/processors/jsdoc/jsdocGenerator.js index d0e531c2e..f1ef55ebe 100644 --- a/test/lib/processors/jsdoc/jsdocGenerator.js +++ b/test/lib/processors/jsdoc/jsdocGenerator.js @@ -136,6 +136,7 @@ test.serial("jsdocGenerator", async (t) => { tmpPath: "/some/tmp/path", options: { projectName: "some.project.name", + namespace: "some/project/name", version: "1.0.0" } }); @@ -180,6 +181,7 @@ test.serial("jsdocGenerator", async (t) => { tmpPath: "/some/tmp/path", options: { projectName: "some.project.name", + namespace: "some/project/name", version: "1.0.0", variants: [] } @@ -196,6 +198,7 @@ test.serial("jsdocGenerator", async (t) => { tmpPath: "/some/tmp/path", options: { projectName: "some.project.name", + namespace: "some/project/name", version: "1.0.0", variants: ["pony"], sdkBuild: true diff --git a/test/lib/tasks/generateJsdoc.js b/test/lib/tasks/generateJsdoc.js index 5df0e0508..60c4326d1 100644 --- a/test/lib/tasks/generateJsdoc.js +++ b/test/lib/tasks/generateJsdoc.js @@ -5,7 +5,7 @@ const path = require("path"); const mock = require("mock-require"); -const generateJsdoc = require("../../../lib/tasks/generateJsdoc"); +const generateJsdoc = require("../../../lib/tasks/jsdoc/generateJsdoc"); test.beforeEach((t) => { t.context.tmpStub = sinon.stub(tmp, "dir"); @@ -38,7 +38,7 @@ test.serial("createTmpDir error", async (t) => { test.serial("createTmpDirs", async (t) => { const makeDirStub = sinon.stub().resolves(); mock("make-dir", makeDirStub); - const generateJsdoc = mock.reRequire("../../../lib/tasks/generateJsdoc"); + const generateJsdoc = mock.reRequire("../../../lib/tasks/jsdoc/generateJsdoc"); t.context.tmpStub.callsArgWithAsync(1, undefined, "/some/path"); @@ -171,7 +171,7 @@ test.serial("writeDependencyApisToDir with byGlob", async (t) => { test.serial("generateJsdoc", async (t) => { const jsdocGeneratorStub = sinon.stub().resolves(["resource A", "resource B"]); mock("../../../lib/processors/jsdoc/jsdocGenerator", jsdocGeneratorStub); - const generateJsdoc = mock.reRequire("../../../lib/tasks/generateJsdoc"); + const generateJsdoc = mock.reRequire("../../../lib/tasks/jsdoc/generateJsdoc"); const createTmpDirsStub = sinon.stub(generateJsdoc, "_createTmpDirs").resolves({ sourcePath: "/some/source/path", @@ -191,6 +191,7 @@ test.serial("generateJsdoc", async (t) => { options: { pattern: "some pattern", projectName: "some.project", + namespace: "some/project", version: "some version" } }); @@ -220,6 +221,7 @@ test.serial("generateJsdoc", async (t) => { tmpPath: "/some/tmp/path", options: { projectName: "some.project", + namespace: "some/project", version: "some version", variants: ["apijson"] } From f085fad70ca8f9e8fcb5af452ee97616ac1a0dac Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Thu, 14 Mar 2019 10:30:27 +0100 Subject: [PATCH 27/38] executeJsdocSdkTransformation: Throw error if too many project api.json or .library resources are found Globs may return resources in random order, therefore choosing the first of many matches can lead to erratic results. --- lib/tasks/jsdoc/executeJsdocSdkTransformation.js | 16 +++++++--------- lib/types/library/LibraryBuilder.js | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/tasks/jsdoc/executeJsdocSdkTransformation.js b/lib/tasks/jsdoc/executeJsdocSdkTransformation.js index b76d69394..f88208355 100644 --- a/lib/tasks/jsdoc/executeJsdocSdkTransformation.js +++ b/lib/tasks/jsdoc/executeJsdocSdkTransformation.js @@ -1,4 +1,3 @@ -const log = require("@ui5/logger").getLogger("builder:tasks:executeJsdocSdkTransformation"); const ReaderCollectionPrioritized = require("@ui5/fs").ReaderCollectionPrioritized; const fsInterface = require("@ui5/fs").fsInterface; const sdkTransformer = require("../../processors/jsdoc/sdkTransformer"); @@ -14,34 +13,33 @@ const sdkTransformer = require("../../processors/jsdoc/sdkTransformer"); * @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files * @param {module:@ui5/fs.AbstractReader} parameters.dependencies Reader or Collection to read dependency files * @param {Object} parameters.options Options - * @param {string|Array} parameters.options.pattern Pattern to locate the files to be processed + * @param {string|Array} parameters.options.dotLibraryPattern Pattern to locate the .library resource to be processed * @param {string} parameters.options.projectName Project name - * @param {string} parameters.options.version Project version * @returns {Promise} Promise resolving with undefined once data has been written */ const executeJsdocSdkTransformation = async function({workspace, dependencies, options} = {}) { - if (!options || !options.projectName || !options.librariesPattern) { + if (!options || !options.projectName || !options.dotLibraryPattern) { throw new Error("[executeJsdocSdkTransformation]: One or more mandatory options not provided"); } const [apiJsons, dotLibraries, depApiJsons] = await Promise.all([ workspace.byGlob("/test-resources/**/designtime/api.json"), - workspace.byGlob(options.librariesPattern), + workspace.byGlob(options.dotLibraryPattern), dependencies.byGlob("/test-resources/**/designtime/api.json") ]); if (!apiJsons.length) { throw new Error(`[executeJsdocSdkTransformation]: Failed to locate api.json resource for project ` + `${options.projectName}.`); } else if (apiJsons.length > 1) { - log.warn(`Found more than one api.json resources for project ${options.projectName}. ` + - `Will use the first resource, found at ${apiJsons[0].getPath()}`); + throw new Error(`[executeJsdocSdkTransformation]: Found more than one api.json resources for project ` + + `${options.projectName}.`); } if (!dotLibraries.length) { throw new Error(`[executeJsdocSdkTransformation]: Failed to locate .library resource for project ` + `${options.projectName}.`); } else if (dotLibraries.length > 1) { - log.warn(`Found more than one .library resources for project ${options.projectName}. ` + - `Will use the first resource, found at ${dotLibraries[0].getPath()}`); + throw new Error(`[executeJsdocSdkTransformation]: Found more than one .library resources for project ` + + `${options.projectName}.`); } const combo = new ReaderCollectionPrioritized({ diff --git a/lib/types/library/LibraryBuilder.js b/lib/types/library/LibraryBuilder.js index c8c9ada1d..94c253c30 100644 --- a/lib/types/library/LibraryBuilder.js +++ b/lib/types/library/LibraryBuilder.js @@ -75,7 +75,7 @@ class LibraryBuilder extends AbstractBuilder { dependencies: resourceCollections.dependencies, options: { projectName: project.metadata.name, - librariesPattern: "/resources/**/*.library", + dotLibraryPattern: "/resources/**/*.library", } }); }); From 89f7792098b3e5071a145abe989874aadd714dbd Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Thu, 14 Mar 2019 10:33:27 +0100 Subject: [PATCH 28/38] executeJsdocSdkTransformation: Add tests --- .../jsdoc/executeJsdocSdkTransformation.js | 234 ++++++++++++++++++ test/lib/tasks/{ => jsdoc}/generateJsdoc.js | 10 +- 2 files changed, 239 insertions(+), 5 deletions(-) create mode 100644 test/lib/tasks/jsdoc/executeJsdocSdkTransformation.js rename test/lib/tasks/{ => jsdoc}/generateJsdoc.js (95%) diff --git a/test/lib/tasks/jsdoc/executeJsdocSdkTransformation.js b/test/lib/tasks/jsdoc/executeJsdocSdkTransformation.js new file mode 100644 index 000000000..4ca67c722 --- /dev/null +++ b/test/lib/tasks/jsdoc/executeJsdocSdkTransformation.js @@ -0,0 +1,234 @@ +const {test} = require("ava"); +const sinon = require("sinon"); +const ui5Fs = require("@ui5/fs"); + +const mock = require("mock-require"); + +const executeJsdocSdkTransformation = require("../../../../lib/tasks/jsdoc/executeJsdocSdkTransformation"); + +test.afterEach.always((t) => { + sinon.restore(); +}); + +test.serial("executeJsdocSdkTransformation", async (t) => { + const sdkTransformerStub = sinon.stub().resolves(["resource A", "resource B"]); + const fsInterfaceStub = sinon.stub(ui5Fs, "fsInterface").returns("custom fs"); + mock("../../../../lib/processors/jsdoc/sdkTransformer", sdkTransformerStub); + + class ReaderCollectionPrioritizedStubClass { + constructor(parameters) { + t.deepEqual(parameters, { + name: "executeJsdocSdkTransformation - custom workspace + dependencies FS: some.project", + readers: [workspace, dependencies] + }, "ReaderCollectionPrioritized got called with the correct arguments"); + } + } + const readerCollectionPrioritizedStub = ReaderCollectionPrioritizedStubClass; + const readerCollectionPrioritizedOrig = ui5Fs.ReaderCollectionPrioritized; + ui5Fs.ReaderCollectionPrioritized = readerCollectionPrioritizedStub; + const executeJsdocSdkTransformation = mock.reRequire("../../../../lib/tasks/jsdoc/executeJsdocSdkTransformation"); + ui5Fs.ReaderCollectionPrioritized = readerCollectionPrioritizedOrig; + + const writeStub = sinon.stub().resolves(); + const byGlobWorkspaceStub = sinon.stub() + .onFirstCall().resolves([{ + getPath: () => "workspace/api.json" + }]) + .onSecondCall().resolves([{ + getPath: () => "workspace/.library" + }]); + const workspace = { + write: writeStub, + byGlob: byGlobWorkspaceStub + }; + const byGlobDependenciesStub = sinon.stub().resolves() + .resolves([{ + getPath: () => "depA/api.json" + }, { + getPath: () => "depB/api.json" + }]); + const dependencies = { + byGlob: byGlobDependenciesStub + }; + await executeJsdocSdkTransformation({ + workspace, + dependencies, + options: { + projectName: "some.project", + dotLibraryPattern: "some .library pattern" + } + }); + t.deepEqual(byGlobWorkspaceStub.callCount, 2, "workspace.byGlob got called twice"); + t.deepEqual(byGlobWorkspaceStub.getCall(0).args[0], "/test-resources/**/designtime/api.json", + "first workspace.byGlob call with correct arguments"); + t.deepEqual(byGlobWorkspaceStub.getCall(1).args[0], "some .library pattern", + "second workspace.byGlob call with correct arguments"); + + t.deepEqual(byGlobDependenciesStub.callCount, 1, "dependencies.byGlob got called once"); + t.deepEqual(byGlobDependenciesStub.getCall(0).args[0], "/test-resources/**/designtime/api.json", + "dependencies.byGlob got called with correct arguments"); + + t.deepEqual(fsInterfaceStub.callCount, 1, "fsInterface got called once"); + t.true(fsInterfaceStub.getCall(0).args[0] instanceof ReaderCollectionPrioritizedStubClass, + "fsInterface got called with an instance of ReaderCollectionPrioritizedStubClass"); + + t.deepEqual(sdkTransformerStub.callCount, 1, "sdkTransformer processor got called once"); + t.deepEqual(sdkTransformerStub.getCall(0).args[0], { + apiJsonPath: "workspace/api.json", + dotLibraryPath: "workspace/.library", + dependencyApiJsonPaths: [ + "depA/api.json", + "depB/api.json" + ], + targetApiJsonPath: "workspace/apiref/api.json", + fs: "custom fs" + }, "sdkTransformer got called with correct arguments"); + + t.deepEqual(writeStub.callCount, 2, "Write got called twice"); + t.deepEqual(writeStub.getCall(0).args[0], "resource A", "Write got called with correct arguments"); + t.deepEqual(writeStub.getCall(1).args[0], "resource B", "Write got called with correct arguments"); + + mock.stop("../../../../lib/processors/jsdoc/sdkTransformer"); +}); + +test("executeJsdocSdkTransformation with missing parameters", async (t) => { + const error = await t.throws(executeJsdocSdkTransformation()); + t.deepEqual(error.message, "[executeJsdocSdkTransformation]: One or more mandatory options not provided", + "Correct error message thrown"); +}); + +test("executeJsdocSdkTransformation with missing project api.json", async (t) => { + const byGlobWorkspaceStub = sinon.stub() + .onFirstCall().resolves([]) + .onSecondCall().resolves([{ + getPath: () => "workspace/.library" + }]); + const workspace = { + byGlob: byGlobWorkspaceStub + }; + const byGlobDependenciesStub = sinon.stub().resolves() + .resolves([{ + getPath: () => "depA/api.json" + }, { + getPath: () => "depB/api.json" + }]); + const dependencies = { + byGlob: byGlobDependenciesStub + }; + + const error = await t.throws(executeJsdocSdkTransformation({ + workspace, + dependencies, + options: { + projectName: "some.project", + dotLibraryPattern: "some .library pattern" + } + })); + t.deepEqual(error.message, + "[executeJsdocSdkTransformation]: Failed to locate api.json resource for project some.project.", + "Correct error message thrown"); +}); + +test("executeJsdocSdkTransformation too many project api.json resources", async (t) => { + const byGlobWorkspaceStub = sinon.stub() + .onFirstCall().resolves([{ + getPath: () => "workspace/a/api.json" + }, { + getPath: () => "workspace/b/api.json" + }]) + .onSecondCall().resolves([{ + getPath: () => "workspace/a/.library" + }]); + const workspace = { + byGlob: byGlobWorkspaceStub + }; + const byGlobDependenciesStub = sinon.stub().resolves() + .resolves([{ + getPath: () => "depA/api.json" + }, { + getPath: () => "depB/api.json" + }]); + const dependencies = { + byGlob: byGlobDependenciesStub + }; + + const error = await t.throws(executeJsdocSdkTransformation({ + workspace, + dependencies, + options: { + projectName: "some.project", + dotLibraryPattern: "some .library pattern" + } + })); + t.deepEqual(error.message, + "[executeJsdocSdkTransformation]: Found more than one api.json resources for project some.project.", + "Correct error message thrown"); +}); + +test("executeJsdocSdkTransformation missing project .library", async (t) => { + const byGlobWorkspaceStub = sinon.stub() + .onFirstCall().resolves([{ + getPath: () => "workspace/api.json" + }]) + .onSecondCall().resolves([]); + const workspace = { + byGlob: byGlobWorkspaceStub + }; + const byGlobDependenciesStub = sinon.stub().resolves() + .resolves([{ + getPath: () => "depA/api.json" + }, { + getPath: () => "depB/api.json" + }]); + const dependencies = { + byGlob: byGlobDependenciesStub + }; + + const error = await t.throws(executeJsdocSdkTransformation({ + workspace, + dependencies, + options: { + projectName: "some.project", + dotLibraryPattern: "some .library pattern" + } + })); + t.deepEqual(error.message, + "[executeJsdocSdkTransformation]: Failed to locate .library resource for project some.project.", + "Correct error message thrown"); +}); + +test("executeJsdocSdkTransformation too many project .library resources", async (t) => { + const byGlobWorkspaceStub = sinon.stub() + .onFirstCall().resolves([{ + getPath: () => "workspace/a/api.json" + }]) + .onSecondCall().resolves([{ + getPath: () => "workspace/a/.library" + }, { + getPath: () => "workspace/b/.library" + }]); + const workspace = { + byGlob: byGlobWorkspaceStub + }; + const byGlobDependenciesStub = sinon.stub().resolves() + .resolves([{ + getPath: () => "depA/api.json" + }, { + getPath: () => "depB/api.json" + }]); + const dependencies = { + byGlob: byGlobDependenciesStub + }; + + const error = await t.throws(executeJsdocSdkTransformation({ + workspace, + dependencies, + options: { + projectName: "some.project", + dotLibraryPattern: "some .library pattern" + } + })); + t.deepEqual(error.message, + "[executeJsdocSdkTransformation]: Found more than one .library resources for project some.project.", + "Correct error message thrown"); +}); diff --git a/test/lib/tasks/generateJsdoc.js b/test/lib/tasks/jsdoc/generateJsdoc.js similarity index 95% rename from test/lib/tasks/generateJsdoc.js rename to test/lib/tasks/jsdoc/generateJsdoc.js index 60c4326d1..816bc07cf 100644 --- a/test/lib/tasks/generateJsdoc.js +++ b/test/lib/tasks/jsdoc/generateJsdoc.js @@ -5,7 +5,7 @@ const path = require("path"); const mock = require("mock-require"); -const generateJsdoc = require("../../../lib/tasks/jsdoc/generateJsdoc"); +const generateJsdoc = require("../../../../lib/tasks/jsdoc/generateJsdoc"); test.beforeEach((t) => { t.context.tmpStub = sinon.stub(tmp, "dir"); @@ -38,7 +38,7 @@ test.serial("createTmpDir error", async (t) => { test.serial("createTmpDirs", async (t) => { const makeDirStub = sinon.stub().resolves(); mock("make-dir", makeDirStub); - const generateJsdoc = mock.reRequire("../../../lib/tasks/jsdoc/generateJsdoc"); + const generateJsdoc = mock.reRequire("../../../../lib/tasks/jsdoc/generateJsdoc"); t.context.tmpStub.callsArgWithAsync(1, undefined, "/some/path"); @@ -170,8 +170,8 @@ test.serial("writeDependencyApisToDir with byGlob", async (t) => { test.serial("generateJsdoc", async (t) => { const jsdocGeneratorStub = sinon.stub().resolves(["resource A", "resource B"]); - mock("../../../lib/processors/jsdoc/jsdocGenerator", jsdocGeneratorStub); - const generateJsdoc = mock.reRequire("../../../lib/tasks/jsdoc/generateJsdoc"); + mock("../../../../lib/processors/jsdoc/jsdocGenerator", jsdocGeneratorStub); + const generateJsdoc = mock.reRequire("../../../../lib/tasks/jsdoc/generateJsdoc"); const createTmpDirsStub = sinon.stub(generateJsdoc, "_createTmpDirs").resolves({ sourcePath: "/some/source/path", @@ -231,7 +231,7 @@ test.serial("generateJsdoc", async (t) => { t.deepEqual(writeStub.getCall(0).args[0], "resource A", "Write got called with correct arguments"); t.deepEqual(writeStub.getCall(1).args[0], "resource B", "Write got called with correct arguments"); - mock.stop("../../../lib/processors/jsdoc/jsdocGenerator"); + mock.stop("../../../../lib/processors/jsdoc/jsdocGenerator"); }); test.serial("generateJsdoc missing parameters", async (t) => { From b8f9768c906a6d253cfdc45933947a08c1683583 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Thu, 14 Mar 2019 10:54:49 +0100 Subject: [PATCH 29/38] sdkTransformer: Add tests --- test/lib/processors/jsdoc/sdkTransformer.js | 61 +++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 test/lib/processors/jsdoc/sdkTransformer.js diff --git a/test/lib/processors/jsdoc/sdkTransformer.js b/test/lib/processors/jsdoc/sdkTransformer.js new file mode 100644 index 000000000..09bfd94dd --- /dev/null +++ b/test/lib/processors/jsdoc/sdkTransformer.js @@ -0,0 +1,61 @@ +const {test} = require("ava"); +const sinon = require("sinon"); +const mock = require("mock-require"); +const sdkTransformer = require("../../../../lib/processors/jsdoc/sdkTransformer"); + +test.afterEach.always((t) => { + sinon.restore(); +}); + +test.serial("sdkTransformer", async (t) => { + const transformerStub = sinon.stub().resolves("api.json content"); + mock("../../../../lib/processors/jsdoc/lib/transform-apijson-for-sdk", transformerStub); + const createResourceStub = sinon.stub(require("@ui5/fs").resourceFactory, "createResource") + .returns("result resource"); + + const sdkTransformer = mock.reRequire("../../../../lib/processors/jsdoc/sdkTransformer"); + + const res = await sdkTransformer({ + apiJsonPath: "/some/path/api.json", + dotLibraryPath: "/some/path/.library", + targetApiJsonPath: "/some/other/path/api.json", + dependencyApiJsonPaths: [ + "/some/path/x/api.json", + "/some/path/y/api.json" + ], + fs: "custom fs" + }); + + t.deepEqual(res.length, 1, "Returned one resource"); + t.deepEqual(res[0], "result resource", "Returned one resource"); + + t.deepEqual(transformerStub.callCount, 1, "generateJsdocConfig called once"); + t.deepEqual(transformerStub.getCall(0).args[0], "/some/path/api.json", + "transform-apijson-for-sdk called with correct argument #1"); + t.deepEqual(transformerStub.getCall(0).args[1], "/ignore/this/path/resource/will/be/returned", + "transform-apijson-for-sdk called with correct argument #2"); + t.deepEqual(transformerStub.getCall(0).args[2], "/some/path/.library", + "transform-apijson-for-sdk called with correct argument #3"); + t.deepEqual(transformerStub.getCall(0).args[3], [ + "/some/path/x/api.json", + "/some/path/y/api.json" + ], "transform-apijson-for-sdk called with correct argument #4"); + t.deepEqual(transformerStub.getCall(0).args[4], { + customFs: "custom fs", + returnOutputFile: true + }, "transform-apijson-for-sdk called with correct argument #5"); + + t.deepEqual(createResourceStub.callCount, 1, "createResource called once"); + t.deepEqual(createResourceStub.getCall(0).args[0], { + path: "/some/other/path/api.json", + string: "api.json content" + }, "createResource called with correct arguments"); + + mock.stop("../../../../lib/processors/jsdoc/lib/transform-apijson-for-sdk"); +}); + +test("sdkTransformer missing parameters", async (t) => { + const error = await t.throws(sdkTransformer()); + t.deepEqual(error.message, "[sdkTransformer]: One or more mandatory parameters not provided", + "Correct error message thrown"); +}); From b3bdf809c1a6051884a964632e769e5c5f87c308 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Thu, 14 Mar 2019 14:33:20 +0100 Subject: [PATCH 30/38] executeJsdocSdkTransformation: Skip projects with missing api.json Prominent example: theme libraries --- .../jsdoc/executeJsdocSdkTransformation.js | 6 ++-- .../jsdoc/executeJsdocSdkTransformation.js | 28 +++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/lib/tasks/jsdoc/executeJsdocSdkTransformation.js b/lib/tasks/jsdoc/executeJsdocSdkTransformation.js index f88208355..ba268a11c 100644 --- a/lib/tasks/jsdoc/executeJsdocSdkTransformation.js +++ b/lib/tasks/jsdoc/executeJsdocSdkTransformation.js @@ -1,3 +1,4 @@ +const log = require("@ui5/logger").getLogger("builder:tasks:executeJsdocSdkTransformation"); const ReaderCollectionPrioritized = require("@ui5/fs").ReaderCollectionPrioritized; const fsInterface = require("@ui5/fs").fsInterface; const sdkTransformer = require("../../processors/jsdoc/sdkTransformer"); @@ -28,8 +29,9 @@ const executeJsdocSdkTransformation = async function({workspace, dependencies, o dependencies.byGlob("/test-resources/**/designtime/api.json") ]); if (!apiJsons.length) { - throw new Error(`[executeJsdocSdkTransformation]: Failed to locate api.json resource for project ` + - `${options.projectName}.`); + log.info(`Failed to locate api.json resource for project ${options.projectName}. ` + + `Skipping SDK Transformation...`); + return; } else if (apiJsons.length > 1) { throw new Error(`[executeJsdocSdkTransformation]: Found more than one api.json resources for project ` + `${options.projectName}.`); diff --git a/test/lib/tasks/jsdoc/executeJsdocSdkTransformation.js b/test/lib/tasks/jsdoc/executeJsdocSdkTransformation.js index 4ca67c722..410487c8d 100644 --- a/test/lib/tasks/jsdoc/executeJsdocSdkTransformation.js +++ b/test/lib/tasks/jsdoc/executeJsdocSdkTransformation.js @@ -89,6 +89,9 @@ test.serial("executeJsdocSdkTransformation", async (t) => { t.deepEqual(writeStub.getCall(1).args[0], "resource B", "Write got called with correct arguments"); mock.stop("../../../../lib/processors/jsdoc/sdkTransformer"); + + sinon.restore(); + mock.reRequire("../../../../lib/tasks/jsdoc/executeJsdocSdkTransformation"); }); test("executeJsdocSdkTransformation with missing parameters", async (t) => { @@ -97,7 +100,15 @@ test("executeJsdocSdkTransformation with missing parameters", async (t) => { "Correct error message thrown"); }); -test("executeJsdocSdkTransformation with missing project api.json", async (t) => { +test.serial("executeJsdocSdkTransformation with missing project api.json (skips processing)", async (t) => { + const logger = require("@ui5/logger"); + const infoLogStub = sinon.stub(); + const myLoggerInstance = { + info: infoLogStub + }; + sinon.stub(logger, "getLogger").returns(myLoggerInstance); + const executeJsdocSdkTransformation = mock.reRequire("../../../../lib/tasks/jsdoc/executeJsdocSdkTransformation"); + const byGlobWorkspaceStub = sinon.stub() .onFirstCall().resolves([]) .onSecondCall().resolves([{ @@ -116,17 +127,22 @@ test("executeJsdocSdkTransformation with missing project api.json", async (t) => byGlob: byGlobDependenciesStub }; - const error = await t.throws(executeJsdocSdkTransformation({ + await executeJsdocSdkTransformation({ workspace, dependencies, options: { projectName: "some.project", dotLibraryPattern: "some .library pattern" } - })); - t.deepEqual(error.message, - "[executeJsdocSdkTransformation]: Failed to locate api.json resource for project some.project.", - "Correct error message thrown"); + }); + + t.deepEqual(infoLogStub.callCount, 1, "One message has been logged"); + t.deepEqual(infoLogStub.getCall(0).args[0], + "Failed to locate api.json resource for project some.project. Skipping SDK Transformation...", + "Correct message has been logged"); + + sinon.restore(); + mock.reRequire("../../../../lib/tasks/jsdoc/executeJsdocSdkTransformation"); }); test("executeJsdocSdkTransformation too many project api.json resources", async (t) => { From 88eda891de8ff097e8ade6be1c891333468fba21 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Thu, 14 Mar 2019 15:15:49 +0100 Subject: [PATCH 31/38] generateJsdoc: Always create tmp dirs This prevents JSDoc errors in case no resources have written to the source path. --- lib/tasks/jsdoc/generateJsdoc.js | 2 ++ test/lib/tasks/jsdoc/generateJsdoc.js | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/tasks/jsdoc/generateJsdoc.js b/lib/tasks/jsdoc/generateJsdoc.js index df053feec..644431196 100644 --- a/lib/tasks/jsdoc/generateJsdoc.js +++ b/lib/tasks/jsdoc/generateJsdoc.js @@ -70,7 +70,9 @@ async function createTmpDirs(projectName) { const {path: tmpDirPath} = await createTmpDir(projectName); const sourcePath = path.join(tmpDirPath, "src"); // dir will be created by writing project resources below + await makeDir(sourcePath, {fs}); const targetPath = path.join(tmpDirPath, "target"); // dir will be created by jsdoc itself + await makeDir(targetPath, {fs}); const tmpPath = path.join(tmpDirPath, "tmp"); // dir needs to be created by us await makeDir(tmpPath, {fs}); diff --git a/test/lib/tasks/jsdoc/generateJsdoc.js b/test/lib/tasks/jsdoc/generateJsdoc.js index 816bc07cf..03f96486d 100644 --- a/test/lib/tasks/jsdoc/generateJsdoc.js +++ b/test/lib/tasks/jsdoc/generateJsdoc.js @@ -49,8 +49,13 @@ test.serial("createTmpDirs", async (t) => { targetPath: path.join("/", "some", "path", "target"), tmpPath: path.join("/", "some", "path", "tmp") }, "Correct temporary directories returned"); - t.deepEqual(makeDirStub.callCount, 1, "One directory got created"); - t.deepEqual(makeDirStub.getCall(0).args[0], path.join("/", "some", "path", "tmp"), "Correct dir path got created"); + t.deepEqual(makeDirStub.callCount, 3, "One directory got created"); + t.deepEqual(makeDirStub.getCall(0).args[0], path.join("/", "some", "path", "src"), + "Correct srcdir path got created"); + t.deepEqual(makeDirStub.getCall(1).args[0], path.join("/", "some", "path", "target"), + "Correct target dir path got created"); + t.deepEqual(makeDirStub.getCall(2).args[0], path.join("/", "some", "path", "tmp"), + "Correct tmp dir path got created"); mock.stop("make-dir"); }); From 8272acbb38245f55b68739acb5b0388aa133d515 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Thu, 14 Mar 2019 15:31:58 +0100 Subject: [PATCH 32/38] generateJsdoc: Skip JSDoc processing if no input resources have been located Prominent example: theme libraries --- .../jsdoc/executeJsdocSdkTransformation.js | 2 +- lib/tasks/jsdoc/generateJsdoc.js | 16 ++++-- test/lib/tasks/jsdoc/generateJsdoc.js | 49 +++++++++++++++++-- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/lib/tasks/jsdoc/executeJsdocSdkTransformation.js b/lib/tasks/jsdoc/executeJsdocSdkTransformation.js index ba268a11c..55226db7d 100644 --- a/lib/tasks/jsdoc/executeJsdocSdkTransformation.js +++ b/lib/tasks/jsdoc/executeJsdocSdkTransformation.js @@ -1,4 +1,4 @@ -const log = require("@ui5/logger").getLogger("builder:tasks:executeJsdocSdkTransformation"); +const log = require("@ui5/logger").getLogger("builder:tasks:jsdoc:executeJsdocSdkTransformation"); const ReaderCollectionPrioritized = require("@ui5/fs").ReaderCollectionPrioritized; const fsInterface = require("@ui5/fs").fsInterface; const sdkTransformer = require("../../processors/jsdoc/sdkTransformer"); diff --git a/lib/tasks/jsdoc/generateJsdoc.js b/lib/tasks/jsdoc/generateJsdoc.js index 644431196..0c807fdee 100644 --- a/lib/tasks/jsdoc/generateJsdoc.js +++ b/lib/tasks/jsdoc/generateJsdoc.js @@ -1,3 +1,4 @@ +const log = require("@ui5/logger").getLogger("builder:tasks:jsdoc:generateJsdoc"); const path = require("path"); const makeDir = require("make-dir"); const fs = require("graceful-fs"); @@ -28,10 +29,9 @@ const generateJsdoc = async function({workspace, dependencies, options} = {}) { const {sourcePath: resourcePath, targetPath, tmpPath} = await generateJsdoc._createTmpDirs(options.projectName); - await Promise.all([ + const [writtenResourcesCount] = await Promise.all([ generateJsdoc._writeResourcesToDir({ workspace, - dependencies, pattern: options.pattern, targetPath: resourcePath }), @@ -41,6 +41,12 @@ const generateJsdoc = async function({workspace, dependencies, options} = {}) { }) ]); + if (writtenResourcesCount === 0) { + log.info(`Failed to find any input resources for project ${options.projectName} using pattern ` + + `${options.pattern}. Skipping JSDoc generation...`); + return; + } + const createdResources = await jsdocGenerator({ sourcePath: resourcePath, targetPath, @@ -121,7 +127,7 @@ function createTmpDir(projectName, keep = false) { * @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files * @param {string} parameters.pattern Pattern to match resources in workspace against * @param {string} parameters.targetPath Path to write the resources to - * @returns {Promise} Promise resolving with undefined once data has been written + * @returns {Promise} Promise resolving with number of resources written to given directory */ async function writeResourcesToDir({workspace, pattern, targetPath}) { const fsTarget = resourceFactory.createAdapter({ @@ -138,6 +144,7 @@ async function writeResourcesToDir({workspace, pattern, targetPath}) { // write all resources to the tmp folder await Promise.all(allResources.map((resource) => fsTarget.write(resource))); + return allResources.length; } /** @@ -147,7 +154,7 @@ async function writeResourcesToDir({workspace, pattern, targetPath}) { * @param {Object} parameters Parameters * @param {module:@ui5/fs.AbstractReader} parameters.dependencies Reader or Collection to read dependency files * @param {string} parameters.targetPath Path to write the resources to - * @returns {Promise} Promise resolving with undefined once data has been written + * @returns {Promise} Promise resolving with number of resources written to given directory */ async function writeDependencyApisToDir({dependencies, targetPath}) { const depApis = await dependencies.byGlob("/test-resources/**/designtime/api.json"); @@ -164,6 +171,7 @@ async function writeDependencyApisToDir({dependencies, targetPath}) { virBasePath: "/" }); await Promise.all(apis.map((resource) => fsTarget.write(resource))); + return apis.length; } module.exports = generateJsdoc; diff --git a/test/lib/tasks/jsdoc/generateJsdoc.js b/test/lib/tasks/jsdoc/generateJsdoc.js index 03f96486d..9e7878e05 100644 --- a/test/lib/tasks/jsdoc/generateJsdoc.js +++ b/test/lib/tasks/jsdoc/generateJsdoc.js @@ -183,8 +183,8 @@ test.serial("generateJsdoc", async (t) => { targetPath: "/some/target/path", tmpPath: "/some/tmp/path", }); - const writeResourcesToDirStub = sinon.stub(generateJsdoc, "_writeResourcesToDir").resolves(); - const writeDependencyApisToDirStub = sinon.stub(generateJsdoc, "_writeDependencyApisToDir").resolves(); + const writeResourcesToDirStub = sinon.stub(generateJsdoc, "_writeResourcesToDir").resolves(1); + const writeDependencyApisToDirStub = sinon.stub(generateJsdoc, "_writeDependencyApisToDir").resolves(0); const writeStub = sinon.stub().resolves(); const workspace = { @@ -208,7 +208,6 @@ test.serial("generateJsdoc", async (t) => { t.deepEqual(writeResourcesToDirStub.callCount, 1, "writeResourcesToDir got called once"); t.deepEqual(writeResourcesToDirStub.getCall(0).args[0], { workspace, - dependencies: "dependencies", pattern: "some pattern", targetPath: "/some/source/path" // one's target is another one's source }, "writeResourcesToDir got called with correct arguments"); @@ -239,6 +238,50 @@ test.serial("generateJsdoc", async (t) => { mock.stop("../../../../lib/processors/jsdoc/jsdocGenerator"); }); +test.serial("generateJsdoc with missing resources", async (t) => { + const jsdocGeneratorStub = sinon.stub().resolves(); + mock("../../../../lib/processors/jsdoc/jsdocGenerator", jsdocGeneratorStub); + const logger = require("@ui5/logger"); + const infoLogStub = sinon.stub(); + const myLoggerInstance = { + info: infoLogStub + }; + sinon.stub(logger, "getLogger").returns(myLoggerInstance); + const generateJsdoc = mock.reRequire("../../../../lib/tasks/jsdoc/generateJsdoc"); + + sinon.stub(generateJsdoc, "_createTmpDirs").resolves({ + sourcePath: "/some/source/path", + targetPath: "/some/target/path", + tmpPath: "/some/tmp/path", + }); + sinon.stub(generateJsdoc, "_writeResourcesToDir").resolves(0); + sinon.stub(generateJsdoc, "_writeDependencyApisToDir").resolves(0); + + const writeStub = sinon.stub().resolves(); + const workspace = { + write: writeStub + }; + await generateJsdoc({ + workspace, + dependencies: "dependencies", + options: { + pattern: "some pattern", + projectName: "some.project", + namespace: "some/project", + version: "some version" + } + }); + + t.deepEqual(infoLogStub.callCount, 1, "One message has been logged"); + t.deepEqual(infoLogStub.getCall(0).args[0], "Failed to find any input resources for project some.project " + + "using pattern some pattern. Skipping JSDoc generation...", + "Correct message has been logged"); + + t.deepEqual(jsdocGeneratorStub.callCount, 0, "jsdocGenerator processor has *not* been called"); + + mock.stop("../../../../lib/processors/jsdoc/jsdocGenerator"); +}); + test.serial("generateJsdoc missing parameters", async (t) => { const error = await t.throws(generateJsdoc()); t.deepEqual(error.message, "[generateJsdoc]: One or more mandatory options not provided", From 769f201d02bb6f6d2c0041819c6e1b583e5ebf75 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Tue, 19 Mar 2019 13:20:55 +0100 Subject: [PATCH 33/38] Update JSDoc lib files from OpenUI5 change --- lib/processors/jsdoc/lib/create-api-index.js | 39 ++++++++++++++----- .../jsdoc/lib/transform-apijson-for-sdk.js | 12 ++++-- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/lib/processors/jsdoc/lib/create-api-index.js b/lib/processors/jsdoc/lib/create-api-index.js index 0866f8852..e722ad8a9 100644 --- a/lib/processors/jsdoc/lib/create-api-index.js +++ b/lib/processors/jsdoc/lib/create-api-index.js @@ -6,10 +6,11 @@ */ "use strict"; -const fs = require("fs"); const path = require("path"); -function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetFileDeprecated, targetFileExperimental, targetFileSince) { +function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetFileDeprecated, targetFileExperimental, targetFileSince, options) { + const fs = options && options.fs || require("fs"); + const returnOutputFiles = options && !!options.returnOutputFiles; console.log("[INFO] creating API index files"); console.log("[INFO] sap-ui-version.json: " + versionInfoFile); @@ -18,6 +19,12 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF console.log("[INFO] target file deprecated: " + targetFileDeprecated); console.log("[INFO] target file experimental: " + targetFileExperimental); console.log("[INFO] target file since: " + targetFileSince); + if (options && options.fs) { + console.log("[INFO] Using custom fs"); + } + if (returnOutputFiles) { + console.log("[INFO] Returning output files instead of writing to fs.") + } console.log("[INFO]"); // Deprecated, Experimental and Since collections @@ -317,6 +324,7 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF function createOverallIndex() { let version = "0.0.0"; + const filesToReturn = {}; var p = readJSONFile(versionInfoFile) .then(versionInfo => { @@ -343,7 +351,11 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF library: "*", symbols: symbols }; - return writeJSON(targetFile, result); + if (returnOutputFiles) { + filesToReturn[targetFile] = JSON.stringify(result); + } else { + return writeJSON(targetFile, result); + } }) .then(() => { /* Lists - modify and cleanup */ @@ -394,12 +406,21 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF delete oListCollection.experimental.noVersion; } }) - .then(() => Promise.all([ - // write deprecated, experimental and since collections in the respective index files - writeJSON(targetFileDeprecated, oListCollection.deprecated), - writeJSON(targetFileExperimental, oListCollection.experimental), - writeJSON(targetFileSince, oListCollection.since) - ])) + .then(() => { + if (returnOutputFiles) { + filesToReturn[targetFileDeprecated] = JSON.stringify(oListCollection.deprecated); + filesToReturn[targetFileExperimental] = JSON.stringify(oListCollection.experimental); + filesToReturn[targetFileSince] = JSON.stringify(oListCollection.since); + return filesToReturn; + } else { + return Promise.all([ + // write deprecated, experimental and since collections in the respective index files + writeJSON(targetFileDeprecated, oListCollection.deprecated), + writeJSON(targetFileExperimental, oListCollection.experimental), + writeJSON(targetFileSince, oListCollection.since) + ]); + } + }) .catch(err => { console.error("**** failed to create API index for libraries:", err) throw err; diff --git a/lib/processors/jsdoc/lib/transform-apijson-for-sdk.js b/lib/processors/jsdoc/lib/transform-apijson-for-sdk.js index 36d9dacde..c8e27a71a 100644 --- a/lib/processors/jsdoc/lib/transform-apijson-for-sdk.js +++ b/lib/processors/jsdoc/lib/transform-apijson-for-sdk.js @@ -38,14 +38,20 @@ const log = (function() { * @returns {Promise} A Promise that resolves after the transformation has been completed */ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles, options) { - const fs = options && options.customFs || require("fs"); - const returnOutputFile = options && !!options.returnOutputFile; + const fs = options && options.fs || require("fs"); + const returnOutputFiles = options && !!options.returnOutputFiles; log.info("Transform API index files for sap.ui.documentation"); log.info(" original file: " + sInputFile); log.info(" output file: " + sOutputFile); log.info(" library file: " + sLibraryFile); log.info(" dependency dir: " + vDependencyAPIFiles); + if (options && options.fs) { + log.info("Using custom fs."); + } + if (returnOutputFiles) { + log.info("Returning output files instead of writing to fs.") + } log.info(""); /** @@ -739,7 +745,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles, * @param oChainObject chain object */ function createApiRefApiJson(oChainObject) { - if (returnOutputFile) { + if (returnOutputFiles) { // If requested, return data instead of writing to FS (required by UI5 Tooling/UI5 Builder) return JSON.stringify(oChainObject.parsedData); } From 652d0cd6c5ff81ea6356ffc72b16d2a58e7adadf Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Tue, 19 Mar 2019 15:14:25 +0100 Subject: [PATCH 34/38] Add generateApiIndex task --- index.js | 5 +- lib/builder/builder.js | 4 ++ lib/processors/jsdoc/apiIndexGenerator.js | 50 +++++++++++++++++++ lib/processors/jsdoc/lib/create-api-index.js | 38 ++++++++++---- lib/processors/jsdoc/sdkTransformer.js | 4 +- lib/tasks/jsdoc/generateApiIndex.js | 49 ++++++++++++++++++ lib/tasks/taskRepository.js | 3 +- lib/types/application/ApplicationBuilder.js | 13 ++++- test/lib/processors/jsdoc/sdkTransformer.js | 4 +- .../types/application/ApplicationBuilder.js | 12 +++-- 10 files changed, 160 insertions(+), 22 deletions(-) create mode 100644 lib/processors/jsdoc/apiIndexGenerator.js create mode 100644 lib/tasks/jsdoc/generateApiIndex.js diff --git a/index.js b/index.js index ef078852f..db8ee8ae9 100644 --- a/index.js +++ b/index.js @@ -13,7 +13,9 @@ module.exports = { flexChangesBundler: require("./lib/processors/bundlers/flexChangesBundler"), manifestBundler: require("./lib/processors/bundlers/manifestBundler"), moduleBundler: require("./lib/processors/bundlers/moduleBundler"), + apiIndexGenerator: require("./lib/processors/jsdoc/apiIndexGenerator"), jsdocGenerator: require("./lib/processors/jsdoc/jsdocGenerator"), + sdkTransformer: require("./lib/processors/jsdoc/sdkTransformer"), bootstrapHtmlTransformer: require("./lib/processors/bootstrapHtmlTransformer"), debugFileCreator: require("./lib/processors/debugFileCreator"), resourceCopier: require("./lib/processors/resourceCopier"), @@ -36,8 +38,9 @@ module.exports = { generateBundle: require("./lib/tasks/bundlers/generateBundle"), buildThemes: require("./lib/tasks/buildThemes"), createDebugFiles: require("./lib/tasks/createDebugFiles"), - generateJsdoc: require("./lib/tasks/jsdoc/generateJsdoc"), executeJsdocSdkTransformation: require("./lib/tasks/jsdoc/executeJsdocSdkTransformation"), + generateApiIndex: require("./lib/tasks/jsdoc/generateApiIndex"), + generateJsdoc: require("./lib/tasks/jsdoc/generateJsdoc"), generateVersionInfo: require("./lib/tasks/generateVersionInfo"), replaceCopyright: require("./lib/tasks/replaceCopyright"), replaceVersion: require("./lib/tasks/replaceVersion"), diff --git a/lib/builder/builder.js b/lib/builder/builder.js index 16ca364f7..7235c3382 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -54,6 +54,7 @@ function composeTaskList({dev, selfContained, jsdoc, includedTasks, excludedTask selectedTasks.transformBootstrapHtml = false; selectedTasks.generateJsdoc = false; selectedTasks.executeJsdocSdkTransformation = false; + selectedTasks.generateApiIndex = false; if (selfContained) { // No preloads, bundle only @@ -67,6 +68,7 @@ function composeTaskList({dev, selfContained, jsdoc, includedTasks, excludedTask // Include JSDoc tasks selectedTasks.generateJsdoc = true; selectedTasks.executeJsdocSdkTransformation = true; + selectedTasks.generateApiIndex = true; // Exclude all tasks not relevant to JSDoc generation selectedTasks.generateComponentPreload = false; @@ -75,6 +77,8 @@ function composeTaskList({dev, selfContained, jsdoc, includedTasks, excludedTask selectedTasks.buildThemes = false; selectedTasks.createDebugFiles = false; selectedTasks.uglify = false; + selectedTasks.generateFlexChangesBundle = false; + selectedTasks.generateManifestBundle = false; } // Only run essential tasks in development mode, it is not desired to run time consuming tasks during development. diff --git a/lib/processors/jsdoc/apiIndexGenerator.js b/lib/processors/jsdoc/apiIndexGenerator.js new file mode 100644 index 000000000..aa3ba3d25 --- /dev/null +++ b/lib/processors/jsdoc/apiIndexGenerator.js @@ -0,0 +1,50 @@ +const resourceFactory = require("@ui5/fs").resourceFactory; +const createIndex = require("./lib/create-api-index"); + +/** + * Compiles API index resources from all api.json resources available in the given test resources directory + * as created by the [sdkTransformer]{@link module:@ui5/builder.processors.sdkTransformer} processor. + * The resulting index resources (e.g. api-index.json, api-index-deprecated.json, + * api-index-experimental.json and api-index-since.json) are mainly to be used in the SDK. + * + * @public + * @alias module:@ui5/builder.processors.apiIndexGenerator + * @param {Object} parameters Parameters + * @param {string} parameters.versionInfoFile Path to sap-ui-version.json resource + * @param {string} parameters.unpackedTestresourcesRoot Path to /test-resources root directory in the + * given fs + * @param {string} parameters.targetFile Path to create the generated API index JSON resource for + * @param {string} parameters.targetFileDeprecated Path to create the generated API index "deprecated" JSON resource for + * @param {string} parameters.targetFileExperimental Path to create the generated API index "experimental" JSON + * resource for + * @param {string} parameters.targetFileSince Path to create the generated API index "since" JSON resource for + * @param {fs|module:@ui5/fs.fsInterface} parameters.fs Node fs or + * custom [fs interface]{@link module:resources/module:@ui5/fs.fsInterface} to use + * @returns {Promise} Promise resolving with created resources api-index.json, + * api-index-deprecated.json, api-index-experimental.json and + * api-index-since.json (names depend on the supplied paths) + */ +const apiIndexGenerator = async function({ + versionInfoFile, unpackedTestresourcesRoot, targetFile, targetFileDeprecated, targetFileExperimental, + targetFileSince, fs +}) { + if (!versionInfoFile || !unpackedTestresourcesRoot || !targetFile || !targetFileDeprecated || + !targetFileExperimental || !targetFileSince || !fs) { + throw new Error("[apiIndexGenerator]: One or more mandatory parameters not provided"); + } + + const resourceMap = await createIndex(versionInfoFile, unpackedTestresourcesRoot, targetFile, + targetFileDeprecated, targetFileExperimental, targetFileSince, { + fs, + returnOutputFiles: true + }); + + return Object.keys(resourceMap).map((resPath) => { + return resourceFactory.createResource({ + path: resPath, + string: resourceMap[resPath] + }); + }); +}; + +module.exports = apiIndexGenerator; diff --git a/lib/processors/jsdoc/lib/create-api-index.js b/lib/processors/jsdoc/lib/create-api-index.js index e722ad8a9..a2186a8e5 100644 --- a/lib/processors/jsdoc/lib/create-api-index.js +++ b/lib/processors/jsdoc/lib/create-api-index.js @@ -7,25 +7,41 @@ "use strict"; const path = require("path"); +const log = (function() { + try { + return require("@ui5/logger").getLogger("builder:processors:jsdoc:create-api-index"); + } catch (error) { + /* eslint-disable no-console */ + return { + info: function info(...msg) { + console.log("[INFO]", ...msg); + }, + error: function error(...msg) { + console.error(...msg); + } + }; + /* eslint-enable no-console */ + } +}()); function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetFileDeprecated, targetFileExperimental, targetFileSince, options) { const fs = options && options.fs || require("fs"); const returnOutputFiles = options && !!options.returnOutputFiles; - console.log("[INFO] creating API index files"); - console.log("[INFO] sap-ui-version.json: " + versionInfoFile); - console.log("[INFO] unpacked test-resources: " + unpackedTestresourcesRoot); - console.log("[INFO] target file: " + targetFile); - console.log("[INFO] target file deprecated: " + targetFileDeprecated); - console.log("[INFO] target file experimental: " + targetFileExperimental); - console.log("[INFO] target file since: " + targetFileSince); + log.info("creating API index files"); + log.info(" sap-ui-version.json: " + versionInfoFile); + log.info(" unpacked test-resources: " + unpackedTestresourcesRoot); + log.info(" target file: " + targetFile); + log.info(" target file deprecated: " + targetFileDeprecated); + log.info(" target file experimental: " + targetFileExperimental); + log.info(" target file since: " + targetFileSince); if (options && options.fs) { - console.log("[INFO] Using custom fs"); + log.info("Using custom fs"); } if (returnOutputFiles) { - console.log("[INFO] Returning output files instead of writing to fs.") + log.info("Returning output files instead of writing to fs.") } - console.log("[INFO]"); + log.info(""); // Deprecated, Experimental and Since collections let oListCollection = { @@ -422,7 +438,7 @@ function process(versionInfoFile, unpackedTestresourcesRoot, targetFile, targetF } }) .catch(err => { - console.error("**** failed to create API index for libraries:", err) + log.error("**** failed to create API index for libraries:", err) throw err; }); diff --git a/lib/processors/jsdoc/sdkTransformer.js b/lib/processors/jsdoc/sdkTransformer.js index c8defa19b..0dbb98dd2 100644 --- a/lib/processors/jsdoc/sdkTransformer.js +++ b/lib/processors/jsdoc/sdkTransformer.js @@ -27,8 +27,8 @@ const sdkTransformer = async function({ } const fakeTargetPath = "/ignore/this/path/resource/will/be/returned"; const apiJsonContent = await transformer(apiJsonPath, fakeTargetPath, dotLibraryPath, dependencyApiJsonPaths, { - customFs: fs, - returnOutputFile: true + fs, + returnOutputFiles: true }); return [resourceFactory.createResource({ path: targetApiJsonPath, diff --git a/lib/tasks/jsdoc/generateApiIndex.js b/lib/tasks/jsdoc/generateApiIndex.js new file mode 100644 index 000000000..b0d1774f4 --- /dev/null +++ b/lib/tasks/jsdoc/generateApiIndex.js @@ -0,0 +1,49 @@ +const ui5Fs = require("@ui5/fs"); +const ReaderCollectionPrioritized = ui5Fs.ReaderCollectionPrioritized; +const fsInterface = ui5Fs.fsInterface; +const apiIndexGenerator = require("../../processors/jsdoc/apiIndexGenerator"); + +/** + * Compiles an api-index.json resource from all available api.json resources as created by the + * [executeJsdocSdkTransformation]{@link module:@ui5/builder.tasks.executeJsdocSdkTransformation} task. + * The resulting api-index.json resource is mainly to be used in the SDK. + * + * @public + * @alias module:@ui5/builder.tasks.generateApiIndex + * @param {Object} parameters Parameters + * @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {module:@ui5/fs.AbstractReader} parameters.dependencies Reader or Collection to read dependency files + * @param {Object} parameters.options Options + * @param {string} parameters.options.projectName Project name + * @returns {Promise} Promise resolving with undefined once data has been written + */ +module.exports = async function({workspace, dependencies, options}) { + if (!options || !options.projectName) { + throw new Error("[generateApiIndex]: One or more mandatory options not provided"); + } + const combo = new ReaderCollectionPrioritized({ + name: `generateApiIndex - workspace + dependencies: ${options.projectName}`, + readers: [workspace, dependencies] + }); + + const versionInfoFile = "/resources/sap-ui-version.json"; + const unpackedTestresourcesRoot = "/test-resources"; + const targetFile = "/docs/api/api-index.json"; + const targetFileDeprecated = "/docs/api/api-index-deprecated.json"; + const targetFileExperimental = "/docs/api/api-index-experimental.json"; + const targetFileSince = "/docs/api/api-index-since.json"; + + const createdResources = await apiIndexGenerator({ + versionInfoFile, + unpackedTestresourcesRoot, + targetFile, + targetFileDeprecated, + targetFileExperimental, + targetFileSince, + fs: fsInterface(combo), + }); + + await Promise.all(createdResources.map((resource) => { + return workspace.write(resource); + })); +}; diff --git a/lib/tasks/taskRepository.js b/lib/tasks/taskRepository.js index 570e80408..d510e2459 100644 --- a/lib/tasks/taskRepository.js +++ b/lib/tasks/taskRepository.js @@ -2,8 +2,9 @@ const tasks = { replaceCopyright: require("./replaceCopyright"), replaceVersion: require("./replaceVersion"), createDebugFiles: require("./createDebugFiles"), - generateJsdoc: require("./jsdoc/generateJsdoc"), executeJsdocSdkTransformation: require("./jsdoc/executeJsdocSdkTransformation"), + generateApiIndex: require("./jsdoc/generateApiIndex"), + generateJsdoc: require("./jsdoc/generateJsdoc"), uglify: require("./uglify"), buildThemes: require("./buildThemes"), transformBootstrapHtml: require("./transformBootstrapHtml"), diff --git a/lib/types/application/ApplicationBuilder.js b/lib/types/application/ApplicationBuilder.js index b4dbdd764..c5bd612d1 100644 --- a/lib/types/application/ApplicationBuilder.js +++ b/lib/types/application/ApplicationBuilder.js @@ -13,7 +13,8 @@ const tasks = { // can't require index.js due to circular dependency replaceCopyright: require("../../tasks/replaceCopyright"), replaceVersion: require("../../tasks/replaceVersion"), transformBootstrapHtml: require("../../tasks/transformBootstrapHtml"), - uglify: require("../../tasks/uglify") + uglify: require("../../tasks/uglify"), + generateApiIndex: require("../../tasks/jsdoc/generateApiIndex") }; class ApplicationBuilder extends AbstractBuilder { @@ -159,6 +160,16 @@ class ApplicationBuilder extends AbstractBuilder { } }); }); + + this.addTask("generateApiIndex", () => { + return tasks.generateApiIndex({ + workspace: resourceCollections.workspace, + dependencies: resourceCollections.dependencies, + options: { + projectName: project.metadata.name + } + }); + }); } } diff --git a/test/lib/processors/jsdoc/sdkTransformer.js b/test/lib/processors/jsdoc/sdkTransformer.js index 09bfd94dd..215392988 100644 --- a/test/lib/processors/jsdoc/sdkTransformer.js +++ b/test/lib/processors/jsdoc/sdkTransformer.js @@ -41,8 +41,8 @@ test.serial("sdkTransformer", async (t) => { "/some/path/y/api.json" ], "transform-apijson-for-sdk called with correct argument #4"); t.deepEqual(transformerStub.getCall(0).args[4], { - customFs: "custom fs", - returnOutputFile: true + fs: "custom fs", + returnOutputFiles: true }, "transform-apijson-for-sdk called with correct argument #5"); t.deepEqual(createResourceStub.callCount, 1, "createResource called once"); diff --git a/test/lib/types/application/ApplicationBuilder.js b/test/lib/types/application/ApplicationBuilder.js index 359550558..a25eccfde 100644 --- a/test/lib/types/application/ApplicationBuilder.js +++ b/test/lib/types/application/ApplicationBuilder.js @@ -67,7 +67,8 @@ test("Instantiation", (t) => { "generateBundle", "createDebugFiles", "uglify", - "generateVersionInfo" + "generateVersionInfo", + "generateApiIndex" ], "ApplicationBuilder is instantiated with standard tasks"); }); @@ -87,7 +88,8 @@ test("Instantiation without component preload project configuration", (t) => { "generateBundle", "createDebugFiles", "uglify", - "generateVersionInfo" + "generateVersionInfo", + "generateApiIndex" ], "ApplicationBuilder is still instantiated with standard tasks"); }); @@ -107,7 +109,8 @@ test("Instantiation without project namespace", (t) => { "generateBundle", "createDebugFiles", "uglify", - "generateVersionInfo" + "generateVersionInfo", + "generateApiIndex" ], "All standard tasks but generateComponentPreload will be executed"); }); @@ -132,6 +135,7 @@ test("Instantiation with custom tasks", (t) => { "createDebugFiles", "uglify", "replaceVersion--1", - "generateVersionInfo" + "generateVersionInfo", + "generateApiIndex" ], "ApplicationBuilder is still instantiated with standard tasks"); }); From 413ccf06b1e77317ff94a4b343a531d55b96c96d Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Wed, 20 Mar 2019 10:47:49 +0100 Subject: [PATCH 35/38] apiIndexGenerator: Rename variables --- lib/processors/jsdoc/apiIndexGenerator.js | 27 ++++++++++++----------- lib/tasks/jsdoc/generateApiIndex.js | 24 ++++++++++---------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/lib/processors/jsdoc/apiIndexGenerator.js b/lib/processors/jsdoc/apiIndexGenerator.js index aa3ba3d25..4e2df07d0 100644 --- a/lib/processors/jsdoc/apiIndexGenerator.js +++ b/lib/processors/jsdoc/apiIndexGenerator.js @@ -10,14 +10,15 @@ const createIndex = require("./lib/create-api-index"); * @public * @alias module:@ui5/builder.processors.apiIndexGenerator * @param {Object} parameters Parameters - * @param {string} parameters.versionInfoFile Path to sap-ui-version.json resource - * @param {string} parameters.unpackedTestresourcesRoot Path to /test-resources root directory in the + * @param {string} parameters.versionInfoPath Path to sap-ui-version.json resource + * @param {string} parameters.testResourcesRootPath Path to /test-resources root directory in the * given fs - * @param {string} parameters.targetFile Path to create the generated API index JSON resource for - * @param {string} parameters.targetFileDeprecated Path to create the generated API index "deprecated" JSON resource for - * @param {string} parameters.targetFileExperimental Path to create the generated API index "experimental" JSON - * resource for - * @param {string} parameters.targetFileSince Path to create the generated API index "since" JSON resource for + * @param {string} parameters.targetApiIndexPath Path to create the generated API index JSON resource for + * @param {string} parameters.targetApiIndexDeprecatedPath Path to create the generated API index "deprecated" JSON + * resource for + * @param {string} parameters.targetApiIndexExperimentalPath Path to create the generated API index "experimental" JSON + * resource for + * @param {string} parameters.targetApiIndexSincePath Path to create the generated API index "since" JSON resource for * @param {fs|module:@ui5/fs.fsInterface} parameters.fs Node fs or * custom [fs interface]{@link module:resources/module:@ui5/fs.fsInterface} to use * @returns {Promise} Promise resolving with created resources api-index.json, @@ -25,16 +26,16 @@ const createIndex = require("./lib/create-api-index"); * api-index-since.json (names depend on the supplied paths) */ const apiIndexGenerator = async function({ - versionInfoFile, unpackedTestresourcesRoot, targetFile, targetFileDeprecated, targetFileExperimental, - targetFileSince, fs + versionInfoPath, testResourcesRootPath, targetApiIndexPath, targetApiIndexDeprecatedPath, + targetApiIndexExperimentalPath, targetApiIndexSincePath, fs }) { - if (!versionInfoFile || !unpackedTestresourcesRoot || !targetFile || !targetFileDeprecated || - !targetFileExperimental || !targetFileSince || !fs) { + if (!versionInfoPath || !testResourcesRootPath || !targetApiIndexPath || !targetApiIndexDeprecatedPath || + !targetApiIndexExperimentalPath || !targetApiIndexSincePath || !fs) { throw new Error("[apiIndexGenerator]: One or more mandatory parameters not provided"); } - const resourceMap = await createIndex(versionInfoFile, unpackedTestresourcesRoot, targetFile, - targetFileDeprecated, targetFileExperimental, targetFileSince, { + const resourceMap = await createIndex(versionInfoPath, testResourcesRootPath, targetApiIndexPath, + targetApiIndexDeprecatedPath, targetApiIndexExperimentalPath, targetApiIndexSincePath, { fs, returnOutputFiles: true }); diff --git a/lib/tasks/jsdoc/generateApiIndex.js b/lib/tasks/jsdoc/generateApiIndex.js index b0d1774f4..324cc9b5e 100644 --- a/lib/tasks/jsdoc/generateApiIndex.js +++ b/lib/tasks/jsdoc/generateApiIndex.js @@ -26,20 +26,20 @@ module.exports = async function({workspace, dependencies, options}) { readers: [workspace, dependencies] }); - const versionInfoFile = "/resources/sap-ui-version.json"; - const unpackedTestresourcesRoot = "/test-resources"; - const targetFile = "/docs/api/api-index.json"; - const targetFileDeprecated = "/docs/api/api-index-deprecated.json"; - const targetFileExperimental = "/docs/api/api-index-experimental.json"; - const targetFileSince = "/docs/api/api-index-since.json"; + const versionInfoPath = "/resources/sap-ui-version.json"; + const testResourcesRootPath = "/test-resources"; + const targetApiIndexPath = "/docs/api/api-index.json"; + const targetApiIndexDeprecatedPath = "/docs/api/api-index-deprecated.json"; + const targetApiIndexExperimentalPath = "/docs/api/api-index-experimental.json"; + const targetApiIndexSincePath = "/docs/api/api-index-since.json"; const createdResources = await apiIndexGenerator({ - versionInfoFile, - unpackedTestresourcesRoot, - targetFile, - targetFileDeprecated, - targetFileExperimental, - targetFileSince, + versionInfoPath, + testResourcesRootPath, + targetApiIndexPath, + targetApiIndexDeprecatedPath, + targetApiIndexExperimentalPath, + targetApiIndexSincePath, fs: fsInterface(combo), }); From 5905a2e115a2400305d658cb7ed7c87a11c9ec60 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Wed, 20 Mar 2019 11:07:13 +0100 Subject: [PATCH 36/38] Add tests for generateApiIndex task and processor --- lib/processors/jsdoc/apiIndexGenerator.js | 2 +- lib/tasks/jsdoc/generateApiIndex.js | 2 +- .../lib/processors/jsdoc/apiIndexGenerator.js | 85 +++++++++++++++++++ test/lib/tasks/jsdoc/generateApiIndex.js | 74 ++++++++++++++++ 4 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 test/lib/processors/jsdoc/apiIndexGenerator.js create mode 100644 test/lib/tasks/jsdoc/generateApiIndex.js diff --git a/lib/processors/jsdoc/apiIndexGenerator.js b/lib/processors/jsdoc/apiIndexGenerator.js index 4e2df07d0..c3026557d 100644 --- a/lib/processors/jsdoc/apiIndexGenerator.js +++ b/lib/processors/jsdoc/apiIndexGenerator.js @@ -28,7 +28,7 @@ const createIndex = require("./lib/create-api-index"); const apiIndexGenerator = async function({ versionInfoPath, testResourcesRootPath, targetApiIndexPath, targetApiIndexDeprecatedPath, targetApiIndexExperimentalPath, targetApiIndexSincePath, fs -}) { +} = {}) { if (!versionInfoPath || !testResourcesRootPath || !targetApiIndexPath || !targetApiIndexDeprecatedPath || !targetApiIndexExperimentalPath || !targetApiIndexSincePath || !fs) { throw new Error("[apiIndexGenerator]: One or more mandatory parameters not provided"); diff --git a/lib/tasks/jsdoc/generateApiIndex.js b/lib/tasks/jsdoc/generateApiIndex.js index 324cc9b5e..005068ec0 100644 --- a/lib/tasks/jsdoc/generateApiIndex.js +++ b/lib/tasks/jsdoc/generateApiIndex.js @@ -17,7 +17,7 @@ const apiIndexGenerator = require("../../processors/jsdoc/apiIndexGenerator"); * @param {string} parameters.options.projectName Project name * @returns {Promise} Promise resolving with undefined once data has been written */ -module.exports = async function({workspace, dependencies, options}) { +module.exports = async function({workspace, dependencies, options} = {}) { if (!options || !options.projectName) { throw new Error("[generateApiIndex]: One or more mandatory options not provided"); } diff --git a/test/lib/processors/jsdoc/apiIndexGenerator.js b/test/lib/processors/jsdoc/apiIndexGenerator.js new file mode 100644 index 000000000..f35b49785 --- /dev/null +++ b/test/lib/processors/jsdoc/apiIndexGenerator.js @@ -0,0 +1,85 @@ +const {test} = require("ava"); +const sinon = require("sinon"); +const mock = require("mock-require"); +const apiIndexGenerator = require("../../../../lib/processors/jsdoc/apiIndexGenerator"); + +test.afterEach.always((t) => { + sinon.restore(); +}); + +test.serial("apiIndexGenerator", async (t) => { + const createApiIndexStub = sinon.stub().resolves({ + "/some/path/api-index.json": "resource content A", + "/some/path/api-index-deprecated.json": "resource content B", + "/some/path/api-index-experimental.json": "resource content C", + "/some/path/api-index-since.json": "resource content D" + }); + mock("../../../../lib/processors/jsdoc/lib/create-api-index", createApiIndexStub); + const createResourceStub = sinon.stub(require("@ui5/fs").resourceFactory, "createResource") + .onCall(0).returns("result resource A") + .onCall(1).returns("result resource B") + .onCall(2).returns("result resource C") + .onCall(3).returns("result resource D"); + + const apiIndexGenerator = mock.reRequire("../../../../lib/processors/jsdoc/apiIndexGenerator"); + + const res = await apiIndexGenerator({ + versionInfoPath: "/some/path/sap-ui-version.json", + testResourcesRootPath: "/some/test-resources/path", + targetApiIndexPath: "/some/path/api-index.json", + targetApiIndexDeprecatedPath: "/some/path/api-index-deprecated.json", + targetApiIndexExperimentalPath: "/some/path/api-index-experimental.json", + targetApiIndexSincePath: "/some/path/api-index-since.json", + fs: "custom fs" + }); + + t.deepEqual(res.length, 4, "Returned one resource"); + t.deepEqual(res[0], "result resource A", "Returned correct resource"); + t.deepEqual(res[1], "result resource B", "Returned correct resource"); + t.deepEqual(res[2], "result resource C", "Returned correct resource"); + t.deepEqual(res[3], "result resource D", "Returned correct resource"); + + t.deepEqual(createApiIndexStub.callCount, 1, "create-api-index called once"); + t.deepEqual(createApiIndexStub.getCall(0).args[0], "/some/path/sap-ui-version.json", + "create-api-index called with correct argument #1"); + t.deepEqual(createApiIndexStub.getCall(0).args[1], "/some/test-resources/path", + "create-api-index called with correct argument #2"); + t.deepEqual(createApiIndexStub.getCall(0).args[2], "/some/path/api-index.json", + "create-api-index called with correct argument #3"); + t.deepEqual(createApiIndexStub.getCall(0).args[3], "/some/path/api-index-deprecated.json", + "create-api-index called with correct argument #4"); + t.deepEqual(createApiIndexStub.getCall(0).args[4], "/some/path/api-index-experimental.json", + "create-api-index called with correct argument #5"); + t.deepEqual(createApiIndexStub.getCall(0).args[5], "/some/path/api-index-since.json", + "create-api-index called with correct argument #6"); + t.deepEqual(createApiIndexStub.getCall(0).args[6], { + fs: "custom fs", + returnOutputFiles: true + }, "create-api-index called with correct argument #7"); + + t.deepEqual(createResourceStub.callCount, 4, "createResource called once"); + t.deepEqual(createResourceStub.getCall(0).args[0], { + path: "/some/path/api-index.json", + string: "resource content A" + }, "createResource called with correct arguments for resource 1"); + t.deepEqual(createResourceStub.getCall(1).args[0], { + path: "/some/path/api-index-deprecated.json", + string: "resource content B" + }, "createResource called with correct arguments for resource 2"); + t.deepEqual(createResourceStub.getCall(2).args[0], { + path: "/some/path/api-index-experimental.json", + string: "resource content C" + }, "createResource called with correct arguments for resource 3"); + t.deepEqual(createResourceStub.getCall(3).args[0], { + path: "/some/path/api-index-since.json", + string: "resource content D" + }, "createResource called with correct arguments for resource 4"); + + mock.stop("../../../../lib/processors/jsdoc/lib/create-api-index"); +}); + +test("apiIndexGenerator missing parameters", async (t) => { + const error = await t.throws(apiIndexGenerator()); + t.deepEqual(error.message, "[apiIndexGenerator]: One or more mandatory parameters not provided", + "Correct error message thrown"); +}); diff --git a/test/lib/tasks/jsdoc/generateApiIndex.js b/test/lib/tasks/jsdoc/generateApiIndex.js new file mode 100644 index 000000000..305a79d3e --- /dev/null +++ b/test/lib/tasks/jsdoc/generateApiIndex.js @@ -0,0 +1,74 @@ +const {test} = require("ava"); +const sinon = require("sinon"); +const ui5Fs = require("@ui5/fs"); + +const mock = require("mock-require"); + +const generateApiIndex = require("../../../../lib/tasks/jsdoc/generateApiIndex"); + +test.afterEach.always((t) => { + sinon.restore(); +}); + +test.serial("generateApiIndex", async (t) => { + const apiIndexGeneratorStub = sinon.stub().resolves(["resource A", "resource B"]); + const fsInterfaceStub = sinon.stub(ui5Fs, "fsInterface").returns("custom fs"); + mock("../../../../lib/processors/jsdoc/apiIndexGenerator", apiIndexGeneratorStub); + + class ReaderCollectionPrioritizedStubClass { + constructor(parameters) { + t.deepEqual(parameters, { + name: "generateApiIndex - workspace + dependencies: some.project", + readers: [workspace, dependencies] + }, "ReaderCollectionPrioritized got called with the correct arguments"); + } + } + const readerCollectionPrioritizedStub = ReaderCollectionPrioritizedStubClass; + const readerCollectionPrioritizedOrig = ui5Fs.ReaderCollectionPrioritized; + ui5Fs.ReaderCollectionPrioritized = readerCollectionPrioritizedStub; + const generateApiIndex = mock.reRequire("../../../../lib/tasks/jsdoc/generateApiIndex"); + ui5Fs.ReaderCollectionPrioritized = readerCollectionPrioritizedOrig; + + const writeStub = sinon.stub().resolves(); + const workspace = { + write: writeStub + }; + const dependencies = {}; + await generateApiIndex({ + workspace, + dependencies, + options: { + projectName: "some.project" + } + }); + + t.deepEqual(fsInterfaceStub.callCount, 1, "fsInterface got called once"); + t.true(fsInterfaceStub.getCall(0).args[0] instanceof ReaderCollectionPrioritizedStubClass, + "fsInterface got called with an instance of ReaderCollectionPrioritizedStubClass"); + + t.deepEqual(apiIndexGeneratorStub.callCount, 1, "apiIndexGenerator processor got called once"); + t.deepEqual(apiIndexGeneratorStub.getCall(0).args[0], { + versionInfoPath: "/resources/sap-ui-version.json", + testResourcesRootPath: "/test-resources", + targetApiIndexPath: "/docs/api/api-index.json", + targetApiIndexDeprecatedPath: "/docs/api/api-index-deprecated.json", + targetApiIndexExperimentalPath: "/docs/api/api-index-experimental.json", + targetApiIndexSincePath: "/docs/api/api-index-since.json", + fs: "custom fs" + }, "apiIndexGenerator got called with correct arguments"); + + t.deepEqual(writeStub.callCount, 2, "Write got called twice"); + t.deepEqual(writeStub.getCall(0).args[0], "resource A", "Write got called with correct arguments"); + t.deepEqual(writeStub.getCall(1).args[0], "resource B", "Write got called with correct arguments"); + + mock.stop("../../../../lib/processors/jsdoc/apiIndexGenerator"); + + sinon.restore(); + mock.reRequire("../../../../lib/tasks/jsdoc/generateApiIndex"); +}); + +test("generateApiIndex with missing parameters", async (t) => { + const error = await t.throws(generateApiIndex()); + t.deepEqual(error.message, "[generateApiIndex]: One or more mandatory options not provided", + "Correct error message thrown"); +}); From b82ac6569b7c47606ddb8e2ebd5073d00bc59fb3 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Thu, 21 Mar 2019 14:10:51 +0100 Subject: [PATCH 37/38] Make theme build default task for JSDoc build As css files are required for a working SDK --- lib/builder/builder.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/builder/builder.js b/lib/builder/builder.js index 7235c3382..0d7605c40 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -70,11 +70,13 @@ function composeTaskList({dev, selfContained, jsdoc, includedTasks, excludedTask selectedTasks.executeJsdocSdkTransformation = true; selectedTasks.generateApiIndex = true; + // Include theme build as required for SDK + selectedTasks.buildThemes = true; + // Exclude all tasks not relevant to JSDoc generation selectedTasks.generateComponentPreload = false; selectedTasks.generateLibraryPreload = false; selectedTasks.generateLibraryManifest = false; - selectedTasks.buildThemes = false; selectedTasks.createDebugFiles = false; selectedTasks.uglify = false; selectedTasks.generateFlexChangesBundle = false; From b61c7d0f25cd463b15f8d74305b8d424cd420622 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Thu, 21 Mar 2019 14:43:02 +0100 Subject: [PATCH 38/38] Also disable replaceCopyright and replaceVersion tasks in JSDoc build They are not necessary in preview/dev scenarios --- lib/builder/builder.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/builder/builder.js b/lib/builder/builder.js index 0d7605c40..5c34192d5 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -74,6 +74,8 @@ function composeTaskList({dev, selfContained, jsdoc, includedTasks, excludedTask selectedTasks.buildThemes = true; // Exclude all tasks not relevant to JSDoc generation + selectedTasks.replaceCopyright = false; + selectedTasks.replaceVersion = false; selectedTasks.generateComponentPreload = false; selectedTasks.generateLibraryPreload = false; selectedTasks.generateLibraryManifest = false;