Skip to main content
Glama

analyze_ui5_performance

Analyze SAPUI5 XML and JavaScript files to identify performance issues and receive actionable recommendations for optimization.

Instructions

Analyze UI5 XML/JS files with performance-focused rules and actionable recommendations.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sourceDirNo
maxFilesNo
maxFindingsNo

Implementation Reference

  • Main tool definition with the async handler that scans UI5 XML/JS files for performance issues. The handler lists tracked files, analyzes each XML and JS file for performance anti-patterns, and returns findings with severity ratings.
    export const analyzeUi5PerformanceTool = {
      name: "analyze_ui5_performance",
      description: "Analyze UI5 XML/JS files with performance-focused rules and actionable recommendations.",
      inputSchema,
      outputSchema,
      async handler(args, { context }) {
        const { sourceDir, maxFiles, maxFindings } = inputSchema.parse(args);
        const root = context.rootDir;
        const resolvedSourceDir = normalizeRelativePath(sourceDir ?? DEFAULT_SOURCE_DIR);
        const files = await listTrackedFiles({
          root,
          sourceDir: resolvedSourceDir,
          maxFiles: maxFiles ?? DEFAULT_MAX_FILES
        });
    
        const findings = [];
        let xmlFiles = 0;
        let jsFiles = 0;
        for (const relativePath of files) {
          const extension = path.extname(relativePath).toLowerCase();
          const content = await readTextFile(relativePath, root);
          if (extension === ".xml") {
            xmlFiles += 1;
            analyzeXmlFile(relativePath, content, findings);
          } else if (extension === ".js") {
            jsFiles += 1;
            analyzeJavaScriptFile(relativePath, content, findings);
          }
        }
    
        const effectiveMaxFindings = maxFindings ?? DEFAULT_MAX_FINDINGS;
        const truncated = findings.length > effectiveMaxFindings;
        const slicedFindings = findings.slice(0, effectiveMaxFindings);
        const byRule = {};
        const bySeverity = { low: 0, medium: 0, high: 0 };
        for (const finding of slicedFindings) {
          bySeverity[finding.severity] += 1;
          byRule[finding.rule] = (byRule[finding.rule] ?? 0) + 1;
        }
    
        return outputSchema.parse({
          sourceDir: resolvedSourceDir,
          scanned: {
            files: files.length,
            xmlFiles,
            jsFiles
          },
          summary: {
            totalFindings: slicedFindings.length,
            bySeverity,
            byRule,
            truncated
          },
          findings: slicedFindings
        });
      }
    };
  • Input schema (sourceDir, maxFiles, maxFindings) and output schema (scanned stats, summary with severity/rule counts, findings array) defined using Zod for validation.
    const inputSchema = z.object({
      sourceDir: z.string().min(1).optional(),
      maxFiles: z.number().int().min(10).max(2000).optional(),
      maxFindings: z.number().int().min(10).max(1000).optional()
    }).strict();
    
    const findingSchema = z.object({
      rule: z.string(),
      severity: z.enum(["low", "medium", "high"]),
      file: z.string(),
      line: z.number().int().positive().nullable(),
      message: z.string(),
      suggestion: z.string(),
      category: z.enum(["xml", "javascript"])
    });
    
    const outputSchema = z.object({
      sourceDir: z.string(),
      scanned: z.object({
        files: z.number().int().nonnegative(),
        xmlFiles: z.number().int().nonnegative(),
        jsFiles: z.number().int().nonnegative()
      }),
      summary: z.object({
        totalFindings: z.number().int().nonnegative(),
        bySeverity: z.object({
          low: z.number().int().nonnegative(),
          medium: z.number().int().nonnegative(),
          high: z.number().int().nonnegative()
        }),
        byRule: z.record(z.number().int().nonnegative()),
        truncated: z.boolean()
      }),
      findings: z.array(findingSchema)
    });
  • Import of analyzeUi5PerformanceTool from the ui5/analyzePerformance.js module.
    import { analyzeUi5PerformanceTool } from "./ui5/analyzePerformance.js";
  • Registration of analyzeUi5PerformanceTool in the allTools export array, making it available to the MCP server.
    analyzeUi5PerformanceTool,
  • analyzeUi5Xml function that parses UI5 XML content to extract controls, bindings, events, and models - used by the handler for XML performance analysis.
    export function analyzeUi5Xml(code) {
      if (typeof code !== "string" || code.trim().length === 0) {
        throw new ToolError("XML content must be a non-empty string.", {
          code: "INVALID_XML_INPUT"
        });
      }
    
      const validation = XMLValidator.validate(code);
      if (validation !== true) {
        const detail = validation?.err ?? {};
        throw new ToolError(`Invalid UI5 XML: ${detail.msg ?? "Unknown parse error."}`, {
          code: "UI5_XML_PARSE_ERROR",
          details: {
            line: detail.line ?? null,
            col: detail.col ?? null
          }
        });
      }
    
      let parsed;
      try {
        parsed = parser.parse(code);
      } catch (error) {
        throw new ToolError(`Unable to parse UI5 XML: ${error.message}`, {
          code: "UI5_XML_PARSE_ERROR"
        });
      }
    
      const rootEntry = findRootEntry(parsed);
      if (!rootEntry) {
        throw new ToolError("Unable to detect XML root element.", {
          code: "UI5_XML_ROOT_NOT_FOUND"
        });
      }
    
      const [rootTag, rootNode] = rootEntry;
      const namespaces = {};
      const controls = [];
      const bindings = [];
      const events = [];
      const models = new Set();
    
      walkElement(rootTag, rootNode, rootTag);
    
      return {
        documentType: detectDocumentType(rootTag),
        rootTag,
        namespaces,
        controls,
        bindings,
        events,
        models: Array.from(models)
      };
    
      function walkElement(tagName, node, currentPath) {
        if (!node || typeof node !== "object") {
          return;
        }
    
        const attributes = extractAttributes(node);
        registerNamespaces(attributes, namespaces);
        const { prefix, localName } = splitTag(tagName);
        controls.push({
          tag: tagName,
          localName,
          namespacePrefix: prefix,
          path: currentPath
        });
    
        for (const [attrName, rawValue] of Object.entries(attributes)) {
          if (typeof rawValue !== "string") {
            continue;
          }
    
          const bindingExpressions = extractBindingExpressions(rawValue);
          for (const expression of bindingExpressions) {
            const parsedBinding = parseBindingExpression(expression);
            bindings.push({
              path: currentPath,
              tag: tagName,
              attribute: attrName,
              expression,
              type: parsedBinding.type,
              model: parsedBinding.model,
              bindingPath: parsedBinding.bindingPath
            });
            if (parsedBinding.model) {
              models.add(parsedBinding.model);
            }
          }
    
          if (isEventAttribute(attrName, rawValue, bindingExpressions.length > 0)) {
            events.push({
              path: currentPath,
              tag: tagName,
              event: attrName,
              handler: rawValue.trim()
            });
          }
        }
    
        for (const [childTag, childValue] of Object.entries(node)) {
          if (isAttributeKey(childTag) || childTag === "#text" || childTag === "#cdata-section") {
            continue;
          }
          for (const childNode of normalizeNodeCollection(childValue)) {
            walkElement(childTag, childNode, `${currentPath}/${childTag}`);
          }
        }
      }
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/santiagosanmartinn/mcpui5server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server