UNPKG

@zspk/ts-esm-generator

Version:

Генератор определений типов TypeScript для пользовательских элементов управления UI5, реализованных в TypeScript.

925 lines 50.5 kB
"use strict"; 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