getImports
Analyze PureScript code to extract all import statements, revealing external module dependencies. Works independently of IDE servers, supporting absolute file paths or direct code input.
Instructions
Find all import statements in PureScript code (like 'import Data.List', 'import Prelude'). Shows what external modules the code depends on. Works without the IDE server.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| code | No | PureScript code string. | |
| filePath | No | Absolute path to the PureScript file. Only absolute paths are supported. |
Implementation Reference
- index.js:823-847 (handler)Handler function for the 'getImports' MCP tool. Parses PureScript code with Tree-sitter, queries for import statements, extracts module names and full paths from qualified_module nodes."getImports": async (args) => { if (!treeSitterInitialized) throw new Error("Tree-sitter not initialized."); const code = await getCodeFromInput(args, true); const tree = purescriptTsParser.parse(code); const query = new Query(PureScriptLanguage, `(import module: (qualified_module) @import.path)`); const captures = query.captures(tree.rootNode); const imports = []; for (const capture of captures) { if (capture.name === 'import.path') { const moduleNodes = capture.node.children.filter(child => child.type === 'module'); if (moduleNodes.length > 0) { const fullPath = moduleNodes.map(n => n.text).join('.'); const moduleName = moduleNodes[0].text; const submoduleName = moduleNodes.length > 1 ? moduleNodes[1].text : undefined; imports.push({ module: moduleName, submodule: submoduleName, fullPath: fullPath }); } } } return { content: [{ type: "text", text: JSON.stringify(imports, null, 2) }] }; },
- index.js:588-600 (schema)Input schema definition for the 'getImports' tool, specifying parameters: filePath (absolute) or code string.{ name: "getImports", description: "Find all import statements in PureScript code (like 'import Data.List', 'import Prelude'). Shows what external modules the code depends on. Works without the IDE server.", inputSchema: { type: "object", properties: { filePath: { type: "string", description: "Absolute path to the PureScript file. Only absolute paths are supported." }, code: { type: "string", description: "PureScript code string." } }, additionalProperties: false, description: "Exactly one of 'filePath' or 'code' must be provided." } },
- index.js:791-1127 (registration)The INTERNAL_TOOL_HANDLERS object registers the 'getImports' handler function, used in 'tools/call' method handling.const INTERNAL_TOOL_HANDLERS = { "get_server_status": internalHandleGetServerStatus, "echo": internalHandleEcho, "query_purescript_ast": internalHandleQueryPurescriptAst, "start_purs_ide_server": internalHandleStartPursIdeServer, "stop_purs_ide_server": internalHandleStopPursIdeServer, "query_purs_ide": internalHandleQueryPursIde, "generate_dependency_graph": internalHandleGenerateDependencyGraph, // --- Phase 1: Core AST Query Tool Handlers (to be added) --- "getModuleName": async (args) => { if (!treeSitterInitialized) throw new Error("Tree-sitter not initialized."); const code = await getCodeFromInput(args, true); const tree = purescriptTsParser.parse(code); // Corrected query to capture the full text of the qualified_module node const query = new Query(PureScriptLanguage, `(purescript name: (qualified_module) @module.qname)`); const captures = query.captures(tree.rootNode); if (captures.length > 0 && captures[0].name === 'module.qname') { // The text of the qualified_module node itself is the full module name return { content: [{ type: "text", text: JSON.stringify(captures[0].node.text.replace(/\s+/g, ''), null, 2) }] }; } return { content: [{ type: "text", text: JSON.stringify(null, null, 2) }] }; }, "getFunctionNames": async (args) => { if (!treeSitterInitialized) throw new Error("Tree-sitter not initialized."); const code = await getCodeFromInput(args, false); const tree = purescriptTsParser.parse(code); const query = new Query(PureScriptLanguage, `(function name: (variable) @func.name)`); const captures = query.captures(tree.rootNode); const functionNames = captures.map(capture => capture.node.text); return { content: [{ type: "text", text: JSON.stringify(functionNames, null, 2) }] }; }, // Stubs for other Phase 1 handlers - to be implemented "getImports": async (args) => { if (!treeSitterInitialized) throw new Error("Tree-sitter not initialized."); const code = await getCodeFromInput(args, true); const tree = purescriptTsParser.parse(code); const query = new Query(PureScriptLanguage, `(import module: (qualified_module) @import.path)`); const captures = query.captures(tree.rootNode); const imports = []; for (const capture of captures) { if (capture.name === 'import.path') { const moduleNodes = capture.node.children.filter(child => child.type === 'module'); if (moduleNodes.length > 0) { const fullPath = moduleNodes.map(n => n.text).join('.'); const moduleName = moduleNodes[0].text; const submoduleName = moduleNodes.length > 1 ? moduleNodes[1].text : undefined; imports.push({ module: moduleName, submodule: submoduleName, fullPath: fullPath }); } } } return { content: [{ type: "text", text: JSON.stringify(imports, null, 2) }] }; }, "getTopLevelDeclarationNames": async (args) => { if (!treeSitterInitialized) throw new Error("Tree-sitter not initialized."); const code = await getCodeFromInput(args, true); // true for module-oriented const tree = purescriptTsParser.parse(code); const querySource = ` [ (function name: (variable) @name) (data name: (type) @name) (class_declaration (class_head (class_name (type) @name))) (type_alias name: (type) @name) (foreign_import name: (variable) @name) (signature name: (variable) @name) (class_instance (instance_name) @name) (kind_value_declaration name: (type) @name) ] `; const query = new Query(PureScriptLanguage, querySource); const captures = query.captures(tree.rootNode); const declarationNames = captures.map(capture => capture.node.text).filter(Boolean); // Deduplicate names const uniqueNames = [...new Set(declarationNames)]; return { content: [{ type: "text", text: JSON.stringify(uniqueNames, null, 2) }] }; }, "getIntegerLiterals": async (args) => { if (!treeSitterInitialized) throw new Error("Tree-sitter not initialized."); const code = await getCodeFromInput(args, false); const tree = purescriptTsParser.parse(code); const integerLiterals = []; const query = new Query(PureScriptLanguage, `(integer) @integer.literal`); const captures = query.captures(tree.rootNode); captures.forEach(capture => { if (capture.name === 'integer.literal') { integerLiterals.push(parseInt(capture.node.text, 10)); } }); return { content: [{ type: "text", text: JSON.stringify(integerLiterals, null, 2) }] }; }, "getWhereBindings": async (args) => { if (!treeSitterInitialized) throw new Error("Tree-sitter not initialized."); const code = await getCodeFromInput(args, false); // Snippet-oriented const tree = purescriptTsParser.parse(code); const whereClausesText = []; // Query for 'where' keyword followed by a 'declarations' block within a function or let binding const querySource = ` (function (where) @where_keyword (declarations) @declarations_block) `; // Also consider 'let' bindings with 'where' clauses, though less common for top-level 'where' // (let_binding (where) @where_keyword (declarations) @declarations_block) const query = new Query(PureScriptLanguage, querySource); const matches = query.matches(tree.rootNode); for (const match of matches) { const whereKeywordNode = match.captures.find(c => c.name === 'where_keyword')?.node; const declarationsNode = match.captures.find(c => c.name === 'declarations_block')?.node; if (whereKeywordNode && declarationsNode) { // Construct the text from "where" keyword to the end of the declarations block // This requires careful handling of start and end positions if they are not contiguous in the source text string // For simplicity, if they are siblings and in order, we can take text from start of 'where' to end of 'declarations' // A safer way is to combine their individual texts if they represent the full conceptual block const fullWhereClauseText = `${whereKeywordNode.text} ${declarationsNode.text}`; whereClausesText.push(fullWhereClauseText.trim()); } } // Deduplicate, as some complex structures might yield multiple partial matches const uniqueWhereClauses = [...new Set(whereClausesText)]; return { content: [{ type: "text", text: JSON.stringify(uniqueWhereClauses, null, 2) }] }; }, "getTopLevelDeclarations": async (args) => { if (!treeSitterInitialized) throw new Error("Tree-sitter not initialized."); const code = await getCodeFromInput(args, true); // true for module-oriented const tree = purescriptTsParser.parse(code); const querySource = ` [ (function name: (variable) @name.function) @DeclValue (data name: (type) @name.data_type) @DeclData (class_declaration (class_head (class_name (type) @name.class))) @DeclClass (type_alias name: (type) @name.type_alias) @DeclType (newtype name: (type) @name.newtype) @DeclNewtype (foreign_import name: (variable) @name.foreign) @DeclForeign (signature name: (variable) @name.signature) @DeclSignature (class_instance (instance_head (class_name) @name.instance_class (type_name)? @name.instance_type)) @DeclInstanceChain (kind_value_declaration name: (type) @name.kind_sig) @DeclKindSignature (derive_declaration) @DeclDerive (type_role_declaration (type) @name.role_type (type_role)+ @name.role_value) @DeclRole (operator_declaration (operator) @name.operator) @DeclFixity ] `; const query = new Query(PureScriptLanguage, querySource); const matches = query.matches(tree.rootNode); const rawDeclarations = []; for (const match of matches) { const mainCapture = match.captures.find(c => c.name.startsWith("Decl")); if (!mainCapture) continue; const declNode = mainCapture.node; const mappedType = mainCapture.name; const value = declNode.text; // This is the full text of the declaration node // Create a map of captures for efficient lookup // Store the full capture object {name, node} as node properties (like .text) are needed const allCapturesMap = new Map(match.captures.map(c => [c.name, c])); let finalName; // Prioritized list of capture names that directly provide the 'name' const singleNameCaptureKeys = [ "name.function", "name.data_type", "name.class", "name.type_alias", "name.newtype", "name.foreign", "name.signature", "name.kind_sig", "name.role_type", "name.operator" ]; let foundSingleName = false; for (const key of singleNameCaptureKeys) { if (allCapturesMap.has(key)) { finalName = allCapturesMap.get(key).node.text; foundSingleName = true; break; } } if (!foundSingleName) { if (allCapturesMap.has("name.instance_class")) { finalName = allCapturesMap.get("name.instance_class").node.text; if (allCapturesMap.has("name.instance_type")) { finalName += ` ${allCapturesMap.get("name.instance_type").node.text}`; } } else if (mappedType === "DeclDerive" || mappedType === "DeclFixity") { // declNode is mainCapture.node, which is already available const firstIdentNode = declNode.descendantsOfType("identifier")[0] || declNode.descendantsOfType("type")[0] || declNode.descendantsOfType("operator")[0]; finalName = firstIdentNode ? firstIdentNode.text : `complex_${mappedType.toLowerCase().replace('decl', '')}`; } else { finalName = "unknown"; // Default if no other specific name found } } rawDeclarations.push({ name: finalName, type: mappedType, value, treeSitterType: declNode.type }); // Removed node property } let declarations = rawDeclarations; // Use rawDeclarations directly without consolidation // Apply filters if provided if (args.filters) { const { name, type, value } = args.filters; if (name) { const nameRegex = new RegExp(name); declarations = declarations.filter(d => nameRegex.test(d.name)); } if (type) { const typeRegex = new RegExp(type); declarations = declarations.filter(d => typeRegex.test(d.type)); } if (value) { const valueRegex = new RegExp(value); declarations = declarations.filter(d => valueRegex.test(d.value)); } } return { content: [{ type: "text", text: JSON.stringify(declarations, null, 2) }] }; }, // --- New purs ide command wrapper handlers --- "pursIdeLoad": async (args) => { const params = args || {}; // If args is null/undefined, pass empty object for default load all const result = await sendCommandToPursIde({ command: "load", params }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; }, "pursIdeType": async (args) => { if (!args || typeof args.search !== 'string') { throw new Error("Invalid input: 'search' (string) is required for pursIdeType."); } const params = { search: args.search, filters: args.filters || [], // Default to empty filters array currentModule: args.currentModule }; const result = await sendCommandToPursIde({ command: "type", params }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; }, "pursIdeCwd": async () => { const result = await sendCommandToPursIde({ command: "cwd" }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; }, "pursIdeReset": async () => { const result = await sendCommandToPursIde({ command: "reset" }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; }, "pursIdeQuit": async () => { let quitMessage = "purs ide server quit command initiated."; let pursIdeResponded = false; if (pursIdeProcess && pursIdeIsReady) { logToStderr("[pursIdeQuit] Attempting to send 'quit' command to purs ide server.", "debug"); sendCommandToPursIde({ command: "quit" }) .then(res => { pursIdeResponded = true; logToStderr(`[pursIdeQuit] purs ide server responded to quit command: ${JSON.stringify(res)}`, 'debug'); }) .catch(err => { logToStderr(`[pursIdeQuit] Error/No response from purs ide server for quit command: ${err.message}`, 'warn'); }); // Wait a short period to allow purs ide server to shut down gracefully // or for the sendCommandToPursIde to potentially resolve/reject. await delay(250); // Increased slightly to 250ms } else { quitMessage = "No purs ide server was running or ready to send quit command to."; logToStderr("[pursIdeQuit] " + quitMessage, "info"); } // Ensure our managed process is stopped regardless of purs ide's response if (pursIdeProcess) { logToStderr("[pursIdeQuit] Ensuring managed purs ide process is stopped.", "debug"); pursIdeProcess.kill(); pursIdeProcess = null; pursIdeIsReady = false; logPursIdeOutput("Managed purs ide server process stopped via pursIdeQuit tool.", "info"); quitMessage += " Managed purs ide process has been stopped."; } else { if (!quitMessage.includes("No purs ide server was running")) { quitMessage += " No managed purs ide process was found running to stop."; } } if (pursIdeResponded) { quitMessage += " purs ide server acknowledged quit."; } else { quitMessage += " purs ide server may not have acknowledged quit before process termination."; } return { content: [{ type: "text", text: JSON.stringify({ status_message: quitMessage, resultType: "success" }, null, 2) }] }; }, "pursIdeRebuild": async (args) => { if (!args || typeof args.file !== 'string') { throw new Error("Invalid input: 'file' (string) is required for pursIdeRebuild."); } const params = { file: args.file, actualFile: args.actualFile, codegen: args.codegen // purs ide server defaults to js if undefined }; const result = await sendCommandToPursIde({ command: "rebuild", params }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; }, "pursIdeUsages": async (args) => { if (!args || typeof args.module !== 'string' || typeof args.namespace !== 'string' || typeof args.identifier !== 'string') { throw new Error("Invalid input: 'module', 'namespace', and 'identifier' (strings) are required for pursIdeUsages."); } const params = { module: args.module, namespace: args.namespace, identifier: args.identifier }; const result = await sendCommandToPursIde({ command: "usages", params }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; }, "pursIdeList": async (args) => { if (!args || typeof args.listType !== 'string') { throw new Error("Invalid input: 'listType' (string) is required for pursIdeList."); } const params = { type: args.listType }; if (args.listType === "import") { if (typeof args.file !== 'string') { throw new Error("'file' (string) is required when listType is 'import'."); } params.file = args.file; } const result = await sendCommandToPursIde({ command: "list", params }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } };