// //#region Imports ... const XValueTools = require('../tools/x-value.tools'); const XTypeDectorTools = require('../tools/x-type-detector.tools'); //#endregion // //#region Constants ... /** * base usefull Expression keys ... */ const ExpressionKeys = { From: 'from', Enum: 'enum', Type: 'type', Class: 'class', Const: 'const', Export: 'export', Import: 'import', Public: 'public', Exports: 'exports', Private: 'private', Extends: 'extends', Abstract: 'abstract', Interface: 'interface', Implements: 'implements', IGNORED_KEY: '|$IGNORED_KEY$|', } /** * all available content types for raw contents ... */ const ContentTypes = { Array: 'array', Object: 'object', String: 'string', Unknown: 'unknown', NonString: 'non_string', NonStringSelf: 'non_string_self', NonStringHasClosure: 'non_string_has_closure', NonStringHasProperty: 'non_string_has_property', } /** * ng files decorator types ... */ const DecoratorTypes = { Module: 'module', Unknown: 'unknown', Component: 'component', Directive: 'directive', Injectable: 'injectable' } /** * all available decorator parts ... */ const DecoratorParts = { Styles: 'styles', Imports: 'imports', Exports: 'exports', Selector: 'selector', Template: 'template', Providers: 'providers', StyleUrls: 'styleUrls', Bootstrap: 'bootstrap', ProvidedIn: 'providedIn', TemplateUrl: 'templateUrl', Declarations: 'declarations', EntryComponents: 'entryComponents', ChangeDetection: 'changeDetection', } const ModuleDecoratorValidParts = [ DecoratorParts.Imports, DecoratorParts.Exports, DecoratorParts.Bootstrap, DecoratorParts.Providers, DecoratorParts.Declarations, DecoratorParts.EntryComponents, ]; const ComponentDecoratorValidParts = [ DecoratorParts.Selector, DecoratorParts.Template, DecoratorParts.TemplateUrl, DecoratorParts.Styles, DecoratorParts.StyleUrls, DecoratorParts.ChangeDetection, ]; const DirectiveDecoratorValidParts = [ DecoratorParts.Selector ]; const InjectableDecoratorValidParts = [ DecoratorParts.ProvidedIn ]; /** * all available types for decorator parts ... */ const DecoratorPartTypes = { Array: 'array', String: 'string' } //#endregion // //#region Content Type ... /** * check a proposed type is valid content type or not ... * * @param {string} type the proposed type to check ... * @returns */ function isValidContentType(type = '') { // // Validate Args ... if (!XValueTools.isValidArg(type)) { return false; } // // Normalize Arg ... type = type.trim(); // let result = Object.values(ContentTypes).includes(type); return result; } /** * retrieve a content type ... * * @param {string} content a content which going to check ... * @returns a member of ContentTypes const ... */ function getContentType(content = '') { // // Validate Args ... if (!XValueTools.isValidArg(content)) { return ContentTypes.Unknown; } // if (XValueTools.isSurroundedObject(content)) { return ContentTypes.Object; } else if (XValueTools.isSurroundedArray(content)) { return ContentTypes.Array; } else if (XValueTools.isSurroundedString(content)) { return ContentTypes.String; } else if ( content.includes(':') || content.includes('=') ) { // if ( content.includes('{') && content.includes('}') ) { return ContentTypes.NonStringHasClosure; } else if ( content.includes('[') && content.includes(']') ) { return ContentTypes.NonStringHasClosure; } else { // const colons = XValueTools.findAllIndexes(':', content); if ( !XValueTools.hasChildArray(colons) || colons.length === 1 ) { return ContentTypes.NonStringSelf } // return ContentTypes.NonStringHasProperty; } } else { return ContentTypes.NonString; } } //#endregion // //#region Decorator ... /** * determines a property name is valid in Decorator Object or not ... * * @param {string} part decorator object property name ... * @returns is it valid or not ... */ function isValidDecoratorPart(part = DecoratorParts.Imports) { // // Validate Args ... if (!XValueTools.isValidArg(part)) { return false; } // result = Object.values(DecoratorParts).includes(part); return result; } /** * determines a proposed value is valid decorator types or not ... * * @param {string} type a child of DecoratorTypes ... * @returns proposed value is valid type or not ... */ function isValidDecoratorType(type = DecoratorTypes.Unknown) { // // Validate Args ... if ( !XValueTools.isValidArg(type) || type === DecoratorTypes.Unknown ) { return false; } // result = Object.values(DecoratorTypes).includes(type); return result; } /** * determines a part of decorator is valid for specified decorator type or not ... * * @param {string} part specified proposed part of DecoratorDescriptor ... * @param {string} type specified proposed DecoratorType ... * @returns part is valid for type or not ... */ function isValidDecoratorPartForType( part = DecoratorParts.Imports, type = DecoratorTypes.Unknown ) { // // Validate Args ... if ( !isValidDecoratorType(type) || !isValidDecoratorPart(part) || !XValueTools.isValidArg(part) || !XValueTools.isValidArg(type) ) { return false; } // let result = false; switch (type) { // case DecoratorTypes.Module: result = ModuleDecoratorValidParts .includes(part); break; // case DecoratorTypes.Component: result = ComponentDecoratorValidParts .includes(part); break; // case DecoratorTypes.Directive: result = DirectiveDecoratorValidParts .includes(part); break; // case DecoratorTypes.Injectable: result = InjectableDecoratorValidParts .includes(part); break; } // return result; } /** * retrieve decorator specific part type ... * * @param {string} part Decorator Object property name ... * @returns decorator part type ... */ function getDecoratorPartType(part) { // // Validate Args ... if ( !isValidDecoratorPart(part) && !XValueTools.isValidArg(part) ) { return undefined; } // let result = ''; // switch (part) { // // Array Parts ... case DecoratorParts.Styles: case DecoratorParts.Imports: case DecoratorParts.Exports: case DecoratorParts.Providers: case DecoratorParts.StyleUrls: case DecoratorParts.Bootstrap: case DecoratorParts.Declarations: case DecoratorParts.EntryComponents: result = DecoratorPartTypes.Array; break; // // String Parts ... case DecoratorParts.Selector: case DecoratorParts.Template: case DecoratorParts.ProvidedIn: case DecoratorParts.TemplateUrl: case DecoratorParts.ChangeDetection: result = DecoratorPartTypes.String; break; } // if (!XValueTools.isValidArg(result)) { return undefined; } // return result; } /** * determines an specific part of decorator is in array type or not ... * * @param {string} part Decorator Object property name ... * @returns */ function isDecoratorPartArray(part) { // // Validate Args ... if ( !isValidDecoratorPart(part) || !XValueTools.isValidArg(part) ) { return false; } // // Retrieve Decorator Part Type ... const type = getDecoratorPartType(part); if (!XValueTools.isValidArg(type)) { return false; } // const result = type === DecoratorPartTypes.Array; return result; } /** * determines an specific part of decorator is in string type or not ... * * @param {string} part Decorator Object property name ... * @returns */ function isDecoratorPartString(part) { // // Validate Args ... if ( !isValidDecoratorPart(part) || !XValueTools.isValidArg(part) ) { return false; } // // Retrieve Decorator Part Type ... const type = getDecoratorPartType(part); if (!XValueTools.isValidArg(type)) { return false; } // const result = type === DecoratorPartTypes.String; return result; } /** * extract specified decorator part values from given content ... * * @param {string} part the part which reuire to extract from given text, which usually must be DecoratorParts value ... * @param {string} content given content to tokenize ... * @param {boolean} isArray determines the given token value is array or not ... * @returns parsed token's value(s) ... */ function extractDecoratorPart( part = '', content = '', ) { // // Validate Args ... if ( !isValidDecoratorPart(part) || !XValueTools.isValidArg(part) || !XValueTools.isValidArg(content) ) { return undefined; } // const partType = getDecoratorPartType(part); const isArray = isDecoratorPartArray(part); // let listSeparator = ','; let end = isArray ? ']' : '\''; let start = isArray ? '[' : '\''; // // find token index ... let partIndex = content.indexOf(part); if (partIndex === -1) { return undefined; } // content = content.substring(partIndex); partIndex = content.indexOf(part); if (part === DecoratorParts.Template) { // const tIndex = content.indexOf(DecoratorParts.TemplateUrl); if (partIndex === tIndex) { return undefined; } } // // find start and end index ... let startIndex = content.indexOf(start, partIndex) + 1; let endIndex = content.indexOf(end, startIndex) - 1; // // Try to fix string interpolation ... if (!isArray && (startIndex < 0 || endIndex < 0)) { startIndex = content.indexOf('`', partIndex + part.length); endIndex = content.indexOf('`', startIndex + 1); } // // Try to fix Object ... if (!isArray && (startIndex < 0 || endIndex < 0)) { startIndex = part.length; endIndex = content.indexOf('`', startIndex + 1) > 0 ? content.indexOf('`', startIndex + 1) : content.indexOf(',', startIndex + 1) > 0 ? content.indexOf(',', startIndex + 1) : content.indexOf('}', startIndex + 1) > 0 ? content.indexOf('}', startIndex + 1) : -1; } if (startIndex === -1 || endIndex === -1) { return undefined; } // // define regExps for clearing content ... const regExp = new RegExp( ' |\n|', 'g' ); // // Extract required content and remove all non required contents from it ... let resultContent = content .substr(startIndex, (endIndex - startIndex) + 1); if (isArray) { resultContent = resultContent.replace(regExp, '').trim(); } else { resultContent = resultContent .replace(',', '') .replace('}', '') .replace(':', '').trim(); } if (!XValueTools.isValidArg(resultContent)) { return isArray ? [] : ''; } // // check content is a list or not ... const isMultipleDeclaration = resultContent.includes(listSeparator); // // prepare result ... const outOfClosedIndexes = XValueTools.findAllIndexesOutOfCloseds(listSeparator, resultContent); const outOfClosedParts = XValueTools.sliceContent(outOfClosedIndexes, resultContent); let result = isMultipleDeclaration ? isArray ? [...outOfClosedParts] : resultContent : isArray ? [resultContent] : resultContent; result = isArray ? result.filter(r => r.length > 0) : result.trim(); // return result; } //#endregion // //#region Parsers ... /** * parse given content and extracts it's XKeyValueDescriptor childs ... * * @param {string} content a proposed content to parse ... * @param {boolean} isInterface force parser to parse object content as an interface content, default is false ... * @returns parsed collection of XKeyValueDescriptor object instance ... */ function parseContent( content = '', isInterface = false ) { // // Validate Args ... if (!XValueTools.isValidArg(content)) { return undefined; } // // Define Result Structure ... let result = [{ key: '', value: undefined, type: ContentTypes.Unknown, }]; result.pop(); // const contentType = getContentType(content); if (isValidContentType(contentType)) { result.type = contentType; } // switch (contentType) { // // parse array contents ... case ContentTypes.Array: result = parseProperties( content, isInterface ); break; // //parse object content ... case ContentTypes.Object: result = parseProperties( content, isInterface ); break; // // add content it self ... case ContentTypes.String: result.push({ type: contentType, value: parseString(content), key: ExpressionKeys.IGNORED_KEY, }); break; // // add content it self without anything ... case ContentTypes.NonString: result.push({ value: content, type: contentType, key: ExpressionKeys.IGNORED_KEY, }); break; // // parse just one property ... case ContentTypes.NonStringSelf: break; // // parse properties more than one ... case ContentTypes.NonStringHasProperty: break; // // parse key and values ... case ContentTypes.NonStringHasClosure: break; // case ContentTypes.Unknown: default: break; } // return result; } /** * parse properties of given content and extract it's values ... * * @param {string} content the content which going to parse ... * @param {boolean} isInterface determines the props is for interface or not ... * @returns parse props as a collection of key/value/type array ... */ function parseProperties( content = '', isInterface = false ) { // let result = [{ key: '', value: undefined, type: ContentTypes.Unknown, }]; result.pop(); // // Validate Args ... if (!XValueTools.isValidArg(content)) { return result; } // // Clear Surrounded Chars ... if (XValueTools.isSurroundedArray(content)) { content = XValueTools.clearArraySurround(content); } else if (XValueTools.isSurroundedObject(content)) { content = XValueTools.clearObjectSurround(content); } // let individualColonIndexes = []; if (!!isInterface) { individualColonIndexes = XValueTools.findAllIndexesOutOfCloseds(';', content); } else { individualColonIndexes = XValueTools.findAllIndexesOutOfCloseds(',', content); } // let parts = []; if (XValueTools.hasChildArray(individualColonIndexes)) { parts = XValueTools .sliceContent(individualColonIndexes, content) .filter(r => XValueTools.isValidArg(r)); } else { parts.push(content); } // for (const part of parts) { // const partType = getContentType(part); // const individualSemicolonIndexes = XValueTools .findAllIndexesOutOfCloseds( [':', '='], part ); // if (!XValueTools.hasChildArray(individualSemicolonIndexes)) { // const parsedContent = parseContent( part, isInterface ) || []; if (partType === ContentTypes.Object) { result.push({ type: partType, value: parsedContent, key: ExpressionKeys.IGNORED_KEY, }); } else { result.push(...parsedContent); } continue; } // const sepIndex = individualSemicolonIndexes[0]; const keyVal = XValueTools.sliceContent([sepIndex], part); if ( keyVal.length !== 2 || !XValueTools.hasChildArray(keyVal) ) { continue; } // let parsedValue = parseContent( keyVal[1], isInterface ); let type = parsedValue.type || getContentType(keyVal[1]); if ( parsedValue.length === 1 && XValueTools.hasChildArray(parsedValue) && parsedValue[0].key === ExpressionKeys.IGNORED_KEY && !Object.values([ContentTypes.Array, ContentTypes.Object]).includes(parsedValue[0].type) ) { parsedValue = parsedValue[0].value; } // result.push({ type, key: keyVal[0], value: parsedValue }); } // return result; } /** * parse a content as string ... * * @param {string} content the content which going to parsed ... * @returns parsed string content ... */ function parseString(content = '') { // // Validate Args ... if ( !XValueTools.isValidArg(content) || !XValueTools.isSurroundedString(content) ) { return ''; } // if ( content.startsWith('\'') && ( XValueTools.endsWidth('\'', content) || XValueTools.endsWidth('\',', content) || XValueTools.endsWidth('\';', content) || XValueTools.endsWidth('\'\n', content) ) ) { // const removeFromLastLength = XValueTools.endsWidth('\'', content) ? 1 : XValueTools.endsWidth('\',', content) || XValueTools.endsWidth('\';', content) || XValueTools.endsWidth('\'\n', content) ? 2 : 0; // content = content.substring(1, content.length - removeFromLastLength); content = XValueTools.surroundBy('\"', content); } else if ( content.startsWith('`') && ( XValueTools.endsWidth('\`', content) || XValueTools.endsWidth('\`,', content) || XValueTools.endsWidth('\`;', content) || XValueTools.endsWidth('\`\n', content) ) ) { content = XValueTools.surroundBy('\"', content); } // return content; } //#endregion // //#region Expressions ... function toExpression(content) { // let result = ''; const isKeyValueType = XValueTools.isKeyValueType(content); if (!isKeyValueType) { // const isContentArray = XTypeDectorTools.isArray(content); if (isContentArray) { // const contentResults = ['']; contentResults.pop(); // for (const contentChild of content) { // const contentResult = toExpression(contentChild); contentResults.push(contentResult); } // result += '[\n' + contentResults.join(',\n') + '\n]'; } else { result += content; } } else { // // Retrieve Type ... const keyValType = content.type || ContentTypes.Unknown; // let contentResult = ''; switch (keyValType) { // // String ... case ContentTypes.String: // let surroundSymbol = content.value.charAt(0); contentResult = XValueTools.clearSurround(surroundSymbol, content.value); // surroundSymbol = contentResult.charAt(0) || ''; if (surroundSymbol !== '\'' && surroundSymbol !== '`') { contentResult = XValueTools.surroundBy('\'', contentResult); } break; // // NonString ... case ContentTypes.NonString: contentResult = content.value; break; // // Array ... case ContentTypes.Array: // const arrayResult = ['']; arrayResult.pop(); // if (XTypeDectorTools.isString(content.value)) { // const normalContentValue = XValueTools.toNormalArray(content.value); content.value = [ ...normalContentValue ] } for (const arrayChild of content.value) { // const arrayChildExp = toExpression(arrayChild); arrayResult.push(arrayChildExp); } // // TODO: create a way to handle array or objects childs for join ... let arrayResultContent = ''; for (let i = 0; i < arrayResult.length; i++) { // const nextResult = arrayResult[i + 1]; arrayResultContent += arrayResult[i] + ( i < arrayResult.length - 1 ? (XValueTools.isSurroundedArray(nextResult) || XValueTools.isSurroundedObject(nextResult)) ? ',\n' : ', ' : '' ); } // contentResult = '[\n' + arrayResultContent + '\n]'; break; // // Object ... case ContentTypes.Object: // let objectResult = '{\n'; // for (let i = 0; i < content.value.length; i++) { // const arrayChild = content.value[i]; const arrayChildExp = toExpression(arrayChild); objectResult += ( arrayChild.key !== ExpressionKeys.IGNORED_KEY ? arrayChild.key + ': ' : '' ) + arrayChildExp + ( i === content.value.length - 1 ? '' : ',\n' ); } // objectResult += '\n}'; // contentResult = objectResult; break; } // result += contentResult; } // return result; } //#endregion // //#region Extractors ... /** * extract all occurance indexes of an identifier inside a content ... * * @param {string} identifier the value which going to search ... * @param {string} content the content is going to searched for value ... * @returns a collection of occurance indexes ... */ function extractIdentifierIndexes( identifier = '', content = '') { // // Prepare Result ... let result = [0]; result.pop(); // // Validate Args ... if ( !XValueTools.isValidArg(content) || !XValueTools.isValidArg(identifier) ) { return result; } // // Extract Identifiers ... result = XValueTools. findAllIndexes( identifier, content ); // return result; } /** * check an index of an identifier inside a content is same as identifier or not ... * * @param {string} identifier the value which going to search ... * @param {number} index the item proposed index on content ... * @param {string} content the content is going to searched for value ... * @returns is it match identifier or not ... */ function validateIdentifierInContent( identifier = '', index = -1, content = '' ) { // // Prepare Result ... let result = false; // // Validate Args ... if ( !XValueTools.isValidArg(content) || !XValueTools.isValidArg(identifier) ) { return false; } // // Normalize Index ... index = index < 0 ? 0 : index > content.length ? content.length : index; // // // Check Type after befre content ... let startIndex = index - 1; let endIndex = index + identifier.length + 1; const tIdentifier = content.substring(startIndex, endIndex).trim(); result = tIdentifier === identifier; // return result; } /** * extract all occurance indexes of an identifier inside a content and Validate them ... * * @param {string} identifier the value which going to search ... * @param {string} content the content is going to searched for value ... * @returns a collection of occurance indexes ... */ function extractValidIdentifierIndexes( identifier = '', content = '' ) { // // Prepare Result ... let result = [0]; result.pop(); // // Validate Args ... if ( !XValueTools.isValidArg(content) || !XValueTools.isValidArg(identifier) ) { return result; } // // Extract Identifier Indexes ... result = extractIdentifierIndexes( identifier, content ); // // Filter result to get Valid Identifiers ... result = result.filter(i => validateIdentifierInContent ( identifier, i, content ) ); // return result; } /** * determines a content expression starter is exported or not ... * * @param {number} index start index for checking export ... * @param {string} content the content which reuired to check ... * @returns is exported or not ... */ function checkIsExported( index = 0, content = '' ) { // // Prepare Result ... let result = false; // // Validate Args ... if (!XValueTools.isValidArg(content)) { return result; } // // Normalize Index ... index = index < 0 ? 0 : index > content.length ? content.length : index; // const startIndex = index - ExpressionKeys.Export.length - 1; const endIndex = startIndex + ExpressionKeys.Export.length; const identifier = content.substring(startIndex, endIndex).trim(); result = identifier === ExpressionKeys.Export; // return result; } //#endregion // //#region Content Cleaners ... /** * clear all comments, regions symbols and non required signs from a content ... * * @param {string} content the content which used ... * @returns cleared content ... */ function clearContent(content = '') { // // Validate Args ... if (!XValueTools.isValidArg(content)) { return ''; } // // Check Content contains Line Return or not ... if (content.includes('\n')) { content = content.split('\n') .map(cc => cc.trim()) .filter(cc => XValueTools.isValidArg(cc) && !cc.startsWith('//') && !cc.startsWith('#')) .join('\n'); } // return content; } //#endregion // // Module Exports ... module.exports = { // ContentTypes, ExpressionKeys, DecoratorTypes, DecoratorParts, DecoratorPartTypes, ModuleDecoratorValidParts, ComponentDecoratorValidParts, DirectiveDecoratorValidParts, InjectableDecoratorValidParts, // getContentType, isValidContentType, // extractDecoratorPart, isValidDecoratorPart, isValidDecoratorType, getDecoratorPartType, isDecoratorPartArray, isDecoratorPartString, isValidDecoratorPartForType, // parseContent, clearContent, checkIsExported, extractIdentifierIndexes, validateIdentifierInContent, extractValidIdentifierIndexes, // toExpression, }