@figma/code-connect
Version:
A tool for connecting your design system components in code with your design system in Figma
238 lines • 9.47 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MatchableNameTypes = exports.PROPERTY_PLACEHOLDER = void 0;
exports.generateValueMapping = generateValueMapping;
exports.buildMatchableNamesMap = buildMatchableNamesMap;
exports.generatePropMapping = generatePropMapping;
const figma_rest_api_1 = require("../figma_rest_api");
const fast_fuzzy_1 = require("fast-fuzzy");
const intrinsics_1 = require("../../connect/intrinsics");
const create_1 = require("../../react/create");
/**
* These thresholds were come up with by running the benchmarking
* and tweaking until high positives + acceptable false positives
* were achieved. It's worth repeating this process whenever making
* changes to the mapping algorithm.
*/
const MININUM_MATCH_SCORES = {
fuzzy: {
property: 0.65,
variantValue: 0.8,
},
embeddings: {
property: 0.84,
variantValue: 0.87,
},
};
/**
* Used when we should output a placeholder for an unknown value in prop mapping.
*/
exports.PROPERTY_PLACEHOLDER = 'PROPERTY_PLACEHOLDER';
function generateValueMapping(propSignature, figmaPropDef) {
const searchableCodeEnumOptions = {};
propSignature.split(' | ').forEach((str) => {
if (str.startsWith('"') && str.endsWith('"')) {
const withoutQuotes = str.substring(1, str.length - 1);
searchableCodeEnumOptions[withoutQuotes] = withoutQuotes;
}
else if (str === 'true') {
searchableCodeEnumOptions[str] = true;
}
else if (str === 'false') {
searchableCodeEnumOptions[str] = false;
}
else if (!isNaN(Number(str))) {
searchableCodeEnumOptions[str] = Number(str);
}
});
const searcher = new fast_fuzzy_1.Searcher(figmaPropDef.variantOptions);
return Object.entries(searchableCodeEnumOptions).reduce((valueMapping, [codeEnumValue, mappedValue]) => {
const results = searcher.search(codeEnumValue, { returnMatchData: true });
if (results.length && results[0].score > 0.5) {
valueMapping[results[0].item] = mappedValue;
}
return valueMapping;
}, {});
}
function signatureIsJsxLike(signature) {
return (signature.includes('ElementType') ||
signature.includes('ReactElement') ||
signature.includes('ReactNode'));
}
/**
* Attempts to create a mapping between a code prop and figma prop.
* These props have been matched by name and aren't guaranteed to
* actually be related, so we return null if no suitable mapping
*/
function generateIntrinsic({ propSignature, figmaPropName: figmaPropNameWithNodeId, figmaPropDef, }) {
const figmaPropName = stripNodeIdFromPropertyName(figmaPropNameWithNodeId);
if (propSignature === 'string' && figmaPropDef.type === figma_rest_api_1.FigmaRestApi.ComponentPropertyType.Text) {
return {
kind: intrinsics_1.IntrinsicKind.String,
args: {
figmaPropName,
},
};
}
if (propSignature === 'false | true' &&
(figmaPropDef.type === figma_rest_api_1.FigmaRestApi.ComponentPropertyType.Boolean ||
(figmaPropDef.type === figma_rest_api_1.FigmaRestApi.ComponentPropertyType.Variant &&
figmaPropDef.variantOptions?.length === 2 &&
figmaPropDef.variantOptions?.every(create_1.isBooleanKind)))) {
return {
kind: intrinsics_1.IntrinsicKind.Boolean,
args: {
figmaPropName,
},
};
}
if (propSignature.includes(' | ') &&
figmaPropDef.type === figma_rest_api_1.FigmaRestApi.ComponentPropertyType.Variant) {
const valueMapping = generateValueMapping(propSignature, figmaPropDef);
// Only valuable if some values were mapped
if (Object.keys(valueMapping).length > 0) {
return {
kind: intrinsics_1.IntrinsicKind.Enum,
args: {
figmaPropName,
valueMapping,
},
};
}
}
if (signatureIsJsxLike(propSignature) &&
figmaPropDef.type === figma_rest_api_1.FigmaRestApi.ComponentPropertyType.InstanceSwap) {
return {
kind: intrinsics_1.IntrinsicKind.Instance,
args: {
figmaPropName,
},
};
}
return null;
}
function stripNodeIdFromPropertyName(propertyName) {
return propertyName.replace(/#\d+:\d+/, '');
}
var MatchableNameTypes;
(function (MatchableNameTypes) {
MatchableNameTypes[MatchableNameTypes["Property"] = 0] = "Property";
MatchableNameTypes[MatchableNameTypes["VariantValue"] = 1] = "VariantValue";
// ChildLayer, // TODO
})(MatchableNameTypes || (exports.MatchableNameTypes = MatchableNameTypes = {}));
const MATCHABLE_NAME_TYPES_PRIORITY = [
MatchableNameTypes.Property,
MatchableNameTypes.VariantValue,
];
/**
* Builds a map of all properties and enum values, indexed by name.
* @param componentPropertyDefinitions
* @returns A map of {name: values[]}. Each value is an array to avoid
* collisions between properties / enum values
*/
function buildMatchableNamesMap(componentPropertyDefinitions) {
const matchableValues = {};
function add(name, definition) {
matchableValues[name] = matchableValues[name] || [];
matchableValues[name].push(definition);
}
Object.entries(componentPropertyDefinitions || {}).forEach(([propName, propDef]) => {
const name = stripNodeIdFromPropertyName(propName);
add(name, {
type: MatchableNameTypes.Property,
name: propName,
});
if (propDef.type === figma_rest_api_1.FigmaRestApi.ComponentPropertyType.Variant) {
propDef.variantOptions?.forEach((variantValue) => {
add(variantValue, {
type: MatchableNameTypes.VariantValue,
name: variantValue,
variantProperty: propName,
});
});
}
});
return matchableValues;
}
function generatePropMapping({ componentPropertyDefinitions, signature, componentMatchResults, matchableNamesMap, }) {
const propMapping = {};
const searchSpace = Object.keys(matchableNamesMap);
const searcher = new fast_fuzzy_1.Searcher(searchSpace);
let minimumMatchScores = MININUM_MATCH_SCORES.fuzzy;
const useEmbeddings = !!componentMatchResults;
if (useEmbeddings) {
minimumMatchScores = MININUM_MATCH_SCORES.embeddings;
}
function attemptGetIntrinsicForProp({ propMatch, propSignature, }) {
const itemsInPriorityOrder = [...matchableNamesMap[propMatch.item]].sort((a, b) => MATCHABLE_NAME_TYPES_PRIORITY.indexOf(a.type) -
MATCHABLE_NAME_TYPES_PRIORITY.indexOf(b.type));
for (const item of itemsInPriorityOrder) {
/**
* First, look for matching property names with compatible types
*/
if (item.type === MatchableNameTypes.Property &&
propMatch.score > minimumMatchScores.property) {
const intrinsic = generateIntrinsic({
propSignature,
figmaPropName: item.name,
figmaPropDef: componentPropertyDefinitions[item.name],
});
if (intrinsic) {
return intrinsic;
}
/**
* Then if no match AND a boolean prop, look for matching variant values, e.g:
*
* disabled: figma.enum('State', {
* Disabled: true,
* })
*/
}
else if (item.type === MatchableNameTypes.VariantValue &&
propSignature === 'false | true' &&
propMatch.score > minimumMatchScores.variantValue) {
return {
kind: intrinsics_1.IntrinsicKind.Enum,
args: {
figmaPropName: item.variantProperty,
valueMapping: {
[item.name]: true,
},
},
};
}
}
return null;
}
/**
* Attempt to generate an intrinsic for a given property name by looking
* for name matches with a minimum threshold
*
* Embeddings are priorized over fuzzy matching if present (they
* may be missing if gated or missing permissions).
*/
for (const [propName, propSignatureWithOptionalModifier] of Object.entries(signature)) {
const propSignature = propSignatureWithOptionalModifier.startsWith('?')
? propSignatureWithOptionalModifier.substring(1)
: propSignatureWithOptionalModifier;
let matches = searcher.search(propName, { returnMatchData: true });
if (useEmbeddings) {
matches = componentMatchResults[propName];
}
if (matches.length === 0) {
continue;
}
for (const match of matches) {
const intrinsic = attemptGetIntrinsicForProp({
propMatch: match,
propSignature,
});
if (intrinsic) {
propMapping[propName] = intrinsic;
break;
}
}
}
return propMapping;
}
//# sourceMappingURL=prop_mapping.js.map