@zspk/ts-esm-generator
Version:
Генератор определений типов TypeScript для пользовательских элементов управления UI5, реализованных в TypeScript.
925 lines • 50.5 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateSDK = exports.generateRuntime = void 0;
const path = require("path");
const fs = require("fs");
const ts = require("typescript");
const Hjson = require("hjson");
const astGenerationHelper_1 = require("./astGenerationHelper");
const astToString_1 = __importDefault(require("./astToString"));
const loglevel_1 = __importDefault(require("loglevel"));
const jsdocGenerator_1 = require("./jsdocGenerator");
const factory = ts.factory;
let ManagedObjectSymbol;
let ElementSymbol;
let ControlSymbol;
let WebComponentSymbol;
const MetadataProperties = ["properties", "aggregations", "associations", "events"];
function ui5BaseClassForSymbol(typeChecker, symbol) {
var _a, _b, _c, _d, _e, _f, _g, _h;
if (!ManagedObjectSymbol) {
const managedObjectModuleDeclaration = (_a = typeChecker.getAmbientModules().filter((m) => m.name === '"sap/ui/base/ManagedObject"')[0]) === null || _a === void 0 ? void 0 : _a.declarations[0];
const managedObjectClassDeclaration = (_b = managedObjectModuleDeclaration === null || managedObjectModuleDeclaration === void 0 ? void 0 : managedObjectModuleDeclaration.body) === null || _b === void 0 ? void 0 : _b.statements.filter((s) => { var _a; return ts.isClassDeclaration(s) && ((_a = s.name) === null || _a === void 0 ? void 0 : _a.text) === "ManagedObject"; })[0];
ManagedObjectSymbol = typeChecker.getSymbolAtLocation(managedObjectClassDeclaration === null || managedObjectClassDeclaration === void 0 ? void 0 : managedObjectClassDeclaration.name);
const elementModuleDeclaration = (_c = typeChecker.getAmbientModules().filter((m) => m.name === '"sap/ui/core/Element"')[0]) === null || _c === void 0 ? void 0 : _c.declarations[0];
const elementClassDeclaration = (_d = elementModuleDeclaration === null || elementModuleDeclaration === void 0 ? void 0 : elementModuleDeclaration.body) === null || _d === void 0 ? void 0 : _d.statements.filter((s) => { var _a; return ts.isClassDeclaration(s) && ((_a = s.name) === null || _a === void 0 ? void 0 : _a.text) === "UI5Element"; })[0];
ElementSymbol = typeChecker.getSymbolAtLocation(elementClassDeclaration === null || elementClassDeclaration === void 0 ? void 0 : elementClassDeclaration.name);
const controlModuleDeclaration = (_e = typeChecker.getAmbientModules().filter((m) => m.name === '"sap/ui/core/Control"')[0]) === null || _e === void 0 ? void 0 : _e.declarations[0];
const controlClassDeclaration = (_f = controlModuleDeclaration === null || controlModuleDeclaration === void 0 ? void 0 : controlModuleDeclaration.body) === null || _f === void 0 ? void 0 : _f.statements.filter((s) => { var _a; return ts.isClassDeclaration(s) && ((_a = s.name) === null || _a === void 0 ? void 0 : _a.text) === "Control"; })[0];
ControlSymbol = typeChecker.getSymbolAtLocation(controlClassDeclaration === null || controlClassDeclaration === void 0 ? void 0 : controlClassDeclaration.name);
const webComponentModuleDeclaration = (_g = typeChecker.getAmbientModules().filter((m) => m.name === '"sap/ui/core/webc/WebComponent"')[0]) === null || _g === void 0 ? void 0 : _g.declarations[0];
const webComponentClassDeclaration = (_h = webComponentModuleDeclaration === null || webComponentModuleDeclaration === void 0 ? void 0 : webComponentModuleDeclaration.body) === null || _h === void 0 ? void 0 : _h.statements.filter((s) => { var _a; return ts.isClassDeclaration(s) && ((_a = s.name) === null || _a === void 0 ? void 0 : _a.text) === "WebComponent"; })[0];
WebComponentSymbol = typeChecker.getSymbolAtLocation(webComponentClassDeclaration === null || webComponentClassDeclaration === void 0 ? void 0 : webComponentClassDeclaration.name);
}
if (symbol === ControlSymbol) {
return "Control";
}
else if (symbol === ElementSymbol) {
return "Element";
}
else if (symbol === WebComponentSymbol) {
return "WebComponent";
}
else if (symbol === ManagedObjectSymbol) {
return "ManagedObject";
}
}
const interestingBaseSettingsClasses = {
'"sap/ui/base/ManagedObject".$ManagedObjectSettings': "$ManagedObjectSettings",
'"sap/ui/core/Element".$UI5ElementSettings': "$ElementSettings",
'"sap/ui/core/Control".$ControlSettings': "$ControlSettings",
'"sap/ui/core/webc/WebComponent".$WebComponentSettings': "$WebComponentSettings"
};
function generateRuntime(sourceFile, typeChecker, allKnownGlobals) {
const moduleInfo = {
imports: [],
declarations: []
};
const runtimeImports = {};
const runtimeStatements = [];
// Сначала просмотреть все импорты, лучше отдельной иттерацией.
sourceFile.forEachChild((node) => {
if (ts.isImportDeclaration(node) && node.importClause) {
let moduleName = node.moduleSpecifier.getText().replace(/\"/g, "");
const clauses = node.importClause.getChildren().map((clause) => clause.getText());
const localName = clauses.filter((clause) => clause.indexOf("{") === -1)[0];
const exportNames = clauses.filter((clause) => clause.indexOf("{") !== -1).map((name) => name.replace(/[{}\s]/g, ""));
if (localName) {
runtimeImports[localName] = {
isDefault: true,
localModule: moduleName
};
}
for (const exportName of exportNames) {
runtimeImports[exportName] = {
isDefault: false,
localModule: moduleName
};
}
}
});
// Затем ищем классы, которые наследуют ManagedObject
sourceFile.forEachChild((node) => {
if (ts.isClassDeclaration(node)) {
if (!node.modifiers || !node.modifiers.length) {
// если нет модификаторов, значит это приватный класс (не экспортируемый).
return;
}
if (!node.modifiers.find((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
return;
}
const managedObject = {
methods: [],
className: node.name ? node.name.text : ""
};
runtimeImports.currentClassName = managedObject.className;
getManagedObjects(managedObject, node, typeChecker, sourceFile);
if (managedObject.metadata) {
generateBasic(moduleInfo, runtimeImports, managedObject, allKnownGlobals);
generateInterface(moduleInfo, managedObject);
const statements = [];
for (const declaration of moduleInfo.declarations) {
if (declaration.jsDoc) {
(0, jsdocGenerator_1.createJSDoc)(declaration.statement, declaration.jsDoc);
}
statements.push(declaration.statement);
}
if (moduleInfo.exportDefault) {
if (moduleInfo.exportDefaultJSDoc) {
(0, jsdocGenerator_1.createJSDoc)(moduleInfo.exportDefault, moduleInfo.exportDefaultJSDoc);
}
statements.push(moduleInfo.exportDefault);
}
const runtimeModule = factory.createModuleDeclaration([factory.createModifier(ts.SyntaxKind.DeclareKeyword)], factory.createStringLiteral("./" + managedObject.className), factory.createModuleBlock(statements));
(0, astGenerationHelper_1.addLineBreakBefore)(runtimeModule, 2);
managedObject.moduleName = "./" + path.basename(sourceFile.fileName, path.extname(sourceFile.fileName));
moduleInfo.imports.push(...getImports(runtimeImports, managedObject.moduleName));
runtimeStatements.push(...moduleInfo.imports, runtimeModule);
writeInterfaceFile(sourceFile.fileName, true, (0, astToString_1.default)(runtimeStatements));
}
}
});
}
exports.generateRuntime = generateRuntime;
function generateSDK(sourceFile, typeChecker, allKnownGlobals) {
const moduleInfo = {
imports: [],
declarations: []
};
const managedObject = {
methods: [],
isIncludeMethodModifier: true
};
const sdkImports = {};
const sdkStatements = [];
// Сначала просмотреть все импорты, лучше отдельной иттерацией.
sourceFile.forEachChild((node) => {
if (ts.isImportDeclaration(node) && node.importClause) {
let moduleName = node.moduleSpecifier.getText().replace(/\"/g, "");
const clauses = node.importClause.getChildren().map((clause) => clause.getText());
const localName = clauses.filter((clause) => clause.indexOf("{") === -1)[0];
const exportNames = clauses.filter((clause) => clause.indexOf("{") !== -1).map((name) => name.replace(/[{}\s]/g, ""));
// Собрать все локальные импорты
if (moduleName.startsWith(".")) {
const index = moduleName.lastIndexOf("/") + 1;
if (index < 0 || index >= moduleName.length) {
return;
}
if (localName) {
const x = allKnownGlobals[localName];
if (x) {
sdkImports[localName] = {
isDefault: !x.exportName,
localModule: x.moduleName
};
}
}
for (const exportName of exportNames) {
const x = allKnownGlobals[exportName];
if (x) {
sdkImports[exportName] = {
isDefault: !x.exportName,
localModule: x.moduleName
};
}
}
}
else {
if (localName) {
sdkImports[localName] = {
isDefault: true,
localModule: moduleName
};
}
for (const exportName of exportNames) {
sdkImports[exportName] = {
isDefault: false,
localModule: moduleName
};
}
}
}
});
// Затем ищем классы, которые наследуют ManagedObject
sourceFile.forEachChild((node) => {
var _a, _b;
if (ts.isClassDeclaration(node)) {
if (!node.modifiers || !node.modifiers.length) {
// если нет модификаторов, значит это приватный класс (не экспортируемый).
return;
}
if (!node.modifiers.find((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
return;
}
moduleInfo.exportDefaultJSDoc = extractJSDoc(ts.getJSDocTags(node));
managedObject.className = node.name ? node.name.text : "";
managedObject.moduleName = getModuleNameFromJSDoc(moduleInfo.exportDefaultJSDoc, (_a = node.name) === null || _a === void 0 ? void 0 : _a.text);
sdkImports.currentClassName = managedObject.className;
getManagedObjects(managedObject, node, typeChecker, sourceFile);
if (managedObject.metadata) {
generateBasic(moduleInfo, sdkImports, managedObject, allKnownGlobals);
generateInterface(moduleInfo, managedObject);
}
generateClass(node, moduleInfo, managedObject);
}
if (ts.isInterfaceDeclaration(node)) {
if (node.modifiers && node.modifiers.length) {
if (node.modifiers.find((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
const name = (_b = node.name) === null || _b === void 0 ? void 0 : _b.text;
if (!name || !node.members)
return;
const members = [];
for (const member of node.members) {
if (ts.isMethodSignature(member)) {
let parameters = undefined;
if (member.parameters && member.parameters.length) {
parameters = [];
for (const parameter of member.parameters) {
if (ts.isUnionTypeNode(parameter.type)) {
if (parameter.type.types) {
parameters.push(factory.createParameterDeclaration(undefined, parameter.dotDotDotToken, parameter.name, parameter.questionToken, factory.createUnionTypeNode(parameter.type.types.map((t) => {
return factory.createTypeReferenceNode(t.getText());
}))));
}
else {
parameters.push(factory.createParameterDeclaration(undefined, parameter.dotDotDotToken, parameter.name, parameter.questionToken, parameter.type));
}
}
else {
parameters.push(factory.createParameterDeclaration(undefined, parameter.dotDotDotToken, parameter.name, parameter.questionToken, parameter.type));
}
}
}
let typeNode = undefined;
if (member.type) {
if (ts.isUnionTypeNode(member.type)) {
typeNode = factory.createUnionTypeNode(member.type.types.map((t) => {
return factory.createTypeReferenceNode(t.getText());
}));
}
else {
typeNode = factory.createTypeReferenceNode(member.type.getText());
}
}
members.push(factory.createMethodSignature(member.modifiers, member.name, member.questionToken, member.typeParameters, parameters, typeNode));
}
if (ts.isPropertySignature(member)) {
let typeNode = undefined;
if (member.type) {
if (ts.isUnionTypeNode(member.type)) {
typeNode = factory.createUnionTypeNode(member.type.types.map((t) => {
return factory.createTypeReferenceNode(t.getText());
}));
}
else {
typeNode = factory.createTypeReferenceNode(member.type.getText());
}
}
members.push(factory.createPropertySignature(member.modifiers, member.name, member.questionToken, typeNode));
}
}
const interfaceNode = factory.createInterfaceDeclaration(node.modifiers, factory.createIdentifier(name), node.typeParameters, node.heritageClauses, members);
moduleInfo.declarations.push({
statement: interfaceNode,
jsDoc: extractJSDoc(ts.getJSDocCommentsAndTags(node))
});
}
}
}
if (ts.isTypeAliasDeclaration(node)) {
}
if (ts.isEnumDeclaration(node)) {
if (node.modifiers && node.modifiers.length) {
if (node.modifiers.find((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
const members = [];
for (const member of node.members) {
if (member.initializer) {
if (ts.isStringLiteral(member.initializer)) {
members.push(factory.createEnumMember(member.name, factory.createStringLiteral(member.initializer.getText().replace(/\"/g, ""))));
}
else if (ts.isNumericLiteral(member.initializer)) {
members.push(factory.createEnumMember(member.name, factory.createNumericLiteral(member.initializer.getText())));
}
else {
members.push(factory.createEnumMember(member.name, undefined));
}
}
else {
members.push(factory.createEnumMember(member.name, undefined));
}
}
moduleInfo.declarations.push({
statement: factory.createEnumDeclaration(node.modifiers, node.name, members),
jsDoc: extractJSDoc(ts.getJSDocCommentsAndTags(node))
});
}
}
}
if (ts.isFunctionDeclaration(node)) {
if (node.modifiers && node.modifiers.length) {
if (node.modifiers.find((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
moduleInfo.declarations.push({
statement: factory.createFunctionDeclaration([factory.createModifier(ts.SyntaxKind.ExportKeyword)], node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, undefined),
jsDoc: extractJSDoc(ts.getJSDocCommentsAndTags(node))
});
}
}
}
if (ts.isVariableStatement(node)) {
if (node.modifiers && node.modifiers.length) {
if (node.modifiers.find((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
const declarations = [];
for (const decl of node.declarationList.declarations) {
declarations.push(factory.createVariableDeclaration(decl.name, decl.exclamationToken, decl.type, undefined));
}
moduleInfo.declarations.push({
statement: factory.createVariableStatement([factory.createModifier(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList(declarations, ts.NodeFlags.Const)),
jsDoc: extractJSDoc(ts.getJSDocCommentsAndTags(node))
});
}
}
}
if (ts.isVariableDeclaration(node)) {
}
if (ts.isExportAssignment(node)) {
const expression = node.expression;
if (!expression)
return;
const jsDoc = extractJSDoc(ts.getJSDocCommentsAndTags(node));
if (!jsDoc.tagName)
return;
const typeFunctionElements = [];
const typePropertyElements = [];
if (expression.properties) {
for (const prop of expression.properties) {
if (ts.isMethodDeclaration(prop)) {
let parameters = undefined;
if (prop.parameters) {
parameters = [];
for (const param of prop.parameters) {
parameters.push(factory.createParameterDeclaration(param.modifiers, param.dotDotDotToken, param.name, param.questionToken, param.type, undefined));
}
}
typeFunctionElements.push(factory.createMethodSignature([], prop.name, undefined, prop.typeParameters, parameters, prop.type || factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword)));
}
if (ts.isPropertyAssignment(prop)) {
if (prop.initializer) {
typePropertyElements.push(factory.createPropertySignature([], prop.name, undefined, createTypeNodeFromKind(prop.initializer.kind)));
}
}
}
}
const typeInterface = factory.createInterfaceDeclaration([factory.createModifier(ts.SyntaxKind.ExportKeyword)], "T" + jsDoc.tagName, undefined, undefined, [
...typePropertyElements,
...typeFunctionElements
]);
const declarations = [
factory.createVariableDeclaration(factory.createIdentifier(jsDoc.tagName), undefined, factory.createTypeReferenceNode("T" + jsDoc.tagName), undefined)
];
moduleInfo.declarations.push({
statement: typeInterface,
jsDoc: null
});
moduleInfo.declarations.push({
statement: factory.createVariableStatement([], factory.createVariableDeclarationList(declarations, ts.NodeFlags.Const)),
jsDoc: null
});
moduleInfo.declarations.push({
statement: factory.createExportDefault(factory.createIdentifier(jsDoc.tagName)),
jsDoc: jsDoc
});
}
});
if (!moduleInfo.declarations.length && !moduleInfo.exportDefault)
return;
if (!managedObject.moduleName) {
for (const declaration of moduleInfo.declarations) {
if (declaration.jsDoc && declaration.jsDoc.tagNamespace) {
managedObject.moduleName = [...declaration.jsDoc.tagNamespace.split("."), path.basename(sourceFile.fileName, path.extname(sourceFile.fileName))].join("/");
break;
}
}
if (!managedObject.moduleName) {
return;
}
}
moduleInfo.imports = getImports(sdkImports, managedObject.moduleName);
if (moduleInfo.imports.length) {
sdkStatements.push(...moduleInfo.imports);
}
if (moduleInfo.declarations.length) {
for (const declaration of moduleInfo.declarations) {
if (declaration.jsDoc) {
(0, jsdocGenerator_1.createJSDoc)(declaration.statement, declaration.jsDoc);
}
sdkStatements.push(declaration.statement);
}
}
if (moduleInfo.exportDefault) {
sdkStatements.push(moduleInfo.exportDefault);
}
const sdkModule = factory.createModuleDeclaration([factory.createModifier(ts.SyntaxKind.DeclareKeyword)], factory.createStringLiteral(managedObject.moduleName), factory.createModuleBlock(sdkStatements));
writeInterfaceFile(sourceFile.fileName, false, (0, astToString_1.default)([sdkModule]));
}
exports.generateSDK = generateSDK;
function createTypeNodeFromKind(kind) {
switch (kind) {
case ts.SyntaxKind.BigIntLiteral:
return factory.createKeywordTypeNode(ts.SyntaxKind.BigIntKeyword);
case ts.SyntaxKind.FalseKeyword:
case ts.SyntaxKind.TrueKeyword:
return factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
case ts.SyntaxKind.NumericLiteral:
return factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
case ts.SyntaxKind.ObjectLiteralExpression:
return factory.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
case ts.SyntaxKind.StringLiteral:
return factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
case ts.SyntaxKind.UndefinedKeyword:
case ts.SyntaxKind.NullKeyword:
return factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword);
case ts.SyntaxKind.VoidKeyword:
return factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
default:
return factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
}
}
function getModuleNameFromJSDoc(commentsAndTags, altName) {
let namespace = commentsAndTags.tagNamespace || "./";
let name = commentsAndTags.tagName || altName || "";
return [...namespace.split("."), name].join("/");
}
/**
* NOTE: OK
*/
function writeInterfaceFile(sourceFileName, isRuntime, interfaceText) {
const pathName = path.dirname(sourceFileName);
const fileName = path.basename(sourceFileName, path.extname(sourceFileName)) + (isRuntime ? "-runtime" : "-sdk") + ".d.ts";
const newFileName = path.join(pathName, fileName);
loglevel_1.default.info(`Запись файла: ${fileName}`);
fs.writeFileSync(newFileName, interfaceText);
}
/**
* NOTE: OK
*/
function getManagedObjects(managedObject, classDeclaration, typeChecker, sourceFile) {
if (classDeclaration.modifiers) {
managedObject.isAbstract = !!classDeclaration.modifiers.find((modifier) => modifier.kind === ts.SyntaxKind.AbstractKeyword);
}
if (classDeclaration.typeParameters && classDeclaration.typeParameters.length) {
managedObject.typeParameters = [];
classDeclaration.typeParameters.forEach((typeParameter) => {
managedObject.typeParameters.push(typeParameter);
});
}
if (!classDeclaration.heritageClauses || !classDeclaration.heritageClauses.length)
return;
// может быть только один базовый класс
const heritageClause = classDeclaration.heritageClauses[0];
if (!heritageClause.types || !heritageClause.types.length)
return;
const typeNode = heritageClause.types[0];
const type = typeChecker.getTypeFromTypeNode(typeNode);
const symbol = type.getSymbol();
if (!symbol) {
loglevel_1.default.error(`Не удалось определить тип "${typeNode.getText()}" в предложении наследования "${heritageClause.getFullText()}".`);
return;
}
// Теперь проверим, является ли этот тип, от которого был унаследован, ManagedObject
managedObject.ui5BaseClass = getUi5BaseClass(type, typeChecker);
if (!managedObject.ui5BaseClass) {
return;
}
// Ок, у нас есть ManagedObject/Control; теперь проверьте, содержит ли он раздел метаданных, а это значит, что необходимо сгенерировать методы доступа
managedObject.metadata = getMetadata(classDeclaration.members)[0];
if (managedObject.metadata && !managedObject.metadata.initializer) {
// ровно одно объявление "метаданных", НО не инициализированное фактическим значением метаданных.
// Это может означать, что кто-то случайно написал "metadata: {...}" вместо "metadata = {...}",
// что синтаксически правильно, но присваивает структура типа, а не значение.
// Это приведет к сбою во время выполнения, поскольку ни одно из запланированных объявлений API не работает,
// но прежде чем произойдет сбой во время выполнения, произойдет сбой здесь, в генераторе, который позже попытается получить доступ к данным.
// Итак, давайте предупредим пользователя.
loglevel_1.default.warn(`В классе ${managedObject.className} существует объявление метаданных без значения. Вы случайно написали «metadata: ...» вместо «metadata = ...»?`);
return;
}
// Теперь проверим, есть ли тип настроек в суперклассе (от которого должен наследовать сгенерированный тип настроек).
// Он действительно должен быть, потому что он должен быть у всех потомков ManagedObject!
const settingsTypeNode = getSettingsType(type, typeChecker);
if (settingsTypeNode) {
const settingsType = typeChecker.getTypeFromTypeNode(settingsTypeNode);
const settingSymbol = settingsType.getSymbol();
managedObject.settingsTypeName = typeChecker.getFullyQualifiedName(settingSymbol);
// settingsTypeName имеет примерно такой вид
// "sap/ui/base/ManagedObject".$ManagedObjectSettings
// "sap/ui/table/AnalyticalTable".$AnalyticalTableSettings
// "sap/ui/core/Control".$ControlSettings
if (managedObject.settingsTypeName.startsWith("./")) {
const settingsTypeDeclaration = settingSymbol.getDeclarations()[0];
const settingsTypeSourceFile = settingsTypeDeclaration.getSourceFile().fileName;
const settingsTypeDirectory = path.dirname(settingsTypeSourceFile);
const managedObjectDirectory = path.dirname(sourceFile.fileName);
if (managedObjectDirectory !== settingsTypeDirectory) {
// settings type of superclass is in different directory, hence the generated import will have to traverse to that directory
const relativePath = path.relative(managedObjectDirectory, settingsTypeDirectory).replace(/\\/, "/");
const match = managedObject.settingsTypeName.match(/".\/([^/]+\/)*([^/]+)".*/);
if (match) {
// insert the relative path
managedObject.settingsTypeName = "./" + relativePath + managedObject.settingsTypeName.slice(2);
}
}
}
}
// проверить наличие уже доступных подписей конструктора (если они не найдены, вывод консоли предложит пользователю их добавить)
managedObject.isConstructorDecorated = checkConstructors(classDeclaration);
return managedObject;
}
/**
* NOTE: OK
*/
function getUi5BaseClass(type, typeChecker) {
// Проверка не нужна, она не работает с дженериками
// if (!type.isClassOrInterface()) {
// return undefined;
// }
let interestingBaseClass = ui5BaseClassForSymbol(typeChecker, type.getSymbol());
if (interestingBaseClass) {
return interestingBaseClass;
}
const baseTypes = typeChecker.getBaseTypes(type);
for (let i = 0; i < baseTypes.length; i++) {
if ((interestingBaseClass = getUi5BaseClass(baseTypes[i], typeChecker))) {
return interestingBaseClass;
}
}
return undefined;
}
/**
* NOTE: OK
* Фильтрует среди свойств элементов класса статическое свойство с именем `metadata`.
*/
function getMetadata(members) {
return members.filter((member) => {
if (ts.isPropertyDeclaration(member) &&
ts.isIdentifier(member.name) &&
member.name.escapedText === "metadata" &&
member.modifiers &&
member.modifiers.some((modifier) => {
return modifier.kind === ts.SyntaxKind.StaticKeyword;
})) {
return true;
}
});
}
/**
* NOTE: OK
* Проверяет наличие стандартных подписей конструктора, чтобы инструмент мог сообщить об их отсутствии
*/
function checkConstructors(classDeclaration) {
let singleParameterDeclarationFound = false, doubleParameterDeclarationFound = false, implementationFound = false;
classDeclaration.members.forEach((member) => {
if (!ts.isConstructorDeclaration(member))
return;
if (member.parameters.length === 1 && member.body === undefined) {
const parameter = member.parameters[0];
if (parameter.questionToken && ts.isUnionTypeNode(parameter.type)) {
if (parameter.type.types.length === 2) {
if (isOneAStringAndTheOtherASettingsObject(parameter.type.types[0], parameter.type.types[1])) {
singleParameterDeclarationFound = true;
}
}
}
}
else if (member.parameters.length === 2) {
if (isOneAStringAndTheOtherASettingsObject(member.parameters[0].type, member.parameters[1].type)) {
if (member.body) {
implementationFound = true;
}
else {
doubleParameterDeclarationFound = true;
}
}
}
else {
loglevel_1.default.warn(`Неожиданная запись конструктора с номером параметра, отличным от 1 или 2 в классе ${member.parent.name.text}`);
}
});
const found = singleParameterDeclarationFound && doubleParameterDeclarationFound && implementationFound;
// if (!found) {
// log.debug(
// classDeclaration.name.text +
// " is missing required constructor signatures: " +
// (singleParameterDeclarationFound ? "" : "\n- constructor declaration with single parameter") +
// (doubleParameterDeclarationFound ? "" : "\n- constructor declaration with two parameters") +
// (implementationFound ? "" : "\n- constructor implementation with two parameters")
// );
// }
return found;
}
/**
* NOTE: OK
*/
function isOneAStringAndTheOtherASettingsObject(type1, type2) {
return ((type1.kind === ts.SyntaxKind.StringKeyword && ts.isTypeReferenceNode(type2)) || // TODO: more specific check for second type
(type2.kind === ts.SyntaxKind.StringKeyword && ts.isTypeReferenceNode(type1)));
}
/**
* NOTE: OK
* Возвращает тип объекта настроек, используемый в конструкторе данного типа.
* Необходимо получить из него новый тип объекта настроек для подкласса.
*/
function getSettingsType(type, typeChecker) {
const declarations = type.getSymbol().getDeclarations();
for (let i = 0; i < declarations.length; i++) {
const declaration = declarations[i];
const members = declaration.members;
for (let j = 0; j < members.length; j++) {
if (ts.isConstructorDeclaration(members[j])) {
const settingsType = getSettingsTypeFromConstructor(members[j], typeChecker);
if (settingsType) {
return settingsType;
}
}
}
}
}
/**
* NOTE: OK
* Возвращает тип первого найденного объекта настроек (наследующий от sap/ui/base/ManagedObject/$ManagedObjectSettings,
* встречающегося среди параметров данного конструктора. Или undefined.
*/
function getSettingsTypeFromConstructor(ctor, typeChecker) {
for (let i = 0; i < ctor.parameters.length; i++) {
const parameter = ctor.parameters[i];
if (parameter.type.kind === ts.SyntaxKind.TypeReference) {
// Этот i-й параметр конструктора базового класса может быть типом настроек.
const potentialSettingsType = typeChecker.getTypeFromTypeNode(parameter.type);
const ui5BaseSettingsClass = getUi5BaseClassSettings(potentialSettingsType, typeChecker);
if (ui5BaseSettingsClass) {
return parameter.type;
}
}
}
}
/**
* NOTE: OK
* Возвращает имя ближайшего типа настроек базового класса ("$ManagedObjectSettings" | "$ElementSettings" | "$ControlSettings") - или undefined
*/
function getUi5BaseClassSettings(type, typeChecker) {
if (!type.isClassOrInterface()) {
return;
}
const symbol = type.getSymbol();
if (!symbol) {
loglevel_1.default.error(`Символ ${type.aliasSymbol ? `для типа '${type.aliasSymbol.getName()}'` : ""} не удалось разрешить. Это означает, что TypeScript не выяснил, что на самом деле представляет собой этот тип. Проверьте исходный код: определен ли этот тип там, где он написан? Если нет, то почему?`);
return;
}
let interestingBaseSettingsClass = interestingBaseSettingsClasses[typeChecker.getFullyQualifiedName(symbol)];
if (interestingBaseSettingsClass) {
return interestingBaseSettingsClass;
}
const baseTypes = typeChecker.getBaseTypes(type);
if ((!baseTypes || baseTypes.length === 0) && type.symbol && type.symbol.escapedName) {
loglevel_1.default.warn(`TypeScript не смог разрешить ни один базовый тип для ${type.symbol.escapedName.toString()}.`);
return;
}
for (let i = 0; i < baseTypes.length; i++) {
if ((interestingBaseSettingsClass = getUi5BaseClassSettings(baseTypes[i], typeChecker))) {
return interestingBaseSettingsClass;
}
}
return undefined;
}
function getTagsWithName(jsDocs, tagName) {
const fnCheck = tagName === "deprecated" ? ts.isJSDocDeprecatedTag : ts.isJSDocUnknownTag;
let tags = [];
jsDocs === null || jsDocs === void 0 ? void 0 : jsDocs.forEach((doc) => {
// for elements of type JSDoc, the actual tags are properties
if (ts.isJSDoc(doc)) {
tags = tags.concat(getTagsWithName(doc.tags, tagName));
}
else if (fnCheck(doc) && doc.tagName.getText() === tagName) {
tags.push(doc);
}
});
return tags;
}
function extractCommentText(jsDocs) {
const comments = [];
jsDocs.forEach((doc) => {
if (ts.isJSDoc(doc)) {
if (typeof doc.comment === "string") {
comments.push(...doc.comment.split("\n"));
}
else if (Array.isArray(doc.comment)) {
// NodeArray<JSDocComment>
doc.comment.forEach((singleDoc) => {
comments.push(...singleDoc.getFullText().split("\n"));
});
}
}
});
return comments;
}
function handleTag(jsDocs, tagName) {
const tags = getTagsWithName(jsDocs, tagName);
if (tags.length === 0) {
return undefined;
}
else {
return tags.map((tag) => tag.comment).join("; ");
}
}
function extractJSDoc(jsDocs) {
return {
comment: extractCommentText(jsDocs),
tagName: handleTag(jsDocs, "name"),
tagNamespace: handleTag(jsDocs, "namespace"),
tagAlias: handleTag(jsDocs, "alias"),
tagExperimental: handleTag(jsDocs, "experimental"),
tagSince: handleTag(jsDocs, "since"),
tagReadonly: handleTag(jsDocs, "readonly"),
tagDeprecation: handleTag(jsDocs, "deprecated")
};
}
function getMemberFromPropertyAssignment(propertyAssignment, propertyName) {
const memberName = propertyAssignment.name.getText().replace(/['"]/g, "");
const member = {
name: memberName
};
try {
const definitionProperty = Hjson.parse(propertyAssignment.initializer.getText()); // parse with some fault tolerance: it's not a real JSON object, but JS code which may contain comments and property names which are not enclosed in double quotes
const definitionObject = (0, astGenerationHelper_1.expandDefaultKey)(definitionProperty, propertyName === "events" ? null : "type");
Object.assign(member, definitionObject);
}
catch (e) {
loglevel_1.default.error(`Ошибка свойства метаданных ${member.name}.`);
return null;
}
Object.assign(member, extractJSDoc(ts.getJSDocCommentsAndTags(propertyAssignment)));
return member;
}
function generateBasic(moduleInfo, requiredImports, managedObject, allKnownGlobals) {
const metadataObject = {};
if (managedObject.metadata) {
const objectLiteralExpression = managedObject.metadata.initializer;
objectLiteralExpression.properties.forEach((propertyAssignment) => {
if (!ts.isPropertyAssignment(propertyAssignment)) {
return;
}
const propertyName = propertyAssignment.name.getText().replace(/['"]/g, "");
if (!MetadataProperties.includes(propertyName)) {
return;
}
const innerObjectLiteralExpression = propertyAssignment.initializer;
if (!ts.isObjectLiteralExpression(innerObjectLiteralExpression) || !innerObjectLiteralExpression.properties) {
return;
}
metadataObject[propertyName] = {};
innerObjectLiteralExpression.properties
.filter((prop) => ts.isPropertyAssignment(prop))
.forEach((prop) => {
const member = getMemberFromPropertyAssignment(prop, propertyName);
if (member) {
const name = prop.name.getText().replace(/['"]/g, "");
metadataObject[propertyName][name] = member;
}
});
});
const eventDefinition = (0, astGenerationHelper_1.generateEventWithGenericsCompatibilityModule)(managedObject.className, requiredImports, allKnownGlobals);
managedObject.eventParameterInterfaces = (0, astGenerationHelper_1.generateEventParameterInterfaces)(metadataObject.events, managedObject.className, requiredImports, allKnownGlobals);
managedObject.eventTypeAliases = (0, astGenerationHelper_1.generateEventTypeAliases)(metadataObject.events, managedObject);
const settingsInterface = (0, astGenerationHelper_1.generateSettingsInterface)(metadataObject, managedObject, requiredImports, allKnownGlobals);
if (eventDefinition) {
managedObject.eventDefinition = {
jsDoc: null,
statement: eventDefinition
};
moduleInfo.declarations.push(managedObject.eventDefinition);
}
moduleInfo.declarations.push(...Object.values(managedObject.eventParameterInterfaces));
moduleInfo.declarations.push(...Object.values(managedObject.eventTypeAliases));
if (settingsInterface) {
managedObject.settingsInterface = {
jsDoc: null,
statement: settingsInterface
};
moduleInfo.declarations.push(managedObject.settingsInterface);
}
}
metadataObject.abstract = metadataObject.abstract || managedObject.isAbstract;
(0, astGenerationHelper_1.collectMetadataClass)(metadataObject, managedObject, requiredImports, allKnownGlobals, astGenerationHelper_1.createMethodSignature);
if (managedObject.typeParameters) {
for (const typeParameter of managedObject.typeParameters) {
if (typeParameter.constraint) {
(0, astGenerationHelper_1.createTypeNode)(typeParameter.constraint.getText(), requiredImports, allKnownGlobals);
}
}
}
}
/**
* NOTE: OK
*/
function generateInterface(moduleInfo, managedObject) {
moduleInfo.exportDefault = factory.createInterfaceDeclaration([factory.createModifier(ts.SyntaxKind.ExportKeyword), factory.createModifier(ts.SyntaxKind.DefaultKeyword)], managedObject.className, managedObject.typeParameters, undefined, managedObject.methods);
(0, astGenerationHelper_1.addLineBreakBefore)(moduleInfo.exportDefault, 2);
}
/**
* NOTE: OK
*/
function generateClass(node, moduleInfo, managedObject) {
const defaultModifier = factory.createModifier(ts.SyntaxKind.PublicKeyword);
const constructors = [];
const properties = [];
const methods = [];
const constructorDescriptions = [];
let heritageClauses = undefined;
if (node.heritageClauses && node.heritageClauses.length) {
heritageClauses = node.heritageClauses.map((heritageClause) => heritageClause);
}
node.forEachChild((child) => {
if (ts.isMethodDeclaration(child)) {
if (child.modifiers && child.modifiers.find((modifier) => modifier.kind === ts.SyntaxKind.PrivateKeyword)) {
return;
}
const modifiers = [];
if (child.modifiers) {
if (!child.modifiers.find((modifier) => modifier.kind === ts.SyntaxKind.PublicKeyword || modifier.kind === ts.SyntaxKind.ProtectedKeyword)) {
modifiers.push(defaultModifier);
}
child.modifiers.forEach((modifier) => modifiers.push(modifier));
}
else {
modifiers.push(defaultModifier);
}
const methodDeclaration = factory.createMethodDeclaration(modifiers, child.asteriskToken, child.name.getText(), child.questionToken, child.typeParameters, child.parameters, child.type, undefined);
(0, jsdocGenerator_1.createJSDoc)(methodDeclaration, extractJSDoc(ts.getJSDocCommentsAndTags(child)));
methods.push(methodDeclaration);
return;
}
if (ts.isPropertyDeclaration(child)) {
if (child.modifiers && child.modifiers.find((modifier) => modifier.kind === ts.SyntaxKind.PrivateKeyword)) {
return;
}
const modifiers = [];
if (child.modifiers) {
if (!child.modifiers.find((modifier) => modifier.kind === ts.SyntaxKind.PublicKeyword || modifier.kind === ts.SyntaxKind.ProtectedKeyword)) {
modifiers.push(defaultModifier);
}
child.modifiers.forEach((modifier) => modifiers.push(modifier));
}
else {
modifiers.push(defaultModifier);
}
const propertyDeclaration = factory.createPropertyDeclaration(modifiers, child.name.getText(), child.questionToken || child.exclamationToken, child.type, undefined);
properties.push(propertyDeclaration);
return;
}
if (ts.isConstructorDeclaration(child)) {
const parameterNames = child.parameters.map((param) => param.name.getText());
const parameters = child.parameters.map((param) => param);
const exists = constructorDescriptions.find((constructor) => constructor.parameterNames.length === parameterNames.length);
if (exists)
return;
constructorDescriptions.push({ parameterNames, parameters });
}
});
if (constructorDescriptions.length) {
constructors.push(...constructorDescriptions.map((constructorDescription) => {
const parameters = [];
for (let i = 0; i < constructorDescription.parameterNames.length; i++) {
parameters.push(factory.createParameterDeclaration(undefined, constructorDescription.parameters[i].dotDotDotToken, constructorDescription.parameterNames[i], constructorDescription.parameters[i].questionToken, constructorDescription.parameters[i].type));
}
return factory.createConstructorDeclaration(undefined, parameters, undefined);
}));
(0, astGenerationHelper_1.addLineBreakBefore)(constructors[0], 2);
}
managedObject.methods.unshift(...properties, ...constructors, ...methods);
moduleInfo.exportDefault = factory.createClassDeclaration([factory.createModifier(ts.SyntaxKind.ExportKeyword), factory.createModifier(ts.SyntaxKind.DefaultKeyword)], managedObject.className, managedObject.typeParameters, heritageClauses, managedObject.methods);
}
/**
* NOTE: OK
*/
function getImports(requiredImports, moduleName) {
const imports = [];
const index = moduleName.lastIndexOf("/");
if (index > -1) {
moduleName = moduleName.slice(index);
}
const importSpecifiers = {};
for (const clauseName in requiredImports) {
if (clauseName === "currentClassName")
continue;
const importClause = requiredImports[clauseName];
const specifierName = importClause.localModule || importClause.globalModule;
if (specifierName.endsWith(moduleName))
continue;
if (!importSpecifiers[specifierName]) {
importSpecifiers[specifierName] = {};
}
if (importClause.isDefault) {
importSpecifiers[specifierName].local = factory.createIdentifier(clauseName);
}
else {
const exportClauseNames = importSpecifiers[specifierName].imports || [];
if (!exportClauseNames.length) {
importSpecifiers[specifierName].imports = exportClauseNames;
}
if (!exportClauseNames.find((clause) => clause.name.escapedText === clauseName)) {
exportClauseNames.push(factory.createImportSpecifier(false, undefined, factory.createIdentifier(clauseName)));
}
}
}
for (const dependencyName in importSpecifiers) {
const specifier = importSpecifiers[dependencyName];
let namedImports = undefined;
if (specifier.imports && specifier.imports.length) {
namedImports = factory.createNamedImports(specifier.imports);
}
imports.push(factory.createImportDeclaration(undefined, factory.createImportClause(false, specifier.local, namedImports), factory.createStringLiteral(dependencyName)));
}
return imports;
}
//# sourceMappingURL=interfaceGenerationHelper.js.map