Skip to main content
Glama
json.js22.5 kB
/** * JSON language handler for the Code-Map Generator tool. * This file contains the language handler for JSON files. */ import { BaseLanguageHandler } from './base.js'; import { getNodeText } from '../astAnalyzer.js'; import logger from '../../../logger.js'; import path from 'path'; /** * Language handler for JSON. * Provides enhanced function name detection for JSON files. */ export class JsonHandler extends BaseLanguageHandler { /** * Options for the handler. */ options; /** * Gets the query patterns for function detection. */ getFunctionQueryPatterns() { return [ 'pair', 'object', 'array' ]; } /** * Gets the query patterns for class detection. */ getClassQueryPatterns() { return [ 'document', 'object' ]; } /** * Gets the query patterns for import detection. */ getImportQueryPatterns() { return [ 'pair' ]; } /** * Extracts the function name from an AST node. */ extractFunctionName(node, sourceCode, options) { try { // Handle key-value pairs if (node.type === 'pair') { const keyNode = node.childForFieldName('key'); if (keyNode) { const key = getNodeText(keyNode, sourceCode).replace(/^["']|["']$/g, ''); // Check for common function-like keys if (['function', 'handler', 'callback', 'run', 'script', 'command', 'exec', 'test'].includes(key)) { return `${key}_function`; } // Check for API endpoints in OpenAPI/Swagger if (this.isInOpenApiContext(node, sourceCode)) { const valueNode = node.childForFieldName('value'); if (valueNode && valueNode.type === 'object') { // Look for HTTP methods for (let i = 0; i < valueNode.childCount; i++) { const child = valueNode.child(i); if (child?.type === 'pair') { const methodKeyNode = child.childForFieldName('key'); if (methodKeyNode) { const method = getNodeText(methodKeyNode, sourceCode).replace(/^["']|["']$/g, ''); if (['get', 'post', 'put', 'delete', 'patch', 'options', 'head'].includes(method.toLowerCase())) { return `${method.toUpperCase()}_${key}`; } } } } } return `endpoint_${key}`; } // Check for AWS CloudFormation/SAM resources if (this.isInCloudFormationContext(node, sourceCode)) { if (key === 'Type') { const valueNode = node.childForFieldName('value'); if (valueNode) { const resourceType = getNodeText(valueNode, sourceCode).replace(/^["']|["']$/g, ''); return `resource_${resourceType.split('::').pop()}`; } } if (key === 'Properties') { return 'resource_properties'; } } // Check for package.json scripts if (this.isInPackageJsonContext(node, sourceCode) && key === 'scripts') { return 'npm_scripts'; } // Check for tsconfig.json compiler options if (this.isInTsConfigContext(node, sourceCode) && key === 'compilerOptions') { return 'compiler_options'; } return key; } } // Handle arrays if (node.type === 'array') { // Check if this array is a value in a key-value pair if (node.parent?.type === 'pair') { const keyNode = node.parent.childForFieldName('key'); if (keyNode) { const key = getNodeText(keyNode, sourceCode).replace(/^["']|["']$/g, ''); return `${key}_array`; } } return 'array'; } // Handle objects if (node.type === 'object') { // Check if this object is a value in a key-value pair if (node.parent?.type === 'pair') { const keyNode = node.parent.childForFieldName('key'); if (keyNode) { const key = getNodeText(keyNode, sourceCode).replace(/^["']|["']$/g, ''); return `${key}_object`; } } return 'object'; } return 'json_element'; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting JSON function name'); return 'json_element'; } } /** * Checks if a node is in an OpenAPI/Swagger context. */ isInOpenApiContext(node, sourceCode) { try { // Check if we're in an OpenAPI file based on filename if (this.options?.filePath) { const filename = path.basename(this.options.filePath).toLowerCase(); if (filename.includes('swagger') || filename.includes('openapi') || filename.includes('api')) { return true; } } // Check for OpenAPI keywords in the document let current = node; while (current.parent) { current = current.parent; if (current.type === 'object') { for (let i = 0; i < current.childCount; i++) { const child = current.child(i); if (child?.type === 'pair') { const keyNode = child.childForFieldName('key'); if (keyNode) { const key = getNodeText(keyNode, sourceCode).replace(/^["']|["']$/g, ''); if (['swagger', 'openapi', 'paths', 'components', 'info'].includes(key)) { return true; } } } } } } return false; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error checking if JSON node is in OpenAPI context'); return false; } } /** * Checks if a node is in a CloudFormation/SAM context. */ isInCloudFormationContext(node, sourceCode) { try { // Check if we're in a CloudFormation file based on filename if (this.options?.filePath) { const filename = path.basename(this.options.filePath).toLowerCase(); if (filename.includes('cloudformation') || filename.includes('template') || filename.includes('stack') || filename.includes('sam')) { return true; } } // Check for CloudFormation keywords in the document let current = node; while (current.parent) { current = current.parent; if (current.type === 'object') { for (let i = 0; i < current.childCount; i++) { const child = current.child(i); if (child?.type === 'pair') { const keyNode = child.childForFieldName('key'); if (keyNode) { const key = getNodeText(keyNode, sourceCode).replace(/^["']|["']$/g, ''); if (['AWSTemplateFormatVersion', 'Resources', 'Outputs', 'Parameters'].includes(key)) { return true; } } } } } } return false; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error checking if JSON node is in CloudFormation context'); return false; } } /** * Checks if a node is in a package.json context. */ isInPackageJsonContext(node, sourceCode) { try { // Check if we're in a package.json file based on filename if (this.options?.filePath) { const filename = path.basename(this.options.filePath).toLowerCase(); if (filename === 'package.json') { return true; } } // Check for package.json keywords in the document let current = node; while (current.parent) { current = current.parent; if (current.type === 'object') { let hasName = false; let hasVersion = false; let hasDependencies = false; for (let i = 0; i < current.childCount; i++) { const child = current.child(i); if (child?.type === 'pair') { const keyNode = child.childForFieldName('key'); if (keyNode) { const key = getNodeText(keyNode, sourceCode).replace(/^["']|["']$/g, ''); if (key === 'name') hasName = true; if (key === 'version') hasVersion = true; if (key === 'dependencies' || key === 'devDependencies') hasDependencies = true; } } } if (hasName && hasVersion && hasDependencies) { return true; } } } return false; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error checking if JSON node is in package.json context'); return false; } } /** * Checks if a node is in a tsconfig.json context. */ isInTsConfigContext(node, sourceCode) { try { // Check if we're in a tsconfig.json file based on filename if (this.options?.filePath) { const filename = path.basename(this.options.filePath).toLowerCase(); if (filename === 'tsconfig.json' || filename.startsWith('tsconfig.')) { return true; } } // Check for tsconfig.json keywords in the document let current = node; while (current.parent) { current = current.parent; if (current.type === 'object') { for (let i = 0; i < current.childCount; i++) { const child = current.child(i); if (child?.type === 'pair') { const keyNode = child.childForFieldName('key'); if (keyNode) { const key = getNodeText(keyNode, sourceCode).replace(/^["']|["']$/g, ''); if (key === 'compilerOptions') { return true; } } } } } } return false; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error checking if JSON node is in tsconfig.json context'); return false; } } /** * Extracts the class name from an AST node. */ extractClassName(node, sourceCode) { try { if (node.type === 'document') { // For OpenAPI/Swagger documents if (this.isInOpenApiContext(node, sourceCode)) { return 'OpenAPI_Document'; } // For CloudFormation templates if (this.isInCloudFormationContext(node, sourceCode)) { return 'CloudFormation_Template'; } // For package.json if (this.isInPackageJsonContext(node, sourceCode)) { // Try to get the package name const rootObject = node.firstChild; if (rootObject?.type === 'object') { for (let i = 0; i < rootObject.childCount; i++) { const child = rootObject.child(i); if (child?.type === 'pair') { const keyNode = child.childForFieldName('key'); if (keyNode && getNodeText(keyNode, sourceCode).replace(/^["']|["']$/g, '') === 'name') { const valueNode = child.childForFieldName('value'); if (valueNode) { return `Package_${getNodeText(valueNode, sourceCode).replace(/^["']|["']$/g, '')}`; } } } } } return 'Package_JSON'; } // For tsconfig.json if (this.isInTsConfigContext(node, sourceCode)) { return 'TSConfig'; } // Default to the filename without extension if (this.options?.filePath) { return `JSON_${path.basename(this.options.filePath, path.extname(this.options.filePath))}`; } } else if (node.type === 'object') { // Check if this object is a value in a key-value pair if (node.parent?.type === 'pair') { const keyNode = node.parent.childForFieldName('key'); if (keyNode) { const key = getNodeText(keyNode, sourceCode).replace(/^["']|["']$/g, ''); return `Object_${key}`; } } } return 'JSON_Object'; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting JSON class name'); return 'JSON_Object'; } } /** * Extracts the import path from an AST node. */ extractImportPath(node, sourceCode) { try { // Handle imports in JSON (e.g., $ref, import, include) if (node.type === 'pair') { const keyNode = node.childForFieldName('key'); if (keyNode) { const key = getNodeText(keyNode, sourceCode).replace(/^["']|["']$/g, ''); if (key === '$ref' || key === 'import' || key === 'include' || key === 'extends') { const valueNode = node.childForFieldName('value'); if (valueNode) { return getNodeText(valueNode, sourceCode).replace(/^["']|["']$/g, ''); } } } } return 'unknown'; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting JSON import path'); return 'unknown'; } } /** * Extracts the function comment from an AST node. */ extractFunctionComment(node, sourceCode) { try { // Look for description fields in OpenAPI if (this.isInOpenApiContext(node, sourceCode) && node.type === 'pair') { // Check if there's a description field const parent = node.parent; if (parent) { for (let i = 0; i < parent.childCount; i++) { const child = parent.child(i); if (child?.type === 'pair') { const keyNode = child.childForFieldName('key'); if (keyNode && getNodeText(keyNode, sourceCode).replace(/^["']|["']$/g, '') === 'description') { const valueNode = child.childForFieldName('value'); if (valueNode) { return getNodeText(valueNode, sourceCode).replace(/^["']|["']$/g, ''); } } } } } } // Look for comments before the node (not typically supported in JSON) return undefined; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting JSON function comment'); return undefined; } } /** * Extracts the class comment from an AST node. */ extractClassComment(node, sourceCode) { try { // Look for description or info fields in OpenAPI if (this.isInOpenApiContext(node, sourceCode) && node.type === 'document') { const rootObject = node.firstChild; if (rootObject?.type === 'object') { // Look for info object for (let i = 0; i < rootObject.childCount; i++) { const child = rootObject.child(i); if (child?.type === 'pair') { const keyNode = child.childForFieldName('key'); if (keyNode && getNodeText(keyNode, sourceCode).replace(/^["']|["']$/g, '') === 'info') { const valueNode = child.childForFieldName('value'); if (valueNode && valueNode.type === 'object') { // Look for description in info object for (let j = 0; j < valueNode.childCount; j++) { const infoChild = valueNode.child(j); if (infoChild?.type === 'pair') { const infoKeyNode = infoChild.childForFieldName('key'); if (infoKeyNode && getNodeText(infoKeyNode, sourceCode).replace(/^["']|["']$/g, '') === 'description') { const infoValueNode = infoChild.childForFieldName('value'); if (infoValueNode) { return getNodeText(infoValueNode, sourceCode).replace(/^["']|["']$/g, ''); } } } } } } } } } } // Look for description in package.json if (this.isInPackageJsonContext(node, sourceCode) && node.type === 'document') { const rootObject = node.firstChild; if (rootObject?.type === 'object') { for (let i = 0; i < rootObject.childCount; i++) { const child = rootObject.child(i); if (child?.type === 'pair') { const keyNode = child.childForFieldName('key'); if (keyNode && getNodeText(keyNode, sourceCode).replace(/^["']|["']$/g, '') === 'description') { const valueNode = child.childForFieldName('value'); if (valueNode) { return getNodeText(valueNode, sourceCode).replace(/^["']|["']$/g, ''); } } } } } } return undefined; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting JSON class comment'); return undefined; } } /** * Detects the framework used in the source code. */ detectFramework(sourceCode) { try { // OpenAPI/Swagger detection if ((sourceCode.includes('"swagger"') || sourceCode.includes('"openapi"')) && sourceCode.includes('"paths"')) { return 'openapi'; } // AWS CloudFormation detection if (sourceCode.includes('"AWSTemplateFormatVersion"') || (sourceCode.includes('"Resources"') && sourceCode.includes('"Type"'))) { return 'cloudformation'; } // package.json detection if (sourceCode.includes('"name"') && sourceCode.includes('"version"') && (sourceCode.includes('"dependencies"') || sourceCode.includes('"devDependencies"'))) { return 'npm'; } // tsconfig.json detection if (sourceCode.includes('"compilerOptions"') && (sourceCode.includes('"target"') || sourceCode.includes('"module"'))) { return 'typescript'; } return null; } catch (error) { logger.warn({ err: error }, 'Error detecting JSON 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