Files
2026-04-17 17:04:44 +03:30

622 lines
16 KiB
JavaScript

//
//#region Imports ...
const XFileTools = require('../tools/x-file.tools');
const XValueTools = require('../tools/x-value.tools');
const XBaseDescriptor = require('./x-base.descriptor');
const XDecoratorDescriptor = require('./x-decorator.descriptor');
//#endregion
/**
* check an object instance is ClassDescriptor or not ...
*
* @param {any} object proposed object to check ...
* @returns
*/
function isDescriptor(object) {
//
if (!object) {
return false;
}
//
let result = false;
result =
object.hasOwnProperty('raw') &&
Array.isArray(object.extends) &&
object.hasOwnProperty('name') &&
Array.isArray(object.implements) &&
object.hasOwnProperty('extends') &&
object.hasOwnProperty('isPublic') &&
object.hasOwnProperty('decorator') &&
object.hasOwnProperty('implements') &&
object.hasOwnProperty('isExported') &&
object.hasOwnProperty('isExtended') &&
object.hasOwnProperty('isAbstract') &&
object.hasOwnProperty('hasDecorator') &&
object.hasOwnProperty('isImplemented');
//
return result;
}
/**
* retrieve base or filled ClassDescriptor object instance ...
*
* @param {any} value a value provider object ...
* @returns an instance of ClassDescriptor object ...
*/
function createDescriptor(value) {
//
const result = {
raw: '',
name: '',
extends: [],
implements: [],
isPublic: true,
isExported: false,
isExtended: false,
isAbstract: false,
hasDecorator: false,
isImplemented: false,
decorator: {
...XDecoratorDescriptor.createDescriptor()
},
};
//
//#region Apply Props ...
if (value) {
//
//#region name ...
if (XValueTools.isValidArg(value.name)) {
result.name = value.name;
}
//#endregion
//
//#region extends ...
if (XValueTools.hasChildArray(value.extends)) {
result.extends = [...value.extends];
}
//#endregion
//
//#region implements ...
if (XValueTools.hasChildArray(value.implements)) {
result.implements = [...value.implements];
}
//#endregion
//
//#region raw ...
if (XValueTools.isValidArg(value.raw)) {
result.raw = value.raw;
}
//#endregion
//
//#region isPublic ...
if (!!value.isPublic) {
result.isPublic = true;
} else if (value.isPublic === false) {
result.isPublic = false;
}
//#endregion
//
//#region isExported ...
if (!!value.isExported) {
result.isExported = true;
}
//#endregion
//
//#region isExtended ...
if (!!value.isExtended) {
result.isExtended = true;
}
//#endregion
//
//#region isAbstract ...
if (!!value.isAbstract) {
result.isAbstract = true;
}
//#endregion
//
//#region hasDecorator ...
if (!!value.hasDecorator) {
result.hasDecorator = true;
}
//#endregion
//
//#region decorator ...
if (!!result.hasDecorator && value.decorator) {
result.decorator = {
...XDecoratorDescriptor.createDescriptor(value.decorator)
};
} else {
result.decorator = undefined;
}
//#endregion
//
//#region isImplemented ...
if (!!value.isImplemented) {
result.isImplemented = true;
}
//#endregion
}
//#endregion
//
return result;
}
/**
* converts an instance of Descriptor to Expression ...
*
* @param descriptor an instance of ClassDescriptor ...
* @returns creator string ...
*/
function toExpression(
descriptor = createDescriptor(),
forceClearContent = false,
) {
//
let result = '';
//
// Validate Arg ...
if (!isDescriptor(descriptor)) {
return result;
}
//
let classContent = '';
let classContentIndexers = XValueTools.findClosedContent('{', '}', descriptor.raw);
if (XValueTools.hasChildArray(classContentIndexers)) {
//
classContent = XValueTools
.clearObjectSurround(classContentIndexers[0].content);
//
if (!!forceClearContent) {
classContent = XBaseDescriptor.clearContent(classContent);
}
}
//
// Handle Class Decorator ...
if (
!!descriptor.hasDecorator &&
XValueTools.isValidArgs(descriptor.decorator.type) &&
XBaseDescriptor.isValidDecoratorType(descriptor.decorator.type)
) {
//
result += XDecoratorDescriptor
.toExpression(descriptor.decorator) +
'\n';
}
//
// Handle Class ...
result +=
//
// Export ...
(
descriptor.isExported ?
XBaseDescriptor.ExpressionKeys.Export +
' ' :
''
) +
//
// Public or Private ...
(
!descriptor.isPublic ?
XBaseDescriptor.ExpressionKeys.Private +
' ' :
''
) +
//
// Abstract ...
(
descriptor.isAbstract ?
XBaseDescriptor.ExpressionKeys.Abstract +
' ' :
''
) +
//
// Class Definition ...
XBaseDescriptor.ExpressionKeys.Class +
' ' +
descriptor.name +
' ' +
//
// Extends ...
(
descriptor.isExtended ?
XBaseDescriptor.ExpressionKeys.Extends +
' ' +
descriptor.extends.join(', ')
+ ' ' :
''
) +
//
// Implements ...
(
descriptor.isImplemented ?
XBaseDescriptor.ExpressionKeys.Implements +
' ' +
descriptor.implements.join(', ') +
' ' :
''
) +
//
// Open Class ...
'{\n' +
//
// Class Content ...
(
XValueTools.isValidArg(classContent) ?
classContent :
''
) +
//
// Close Class ...
'\n}';
//
return result;
}
/**
* extract all exists ClassDescriptors ...
*
* @param {string} file a file path ...
* @returns a collection of ClassDescriptors ...
*/
async function extractDescriptors(file = '') {
//
if (!XFileTools.isFileExists(file)) {
return [];
}
//
const fileContent = await XFileTools.readFile(file);
if (!XValueTools.isValidArg(fileContent)) {
return [];
}
//
let result = [
createDescriptor()
];
result.pop();
//
result = extractDescriptorsFromContent(fileContent);
//
return result;
}
/**
* extract all exists ClassDescriptors ...
*
* @param {string} content a file content ...
* @returns a collection of ClassDescriptors ...
*/
function extractDescriptorsFromContent(content = '') {
//
// Prepare result ...
let result = [
createDescriptor()
];
result.pop();
//
// Validate Args ...
if (
!XValueTools.isValidArg(content) ||
!content.includes(XBaseDescriptor.ExpressionKeys.Class)
) {
return result;
}
//
// Extract all Key Indexes ...
const keyIndexes = XBaseDescriptor.
extractValidIdentifierIndexes(XBaseDescriptor.ExpressionKeys.Class, content);
//
// Validate Keys Exists on Content ...
if (!XValueTools.hasChildArray(keyIndexes)) {
return result;
}
//
// Loop through indexes ...
for (let index of keyIndexes) {
//
// create an empty instance of descriptor ...
let descriptor = {
...createDescriptor()
}
//
let startIndex = index;
let endIndex = content.indexOf('{', startIndex);
let rawIndexContents = XValueTools.findClosedContent('{', '}', content, endIndex);
if (!XValueTools.hasChildArray(rawIndexContents)) {
continue;
}
//
let identifier = content.substring(
index + XBaseDescriptor.ExpressionKeys.Class.length,
endIndex
).trim();
//
//#region HasExtends, Extends, HasImplements, Implements ...
//
const hasExtends = identifier.includes(XBaseDescriptor.ExpressionKeys.Extends);
const hasImplements = identifier.includes(XBaseDescriptor.ExpressionKeys.Implements);
//
descriptor.isExtended = hasExtends;
descriptor.isImplemented = hasImplements;
//
let extendsStartIndex = -1;
let implementsStartIndex = -1;
//
// Extends ...
if (!!hasExtends) {
//
extendsStartIndex = identifier
.indexOf(XBaseDescriptor.ExpressionKeys.Extends) +
XBaseDescriptor.ExpressionKeys.Extends.length;
//
let extendsIdentifier = identifier
.substring(extendsStartIndex)
.trim();
if (extendsIdentifier
.includes(XBaseDescriptor.ExpressionKeys.Implements)) {
//
let extendsEndIndex = extendsIdentifier
.indexOf(XBaseDescriptor.ExpressionKeys.Implements);
extendsIdentifier = extendsIdentifier
.substring(0, extendsEndIndex)
.trim();
}
//
// Continue if it's not Valid ...
if (!XValueTools.isValidArg(extendsIdentifier)) {
continue;
}
//
// Check if extends is Array or not ...
if (extendsIdentifier.includes(',')) {
descriptor.extends.push(
...extendsIdentifier
.split(',')
.map(e => e.trim())
.filter(e => XValueTools.isValidArg(e))
)
} else {
descriptor.extends.push(extendsIdentifier);
}
}
//
// Implements ...
if (!!hasImplements) {
//
implementsStartIndex = identifier
.indexOf(XBaseDescriptor.ExpressionKeys.Implements) +
XBaseDescriptor.ExpressionKeys.Implements.length;
//
let implementsIdentifier = identifier
.substring(implementsStartIndex)
.trim();
if (implementsIdentifier
.includes(XBaseDescriptor.ExpressionKeys.Extends)) {
//
let implementsEndIndex = implementsIdentifier
.indexOf(XBaseDescriptor.ExpressionKeys.Extends);
implementsIdentifier = implementsIdentifier
.substring(0, implementsEndIndex)
.trim();
}
//
// Continue if it's not Valid ...
if (!XValueTools.isValidArg(implementsIdentifier)) {
continue;
}
//
// Check if extends is Array or not ...
if (implementsIdentifier.includes(',')) {
descriptor.implements.push(
...implementsIdentifier
.split(',')
.map(i => i.trim())
.filter(i => XValueTools.isValidArg(i))
)
} else {
descriptor.implements.push(implementsIdentifier);
}
}
//#endregion
//
//#region Class Name ...
let nameEndIndex = Math.min(
...[
extendsStartIndex,
implementsStartIndex,
identifier.length
]
.filter(i => i >= 0)
);
identifier = identifier
.substring(0, nameEndIndex)
.replace(XBaseDescriptor.ExpressionKeys.Extends, '')
.replace(XBaseDescriptor.ExpressionKeys.Implements, '')
.trim();
//
// Continue if class name is not valid ...
if (!XValueTools.isValidArg(identifier)) {
continue;
}
//
// Set Descriptor name ...
descriptor.name = identifier;
//#endregion
//
//#region Exported, Public, Abstract ...
//
const lengthForComeBack =
XBaseDescriptor.ExpressionKeys.Export.length +
XBaseDescriptor.ExpressionKeys.Public.length +
XBaseDescriptor.ExpressionKeys.Private.length +
XBaseDescriptor.ExpressionKeys.Abstract.length;
//
startIndex = index - lengthForComeBack;
endIndex = index;
identifier = content
.substring(startIndex, endIndex)
.trim();
//
// Exported ...
descriptor.isExported = identifier.includes(XBaseDescriptor.ExpressionKeys.Export);
//
// Public ...
descriptor.isPublic = identifier.includes(XBaseDescriptor.ExpressionKeys.Public) ||
!identifier.includes(XBaseDescriptor.ExpressionKeys.Private);
//
// Abstract ...
descriptor.isAbstract = identifier.includes(XBaseDescriptor.ExpressionKeys.Abstract);
//#endregion
//
//#region Raw ...
let classStartIndex = Math.max(
...[
content.indexOf(XBaseDescriptor.ExpressionKeys.Export, startIndex),
content.indexOf(XBaseDescriptor.ExpressionKeys.Public, startIndex),
content.indexOf(XBaseDescriptor.ExpressionKeys.Private, startIndex),
content.indexOf(XBaseDescriptor.ExpressionKeys.Abstract, startIndex),
]
.filter(i => i >= 0 && i <= rawIndexContents[0].start)
);
let classEndIndex = rawIndexContents[0].end + 1;
descriptor.raw = content.substring(classStartIndex, classEndIndex);
//#endregion
//
//#region Decorator ...
//
const forExtractDecoratorContentEndIndex = content.indexOf(descriptor.raw);
let forExtractDecoratorContent = content.substring(0, forExtractDecoratorContentEndIndex);
//
let decoratorIndexes = XValueTools
.findAllIndexes('@(.*)\\({', forExtractDecoratorContent)
.map(index => {
return {
startIndex: index,
decoratorContentIndexer: XValueTools
.findClosedContent('({', '})', forExtractDecoratorContent, index)[0]
}
});
let decoratorIndex = decoratorIndexes
.find(i => i.decoratorContentIndexer.end + 1 === classStartIndex - 1);
if (
!decoratorIndex &&
decoratorIndexes.length === 1 &&
XValueTools.hasChildArray(decoratorIndexes)
) {
//
decoratorIndex = {
//
...decoratorIndexes[0],
//
decoratorContentIndexer: {
//
...decoratorIndexes[0]
.decoratorContentIndexer,
//
end: classStartIndex - 1,
//
content: content.substring(
decoratorIndexes[0]
.decoratorContentIndexer
.start,
classStartIndex - 1
),
}
};
}
//
if (decoratorIndex) {
//
identifier = forExtractDecoratorContent
.substring(
decoratorIndex.startIndex,
decoratorIndex.decoratorContentIndexer.end + 1
);
//
const decorator = XDecoratorDescriptor.extractDescriptorFromContent(identifier);
if (!decorator) {
continue;
}
//
descriptor.hasDecorator = true;
descriptor.decorator = {
...decorator
};
}
//#endregion
//
result.push(descriptor);
}
//
return result;
}
//
// Module Exports ...
module.exports = {
//
isDescriptor,
toExpression,
createDescriptor,
extractDescriptors,
extractDescriptorsFromContent,
}