views-morph
Version:
Views language morpher
684 lines (588 loc) • 17.7 kB
JavaScript
;
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var cssProperties = _interopDefault(require('css-properties'));
var toCamelCase = _interopDefault(require('to-camel-case'));
var slicedToArray = function () {
function sliceIterator(arr, i) {
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"]) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
return function (arr, i) {
if (Array.isArray(arr)) {
return arr;
} else if (Symbol.iterator in Object(arr)) {
return sliceIterator(arr, i);
} else {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
};
}();
var toArray = function (arr) {
return Array.isArray(arr) ? arr : Array.from(arr);
};
var BASIC = /^(CaptureEmail|CaptureFile|CaptureNumber|CapturePhone|CaptureSecure|CaptureText|CaptureTextArea|G|Horizontal|Image|List|Svg|SvgCircle|SvgEllipse|SvgDefs|SvgGroup|SvgLinearGradient|SvgRadialGradient|SvgLine|SvgPath|SvgPolygon|SvgPolyline|SvgRect|SvgSymbol|SvgText|SvgUse|SvgStop|Text|Vertical|FakeProps)$/i;
var BLOCK = /^([A-Z][a-zA-Z0-9]*)(\s+([A-Z][a-zA-Z0-9]*))?$/;
var BOOL = /^(false|true)$/i;
var CAPTURE = /^(CaptureEmail|CaptureFile|CaptureNumber|CapturePhone|CaptureSecure|CaptureText|CaptureTextArea)$/i;
var CODE_EXPLICIT = /^{.+}$/;
var CODE_IMPLICIT = /props\./;
var CODE_DEFAULT = /^props$/;
var COMMENT = /^#(.+)$/;
var INTERPOLATED_EXPRESSION = /\${.+}/;
var FLOAT = /^[0-9]+\.[0-9]+$/;
var FONTABLE = /^(CaptureEmail|CaptureNumber|CapturePhone|CaptureSecure|CaptureText|CaptureTextArea|Text)$/;
var INT = /^[0-9]+$/;
var NOT_GROUP = /^(Image|FakeProps|Text|Proxy|SvgCircle|SvgEllipse|SvgLine|SvgPath|SvgPolygon|SvgPolyline|SvgRect|SvgText|SvgStop)$/i;
var PROP = /^([a-z][a-zA-Z0-9]*)(\s+(.+))?$/;
var STYLE = new RegExp('^(' + cssProperties.map(toCamelCase).join('|') + '|pointerEvents|clipPath|appRegion|userSelect|hyphens|overflowWrap)$');
var TEMPLATE_LITERAL = /^`.+`$/;
var TRUE = /^true$/i;
var USER_COMMENT = /^##(.*)$/;
var is = function is(thing, line) {
return thing.test(line);
};
var isBasic = function isBasic(line) {
return is(BASIC, line);
};
var isBlock = function isBlock(line) {
return is(BLOCK, line);
};
var isBool = function isBool(line) {
return is(BOOL, line);
};
var isCapture = function isCapture(line) {
return is(CAPTURE, line);
};
var isCode = function isCode(line) {
return is(CODE_EXPLICIT, line) || is(CODE_IMPLICIT, line) || isCodeDefault(line);
};
var isCodeDefault = function isCodeDefault(line) {
return is(CODE_DEFAULT, line);
};
// TODO
var isCodeInvalid = function isCodeInvalid(line) {
return getCodeData(line).find(function (l) {
return (/\. /.test(l) || // props. x
/ \./.test(l) || // props .
/ \[/.test(l) || // props[
/\]/.test(l)
);
} // props]
);
};
var isComment = function isComment(line) {
return is(COMMENT, line);
};
var isEmptyText = function isEmptyText(line) {
return line === '';
};
var isEnd = function isEnd(line) {
return line === '';
};
var isInterpolatedExpression = function isInterpolatedExpression(line) {
return is(INTERPOLATED_EXPRESSION, line);
};
var isFloat = function isFloat(line) {
return is(FLOAT, line);
};
var isFontable = function isFontable(line) {
return is(FONTABLE, line);
};
var isGroup = function isGroup(line) {
return !is(NOT_GROUP, line) && !isCapture(line);
};
var isList = function isList(line) {
return line === 'List';
};
var isInt = function isInt(line) {
return is(INT, line);
};
var isProp = function isProp(line) {
return is(PROP, line);
};
var isTemplateLiteral = function isTemplateLiteral(line) {
return is(TEMPLATE_LITERAL, line);
};
var isStyle = function isStyle(line) {
return is(STYLE, line);
};
var isTrue = function isTrue(line) {
return is(TRUE, line);
};
var isUserComment = function isUserComment(line) {
return is(USER_COMMENT, line);
};
var get = function get(regex, line) {
return line.match(regex);
};
var getBlock = function getBlock(line) {
// eslint-disable-next-line
var _get = get(BLOCK, line),
_get2 = slicedToArray(_get, 4),
_ = _get2[0],
is = _get2[1],
_1 = _get2[2],
block = _get2[3];
return {
block: block || is,
is: block ? is : null
};
};
var getCodeData = function getCodeData(line) {
return line.replace(/^{/, '').replace(/}$/, '').split(' ').filter(function (l) {
return (/[.[]/.test(l)
);
});
};
var getComment = function getComment(line) {
try {
return get(COMMENT, line).slice(1);
} catch (err) {
return '';
}
};
var getMainFont = function getMainFont(line) {
return line ? line.split(',')[0].replace(/['"]/g, '') : '';
};
var getProp = function getProp(line) {
// eslint-disable-next-line
var _get3 = get(PROP, line),
_get4 = slicedToArray(_get3, 4),
_ = _get4[0],
prop = _get4[1],
_1 = _get4[2],
_get4$ = _get4[3],
value = _get4$ === undefined ? '' : _get4$;
if (isCodeDefault(value)) {
value = 'props.' + prop;
}
return [prop, value];
};
var getValue = function getValue(value) {
if (isFloat(value)) {
return parseFloat(value, 10);
} else if (isInt(value)) {
return parseInt(value, 10);
} else if (isEmptyText(value)) {
return '';
} else if (isBool(value)) {
return isTrue(value);
} else if (isInterpolatedExpression(value)) {
return fixBackticks(value);
} else {
return value;
}
};
var fixBackticks = function fixBackticks(value) {
return isTemplateLiteral(value) ? value : '`' + value + '`';
};
var warn = function warn(message, block) {
if (!Array.isArray(block.warnings)) {
block.warnings = [];
}
block.warnings.push(message);
};
var SYSTEM_SCOPES = ['active',
// TODO disabled should be a prop instead I think
'disabled', 'focus', 'hover', 'placeholder', 'print'];
var isSystemScope = function isSystemScope(name) {
return SYSTEM_SCOPES.includes(name);
};
var getLoc = (function (line, scolumn, ecolumn) {
return {
start: {
line: line,
column: scolumn
},
end: {
line: line,
column: ecolumn
}
};
});
var getMeta = (function (value, line, startLine) {
if (!isCode(value)) return [];
return getCodeData(value).map(function (value) {
return {
tag: 'code',
value: value
};
});
});
var isNumber = {
width: true,
maxWidth: true,
minWidth: true,
height: true,
maxHeight: true,
minHeight: true,
margin: true,
marginBottom: true,
marginLeft: true,
marginRight: true,
marginTop: true,
padding: true,
paddingBottom: true,
paddingLeft: true,
paddingRight: true,
paddingTop: true,
bottom: true,
left: true,
right: true,
top: true,
outline: true,
opacity: true,
zIndex: true,
scale: true,
translateX: true,
translateY: true,
borderBottomLeftRadius: true,
borderBottomRightRadius: true,
borderBottomWidth: true,
borderLeftWidth: true,
borderRadius: true,
borderRightWidth: true,
borderTopLeftRadius: true,
borderTopRightRadius: true,
borderTopWidth: true,
borderWidth: true,
fontSize: true,
fontWeight: true,
letterSpacing: true,
lineHeight: true,
wordSpacing: true
};
var isActionable = function isActionable(item) {
return (
// (item.type === 'Horizontal' ||
// item.type === 'Vertical' ||
// /^Capture/.test(item.type)) &&
item.name !== 'onWhen' && /^on[A-Z]/.test(item.name)
);
};
var extractPropsAndItems = function extractPropsAndItems(item) {
var props = [];
var regex = /(props|item)\.([a-zA-Z][a-zA-Z0-9.]*[a-zA-Z0-9]+)(.)?/g;
var matches = regex.exec(item.value);
while (matches !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (matches.index === regex.lastIndex) {
regex.lastIndex++;
}
var isExplicitFunctionCall = matches[3] === '(';
props.push({
isItem: matches[1] === 'item',
path: matches[2],
type: isActionable(item) || isExplicitFunctionCall ? 'function' : isNumber[item.name] ? 'number' : 'string'
});
matches = regex.exec(item.value);
}
return props;
};
var isList$1 = function isList(item) {
return item.type === 'List';
};
var isListFrom = function isListFrom(item) {
return item.name === 'from' && isList$1(item);
};
var getPropTypes = (function (list) {
var flatProps = {};
var listFromPath = void 0;
list.forEach(function (item, index) {
extractPropsAndItems(item).forEach(function (propOrItem) {
if (isListFrom(item)) {
// store the list prop path for later
listFromPath = propOrItem.path;
} else if (isList$1(item) && propOrItem.path.endsWith('.length')) {
// skip array length checks in list
return;
}
if (propOrItem.isItem) {
// if we didn't find a list, don't include item props
if (!listFromPath) return;
propOrItem.path = listFromPath + '.' + propOrItem.path;
}
flatProps[propOrItem.path] = propOrItem;
});
});
var props = {};
Object.keys(flatProps).sort().forEach(function (key) {
var prop = flatProps[key];
if (key.includes('.')) {
var _key$split = key.split('.'),
_key$split2 = toArray(_key$split),
main = _key$split2[0],
rest = _key$split2.slice(1);
var ref = props[main];
if (!ref || typeof ref === 'string') {
ref = props[main] = {
type: prop.isItem ? 'array' : 'object',
shape: {}
};
}
// TODO support any nesting
rest.forEach(function (part) {
ref.shape[part] = prop.type;
});
} else {
props[key] = prop.type;
}
});
return props;
});
var getTags = (function (prop, value) {
var tags = {};
if (isCode(value)) tags.code = isCodeInvalid(value) ? 'invalid' : true;
if (isStyle(prop)) tags.style = true;
return tags;
});
var index = (function (rtext) {
var skipComments = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
// convert crlf to lf
var text = rtext.replace(/\r\n/g, '\n');
var fonts = [];
var lines = text.split('\n').map(function (line) {
return line.trim();
});
var props = [];
var stack = [];
var views = [];
var lastCapture = void 0;
var getChildrenProxyMap = function getChildrenProxyMap(block) {
var childrenProxyMap = {};
block.children.forEach(function (child, i) {
var maybeName = child.is || child.name;
var name = maybeName;
var next = 1;
while (name in childrenProxyMap) {
name = '' + maybeName + next;
next++;
}
childrenProxyMap[name] = i;
});
return Object.keys(childrenProxyMap).length === 0 ? null : childrenProxyMap;
};
var lookForFonts = function lookForFonts(block) {
if (block.properties && (isFontable(block.name) || !block.isBasic)) {
var fontFamilyProp = block.properties.find(function (p) {
return p.name === 'fontFamily';
});
if (fontFamilyProp) {
var fontFamily = getMainFont(fontFamilyProp.value);
var fontWeightProp = block.properties.find(function (p) {
return p.name === 'fontWeight';
});
var fontStyleProp = block.properties.find(function (p) {
return p.name === 'fontStyle';
});
var fontWeight = fontWeightProp ? fontWeightProp.value.toString() : '400';
var fontStyle = fontStyleProp ? fontStyleProp.value.toString() : 'normal';
if (!fonts.find(function (font) {
return font.family === fontFamily && font.weight === fontWeight && font.style === fontStyle;
})) {
fonts.push({
id: fontFamily + '-' + fontWeight + (fontStyle === 'italic' ? '-italic' : ''),
family: fontFamily,
weight: fontWeight,
style: fontStyle
});
}
}
}
};
var end = function end(block, endLine) {
block.loc.end = {
line: endLine,
column: Math.max(0, lines[endLine].length - 1)
};
if (block.isGroup && !block.isBasic) {
block.childrenProxyMap = getChildrenProxyMap(block);
}
if (!block.properties) {
block.properties = [];
}
if (stack.length === 0) {
// if we're the last block on the stack, then this is the view!
views.push(block);
return true;
}
return false;
};
var parseBlock = function parseBlock(line, i) {
var _getBlock = getBlock(line),
name = _getBlock.block,
is$$1 = _getBlock.is;
var shouldPushToStack = false;
var block = {
type: 'Block',
name: name,
isBasic: isBasic(name),
isGroup: false,
loc: getLoc(i, 0),
properties: [],
scopes: []
};
if (is$$1) {
block.is = is$$1;
if (isCapture(name)) {
if (lastCapture) {
lastCapture.captureNext = is$$1;
}
lastCapture = block;
}
}
var last = stack[stack.length - 1];
if (last) {
if (last.isGroup) {
if (last.isList) {
if (block.isBasic) {
warn('A basic block can\'t be inside a List.\nPut 1 empty line before', block);
shouldPushToStack = true;
} else if (last.children.length > 0) {
warn('A List can only have one view inside. This block is outside of it.\nPut 1 empty line before.', block);
shouldPushToStack = true;
} else {
last.children.push(block);
}
} else {
last.children.push(block);
}
} else {
// the block is inside a block that isn't a group
end(stack.pop(), i);
if (views[0].isGroup) {
warn(lines[i - 1] === '' ? 'put 1 empty line before' : 'put 2 empty lines before', block);
} else {
warn('add Vertical at the top', block);
}
shouldPushToStack = true;
}
} else if (views.length > 0) {
// the block is outside the top level block
var newLinesBeforePreviousBlock = 1;
while (isEnd(lines[i - newLinesBeforePreviousBlock])) {
newLinesBeforePreviousBlock++;
}
var help = [];
if (!views[0].isGroup) {
help.push('add Vertical at the top');
}
if (newLinesBeforePreviousBlock > 2) {
var linesToRemove = newLinesBeforePreviousBlock - 2;
help.push('remove ' + linesToRemove + ' empty line' + (linesToRemove > 1 ? 's' : '') + ' before');
}
warn(help.join(', '), block);
}
if (isGroup(name)) {
block.isGroup = true;
block.isList = isList(name);
block.children = [];
shouldPushToStack = true;
}
if (shouldPushToStack || stack.length === 0) {
stack.push(block);
}
parseProps(i, block);
lookForFonts(block);
};
var parseProps = function parseProps(i, block) {
var endOfBlockIndex = i;
while (endOfBlockIndex < lines.length - 1 && !isBlock(lines[endOfBlockIndex + 1])) {
endOfBlockIndex++;
}
var properties = [];
var scopes = [];
var scope = void 0;
var inScope = false;
for (var j = i; j <= endOfBlockIndex; j++) {
var line = lines[j];
var propNode = null;
if (isProp(line)) {
var _getProp = getProp(line),
_getProp2 = slicedToArray(_getProp, 2),
name = _getProp2[0],
value = _getProp2[1];
var tags = getTags(name, value);
if (tags.code) {
props.push({ type: block.name, name: name, value: value });
}
if (tags.style && tags.code) {
block.maybeAnimated = true;
}
if (name === 'when') {
tags.scope = value;
inScope = value;
scope = { isSystem: isSystemScope(value), value: value, properties: [] };
scopes.push(scope);
}
propNode = {
type: 'Property',
loc: getLoc(j, line.indexOf(name), line.length - 1),
name: name,
tags: tags,
meta: getMeta(value, line, j),
value: getValue(value)
};
} else if (isComment(line) && !skipComments) {
var _getComment = getComment(line),
_getComment2 = slicedToArray(_getComment, 1),
_value = _getComment2[0];
var userComment = isUserComment(line);
if (userComment) {
_value = getComment(_value);
}
propNode = {
type: 'Property',
loc: getLoc(j, 0, line.length - 1),
value: _value,
tags: { comment: true, userComment: userComment }
};
}
if (propNode) {
block.loc.end = propNode.loc.end;
if (inScope) {
scope.properties.push(propNode);
} else {
properties.push(propNode);
}
}
}
block.properties = properties;
block.scopes = scopes;
};
lines.forEach(function (line, i) {
if (isBlock(line)) {
parseBlock(line, i);
} else if (isEnd(line) && stack.length > 0) {
end(stack.pop(), i);
}
});
if (stack.length > 0) {
while (!end(stack.pop(), lines.length - 1)) {}
}
return {
fonts: fonts,
props: getPropTypes(props),
views: views
};
});
module.exports = index;
//# sourceMappingURL=parse.js.map