Skip to main content
Glama
scala.ts15.1 kB
/** * Scala language handler for the Code-Map Generator tool. * This file contains the language handler for Scala files. */ import { BaseLanguageHandler } from './base.js'; import { SyntaxNode } from '../parser.js'; import { FunctionExtractionOptions } from '../types.js'; import { getNodeText } from '../astAnalyzer.js'; import logger from '../../../logger.js'; import { ImportedItem } from '../codeMapModel.js'; /** * Language handler for Scala. * Provides enhanced function name detection for Scala files. */ export class ScalaHandler extends BaseLanguageHandler { /** * Gets the query patterns for function detection. */ protected getFunctionQueryPatterns(): string[] { return [ 'function_definition', 'method_definition', 'val_definition', 'var_definition', 'anonymous_function' ]; } /** * Gets the query patterns for class detection. */ protected getClassQueryPatterns(): string[] { return [ 'class_definition', 'object_definition', 'trait_definition', 'case_class_definition' ]; } /** * Gets the query patterns for import detection. */ protected getImportQueryPatterns(): string[] { return [ 'import_declaration', 'package_declaration' ]; } /** * Extracts the function name from an AST node. */ protected extractFunctionName( node: SyntaxNode, sourceCode: string, _options?: FunctionExtractionOptions ): string { try { // Handle function definitions if (node.type === 'function_definition' || node.type === 'method_definition') { const nameNode = node.childForFieldName('name'); if (nameNode) { const name = getNodeText(nameNode, sourceCode); // Check for test functions if (name.startsWith('test') || this.hasAnnotation(node, 'Test')) { return `test_${name}`; } // Check for apply/unapply methods if (name === 'apply') { return 'factory_apply'; } else if (name === 'unapply') { return 'extractor_unapply'; } return name; } } // Handle val/var definitions with function values if (node.type === 'val_definition' || node.type === 'var_definition') { const nameNode = node.childForFieldName('name'); const valueNode = node.childForFieldName('value'); if (nameNode && valueNode && (valueNode.type === 'anonymous_function' || valueNode.text.includes('=>'))) { return getNodeText(nameNode, sourceCode); } } // Handle anonymous functions if (node.type === 'anonymous_function') { // Check if assigned to a val/var if (node.parent?.type === 'val_definition' || node.parent?.type === 'var_definition') { const nameNode = node.parent.childForFieldName('name'); if (nameNode) { return getNodeText(nameNode, sourceCode); } } // Check if used in a function call if (node.parent?.type === 'argument' && node.parent.parent?.type === 'argument_list' && node.parent.parent.parent?.type === 'call_expression') { const funcNode = node.parent.parent.parent.childForFieldName('function'); if (funcNode) { const funcName = getNodeText(funcNode, sourceCode); // Common Scala higher-order functions if (['map', 'flatMap', 'filter', 'foreach', 'fold', 'reduce'].includes(funcName)) { return `${funcName}_function`; } } } return 'anonymous_function'; } return 'anonymous'; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Scala function name'); return 'anonymous'; } } /** * Checks if a function has a specific annotation. */ private hasAnnotation(node: SyntaxNode, annotationName: string): boolean { try { const annotationsNode = node.childForFieldName('annotations'); if (!annotationsNode) return false; // Check each annotation for (let i = 0; i < annotationsNode.childCount; i++) { const annotation = annotationsNode.child(i); if (annotation?.text.includes(`@${annotationName}`)) { return true; } } return false; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error checking Scala annotation'); return false; } } /** * Extracts the class name from an AST node. */ protected extractClassName(node: SyntaxNode, sourceCode: string): string { try { if (node.type === 'class_definition' || node.type === 'trait_definition' || node.type === 'case_class_definition') { const nameNode = node.childForFieldName('name'); if (nameNode) { return getNodeText(nameNode, sourceCode); } } else if (node.type === 'object_definition') { const nameNode = node.childForFieldName('name'); if (nameNode) { return `Object_${getNodeText(nameNode, sourceCode)}`; } } return 'AnonymousClass'; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Scala class name'); return 'AnonymousClass'; } } /** * Extracts the parent class from an AST node. */ protected extractParentClass(node: SyntaxNode, sourceCode: string): string | undefined { try { if (node.type === 'class_definition' || node.type === 'case_class_definition' || node.type === 'object_definition') { const extendsNode = node.childForFieldName('extends_clause'); if (extendsNode) { const typeNode = extendsNode.childForFieldName('type'); if (typeNode) { return getNodeText(typeNode, sourceCode); } } } return undefined; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Scala parent class'); return undefined; } } /** * Extracts implemented interfaces (traits) from an AST node. */ protected extractImplementedInterfaces(node: SyntaxNode, sourceCode: string): string[] | undefined { try { if (node.type === 'class_definition' || node.type === 'case_class_definition' || node.type === 'object_definition') { const withClausesNode = node.childForFieldName('with_clauses'); if (withClausesNode) { const traits: string[] = []; // Extract traits from with clauses for (let i = 0; i < withClausesNode.childCount; i++) { const withClause = withClausesNode.child(i); if (withClause?.type === 'with_clause') { const typeNode = withClause.childForFieldName('type'); if (typeNode) { traits.push(getNodeText(typeNode, sourceCode)); } } } return traits.length > 0 ? traits : undefined; } } return undefined; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Scala implemented interfaces'); return undefined; } } /** * Extracts the import path from an AST node. */ protected extractImportPath(node: SyntaxNode, sourceCode: string): string { try { if (node.type === 'import_declaration') { const importeeNode = node.childForFieldName('importee'); if (importeeNode) { return getNodeText(importeeNode, sourceCode); } } else if (node.type === 'package_declaration') { const refNode = node.childForFieldName('ref'); if (refNode) { return getNodeText(refNode, sourceCode); } } return 'unknown'; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Scala import path'); return 'unknown'; } } /** * Extracts imported items from an AST node. */ protected extractImportedItems(node: SyntaxNode, sourceCode: string): ImportedItem[] | undefined { try { // Handle import declarations (import scala.collection.mutable.{Map, Set}) if (node.type === 'import_declaration') { const importeeNode = node.childForFieldName('importee'); if (importeeNode) { const fullPath = getNodeText(importeeNode, sourceCode); // Check for different types of Scala imports // Case 1: Wildcard import - import scala.collection._ if (fullPath.endsWith('._')) { const basePath = fullPath.substring(0, fullPath.length - 2); return [{ name: '*', path: basePath, isDefault: false, isNamespace: true, nodeText: node.text, // Add Scala-specific metadata isWildcardImport: true }]; } // Case 2: Selector import - import scala.collection.mutable.{Map, Set} else if (fullPath.includes('{') && fullPath.includes('}')) { const basePath = fullPath.substring(0, fullPath.indexOf('{')); const selectorsText = fullPath.substring( fullPath.indexOf('{') + 1, fullPath.lastIndexOf('}') ); // Split selectors by comma, handling potential whitespace const selectors = selectorsText.split(',').map(s => s.trim()); const items: ImportedItem[] = []; for (const selector of selectors) { // Handle renamed imports - Map => MutableMap if (selector.includes('=>')) { const [originalName, alias] = selector.split('=>').map(s => s.trim()); items.push({ name: originalName, path: basePath + originalName, alias: alias, isDefault: false, isNamespace: false, nodeText: selector, // Add Scala-specific metadata isSelectorImport: true }); } // Handle regular selector imports else { items.push({ name: selector, path: basePath + selector, isDefault: false, isNamespace: false, nodeText: selector, // Add Scala-specific metadata isSelectorImport: true }); } } return items.length > 0 ? items : undefined; } // Case 3: Simple import - import scala.collection.mutable.Map else { const parts = fullPath.split('.'); const name = parts[parts.length - 1]; return [{ name: name, path: fullPath, isDefault: false, isNamespace: false, nodeText: node.text }]; } } } // Handle package declarations (package com.example.app) else if (node.type === 'package_declaration') { const refNode = node.childForFieldName('ref'); if (refNode) { const packagePath = getNodeText(refNode, sourceCode); return [{ name: packagePath, path: packagePath, isDefault: false, isNamespace: true, nodeText: node.text, // Add Scala-specific metadata isPackageDeclaration: true }]; } } return undefined; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Scala imported items'); return undefined; } } /** * Extracts the function comment from an AST node. */ protected extractFunctionComment(node: SyntaxNode, _sourceCode: string): string | undefined { try { // Look for Scaladoc comments const current = node; let prev = current.previousNamedSibling; while (prev && prev.type !== 'comment') { prev = prev.previousNamedSibling; } if (prev && prev.type === 'comment' && prev.text.startsWith('/**')) { return this.parseScaladocComment(prev.text); } return undefined; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Scala function comment'); return undefined; } } /** * Extracts the class comment from an AST node. */ protected extractClassComment(node: SyntaxNode, _sourceCode: string): string | undefined { try { // Look for Scaladoc comments const current = node; let prev = current.previousNamedSibling; while (prev && prev.type !== 'comment') { prev = prev.previousNamedSibling; } if (prev && prev.type === 'comment' && prev.text.startsWith('/**')) { return this.parseScaladocComment(prev.text); } return undefined; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Scala class comment'); return undefined; } } /** * Parses a Scaladoc comment into a clean comment. */ private parseScaladocComment(comment: string): string { try { // Remove comment markers and asterisks const text = comment.substring(3, comment.length - 2); // Split into lines and remove leading asterisks and whitespace const lines = text.split('\n') .map(line => line.trim().replace(/^\*\s*/, '')) .filter(line => !line.startsWith('@')); // Remove tag lines // Join lines and return the description return lines.join(' ').trim(); } catch (error) { logger.warn({ err: error }, 'Error parsing Scaladoc comment'); return comment; } } /** * Detects the framework used in the source code. */ detectFramework(sourceCode: string): string | null { try { // Akka detection if (sourceCode.includes('import akka.') || sourceCode.includes('extends Actor') || sourceCode.includes('ActorSystem')) { return 'akka'; } // Play Framework detection if (sourceCode.includes('import play.') || sourceCode.includes('extends Controller') || sourceCode.includes('Action {')) { return 'play'; } // Spark detection if (sourceCode.includes('import org.apache.spark') || sourceCode.includes('SparkContext') || sourceCode.includes('SparkSession')) { return 'spark'; } // Cats/Cats Effect detection if (sourceCode.includes('import cats.') || sourceCode.includes('import cats.effect.') || sourceCode.includes('extends IOApp')) { return 'cats'; } return null; } catch (error) { logger.warn({ err: error }, 'Error detecting Scala framework'); return null; } } }

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/freshtechbro/vibe-coder-mcp'

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