UNPKG

sfcc-dts

Version:

> High quality Salesforce Commerce Cloud type definitions. A dw-api-types "done right"

345 lines (344 loc) 14.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const prettier_1 = __importDefault(require("prettier")); const json_merge_patch_1 = __importDefault(require("json-merge-patch")); const properties_reader_1 = __importDefault(require("properties-reader")); const basePathGenerated = path_1.default.join(process.cwd(), "./@types", "sfcc"); const sfccApi = json_merge_patch_1.default.apply(JSON.parse(fs_1.default.readFileSync("./api/sfcc-api.json", "utf8")), JSON.parse(fs_1.default.readFileSync("./api/patches.json", "utf8"))); var genericsremap = (0, properties_reader_1.default)('./api/generics.properties'); const config = { typesMapping: { Number: "number", String: "string", Boolean: "boolean", Object: "any", arguments: "IArguments", Array: "Array", Module: "NodeModule" }, generics: [ "dw.util.Collection", "dw.util.List", "dw.util.ArrayList", "dw.util.FilteringCollection", "dw.util.SeekableIterator", "dw.web.LoopIterator", "dw.util.Iterator", "dw.util.LinkedHashSet", "dw.util.Set", "dw.util.SortedSet", "dw.web.PagingModel", "Array", "TopLevel.Array", "dw.object.Extensible", "dw.object.ExtensibleObject", "dw.object.SimpleExtensible", "dw.svc.Result" ], maps: [ "dw.util.Map", "dw.util.HashMap", "dw.util.LinkedHashMap", "dw.util.MapEntry", "dw.util.SortedMap" ], extensible: [ "dw.object.Extensible", "dw.object.ExtensibleObject", "dw.object.SimpleExtensible", "dw.object.CustomObject" ], argsMapping: { function: "fn", }, exclusions: { classes: ["Generator"], global: { constants: ["undefined"], properties: [], methods: [], }, }, }; const checkIsCollection = (fullclassname) => { return config.generics.includes(fullclassname); }; const checkIsMap = (fullclassname) => { return config.maps.includes(fullclassname); }; const sanitizeType = (type, generics, isGeneric) => { var sanitizedType = (sfccApi.mapping[type] || type).replace("TopLevel.", ""); let mapped = config.typesMapping[sanitizedType] || sanitizedType; if (generics) { return `${mapped}<${generics}>`; } let result = isGeneric && mapped === 'any' ? 'T' : mapped; if (isGeneric && checkIsCollection(result)) { return `${result}<T>`; } if (checkIsCollection(result)) { return `${result}<any>`; } if (checkIsMap(result)) { return `${result}<any, any>`; } return result; }; const sanitizeArg = (arg) => config.argsMapping[arg] || arg; const sanitizeValue = (obj) => { switch (obj.class.name) { case "number": case "Number": try { return obj.value.match(/[0-9.]+/)[0]; } catch (e) { console.log(obj.name); } break; default: return obj.value; } }; const standardDefinition = (element) => { // if (element === 'undefined') { // return true; // } // return false; if (element === 'Iterator' || element === 'Iterable') { return true; } // if (element === 'module') { // return false; // we need this anyway // } try { eval(element); return true; } catch (e) { return false; } }; const doc = (obj) => { if (!obj.description) { return ''; } let description = obj.description; if (obj.args) { description += '\n' + obj.args.map((param) => `@param ${param.name} ${param.description || ''}`).join("\n"); } if (obj.class && obj.class.description) { description += `\n@return ${obj.class.description}`; } return `/**\n${description .split("\n") .map((line) => ` * ${line.replace('*/', '*\\/')}`) .join("\n")}\n*/\n`; }; const formatArgument = (arg, isGeneric) => `${arg.multiple ? "..." : ""}${sanitizeArg(arg.name)}: ${sanitizeType(arg.class.name, arg.class.generics, isGeneric)}${arg.multiple ? "[]" : ""}`; function filterComponent(key, prop) { return (element) => (key === "global" && !standardDefinition(element.name)) || (key !== "global" && !(config.exclusions[key] && config.exclusions[key][prop] && config.exclusions[key][prop].includes(element.name))); } ; const filterConstants = (key) => filterComponent(key, "constants"); const filterProperties = (key) => filterComponent(key, "properties"); const filterMethods = (key) => filterComponent(key, "methods"); const generateExportFileForClass = (theClass) => { var packageTokens = theClass.fullClassName.split("."); if (packageTokens.length && packageTokens[0] === "TopLevel") { packageTokens.shift(); } var className = packageTokens.pop(); if (theClass.fullClassName === 'TopLevel.Module') { theClass.fullClassName = 'TopLevel.NodeModule'; // hardcoded remapping to extend existing standard declaration } var foldersPath = path_1.default.join.apply(null, [].concat(basePathGenerated, packageTokens)); fs_1.default.mkdirSync(foldersPath, { recursive: true, }); var sourcePath = path_1.default.join(foldersPath, className + ".d.ts"); fs_1.default.writeFileSync(sourcePath, `/// <reference path="${packageTokens.length == 0 ? "./" : packageTokens.map(() => "../").join("")}index.d.ts" />\nexport = ${theClass.fullClassName.replace("TopLevel.", "")};`); }; const generateCodeForClass = (theClass, customAttrTypes) => { var source = ""; var packageTokens = theClass.fullClassName.split("."); var isTopLevel = false; if (packageTokens.length && packageTokens[0] === "TopLevel") { isTopLevel = true; packageTokens.shift(); } var className = packageTokens.pop(); var isInterface = sfccApi.interfaces.includes(theClass.fullClassName); var isGlobal = className === "global"; var isStatic = "static "; var readonly = "readonly "; if (isGlobal) { isStatic = "const "; readonly = ""; } let isGeneric = checkIsCollection(theClass.fullClassName); let isMap = checkIsMap(theClass.fullClassName); if (!isGlobal) { if (theClass.description) { source += doc(theClass); } // hardcoded remapping to extend existing standard declaration source += `${isTopLevel ? 'declare ' : ''}class ${className === 'Module' ? 'NodeModule' : className}${isGeneric ? '<T>' : isMap ? '<K, V>' : ''} `; if (theClass.hierarchy.length > 1) { let hierarchyClass = theClass.hierarchy.pop().name; let generics = null; // if (theClass.hierarchy.find((h: any) => h.name === 'dw.object.ExtensibleObject')) { if (hierarchyClass === 'dw.object.ExtensibleObject' || hierarchyClass === 'dw.object.Extensible') { generics = className + 'CustomAttributes'; customAttrTypes.add({ name: className }); } else if (theClass.hierarchy.find((h) => h.name === 'dw.object.ExtensibleObject' || h.name === 'dw.object.Extensible')) { // extends an extensible class, eg. ProductLineItem -> LineItem -> ExtensibleObject customAttrTypes.add({ name: className, extends: hierarchyClass }); if (!theClass.properties.custom) { theClass.properties.custom = { "name": "custom", "class": { "name": className + 'CustomAttributes' }, "static": false, "readonly": true, "description": "The custom attributes for this object. The returned object is\n used for retrieving and storing attribute values. See\n CustomAttributes for a detailed example of the syntax for\n working with custom attributes.", "deprecated": false }; theClass.methods.getCustom = { "name": "getCustom", "args": [], "class": { "name": className + 'CustomAttributes' }, "description": "Returns the custom attributes for this extensible object." }; } } source += `extends ${sanitizeType(hierarchyClass, generics, isGeneric)} `; } source += "{\n"; } let constants = Object.values(theClass.constants); source += constants .filter(filterConstants(className)) .sort((a, b) => a.name.localeCompare(b.name)) .reduce((constantSource, constant) => `${constantSource}${doc(constant)}${isGlobal ? 'declare ' : ''}${isStatic}${readonly}${constant.name}${!constant.value ? ": " + sanitizeType(constant.class.name, constant.class.generics, isGeneric && !isStatic) : ""}${constant.value ? " = " + sanitizeValue(constant) : ""};\n`, ""); source += "\n"; let properties = Object.values(theClass.properties); properties = properties.filter(prop => !constants.find(co => co.name == prop.name)); // properties my duplicate constants source += properties .filter(filterProperties(className)) .sort((a, b) => a.name.localeCompare(b.name)) .reduce((propSource, property) => { let returnType = sanitizeType(property.class.name, property.class.generics, isGeneric && !property.static); if (!isGeneric) { returnType = checkGenerics(returnType, theClass, property); } if (!config.extensible.includes(theClass.fullClassName) && property.name === 'custom' && returnType === 'dw.object.CustomAttributes') { returnType = className + 'CustomAttributes'; if (!Array.from(customAttrTypes).find(a => a.name === className)) { customAttrTypes.add({ name: className }); } } return `${propSource}${doc(property)}${isGlobal ? 'declare ' : ''}${property.static ? isStatic : ""}${property.readonly ? readonly : ""}${property.name}: ${returnType};\n`; }, ""); source += "\n"; if (!isInterface) { let constructors = Object.values(theClass.constructors); source += constructors .reduce((constructorSource, constructor) => `${constructorSource}${doc(constructor)}constructor(${constructor.args.map((m) => formatArgument(m, isGeneric)).join(", ")});\n`, ""); if (Object.keys(theClass.constructors).length === 0 && !isGlobal) { source += "private constructor();\n"; } source += "\n"; } let methods = Object.values(theClass.methods); source += methods .filter(filterMethods(className)) .sort((a, b) => a.name.localeCompare(b.name)) .reduce((methodSource, method) => { let returnType = sanitizeType(method.class.name, method.class.generics, isGeneric); if (!isGeneric) { returnType = checkGenerics(returnType, theClass, method); } if (!config.extensible.includes(theClass.fullClassName) && method.name === 'getCustom' && returnType === 'dw.object.CustomAttributes') { returnType = className + 'CustomAttributes'; if (!Array.from(customAttrTypes).find(a => a.name === className)) { customAttrTypes.add({ name: className }); } } return `${methodSource}${doc(method)}${isGlobal ? "declare function " : ""}${method.static && !isGlobal ? isStatic : ""}${method.name.replace('@@iterator', '[Symbol.iterator]')}(${method.args.map((m) => formatArgument(m, isGeneric)).join(", ")}): ${returnType};\n`; }, ""); source += "\n"; if (!isGlobal) { source += "}\n"; } generateExportFileForClass(theClass); return source + "\n"; }; const generateCode = (pkg, customAttrTypes) => Object.keys(pkg).reduce((source, key) => { if (!config.exclusions.classes[key] && !(pkg[key].package === 'TopLevel' && standardDefinition(key))) { if (pkg[key].fullClassName && !config.exclusions.classes[key]) { source += generateCodeForClass(pkg[key], customAttrTypes) + "\n"; } else { source += ` namespace ${key} {\n`; source += generateCode(pkg[key], customAttrTypes); source += `}\n`; } } return source; }, ""); let customAttrTypes = new Set(); var source = ""; //source += "declare global {\n"; source += generateCodeForClass(sfccApi.api.TopLevel.global, customAttrTypes); delete sfccApi.api.TopLevel.global; source += generateCode(sfccApi.api.TopLevel, customAttrTypes); source += "declare namespace dw {\n"; source += generateCode(sfccApi.api.dw, customAttrTypes); source += "}\n"; //source += "}\n"; let formatted = source; try { formatted = prettier_1.default.format(source, { parser: "typescript" }); } catch (e) { console.error(e); } fs_1.default.writeFileSync(path_1.default.join(basePathGenerated, "index.d.ts"), formatted); let customattrsrc = Array.from(customAttrTypes).map(i => ` /** * Custom attributes for ${i.name} object. */ declare class ${i.name}CustomAttributes ${i.extends ? 'extends ' + i.extends.substring(i.extends.lastIndexOf('.') + 1, i.extends.length) + 'CustomAttributes' : ''}{ /** * Returns the custom attribute with this name. Throws an exception if attribute is not defined */ [name: string]: any; }`).join('\n'); fs_1.default.writeFileSync(path_1.default.join(basePathGenerated, "attrs.d.ts"), prettier_1.default.format(customattrsrc, { parser: "typescript" })); fs_1.default.writeFileSync(path_1.default.join(basePathGenerated, "attrs.txt"), Array.from(new Set(Array.from(customAttrTypes).map(cu => cu.name))).sort().join('\n')); function checkGenerics(returnType, theClass, member) { if (returnType.indexOf('<any') > -1) { let propkey = member.type === 'property' ? `${theClass.fullClassName}.get${member.name.charAt(0).toUpperCase()}${member.name.substring(1)}` : `${theClass.fullClassName}.${member.name}`; // @ts-ignore returnType = genericsremap.get(propkey) || returnType; if (returnType.indexOf('<any') > -1) { console.log(`Unmapped generics: ${propkey}=${returnType}`); } } return returnType; }