UNPKG

pcf-scripts

Version:

This package contains a module for building PowerApps Component Framework (PCF) controls. See project homepage how to install.

303 lines (301 loc) 14.3 kB
"use strict"; // Copyright (C) Microsoft Corporation. All rights reserved. Object.defineProperty(exports, "__esModule", { value: true }); exports.ManifestTypesGenerator = void 0; const semver_1 = require("semver"); const ts = require("typescript"); /* eslint-disable @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access */ // the following props are compatible number properties that can be grouped together in the same type group const numberProperties = [ "ComponentFramework.PropertyTypes.NumberProperty", "ComponentFramework.PropertyTypes.WholeNumberProperty", "ComponentFramework.PropertyTypes.DecimalNumberProperty", "ComponentFramework.PropertyTypes.FloatingNumberProperty", ]; class ManifestTypesGenerator { constructor() { // map each type group's name to its type name this.typeGroups = {}; this.outputs = {}; // map each property name defined in manifest to its type name this.parameters = {}; this.dataSetNames = []; this.internalImportsRequired = {}; } getFieldType(type) { const prefix = type.split(".")[0]; switch (prefix) { case "SingleLine": case "Multiple": return "ComponentFramework.PropertyTypes.StringProperty"; case "Whole": return "ComponentFramework.PropertyTypes.WholeNumberProperty"; case "TwoOptions": return "ComponentFramework.PropertyTypes.TwoOptionsProperty"; case "DateAndTime": return "ComponentFramework.PropertyTypes.DateTimeProperty"; case "Decimal": return "ComponentFramework.PropertyTypes.DecimalNumberProperty"; case "FP": return "ComponentFramework.PropertyTypes.FloatingNumberProperty"; case "Currency": return "ComponentFramework.PropertyTypes.NumberProperty"; case "OptionSet": return "ComponentFramework.PropertyTypes.OptionSetProperty"; case "Lookup": return "ComponentFramework.PropertyTypes.LookupProperty"; case "MultiSelectOptionSet": return "ComponentFramework.PropertyTypes.MultiSelectOptionSetProperty"; default: return this.handleInternalTypes(prefix) ?? "ComponentFramework.PropertyTypes.Property"; } } handleInternalTypes(prefix) { const result = undefined; let interfaceName; switch (prefix) { case "File": return "any"; default: } if (result) { // eslint-disable-next-line no-prototype-builtins if (!this.internalImportsRequired.hasOwnProperty(interfaceName)) { this.internalImportsRequired[interfaceName] = []; } if (!this.internalImportsRequired[interfaceName].includes(result)) { this.internalImportsRequired[interfaceName].push(result); } } return result; } getTypeGroupType(tg) { let fieldType = ""; let nextType; for (const type of tg.type) { // first type in the type group, set the base if (fieldType === "") { fieldType = this.getFieldType(type); } else { // else check whether the next type in the type group is compatible nextType = this.getFieldType(type); if (nextType !== fieldType) { // incompatible types might all be NumberProperty if (numberProperties.includes(nextType) && numberProperties.includes(fieldType)) { fieldType = "ComponentFramework.PropertyTypes.NumberProperty"; } else { fieldType = "ComponentFramework.PropertyTypes.Property"; break; } } } } return fieldType; } // Get the type for props under IOutput interface (string, number, boolean or any) getOutputFieldType(type) { // Enum is always treated as string if (type.includes("ComponentFramework.PropertyTypes.EnumProperty")) { return ts.SyntaxKind.StringKeyword; } switch (type) { case "ComponentFramework.PropertyTypes.StringProperty": return ts.SyntaxKind.StringKeyword; case "ComponentFramework.PropertyTypes.NumberProperty": case "ComponentFramework.PropertyTypes.WholeNumberProperty": case "ComponentFramework.PropertyTypes.DecimalNumberProperty": case "ComponentFramework.PropertyTypes.FloatingNumberProperty": case "ComponentFramework.PropertyTypes.OptionSetProperty": return ts.SyntaxKind.NumberKeyword; case "ComponentFramework.PropertyTypes.TwoOptionsProperty": return ts.SyntaxKind.BooleanKeyword; default: return ts.SyntaxKind.AnyKeyword; } } getEnumType(propNode) { if (!propNode.value) { return "ComponentFramework.PropertyTypes.EnumProperty<any>"; } let enumType = ""; let first = true; const values = propNode.value; for (const val of values) { if (!first) { enumType += " | "; } else { first = false; } enumType += `"${val._}"`; } return `ComponentFramework.PropertyTypes.EnumProperty<${enumType}>`; } parseTypeGroupNode(typeGroups) { if (typeGroups) { for (const tg of typeGroups) { const typeGroupName = tg.$.name; const typeGroupType = this.getTypeGroupType(tg); this.typeGroups[typeGroupName] = typeGroupType; } } } parsePropertyNodes(props) { if (!props) { return; } for (const prop of props) { const propName = prop.$.name; let propType; // check if usage is defined; ignore those without a usage if (prop.$.usage) { if (prop.$["of-type-group"]) { if (prop.$["of-type-group"] in this.typeGroups) { propType = this.typeGroups[prop.$["of-type-group"]]; } else { throw new Error(`Type group ${prop.$["of-type-group"]} not found.`); } } else if (prop.$["of-type"]) { propType = prop.$["of-type"] === "Enum" ? this.getEnumType(prop) : this.getFieldType(prop.$["of-type"]); } else { propType = "ComponentFramework.PropertyTypes.Property"; } if (prop.$.usage === "output" || prop.$.usage === "bound") { this.outputs[propName] = propType; } if (prop.$.usage === "input" || prop.$.usage === "bound") { this.parameters[propName] = propType; } } } } parseDataSetNodes(dataSets) { if (!dataSets) { return; } this.dataSetNames = dataSets.map((set) => set.$.name); } // Helper for making the AST of ManifestTypes.d.ts - create all properties under IInput interface createInputProperties() { const inputProperties = Object.entries(this.parameters).map(([propName, propValue]) => { const typeRef = this.getTypescriptCompilerApi().createTypeReferenceNode(/*typeName*/ propValue, /*typeArguments*/ undefined); return this.getTypescriptCompilerApi().createPropertySignature( /*modifiers*/ undefined, /*name*/ propName, /*questionToken*/ undefined, /*type*/ typeRef); }); const dataSetTypeRef = this.getTypescriptCompilerApi().createTypeReferenceNode( /*typeName*/ "ComponentFramework.PropertyTypes.DataSet", /*typeArguments*/ undefined); const dataSetsProperties = this.dataSetNames.map((name) => { return this.getTypescriptCompilerApi().createPropertySignature( /*modifiers*/ undefined, /*name*/ name, /*questionToken*/ undefined, /*type*/ dataSetTypeRef); }); return inputProperties.concat(dataSetsProperties); } // Helper for making the AST of ManifestTypes.d.ts - create all properties under IOutput interface createOutputProperties() { const outputProperties = Object.keys(this.outputs).map((propName) => { let typeRef; // DateTimeProperty is a special case here - it doesn't have a keyword type defined in the API if (this.outputs[propName] === "ComponentFramework.PropertyTypes.DateTimeProperty") { typeRef = this.getTypescriptCompilerApi().createTypeReferenceNode(/*typeName*/ "Date", /*typeArguments*/ undefined); } else if (this.outputs[propName] === "any") { typeRef = this.getTypescriptCompilerApi().createTypeReferenceNode(/*typeName*/ "any", /*typeArguments*/ undefined); } else if (this.outputs[propName] === "ComponentFramework.PropertyTypes.LookupProperty") { typeRef = this.getTypescriptCompilerApi().createTypeReferenceNode(/*typeName*/ "ComponentFramework.LookupValue[]", /*typeArguments*/ undefined); } else if (this.outputs[propName] === "ComponentFramework.PropertyTypes.MultiSelectOptionSetProperty") { typeRef = this.getTypescriptCompilerApi().createTypeReferenceNode(/*typeName*/ "number[]", /*typeArguments*/ undefined); } else { const keyword = this.getOutputFieldType(this.outputs[propName]); typeRef = this.getTypescriptCompilerApi().createKeywordTypeNode(keyword); } return this.getTypescriptCompilerApi().createPropertySignature( /*modifiers*/ undefined, /*name*/ propName, /*questionToken*/ this.getTypescriptCompilerApi().createToken(ts.SyntaxKind.QuestionToken), /*type*/ typeRef); }); return outputProperties; } // Construct the full AST of ManifestTypes.d.ts makeAST() { const getArgs = (name) => { const args = [ /*modifiers*/ [this.getTypescriptCompilerApi().createToken(ts.SyntaxKind.ExportKeyword)], /*name*/ name, /*typeParameters*/ undefined, /*heritageClauses*/ undefined, /*members*/ name === "IInputs" ? this.createInputProperties() : this.createOutputProperties(), ]; // createInterfaceDeclaration has a different interface on Typescript 5+ and deprecated since 4.8.0 if (!ts.version || (0, semver_1.lt)(ts.version, "4.8.0")) { // Add decorators as the first argument for typescript versions < 4.8.0 return [/*decorators*/ undefined, ...args]; } return args; }; const inputsInterface = this.getTypescriptCompilerApi().createInterfaceDeclaration(...getArgs("IInputs")); const outputsInterface = this.getTypescriptCompilerApi().createInterfaceDeclaration(...getArgs("IOutputs")); const importsToAdd = []; for (const typeNS in this.internalImportsRequired) { // eslint-disable-next-line no-prototype-builtins if (this.internalImportsRequired.hasOwnProperty(typeNS)) { const properties = this.internalImportsRequired[typeNS]; const importSpecifiers = []; properties.forEach((property) => { importSpecifiers.push(this.getTypescriptCompilerApi().createImportSpecifier(undefined, this.getTypescriptCompilerApi().createIdentifier(property))); }); importsToAdd.push(this.getTypescriptCompilerApi().createImportDeclaration(undefined, undefined, this.getTypescriptCompilerApi().createImportClause(false, undefined, this.getTypescriptCompilerApi().createNamedImports(importSpecifiers)), this.getTypescriptCompilerApi().createStringLiteral(typeNS))); } } return this.getTypescriptCompilerApi().createNodeArray([...importsToAdd, inputsInterface, outputsInterface], false); } // Use Typescript's printer API to generate the actual code from the AST generateManifestTypes(manifestObj) { this.parseTypeGroupNode(manifestObj.manifest.control["type-group"]); this.parsePropertyNodes(manifestObj.manifest.control.property); this.parseDataSetNodes(manifestObj.manifest.control["data-set"]); const resultFile = ts.createSourceFile( /*fileName*/ "output.ts", /*sourceText*/ "", /*languageVersion*/ ts.ScriptTarget.Latest, /*setParentNodes*/ false, /*scriptKind*/ ts.ScriptKind.TS); const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed, }); const result = printer.printList(ts.ListFormat.MultiLine, this.makeAST(), resultFile); return result; } /** * This method is used to make typescript compiler API available in * typescript versions < 4.2.4 and above 4.2.4 * Typescript < 4.2.4 requires 'ts' and above requires 'ts.factory' */ // eslint-disable-next-line @typescript-eslint/no-explicit-any getTypescriptCompilerApi() { if (ts.factory !== undefined) { return ts.factory; } else { return ts; } } } exports.ManifestTypesGenerator = ManifestTypesGenerator; /* eslint-enable @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access */ //# sourceMappingURL=manifestTypesGenerator.js.map