UNPKG

@ui5/linter

Version:

A static code analysis tool for UI5

347 lines 11.8 kB
import ts from "typescript"; import { getPropertyNameText } from "./utils/utils.js"; export var Ui5TypeInfoKind; (function (Ui5TypeInfoKind) { Ui5TypeInfoKind["Module"] = "Module"; Ui5TypeInfoKind["Namespace"] = "Namespace"; Ui5TypeInfoKind["Class"] = "Class"; Ui5TypeInfoKind["Constructor"] = "Constructor"; Ui5TypeInfoKind["ConstructorParameter"] = "ConstructorParameter"; Ui5TypeInfoKind["ManagedObjectSettings"] = "ManagedObjectSettings"; Ui5TypeInfoKind["MetadataProperty"] = "MetadataProperty"; Ui5TypeInfoKind["MetadataEvent"] = "MetadataEvent"; Ui5TypeInfoKind["MetadataAggregation"] = "MetadataAggregation"; Ui5TypeInfoKind["MetadataAssociation"] = "MetadataAssociation"; Ui5TypeInfoKind["Function"] = "Function"; Ui5TypeInfoKind["Method"] = "Method"; Ui5TypeInfoKind["Property"] = "Property"; Ui5TypeInfoKind["StaticMethod"] = "StaticMethod"; Ui5TypeInfoKind["StaticProperty"] = "StaticProperty"; Ui5TypeInfoKind["Enum"] = "Enum"; Ui5TypeInfoKind["EnumMember"] = "EnumMember"; Ui5TypeInfoKind["Export"] = "Export"; })(Ui5TypeInfoKind || (Ui5TypeInfoKind = {})); function isManagedObjectSettingsInterfaceName(name) { // This check is based on the naming convention when generating the Interface for ManagedObject settings: // $<ClassName>Settings return name.startsWith("$") && name.endsWith("Settings") && name.length > 9; } function isInterfaceInheritingFromManagedObjectSettings(node) { if (!node.heritageClauses) { return false; } if (!isManagedObjectSettingsInterfaceName(node.name.text)) { return false; } return true; } function getLibraryNameFromSourceFile(sourceFile) { const fileName = sourceFile.fileName; const match = /(?:^|\/)([a-zA-Z0-9.]+)\.d\.ts$/.exec(fileName); if (match?.[1]) { return match[1]; } return fileName.replace(/\.d\.ts$/, ""); } /** * Extracts module / global type information from UI5 symbols. */ export function getUi5TypeInfoFromSymbol(symbol, apiExtract) { if (symbol.valueDeclaration) { return getUi5TypeInfoFromDeclaration(symbol.valueDeclaration, apiExtract); } else if (symbol.declarations) { for (const declaration of symbol.declarations) { const typeInfo = getUi5TypeInfoFromDeclaration(declaration, apiExtract); if (typeInfo) { return typeInfo; } } } } function getUi5TypeInfoFromDeclaration(declaration, apiExtract) { const sourceFile = declaration.getSourceFile(); const node = declaration; let currentNode = declaration; let currentNamespaceTypeInfo; let namespaceTypeInfo; let moduleName; while (currentNode) { if (ts.isModuleDeclaration(currentNode)) { if (currentNode.flags & ts.NodeFlags.Namespace) { const newNamespace = { kind: Ui5TypeInfoKind.Namespace, name: currentNode.name.text, }; if (currentNamespaceTypeInfo) { currentNamespaceTypeInfo.parent = newNamespace; } else { namespaceTypeInfo = newNamespace; } currentNamespaceTypeInfo = newNamespace; } else if (currentNode.parent && ts.isSourceFile(currentNode.parent)) { // Only consider top-level module declarations moduleName = currentNode.name.text; break; } } currentNode = currentNode.parent; } const libraryName = getLibraryNameFromSourceFile(sourceFile); let leafNode; if (namespaceTypeInfo) { leafNode = namespaceTypeInfo; } if (moduleName) { const moduleNode = { kind: Ui5TypeInfoKind.Module, name: moduleName, library: libraryName, }; if (!namespaceTypeInfo) { leafNode = moduleNode; } else { namespaceTypeInfo.parent = moduleNode; } } if (!leafNode) { // No module or namespace found, ignore return; } let currentTypeInfo; if (moduleName && apiExtract) { currentTypeInfo ??= createMangedObjectSettingsTypeInfo(node, moduleName, leafNode, apiExtract); } currentTypeInfo ??= createClassTypeInfo(node, leafNode); currentTypeInfo ??= createEnumMemberTypeInfo(node, leafNode); currentTypeInfo ??= createFunctionTypeInfo(node, leafNode); currentTypeInfo ??= createExportTypeInfo(node, leafNode); return currentTypeInfo; } export function getModuleTypeInfo(node) { if (node.kind === Ui5TypeInfoKind.Module) { return node; } while (node.parent) { if (node.parent.kind === Ui5TypeInfoKind.Module) { return node.parent; } node = node.parent; } return undefined; } export function getNamespace(node) { let namespace; while (node) { if (node.kind === Ui5TypeInfoKind.Namespace) { namespace = node.name + (namespace ? "." + namespace : ""); } if ("parent" in node && node.parent) { node = node.parent; } else { break; } } return namespace; } function createMangedObjectSettingsTypeInfo(node, moduleName, parent, apiExtract) { if (ts.isPropertySignature(node)) { // Potentially a UI5 metadata property in a constructor call const parentNode = node.parent; if (ts.isInterfaceDeclaration(parentNode) && isInterfaceInheritingFromManagedObjectSettings(parentNode)) { // This is a UI5 metadata property const className = moduleName.replace(/\//g, "."); const name = getPropertyNameText(node.name); if (className && name) { let kind = undefined; if (apiExtract.isProperty(className, name)) { kind = Ui5TypeInfoKind.MetadataProperty; } else if (apiExtract.isAggregation(className, name)) { kind = Ui5TypeInfoKind.MetadataAggregation; } else if (apiExtract.isAssociation(className, name)) { kind = Ui5TypeInfoKind.MetadataAssociation; } else if (apiExtract.isEvent(className, name)) { kind = Ui5TypeInfoKind.MetadataEvent; } // else: Special settings, etc. if (kind) { return { kind, name, parent: { kind: Ui5TypeInfoKind.ManagedObjectSettings, name: parentNode.name.text, parent, }, }; } } } } } function createClassTypeInfo(node, parent) { if (ts.isClassDeclaration(node) && node.name) { return { kind: Ui5TypeInfoKind.Class, name: node.name.text, parent, }; } let classNode = node.parent; const skippedNodes = []; while (!ts.isInterfaceDeclaration(classNode) && !ts.isClassDeclaration(classNode)) { if (!classNode.parent) { return; } skippedNodes.unshift(classNode); classNode = classNode.parent; } if (!classNode.name) { // Class name can be undefined in `export default class { ... }`. But we generally don't expect // to encounter this case in the UI5 types so we can safely ignore it and keeps our types greedy. return; } const classTypeInfo = { kind: Ui5TypeInfoKind.Class, name: classNode.name.text, parent, }; if (ts.isMethodSignature(node) || ts.isMethodDeclaration(node)) { if (skippedNodes.length) { throw new Error(`Unexpected nested method declaration: ${skippedNodes.map((n) => n.getText()).join(" -> ")}`); } const name = getPropertyNameText(node.name); if (!name) { // We need a name for methods and properties return undefined; } return { kind: hasStaticModifier(node) ? Ui5TypeInfoKind.StaticMethod : Ui5TypeInfoKind.Method, name, parent: classTypeInfo, }; } if (ts.isPropertySignature(node) || ts.isPropertyDeclaration(node) || ts.isParameter(node)) { let parent = classTypeInfo; for (const skippedNode of skippedNodes) { if (ts.isConstructorDeclaration(skippedNode)) { parent = { kind: Ui5TypeInfoKind.Constructor, name: "constructor", parent, }; } else if (ts.isParameter(skippedNode)) { parent = { kind: Ui5TypeInfoKind.ConstructorParameter, name: skippedNode.name.getText(), parent, }; } } if (!ts.isPropertyName(node.name)) { return; } const name = getPropertyNameText(node.name); if (!name) { // We need a name for methods and properties return undefined; } if (ts.isParameter(node)) { return { kind: Ui5TypeInfoKind.ConstructorParameter, name, parent: parent, }; } // PropertySignature or PropertyDeclaration return { kind: hasStaticModifier(node) ? Ui5TypeInfoKind.StaticProperty : Ui5TypeInfoKind.Property, name, parent, }; } } function createEnumTypeInfo(node, parent) { if (!ts.isEnumDeclaration(node)) { return; } return { kind: Ui5TypeInfoKind.Enum, name: node.name.text, parent, }; } function createEnumMemberTypeInfo(node, parent) { if (ts.isEnumDeclaration(node)) { return createEnumTypeInfo(node, parent); } if (!ts.isEnumMember(node)) { return; } const enumTypeInfo = createEnumTypeInfo(node.parent, parent); if (!enumTypeInfo) { return; } const name = getPropertyNameText(node.name); if (!name) { // We need a name for enum members return; } return { kind: Ui5TypeInfoKind.EnumMember, name, parent: enumTypeInfo, }; } function createFunctionTypeInfo(node, parent) { if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node) && !ts.isArrowFunction(node)) { return; } if (!node.name) { // Ignore anonymous functions return; } return { kind: Ui5TypeInfoKind.Function, name: node.name.text, parent, }; } function createExportTypeInfo(node, parent) { if (ts.isExportAssignment(node)) { // Exported module return { kind: Ui5TypeInfoKind.Export, name: node.isExportEquals ? "default" : "export", // export default foo vs. export = foo parent, }; } else if (ts.isExportSpecifier(node)) { // Exported symbol const name = getPropertyNameText(node.name); if (!name) { // We need a name for exports return; } return { kind: Ui5TypeInfoKind.Export, name, parent, }; } return; } function hasStaticModifier(node) { if (!ts.canHaveModifiers(node)) { return false; } return ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword); } //# sourceMappingURL=Ui5TypeInfo.js.map