ui5plugin-parser
Version:
402 lines (401 loc) • 17.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CustomTSClass = void 0;
const Hjson = require("hjson");
const path = require("path");
const ts_morph_1 = require("ts-morph");
const ParserPool_1 = require("../../../../parser/pool/ParserPool");
const AbstractCustomClass_1 = require("../AbstractCustomClass");
class CustomTSClass extends AbstractCustomClass_1.AbstractCustomClass {
_fillIsAbstract() {
throw new Error("Method not implemented.");
}
_getUIDefine() {
throw new Error("Method not implemented.");
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
_fillParentClassName() { }
constructor(classDeclaration, parser, typeChecker) {
const sourceFile = classDeclaration.getSourceFile();
const className = parser.fileReader.getClassNameFromPath(sourceFile.compilerNode.fileName);
super(className ?? "", parser);
this.parentClassNameDotNotation = "";
this.methods = [];
this.fields = [];
this.constructors = [];
this.UIDefine = [];
this.fsPath = path.resolve(sourceFile.compilerNode.fileName);
this.classText = sourceFile.getFullText();
this._sourceFile = sourceFile;
this.typeChecker = typeChecker;
this.node = classDeclaration;
const heritageClause = classDeclaration.compilerNode.heritageClauses?.find(heritage => {
return heritage.token == ts_morph_1.ts.SyntaxKind.ExtendsKeyword;
});
if (heritageClause) {
const parentName = heritageClause.types[0].expression.getText();
const parentImportDeclaration = sourceFile.getImportDeclaration(declaration => {
return declaration.getImportClause()?.getDefaultImport()?.getText() === parentName;
});
const parentModule = parentImportDeclaration?.getModuleSpecifierValue();
this.parentClassNameDotNotation =
(parentModule && this._generateClassNameDotNotationFor(parentModule)) ?? "";
}
this._fillUI5Metadata(undefined, false);
this._fillClassJSDoc();
}
_fillClassJSDoc() {
const classJsDocs = this.node.getJsDocs();
this._fillDefaultModelClassName(classJsDocs);
this._fillClassDescription(classJsDocs);
}
_fillDefaultModelClassName(classJsDocs) {
const ui5modelDoc = classJsDocs.find(jsDoc => jsDoc.getTags().some(tag => tag.getTagName() === "ui5model"));
const tag = ui5modelDoc?.getTags().find(tag => tag.getTagName() === "ui5model");
const comment = tag?.getComment();
if (comment && typeof comment === "string") {
const trimmedComment = comment.trim();
this.defaultModelClassName = trimmedComment.substring(1, trimmedComment.length - 1);
}
}
_fillClassDescription(classJsDocs) {
const descriptionDoc = classJsDocs.find(jsDoc => jsDoc.getTags().some(tag => tag.getTagName() === "description"));
const description = descriptionDoc?.getDescription();
if (description) {
this.description = description;
}
}
loadTypes() {
this._fillUI5Metadata(undefined, true);
}
_fillUIDefine() {
const importStatements = this._sourceFile.getImportDeclarations();
this.UIDefine = importStatements.map(importStatement => {
const modulePath = importStatement.getModuleSpecifier().getLiteralText();
return {
path: modulePath,
className: modulePath.split("/").pop() ?? "",
classNameDotNotation: this._generateClassNameDotNotationFor(modulePath),
start: importStatement.getStart(),
end: importStatement.getEnd(),
acornNode: importStatement
};
});
}
_generateClassNameDotNotationFor(moduleNameSlash) {
let className = moduleNameSlash.replace(/\//g, ".");
if (moduleNameSlash?.startsWith(".")) {
const manifest = ParserPool_1.default.getManifestForClass(this.className);
if (manifest && this.fsPath) {
const normalizedManifestPath = path.normalize(manifest.fsPath);
const importClassPath = path.resolve(path.dirname(this.fsPath), moduleNameSlash);
const relativeToManifest = path.relative(normalizedManifestPath, importClassPath);
const pathRelativeToManifestDotNotation = relativeToManifest.split(path.sep).join(".");
className = `${manifest.componentName}.${pathRelativeToManifestDotNotation}`;
}
}
if (className.endsWith(".controller")) {
className = className.substring(0, className.length - ".controller".length);
}
return className;
}
_guessTypeFromUIDefine(typeName) {
if (typeName) {
const UIDefine = this.UIDefine.find(define => define.className === typeName);
return UIDefine?.classNameDotNotation;
}
}
_fillFields(metadata, fillTypes = false) {
const fields = this.node.getProperties();
const UIFields = fields.map(field => {
const jsDocs = field.getJsDocs();
const ui5IgnoreDoc = jsDocs.some(jsDoc => jsDoc.getTags().some(tag => tag.getTagName() === "ui5ignore"));
const positionStart = this._sourceFile.getLineAndColumnAtPos(field.getNameNode().getStart());
const positionEnd = this._sourceFile.getLineAndColumnAtPos(field.getNameNode().getEnd());
const typeNode = field.getTypeNode();
const typeReference = typeNode?.asKind(ts_morph_1.ts.SyntaxKind.TypeReference);
const typeQuery = typeNode?.asKind(ts_morph_1.ts.SyntaxKind.TypeQuery);
const typeName = typeReference?.getText() ?? typeQuery?.getExprName().getText();
let type = fillTypes ? field.getType().getText() : this._guessTypeFromUIDefine(typeName);
if (!fillTypes && !type) {
type = this._guessTypeFromInitialization(field);
}
type = this._modifyType(type ?? "any");
return {
ui5ignored: ui5IgnoreDoc,
owner: this.className,
static: field.isStatic(),
abstract: field.isAbstract(),
type: type,
visibility: field
.getModifiers()
.find(modifier => [
ts_morph_1.ts.SyntaxKind.ProtectedKeyword,
ts_morph_1.ts.SyntaxKind.PrivateKeyword,
ts_morph_1.ts.SyntaxKind.PublicKeyword
].includes(modifier.getKind()))
?.getText() ?? "public",
name: field.getName(),
deprecated: jsDocs.some(jsDoc => ts_morph_1.ts.isJSDocDeprecatedTag(jsDoc.compilerNode)),
description: "",
isEventHandler: false,
node: field,
mentionedInTheXMLDocument: false,
loc: {
start: {
line: positionStart.line,
column: positionStart.column - 1
},
end: {
line: positionEnd.line,
column: positionEnd.column - 1
}
}
};
});
return UIFields;
}
_guessTypeFromInitialization(field) {
let type;
const initializer = field.getInitializer();
const newExpression = initializer?.asKind(ts_morph_1.ts.SyntaxKind.NewExpression);
const identifier = initializer?.asKind(ts_morph_1.ts.SyntaxKind.Identifier);
if (newExpression) {
type = newExpression.getExpression().getText();
}
else if (identifier) {
type = identifier.getText();
}
type = this._guessTypeFromUIDefine(type);
return type;
}
_fillMethods(metadata, fillTypes = false) {
const methods = this.node.getMethods();
const UIMethods = methods.map(method => {
const jsDocs = method.getJsDocs();
const ui5IgnoreDoc = jsDocs.some(jsDoc => jsDoc.getTags().some(tag => tag.getTagName() === "ui5ignore"));
const positionStart = this._sourceFile.getLineAndColumnAtPos(method.getNameNode().getStart());
const positionEnd = this._sourceFile.getLineAndColumnAtPos(method.getNameNode().getEnd());
let returnType = fillTypes ? method.getReturnType().getText() : "void";
returnType = this._modifyType(returnType);
return {
ui5ignored: !!ui5IgnoreDoc,
owner: this.className,
static: method.isStatic(),
abstract: method.isAbstract(),
returnType: returnType ?? "void",
visibility: method
.getModifiers()
.find(modifier => [
ts_morph_1.ts.SyntaxKind.ProtectedKeyword,
ts_morph_1.ts.SyntaxKind.PrivateKeyword,
ts_morph_1.ts.SyntaxKind.PublicKeyword
].includes(modifier.getKind()))
?.getText() ?? "public",
params: method.getParameters().map(param => {
return {
name: param.getName(),
type: fillTypes ? this._modifyType(param.getType().getText()) ?? "any" : "any",
description: "",
isOptional: false
};
}),
name: method.getName(),
position: method.getStart(),
deprecated: jsDocs.some(jsDoc => ts_morph_1.ts.isJSDocDeprecatedTag(jsDoc.compilerNode)),
description: "",
isEventHandler: false,
node: method,
loc: {
start: {
line: positionStart.line,
column: positionStart.column - 1
},
end: {
line: positionEnd.line,
column: positionEnd.column - 1
}
},
mentionedInTheXMLDocument: false
};
});
return UIMethods;
}
_modifyType(returnType) {
if (/import\(".*?"\).default/.test(returnType)) {
const path = /(?<=import\(").*?(?="\).default)/.exec(returnType)?.[0];
const UI5Type = path?.startsWith("sap/")
? path.replace(/\//g, ".")
: path
? this.parser.fileReader.getClassNameFromPath(path)
: undefined;
if (UI5Type) {
returnType = UI5Type;
}
}
if (/import\(".*?"\)\.[a-zA-Z|$]*/.test(returnType)) {
const className = /(?<=import\(".*?"\)\.)[a-zA-Z|$]*/.exec(returnType)?.[0];
if (className) {
returnType = className;
}
}
return returnType;
}
_fillUI5Metadata(classInfo, fillTypes = false) {
this._fillUIDefine();
this.methods = this._fillMethods(undefined, fillTypes);
this.fields = this._fillFields(undefined, fillTypes);
this.constructors = this._fillConstructors(undefined, fillTypes);
const metadata = this.node.getProperty("metadata");
const metadataText = metadata?.getInitializer()?.getText();
if (metadataText) {
let metadataObject;
try {
metadataObject = Hjson.parse(metadataText);
this.properties = this._fillProperties(metadataObject);
this.aggregations = this._fillAggregations(metadataObject);
this.events = this._fillEvents(metadataObject);
this.associations = this._fillAssociations(metadataObject);
this.interfaces = this._fillInterfaces(metadataObject);
}
catch (error) {
console.error(`Couldn't parse metadata: ${error.message}`);
return;
}
}
}
_fillConstructors(metadata, fillTypes = false) {
const constructorDeclarations = this.node.getConstructors();
const constructors = constructorDeclarations.map(constructor => {
const jsDocs = constructor.getJsDocs();
const ui5IgnoreDoc = jsDocs.some(jsDoc => jsDoc.getTags().some(tag => tag.getTagName() === "ui5ignore"));
const positionStart = this._sourceFile.getLineAndColumnAtPos(constructor.getStart());
const positionEnd = this._sourceFile.getLineAndColumnAtPos(constructor.getEnd());
const method = {
ui5ignored: !!ui5IgnoreDoc,
owner: this.className,
static: false,
abstract: false,
returnType: fillTypes
? this._modifyType(constructor.getReturnType().getText()) ?? "void"
: this.className,
visibility: constructor
.getModifiers()
.find(modifier => [
ts_morph_1.ts.SyntaxKind.ProtectedKeyword,
ts_morph_1.ts.SyntaxKind.PrivateKeyword,
ts_morph_1.ts.SyntaxKind.PublicKeyword
].includes(modifier.getKind()))
?.getText() ?? "public",
params: constructor.getParameters().map(param => {
return {
name: param.getName(),
type: fillTypes ? this._modifyType(param.getType().getText()) ?? "any" : "any",
description: "",
isOptional: false
};
}),
name: "constructor",
position: constructor.getStart(),
deprecated: jsDocs.some(jsDoc => ts_morph_1.ts.isJSDocDeprecatedTag(jsDoc.compilerNode)),
description: "",
node: constructor,
isEventHandler: false,
loc: {
start: {
line: positionStart.line,
column: positionStart.column - 1
},
end: {
line: positionEnd.line,
column: positionEnd.column - 1
}
},
mentionedInTheXMLDocument: false
};
return method;
});
return constructors;
}
_fillInterfaces(metadata) {
const metadataInterfaces = metadata.interfaces;
if (!metadataInterfaces) {
return [];
}
return metadataInterfaces;
}
_fillAggregations(metadata) {
const metadataAggregations = metadata.aggregations;
if (!metadataAggregations) {
return [];
}
return Object.keys(metadataAggregations).map(sKey => {
const aggregation = metadataAggregations[sKey];
return {
name: aggregation.name ?? sKey ?? "",
type: aggregation.type ?? "any",
multiple: aggregation.cardinality === "0..n",
singularName: aggregation.singularName ?? aggregation.name ?? sKey ?? "",
description: aggregation.deprecation ?? "",
visibility: aggregation.visibility ?? "public",
default: false
};
});
}
_fillEvents(metadata) {
const metadataEvents = metadata.events;
if (!metadataEvents) {
return [];
}
return Object.keys(metadataEvents).map(sKey => {
const event = metadataEvents[sKey];
return {
name: event.name ?? sKey ?? "",
description: "",
visibility: event.visibility ?? "public",
params: Object.keys(event.parameters ?? {}).map(sKey => {
return {
name: event.parameters[sKey].name ?? sKey,
type: event.parameters[sKey].type
};
})
};
});
}
_fillProperties(metadata) {
const metadataProperties = metadata.properties;
if (!metadataProperties) {
return [];
}
const properties = Object.keys(metadataProperties).map(sKey => {
const property = metadataProperties[sKey];
return {
name: property.name ?? sKey ?? "",
type: property.type ?? "any",
visibility: property.visibility ?? "public",
description: "",
typeValues: this.generateTypeValues(property.type ?? "")
};
});
return properties;
}
_fillAssociations(metadata) {
const metadataAssociations = metadata.associations;
if (!metadataAssociations) {
return [];
}
const associations = Object.keys(metadataAssociations).map(sKey => {
const association = metadataAssociations[sKey];
return {
name: association.name ?? sKey ?? "",
type: association.type ?? "any",
multiple: association.cardinality === "0..n",
singularName: association.singularName ?? association.name ?? sKey ?? "",
description: association.deprecation ?? "",
visibility: association.visibility ?? "public"
};
});
return associations;
}
}
exports.CustomTSClass = CustomTSClass;