UNPKG

ask-cli-x

Version:

Alexa Skills Kit (ASK) Command Line Interfaces

206 lines (205 loc) 9.81 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.tryMapField = exports.tryMapReference = exports.mapExpressions = exports.simpleExpressionMap = exports.tryMapTypes = exports.encodeTypeName = void 0; const acdl_1 = require("@alexa/acdl"); const cli_error_1 = __importDefault(require("../../../exceptions/cli-error")); const encodeTypeName = (type) => { var _a, _b, _c, _d; if (!type) { return; } if ((((_a = type.genericArguments) === null || _a === void 0 ? void 0 : _a.length) || 0) > 0) { return `${(_b = type.name) === null || _b === void 0 ? void 0 : _b.name}||${(_c = type.genericArguments) === null || _c === void 0 ? void 0 : _c.map((arg) => (0, exports.encodeTypeName)(arg)).join("||")}`; } return `${(_d = type.name) === null || _d === void 0 ? void 0 : _d.name}`; }; exports.encodeTypeName = encodeTypeName; /** * Lossy attempt to map two possibly related types to each other. * * Note: This implementation is hacky. Try to improve the mapping algorithm as needed. * * Rules: * 1. All property mappings much be pointing to the same type in both structures. * 2. First look for exact property name matches (within the type). * 3. Then match positionally, with the first instance of a property of a type in the target being matched to the first instance in the source. * Note: This is after exact matches are removed. * 4. If there are more instances of a property of a type in the source than the target, map the property name to the first * property of that type in the target, no matter if its been used before. * @returns a mapping from the fields in source to the opinionated match in target. * if the source or both types have no properties, an empty collection is returned. * @throws If the target has no properties and the source does have properties. * If the source contains a property of a type that is not present in the source. */ const tryMapTypes = (source, target) => { var _a, _b, _c, _d; if (!target.properties) { // Neither have properties! Return empty. if (!source.properties) { return {}; } throw new cli_error_1.default(`Target type (${(_a = target.name) === null || _a === void 0 ? void 0 : _a.name}) has no properties. There are no properties to map from the source type (${(_b = source.name) === null || _b === void 0 ? void 0 : _b.name}).`); } if (!source.properties) { // If the source has no properties, it doesn't matter what the target is. return {}; } const targetTypes = target.properties .filter((p) => !!p.type) .map((p) => [(0, exports.encodeTypeName)(p.type), p.name]) .filter((typeAndName) => !!typeAndName[0] && !!typeAndName[1]) .reduce((acc, [type, name]) => ({ ...acc, [type]: type in acc ? [...acc[type], name] : [name], }), {}); const sourceTypes = source.properties .map((p) => [(0, exports.encodeTypeName)(p.type), p.name]) .filter((typeAndName) => !!typeAndName[0] && !!typeAndName[1]) .reduce((acc, [type, name]) => ({ ...acc, [type]: type in acc ? [...acc[type], name] : [name], }), {}); if (!Object.keys(sourceTypes).every((t) => Object.keys(targetTypes).includes(t))) { throw new cli_error_1.default(`Source type (${(_c = source.name) === null || _c === void 0 ? void 0 : _c.name}) references a property of a type missing in the target type (${(_d = target.name) === null || _d === void 0 ? void 0 : _d.name}).`); } return Object.entries(sourceTypes).reduce((acc, [type, names]) => { const targetNames = targetTypes[type]; const matchingNames = names.filter((name) => targetNames.includes(name)); // Remove the exact map names from both collections. const remainingNames = names.filter((name) => !matchingNames.includes(name)); const remainingTargetNames = targetNames.filter((name) => !matchingNames.includes(name)); // Add exact matches to the map... const mappings = matchingNames.reduce((_acc, name) => ({ ..._acc, [name]: { name, encodedTypeName: type } }), acc); const [mapping] = remainingNames.reduce(([_acc, rem], name) => { const [first, ...rest] = rem; // if the next position if (first) { return [ { ..._acc, [name]: { name: first, encodedTypeName: type }, }, rest, ]; } // The target has fewer instances of properties of this type than the source... // Just take the first name? else { return [ { ..._acc, [name]: { name: targetNames[0], encodedTypeName: type }, }, [], ]; } }, [mappings, remainingTargetNames]); return mapping; }, {}); }; exports.tryMapTypes = tryMapTypes; /** * Maps a set of expressions to itself. * * Allows for symmetrical resolution of ACIR to ASK. */ const simpleExpressionMap = (source, sourceProject) => (0, exports.mapExpressions)(source, source, sourceProject, sourceProject); exports.simpleExpressionMap = simpleExpressionMap; /** * Name declaration mapping between two similar sequence of expressions. * * In MFT we receive a response from the server that contains a similar sequence of actions that may not * look like the original expressions or expressions provided by the user. * * This method helps provide a singular, heuristic based, mapping between the two expression sequences. * * TODO: Current implementation assumes that there are the same number of named expressions in each sequence. * Next iteration may try to match on the type of the name and optimize for the best match. * * @throws if the target and source have a different number of named expressions. */ const mapExpressions = (sources, targets, sourceProject, targetProject) => { if (sources.length > targets.length) { throw new cli_error_1.default("Source cannot have more expressions declarations than the target."); } // TODO: Improve this function by better mapping pairs. const sourceTargetPairs = sources.map((source, i) => ({ source, target: targets[i] })); return (sourceTargetPairs // only map when both source and target are named. .filter((x) => { var _a, _b; return (0, acdl_1.isNameDeclaration)(x.source) && !!((_a = x.source.name) === null || _a === void 0 ? void 0 : _a.name) && (0, acdl_1.isNameDeclaration)(x.target) && !!((_b = x.target.name) === null || _b === void 0 ? void 0 : _b.name); }) .map(({ source, target }, i) => { var _a, _b; const sourceType = extractType(source, sourceProject); const targetType = extractType(target, targetProject); if (!sourceType || !targetType) { throw new cli_error_1.default(`Source ${(_a = source.name) === null || _a === void 0 ? void 0 : _a.name} or Target ${(_b = target.name) === null || _b === void 0 ? void 0 : _b.name} Type could not be found.`); } return { /** * Used to track the general order of mapped type to break ties. */ order: i, // Generally frown on not null assertions, but we are checking it when filtering, shouldn't be null anyways. targetReference: target.name.name, sourceReference: source.name.name, typeMapping: (0, exports.tryMapTypes)(sourceType, targetType), }; }) .reduce((acc, { sourceReference, ...rest }) => ({ ...acc, [sourceReference]: rest, }), {})); }; exports.mapExpressions = mapExpressions; /** * Given an expression map, try to find a name that best matches the given fieldName. * * Optionally maps a single segment field name to a field in the selected expression. * If the field is NOT found on the selected reference, field is returned as undefined. * * TODO: Improve heuristics like type matching. */ const tryMapReference = (name, expressionMap, fieldName) => { // candidates have a matching field. if (name in expressionMap) { const exp = expressionMap[name]; if (fieldName) { if (fieldName in exp.typeMapping) { return { reference: exp.targetReference, field: exp.typeMapping[fieldName].name }; } } return { reference: exp.targetReference }; } return; }; exports.tryMapReference = tryMapReference; /** * Given an expression map, try to find a property name that best matches the given fieldName. * * Supports only a single segment field name. * * TODO: Improve heuristics like type matching. */ const tryMapField = (fieldName, expressionMap) => { // candidates have a matching field. const candidates = Object.values(expressionMap) .filter(({ typeMapping }) => fieldName in typeMapping) .map((value) => ({ reference: value.targetReference, field: value.typeMapping[fieldName].name, order: value.order, })); // uhh oh, no candidates if (candidates.length == 0) return; // Take the lowest order instance of a field in the map, based on the order. return candidates.sort((a, b) => (a.order = b.order))[0]; }; exports.tryMapField = tryMapField; const extractType = (named, project) => { const checker = project.getTypeChecker(); return checker.getType(named); };