vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
424 lines (423 loc) • 19.2 kB
JavaScript
import { BaseLanguageHandler } from './base.js';
import { getNodeText } from '../astAnalyzer.js';
import logger from '../../../logger.js';
import path from 'path';
export class JsonHandler extends BaseLanguageHandler {
options;
getFunctionQueryPatterns() {
return [
'pair',
'object',
'array'
];
}
getClassQueryPatterns() {
return [
'document',
'object'
];
}
getImportQueryPatterns() {
return [
'pair'
];
}
extractFunctionName(node, sourceCode, _options) {
try {
if (node.type === 'pair') {
const keyNode = node.childForFieldName('key');
if (keyNode) {
const key = getNodeText(keyNode, sourceCode).replace(/^["']|["']$/g, '');
if (['function', 'handler', 'callback', 'run', 'script', 'command', 'exec', 'test'].includes(key)) {
return `${key}_function`;
}
if (this.isInOpenApiContext(node, sourceCode)) {
const valueNode = node.childForFieldName('value');
if (valueNode && valueNode.type === 'object') {
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}`;
}
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';
}
}
if (this.isInPackageJsonContext(node, sourceCode) && key === 'scripts') {
return 'npm_scripts';
}
if (this.isInTsConfigContext(node, sourceCode) && key === 'compilerOptions') {
return 'compiler_options';
}
return key;
}
}
if (node.type === 'array') {
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';
}
if (node.type === 'object') {
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';
}
}
isInOpenApiContext(node, sourceCode) {
try {
if (this.options?.filePath) {
const filename = path.basename(this.options.filePath).toLowerCase();
if (filename.includes('swagger') ||
filename.includes('openapi') ||
filename.includes('api')) {
return true;
}
}
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;
}
}
isInCloudFormationContext(node, sourceCode) {
try {
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;
}
}
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;
}
}
isInPackageJsonContext(node, sourceCode) {
try {
if (this.options?.filePath) {
const filename = path.basename(this.options.filePath).toLowerCase();
if (filename === 'package.json') {
return true;
}
}
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;
}
}
isInTsConfigContext(node, sourceCode) {
try {
if (this.options?.filePath) {
const filename = path.basename(this.options.filePath).toLowerCase();
if (filename === 'tsconfig.json' || filename.startsWith('tsconfig.')) {
return true;
}
}
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;
}
}
extractClassName(node, sourceCode) {
try {
if (node.type === 'document') {
if (this.isInOpenApiContext(node, sourceCode)) {
return 'OpenAPI_Document';
}
if (this.isInCloudFormationContext(node, sourceCode)) {
return 'CloudFormation_Template';
}
if (this.isInPackageJsonContext(node, sourceCode)) {
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';
}
if (this.isInTsConfigContext(node, sourceCode)) {
return 'TSConfig';
}
if (this.options?.filePath) {
return `JSON_${path.basename(this.options.filePath, path.extname(this.options.filePath))}`;
}
}
else if (node.type === 'object') {
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';
}
}
extractImportPath(node, sourceCode) {
try {
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';
}
}
extractFunctionComment(node, sourceCode) {
try {
if (this.isInOpenApiContext(node, sourceCode) && node.type === 'pair') {
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, '');
}
}
}
}
}
}
return undefined;
}
catch (error) {
logger.warn({ err: error, nodeType: node.type }, 'Error extracting JSON function comment');
return undefined;
}
}
extractClassComment(node, sourceCode) {
try {
if (this.isInOpenApiContext(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, '') === 'info') {
const valueNode = child.childForFieldName('value');
if (valueNode && valueNode.type === '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, '');
}
}
}
}
}
}
}
}
}
}
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;
}
}
detectFramework(sourceCode) {
try {
if ((sourceCode.includes('"swagger"') || sourceCode.includes('"openapi"')) &&
sourceCode.includes('"paths"')) {
return 'openapi';
}
if (sourceCode.includes('"AWSTemplateFormatVersion"') ||
(sourceCode.includes('"Resources"') && sourceCode.includes('"Type"'))) {
return 'cloudformation';
}
if (sourceCode.includes('"name"') &&
sourceCode.includes('"version"') &&
(sourceCode.includes('"dependencies"') || sourceCode.includes('"devDependencies"'))) {
return 'npm';
}
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;
}
}
}