Files
xSaherElmClient/platforms/electron/modules/models/x-base.descriptor.js
T
2026-04-17 17:04:44 +03:30

1157 lines
28 KiB
JavaScript

//
//#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,
}