From efa91c70fbc8cc1cec5e845c974e45484cfbe0d6 Mon Sep 17 00:00:00 2001 From: shivasurya Date: Sun, 9 Mar 2025 17:58:18 -0400 Subject: [PATCH 01/22] added playground app init code` --- playground/go.mod | 17 + playground/go.sum | 22 + playground/main.go | 314 +++++++++++ playground/public/static/index.html | 85 +++ playground/public/static/script.js | 773 ++++++++++++++++++++++++++++ playground/public/static/style.css | 402 +++++++++++++++ 6 files changed, 1613 insertions(+) create mode 100644 playground/go.mod create mode 100644 playground/go.sum create mode 100644 playground/main.go create mode 100644 playground/public/static/index.html create mode 100644 playground/public/static/script.js create mode 100644 playground/public/static/style.css diff --git a/playground/go.mod b/playground/go.mod new file mode 100644 index 00000000..0b99b711 --- /dev/null +++ b/playground/go.mod @@ -0,0 +1,17 @@ +module github.com/shivasurya/code-pathfinder/playground + +go 1.24.1 + +replace github.com/shivasurya/code-pathfinder/sourcecode-parser => ../sourcecode-parser + +require github.com/shivasurya/code-pathfinder/sourcecode-parser v0.0.0-00010101000000-000000000000 + +require ( + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/expr-lang/expr v1.16.9 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/posthog/posthog-go v1.2.24 // indirect + github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 // indirect + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect +) diff --git a/playground/go.sum b/playground/go.sum new file mode 100644 index 00000000..9802fc33 --- /dev/null +++ b/playground/go.sum @@ -0,0 +1,22 @@ +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI= +github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posthog/posthog-go v1.2.24 h1:A+iG4saBJemo++VDlcWovbYf8KFFNUfrCoJtsc40RPA= +github.com/posthog/posthog-go v1.2.24/go.mod h1:uYC2l1Yktc8E+9FAHJ9QZG4vQf/NHJPD800Hsm7DzoM= +github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 h1:6C8qej6f1bStuePVkLSFxoU22XBS165D3klxlzRg8F4= +github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82/go.mod h1:xe4pgH49k4SsmkQq5OT8abwhWmnzkhpgnXeekbx2efw= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/playground/main.go b/playground/main.go new file mode 100644 index 00000000..90393c7c --- /dev/null +++ b/playground/main.go @@ -0,0 +1,314 @@ +// Package main implements a web server for analyzing Java source code and executing CodeQL queries. +// It provides endpoints for code analysis, AST parsing, and visualization. +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + parser "github.com/shivasurya/code-pathfinder/sourcecode-parser/antlr" + "github.com/shivasurya/code-pathfinder/sourcecode-parser/graph" +) + +const ( + // QueryTimeout is the maximum time allowed for query execution + QueryTimeout = 60 * time.Second + + // FilePermissions for created files + FilePermissions = 0644 +) + +// Request/Response Types +type ( + // AnalyzeRequest represents the input for code analysis + AnalyzeRequest struct { + JavaSource string `json:"javaSource"` + Query string `json:"query"` + } + + // QueryResult represents a single result from code analysis + QueryResult struct { + File string `json:"file"` + Line int `json:"line"` + Snippet string `json:"snippet"` + } + + // AnalyzeResponse represents the response from code analysis + AnalyzeResponse struct { + Results []QueryResult `json:"results"` + Error string `json:"error,omitempty"` + } + + // ParseRequest represents the input for AST parsing + ParseRequest struct { + JavaSource string `json:"javaSource"` + } + + // ASTNode represents a node in the Abstract Syntax Tree + ASTNode struct { + Type string `json:"type"` + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` + Line int `json:"line"` + Children []ASTNode `json:"children,omitempty"` + } + + // ParseResponse represents the response from AST parsing + ParseResponse struct { + AST *ASTNode `json:"ast"` + Error string `json:"error,omitempty"` + } +) + +// Channel types +type resultChannel chan []QueryResult + +// HTTP Handlers + +// analyzeHandler processes POST requests to /analyze endpoint. +// It accepts Java source code and a CodeQL query, executes the query using code-pathfinder, +// and returns the query results. The execution is done with a 60-second timeout. +func analyzeHandler(w http.ResponseWriter, r *http.Request) { + start := time.Now() + defer logRequestDuration("analyzeHandler", start) + + if !validateMethod(w, r, http.MethodPost) { + return + } + + var req AnalyzeRequest + if err := decodeJSONRequest(w, r, &req); err != nil { + return + } + + tmpDir, err := createTempWorkspace("code-analysis-*") + if err != nil { + sendErrorResponse(w, "Failed to create temporary directory", err) + return + } + defer os.RemoveAll(tmpDir) + + if err := writeSourceAndQueryFiles(tmpDir, req.JavaSource, req.Query); err != nil { + sendErrorResponse(w, "Failed to write files", err) + return + } + + results, err := executeQueryWithTimeout(tmpDir, req.Query) + if err != nil { + sendErrorResponse(w, "Query execution failed", err) + return + } + + sendJSONResponse(w, AnalyzeResponse{Results: results}) +} + +// parseHandler processes POST requests to /parse endpoint. +// It accepts Java source code, parses it into an AST using code-pathfinder, +// and returns the AST structure for visualization. +func parseHandler(w http.ResponseWriter, r *http.Request) { + start := time.Now() + defer logRequestDuration("parseHandler", start) + + if !validateMethod(w, r, http.MethodPost) { + return + } + + var req ParseRequest + if err := decodeJSONRequest(w, r, &req); err != nil { + return + } + + tmpDir, err := createTempWorkspace("ast-parse-*") + if err != nil { + sendErrorResponse(w, "Failed to create temporary directory", err) + return + } + defer os.RemoveAll(tmpDir) + + if err := writeSourceFile(tmpDir, req.JavaSource); err != nil { + sendErrorResponse(w, "Failed to write source file", err) + return + } + + codeGraph := graph.Initialize(tmpDir) + if codeGraph == nil { + sendErrorResponse(w, "Failed to initialize code graph", nil) + return + } + + ast := buildAST(codeGraph) + sendJSONResponse(w, ParseResponse{AST: ast}) +} + +// Helper Functions + +// logRequestDuration logs the duration of a request +func logRequestDuration(handler string, start time.Time) { + log.Printf("[%s] Completed in %v", handler, time.Since(start)) +} + +// validateMethod checks if the request method matches the expected method +func validateMethod(w http.ResponseWriter, r *http.Request, method string) bool { + if r.Method != method { + log.Printf("[%s] Invalid method %s", r.URL.Path, r.Method) + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return false + } + return true +} + +// decodeJSONRequest decodes the JSON request body into the target struct +func decodeJSONRequest(w http.ResponseWriter, r *http.Request, target interface{}) error { + if err := json.NewDecoder(r.Body).Decode(target); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return err + } + return nil +} + +// createTempWorkspace creates a temporary directory for analysis +func createTempWorkspace(prefix string) (string, error) { + return os.MkdirTemp("", prefix) +} + +// writeSourceAndQueryFiles writes the source and query files to the workspace +func writeSourceAndQueryFiles(dir, source, query string) error { + if err := writeSourceFile(dir, source); err != nil { + return err + } + return writeFile(filepath.Join(dir, "query.cql"), query) +} + +// writeSourceFile writes the Java source code to a file +func writeSourceFile(dir, source string) error { + return writeFile(filepath.Join(dir, "Source.java"), source) +} + +// writeFile writes content to a file with proper permissions +func writeFile(path, content string) error { + return os.WriteFile(path, []byte(content), FilePermissions) +} + +// executeQueryWithTimeout executes the query with a timeout +func executeQueryWithTimeout(tmpDir, queryStr string) ([]QueryResult, error) { + resultsChan := make(resultChannel, 1) + errorChan := make(chan error, 1) + + go executeQuery(tmpDir, queryStr, resultsChan, errorChan) + + select { + case results := <-resultsChan: + return results, nil + case err := <-errorChan: + return nil, err + case <-time.After(QueryTimeout): + return nil, fmt.Errorf("query execution timed out after %v", QueryTimeout) + } +} + +// executeQuery performs the actual query execution +func executeQuery(tmpDir, queryStr string, resultsChan resultChannel, errorChan chan error) { + codeGraph := graph.Initialize(tmpDir) + if codeGraph == nil { + errorChan <- fmt.Errorf("failed to initialize code graph") + return + } + + parsedQuery, err := parser.ParseQuery(queryStr) + if err != nil { + errorChan <- fmt.Errorf("failed to parse query: %v", err) + return + } + + entities, _ := graph.QueryEntities(codeGraph, parsedQuery) + results := formatQueryResults(entities) + resultsChan <- results +} + +// formatQueryResults formats the query results into the response structure +func formatQueryResults(entities [][]*graph.Node) []QueryResult { + results := make([]QueryResult, 0) + for _, entity := range entities { + for _, node := range entity { + if node != nil { + results = append(results, QueryResult{ + File: node.File, + Line: int(node.LineNumber), + Snippet: node.CodeSnippet, + }) + } + } + } + return results +} + +// sendErrorResponse sends an error response to the client +func sendErrorResponse(w http.ResponseWriter, msg string, err error) { + errMsg := msg + if err != nil { + errMsg += ": " + err.Error() + } + w.WriteHeader(http.StatusInternalServerError) + sendJSONResponse(w, AnalyzeResponse{Error: errMsg}) +} + +// sendJSONResponse sends a JSON response to the client +func sendJSONResponse(w http.ResponseWriter, response interface{}) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} +// buildAST converts a CodeGraph into an AST structure +func buildAST(codeGraph *graph.CodeGraph) *ASTNode { + if codeGraph == nil { + return nil + } + + // Create root node for the compilation unit + root := &ASTNode{ + Type: "CompilationUnit", + Children: make([]ASTNode, 0), + } + + // Process all nodes + for _, node := range codeGraph.Nodes { + // Skip non-declaration nodes + if !strings.Contains(node.Type, "Declaration") { + continue + } + + // Create node based on type + astNode := ASTNode{ + Type: node.Type, + Name: node.Name, + Line: int(node.LineNumber), + Children: make([]ASTNode, 0), + } + + // Add to root + root.Children = append(root.Children, astNode) + } + + return root +} + +func main() { + // Serve static files + fs := http.FileServer(http.Dir("public/static")) + http.Handle("/", fs) + + // API endpoints + http.HandleFunc("/analyze", analyzeHandler) + http.HandleFunc("/parse", parseHandler) + + port := ":8080" + log.Printf("Starting server on port %s", port) + if err := http.ListenAndServe(port, nil); err != nil { + log.Fatalf("Server failed to start: %v", err) + } +} diff --git a/playground/public/static/index.html b/playground/public/static/index.html new file mode 100644 index 00000000..096e569e --- /dev/null +++ b/playground/public/static/index.html @@ -0,0 +1,85 @@ + + + + + + Code Structure Visualizer + + + + + + + + + +
+
+

Code-Pathfinder Visualizer

+
+
+
+
+
+

Code Editor

+
+
+
+
+
+
+
+

Visualization

+
+
+ + Class +
+
+ + Method +
+
+ + Field +
+
+ + Declaration +
+
+
+
+
+
+
+
+

Code-PathFinder Query Console

+
+
+
+
+ + +
+
+
+

+                    
+
+
+
+
+ + + diff --git a/playground/public/static/script.js b/playground/public/static/script.js new file mode 100644 index 00000000..84b5faa6 --- /dev/null +++ b/playground/public/static/script.js @@ -0,0 +1,773 @@ +// Debounce function to limit the rate of API calls +function debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} + +// Initialize split panel resizing +function initResizablePanel() { + const gutter = document.querySelector('.gutter'); + const leftPanel = document.querySelector('.left-panel'); + const rightPanel = document.querySelector('.right-panel'); + let isResizing = false; + let startX; + let startLeftWidth; + + gutter.addEventListener('mousedown', (e) => { + isResizing = true; + gutter.classList.add('active'); + startX = e.pageX; + startLeftWidth = leftPanel.offsetWidth; + }); + + document.addEventListener('mousemove', (e) => { + if (!isResizing) return; + + const mainContent = document.querySelector('.main-content'); + const totalWidth = mainContent.offsetWidth; + const dx = e.pageX - startX; + + // Calculate new widths as percentages + let newLeftWidth = ((startLeftWidth + dx) / totalWidth) * 100; + newLeftWidth = Math.min(Math.max(newLeftWidth, 20), 80); // Limit between 20% and 80% + + leftPanel.style.width = `${newLeftWidth}%`; + rightPanel.style.width = `${100 - newLeftWidth}%`; + gutter.style.left = `${newLeftWidth}%`; + + // Refresh editor and visualization + editor.refresh(); + updateVisualization(currentNodes, currentEdges); + }); + + document.addEventListener('mouseup', () => { + isResizing = false; + gutter.classList.remove('active'); + }); +} + +// Keep track of current graph data +let currentNodes = []; +let currentEdges = []; + +// Initialize global variables +let network = null; +let editor = null; +let queryEditor = null; + +// Network visualization options +const options = { + nodes: { + shape: 'dot', + size: 20, + font: { + face: 'Inter', + size: 14, + color: '#ffffff', + strokeWidth: 0 + }, + borderWidth: 0, + shadow: { + enabled: true, + color: 'rgba(0,0,0,0.2)', + size: 5, + x: 0, + y: 2 + } + }, + edges: { + color: { + color: '#4f4f4f', + highlight: '#666666', + hover: '#666666' + }, + width: 1, + smooth: { + type: 'continuous', + roundness: 0.5 + }, + arrows: { + to: { + enabled: true, + scaleFactor: 0.5 + } + }, + dashes: true + }, + physics: { + enabled: true, + solver: 'forceAtlas2Based', + forceAtlas2Based: { + gravitationalConstant: -50, + springLength: 200, + springConstant: 0.1 + }, + stabilization: { + enabled: true, + iterations: 1000 + } + }, + interaction: { + hover: true, + tooltipDelay: 200, + zoomView: true, + dragView: true + }, + layout: { + improvedLayout: true, + hierarchical: false + } +}; + +// Initialize visualization and editors when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + // Initialize resizable panels + initResizablePanel(); + + // Initialize network container + const container = document.getElementById('visualization'); + if (container) { + // Create the network + network = new vis.Network(container, { + nodes: new vis.DataSet([]), + edges: new vis.DataSet([]) + }, options); + + // Add zoom controls to the container + container.appendChild(zoomControls); + + // Initialize network events + initializeNetworkEvents(); + } + + // Initialize main CodeMirror + editor = CodeMirror(document.getElementById('codeEditor'), { + mode: 'text/x-java', + theme: 'monokai', + lineNumbers: true, + autoCloseBrackets: true, + matchBrackets: true, + value: `public class UserService { + private final UserRepository userRepository; + private final Logger logger; + + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + this.logger = LoggerFactory.getLogger(UserService.class); + } + + public User getUserById(String id) { + logger.info("Fetching user with id: {}", id); + return userRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException("User not found")); + } + + public List getAllUsers() { + return userRepository.findAll(); + } + + public User createUser(User user) { + if (userRepository.existsByEmail(user.getEmail())) { + throw new DuplicateEmailException("Email already exists"); + } + return userRepository.save(user); + } + + public void deleteUser(String id) { + if (!userRepository.existsById(id)) { + throw new UserNotFoundException("User not found"); + } + userRepository.deleteById(id); + } +}`, + }); + + // Add execute button event listener + document.getElementById('executeQuery').addEventListener('click', async () => { + const javaSource = editor.getValue(); + const query = queryEditor.getValue(); + + try { + // Execute query + const response = await fetch('/analyze', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + javaSource, + query, + }), + }); + + const data = await response.json(); + if (data.error) { + console.error('Query error:', data.error); + return; + } + + // Display results + const resultsContainer = document.getElementById('queryResults'); + resultsContainer.innerHTML = ''; + data.results.forEach(result => { + const resultDiv = document.createElement('div'); + resultDiv.className = 'result-item'; + resultDiv.innerHTML = ` +
Line ${result.line}: ${result.file}
+
${result.snippet}
+ `; + resultsContainer.appendChild(resultDiv); + }); + } catch (error) { + console.error('Failed to execute query:', error); + } + }); + + // Add parse button event listener + document.getElementById('parseAST')?.addEventListener('click', async () => { + const javaSource = editor.getValue(); + + try { + // Parse AST + const response = await fetch('/parse', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + javaSource, + }), + }); + + const data = await response.json(); + if (data.error) { + console.error('Parse error:', data.error); + return; + } + + // Convert AST to vis.js format + const visNodes = []; + const visEdges = []; + let nodeId = 1; + + function processNode(node, parentId = null) { + const currentId = nodeId++; + visNodes.push({ + id: currentId, + label: `${node.type}\n${node.name || ''}`, + color: getNodeColor(node.type), + title: `Line: ${node.line}`, + }); + + if (parentId !== null) { + visEdges.push({ + from: parentId, + to: currentId, + }); + } + + if (node.children) { + node.children.forEach(child => processNode(child, currentId)); + } + } + + processNode(data.ast); + + // Update visualization + updateVisualization(visNodes, visEdges); + } catch (error) { + console.error('Failed to parse AST:', error); + } + }); + + // Initialize query editor + queryEditor = CodeMirror(document.getElementById('queryEditor'), { + mode: 'text/x-java', + theme: 'monokai', + lineNumbers: true, + placeholder: 'Enter your query here...', + lineWrapping: true, + value: `from Method m +where m.hasName("get") and m.getReturnType() instanceof TypeString +select m, "Found getter method returning String"` + }); + + // Handle window resize + window.addEventListener('resize', () => { + editor.refresh(); + queryEditor.refresh(); + }); + + // Handle query execution + document.getElementById('executeQuery')?.addEventListener('click', async () => { + const query = queryEditor.getValue(); + const results = document.getElementById('queryResults'); + + try { + const response = await fetch('/query', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + code: editor.getValue(), + query: query + }), + }); + + const data = await response.json(); + + if (data.error) { + results.innerHTML = `Error: ${data.error}`; + return; + } + + // Highlight matching nodes in the visualization + highlightNodes(data.matches); + + // Display results + results.innerHTML = formatQueryResults(data); + } catch (error) { + results.innerHTML = `Error: ${error.message}`; + } + }); + + + // Trigger initial parse + const initialCode = editor.getValue(); + fetch('/parse', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ code: initialCode }), + }) + .then(response => response.json()) + .then(data => { + if (!data.error) { + updateVisualization(data.nodes, data.edges); + } + }) + .catch(error => console.error('Error:', error)); + + // Auto-parse on editor changes + editor.on('change', debounce(async () => { + try { + const code = editor.getValue(); + const response = await fetch('/parse', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ code }), + }); + const data = await response.json(); + if (data.error) { + console.error('Error parsing code:', data.error); + return; + } + updateVisualization(data.nodes, data.edges); + } catch (error) { + console.error('Error:', error); + } + }, 1000)); +}); + + + +// Create zoom controls for the visualization +const zoomControls = document.createElement('div'); +zoomControls.className = 'zoom-controls'; +zoomControls.innerHTML = ` + + + + 100% +`; + +// Initialize visualization when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + // Initialize resizable panels + initResizablePanel(); + + // Initialize network container + const container = document.getElementById('visualization'); + if (container) { + // Create the network + network = new vis.Network(container, { + nodes: new vis.DataSet([]), + edges: new vis.DataSet([]) + }, options); + + // Add zoom controls to the container + container.appendChild(zoomControls); + + // Add zoom control event listeners + document.querySelector('.zoom-in')?.addEventListener('click', () => { + if (network) { + network.moveTo({ + scale: network.getScale() * 1.2 + }); + } + }); + + document.querySelector('.zoom-out')?.addEventListener('click', () => { + if (network) { + network.moveTo({ + scale: network.getScale() * 0.8 + }); + } + }); + + document.querySelector('.zoom-reset')?.addEventListener('click', () => { + if (network) { + network.fit(); + } + }); + } + + // Initialize CodeMirror editor + editor = CodeMirror(document.getElementById('codeEditor'), { + mode: 'text/x-java', + theme: 'monokai', + lineNumbers: true, + autoCloseBrackets: true, + matchBrackets: true, + value: `public class UserService { + private final UserRepository userRepository; + private final Logger logger; + + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + this.logger = LoggerFactory.getLogger(UserService.class); + } + + public User getUserById(String id) { + logger.info("Fetching user with id: {}", id); + return userRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException("User not found")); + } + + public List getAllUsers() { + return userRepository.findAll(); + } + + public User createUser(User user) { + if (userRepository.existsByEmail(user.getEmail())) { + throw new DuplicateEmailException("Email already exists"); + } + return userRepository.save(user); + } + + public void deleteUser(String id) { + if (!userRepository.existsById(id)) { + throw new UserNotFoundException("User not found"); + } + userRepository.deleteById(id); + } +}` + }); + + // Initialize query editor + queryEditor = CodeMirror(document.getElementById('queryEditor'), { + mode: 'text/x-java', + theme: 'monokai', + lineNumbers: true, + autoCloseBrackets: true, + matchBrackets: true, + value: '// Enter your CodeQL query here' + }); + + // Add event listeners for buttons + document.getElementById('executeQuery')?.addEventListener('click', async () => { + const javaSource = editor.getValue(); + const query = queryEditor.getValue(); + + try { + // Execute query + const response = await fetch('/analyze', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + javaSource, + query + }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + document.getElementById('queryResults').innerHTML = formatQueryResults(data); + highlightNodes(data.matches); + } catch (error) { + console.error('Error:', error); + document.getElementById('queryResults').innerHTML = + `Error: ${error.message}`; + } + }); + + document.getElementById('parseAST')?.addEventListener('click', async () => { + const javaSource = editor.getValue(); + + try { + // Parse AST + const response = await fetch('/parse', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + javaSource + }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + const visNodes = []; + const visEdges = []; + + // Process AST nodes + function processNode(node, parentId = null) { + const nodeId = node.id || `node_${Math.random().toString(36).substr(2, 9)}`; + visNodes.push({ + id: nodeId, + label: `${node.type}\n${node.name || ''}`, + type: node.type, + line: node.line + }); + + if (parentId) { + visEdges.push({ + from: parentId, + to: nodeId + }); + } + + if (node.children) { + node.children.forEach(child => processNode(child, nodeId)); + } + } + + processNode(data.ast); + updateVisualization(visNodes, visEdges); + updateASTList(visNodes); + } catch (error) { + console.error('Error:', error); + document.getElementById('astList').innerHTML = + `Error parsing AST: ${error.message}`; + } + }); +}); + +// Format query results +function formatQueryResults(data) { + if (!data.matches || data.matches.length === 0) { + return 'No matches found'; + } + + return `Found ${data.matches.length} matches:\n\n` + + data.matches.map(match => { + return `• ${match.type}: ${match.label}\n ${match.details || ''}`; + }).join('\n\n'); +} + +// Highlight matching nodes in the visualization +function highlightNodes(matches) { + if (!matches || !matches.length || !network) return; + + const matchIds = new Set(matches.map(m => m.id)); + const allNodes = network.body.data.nodes.get(); + const allEdges = network.body.data.edges.get(); + + // Update nodes + allNodes.forEach(node => { + const isHighlighted = matchIds.has(node.id); + network.body.data.nodes.update({ + id: node.id, + opacity: isHighlighted ? 1 : 0.2, + font: { + ...node.font, + color: isHighlighted ? '#ffffff' : 'rgba(255,255,255,0.3)' + } + }); + }); + + // Update edges + allEdges.forEach(edge => { + const isHighlighted = matchIds.has(edge.from) && matchIds.has(edge.to); + network.body.data.edges.update({ + id: edge.id, + opacity: isHighlighted ? 0.8 : 0.1 + }); + }); +} + +function updateVisualization(newNodes = [], newEdges = []) { + if (!network || !newNodes || !newEdges) return; + + // Update current graph data + currentNodes = newNodes; + currentEdges = newEdges; + + // Create DataSet for nodes with proper styling + const nodesDataSet = new vis.DataSet(newNodes.map(node => ({ + id: node.id, + label: node.label || `${node.type}\n${node.name || ''}`, + color: getNodeColor(node.type), + font: { + color: '#ffffff', + size: 14, + face: 'Inter' + }, + shape: 'box', + margin: 10, + shadow: true, + title: node.line ? `Line: ${node.line}` : undefined + }))); + + // Create DataSet for edges with consistent styling + const edgesDataSet = new vis.DataSet(newEdges.map(edge => ({ + from: edge.from, + to: edge.to, + arrows: 'to', + color: { color: '#4d4d4d', highlight: '#61dafb' }, + width: 1, + smooth: { + type: 'continuous', + roundness: 0.5 + } + }))); + + // Update the network + network.setData({ + nodes: nodesDataSet, + edges: edgesDataSet + }); + + // Stabilize and fit the network + network.stabilize(); + network.fit(); +} + + +function getNodeColor(type) { + if (!type) return '#FF5722'; + + const colors = { + 'classdeclaration': { + background: '#4CAF50', + border: '#4CAF50', + highlight: { background: '#66BB6A', border: '#66BB6A' }, + hover: { background: '#66BB6A', border: '#66BB6A' } + }, + 'methoddeclaration': { + background: '#2196F3', + border: '#2196F3', + highlight: { background: '#42A5F5', border: '#42A5F5' }, + hover: { background: '#42A5F5', border: '#42A5F5' } + }, + 'fielddeclaration': { + background: '#FF9800', + border: '#FF9800', + highlight: { background: '#FFA726', border: '#FFA726' }, + hover: { background: '#FFA726', border: '#FFA726' } + }, + 'compilationunit': { + background: '#9C27B0', + border: '#9C27B0', + highlight: { background: '#AB47BC', border: '#AB47BC' }, + hover: { background: '#AB47BC', border: '#AB47BC' } + }, + 'default': { + background: '#FF5722', + border: '#FF5722', + highlight: { background: '#FF7043', border: '#FF7043' }, + hover: { background: '#FF7043', border: '#FF7043' } + } + }; + + const nodeType = type.toLowerCase(); + if (nodeType.includes('class')) return colors.classdeclaration; + if (nodeType.includes('method')) return colors.methoddeclaration; + if (nodeType.includes('field')) return colors.fielddeclaration; + if (nodeType.includes('compilation')) return colors.compilationunit; + return colors.default; +} + +function updateASTList(nodes) { + const astList = document.querySelector('.ast-list'); + if (!astList) return; // Skip if element doesn't exist + + astList.innerHTML = nodes.map(node => ` +
+ ${node.type} + ${node.label} +
+ `).join(''); +} + +// Initialize network event handlers +function initializeNetworkEvents() { + if (!network) return; + + // Handle node selection + network.on('selectNode', function(params) { + if (params.nodes.length > 0) { + const nodeId = params.nodes[0]; + const node = currentNodes.find(n => n.id === nodeId); + if (node) { + const details = document.getElementById('nodeDetails'); + details.innerHTML = ` +

Node Details

+

Type: ${node.type}

+

Line: ${node.line || 'N/A'}

+ ${node.name ? `

Name: ${node.name}

` : ''} + `; + highlightNodes([node]); + } + } + }); + + // Handle node deselection + network.on('deselectNode', function() { + const details = document.getElementById('nodeDetails'); + details.innerHTML = ''; + // Reset node highlighting + if (currentNodes.length > 0) { + highlightNodes([]); + } + }); + + // Handle zoom events + network.on('zoom', function() { + const scale = network.getScale(); + const zoomLevel = document.querySelector('.zoom-level'); + if (zoomLevel) { + zoomLevel.textContent = `${Math.round(scale * 100)}%`; + } + }); + + // Handle stabilization events + network.on('stabilizationProgress', function(params) { + const progress = Math.round(params.iterations / params.total * 100); + console.log(`Stabilizing: ${progress}%`); + }); + + network.on('stabilizationIterationsDone', function() { + console.log('Network stabilized'); + }); +} diff --git a/playground/public/static/style.css b/playground/public/static/style.css new file mode 100644 index 00000000..e4f38d3a --- /dev/null +++ b/playground/public/static/style.css @@ -0,0 +1,402 @@ +/* Reset and base styles */ +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + margin: 0; + padding: 0; + background-color: #111111; + color: #ffffff; + font-feature-settings: 'liga' 1, 'calt' 1; /* Enable ligatures */ +} + +/* App Container */ +.app-container { + display: flex; + flex-direction: column; + height: 100vh; +} + +/* Header */ +.header { + background-color: #111111; + padding: 0.8rem 1.5rem; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #3d3d3d; +} + +.header h1 { + margin: 0; + font-family: 'Space Grotesk', sans-serif; + font-size: 1.2rem; + font-weight: 600; + color: #61dafb; + letter-spacing: 0.5px; + text-shadow: 0 0 10px rgba(97, 218, 251, 0.3); +} + +.view-controls button { + background: transparent; + border: 1px solid #4d4d4d; + color: #ffffff; + padding: 0.4rem 1rem; + margin-left: 0.5rem; + cursor: pointer; + transition: all 0.2s; + font-size: 0.9rem; + border-radius: 3px; +} + +.view-controls button:hover { + border-color: #61dafb; + background-color: rgba(97, 218, 251, 0.1); +} + +.view-controls button.active { + background-color: #61dafb; + border-color: #61dafb; + color: #1e1e1e; +} + +/* Main Content */ +.main-content { + display: flex; + flex: 1; + overflow: hidden; + position: relative; +} + +/* Gutter for resizable panels */ +.gutter { + background-color: #2d2d2d; + position: absolute; + z-index: 10; + touch-action: none; +} + +.gutter-horizontal { + cursor: col-resize; + width: 6px; + height: 100%; + left: 30%; + transform: translateX(-50%); +} + +.gutter:hover { + background-color: #61dafb; +} + +.gutter.active { + background-color: #61dafb; +} + +/* Panel Headers */ +.panel-header { + padding: 0.8rem 1rem; + background-color: #2d2d2d; + border-bottom: 1px solid #3d3d3d; + display: flex; + justify-content: space-between; + align-items: center; +} + +.panel-header h2 { + margin: 0; + font-family: 'Space Grotesk', sans-serif; + font-size: 0.9rem; + font-weight: 500; + color: #e2e2e2; + text-transform: uppercase; + letter-spacing: 0.5px; + text-shadow: 0 0 8px rgba(226, 226, 226, 0.2); +} + +/* Left Panel */ +.left-panel { + width: 30%; + background-color: #111111; + display: flex; + flex-direction: column; + transition: width 0.1s ease; +} + +.editor-container { + flex: 1; + overflow: hidden; +} + +/* Right Panel */ +.right-panel { + width: 70%; + background-color: #111111; + display: flex; + flex-direction: column; + transition: width 0.1s ease; +} + +/* Query Console */ +.query-console { + height: 200px; + background-color: #111111; + border-top: 1px solid #3d3d3d; + display: flex; + flex-direction: column; +} + +.query-input-container { + display: flex; + flex-direction: column; + gap: 10px; + padding: 10px; + background-color: #111111; +} + +.button-group { + display: flex; + gap: 8px; + margin-top: 8px; +} + +.action-btn { + display: flex; + align-items: center; + gap: 6px; + background-color: #1b4332; + color: #ffffff; + border: none; + padding: 8px 12px; + border-radius: 4px; + cursor: pointer; + font-size: 0.9rem; + transition: all 0.2s ease; + box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.1); +} + +.action-btn:hover { + background-color: #2d6a4f; + transform: translateY(-1px); + box-shadow: + inset 0 0 15px rgba(255, 255, 255, 0.2), + 0 4px 8px rgba(0, 0, 0, 0.2); +} + +.btn-icon { + width: 16px; + height: 16px; + opacity: 0.9; +} + +#queryEditor { + flex-grow: 1; + height: 60px; +} + +.run-query-btn { + background-color: #1b4332; + color: #ffffff; + border: none; + padding: 6px; + border-radius: 4px; + cursor: pointer; + height: 32px; + width: 32px; + transition: all 0.2s ease; + margin-top: 4px; + display: flex; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; + box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.1); +} + +.run-query-btn::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 70%); + transform: rotate(45deg); + transition: all 0.3s ease; + pointer-events: none; + opacity: 0; +} + +.query-icon { + width: 16px; + height: 16px; +} + +.run-query-btn:hover { + background-color: #2d6a4f; + transform: scale(1.05); + box-shadow: + inset 0 0 15px rgba(255, 255, 255, 0.2), + 0 0 10px rgba(45, 106, 79, 0.5); +} + +.run-query-btn:hover::before { + opacity: 1; +} + +.run-query-btn:active { + transform: scale(0.95); +} + +.query-results { + flex-grow: 1; + padding: 10px; + background-color: #111111; + overflow-y: auto; + font-family: 'Fira Code', monospace; + font-size: 13px; + color: #d4d4d4; +} + +.query-results pre { + margin: 0; + white-space: pre-wrap; + word-wrap: break-word; +} + +.visualization-container { + flex: 1; + position: relative; + overflow: hidden; + background-color: #111111; + min-height: 300px; + background-image: radial-gradient(circle at 2px 2px, rgba(97, 218, 251, 0.15) 2px, transparent 0); + background-size: 25px 25px; +} + +#visualization { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; +} + +/* CodeMirror Customization */ +.CodeMirror { + height: 100% !important; + font-family: 'Fira Code', monospace; + background-color: #1e1e1e !important; + font-size: 14px; + line-height: 1.6; + padding: 1rem 0; +} + +.CodeMirror-gutters { + border-right: 1px solid #3d3d3d !important; + background-color: #252526 !important; +} + +.CodeMirror-linenumber { + color: #6b6b6b !important; +} + +/* Graph Visualization */ +#visualization { + width: 100%; + height: 100%; +} + +.node circle { + stroke: #2d2d2d; + stroke-width: 2px; +} + +.node text { + fill: #ffffff; + font-size: 12px; + font-family: 'Fira Code', monospace; +} + +.link { + stroke: #4d4d4d; + stroke-width: 1px; + opacity: 0.6; +} + +/* Legend */ +.legend { + display: flex; + gap: 1rem; + align-items: center; + margin-left: auto; +} + +.legend-item { + display: flex; + align-items: center; + gap: 6px; + font-size: 0.8rem; +} + +.dot { + width: 12px; + height: 12px; + border-radius: 50%; +} + +.dot.class { background-color: #4CAF50; } +.dot.method { background-color: #2196F3; } +.dot.field { background-color: #FF9800; } +.dot.declaration { background-color: #FF5722; } + +.legend-item { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.8rem; + color: #cccccc; +} + +.dot { + width: 8px; + height: 8px; + border-radius: 50%; + display: inline-block; +} + +.dot.function { background-color: #61dafb; } +.dot.variable { background-color: #c678dd; } +.dot.expression { background-color: #98c379; } + +/* Node colors for different types */ +.node.function circle { fill: #61dafb; } +.node.variable circle { fill: #c678dd; } +.node.expression circle { fill: #98c379; } + +/* Zoom controls */ +.zoom-controls { + position: absolute; + bottom: 20px; + right: 20px; + display: flex; + gap: 0.5rem; +} + +.zoom-controls button { + background: rgba(45, 45, 45, 0.9); + border: 1px solid #4d4d4d; + color: #ffffff; + width: 32px; + height: 32px; + border-radius: 4px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + transition: all 0.2s; +} + +.zoom-controls button:hover { + border-color: #61dafb; + background: rgba(45, 45, 45, 1); +} + From 7b6e8d5b3b9b075855c697bc35bc939142d6223d Mon Sep 17 00:00:00 2001 From: shivasurya Date: Sun, 9 Mar 2025 18:21:43 -0400 Subject: [PATCH 02/22] =?UTF-8?q?added=20dockerized=20setup=20=F0=9F=8F=84?= =?UTF-8?q?=F0=9F=8C=AC=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playground-Dockerfile | 45 +++++++++++++ playground/.dockerignore | 32 +++++++++ playground/go.mod | 6 +- playground/main.go | 100 ++++++++++++++++++++++++++-- playground/public/static/index.html | 2 +- 5 files changed, 175 insertions(+), 10 deletions(-) create mode 100644 playground-Dockerfile create mode 100644 playground/.dockerignore diff --git a/playground-Dockerfile b/playground-Dockerfile new file mode 100644 index 00000000..b0a7a9b8 --- /dev/null +++ b/playground-Dockerfile @@ -0,0 +1,45 @@ +# Use Wolfi as base image with Go support for builder stage +FROM cgr.dev/chainguard/go:latest as builder + +# Set working directory +WORKDIR /build + +# First, copy the sourcecode-parser module +COPY ./sourcecode-parser /build/sourcecode-parser + +# Copy the playground module +COPY ./playground /build/playground + +# Set working directory to playground +WORKDIR /build/playground + +# Build the application with security flags +ENV CGO_ENABLED=1 +RUN go build -o playground + +# Use distroless base image for minimal attack surface +FROM cgr.dev/chainguard/wolfi-base:latest + +# Create non-root user +USER nonroot:nonroot + +# Set working directory +WORKDIR /app + +# Copy the binary from builder +COPY --from=builder --chown=nonroot:nonroot /build/playground/playground /app/ + +# Copy static files +COPY --from=builder --chown=nonroot:nonroot /build/playground/public/static /app/public/static + +# Create and set permissions for temporary directory +RUN mkdir -p /tmp/code-analysis && \ + chmod 0750 /tmp/code-analysis && \ + chown nonroot:nonroot /tmp/code-analysis + +# Expose port 8080 +EXPOSE 8080 + +# Run the application with reduced capabilities +CMD ["/app/playground"] + diff --git a/playground/.dockerignore b/playground/.dockerignore new file mode 100644 index 00000000..75eeea0b --- /dev/null +++ b/playground/.dockerignore @@ -0,0 +1,32 @@ +# Version control +.git +.gitignore + +# Go build artifacts +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out + +# IDE directories +.idea/ +.vscode/ + +# Temporary files +*.tmp +*.temp +tmp/ +temp/ + +# Docker files +Dockerfile +.dockerignore + +# Debug files +debug/ + +# Dependencies +/vendor/ diff --git a/playground/go.mod b/playground/go.mod index 0b99b711..0be9e641 100644 --- a/playground/go.mod +++ b/playground/go.mod @@ -4,12 +4,14 @@ go 1.24.1 replace github.com/shivasurya/code-pathfinder/sourcecode-parser => ../sourcecode-parser -require github.com/shivasurya/code-pathfinder/sourcecode-parser v0.0.0-00010101000000-000000000000 +require ( + github.com/google/uuid v1.6.0 + github.com/shivasurya/code-pathfinder/sourcecode-parser v0.0.0-00010101000000-000000000000 +) require ( github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/expr-lang/expr v1.16.9 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/posthog/posthog-go v1.2.24 // indirect github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 // indirect diff --git a/playground/main.go b/playground/main.go index 90393c7c..cf20a60d 100644 --- a/playground/main.go +++ b/playground/main.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/google/uuid" parser "github.com/shivasurya/code-pathfinder/sourcecode-parser/antlr" "github.com/shivasurya/code-pathfinder/sourcecode-parser/graph" ) @@ -263,6 +264,7 @@ func sendJSONResponse(w http.ResponseWriter, response interface{}) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } + // buildAST converts a CodeGraph into an AST structure func buildAST(codeGraph *graph.CodeGraph) *ASTNode { if codeGraph == nil { @@ -297,18 +299,102 @@ func buildAST(codeGraph *graph.CodeGraph) *ASTNode { return root } +// loggingMiddleware logs request details with security context +func loggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + // Get client IP with security checks + clientIP := r.Header.Get("X-Real-IP") + if clientIP == "" { + clientIP = r.Header.Get("X-Forwarded-For") + if clientIP == "" { + // Extract IP without port + clientIP = strings.Split(r.RemoteAddr, ":")[0] + } + } + + // Get user agent (sanitized) + userAgent := strings.ReplaceAll(r.Header.Get("User-Agent"), "\n", "") + + // Get request ID or generate one + requestID := r.Header.Get("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + } + + // Add request ID to response headers for tracing + w.Header().Set("X-Request-ID", requestID) + + // Create a response writer that captures the status code + lrw := &loggingResponseWriter{ + ResponseWriter: w, + statusCode: http.StatusOK, + } + + // Call the next handler + next.ServeHTTP(lrw, r) + + // Calculate request duration + duration := time.Since(start) + + // Get content length + contentLength := r.Header.Get("Content-Length") + + // Log request details with security context + log.Printf("[%s] [%s] Method=%s Path=%s IP=%s Status=%d Duration=%v Size=%s UA=%s Referer=%s Protocol=%s Host=%s", + time.Now().Format(time.RFC3339), + requestID, + r.Method, + r.URL.Path, + clientIP, + lrw.statusCode, + duration, + contentLength, + userAgent, + r.Referer(), + r.Proto, + r.Host, + ) + }) +} + +// loggingResponseWriter is a custom response writer that captures the status code +type loggingResponseWriter struct { + http.ResponseWriter + statusCode int +} + +func (lrw *loggingResponseWriter) WriteHeader(code int) { + lrw.statusCode = code + lrw.ResponseWriter.WriteHeader(code) +} + func main() { - // Serve static files + // Create a new mux for better control over middleware + mux := http.NewServeMux() + + // Serve static files with security and logging middleware fs := http.FileServer(http.Dir("public/static")) - http.Handle("/", fs) + mux.Handle("/", loggingMiddleware(fs)) + + // API endpoints with security and logging middleware + mux.Handle("/analyze", loggingMiddleware(http.HandlerFunc(analyzeHandler))) + mux.Handle("/parse", loggingMiddleware(http.HandlerFunc(parseHandler))) - // API endpoints - http.HandleFunc("/analyze", analyzeHandler) - http.HandleFunc("/parse", parseHandler) + // Get port from environment variable or use default + port := os.Getenv("PORT") + if port == "" { + port = "8080" + } + + // Ensure port starts with : + if !strings.HasPrefix(port, ":") { + port = ":" + port + } - port := ":8080" log.Printf("Starting server on port %s", port) - if err := http.ListenAndServe(port, nil); err != nil { + if err := http.ListenAndServe(port, mux); err != nil { log.Fatalf("Server failed to start: %v", err) } } diff --git a/playground/public/static/index.html b/playground/public/static/index.html index 096e569e..03fb56e3 100644 --- a/playground/public/static/index.html +++ b/playground/public/static/index.html @@ -6,7 +6,7 @@ Code Structure Visualizer - + From 0804bd1b077f88bd24b001d189ee3afdaef090c0 Mon Sep 17 00:00:00 2001 From: shivasurya Date: Mon, 10 Mar 2025 21:40:41 -0400 Subject: [PATCH 03/22] refactor: playground app module --- playground/go.mod | 2 +- playground/main.go | 368 +-------------------------- playground/pkg/ast/parser.go | 148 +++++++++++ playground/pkg/handlers/analyze.go | 97 +++++++ playground/pkg/handlers/parse.go | 43 ++++ playground/pkg/middleware/logging.go | 82 ++++++ playground/pkg/models/analysis.go | 28 ++ playground/pkg/models/types.go | 28 ++ playground/pkg/utils/files.go | 58 +++++ playground/pkg/utils/http.go | 46 ++++ playground/public/static/index.html | 1 + playground/public/static/script.js | 73 +++++- playground/public/static/style.css | 25 ++ 13 files changed, 626 insertions(+), 373 deletions(-) create mode 100644 playground/pkg/ast/parser.go create mode 100644 playground/pkg/handlers/analyze.go create mode 100644 playground/pkg/handlers/parse.go create mode 100644 playground/pkg/middleware/logging.go create mode 100644 playground/pkg/models/analysis.go create mode 100644 playground/pkg/models/types.go create mode 100644 playground/pkg/utils/files.go create mode 100644 playground/pkg/utils/http.go diff --git a/playground/go.mod b/playground/go.mod index 0be9e641..946dde69 100644 --- a/playground/go.mod +++ b/playground/go.mod @@ -7,6 +7,7 @@ replace github.com/shivasurya/code-pathfinder/sourcecode-parser => ../sourcecode require ( github.com/google/uuid v1.6.0 github.com/shivasurya/code-pathfinder/sourcecode-parser v0.0.0-00010101000000-000000000000 + github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 ) require ( @@ -14,6 +15,5 @@ require ( github.com/expr-lang/expr v1.16.9 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/posthog/posthog-go v1.2.24 // indirect - github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 // indirect golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect ) diff --git a/playground/main.go b/playground/main.go index cf20a60d..d32c3f9d 100644 --- a/playground/main.go +++ b/playground/main.go @@ -3,384 +3,26 @@ package main import ( - "encoding/json" - "fmt" "log" "net/http" "os" - "path/filepath" "strings" - "time" - "github.com/google/uuid" - parser "github.com/shivasurya/code-pathfinder/sourcecode-parser/antlr" - "github.com/shivasurya/code-pathfinder/sourcecode-parser/graph" + "github.com/shivasurya/code-pathfinder/playground/pkg/handlers" + "github.com/shivasurya/code-pathfinder/playground/pkg/middleware" ) -const ( - // QueryTimeout is the maximum time allowed for query execution - QueryTimeout = 60 * time.Second - - // FilePermissions for created files - FilePermissions = 0644 -) - -// Request/Response Types -type ( - // AnalyzeRequest represents the input for code analysis - AnalyzeRequest struct { - JavaSource string `json:"javaSource"` - Query string `json:"query"` - } - - // QueryResult represents a single result from code analysis - QueryResult struct { - File string `json:"file"` - Line int `json:"line"` - Snippet string `json:"snippet"` - } - - // AnalyzeResponse represents the response from code analysis - AnalyzeResponse struct { - Results []QueryResult `json:"results"` - Error string `json:"error,omitempty"` - } - - // ParseRequest represents the input for AST parsing - ParseRequest struct { - JavaSource string `json:"javaSource"` - } - - // ASTNode represents a node in the Abstract Syntax Tree - ASTNode struct { - Type string `json:"type"` - Name string `json:"name,omitempty"` - Value string `json:"value,omitempty"` - Line int `json:"line"` - Children []ASTNode `json:"children,omitempty"` - } - - // ParseResponse represents the response from AST parsing - ParseResponse struct { - AST *ASTNode `json:"ast"` - Error string `json:"error,omitempty"` - } -) - -// Channel types -type resultChannel chan []QueryResult - -// HTTP Handlers - -// analyzeHandler processes POST requests to /analyze endpoint. -// It accepts Java source code and a CodeQL query, executes the query using code-pathfinder, -// and returns the query results. The execution is done with a 60-second timeout. -func analyzeHandler(w http.ResponseWriter, r *http.Request) { - start := time.Now() - defer logRequestDuration("analyzeHandler", start) - - if !validateMethod(w, r, http.MethodPost) { - return - } - - var req AnalyzeRequest - if err := decodeJSONRequest(w, r, &req); err != nil { - return - } - - tmpDir, err := createTempWorkspace("code-analysis-*") - if err != nil { - sendErrorResponse(w, "Failed to create temporary directory", err) - return - } - defer os.RemoveAll(tmpDir) - - if err := writeSourceAndQueryFiles(tmpDir, req.JavaSource, req.Query); err != nil { - sendErrorResponse(w, "Failed to write files", err) - return - } - - results, err := executeQueryWithTimeout(tmpDir, req.Query) - if err != nil { - sendErrorResponse(w, "Query execution failed", err) - return - } - - sendJSONResponse(w, AnalyzeResponse{Results: results}) -} - -// parseHandler processes POST requests to /parse endpoint. -// It accepts Java source code, parses it into an AST using code-pathfinder, -// and returns the AST structure for visualization. -func parseHandler(w http.ResponseWriter, r *http.Request) { - start := time.Now() - defer logRequestDuration("parseHandler", start) - - if !validateMethod(w, r, http.MethodPost) { - return - } - - var req ParseRequest - if err := decodeJSONRequest(w, r, &req); err != nil { - return - } - - tmpDir, err := createTempWorkspace("ast-parse-*") - if err != nil { - sendErrorResponse(w, "Failed to create temporary directory", err) - return - } - defer os.RemoveAll(tmpDir) - - if err := writeSourceFile(tmpDir, req.JavaSource); err != nil { - sendErrorResponse(w, "Failed to write source file", err) - return - } - - codeGraph := graph.Initialize(tmpDir) - if codeGraph == nil { - sendErrorResponse(w, "Failed to initialize code graph", nil) - return - } - - ast := buildAST(codeGraph) - sendJSONResponse(w, ParseResponse{AST: ast}) -} - -// Helper Functions - -// logRequestDuration logs the duration of a request -func logRequestDuration(handler string, start time.Time) { - log.Printf("[%s] Completed in %v", handler, time.Since(start)) -} - -// validateMethod checks if the request method matches the expected method -func validateMethod(w http.ResponseWriter, r *http.Request, method string) bool { - if r.Method != method { - log.Printf("[%s] Invalid method %s", r.URL.Path, r.Method) - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return false - } - return true -} - -// decodeJSONRequest decodes the JSON request body into the target struct -func decodeJSONRequest(w http.ResponseWriter, r *http.Request, target interface{}) error { - if err := json.NewDecoder(r.Body).Decode(target); err != nil { - http.Error(w, "Invalid request body", http.StatusBadRequest) - return err - } - return nil -} - -// createTempWorkspace creates a temporary directory for analysis -func createTempWorkspace(prefix string) (string, error) { - return os.MkdirTemp("", prefix) -} - -// writeSourceAndQueryFiles writes the source and query files to the workspace -func writeSourceAndQueryFiles(dir, source, query string) error { - if err := writeSourceFile(dir, source); err != nil { - return err - } - return writeFile(filepath.Join(dir, "query.cql"), query) -} - -// writeSourceFile writes the Java source code to a file -func writeSourceFile(dir, source string) error { - return writeFile(filepath.Join(dir, "Source.java"), source) -} - -// writeFile writes content to a file with proper permissions -func writeFile(path, content string) error { - return os.WriteFile(path, []byte(content), FilePermissions) -} - -// executeQueryWithTimeout executes the query with a timeout -func executeQueryWithTimeout(tmpDir, queryStr string) ([]QueryResult, error) { - resultsChan := make(resultChannel, 1) - errorChan := make(chan error, 1) - - go executeQuery(tmpDir, queryStr, resultsChan, errorChan) - - select { - case results := <-resultsChan: - return results, nil - case err := <-errorChan: - return nil, err - case <-time.After(QueryTimeout): - return nil, fmt.Errorf("query execution timed out after %v", QueryTimeout) - } -} - -// executeQuery performs the actual query execution -func executeQuery(tmpDir, queryStr string, resultsChan resultChannel, errorChan chan error) { - codeGraph := graph.Initialize(tmpDir) - if codeGraph == nil { - errorChan <- fmt.Errorf("failed to initialize code graph") - return - } - - parsedQuery, err := parser.ParseQuery(queryStr) - if err != nil { - errorChan <- fmt.Errorf("failed to parse query: %v", err) - return - } - - entities, _ := graph.QueryEntities(codeGraph, parsedQuery) - results := formatQueryResults(entities) - resultsChan <- results -} - -// formatQueryResults formats the query results into the response structure -func formatQueryResults(entities [][]*graph.Node) []QueryResult { - results := make([]QueryResult, 0) - for _, entity := range entities { - for _, node := range entity { - if node != nil { - results = append(results, QueryResult{ - File: node.File, - Line: int(node.LineNumber), - Snippet: node.CodeSnippet, - }) - } - } - } - return results -} - -// sendErrorResponse sends an error response to the client -func sendErrorResponse(w http.ResponseWriter, msg string, err error) { - errMsg := msg - if err != nil { - errMsg += ": " + err.Error() - } - w.WriteHeader(http.StatusInternalServerError) - sendJSONResponse(w, AnalyzeResponse{Error: errMsg}) -} - -// sendJSONResponse sends a JSON response to the client -func sendJSONResponse(w http.ResponseWriter, response interface{}) { - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) -} - -// buildAST converts a CodeGraph into an AST structure -func buildAST(codeGraph *graph.CodeGraph) *ASTNode { - if codeGraph == nil { - return nil - } - - // Create root node for the compilation unit - root := &ASTNode{ - Type: "CompilationUnit", - Children: make([]ASTNode, 0), - } - - // Process all nodes - for _, node := range codeGraph.Nodes { - // Skip non-declaration nodes - if !strings.Contains(node.Type, "Declaration") { - continue - } - - // Create node based on type - astNode := ASTNode{ - Type: node.Type, - Name: node.Name, - Line: int(node.LineNumber), - Children: make([]ASTNode, 0), - } - - // Add to root - root.Children = append(root.Children, astNode) - } - - return root -} - -// loggingMiddleware logs request details with security context -func loggingMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - start := time.Now() - - // Get client IP with security checks - clientIP := r.Header.Get("X-Real-IP") - if clientIP == "" { - clientIP = r.Header.Get("X-Forwarded-For") - if clientIP == "" { - // Extract IP without port - clientIP = strings.Split(r.RemoteAddr, ":")[0] - } - } - - // Get user agent (sanitized) - userAgent := strings.ReplaceAll(r.Header.Get("User-Agent"), "\n", "") - - // Get request ID or generate one - requestID := r.Header.Get("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - } - - // Add request ID to response headers for tracing - w.Header().Set("X-Request-ID", requestID) - - // Create a response writer that captures the status code - lrw := &loggingResponseWriter{ - ResponseWriter: w, - statusCode: http.StatusOK, - } - - // Call the next handler - next.ServeHTTP(lrw, r) - - // Calculate request duration - duration := time.Since(start) - - // Get content length - contentLength := r.Header.Get("Content-Length") - - // Log request details with security context - log.Printf("[%s] [%s] Method=%s Path=%s IP=%s Status=%d Duration=%v Size=%s UA=%s Referer=%s Protocol=%s Host=%s", - time.Now().Format(time.RFC3339), - requestID, - r.Method, - r.URL.Path, - clientIP, - lrw.statusCode, - duration, - contentLength, - userAgent, - r.Referer(), - r.Proto, - r.Host, - ) - }) -} - -// loggingResponseWriter is a custom response writer that captures the status code -type loggingResponseWriter struct { - http.ResponseWriter - statusCode int -} - -func (lrw *loggingResponseWriter) WriteHeader(code int) { - lrw.statusCode = code - lrw.ResponseWriter.WriteHeader(code) -} - func main() { // Create a new mux for better control over middleware mux := http.NewServeMux() // Serve static files with security and logging middleware fs := http.FileServer(http.Dir("public/static")) - mux.Handle("/", loggingMiddleware(fs)) + mux.Handle("/", middleware.LoggingMiddleware(fs)) // API endpoints with security and logging middleware - mux.Handle("/analyze", loggingMiddleware(http.HandlerFunc(analyzeHandler))) - mux.Handle("/parse", loggingMiddleware(http.HandlerFunc(parseHandler))) + mux.Handle("/api/analyze", middleware.LoggingMiddleware(http.HandlerFunc(handlers.AnalyzeHandler))) + mux.Handle("/api/parse", middleware.LoggingMiddleware(http.HandlerFunc(handlers.ParseHandler))) // Get port from environment variable or use default port := os.Getenv("PORT") diff --git a/playground/pkg/ast/parser.go b/playground/pkg/ast/parser.go new file mode 100644 index 00000000..22f2e930 --- /dev/null +++ b/playground/pkg/ast/parser.go @@ -0,0 +1,148 @@ +package ast + +import ( + "fmt" + + "github.com/smacker/go-tree-sitter" + "github.com/smacker/go-tree-sitter/java" + "github.com/shivasurya/code-pathfinder/playground/pkg/models" +) + +// ParseJavaSource parses Java source code into an AST using tree-sitter +func ParseJavaSource(sourceCode string) (*models.ASTNode, error) { + parser := sitter.NewParser() + parser.SetLanguage(java.GetLanguage()) + + sourceBytes := []byte(sourceCode) + tree := parser.Parse(nil, sourceBytes) + if tree == nil { + return nil, fmt.Errorf("failed to parse source code") + } + defer tree.Close() + + root := tree.RootNode() + if root == nil { + return nil, fmt.Errorf("invalid AST: no root node") + } + + ast := buildASTFromTreeSitter(root, sourceBytes) + if ast == nil { + return nil, fmt.Errorf("failed to build AST") + } + + return ast, nil +} + +// buildASTFromTreeSitter converts a tree-sitter AST into our AST structure +func buildASTFromTreeSitter(node *sitter.Node, sourceBytes []byte) *models.ASTNode { + if node == nil { + return nil + } + + // Create AST node + astNode := &models.ASTNode{ + Type: node.Type(), + Name: node.Type(), + Code: getNodeText(node, sourceBytes), + Line: int(node.StartPoint().Row + 1), + Children: make([]models.ASTNode, 0), + } + + // Process specific node types + switch node.Type() { + case "package_declaration": + astNode.Type = "PackageDeclaration" + astNode.Package = getNodeText(node, sourceBytes) + + case "import_declaration": + astNode.Type = "ImportDeclaration" + astNode.Imports = []string{getNodeText(node, sourceBytes)} + + case "class_declaration": + astNode.Type = "ClassDeclaration" + if nameNode := node.ChildByFieldName("name"); nameNode != nil { + astNode.Name = getNodeText(nameNode, sourceBytes) + } + if superNode := node.ChildByFieldName("superclass"); superNode != nil { + astNode.SuperClass = getNodeText(superNode, sourceBytes) + } + astNode.Modifier = getModifiers(node, sourceBytes) + + case "method_declaration": + astNode.Type = "MethodDeclaration" + if nameNode := node.ChildByFieldName("name"); nameNode != nil { + astNode.Name = getNodeText(nameNode, sourceBytes) + } + if typeNode := node.ChildByFieldName("type"); typeNode != nil { + astNode.ReturnType = getNodeText(typeNode, sourceBytes) + } + astNode.Arguments = getMethodParameters(node, sourceBytes) + astNode.Modifier = getModifiers(node, sourceBytes) + + case "field_declaration": + astNode.Type = "FieldDeclaration" + if nameNode := node.ChildByFieldName("name"); nameNode != nil { + astNode.Name = getNodeText(nameNode, sourceBytes) + } + if typeNode := node.ChildByFieldName("type"); typeNode != nil { + astNode.DataType = getNodeText(typeNode, sourceBytes) + } + astNode.Modifier = getModifiers(node, sourceBytes) + astNode.Value = getInitializer(node, sourceBytes) + + case "variable_declaration": + astNode.Type = "VariableDeclaration" + if nameNode := node.ChildByFieldName("name"); nameNode != nil { + astNode.Name = getNodeText(nameNode, sourceBytes) + } + if typeNode := node.ChildByFieldName("type"); typeNode != nil { + astNode.DataType = getNodeText(typeNode, sourceBytes) + } + astNode.Value = getInitializer(node, sourceBytes) + } + + // Process child nodes + for i := 0; i < int(node.NamedChildCount()); i++ { + if child := node.NamedChild(i); child != nil { + if childNode := buildASTFromTreeSitter(child, sourceBytes); childNode != nil { + astNode.Children = append(astNode.Children, *childNode) + } + } + } + + return astNode +} + +// Helper functions for tree-sitter AST processing +func getNodeText(node *sitter.Node, sourceBytes []byte) string { + if node == nil { + return "" + } + return string(node.Content(sourceBytes)) +} + +func getModifiers(node *sitter.Node, sourceBytes []byte) string { + if modNode := node.ChildByFieldName("modifiers"); modNode != nil { + return getNodeText(modNode, sourceBytes) + } + return "" +} + +func getMethodParameters(node *sitter.Node, sourceBytes []byte) []string { + var params []string + if paramsList := node.ChildByFieldName("parameters"); paramsList != nil { + for i := 0; i < int(paramsList.NamedChildCount()); i++ { + if param := paramsList.NamedChild(i); param != nil { + params = append(params, getNodeText(param, sourceBytes)) + } + } + } + return params +} + +func getInitializer(node *sitter.Node, sourceBytes []byte) string { + if initNode := node.ChildByFieldName("initializer"); initNode != nil { + return getNodeText(initNode, sourceBytes) + } + return "" +} diff --git a/playground/pkg/handlers/analyze.go b/playground/pkg/handlers/analyze.go new file mode 100644 index 00000000..c7e50664 --- /dev/null +++ b/playground/pkg/handlers/analyze.go @@ -0,0 +1,97 @@ +package handlers + +import ( + "errors" + "net/http" + "os" + "time" + + "github.com/shivasurya/code-pathfinder/playground/pkg/models" + "github.com/shivasurya/code-pathfinder/playground/pkg/utils" +) + +const ( + // QueryTimeout is the maximum time allowed for query execution + QueryTimeout = 60 * time.Second +) + +// Error definitions +var ( + ErrQueryTimeout = errors.New("query execution timed out") +) + +// Channel types +type resultChannel chan []models.QueryResult + +// AnalyzeHandler processes POST requests to /analyze endpoint. +// It accepts Java source code and a CodeQL query, executes the query using code-pathfinder, +// and returns the query results. The execution is done with a 60-second timeout. +func AnalyzeHandler(w http.ResponseWriter, r *http.Request) { + start := time.Now() + defer utils.LogRequestDuration("analyzeHandler", start) + + if !utils.ValidateMethod(w, r, http.MethodPost) { + return + } + + var req models.AnalyzeRequest + if err := utils.DecodeJSONRequest(w, r, &req); err != nil { + return + } + + tmpDir, err := utils.CreateTempWorkspace("code-analysis-*") + if err != nil { + utils.SendErrorResponse(w, "Failed to create temporary directory", err) + return + } + defer os.RemoveAll(tmpDir) + + if err := utils.WriteSourceAndQueryFiles(tmpDir, req.JavaSource, req.Query); err != nil { + utils.SendErrorResponse(w, "Failed to write files", err) + return + } + + results, err := executeQueryWithTimeout(tmpDir, req.Query) + if err != nil { + utils.SendErrorResponse(w, "Query execution failed", err) + return + } + + utils.SendJSONResponse(w, models.AnalyzeResponse{Results: results}) +} + +// executeQueryWithTimeout executes the query with a timeout +func executeQueryWithTimeout(tmpDir, queryStr string) ([]models.QueryResult, error) { + resultsChan := make(resultChannel) + errorChan := make(chan error) + + go executeQuery(tmpDir, queryStr, resultsChan, errorChan) + + select { + case results := <-resultsChan: + return results, nil + case err := <-errorChan: + return nil, err + case <-time.After(QueryTimeout): + return nil, ErrQueryTimeout + } +} + +// executeQuery performs the actual query execution +func executeQuery(tmpDir, queryStr string, resultsChan resultChannel, errorChan chan error) { + // TODO: This is a placeholder implementation + // In a real implementation, this would use the code-pathfinder library + // to execute the query and analyze the code + + // For now, we'll look for WebView security issues in the source code + var results []models.QueryResult + + // Add a dummy result for testing + results = append(results, models.QueryResult{ + File: "MainActivity.java", + Line: 42, + Snippet: "setJavaScriptEnabled(true)", + }) + + resultsChan <- results +} diff --git a/playground/pkg/handlers/parse.go b/playground/pkg/handlers/parse.go new file mode 100644 index 00000000..0062bcec --- /dev/null +++ b/playground/pkg/handlers/parse.go @@ -0,0 +1,43 @@ +package handlers + +import ( + "net/http" + "strings" + "time" + + "github.com/shivasurya/code-pathfinder/playground/pkg/ast" + "github.com/shivasurya/code-pathfinder/playground/pkg/models" + "github.com/shivasurya/code-pathfinder/playground/pkg/utils" +) + +// ParseHandler processes POST requests to /parse endpoint. +// It accepts Java source code, parses it into an AST using tree-sitter, +// and returns the AST structure for visualization. +func ParseHandler(w http.ResponseWriter, r *http.Request) { + start := time.Now() + defer utils.LogRequestDuration("parseHandler", start) + + if !utils.ValidateMethod(w, r, http.MethodPost) { + return + } + + var req models.ParseRequest + if err := utils.DecodeJSONRequest(w, r, &req); err != nil { + return + } + + // Validate source code is not empty + if strings.TrimSpace(req.JavaSource) == "" { + utils.SendErrorResponse(w, "Source code cannot be empty", nil) + return + } + + // Parse the Java source code + ast, err := ast.ParseJavaSource(req.JavaSource) + if err != nil { + utils.SendErrorResponse(w, "Failed to parse source code", err) + return + } + + utils.SendJSONResponse(w, models.ParseResponse{AST: ast}) +} diff --git a/playground/pkg/middleware/logging.go b/playground/pkg/middleware/logging.go new file mode 100644 index 00000000..7d1abf37 --- /dev/null +++ b/playground/pkg/middleware/logging.go @@ -0,0 +1,82 @@ +package middleware + +import ( + "log" + "net/http" + "strings" + "time" + + "github.com/google/uuid" +) + +// LoggingResponseWriter is a custom response writer that captures the status code +type LoggingResponseWriter struct { + http.ResponseWriter + statusCode int +} + +// WriteHeader captures the status code +func (lrw *LoggingResponseWriter) WriteHeader(code int) { + lrw.statusCode = code + lrw.ResponseWriter.WriteHeader(code) +} + +// LoggingMiddleware logs request details with security context +func LoggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + // Get client IP with security checks + clientIP := r.Header.Get("X-Real-IP") + if clientIP == "" { + clientIP = r.Header.Get("X-Forwarded-For") + if clientIP == "" { + // Extract IP without port + clientIP = strings.Split(r.RemoteAddr, ":")[0] + } + } + + // Get user agent (sanitized) + userAgent := strings.ReplaceAll(r.Header.Get("User-Agent"), "\n", "") + + // Get request ID or generate one + requestID := r.Header.Get("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + } + + // Add request ID to response headers for tracing + w.Header().Set("X-Request-ID", requestID) + + // Create a response writer that captures the status code + lrw := &LoggingResponseWriter{ + ResponseWriter: w, + statusCode: http.StatusOK, + } + + // Call the next handler + next.ServeHTTP(lrw, r) + + // Calculate request duration + duration := time.Since(start) + + // Get content length + contentLength := r.Header.Get("Content-Length") + + // Log request details with security context + log.Printf("[%s] [%s] Method=%s Path=%s IP=%s Status=%d Duration=%v Size=%s UA=%s Referer=%s Protocol=%s Host=%s", + time.Now().Format(time.RFC3339), + requestID, + r.Method, + r.URL.Path, + clientIP, + lrw.statusCode, + duration, + contentLength, + userAgent, + r.Referer(), + r.Proto, + r.Host, + ) + }) +} diff --git a/playground/pkg/models/analysis.go b/playground/pkg/models/analysis.go new file mode 100644 index 00000000..e47c4591 --- /dev/null +++ b/playground/pkg/models/analysis.go @@ -0,0 +1,28 @@ +package models + +// AnalyzeRequest represents the input for code analysis +type AnalyzeRequest struct { + JavaSource string `json:"javaSource"` + Query string `json:"query"` +} + +// QueryResult represents a single result from code analysis +type QueryResult struct { + File string `json:"file"` + Line int `json:"line"` + Snippet string `json:"snippet"` +} + +// AnalyzeResponse represents the response from code analysis +type AnalyzeResponse struct { + Results []QueryResult `json:"results"` + Error string `json:"error,omitempty"` +} + +// Security represents security-related information for a node +type Security struct { + Risk string `json:"risk,omitempty"` + Impact string `json:"impact,omitempty"` + Category string `json:"category,omitempty"` + Rules []string `json:"rules,omitempty"` +} diff --git a/playground/pkg/models/types.go b/playground/pkg/models/types.go new file mode 100644 index 00000000..54c0f6b5 --- /dev/null +++ b/playground/pkg/models/types.go @@ -0,0 +1,28 @@ +package models + +// ParseRequest represents the input for AST parsing +type ParseRequest struct { + JavaSource string `json:"code"` +} + +// ParseResponse represents the output from AST parsing +type ParseResponse struct { + AST *ASTNode `json:"ast"` +} + +// ASTNode represents a node in the Abstract Syntax Tree +type ASTNode struct { + Type string `json:"type"` + Name string `json:"name"` + Code string `json:"code"` + Line int `json:"line"` + Modifier string `json:"modifier,omitempty"` + Value string `json:"value,omitempty"` + Package string `json:"package,omitempty"` + Imports []string `json:"imports,omitempty"` + SuperClass string `json:"superClass,omitempty"` + DataType string `json:"dataType,omitempty"` + ReturnType string `json:"returnType,omitempty"` + Arguments []string `json:"arguments,omitempty"` + Children []ASTNode `json:"children"` +} diff --git a/playground/pkg/utils/files.go b/playground/pkg/utils/files.go new file mode 100644 index 00000000..05d91a1e --- /dev/null +++ b/playground/pkg/utils/files.go @@ -0,0 +1,58 @@ +package utils + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/google/uuid" +) + +const ( + // FilePermissions for created files + FilePermissions = 0644 +) + +// CreateTempWorkspace creates a temporary directory for analysis +func CreateTempWorkspace(prefix string) (string, error) { + // Create a unique directory name + dirName := fmt.Sprintf("%s-%s", prefix, uuid.New().String()) + tmpDir := filepath.Join(os.TempDir(), dirName) + + // Create directory with secure permissions + if err := os.MkdirAll(tmpDir, 0700); err != nil { + return "", fmt.Errorf("failed to create temp directory: %v", err) + } + + return tmpDir, nil +} + +// WriteSourceAndQueryFiles writes the source and query files to the workspace +func WriteSourceAndQueryFiles(dir, source, query string) error { + if err := WriteSourceFile(dir, source); err != nil { + return err + } + return WriteFile(filepath.Join(dir, "query.ql"), query) +} + +// WriteSourceFile writes the Java source code to a file +func WriteSourceFile(dir, source string) error { + return WriteFile(filepath.Join(dir, "Main.java"), source) +} + +// WriteFile writes content to a file with proper permissions +func WriteFile(path, content string) error { + // Create or truncate the file + file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, FilePermissions) + if err != nil { + return fmt.Errorf("failed to create file: %v", err) + } + defer file.Close() + + // Write content + if _, err := file.WriteString(content); err != nil { + return fmt.Errorf("failed to write content: %v", err) + } + + return nil +} diff --git a/playground/pkg/utils/http.go b/playground/pkg/utils/http.go new file mode 100644 index 00000000..fc65e0fc --- /dev/null +++ b/playground/pkg/utils/http.go @@ -0,0 +1,46 @@ +package utils + +import ( + "encoding/json" + "log" + "net/http" + "time" +) + +// LogRequestDuration logs the duration of a request +func LogRequestDuration(handler string, start time.Time) { + log.Printf("%s took %v", handler, time.Since(start)) +} + +// ValidateMethod checks if the request method matches the expected method +func ValidateMethod(w http.ResponseWriter, r *http.Request, method string) bool { + if r.Method != method { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return false + } + return true +} + +// DecodeJSONRequest decodes a JSON request body into a struct +func DecodeJSONRequest(w http.ResponseWriter, r *http.Request, v interface{}) error { + if err := json.NewDecoder(r.Body).Decode(v); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return err + } + return nil +} + +// SendJSONResponse sends a JSON response +func SendJSONResponse(w http.ResponseWriter, v interface{}) { + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(v); err != nil { + log.Printf("Error encoding response: %v", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + } +} + +// SendErrorResponse sends an error response +func SendErrorResponse(w http.ResponseWriter, message string, err error) { + log.Printf("Error: %s: %v", message, err) + http.Error(w, message, http.StatusInternalServerError) +} diff --git a/playground/public/static/index.html b/playground/public/static/index.html index 03fb56e3..d6953131 100644 --- a/playground/public/static/index.html +++ b/playground/public/static/index.html @@ -16,6 +16,7 @@

Code-Pathfinder Visualizer

+
diff --git a/playground/public/static/script.js b/playground/public/static/script.js index 84b5faa6..3fc74428 100644 --- a/playground/public/static/script.js +++ b/playground/public/static/script.js @@ -147,6 +147,56 @@ document.addEventListener('DOMContentLoaded', () => { initializeNetworkEvents(); } + // Function to parse and visualize code + const parseAndVisualizeCode = async (javaSource) => { + try { + const response = await fetch('/api/parse', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ code: javaSource }), + }); + + const data = await response.json(); + if (!response.ok) { + // Show error message to user + const errorMsg = data.error || 'Failed to parse code'; + document.getElementById('errorMessage').textContent = errorMsg; + document.getElementById('errorMessage').style.display = 'block'; + setTimeout(() => { + document.getElementById('errorMessage').style.display = 'none'; + }, 5000); + return; + } + + // Clear any previous error messages + document.getElementById('errorMessage').style.display = 'none'; + + const visNodes = []; + const visEdges = []; + + // Process AST nodes and update visualization + if (data.ast) { + processNode(data.ast); + updateVisualization(visNodes, visEdges); + updateASTList(visNodes); + } else { + throw new Error('Invalid AST structure received'); + } + } catch (error) { + console.error('Error parsing code:', error); + document.getElementById('errorMessage').textContent = 'Failed to process code: ' + error.message; + document.getElementById('errorMessage').style.display = 'block'; + setTimeout(() => { + document.getElementById('errorMessage').style.display = 'none'; + }, 5000); + } + }; + + // Create debounced version of parse function + const debouncedParse = debounce(parseAndVisualizeCode, 1000); + // Initialize main CodeMirror editor = CodeMirror(document.getElementById('codeEditor'), { mode: 'text/x-java', @@ -154,6 +204,11 @@ document.addEventListener('DOMContentLoaded', () => { lineNumbers: true, autoCloseBrackets: true, matchBrackets: true, + // Add change event handler for automatic parsing + onChange: (cm) => { + const code = cm.getValue(); + debouncedParse(code); + }, value: `public class UserService { private final UserRepository userRepository; private final Logger logger; @@ -196,7 +251,7 @@ document.addEventListener('DOMContentLoaded', () => { try { // Execute query - const response = await fetch('/analyze', { + const response = await fetch('/api/analyze', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -236,13 +291,13 @@ document.addEventListener('DOMContentLoaded', () => { try { // Parse AST - const response = await fetch('/parse', { + const response = await fetch('/api/parse', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ - javaSource, + code: javaSource, }), }); @@ -342,7 +397,7 @@ select m, "Found getter method returning String"` // Trigger initial parse const initialCode = editor.getValue(); - fetch('/parse', { + fetch('/api/parse', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -361,7 +416,7 @@ select m, "Found getter method returning String"` editor.on('change', debounce(async () => { try { const code = editor.getValue(); - const response = await fetch('/parse', { + const response = await fetch('/api/parse', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -492,7 +547,7 @@ document.addEventListener('DOMContentLoaded', () => { try { // Execute query - const response = await fetch('/analyze', { + const response = await fetch('/api/analyze', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -522,13 +577,13 @@ document.addEventListener('DOMContentLoaded', () => { try { // Parse AST - const response = await fetch('/parse', { + const response = await fetch('/api/parse', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ - javaSource + code: javaSource }) }); @@ -545,7 +600,7 @@ document.addEventListener('DOMContentLoaded', () => { const nodeId = node.id || `node_${Math.random().toString(36).substr(2, 9)}`; visNodes.push({ id: nodeId, - label: `${node.type}\n${node.name || ''}`, + label: `${node.type}`, type: node.type, line: node.line }); diff --git a/playground/public/static/style.css b/playground/public/static/style.css index e4f38d3a..46fe8c5b 100644 --- a/playground/public/static/style.css +++ b/playground/public/static/style.css @@ -23,6 +23,31 @@ body { justify-content: space-between; align-items: center; border-bottom: 1px solid #3d3d3d; + position: relative; +} + +/* Error Message */ +.error-message { + position: fixed; + top: 20px; + right: 20px; + background-color: #ff4444; + color: white; + padding: 12px 20px; + border-radius: 4px; + box-shadow: 0 2px 4px rgba(0,0,0,0.2); + z-index: 1000; + font-family: 'Inter', sans-serif; + font-size: 14px; + max-width: 400px; + word-wrap: break-word; + display: none; + animation: fadeIn 0.3s ease-in-out; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(-10px); } + to { opacity: 1; transform: translateY(0); } } .header h1 { From f3f513ae2eeafb25b1179436e790f385fc319b15 Mon Sep 17 00:00:00 2001 From: shivasurya Date: Wed, 12 Mar 2025 23:24:48 -0400 Subject: [PATCH 04/22] fix ui and code editor --- playground/public/static/index.html | 57 +-- playground/public/static/script.js | 602 +++++++++++++--------------- playground/public/static/style.css | 192 ++++++++- 3 files changed, 484 insertions(+), 367 deletions(-) diff --git a/playground/public/static/index.html b/playground/public/static/index.html index d6953131..868bc001 100644 --- a/playground/public/static/index.html +++ b/playground/public/static/index.html @@ -22,7 +22,7 @@

Code-Pathfinder Visualizer

-

Code Editor

+

Editor

@@ -30,52 +30,61 @@

Code Editor

-

Visualization

+
+ + +
- + Class
- + Method
- - Field + + Fields
- - Declaration + + Variables
-
-
+
+
+
+
+
+
+
+
+

+                        
+
-

Code-PathFinder Query Console

-
-
-
+

Query Console

-
-
-

+                    
+
diff --git a/playground/public/static/script.js b/playground/public/static/script.js index 3fc74428..8fb7e0ec 100644 --- a/playground/public/static/script.js +++ b/playground/public/static/script.js @@ -53,65 +53,275 @@ function initResizablePanel() { }); } -// Keep track of current graph data -let currentNodes = []; -let currentEdges = []; +// UI State Management +const UIState = { + activeTab: 'visualization', + setActiveTab(tabName) { + this.activeTab = tabName; + this.updateTabUI(); + }, + updateTabUI() { + document.querySelectorAll('.tab-button').forEach(button => { + button.classList.toggle('active', button.dataset.tab === this.activeTab); + }); + document.querySelectorAll('.tab-pane').forEach(pane => { + pane.classList.toggle('active', pane.id === `${this.activeTab}-tab`); + }); + } +}; + +// AST Processing Service +const ASTService = { + async parseAndVisualize(javaSource) { + try { + const response = await fetch('/api/parse', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ code: javaSource }) + }); + + const data = await response.json(); + if (!response.ok) { + throw new Error(data.error || 'Failed to parse code'); + } + + if (!data.ast) { + throw new Error('Invalid AST structure received'); + } + + const { nodes, edges } = this.processASTData(data.ast); + this.updateVisualization(nodes, edges); + return { nodes, edges }; + } catch (error) { + this.handleError('Error parsing code:', error); + return null; + } + }, + + processASTData(node, parentId = null, nodes = [], edges = []) { + const nodeId = "sss"; + + nodes.push({ + id: nodeId, + label: `${node.name || node.type}\n${node.kind || ''}`, + color: this.getNodeColor(node.name), + title: this.generateNodeTooltip(node), + type: node.type, + font: { + size: 14, + face: 'Inter', + multi: 'html', + bold: category === 'Rule' || category === 'TechnologyRule' + }, + borderWidth: 2, + shadow: { + enabled: true, + color: 'rgba(97, 218, 251, 0.2)', + size: 4, + x: 0, + y: 2 + } + }); + + if (parentId) { + edges.push({ + from: parentId, + to: nodeId, + arrows: 'to' + }); + } + + if (node.children) { + node.children.forEach(child => { + this.processASTData(child, nodeId, nodes, edges); + }); + } + + return { nodes, edges }; + }, + + getNodeColor(type) { + // Colors based on Code-Pathfinder rule categories + const colors = { + // Technology-based bundles (e.g., android/) + 'TechnologyRule': '#61dafb', + 'AndroidRule': '#61dafb', + 'WebRule': '#61dafb', + + // Language-based bundles (e.g., java/) + 'LanguageRule': '#98c379', + 'JavaRule': '#98c379', + 'KotlinRule': '#98c379', + + // Rule types + 'Rule': '#c678dd', + 'CQLRule': '#c678dd', + 'QueryRule': '#c678dd', + + // Metadata and others + 'RuleMetadata': '#e5c07b', + 'RuleProvider': '#e5c07b', + 'RuleBundle': '#e5c07b', + 'default': '#4d4d4d' + }; + return colors[type] || colors.default; + }, + + generateNodeTooltip(node) { + const details = []; + if (node.type) details.push(`Type: ${node.type}`); + if (node.name) details.push(`Name: ${node.name}`); + if (node.kind) details.push(`Kind: ${node.kind}`); + if (node.severity) details.push(`Severity: ${node.severity}`); + if (node.securitySeverity) details.push(`Security Severity: ${node.securitySeverity}`); + if (node.precision) details.push(`Precision: ${node.precision}`); + if (node.tags && node.tags.length > 0) details.push(`Tags: ${node.tags.join(', ')}`); + if (node.ruleProvider) details.push(`Provider: ${node.ruleProvider}`); + if (node.description) details.push(`\nDescription: ${node.description}`); + return details.join('\n'); + }, + + + + handleError(message, error) { + console.error(message, error); + const errorElement = document.getElementById('errorMessage'); + errorElement.textContent = error.message; + errorElement.style.display = 'block'; + setTimeout(() => { + errorElement.style.display = 'none'; + }, 5000); + } +}; + +// Network Visualization Service +const VisualizationService = { + updateVisualization(nodes, edges) { + if (!network) return; + + const data = { + nodes: new vis.DataSet(nodes), + edges: new vis.DataSet(edges) + }; + + network.setData(data); + currentNodes = nodes; + currentEdges = edges; + + // Fit the network view + network.fit({ + animation: { + duration: 1000, + easingFunction: 'easeInOutQuad' + } + }); + }, + + initializeNetwork(container) { + network = new vis.Network(container, { + nodes: new vis.DataSet([]), + edges: new vis.DataSet([]) + }, options); + + this.initializeNetworkEvents(); + }, + + initializeNetworkEvents() { + if (!network) return; + + network.on('click', (params) => { + if (params.nodes.length > 0) { + const nodeId = params.nodes[0]; + const node = currentNodes.find(n => n.id === nodeId); + if (node) { + console.log('Selected node:', node); + } + } + }); + + network.on('stabilizationProgress', (params) => { + console.log('Layout stabilization:', Math.round(params.iterations / params.total * 100), '%'); + }); + + network.on('stabilizationIterationsDone', () => { + console.log('Layout stabilization finished'); + }); + } +}; // Initialize global variables let network = null; let editor = null; let queryEditor = null; +let currentNodes = []; +let currentEdges = []; // Network visualization options const options = { nodes: { - shape: 'dot', - size: 20, + shape: 'circle', + margin: 10, + widthConstraint: { + maximum: 200 + }, + borderWidth: 2, + color: { + border: '#61dafb', + background: '#1e1e1e' + }, font: { face: 'Inter', size: 14, color: '#ffffff', - strokeWidth: 0 + multi: true, + bold: { + color: '#61dafb', + size: 15 + } }, - borderWidth: 0, shadow: { enabled: true, - color: 'rgba(0,0,0,0.2)', - size: 5, + color: 'rgba(97, 218, 251, 0.2)', + size: 4, x: 0, y: 2 } }, edges: { color: { - color: '#4f4f4f', - highlight: '#666666', - hover: '#666666' + color: '#4d4d4d', + highlight: '#61dafb', + hover: '#61dafb' }, - width: 1, + width: 1.5, smooth: { - type: 'continuous', - roundness: 0.5 + type: 'cubicBezier', + forceDirection: 'vertical', + roundness: 0.3 }, arrows: { to: { enabled: true, - scaleFactor: 0.5 + scaleFactor: 0.8, + type: 'arrow' } }, - dashes: true + selectionWidth: 2, + hoverWidth: 2 }, physics: { enabled: true, - solver: 'forceAtlas2Based', - forceAtlas2Based: { - gravitationalConstant: -50, - springLength: 200, - springConstant: 0.1 + hierarchicalRepulsion: { + nodeDistance: 150, + springLength: 150, + springConstant: 0.2, + damping: 0.09 }, stabilization: { enabled: true, - iterations: 1000 + iterations: 1000, + updateInterval: 50, + fit: true } }, interaction: { @@ -122,93 +332,37 @@ const options = { }, layout: { improvedLayout: true, - hierarchical: false + hierarchical: { + enabled: true, + direction: 'UD', + sortMethod: 'directed', + nodeSpacing: 120, + levelSeparation: 150, + blockShifting: true, + edgeMinimization: true, + parentCentralization: true, + treeSpacing: 100 + } } }; -// Initialize visualization and editors when DOM is loaded +// Initialize application when DOM is loaded document.addEventListener('DOMContentLoaded', () => { - // Initialize resizable panels initResizablePanel(); - // Initialize network container const container = document.getElementById('visualization'); if (container) { - // Create the network - network = new vis.Network(container, { - nodes: new vis.DataSet([]), - edges: new vis.DataSet([]) - }, options); - - // Add zoom controls to the container - container.appendChild(zoomControls); - - // Initialize network events - initializeNetworkEvents(); + VisualizationService.initializeNetwork(container); } - // Function to parse and visualize code - const parseAndVisualizeCode = async (javaSource) => { - try { - const response = await fetch('/api/parse', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ code: javaSource }), - }); - - const data = await response.json(); - if (!response.ok) { - // Show error message to user - const errorMsg = data.error || 'Failed to parse code'; - document.getElementById('errorMessage').textContent = errorMsg; - document.getElementById('errorMessage').style.display = 'block'; - setTimeout(() => { - document.getElementById('errorMessage').style.display = 'none'; - }, 5000); - return; - } - - // Clear any previous error messages - document.getElementById('errorMessage').style.display = 'none'; - - const visNodes = []; - const visEdges = []; - - // Process AST nodes and update visualization - if (data.ast) { - processNode(data.ast); - updateVisualization(visNodes, visEdges); - updateASTList(visNodes); - } else { - throw new Error('Invalid AST structure received'); - } - } catch (error) { - console.error('Error parsing code:', error); - document.getElementById('errorMessage').textContent = 'Failed to process code: ' + error.message; - document.getElementById('errorMessage').style.display = 'block'; - setTimeout(() => { - document.getElementById('errorMessage').style.display = 'none'; - }, 5000); - } - }; - - // Create debounced version of parse function - const debouncedParse = debounce(parseAndVisualizeCode, 1000); - - // Initialize main CodeMirror + // Initialize editors editor = CodeMirror(document.getElementById('codeEditor'), { mode: 'text/x-java', theme: 'monokai', lineNumbers: true, - autoCloseBrackets: true, - matchBrackets: true, - // Add change event handler for automatic parsing - onChange: (cm) => { - const code = cm.getValue(); - debouncedParse(code); - }, + lineWrapping: true, + scrollbarStyle: 'native', + viewportMargin: Infinity, value: `public class UserService { private final UserRepository userRepository; private final Logger logger; @@ -241,202 +395,44 @@ document.addEventListener('DOMContentLoaded', () => { } userRepository.deleteById(id); } -}`, - }); - - // Add execute button event listener - document.getElementById('executeQuery').addEventListener('click', async () => { - const javaSource = editor.getValue(); - const query = queryEditor.getValue(); - - try { - // Execute query - const response = await fetch('/api/analyze', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - javaSource, - query, - }), - }); - - const data = await response.json(); - if (data.error) { - console.error('Query error:', data.error); - return; - } - - // Display results - const resultsContainer = document.getElementById('queryResults'); - resultsContainer.innerHTML = ''; - data.results.forEach(result => { - const resultDiv = document.createElement('div'); - resultDiv.className = 'result-item'; - resultDiv.innerHTML = ` -
Line ${result.line}: ${result.file}
-
${result.snippet}
- `; - resultsContainer.appendChild(resultDiv); - }); - } catch (error) { - console.error('Failed to execute query:', error); - } - }); - - // Add parse button event listener - document.getElementById('parseAST')?.addEventListener('click', async () => { - const javaSource = editor.getValue(); - - try { - // Parse AST - const response = await fetch('/api/parse', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - code: javaSource, - }), - }); - - const data = await response.json(); - if (data.error) { - console.error('Parse error:', data.error); - return; - } - - // Convert AST to vis.js format - const visNodes = []; - const visEdges = []; - let nodeId = 1; - - function processNode(node, parentId = null) { - const currentId = nodeId++; - visNodes.push({ - id: currentId, - label: `${node.type}\n${node.name || ''}`, - color: getNodeColor(node.type), - title: `Line: ${node.line}`, - }); - - if (parentId !== null) { - visEdges.push({ - from: parentId, - to: currentId, - }); - } - - if (node.children) { - node.children.forEach(child => processNode(child, currentId)); - } - } - - processNode(data.ast); - - // Update visualization - updateVisualization(visNodes, visEdges); - } catch (error) { - console.error('Failed to parse AST:', error); - } +}` }); - // Initialize query editor queryEditor = CodeMirror(document.getElementById('queryEditor'), { mode: 'text/x-java', theme: 'monokai', lineNumbers: true, - placeholder: 'Enter your query here...', lineWrapping: true, - value: `from Method m -where m.hasName("get") and m.getReturnType() instanceof TypeString -select m, "Found getter method returning String"` + placeholder: 'Enter your query here...' }); - // Handle window resize - window.addEventListener('resize', () => { - editor.refresh(); - queryEditor.refresh(); + // Add event listeners + document.getElementById('parseAST').addEventListener('click', () => { + const code = editor.getValue(); + ASTService.parseAndVisualize(code); }); - // Handle query execution - document.getElementById('executeQuery')?.addEventListener('click', async () => { + document.getElementById('executeQuery').addEventListener('click', async () => { + const code = editor.getValue(); const query = queryEditor.getValue(); - const results = document.getElementById('queryResults'); - - try { - const response = await fetch('/query', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - code: editor.getValue(), - query: query - }), - }); - - const data = await response.json(); - - if (data.error) { - results.innerHTML = `Error: ${data.error}`; - return; - } - - // Highlight matching nodes in the visualization - highlightNodes(data.matches); - - // Display results - results.innerHTML = formatQueryResults(data); - } catch (error) { - results.innerHTML = `Error: ${error.message}`; - } + await executeQuery(code, query); + UIState.setActiveTab('results'); }); + // Initialize tab switching + document.querySelectorAll('.tab-button').forEach(button => { + button.addEventListener('click', () => { + UIState.setActiveTab(button.dataset.tab); + }); + }); - // Trigger initial parse - const initialCode = editor.getValue(); - fetch('/api/parse', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ code: initialCode }), - }) - .then(response => response.json()) - .then(data => { - if (!data.error) { - updateVisualization(data.nodes, data.edges); - } - }) - .catch(error => console.error('Error:', error)); - - // Auto-parse on editor changes - editor.on('change', debounce(async () => { - try { - const code = editor.getValue(); - const response = await fetch('/api/parse', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ code }), - }); - const data = await response.json(); - if (data.error) { - console.error('Error parsing code:', data.error); - return; - } - updateVisualization(data.nodes, data.edges); - } catch (error) { - console.error('Error:', error); - } + // Add automatic parsing on code change + editor.on('change', debounce(() => { + const code = editor.getValue(); + ASTService.parseAndVisualize(code); }, 1000)); }); - - // Create zoom controls for the visualization const zoomControls = document.createElement('div'); zoomControls.className = 'zoom-controls'; @@ -488,58 +484,6 @@ document.addEventListener('DOMContentLoaded', () => { }); } - // Initialize CodeMirror editor - editor = CodeMirror(document.getElementById('codeEditor'), { - mode: 'text/x-java', - theme: 'monokai', - lineNumbers: true, - autoCloseBrackets: true, - matchBrackets: true, - value: `public class UserService { - private final UserRepository userRepository; - private final Logger logger; - - public UserService(UserRepository userRepository) { - this.userRepository = userRepository; - this.logger = LoggerFactory.getLogger(UserService.class); - } - - public User getUserById(String id) { - logger.info("Fetching user with id: {}", id); - return userRepository.findById(id) - .orElseThrow(() -> new UserNotFoundException("User not found")); - } - - public List getAllUsers() { - return userRepository.findAll(); - } - - public User createUser(User user) { - if (userRepository.existsByEmail(user.getEmail())) { - throw new DuplicateEmailException("Email already exists"); - } - return userRepository.save(user); - } - - public void deleteUser(String id) { - if (!userRepository.existsById(id)) { - throw new UserNotFoundException("User not found"); - } - userRepository.deleteById(id); - } -}` - }); - - // Initialize query editor - queryEditor = CodeMirror(document.getElementById('queryEditor'), { - mode: 'text/x-java', - theme: 'monokai', - lineNumbers: true, - autoCloseBrackets: true, - matchBrackets: true, - value: '// Enter your CodeQL query here' - }); - // Add event listeners for buttons document.getElementById('executeQuery')?.addEventListener('click', async () => { const javaSource = editor.getValue(); @@ -600,9 +544,8 @@ document.addEventListener('DOMContentLoaded', () => { const nodeId = node.id || `node_${Math.random().toString(36).substr(2, 9)}`; visNodes.push({ id: nodeId, - label: `${node.type}`, - type: node.type, - line: node.line + label: node.name || node.type, + type: node.type }); if (parentId) { @@ -718,7 +661,6 @@ function updateVisualization(newNodes = [], newEdges = []) { network.fit(); } - function getNodeColor(type) { if (!type) return '#FF5722'; diff --git a/playground/public/static/style.css b/playground/public/static/style.css index 46fe8c5b..48774a3f 100644 --- a/playground/public/static/style.css +++ b/playground/public/static/style.css @@ -103,7 +103,7 @@ body { cursor: col-resize; width: 6px; height: 100%; - left: 30%; + left: 50%; transform: translateX(-50%); } @@ -138,7 +138,7 @@ body { /* Left Panel */ .left-panel { - width: 30%; + width: 50%; background-color: #111111; display: flex; flex-direction: column; @@ -147,12 +147,16 @@ body { .editor-container { flex: 1; - overflow: hidden; + display: flex; + flex-direction: column; + position: relative; + overflow: auto; + min-height: 0; } /* Right Panel */ .right-panel { - width: 70%; + width: 50%; background-color: #111111; display: flex; flex-direction: column; @@ -161,7 +165,7 @@ body { /* Query Console */ .query-console { - height: 200px; + height: 300px; background-color: #111111; border-top: 1px solid #3d3d3d; display: flex; @@ -174,6 +178,24 @@ body { gap: 10px; padding: 10px; background-color: #111111; + height: 200px; + position: relative; + overflow: hidden; +} + +.query-input-container .CodeMirror { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + height: 100% !important; + font-size: 14px; + line-height: 1.6; + padding: 8px; + border-radius: 4px; + background-color: #1e1e1e; + font-family: 'Fira Code', monospace; } .button-group { @@ -185,16 +207,19 @@ body { .action-btn { display: flex; align-items: center; - gap: 6px; - background-color: #1b4332; + gap: 8px; + background-color: #2d8632; color: #ffffff; border: none; - padding: 8px 12px; + padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 0.9rem; + font-weight: 500; transition: all 0.2s ease; - box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.1); + box-shadow: 0 2px 4px rgba(0,0,0,0.2); + text-transform: uppercase; + letter-spacing: 0.5px; } .action-btn:hover { @@ -349,25 +374,53 @@ body { /* Legend */ .legend { display: flex; - gap: 1rem; + gap: 20px; align-items: center; margin-left: auto; + background-color: rgba(97, 218, 251, 0.05); + padding: 8px 12px; + border-radius: 4px; + border: 1px solid rgba(97, 218, 251, 0.1); } .legend-item { display: flex; align-items: center; - gap: 6px; - font-size: 0.8rem; + gap: 8px; + font-size: 0.85rem; + font-weight: 500; + letter-spacing: 0.3px; + color: #e2e2e2; } .dot { width: 12px; height: 12px; border-radius: 50%; + display: inline-block; + margin-right: 8px; + box-shadow: 0 1px 3px rgba(0,0,0,0.2); } -.dot.class { background-color: #4CAF50; } +.dot.technology { + background-color: #61dafb; + border: 2px solid rgba(97, 218, 251, 0.3); +} + +.dot.language { + background-color: #98c379; + border: 2px solid rgba(152, 195, 121, 0.3); +} + +.dot.rule { + background-color: #c678dd; + border: 2px solid rgba(198, 120, 221, 0.3); +} + +.dot.metadata { + background-color: #e5c07b; + border: 2px solid rgba(229, 192, 123, 0.3); +} .dot.method { background-color: #2196F3; } .dot.field { background-color: #FF9800; } .dot.declaration { background-color: #FF5722; } @@ -425,3 +478,116 @@ body { background: rgba(45, 45, 45, 1); } +/* Tabbed Interface */ +.tab-container { + display: flex; + gap: 1rem; + margin-bottom: 1rem; +} + +.tab-button { + background: #2a2a2a; + border: none; + color: #ffffff; + padding: 0.5rem 1rem; + border-radius: 4px; + cursor: pointer; + font-family: 'Inter', sans-serif; + font-weight: 500; + transition: background-color 0.2s; +} + +.tab-button:hover { + background: #3a3a3a; +} + +.tab-button.active { + background: #4a4a4a; +} + +.tab-content { + flex: 1; + display: flex; + flex-direction: column; +} + +.tab-pane { + display: none; + flex: 1; +} + +.tab-pane.active { + display: flex; +} + +.query-console { + border-top: 1px solid #3a3a3a; +} + +.query-console .panel-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.button-group { + display: flex; + gap: 0.5rem; +} + +.action-btn { + background: #2d8632; + border: none; + color: #ffffff; + padding: 0.5rem 1rem; + border-radius: 4px; + cursor: pointer; + display: flex; + align-items: center; + gap: 0.5rem; + justify-content: center; + transition: all 0.2s; + font-weight: 500; +} + +.action-btn:hover { + background: #36a03c; + transform: translateY(-1px); +} + +.btn-icon { + width: 16px; + height: 16px; +} + +.query-results { + background: #1e1e1e; + border-radius: 4px; + padding: 1rem; + margin-top: 1rem; + overflow: auto; + flex: 1; +} + +.result-item { + margin-bottom: 1rem; + padding: 1rem; + background: #2a2a2a; + border-radius: 4px; +} + +.result-location { + color: #9e9e9e; + font-size: 0.9rem; + margin-bottom: 0.5rem; +} + +.result-snippet { + margin: 0; + padding: 0.5rem; + background: #1e1e1e; + border-radius: 2px; + font-family: monospace; + white-space: pre-wrap; +} From 6e90e15c038ae43e5ce2c72568fc7bd8ca386bea Mon Sep 17 00:00:00 2001 From: shivasurya Date: Wed, 12 Mar 2025 23:47:32 -0400 Subject: [PATCH 05/22] fix duplicate call --- playground/public/static/script.js | 71 ++++++++++++------------------ 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/playground/public/static/script.js b/playground/public/static/script.js index 8fb7e0ec..d590af3d 100644 --- a/playground/public/static/script.js +++ b/playground/public/static/script.js @@ -90,7 +90,7 @@ const ASTService = { } const { nodes, edges } = this.processASTData(data.ast); - this.updateVisualization(nodes, edges); + VisualizationService.updateVisualization(nodes, edges); return { nodes, edges }; } catch (error) { this.handleError('Error parsing code:', error); @@ -101,17 +101,42 @@ const ASTService = { processASTData(node, parentId = null, nodes = [], edges = []) { const nodeId = "sss"; + // Filter for specific node types + const validTypes = ['ClassDeclaration', 'ClassOrInterfaceDeclaration', + 'MethodDeclaration', 'ConstructorDeclaration', + 'VariableDeclaration', 'VariableDeclarator', + 'Parameter', 'LocalVariable', + 'FieldDeclaration', 'FieldAccess']; + + if (!validTypes.includes(node.type)) { + return { nodes, edges }; + } + + // Determine node category based on Java AST node types + let category = 'expressions'; + + if (node.type === 'ClassDeclaration' || node.type === 'ClassOrInterfaceDeclaration') { + category = 'class'; + } else if (node.type === 'MethodDeclaration' || node.type === 'ConstructorDeclaration') { + category = 'constructor-method'; + } else if (node.type === 'VariableDeclaration' || node.type === 'VariableDeclarator' || + node.type === 'Parameter' || node.type === 'LocalVariable') { + category = 'variables'; + } else if (node.type === 'FieldDeclaration' || node.type === 'FieldAccess') { + category = 'fields'; + } + nodes.push({ id: nodeId, label: `${node.name || node.type}\n${node.kind || ''}`, - color: this.getNodeColor(node.name), + group: category, // Use group to apply CSS classes title: this.generateNodeTooltip(node), type: node.type, font: { size: 14, face: 'Inter', multi: 'html', - bold: category === 'Rule' || category === 'TechnologyRule' + bold: category === 'class' }, borderWidth: 2, shadow: { @@ -141,48 +166,16 @@ const ASTService = { }, getNodeColor(type) { - // Colors based on Code-Pathfinder rule categories - const colors = { - // Technology-based bundles (e.g., android/) - 'TechnologyRule': '#61dafb', - 'AndroidRule': '#61dafb', - 'WebRule': '#61dafb', - - // Language-based bundles (e.g., java/) - 'LanguageRule': '#98c379', - 'JavaRule': '#98c379', - 'KotlinRule': '#98c379', - - // Rule types - 'Rule': '#c678dd', - 'CQLRule': '#c678dd', - 'QueryRule': '#c678dd', - - // Metadata and others - 'RuleMetadata': '#e5c07b', - 'RuleProvider': '#e5c07b', - 'RuleBundle': '#e5c07b', - 'default': '#4d4d4d' - }; - return colors[type] || colors.default; + return colors.default; }, generateNodeTooltip(node) { const details = []; if (node.type) details.push(`Type: ${node.type}`); if (node.name) details.push(`Name: ${node.name}`); - if (node.kind) details.push(`Kind: ${node.kind}`); - if (node.severity) details.push(`Severity: ${node.severity}`); - if (node.securitySeverity) details.push(`Security Severity: ${node.securitySeverity}`); - if (node.precision) details.push(`Precision: ${node.precision}`); - if (node.tags && node.tags.length > 0) details.push(`Tags: ${node.tags.join(', ')}`); - if (node.ruleProvider) details.push(`Provider: ${node.ruleProvider}`); - if (node.description) details.push(`\nDescription: ${node.description}`); return details.join('\n'); }, - - handleError(message, error) { console.error(message, error); const errorElement = document.getElementById('errorMessage'); @@ -406,12 +399,6 @@ document.addEventListener('DOMContentLoaded', () => { placeholder: 'Enter your query here...' }); - // Add event listeners - document.getElementById('parseAST').addEventListener('click', () => { - const code = editor.getValue(); - ASTService.parseAndVisualize(code); - }); - document.getElementById('executeQuery').addEventListener('click', async () => { const code = editor.getValue(); const query = queryEditor.getValue(); From 522f707c1a00a7e70db7f2b2695ea3537b3d8cef Mon Sep 17 00:00:00 2001 From: shivasurya Date: Thu, 13 Mar 2025 10:14:32 -0400 Subject: [PATCH 06/22] fix the graph viz --- playground/public/static/script.js | 174 ++++++++--------------------- 1 file changed, 44 insertions(+), 130 deletions(-) diff --git a/playground/public/static/script.js b/playground/public/static/script.js index d590af3d..ddc10d5f 100644 --- a/playground/public/static/script.js +++ b/playground/public/static/script.js @@ -70,123 +70,6 @@ const UIState = { } }; -// AST Processing Service -const ASTService = { - async parseAndVisualize(javaSource) { - try { - const response = await fetch('/api/parse', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ code: javaSource }) - }); - - const data = await response.json(); - if (!response.ok) { - throw new Error(data.error || 'Failed to parse code'); - } - - if (!data.ast) { - throw new Error('Invalid AST structure received'); - } - - const { nodes, edges } = this.processASTData(data.ast); - VisualizationService.updateVisualization(nodes, edges); - return { nodes, edges }; - } catch (error) { - this.handleError('Error parsing code:', error); - return null; - } - }, - - processASTData(node, parentId = null, nodes = [], edges = []) { - const nodeId = "sss"; - - // Filter for specific node types - const validTypes = ['ClassDeclaration', 'ClassOrInterfaceDeclaration', - 'MethodDeclaration', 'ConstructorDeclaration', - 'VariableDeclaration', 'VariableDeclarator', - 'Parameter', 'LocalVariable', - 'FieldDeclaration', 'FieldAccess']; - - if (!validTypes.includes(node.type)) { - return { nodes, edges }; - } - - // Determine node category based on Java AST node types - let category = 'expressions'; - - if (node.type === 'ClassDeclaration' || node.type === 'ClassOrInterfaceDeclaration') { - category = 'class'; - } else if (node.type === 'MethodDeclaration' || node.type === 'ConstructorDeclaration') { - category = 'constructor-method'; - } else if (node.type === 'VariableDeclaration' || node.type === 'VariableDeclarator' || - node.type === 'Parameter' || node.type === 'LocalVariable') { - category = 'variables'; - } else if (node.type === 'FieldDeclaration' || node.type === 'FieldAccess') { - category = 'fields'; - } - - nodes.push({ - id: nodeId, - label: `${node.name || node.type}\n${node.kind || ''}`, - group: category, // Use group to apply CSS classes - title: this.generateNodeTooltip(node), - type: node.type, - font: { - size: 14, - face: 'Inter', - multi: 'html', - bold: category === 'class' - }, - borderWidth: 2, - shadow: { - enabled: true, - color: 'rgba(97, 218, 251, 0.2)', - size: 4, - x: 0, - y: 2 - } - }); - - if (parentId) { - edges.push({ - from: parentId, - to: nodeId, - arrows: 'to' - }); - } - - if (node.children) { - node.children.forEach(child => { - this.processASTData(child, nodeId, nodes, edges); - }); - } - - return { nodes, edges }; - }, - - getNodeColor(type) { - return colors.default; - }, - - generateNodeTooltip(node) { - const details = []; - if (node.type) details.push(`Type: ${node.type}`); - if (node.name) details.push(`Name: ${node.name}`); - return details.join('\n'); - }, - - handleError(message, error) { - console.error(message, error); - const errorElement = document.getElementById('errorMessage'); - errorElement.textContent = error.message; - errorElement.style.display = 'block'; - setTimeout(() => { - errorElement.style.display = 'none'; - }, 5000); - } -}; - // Network Visualization Service const VisualizationService = { updateVisualization(nodes, edges) { @@ -416,7 +299,7 @@ document.addEventListener('DOMContentLoaded', () => { // Add automatic parsing on code change editor.on('change', debounce(() => { const code = editor.getValue(); - ASTService.parseAndVisualize(code); + //ASTService.parseAndVisualize(code); }, 1000)); }); @@ -527,23 +410,54 @@ document.addEventListener('DOMContentLoaded', () => { const visEdges = []; // Process AST nodes - function processNode(node, parentId = null) { - const nodeId = node.id || `node_${Math.random().toString(36).substr(2, 9)}`; - visNodes.push({ - id: nodeId, - label: node.name || node.type, - type: node.type - }); + function processNode(node, parentId = null, lastValidParentId = null) { + const validTypes = ['ClassDeclaration', 'ClassOrInterfaceDeclaration', + 'MethodDeclaration', 'ConstructorDeclaration', + 'VariableDeclaration', 'VariableDeclarator', + 'Parameter', 'LocalVariable', + 'FieldDeclaration', 'FieldAccess']; - if (parentId) { - visEdges.push({ - from: parentId, - to: nodeId + const nodeId = node.id || `node_${Math.random().toString(36).substr(2, 9)}`; + let currentValidParentId = lastValidParentId; + + // Determine node category and add to visualization if it's a valid type + if (validTypes.includes(node.type)) { + let category = 'expressions'; + + if (node.type === 'ClassDeclaration' || node.type === 'ClassOrInterfaceDeclaration') { + category = 'class'; + } else if (node.type === 'MethodDeclaration' || node.type === 'ConstructorDeclaration') { + category = 'constructor-method'; + } else if (node.type === 'VariableDeclaration' || node.type === 'VariableDeclarator' || + node.type === 'Parameter' || node.type === 'LocalVariable') { + category = 'variables'; + } else if (node.type === 'FieldDeclaration' || node.type === 'FieldAccess') { + category = 'fields'; + } + + visNodes.push({ + id: nodeId, + label: node.name || node.type, + type: node.type, + group: category }); + + // Connect to the last valid parent if it exists + if (lastValidParentId) { + visEdges.push({ + from: lastValidParentId, + to: nodeId, + arrows: 'to' + }); + } + + // Update the last valid parent ID for children + currentValidParentId = nodeId; } + // Process children with the current valid parent ID if (node.children) { - node.children.forEach(child => processNode(child, nodeId)); + node.children.forEach(child => processNode(child, nodeId, currentValidParentId)); } } From 48486e0f8e2cc2b053f3f1809afcad1f80b34223 Mon Sep 17 00:00:00 2001 From: shivasurya Date: Thu, 13 Mar 2025 14:47:49 -0400 Subject: [PATCH 07/22] fix viz config --- playground/pkg/ast/parser.go | 31 +++- playground/public/static/index.html | 8 +- playground/public/static/script.js | 244 ++++++++++++++++++++++------ playground/public/static/style.css | 52 +++--- 4 files changed, 254 insertions(+), 81 deletions(-) diff --git a/playground/pkg/ast/parser.go b/playground/pkg/ast/parser.go index 22f2e930..5641f97f 100644 --- a/playground/pkg/ast/parser.go +++ b/playground/pkg/ast/parser.go @@ -2,10 +2,11 @@ package ast import ( "fmt" + "strings" - "github.com/smacker/go-tree-sitter" - "github.com/smacker/go-tree-sitter/java" "github.com/shivasurya/code-pathfinder/playground/pkg/models" + sitter "github.com/smacker/go-tree-sitter" + "github.com/smacker/go-tree-sitter/java" ) // ParseJavaSource parses Java source code into an AST using tree-sitter @@ -68,6 +69,13 @@ func buildASTFromTreeSitter(node *sitter.Node, sourceBytes []byte) *models.ASTNo } astNode.Modifier = getModifiers(node, sourceBytes) + case "constructor_declaration": + astNode.Type = "ConstructorDeclaration" + if nameNode := node.ChildByFieldName("name"); nameNode != nil { + astNode.Name = getNodeText(nameNode, sourceBytes) + } + astNode.Arguments = getMethodParameters(node, sourceBytes) + astNode.Modifier = getModifiers(node, sourceBytes) case "method_declaration": astNode.Type = "MethodDeclaration" if nameNode := node.ChildByFieldName("name"); nameNode != nil { @@ -90,7 +98,7 @@ func buildASTFromTreeSitter(node *sitter.Node, sourceBytes []byte) *models.ASTNo astNode.Modifier = getModifiers(node, sourceBytes) astNode.Value = getInitializer(node, sourceBytes) - case "variable_declaration": + case "local_variable_declaration": astNode.Type = "VariableDeclaration" if nameNode := node.ChildByFieldName("name"); nameNode != nil { astNode.Name = getNodeText(nameNode, sourceBytes) @@ -99,6 +107,23 @@ func buildASTFromTreeSitter(node *sitter.Node, sourceBytes []byte) *models.ASTNo astNode.DataType = getNodeText(typeNode, sourceBytes) } astNode.Value = getInitializer(node, sourceBytes) + + case "method_invocation": + astNode.Type = "MethodInvocation" + if nameNode := node.ChildByFieldName("name"); nameNode != nil { + var args []string + if paramNode := node.ChildByFieldName("arguments"); paramNode != nil { + args = getMethodParameters(paramNode, sourceBytes) + } + // print full method invocation with arguments + astNode.Name = fmt.Sprintf("%s(%s)", getNodeText(nameNode, sourceBytes), strings.Join(args, ", ")) + } + if typeNode := node.ChildByFieldName("type"); typeNode != nil { + astNode.ReturnType = getNodeText(typeNode, sourceBytes) + } + astNode.Arguments = getMethodParameters(node, sourceBytes) + astNode.Modifier = getModifiers(node, sourceBytes) + astNode.Value = getInitializer(node, sourceBytes) } // Process child nodes diff --git a/playground/public/static/index.html b/playground/public/static/index.html index 868bc001..b39af53e 100644 --- a/playground/public/static/index.html +++ b/playground/public/static/index.html @@ -36,19 +36,19 @@

Editor

- + Class
- + Method
- + Fields
- + Variables
diff --git a/playground/public/static/script.js b/playground/public/static/script.js index ddc10d5f..54497179 100644 --- a/playground/public/static/script.js +++ b/playground/public/static/script.js @@ -135,11 +135,8 @@ let currentEdges = []; // Network visualization options const options = { nodes: { - shape: 'circle', - margin: 10, - widthConstraint: { - maximum: 200 - }, + shape: 'dot', + size: 16, borderWidth: 2, color: { border: '#61dafb', @@ -148,12 +145,7 @@ const options = { font: { face: 'Inter', size: 14, - color: '#ffffff', - multi: true, - bold: { - color: '#61dafb', - size: 15 - } + color: '#ffffff' }, shadow: { enabled: true, @@ -166,58 +158,183 @@ const options = { edges: { color: { color: '#4d4d4d', + opacity: 0.6, highlight: '#61dafb', hover: '#61dafb' }, - width: 1.5, + width: 2, smooth: { - type: 'cubicBezier', - forceDirection: 'vertical', - roundness: 0.3 + type: 'curvedCW', + roundness: 0.2, + forceDirection: 'none' }, arrows: { to: { enabled: true, - scaleFactor: 0.8, - type: 'arrow' + scaleFactor: 0.7, + type: 'circle' } }, - selectionWidth: 2, - hoverWidth: 2 + shadow: { + enabled: true, + color: 'rgba(0,0,0,0.2)', + size: 5, + x: 2, + y: 2 + } }, physics: { enabled: true, - hierarchicalRepulsion: { - nodeDistance: 150, - springLength: 150, - springConstant: 0.2, - damping: 0.09 + solver: 'barnesHut', // Changed to barnesHut for more stability + barnesHut: { + gravitationalConstant: -10000, + centralGravity: 1.5, // Very strong center pull + springLength: 300, + springConstant: 0.5, // Very stiff springs + damping: 0.9, // High damping to prevent movement + avoidOverlap: 1 }, stabilization: { enabled: true, iterations: 1000, - updateInterval: 50, + updateInterval: 10, + onlyDynamicEdges: false, fit: true - } + }, + minVelocity: 0.01, // Very low min velocity + maxVelocity: 10, // Very low max velocity + timestep: 0.1, // Very slow physics + adaptiveTimestep: true }, interaction: { hover: true, tooltipDelay: 200, zoomView: true, - dragView: true + dragView: true, + dragNodes: true, + multiselect: true }, layout: { improvedLayout: true, + randomSeed: 42, hierarchical: { - enabled: true, - direction: 'UD', + enabled: false, + direction: 'LR', sortMethod: 'directed', - nodeSpacing: 120, - levelSeparation: 150, - blockShifting: true, - edgeMinimization: true, - parentCentralization: true, - treeSpacing: 100 + nodeSpacing: 200, + levelSeparation: 300 + } + }, + groups: { + class: { + color: { + background: 'rgba(97, 218, 251, 0.7)', + border: '#61dafb', + highlight: { background: '#61dafb', border: '#61dafb' }, + hover: { background: '#61dafb', border: '#61dafb' } + }, + shape: 'dot', + size: 50, + font: { + size: 18, + strokeWidth: 2, + strokeColor: '#000000' + }, + shadow: { + enabled: true, + color: 'rgba(0,0,0,0.2)', + size: 10, + x: 4, + y: 4 + } + }, + 'constructor-method': { + color: { + background: 'rgba(152, 195, 121, 0.7)', + border: '#98c379', + highlight: { background: '#98c379', border: '#98c379' }, + hover: { background: '#98c379', border: '#98c379' } + }, + shape: 'dot', + size: 45, + font: { + size: 16, + strokeWidth: 2, + strokeColor: '#000000' + }, + shadow: { + enabled: true, + color: 'rgba(0,0,0,0.2)', + size: 8, + x: 3, + y: 3 + } + }, + fields: { + color: { + background: 'rgba(229, 192, 123, 0.7)', + border: '#e5c07b', + highlight: { background: '#e5c07b', border: '#e5c07b' }, + hover: { background: '#e5c07b', border: '#e5c07b' } + }, + shape: 'dot', + size: 40, + font: { + size: 16, + strokeWidth: 2, + strokeColor: '#000000' + }, + shadow: { + enabled: true, + color: 'rgba(0,0,0,0.2)', + size: 8, + x: 3, + y: 3 + } + }, + variables: { + color: { + background: 'rgba(198, 120, 221, 0.7)', + border: '#c678dd', + highlight: { background: '#c678dd', border: '#c678dd' }, + hover: { background: '#c678dd', border: '#c678dd' } + }, + shape: 'dot', + size: 35, + font: { + size: 14, + strokeWidth: 2, + strokeColor: '#000000' + }, + shadow: { + enabled: true, + color: 'rgba(0,0,0,0.2)', + size: 8, + x: 3, + y: 3 + } + }, + 'method-calls': { + color: { + background: 'rgba(95, 179, 179, 0.7)', + border: '#5fb3b3', + highlight: { background: '#5fb3b3', border: '#5fb3b3' }, + hover: { background: '#5fb3b3', border: '#5fb3b3' } + }, + shape: 'dot', + size: 35, + font: { + size: 14, + strokeWidth: 2, + strokeColor: '#000000' + }, + shadow: { + enabled: true, + color: 'rgba(0,0,0,0.2)', + size: 8, + x: 3, + y: 3 + } } } }; @@ -266,7 +383,8 @@ document.addEventListener('DOMContentLoaded', () => { } public void deleteUser(String id) { - if (!userRepository.existsById(id)) { + int i = 0; + if (!userRepository.existsById(id, i)) { throw new UserNotFoundException("User not found"); } userRepository.deleteById(id); @@ -409,37 +527,52 @@ document.addEventListener('DOMContentLoaded', () => { const visNodes = []; const visEdges = []; - // Process AST nodes + // Process AST nodes with clustering support function processNode(node, parentId = null, lastValidParentId = null) { const validTypes = ['ClassDeclaration', 'ClassOrInterfaceDeclaration', 'MethodDeclaration', 'ConstructorDeclaration', 'VariableDeclaration', 'VariableDeclarator', 'Parameter', 'LocalVariable', - 'FieldDeclaration', 'FieldAccess']; + 'FieldDeclaration', 'FieldAccess', 'MethodInvocation']; const nodeId = node.id || `node_${Math.random().toString(36).substr(2, 9)}`; let currentValidParentId = lastValidParentId; - - // Determine node category and add to visualization if it's a valid type + if (validTypes.includes(node.type)) { - let category = 'expressions'; + console.log(node.type); + let category; + let mass = 1; // Base mass for node physics if (node.type === 'ClassDeclaration' || node.type === 'ClassOrInterfaceDeclaration') { category = 'class'; + mass = 3; // Make classes more stable } else if (node.type === 'MethodDeclaration' || node.type === 'ConstructorDeclaration') { category = 'constructor-method'; + mass = 2; // Methods slightly more stable } else if (node.type === 'VariableDeclaration' || node.type === 'VariableDeclarator' || node.type === 'Parameter' || node.type === 'LocalVariable') { category = 'variables'; } else if (node.type === 'FieldDeclaration' || node.type === 'FieldAccess') { category = 'fields'; + mass = 1.5; // Fields slightly more stable than variables + } else if (node.type === 'MethodInvocation') { + category = 'method-calls'; } visNodes.push({ id: nodeId, label: node.name || node.type, type: node.type, - group: category + group: category, + mass: mass, + value: mass * 5, + font: { + size: 14, + color: '#ffffff', + face: 'Inter' + }, + title: `${node.type}${node.name ? ': ' + node.name : ''} +${node.line ? 'Line: ' + node.line : ''}` }); // Connect to the last valid parent if it exists @@ -447,7 +580,18 @@ document.addEventListener('DOMContentLoaded', () => { visEdges.push({ from: lastValidParentId, to: nodeId, - arrows: 'to' + arrows: { + to: { + enabled: true, + scaleFactor: 0.5 + } + }, + length: 200, + value: 1 / mass, + smooth: { + type: 'continuous', + roundness: 0.5 + } }); } @@ -572,7 +716,13 @@ function getNodeColor(type) { highlight: { background: '#66BB6A', border: '#66BB6A' }, hover: { background: '#66BB6A', border: '#66BB6A' } }, - 'methoddeclaration': { + 'constructordeclaration': { + background: '#2196F3', + border: '#2196F3', + highlight: { background: '#42A5F5', border: '#42A5F5' }, + hover: { background: '#42A5F5', border: '#42A5F5' } + }, + 'MethodDeclaration': { background: '#2196F3', border: '#2196F3', highlight: { background: '#42A5F5', border: '#42A5F5' }, @@ -584,7 +734,7 @@ function getNodeColor(type) { highlight: { background: '#FFA726', border: '#FFA726' }, hover: { background: '#FFA726', border: '#FFA726' } }, - 'compilationunit': { + 'methodinvocation': { background: '#9C27B0', border: '#9C27B0', highlight: { background: '#AB47BC', border: '#AB47BC' }, @@ -600,9 +750,11 @@ function getNodeColor(type) { const nodeType = type.toLowerCase(); if (nodeType.includes('class')) return colors.classdeclaration; - if (nodeType.includes('method')) return colors.methoddeclaration; + if (nodeType.includes('methoddeclaration')) return colors.methoddeclaration; if (nodeType.includes('field')) return colors.fielddeclaration; if (nodeType.includes('compilation')) return colors.compilationunit; + if (nodeType.includes('methodinvocation')) return colors.methodinvocation; + if (nodeType.includes('constructor')) return colors.constructordeclaration; return colors.default; } diff --git a/playground/public/static/style.css b/playground/public/static/style.css index 48774a3f..f1caa991 100644 --- a/playground/public/static/style.css +++ b/playground/public/static/style.css @@ -402,52 +402,48 @@ body { box-shadow: 0 1px 3px rgba(0,0,0,0.2); } -.dot.technology { - background-color: #61dafb; - border: 2px solid rgba(97, 218, 251, 0.3); +.dot.class { + background-color: #4CAF50; + border: 2px solid hsl(134, 73%, 65%); } -.dot.language { - background-color: #98c379; - border: 2px solid rgba(152, 195, 121, 0.3); +.dot.method { + background-color: #2196F3; + border: 2px solid hsl(189, 38%, 62%); } -.dot.rule { - background-color: #c678dd; - border: 2px solid rgba(198, 120, 221, 0.3); +.dot.field { + background-color: #E9C07B; + border: 2px solid #e5c07b; } -.dot.metadata { - background-color: #e5c07b; - border: 2px solid rgba(229, 192, 123, 0.3); +.dot.variable { + background-color: #ec8c4cb3; + border: 2px solid #c678dd; } -.dot.method { background-color: #2196F3; } -.dot.field { background-color: #FF9800; } -.dot.declaration { background-color: #FF5722; } - +/* Legend styling */ .legend-item { display: flex; align-items: center; gap: 0.5rem; - font-size: 0.8rem; - color: #cccccc; + font-size: 0.85rem; + color: #e2e2e2; + font-weight: 500; } .dot { - width: 8px; - height: 8px; + width: 10px; + height: 10px; border-radius: 50%; display: inline-block; + box-shadow: 0 1px 3px rgba(0,0,0,0.2); } -.dot.function { background-color: #61dafb; } -.dot.variable { background-color: #c678dd; } -.dot.expression { background-color: #98c379; } - -/* Node colors for different types */ -.node.function circle { fill: #61dafb; } -.node.variable circle { fill: #c678dd; } -.node.expression circle { fill: #98c379; } +/* Node colors following Atlas design principles */ +.node.class circle { fill: rgba(97, 218, 251, 0.7); stroke: #61dafb; } +.node.method circle { fill: rgba(152, 195, 121, 0.7); stroke: #98c379; } +.node.field circle { fill: rgba(229, 192, 123, 0.7); stroke: #e5c07b; } +.node.variable circle { fill: rgba(198, 120, 221, 0.7); stroke: #c678dd; } /* Zoom controls */ .zoom-controls { From ad99a19c5fe4c5bb47f8567e794c2b793820b74a Mon Sep 17 00:00:00 2001 From: shivasurya Date: Thu, 13 Mar 2025 14:48:19 -0400 Subject: [PATCH 08/22] go mod tidy --- playground/go.mod | 9 +-------- playground/go.sum | 10 ---------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/playground/go.mod b/playground/go.mod index 946dde69..6167d56c 100644 --- a/playground/go.mod +++ b/playground/go.mod @@ -6,14 +6,7 @@ replace github.com/shivasurya/code-pathfinder/sourcecode-parser => ../sourcecode require ( github.com/google/uuid v1.6.0 - github.com/shivasurya/code-pathfinder/sourcecode-parser v0.0.0-00010101000000-000000000000 github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 ) -require ( - github.com/antlr4-go/antlr/v4 v4.13.1 // indirect - github.com/expr-lang/expr v1.16.9 // indirect - github.com/joho/godotenv v1.5.1 // indirect - github.com/posthog/posthog-go v1.2.24 // indirect - golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect -) +require github.com/stretchr/testify v1.10.0 // indirect diff --git a/playground/go.sum b/playground/go.sum index 9802fc33..a246f53b 100644 --- a/playground/go.sum +++ b/playground/go.sum @@ -1,22 +1,12 @@ -github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= -github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI= -github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posthog/posthog-go v1.2.24 h1:A+iG4saBJemo++VDlcWovbYf8KFFNUfrCoJtsc40RPA= -github.com/posthog/posthog-go v1.2.24/go.mod h1:uYC2l1Yktc8E+9FAHJ9QZG4vQf/NHJPD800Hsm7DzoM= github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 h1:6C8qej6f1bStuePVkLSFxoU22XBS165D3klxlzRg8F4= github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82/go.mod h1:xe4pgH49k4SsmkQq5OT8abwhWmnzkhpgnXeekbx2efw= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 04df0fec8b69c90f2ee27d8dc0a25bd328ebe999 Mon Sep 17 00:00:00 2001 From: shivasurya Date: Thu, 13 Mar 2025 18:02:25 -0400 Subject: [PATCH 09/22] fix ui for viz --- playground/public/static/script.js | 13 +++++++------ playground/public/static/style.css | 17 +++++------------ 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/playground/public/static/script.js b/playground/public/static/script.js index 54497179..547d8abb 100644 --- a/playground/public/static/script.js +++ b/playground/public/static/script.js @@ -136,7 +136,7 @@ let currentEdges = []; const options = { nodes: { shape: 'dot', - size: 16, + size: 20, borderWidth: 2, color: { border: '#61dafb', @@ -673,13 +673,14 @@ function updateVisualization(newNodes = [], newEdges = []) { color: getNodeColor(node.type), font: { color: '#ffffff', - size: 14, + size: 20, face: 'Inter' }, - shape: 'box', - margin: 10, + shape: 'dot', + size: 25, shadow: true, - title: node.line ? `Line: ${node.line}` : undefined + title: node.line ? `Line: ${node.line}` : undefined, + mass: 1, }))); // Create DataSet for edges with consistent styling @@ -722,7 +723,7 @@ function getNodeColor(type) { highlight: { background: '#42A5F5', border: '#42A5F5' }, hover: { background: '#42A5F5', border: '#42A5F5' } }, - 'MethodDeclaration': { + 'methoddeclaration': { background: '#2196F3', border: '#2196F3', highlight: { background: '#42A5F5', border: '#42A5F5' }, diff --git a/playground/public/static/style.css b/playground/public/static/style.css index f1caa991..c336aadf 100644 --- a/playground/public/static/style.css +++ b/playground/public/static/style.css @@ -409,17 +409,17 @@ body { .dot.method { background-color: #2196F3; - border: 2px solid hsl(189, 38%, 62%); + border: 2px solid #2196F3; } .dot.field { - background-color: #E9C07B; - border: 2px solid #e5c07b; + background-color: #FF9800; + border: 2px solid #FF9800; } .dot.variable { - background-color: #ec8c4cb3; - border: 2px solid #c678dd; + background-color: #FF5722; + border: 2px solid #FF5722; } /* Legend styling */ .legend-item { @@ -439,12 +439,6 @@ body { box-shadow: 0 1px 3px rgba(0,0,0,0.2); } -/* Node colors following Atlas design principles */ -.node.class circle { fill: rgba(97, 218, 251, 0.7); stroke: #61dafb; } -.node.method circle { fill: rgba(152, 195, 121, 0.7); stroke: #98c379; } -.node.field circle { fill: rgba(229, 192, 123, 0.7); stroke: #e5c07b; } -.node.variable circle { fill: rgba(198, 120, 221, 0.7); stroke: #c678dd; } - /* Zoom controls */ .zoom-controls { position: absolute; @@ -524,7 +518,6 @@ body { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 1rem; } .button-group { From 3a00025a26ad24024e3a02363395bf436934e469 Mon Sep 17 00:00:00 2001 From: shivasurya Date: Thu, 13 Mar 2025 18:05:28 -0400 Subject: [PATCH 10/22] fix ui stuff --- playground/public/static/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/playground/public/static/style.css b/playground/public/static/style.css index c336aadf..1a85fb6f 100644 --- a/playground/public/static/style.css +++ b/playground/public/static/style.css @@ -381,6 +381,7 @@ body { padding: 8px 12px; border-radius: 4px; border: 1px solid rgba(97, 218, 251, 0.1); + margin-bottom: 10px; } .legend-item { From 120078406db0d283f1aae915c0d59432f3c06cc4 Mon Sep 17 00:00:00 2001 From: shivasurya Date: Thu, 13 Mar 2025 18:37:48 -0400 Subject: [PATCH 11/22] fixed code style --- playground/public/static/index.html | 30 ++- playground/public/static/js/app.js | 128 +++++++++ .../static/js/components/UIComponents.js | 111 ++++++++ .../public/static/js/services/ASTService.js | 95 +++++++ .../static/js/services/EditorService.js | 106 ++++++++ .../js/services/VisualizationService.js | 248 ++++++++++++++++++ playground/public/static/js/utils/helpers.js | 12 + playground/public/static/style.css | 12 +- 8 files changed, 728 insertions(+), 14 deletions(-) create mode 100644 playground/public/static/js/app.js create mode 100644 playground/public/static/js/components/UIComponents.js create mode 100644 playground/public/static/js/services/ASTService.js create mode 100644 playground/public/static/js/services/EditorService.js create mode 100644 playground/public/static/js/services/VisualizationService.js create mode 100644 playground/public/static/js/utils/helpers.js diff --git a/playground/public/static/index.html b/playground/public/static/index.html index b39af53e..a5e4922e 100644 --- a/playground/public/static/index.html +++ b/playground/public/static/index.html @@ -3,14 +3,32 @@ - Code Structure Visualizer - + Code-Pathfinder + + + + + + + + + + + + + + + + + + +
@@ -90,6 +108,12 @@

Query Console

- + + + + + + + diff --git a/playground/public/static/js/app.js b/playground/public/static/js/app.js new file mode 100644 index 00000000..d317d70f --- /dev/null +++ b/playground/public/static/js/app.js @@ -0,0 +1,128 @@ +// Main application entry point +import { VisualizationService } from './services/VisualizationService.js'; +import { ASTService } from './services/ASTService.js'; +import { UIComponents } from './components/UIComponents.js'; +import { EditorService } from './services/EditorService.js'; +import { debounce } from './utils/helpers.js'; + +class CodePathfinder { + constructor() { + this.visualizationService = new VisualizationService(); + this.astService = new ASTService(); + this.uiComponents = new UIComponents(); + this.editorService = new EditorService(); + } + + async initialize() { + // Initialize UI components + this.uiComponents.initializeResizablePanel(); + + // Initialize network container + const container = document.getElementById('visualization'); + if (container) { + this.network = this.visualizationService.initializeNetwork(container); + this.uiComponents.initializeZoomControls(container, this.network); + } + + // Initialize event listeners + this.initializeEventListeners(); + + // Initialize editor and trigger initial parse + try { + const { editor, queryEditor } = await this.editorService.initializeEditors(); + + this.editor = editor; + this.queryEditor = queryEditor; + + const code = this.editor.getValue(); + const data = await this.editorService.parseCode(code); + const { nodes, edges } = await this.astService.parseAndVisualize(data.ast); + this.visualizationService.updateVisualization(nodes, edges); + this.uiComponents.updateASTList(nodes); + + // Add automatic parsing on code change + if (this.editor) { + this.editor.on('change', debounce(async () => { + try { + const code = this.editor.getValue(); + const data = await this.editorService.parseCode(code); + const { nodes, edges } = await this.astService.parseAndVisualize(data.ast); + this.visualizationService.updateVisualization(nodes, edges); + this.uiComponents.updateASTList(nodes); + } catch (error) { + console.error('Error parsing code:', error); + } + }, 1000)); + } + } catch (error) { + console.error('Error initializing editor:', error); + } + } + + initializeEventListeners() { + // Query execution + document.getElementById('executeQuery')?.addEventListener('click', async () => { + const code = this.editor?.getValue(); + const query = this.queryEditor?.getValue(); + if (code && query) { + await this.executeQuery(code, query); + this.uiComponents.setActiveTab('results'); + } + }); + + // Parse button + document.getElementById('parseAST')?.addEventListener('click', async () => { + try { + const code = this.editor?.getValue(); + if (code) { + const data = await this.editorService.parseCode(code); + const { nodes, edges } = await this.astService.parseAndVisualize(data.ast); + this.visualizationService.updateVisualization(nodes, edges); + this.uiComponents.updateASTList(nodes); + } + } catch (error) { + console.error('Error parsing code:', error); + } + }); + + // Tab switching + document.querySelectorAll('.tab-button').forEach(button => { + button.addEventListener('click', () => { + this.uiComponents.setActiveTab(button.dataset.tab); + }); + }); + } + + async executeQuery(code, query) { + try { + const response = await fetch('/api/analyze', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ javaSource: code, query }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + document.getElementById('queryResults').innerHTML = this.astService.formatQueryResults(data); + this.visualizationService.highlightNodes(data.matches); + } catch (error) { + console.error('Error executing query:', error); + document.getElementById('queryResults').innerHTML = ` +
+ Error executing query: ${error.message} +
+ `; + } + } +} + +// Initialize application when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + const app = new CodePathfinder(); + app.initialize(); +}); diff --git a/playground/public/static/js/components/UIComponents.js b/playground/public/static/js/components/UIComponents.js new file mode 100644 index 00000000..9284934a --- /dev/null +++ b/playground/public/static/js/components/UIComponents.js @@ -0,0 +1,111 @@ +// UI Components - Handles UI-related functionality and components +export class UIComponents { + constructor() { + this.activeTab = 'visualization'; + } + + initializeResizablePanel() { + const gutter = document.querySelector('.gutter'); + const leftPanel = document.querySelector('.left-panel'); + const rightPanel = document.querySelector('.right-panel'); + let isResizing = false; + let startX; + let startLeftWidth; + + gutter.addEventListener('mousedown', (e) => { + isResizing = true; + gutter.classList.add('active'); + startX = e.pageX; + startLeftWidth = leftPanel.offsetWidth; + }); + + document.addEventListener('mousemove', (e) => { + if (!isResizing) return; + + const mainContent = document.querySelector('.main-content'); + const totalWidth = mainContent.offsetWidth; + const dx = e.pageX - startX; + + let newLeftWidth = ((startLeftWidth + dx) / totalWidth) * 100; + newLeftWidth = Math.min(Math.max(newLeftWidth, 20), 80); + + leftPanel.style.width = `${newLeftWidth}%`; + rightPanel.style.width = `${100 - newLeftWidth}%`; + gutter.style.left = `${newLeftWidth}%`; + + if (window.editor) { + window.editor.refresh(); + } + if (window.updateVisualization) { + window.updateVisualization(window.currentNodes, window.currentEdges); + } + }); + + document.addEventListener('mouseup', () => { + isResizing = false; + gutter.classList.remove('active'); + }); + } + + initializeZoomControls(container, network) { + const zoomControls = document.createElement('div'); + zoomControls.className = 'zoom-controls'; + zoomControls.innerHTML = ` + + + + 100% + `; + + container.appendChild(zoomControls); + + document.querySelector('.zoom-in')?.addEventListener('click', () => { + if (network) { + network.moveTo({ + scale: network.getScale() * 1.2 + }); + } + }); + + document.querySelector('.zoom-out')?.addEventListener('click', () => { + if (network) { + network.moveTo({ + scale: network.getScale() * 0.8 + }); + } + }); + + document.querySelector('.zoom-reset')?.addEventListener('click', () => { + if (network) { + network.fit(); + } + }); + } + + setActiveTab(tabName) { + this.activeTab = tabName; + this.updateTabUI(); + } + + updateTabUI() { + document.querySelectorAll('.tab-button').forEach(button => { + button.classList.toggle('active', button.dataset.tab === this.activeTab); + }); + document.querySelectorAll('.tab-pane').forEach(pane => { + pane.classList.toggle('active', pane.id === `${this.activeTab}-tab`); + }); + } + + updateASTList(nodes) { + const astList = document.getElementById('ast-list'); + if (!astList) return; + + astList.innerHTML = nodes.map(node => ` +
+ ${node.type} + ${node.name ? `${node.name}` : ''} + ${node.line ? `Line: ${node.line}` : ''} +
+ `).join(''); + } +} diff --git a/playground/public/static/js/services/ASTService.js b/playground/public/static/js/services/ASTService.js new file mode 100644 index 00000000..59b537ec --- /dev/null +++ b/playground/public/static/js/services/ASTService.js @@ -0,0 +1,95 @@ +// AST Service - Handles AST processing and node management +export class ASTService { + constructor() { + this.validTypes = [ + 'ClassDeclaration', 'ClassOrInterfaceDeclaration', + 'MethodDeclaration', 'ConstructorDeclaration', + 'VariableDeclaration', 'VariableDeclarator', + 'Parameter', 'LocalVariable', + 'FieldDeclaration', 'FieldAccess', 'MethodInvocation' + ]; + } + + processNode(node, parentId = null, lastValidParentId = null, level = 0) { + const nodeId = node.id || `node_${Math.random().toString(36).substr(2, 9)}`; + let currentValidParentId = lastValidParentId; + + if (this.validTypes.includes(node.type)) { + console.log(node.type); + let category; + let mass = 1; // Base mass for node physics + + if (node.type === 'ClassDeclaration' || node.type === 'ClassOrInterfaceDeclaration') { + category = 'class'; + mass = 3; // Classes are heavier + } else if (node.type === 'MethodDeclaration' || node.type === 'ConstructorDeclaration') { + category = 'method'; + mass = 2; // Methods are medium weight + } else if (node.type === 'FieldDeclaration' || node.type === 'FieldAccess') { + category = 'fields'; + mass = 1.5; // Fields slightly more stable than variables + } else if (node.type === 'MethodInvocation') { + category = 'method-calls'; + } + + // Add node to visualization data + this.visNodes.push({ + id: nodeId, + label: `${node.type}\n${node.name || ''}`, + type: node.type, + category: category, + level: level, + mass: mass, + name: node.name, + line: node.line + }); + + // Connect to parent if exists + if (parentId) { + this.visEdges.push({ + from: parentId, + to: nodeId, + arrows: 'to' + }); + } + + currentValidParentId = nodeId; + } + + // Process child nodes + if (node.children) { + node.children.forEach(child => { + this.processNode(child, currentValidParentId, currentValidParentId, level + 1); + }); + } + } + + async parseAndVisualize(ast) { + if (!ast) { + console.error('No AST data provided'); + return { nodes: [], edges: [] }; + } + + try { + this.visNodes = []; + this.visEdges = []; + this.processNode(ast); + return { nodes: this.visNodes, edges: this.visEdges }; + } catch (error) { + console.error('Error processing AST:', error); + return { nodes: [], edges: [] }; + } + } + + formatQueryResults(data) { + if (!data || !data.matches) return ''; + + return data.matches.map(match => ` +
+
${match.type}
+
${match.name || ''}
+ ${match.line ? `
Line: ${match.line}
` : ''} +
+ `).join(''); + } +} diff --git a/playground/public/static/js/services/EditorService.js b/playground/public/static/js/services/EditorService.js new file mode 100644 index 00000000..81a7b477 --- /dev/null +++ b/playground/public/static/js/services/EditorService.js @@ -0,0 +1,106 @@ +// Editor Service - Handles CodeMirror editor initialization and management +export class EditorService { + constructor() { + this.editor = null; + this.queryEditor = null; + this.defaultJavaCode = `public class UserService { + private final UserRepository userRepository; + private final Logger logger; + + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + this.logger = LoggerFactory.getLogger(UserService.class); + } + + public User getUserById(String id) { + logger.info("Fetching user with id: {}", id); + return userRepository.findById(id) + .orElseThrow(() -> new UserNotFoundException("User not found")); + } + + public List getAllUsers() { + return userRepository.findAll(); + } + + public User createUser(User user) { + if (userRepository.existsByEmail(user.getEmail())) { + throw new DuplicateEmailException("Email already exists"); + } + return userRepository.save(user); + } + + public void deleteUser(String id) { + int i = 0; + if (!userRepository.existsById(id, i)) { + throw new UserNotFoundException("User not found"); + } + userRepository.deleteById(id); + } +}`; + } + + async initializeEditors() { + // Initialize main code editor + let editorElement = document.getElementById('codeEditor'); + if (editorElement) { + this.editor = CodeMirror(editorElement, { + mode: 'text/x-java', + theme: 'monokai', + lineNumbers: true, + lineWrapping: true, + scrollbarStyle: 'native', + viewportMargin: Infinity, + value: this.defaultJavaCode + }); + } + + // Initialize query editor + let queryEditorElement = document.getElementById('queryEditor'); + if (queryEditorElement) { + this.queryEditor = CodeMirror(queryEditorElement, { + mode: 'text/x-java', + theme: 'monokai', + lineNumbers: true, + autoCloseBrackets: true, + matchBrackets: true, + indentUnit: 4, + tabSize: 4, + lineWrapping: true + }); + } + + return { + editor: this.editor, + queryEditor: this.queryEditor + }; + } + + getEditor() { + return this.editor; + } + + getQueryEditor() { + return this.queryEditor; + } + + async parseCode(code) { + try { + const response = await fetch('/api/parse', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ code: code }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error('Error parsing code:', error); + throw error; + } + } +} diff --git a/playground/public/static/js/services/VisualizationService.js b/playground/public/static/js/services/VisualizationService.js new file mode 100644 index 00000000..0cf59f2f --- /dev/null +++ b/playground/public/static/js/services/VisualizationService.js @@ -0,0 +1,248 @@ +// Visualization Service - Handles graph visualization using vis.js +export class VisualizationService { + constructor() { + this.network = null; + this.currentNodes = []; + this.currentEdges = []; + this.visNodes = []; + this.visEdges = []; + this.options = { + nodes: { + shape: 'box', + size: 16, + borderWidth: 2, + color: { + border: '#61dafb', + background: '#1e1e1e' + }, + font: { + face: 'Inter', + size: 14, + color: '#ffffff' + }, + shadow: { + enabled: true, + color: 'rgba(97, 218, 251, 0.2)', + size: 4, + x: 0, + y: 2 + } + }, + edges: { + color: { + color: '#4d4d4d', + opacity: 0.6, + highlight: '#61dafb', + hover: '#61dafb' + }, + width: 2, + smooth: { + type: 'curvedCW', + roundness: 0.2, + forceDirection: 'none' + }, + arrows: { + to: { + enabled: true, + scaleFactor: 0.7, + type: 'circle' + } + }, + shadow: { + enabled: true, + color: 'rgba(0,0,0,0.2)', + size: 5, + x: 2, + y: 2 + } + }, + physics: { + enabled: true, + solver: 'barnesHut', + barnesHut: { + gravitationalConstant: -10000, + centralGravity: 1.5, + springLength: 300, + springConstant: 0.5, + damping: 0.9, + avoidOverlap: 1 + }, + stabilization: { + enabled: true, + iterations: 1000, + updateInterval: 10, + onlyDynamicEdges: false, + fit: true + }, + minVelocity: 0.01, + maxVelocity: 10, + timestep: 0.1, + adaptiveTimestep: true + }, + layout: { + improvedLayout: true, + randomSeed: 42, + hierarchical: { + enabled: false, + direction: 'LR', + sortMethod: 'directed', + nodeSpacing: 200, + levelSeparation: 300 + } + }, + groups: this.getNodeGroups() + }; + } + + getNodeGroups() { + return { + class: { + color: { + background: '#4CAF50', + border: '#66BB6A', + highlight: { background: '#66BB6A', border: '#66BB6A' }, + hover: { background: '#66BB6A', border: '#66BB6A' } + } + }, + constructordeclaration: { + background: '#2196F3', + border: '#2196F3', + highlight: { background: '#42A5F5', border: '#42A5F5' }, + hover: { background: '#42A5F5', border: '#42A5F5' } + }, + 'MethodDeclaration': { + background: '#2196F3', + border: '#2196F3', + highlight: { background: '#42A5F5', border: '#42A5F5' }, + hover: { background: '#42A5F5', border: '#42A5F5' } + }, + methodinvocation: { + background: '#9C27B0', + border: '#9C27B0', + highlight: { background: '#AB47BC', border: '#AB47BC' }, + hover: { background: '#AB47BC', border: '#AB47BC' } + } + }; + } + + initializeNetwork(container) { + if (!container) return; + + this.network = new vis.Network(container, { + nodes: new vis.DataSet([]), + edges: new vis.DataSet([]) + }, this.options); + + this.initializeNetworkEvents(); + return this.network; + } + + initializeNetworkEvents() { + if (!this.network) return; + + this.network.on('click', (params) => { + if (params.nodes.length > 0) { + const nodeId = params.nodes[0]; + const node = this.currentNodes.find(n => n.id === nodeId); + if (node) { + console.log('Selected node:', node); + } + } + }); + + this.network.on('stabilizationProgress', (params) => { + console.log('Layout stabilization:', Math.round(params.iterations / params.total * 100), '%'); + }); + + this.network.on('stabilizationIterationsDone', () => { + console.log('Layout stabilization finished'); + }); + } + + updateVisualization(newNodes = [], newEdges = []) { + if (!this.network || !newNodes || !newEdges) return; + + this.currentNodes = newNodes; + this.currentEdges = newEdges; + this.visNodes = newNodes; + this.visEdges = newEdges; + + const nodesDataSet = new vis.DataSet(newNodes.map(node => ({ + id: node.id, + label: node.label || `${node.type}\n${node.name || ''}`, + color: this.getNodeColor(node.type), + font: { + color: '#ffffff', + size: 14, + face: 'Inter' + }, + shape: 'box', + margin: 10, + shadow: true, + title: node.line ? `Line: ${node.line}` : undefined + }))); + + const edgesDataSet = new vis.DataSet(newEdges.map(edge => ({ + from: edge.from, + to: edge.to, + arrows: 'to', + color: { color: '#4d4d4d', highlight: '#61dafb' }, + width: 1, + smooth: { + type: 'continuous', + roundness: 0.2 + } + }))); + + this.network.setData({ nodes: nodesDataSet, edges: edgesDataSet }); + } + + getNodeColor(type) { + const nodeType = type.toLowerCase(); + const colors = { + classdeclaration: { background: '#4CAF50', border: '#66BB6A' }, + methoddeclaration: { background: '#2196F3', border: '#42A5F5' }, + fielddeclaration: { background: '#E9C07B', border: '#e5c07b' }, + compilationunit: { background: '#ec8c4c', border: '#c678dd' }, + constructordeclaration: { background: '#2196F3', border: '#42A5F5' }, + methodinvocation: { background: '#9C27B0', border: '#AB47BC' }, + default: { background: '#1e1e1e', border: '#4d4d4d' } + }; + + if (nodeType.includes('class')) return colors.classdeclaration; + if (nodeType.includes('methoddeclaration')) return colors.methoddeclaration; + if (nodeType.includes('field')) return colors.fielddeclaration; + if (nodeType.includes('compilation')) return colors.compilationunit; + if (nodeType.includes('methodinvocation')) return colors.methodinvocation; + if (nodeType.includes('constructor')) return colors.constructordeclaration; + return colors.default; + } + + highlightNodes(matches) { + if (!this.network || !matches || !matches.length) return; + + const matchIds = new Set(matches.map(m => m.id)); + const allNodes = this.network.body.data.nodes.get(); + const allEdges = this.network.body.data.edges.get(); + + allNodes.forEach(node => { + const isHighlighted = matchIds.has(node.id); + this.network.body.data.nodes.update({ + id: node.id, + opacity: isHighlighted ? 1 : 0.2, + font: { + ...node.font, + color: isHighlighted ? '#ffffff' : 'rgba(255,255,255,0.3)' + } + }); + }); + + allEdges.forEach(edge => { + const isHighlighted = matchIds.has(edge.from) && matchIds.has(edge.to); + this.network.body.data.edges.update({ + id: edge.id, + opacity: isHighlighted ? 0.8 : 0.1 + }); + }); + } +} diff --git a/playground/public/static/js/utils/helpers.js b/playground/public/static/js/utils/helpers.js new file mode 100644 index 00000000..a2c0e262 --- /dev/null +++ b/playground/public/static/js/utils/helpers.js @@ -0,0 +1,12 @@ +// Helper functions and utilities +export const debounce = (func, wait) => { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +}; diff --git a/playground/public/static/style.css b/playground/public/static/style.css index 1a85fb6f..add24367 100644 --- a/playground/public/static/style.css +++ b/playground/public/static/style.css @@ -388,8 +388,7 @@ body { display: flex; align-items: center; gap: 8px; - font-size: 0.85rem; - font-weight: 500; + font-size: 0.75rem; letter-spacing: 0.3px; color: #e2e2e2; } @@ -422,15 +421,6 @@ body { background-color: #FF5722; border: 2px solid #FF5722; } -/* Legend styling */ -.legend-item { - display: flex; - align-items: center; - gap: 0.5rem; - font-size: 0.85rem; - color: #e2e2e2; - font-weight: 500; -} .dot { width: 10px; From 3a7645f0ed0bfe59d9bb51973a653d6df5516065 Mon Sep 17 00:00:00 2001 From: shivasurya Date: Thu, 13 Mar 2025 22:28:34 -0400 Subject: [PATCH 12/22] fix go version --- playground/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/go.mod b/playground/go.mod index 6167d56c..08182fda 100644 --- a/playground/go.mod +++ b/playground/go.mod @@ -1,6 +1,6 @@ module github.com/shivasurya/code-pathfinder/playground -go 1.24.1 +go 1.24 replace github.com/shivasurya/code-pathfinder/sourcecode-parser => ../sourcecode-parser From d0418746fff6f9a85f3ad63fc0e266e27dcf0979 Mon Sep 17 00:00:00 2001 From: shivasurya Date: Thu, 13 Mar 2025 22:30:13 -0400 Subject: [PATCH 13/22] fix go version --- playground/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/go.mod b/playground/go.mod index 08182fda..5d0eb363 100644 --- a/playground/go.mod +++ b/playground/go.mod @@ -1,6 +1,6 @@ module github.com/shivasurya/code-pathfinder/playground -go 1.24 +go 1.23 replace github.com/shivasurya/code-pathfinder/sourcecode-parser => ../sourcecode-parser From 658dbce22306482fb7b06818a8b3946d24c29a87 Mon Sep 17 00:00:00 2001 From: shivasurya Date: Thu, 13 Mar 2025 22:35:04 -0400 Subject: [PATCH 14/22] revert to latest version --- playground/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/go.mod b/playground/go.mod index 5d0eb363..6167d56c 100644 --- a/playground/go.mod +++ b/playground/go.mod @@ -1,6 +1,6 @@ module github.com/shivasurya/code-pathfinder/playground -go 1.23 +go 1.24.1 replace github.com/shivasurya/code-pathfinder/sourcecode-parser => ../sourcecode-parser From dc3c12a75a80f2b24611d2a9a2a71b3df2fc9f87 Mon Sep 17 00:00:00 2001 From: shivasurya Date: Fri, 14 Mar 2025 17:57:33 -0400 Subject: [PATCH 15/22] fix meta tags --- playground/public/static/index.html | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/playground/public/static/index.html b/playground/public/static/index.html index a5e4922e..64154ebb 100644 --- a/playground/public/static/index.html +++ b/playground/public/static/index.html @@ -3,7 +3,33 @@ - Code-Pathfinder + Code PathFinder Playground - Interactive Code Analysis Tool | Open Source CodeQL alternative + + + + + + + + + + + + + + + + + + + + + + + + + + From 45e6e043d158ff4ec569a7ec8a3c51b0b831be9b Mon Sep 17 00:00:00 2001 From: shivasurya Date: Sun, 16 Mar 2025 10:24:57 -0400 Subject: [PATCH 16/22] added query execution api and ui --- playground/go.mod | 9 +- playground/go.sum | 10 + playground/pkg/handlers/analyze.go | 52 +++-- playground/pkg/models/analysis.go | 28 --- playground/pkg/models/models.go | 21 ++ playground/public/static/index.html | 1 + playground/public/static/js/app.js | 6 +- .../public/static/js/services/ASTService.js | 58 +++++- .../static/js/services/EditorService.js | 34 ++- .../js/services/VisualizationService.js | 14 +- playground/public/static/script.js | 9 - playground/public/static/style.css | 194 +++++++++++++++++- 12 files changed, 362 insertions(+), 74 deletions(-) delete mode 100644 playground/pkg/models/analysis.go create mode 100644 playground/pkg/models/models.go diff --git a/playground/go.mod b/playground/go.mod index 6167d56c..946dde69 100644 --- a/playground/go.mod +++ b/playground/go.mod @@ -6,7 +6,14 @@ replace github.com/shivasurya/code-pathfinder/sourcecode-parser => ../sourcecode require ( github.com/google/uuid v1.6.0 + github.com/shivasurya/code-pathfinder/sourcecode-parser v0.0.0-00010101000000-000000000000 github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 ) -require github.com/stretchr/testify v1.10.0 // indirect +require ( + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/expr-lang/expr v1.16.9 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/posthog/posthog-go v1.2.24 // indirect + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect +) diff --git a/playground/go.sum b/playground/go.sum index a246f53b..9802fc33 100644 --- a/playground/go.sum +++ b/playground/go.sum @@ -1,12 +1,22 @@ +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI= +github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posthog/posthog-go v1.2.24 h1:A+iG4saBJemo++VDlcWovbYf8KFFNUfrCoJtsc40RPA= +github.com/posthog/posthog-go v1.2.24/go.mod h1:uYC2l1Yktc8E+9FAHJ9QZG4vQf/NHJPD800Hsm7DzoM= github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 h1:6C8qej6f1bStuePVkLSFxoU22XBS165D3klxlzRg8F4= github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82/go.mod h1:xe4pgH49k4SsmkQq5OT8abwhWmnzkhpgnXeekbx2efw= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/playground/pkg/handlers/analyze.go b/playground/pkg/handlers/analyze.go index c7e50664..e4c5040d 100644 --- a/playground/pkg/handlers/analyze.go +++ b/playground/pkg/handlers/analyze.go @@ -4,10 +4,13 @@ import ( "errors" "net/http" "os" + "strings" "time" "github.com/shivasurya/code-pathfinder/playground/pkg/models" "github.com/shivasurya/code-pathfinder/playground/pkg/utils" + parser "github.com/shivasurya/code-pathfinder/sourcecode-parser/antlr" + "github.com/shivasurya/code-pathfinder/sourcecode-parser/graph" ) const ( @@ -57,7 +60,9 @@ func AnalyzeHandler(w http.ResponseWriter, r *http.Request) { return } - utils.SendJSONResponse(w, models.AnalyzeResponse{Results: results}) + utils.SendJSONResponse(w, models.AnalyzeResponse{ + Results: results, + }) } // executeQueryWithTimeout executes the query with a timeout @@ -79,19 +84,42 @@ func executeQueryWithTimeout(tmpDir, queryStr string) ([]models.QueryResult, err // executeQuery performs the actual query execution func executeQuery(tmpDir, queryStr string, resultsChan resultChannel, errorChan chan error) { - // TODO: This is a placeholder implementation - // In a real implementation, this would use the code-pathfinder library - // to execute the query and analyze the code + // Initialize code graph with the temporary directory + codeGraph := graph.Initialize(tmpDir) - // For now, we'll look for WebView security issues in the source code - var results []models.QueryResult + // Parse the query + parsedQuery, err := parser.ParseQuery(queryStr) + if err != nil { + errorChan <- err + return + } - // Add a dummy result for testing - results = append(results, models.QueryResult{ - File: "MainActivity.java", - Line: 42, - Snippet: "setJavaScriptEnabled(true)", - }) + // Extract WHERE clause + parts := strings.SplitN(queryStr, "WHERE", 2) + if len(parts) > 1 { + parsedQuery.Expression = strings.SplitN(parts[1], "SELECT", 2)[0] + } + + // Execute query on the graph + entities, _ := graph.QueryEntities(codeGraph, parsedQuery) + + // Convert results to QueryResult format + var results []models.QueryResult + for _, entity := range entities { + for _, entityObject := range entity { + + // Create QueryResult + result := models.QueryResult{ + File: "Main.java", + Line: int64(entityObject.LineNumber), + Snippet: entityObject.CodeSnippet, + Kind: entityObject.Type, // Use the Type field from entityObject + } + + // Add the result to the list + results = append(results, result) + } + } resultsChan <- results } diff --git a/playground/pkg/models/analysis.go b/playground/pkg/models/analysis.go deleted file mode 100644 index e47c4591..00000000 --- a/playground/pkg/models/analysis.go +++ /dev/null @@ -1,28 +0,0 @@ -package models - -// AnalyzeRequest represents the input for code analysis -type AnalyzeRequest struct { - JavaSource string `json:"javaSource"` - Query string `json:"query"` -} - -// QueryResult represents a single result from code analysis -type QueryResult struct { - File string `json:"file"` - Line int `json:"line"` - Snippet string `json:"snippet"` -} - -// AnalyzeResponse represents the response from code analysis -type AnalyzeResponse struct { - Results []QueryResult `json:"results"` - Error string `json:"error,omitempty"` -} - -// Security represents security-related information for a node -type Security struct { - Risk string `json:"risk,omitempty"` - Impact string `json:"impact,omitempty"` - Category string `json:"category,omitempty"` - Rules []string `json:"rules,omitempty"` -} diff --git a/playground/pkg/models/models.go b/playground/pkg/models/models.go new file mode 100644 index 00000000..eb1e1faa --- /dev/null +++ b/playground/pkg/models/models.go @@ -0,0 +1,21 @@ +package models + +// AnalyzeRequest represents the request body for code analysis +type AnalyzeRequest struct { + JavaSource string `json:"javaSource"` + Query string `json:"query"` + Category string `json:"category"` // Technology or language-based category +} + +// AnalyzeResponse represents the response from code analysis +type AnalyzeResponse struct { + Results []QueryResult `json:"results"` +} + +// QueryResult represents a single result from query execution +type QueryResult struct { + File string `json:"file"` + Line int64 `json:"line"` + Snippet string `json:"snippet"` + Kind string `json:"kind,omitempty"` // Type of the node or result +} diff --git a/playground/public/static/index.html b/playground/public/static/index.html index 64154ebb..82889602 100644 --- a/playground/public/static/index.html +++ b/playground/public/static/index.html @@ -55,6 +55,7 @@ +
diff --git a/playground/public/static/js/app.js b/playground/public/static/js/app.js index d317d70f..e0f48866 100644 --- a/playground/public/static/js/app.js +++ b/playground/public/static/js/app.js @@ -108,8 +108,12 @@ class CodePathfinder { } const data = await response.json(); + // sort the data.results based on result.line + data.results.sort((a, b) => a.line - b.line); document.getElementById('queryResults').innerHTML = this.astService.formatQueryResults(data); - this.visualizationService.highlightNodes(data.matches); + this.visualizationService.highlightNodes(data.results); + // highlight code line number from result.line + this.editorService.highlightCodeLines(data.results); } catch (error) { console.error('Error executing query:', error); document.getElementById('queryResults').innerHTML = ` diff --git a/playground/public/static/js/services/ASTService.js b/playground/public/static/js/services/ASTService.js index 59b537ec..bd0c5b54 100644 --- a/playground/public/static/js/services/ASTService.js +++ b/playground/public/static/js/services/ASTService.js @@ -15,7 +15,6 @@ export class ASTService { let currentValidParentId = lastValidParentId; if (this.validTypes.includes(node.type)) { - console.log(node.type); let category; let mass = 1; // Base mass for node physics @@ -82,14 +81,55 @@ export class ASTService { } formatQueryResults(data) { - if (!data || !data.matches) return ''; + if (!data || !data.results) return ''; - return data.matches.map(match => ` -
-
${match.type}
-
${match.name || ''}
- ${match.line ? `
Line: ${match.line}
` : ''} -
- `).join(''); + const tableHeader = ` + + File + Line + Type + `; + + const tableRows = data.results.map(result => { + const kind = result.kind || ''; + const category = this.getCategory(kind); + + return ` + + ${result.file.split('/').pop()} + ${result.line || '-'} + ${kind || '-'} + + `; + }).join(''); + + return ` + + ${tableHeader} + ${tableRows} +
+ `; + } + + getCategory(kind) { + // Java language-based types + const javaTypes = [ + 'method_declaration', + 'class_declaration', + 'interface_declaration', + 'field_declaration', + 'constructor_declaration', + 'variable_declaration', + 'parameter', + 'annotation', + 'enum_declaration', + 'package_declaration', + 'import_declaration' + ]; + + // Check if the kind matches any of our known types + const kindLower = kind.toLowerCase(); + if (javaTypes.some(type => kindLower.includes(type))) return 'java'; + return 'other'; } } diff --git a/playground/public/static/js/services/EditorService.js b/playground/public/static/js/services/EditorService.js index 81a7b477..eb83b065 100644 --- a/playground/public/static/js/services/EditorService.js +++ b/playground/public/static/js/services/EditorService.js @@ -37,6 +37,9 @@ export class EditorService { userRepository.deleteById(id); } }`; + this.defaultQuery = `FROM method_declaration AS md +WHERE md.getVisibility() == "public" +SELECT md, "Listing all public methods"`; } async initializeEditors() { @@ -58,14 +61,17 @@ export class EditorService { let queryEditorElement = document.getElementById('queryEditor'); if (queryEditorElement) { this.queryEditor = CodeMirror(queryEditorElement, { - mode: 'text/x-java', + mode: 'text/x-sql', theme: 'monokai', lineNumbers: true, autoCloseBrackets: true, matchBrackets: true, indentUnit: 4, tabSize: 4, - lineWrapping: true + lineWrapping: true, + scrollbarStyle: 'native', + viewportMargin: Infinity, + value: this.defaultQuery }); } @@ -103,4 +109,28 @@ export class EditorService { throw error; } } + + highlightCodeLines(results) { + if (!this.editor) return; + + // Clear any existing highlights + this.editor.getAllMarks().forEach(mark => mark.clear()); + + results.forEach(result => { + // Add a gutter marker + const marker = document.createElement('div'); + marker.className = 'CodeMirror-search-marker'; + this.editor.setGutterMarker(result.line - 1, 'CodeMirror-search-markers', marker); + + // Highlight the line + this.editor.addLineClass(result.line - 1, 'background', 'CodeMirror-search-match'); + + // Mark the text for better visibility + this.editor.markText( + {line: result.line - 1, ch: 0}, + {line: result.line - 1, ch: this.editor.getLine(result.line - 1).length}, + {className: 'CodeMirror-search-text'} + ); + }); + } } diff --git a/playground/public/static/js/services/VisualizationService.js b/playground/public/static/js/services/VisualizationService.js index 0cf59f2f..10910113 100644 --- a/playground/public/static/js/services/VisualizationService.js +++ b/playground/public/static/js/services/VisualizationService.js @@ -149,14 +149,6 @@ export class VisualizationService { } } }); - - this.network.on('stabilizationProgress', (params) => { - console.log('Layout stabilization:', Math.round(params.iterations / params.total * 100), '%'); - }); - - this.network.on('stabilizationIterationsDone', () => { - console.log('Layout stabilization finished'); - }); } updateVisualization(newNodes = [], newEdges = []) { @@ -218,10 +210,10 @@ export class VisualizationService { return colors.default; } - highlightNodes(matches) { - if (!this.network || !matches || !matches.length) return; + highlightNodes(results) { + if (!this.network || !results || !results.length) return; - const matchIds = new Set(matches.map(m => m.id)); + const matchIds = new Set(results.map(r => r.line)); const allNodes = this.network.body.data.nodes.get(); const allEdges = this.network.body.data.edges.get(); diff --git a/playground/public/static/script.js b/playground/public/static/script.js index 547d8abb..0d2348c3 100644 --- a/playground/public/static/script.js +++ b/playground/public/static/script.js @@ -114,14 +114,6 @@ const VisualizationService = { } } }); - - network.on('stabilizationProgress', (params) => { - console.log('Layout stabilization:', Math.round(params.iterations / params.total * 100), '%'); - }); - - network.on('stabilizationIterationsDone', () => { - console.log('Layout stabilization finished'); - }); } }; @@ -539,7 +531,6 @@ document.addEventListener('DOMContentLoaded', () => { let currentValidParentId = lastValidParentId; if (validTypes.includes(node.type)) { - console.log(node.type); let category; let mass = 1; // Base mass for node physics diff --git a/playground/public/static/style.css b/playground/public/static/style.css index add24367..b394337e 100644 --- a/playground/public/static/style.css +++ b/playground/public/static/style.css @@ -172,6 +172,199 @@ body { flex-direction: column; } +/* Results Table Styles */ +.results-table { + width: 100%; + border-collapse: collapse; + font-size: 13px; + font-family: 'Space Grotesk', sans-serif; + margin: 8px 0; + border: 1px solid #2d2d2d; + border-radius: 4px; + overflow: hidden; +} + +.results-table th, +.results-table td { + padding: 8px 12px; + text-align: left; + border-bottom: 1px solid #2d2d2d; +} + +.results-table th { + color: #8b949e; + font-size: 12px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; + background: #1e1e1e; +} + +.results-table tr:hover { + background: rgba(97, 218, 251, 0.05); +} + +/* CodeMirror Search Highlights */ +.CodeMirror-search-match { + background: rgba(97, 218, 251, 0.1); +} + +.CodeMirror-search-text { + background: rgba(97, 218, 251, 0.2); + border-radius: 2px; +} + +.CodeMirror-search-marker { + width: 8px; + height: 8px; + border-radius: 50%; + background: #61dafb; + margin-top: 6px; + box-shadow: 0 0 8px rgba(97, 218, 251, 0.4); +} + +.file-cell { + color: #e2e2e2; + max-width: 300px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.line-cell { + color: #8b949e; + text-align: right; + width: 50px; + font-family: monospace; +} + +.kind-cell { + color: #61dafb; + font-family: monospace; + transition: color 0.2s ease; +} + +.kind-cell[data-category="java"] { + color: #61dafb; +} + +.kind-cell[data-category="java"]:hover { + color: #61dafb; + text-shadow: 0 0 8px rgba(97, 218, 251, 0.3); +} + +.results-table-row:hover { + border-left-color: #61dafb; + background: rgba(97, 218, 251, 0.05); +} + +/* Interactive hover effects */ +.results-table-row:hover { + background: rgba(97, 218, 251, 0.05); +} + +.results-table-row:hover .kind-cell[data-category="java"] { + color: #61dafb; + text-shadow: 0 0 8px rgba(97, 218, 251, 0.3); +} + +.results-table-row:hover .kind-cell[data-category="android"] { + color: #a5d6a7; + text-shadow: 0 0 8px rgba(165, 214, 167, 0.3); +} + +/* Smooth transitions */ +.results-table-row, +.kind-cell { + transition: all 0.2s ease; +} + + + +.results-table-row:not(.results-table-header):hover { + background: #2d2d2d; +} + +.header-cell { + font-weight: 600; + color: #e2e2e2; + font-size: 0.875rem; + text-transform: uppercase; + letter-spacing: 0.05em; + font-family: 'Space Grotesk', sans-serif; + position: relative; + padding-bottom: 0.5rem; +} + +.header-cell::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 2rem; + height: 2px; + background: #3d3d3d; + transition: width 0.2s ease; +} + +.results-table-header:hover .header-cell::after { + width: 3rem; + background: #61dafb; +} + +.file-cell { + color: #e2e2e2; + max-width: 300px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.line-cell { + color: #8b949e; + text-align: right; + width: 50px; + font-family: monospace; +} + +.kind-cell { + color: #61dafb; + font-family: monospace; + transition: color 0.2s ease; +} + +.kind-cell[data-category="java"]:hover { + color: #61dafb; +} + +/* Category-specific colors */ +.kind-cell[data-category="java"] { + color: #61dafb; +} + +.kind-cell[data-category="android"] { + color: #a5d6a7; +} + +.kind-cell::before { + content: ''; + position: absolute; + left: 0; + width: 3px; + height: 16px; + background: currentColor; + border-radius: 2px; + opacity: 0.7; +} + + + + + +.results-table-body .results-table-row:not(:last-child) { + border-bottom: 1px solid #3d3d3d; +} + .query-input-container { display: flex; flex-direction: column; @@ -545,7 +738,6 @@ body { background: #1e1e1e; border-radius: 4px; padding: 1rem; - margin-top: 1rem; overflow: auto; flex: 1; } From f43ff695eccf3535ff222c6656a95c1ce4bb3df8 Mon Sep 17 00:00:00 2001 From: shivasurya Date: Sun, 16 Mar 2025 11:24:51 -0400 Subject: [PATCH 17/22] added ui --- playground/public/static/index.html | 1 + playground/public/static/js/app.js | 1 - .../js/services/VisualizationService.js | 28 ------------------- 3 files changed, 1 insertion(+), 29 deletions(-) diff --git a/playground/public/static/index.html b/playground/public/static/index.html index 82889602..adc5fbbb 100644 --- a/playground/public/static/index.html +++ b/playground/public/static/index.html @@ -3,6 +3,7 @@ + Code PathFinder Playground - Interactive Code Analysis Tool | Open Source CodeQL alternative diff --git a/playground/public/static/js/app.js b/playground/public/static/js/app.js index e0f48866..4a93b4a3 100644 --- a/playground/public/static/js/app.js +++ b/playground/public/static/js/app.js @@ -111,7 +111,6 @@ class CodePathfinder { // sort the data.results based on result.line data.results.sort((a, b) => a.line - b.line); document.getElementById('queryResults').innerHTML = this.astService.formatQueryResults(data); - this.visualizationService.highlightNodes(data.results); // highlight code line number from result.line this.editorService.highlightCodeLines(data.results); } catch (error) { diff --git a/playground/public/static/js/services/VisualizationService.js b/playground/public/static/js/services/VisualizationService.js index 10910113..73ff42c1 100644 --- a/playground/public/static/js/services/VisualizationService.js +++ b/playground/public/static/js/services/VisualizationService.js @@ -209,32 +209,4 @@ export class VisualizationService { if (nodeType.includes('constructor')) return colors.constructordeclaration; return colors.default; } - - highlightNodes(results) { - if (!this.network || !results || !results.length) return; - - const matchIds = new Set(results.map(r => r.line)); - const allNodes = this.network.body.data.nodes.get(); - const allEdges = this.network.body.data.edges.get(); - - allNodes.forEach(node => { - const isHighlighted = matchIds.has(node.id); - this.network.body.data.nodes.update({ - id: node.id, - opacity: isHighlighted ? 1 : 0.2, - font: { - ...node.font, - color: isHighlighted ? '#ffffff' : 'rgba(255,255,255,0.3)' - } - }); - }); - - allEdges.forEach(edge => { - const isHighlighted = matchIds.has(edge.from) && matchIds.has(edge.to); - this.network.body.data.edges.update({ - id: edge.id, - opacity: isHighlighted ? 0.8 : 0.1 - }); - }); - } } From c70036a57bee1e828789b04166aabea6a7bee711 Mon Sep 17 00:00:00 2001 From: shivasurya Date: Sun, 16 Mar 2025 11:43:39 -0400 Subject: [PATCH 18/22] fix ui bugs --- .../public/static/js/services/VisualizationService.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/playground/public/static/js/services/VisualizationService.js b/playground/public/static/js/services/VisualizationService.js index 73ff42c1..f9267f78 100644 --- a/playground/public/static/js/services/VisualizationService.js +++ b/playground/public/static/js/services/VisualizationService.js @@ -193,12 +193,12 @@ export class VisualizationService { const nodeType = type.toLowerCase(); const colors = { classdeclaration: { background: '#4CAF50', border: '#66BB6A' }, - methoddeclaration: { background: '#2196F3', border: '#42A5F5' }, - fielddeclaration: { background: '#E9C07B', border: '#e5c07b' }, + methoddeclaration: { background: '#2196F3', border: '#2196F3' }, + fielddeclaration: { background: '#FF9800', border: '#FF9800' }, compilationunit: { background: '#ec8c4c', border: '#c678dd' }, - constructordeclaration: { background: '#2196F3', border: '#42A5F5' }, + constructordeclaration: { background: '#2196F3', border: '#2196F3' }, methodinvocation: { background: '#9C27B0', border: '#AB47BC' }, - default: { background: '#1e1e1e', border: '#4d4d4d' } + default: { background: '#FF5722', border: '#FF5722' } }; if (nodeType.includes('class')) return colors.classdeclaration; From db4a8cb9bc62417ade7e4aeb6d4df4c6c3fec546 Mon Sep 17 00:00:00 2001 From: shivasurya Date: Sun, 16 Mar 2025 11:50:46 -0400 Subject: [PATCH 19/22] add cta button --- docs/src/content/docs/index.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index b95d6487..b5394a2c 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -9,15 +9,15 @@ hero: link: /quickstart icon: right-arrow variant: primary + - text: Playground + link: https://play.codepathfinder.dev + icon: forward-slash - text: Browse Rules link: /atlas icon: open-book - text: Tech Blog link: /blog icon: pen - - text: Get Source - link: https://github.com/shivasurya/code-pathfinder - icon: github --- import { Card, CardGrid, Icon } from '@astrojs/starlight/components'; From a957b586ddce63dd097276d4c14e67c9a682b53a Mon Sep 17 00:00:00 2001 From: shivasurya Date: Sun, 16 Mar 2025 11:51:37 -0400 Subject: [PATCH 20/22] added cta button --- docs/src/content/docs/atlas/index.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/src/content/docs/atlas/index.mdx b/docs/src/content/docs/atlas/index.mdx index ad6a3df1..1e569afc 100644 --- a/docs/src/content/docs/atlas/index.mdx +++ b/docs/src/content/docs/atlas/index.mdx @@ -9,6 +9,9 @@ hero: link: /quickstart icon: right-arrow variant: primary + - text: Playground + link: https://play.codepathfinder.dev + icon: forward-slash - text: Documentation link: /overview icon: open-book From 670fc589963d2abfd4f8b9ec4b0eb79eaabae129 Mon Sep 17 00:00:00 2001 From: shivasurya Date: Sun, 16 Mar 2025 11:59:26 -0400 Subject: [PATCH 21/22] semgrep folks aren't happy seeing this blog post. I'll write a deep technical post differentiating how codeql outperforms semgrep and how semgrep conditions based filtering falls short while performing taint analysis --- .../blog/code-pathfinder-closure-table-hierarchical-queries.mdx | 2 +- .../docs/blog/finding-webview-misconfigurations-android.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/content/docs/blog/code-pathfinder-closure-table-hierarchical-queries.mdx b/docs/src/content/docs/blog/code-pathfinder-closure-table-hierarchical-queries.mdx index d5eaf619..0efc5bc1 100644 --- a/docs/src/content/docs/blog/code-pathfinder-closure-table-hierarchical-queries.mdx +++ b/docs/src/content/docs/blog/code-pathfinder-closure-table-hierarchical-queries.mdx @@ -159,7 +159,7 @@ import { Card } from '@astrojs/starlight/components'; ### Closing Note - Discover [Code-PathFinder](https://github.com/shivasurya/code-pathfinder), the open-source alternative to CodeQL—a powerful tool engineered to detect security vulnerabilities. Unlike grep-based scanners such as Semgrep or ast-grep, Code-PathFinder enables fine-tuning of queries to more effectively eliminate false positives, thanks to its advanced taint analysis and source-to-sink tracing capabilities. Give it a try, and if you encounter any bugs or have suggestions, please file an issue. + Discover [Code-PathFinder](https://github.com/shivasurya/code-pathfinder), the open-source alternative to CodeQL—a powerful tool engineered to detect security vulnerabilities. Unlike grep-based scanners such as ast-grep, Code-PathFinder enables fine-tuning of queries to more effectively eliminate false positives, thanks to its advanced taint analysis and source-to-sink tracing capabilities. Give it a try, and if you encounter any bugs or have suggestions, please file an issue.
diff --git a/docs/src/content/docs/blog/finding-webview-misconfigurations-android.mdx b/docs/src/content/docs/blog/finding-webview-misconfigurations-android.mdx index 08464baf..50f4ec7c 100644 --- a/docs/src/content/docs/blog/finding-webview-misconfigurations-android.mdx +++ b/docs/src/content/docs/blog/finding-webview-misconfigurations-android.mdx @@ -132,7 +132,7 @@ import { Card } from '@astrojs/starlight/components'; ### Conclusion While [Code-PathFinder, the open-source alternative to CodeQL](https://codepathfinder.dev), is a powerful tool for finding security vulnerabilities in Android applications, one can always tweak the queries to filter out false positives - more effectively compared to grep-based scanners like `Semgrep` or `ast-grep`. This is because the taint analysis and source-to-sink analysis are far more powerful than grep-based scanners. Give it a try and file an [issue](https://github.com/shivasurya/code-pathfinder/issues) + more effectively compared to grep-based scanners like `ast-grep`. This is because the taint analysis and source-to-sink analysis are far more powerful than grep-based scanners. Give it a try and file an [issue](https://github.com/shivasurya/code-pathfinder/issues) if you find any bugs or have any suggestions. From a9001035ffa3a61ce26acc8a7d3fd98519146071 Mon Sep 17 00:00:00 2001 From: shivasurya Date: Sun, 16 Mar 2025 12:23:20 -0400 Subject: [PATCH 22/22] add readme and add playground links --- README.md | 5 ++++- playground/README.md | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 playground/README.md diff --git a/README.md b/README.md index f58c783e..68fbe525 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,16 @@ About Code Pathfinder, the open-source alternative to GitHub CodeQL. Built for advanced structural search, derive insights, find vulnerabilities in code. [![Build and Release](https://github.com/shivasurya/code-pathfinder/actions/workflows/build.yml/badge.svg)](https://github.com/shivasurya/code-pathfinder/actions/workflows/build.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/shivasurya/code-pathfinder/sourcecode-parser)](https://goreportcard.com/report/github.com/shivasurya/code-pathfinder/sourcecode-parser) -[![MIT License](https://img.shields.io/github/license/shivasurya/code-pathfinder)](https://github.com/shivasurya/code-pathfinder/blob/main/LICENSE) +[![AGPL-3.0 License](https://img.shields.io/github/license/shivasurya/code-pathfinder)](https://github.com/shivasurya/code-pathfinder/blob/main/LICENSE) [![Discord](https://img.shields.io/discord/1259511338183557120?logo=discord&label=discord&utm_source=github)](https://discord.gg/xmPdJC6WPX) [![codecov](https://codecov.io/gh/shivasurya/code-pathfinder/graph/badge.svg?token=VYQLI49TF4)](https://codecov.io/gh/shivasurya/code-pathfinder) +![Code-Pathfinder Playground](https://badgen.net/static/Online%20Playground/live/cyan?icon=terminal)
## :tv: Demo +Try interactive online playground [here](https://play.codepathfinder.dev/). + ```bash docker run --rm -v "./src:/src" shivasurya/code-pathfinder:stable-latest ci --project /src/code-pathfinder/test-src --ruleset cpf/java ``` diff --git a/playground/README.md b/playground/README.md new file mode 100644 index 00000000..d43f129d --- /dev/null +++ b/playground/README.md @@ -0,0 +1,26 @@ +### Code-Pathfinder Playground + +The Code-Pathfinder Playground is a online interactive app that allows you to analyze code and execute Code-Pathfinder (CodeQL) queries on it. + +![Code-Pathfinder Playground](https://badgen.net/static/Online%20Playground/live/cyan?icon=terminal) + +### Quickstart + +In the playground directory, run: + +```shell +$ go run main.go +``` + +This will start the playground server. Visit `http://localhost:8080` to access the playground. + +### Docker Build + +From the root directory, run: + +```shell +$ podman build --platform linux/amd64 -t docker.io/shivasurya/cpf-playground:latest . -f playground-Dockerfile +``` + +This will build the playground Docker image. +