sfcc-dts
Version:
> High quality Salesforce Commerce Cloud type definitions. A dw-api-types "done right"
345 lines (344 loc) • 14.8 kB
JavaScript
;
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;
}