mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add bash tree-sitter support
- Add bash query patterns for parsing bash scripts - Add bash language support to languageParser.ts for .sh and .bash extensions - Create comprehensive test suite for bash parsing - Add sample bash fixture with various language constructs Fixes #9430
This commit is contained in:
parent
0851769450
commit
0eb381a6fa
6 changed files with 387 additions and 0 deletions
143
src/services/tree-sitter/__tests__/fixtures/sample-bash.ts
Normal file
143
src/services/tree-sitter/__tests__/fixtures/sample-bash.ts
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
export const sampleBashContent = `
|
||||
#!/bin/bash
|
||||
|
||||
# Function definition - demonstrates basic function structure
|
||||
function multi_line_function() {
|
||||
local param1=\$1
|
||||
local param2=\$2
|
||||
echo "Processing \$param1 and \$param2"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Alternative function syntax
|
||||
another_function() {
|
||||
local result=""
|
||||
for i in {1..5}; do
|
||||
result+="\$i "
|
||||
done
|
||||
echo "\$result"
|
||||
}
|
||||
|
||||
# Variable assignments and exports
|
||||
GLOBAL_VAR="global_value"
|
||||
export PATH_VAR="/usr/local/bin:\$PATH"
|
||||
readonly CONSTANT_VAR="immutable"
|
||||
declare -a array_var=(
|
||||
"element1"
|
||||
"element2"
|
||||
"element3"
|
||||
)
|
||||
|
||||
# Alias definitions
|
||||
alias ll='ls -la'
|
||||
alias grep='grep --color=auto'
|
||||
alias ..='cd ..'
|
||||
|
||||
# Complex variable assignment with command substitution
|
||||
CURRENT_DIR=\$(
|
||||
pwd |
|
||||
sed 's/\\/home\\///' |
|
||||
tr '/' '-'
|
||||
)
|
||||
|
||||
# Here document example
|
||||
cat <<EOF > output.txt
|
||||
This is a multi-line
|
||||
here document that spans
|
||||
several lines for testing
|
||||
EOF
|
||||
|
||||
# Case statement with multiple patterns
|
||||
case "\$1" in
|
||||
start|START)
|
||||
echo "Starting service..."
|
||||
start_service
|
||||
;;
|
||||
stop|STOP)
|
||||
echo "Stopping service..."
|
||||
stop_service
|
||||
;;
|
||||
restart|RESTART)
|
||||
echo "Restarting service..."
|
||||
restart_service
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# If statement with multiple conditions
|
||||
if [[ -f "\$CONFIG_FILE" && -r "\$CONFIG_FILE" ]]; then
|
||||
source "\$CONFIG_FILE"
|
||||
echo "Configuration loaded"
|
||||
elif [[ -f "\$DEFAULT_CONFIG" ]]; then
|
||||
source "\$DEFAULT_CONFIG"
|
||||
echo "Default configuration loaded"
|
||||
else
|
||||
echo "No configuration found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# While loop with read
|
||||
while IFS= read -r line; do
|
||||
process_line "\$line"
|
||||
counter=\$((counter + 1))
|
||||
done < input.txt
|
||||
|
||||
# For loop with array iteration
|
||||
for element in "\${array_var[@]}"; do
|
||||
echo "Processing: \$element"
|
||||
transform_element "\$element"
|
||||
done
|
||||
|
||||
# Pipeline example
|
||||
cat data.txt |
|
||||
grep "pattern" |
|
||||
sort -u |
|
||||
head -20 > results.txt
|
||||
|
||||
# Function with arithmetic operations
|
||||
calculate_sum() {
|
||||
local sum=0
|
||||
for num in "\$@"; do
|
||||
sum=\$((sum + num))
|
||||
done
|
||||
echo \$sum
|
||||
}
|
||||
|
||||
# Source another script
|
||||
source ./config.sh
|
||||
. ./utils.sh
|
||||
|
||||
# Array manipulation
|
||||
declare -A associative_array
|
||||
associative_array["key1"]="value1"
|
||||
associative_array["key2"]="value2"
|
||||
|
||||
# Test command examples
|
||||
if [ -z "\$VAR" ]; then
|
||||
echo "Variable is empty"
|
||||
fi
|
||||
|
||||
if [[ "\$VAR" =~ ^[0-9]+\$ ]]; then
|
||||
echo "Variable is numeric"
|
||||
fi
|
||||
|
||||
# Command substitution in arithmetic context
|
||||
result=\$((10 + \$(get_value)))
|
||||
|
||||
# Redirection examples
|
||||
exec 3< input.txt
|
||||
exec 4> output.txt
|
||||
exec 5>&1
|
||||
|
||||
# Trap signal handling
|
||||
trap cleanup EXIT
|
||||
trap 'echo "Interrupted"' INT TERM
|
||||
`
|
||||
|
||||
export default {
|
||||
path: "test.sh",
|
||||
content: sampleBashContent,
|
||||
}
|
||||
51
src/services/tree-sitter/__tests__/inspectBash.spec.ts
Normal file
51
src/services/tree-sitter/__tests__/inspectBash.spec.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { inspectTreeStructure, debugLog } from "./helpers"
|
||||
import { sampleBashContent } from "./fixtures/sample-bash"
|
||||
|
||||
describe("Inspect Bash", () => {
|
||||
it("should capture Bash-specific constructs", async () => {
|
||||
const result = await inspectTreeStructure(sampleBashContent, "bash")
|
||||
debugLog("Bash Inspect Result:", result)
|
||||
|
||||
// Check for function definitions
|
||||
expect(result).toContain("function_definition")
|
||||
|
||||
// Check for variable assignments
|
||||
expect(result).toContain("variable_assignment")
|
||||
|
||||
// Check for command structures
|
||||
expect(result).toContain("command")
|
||||
|
||||
// Check for control flow structures
|
||||
expect(result).toContain("case_statement")
|
||||
expect(result).toContain("if_statement")
|
||||
expect(result).toContain("while_statement")
|
||||
expect(result).toContain("for_statement")
|
||||
|
||||
// Check for test commands
|
||||
expect(result).toContain("test_command")
|
||||
|
||||
// Check for arithmetic operations
|
||||
expect(result).toContain("arithmetic_expansion")
|
||||
|
||||
// Check for here documents
|
||||
expect(result).toContain("heredoc_redirect")
|
||||
|
||||
// Check for pipelines
|
||||
expect(result).toContain("pipeline")
|
||||
|
||||
// Check for redirections
|
||||
expect(result).toContain("redirect")
|
||||
|
||||
// Check for arrays
|
||||
expect(result).toContain("array")
|
||||
|
||||
// Check for command substitution
|
||||
expect(result).toContain("command_substitution")
|
||||
|
||||
// Check for program structure
|
||||
expect(result).toContain("program")
|
||||
|
||||
// The shebang appears as a comment in the tree
|
||||
expect(result).toContain("comment")
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
TODO: The following structures can be parsed by tree-sitter but lack query support:
|
||||
|
||||
1. Process Substitution:
|
||||
(process_substitution)
|
||||
Example: diff <(command1) <(command2)
|
||||
|
||||
2. Coprocess:
|
||||
(coproc_statement)
|
||||
Example: coproc NAME { command; }
|
||||
|
||||
3. Compound Commands:
|
||||
(compound_statement)
|
||||
Example: { command1; command2; }
|
||||
|
||||
4. Select Loops:
|
||||
(select_statement)
|
||||
Example: select item in list; do ...; done
|
||||
|
||||
5. Extended Glob Patterns:
|
||||
(extglob_pattern)
|
||||
Example: !(pattern), ?(pattern), *(pattern), +(pattern), @(pattern)
|
||||
*/
|
||||
|
||||
import { testParseSourceCodeDefinitions, debugLog } from "./helpers"
|
||||
import { sampleBashContent } from "./fixtures/sample-bash"
|
||||
import { bashQuery } from "../queries"
|
||||
|
||||
// Bash test options
|
||||
const bashOptions = {
|
||||
language: "bash",
|
||||
wasmFile: "tree-sitter-bash.wasm",
|
||||
queryString: bashQuery,
|
||||
extKey: "sh",
|
||||
}
|
||||
|
||||
describe("parseSourceCodeDefinitionsForFile with Bash", () => {
|
||||
let parseResult: string | undefined
|
||||
|
||||
beforeAll(async () => {
|
||||
// Cache parse result for all tests
|
||||
parseResult = await testParseSourceCodeDefinitions("test.sh", sampleBashContent, bashOptions)
|
||||
debugLog("Bash Parse Result:", parseResult)
|
||||
})
|
||||
|
||||
it("should parse function definitions", () => {
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| function multi_line_function\(\)/)
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| another_function\(\)/)
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| calculate_sum\(\)/)
|
||||
debugLog("Function definitions found:", parseResult)
|
||||
})
|
||||
|
||||
it("should parse variable declarations and exports", () => {
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| GLOBAL_VAR="global_value"/)
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| export PATH_VAR/)
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| readonly CONSTANT_VAR/)
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| declare -a array_var/)
|
||||
debugLog("Variable declarations found:", parseResult)
|
||||
})
|
||||
|
||||
it("should parse alias definitions", () => {
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| alias ll='ls -la'/)
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| alias grep='grep --color=auto'/)
|
||||
debugLog("Alias definitions found:", parseResult)
|
||||
})
|
||||
|
||||
it("should parse control structures", () => {
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| case "\$1" in/)
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| if \[\[ -f "\$CONFIG_FILE"/)
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| while IFS= read/)
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| for element in/)
|
||||
debugLog("Control structures found:", parseResult)
|
||||
})
|
||||
|
||||
it("should parse here documents and redirections", () => {
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| cat <<EOF/)
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| cat data.txt/)
|
||||
debugLog("Here documents and redirections found:", parseResult)
|
||||
})
|
||||
|
||||
it("should parse source commands", () => {
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| source \.\/config.sh/)
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| \. \.\/utils.sh/)
|
||||
debugLog("Source commands found:", parseResult)
|
||||
})
|
||||
|
||||
it("should parse test commands", () => {
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| if \[ -z "\$VAR" \]/)
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| if \[\[ "\$VAR" =~ \^/)
|
||||
debugLog("Test commands found:", parseResult)
|
||||
})
|
||||
|
||||
it("should parse arithmetic operations", () => {
|
||||
expect(parseResult).toMatch(/\d+--\d+ \|.*\$\(\(/)
|
||||
debugLog("Arithmetic operations found:", parseResult)
|
||||
})
|
||||
|
||||
it("should parse shebang", () => {
|
||||
expect(parseResult).toMatch(/\d+--\d+ \| #!\/bin\/bash/)
|
||||
debugLog("Shebang found:", parseResult)
|
||||
})
|
||||
})
|
||||
|
|
@ -28,6 +28,7 @@ import {
|
|||
embeddedTemplateQuery,
|
||||
elispQuery,
|
||||
elixirQuery,
|
||||
bashQuery,
|
||||
} from "./queries"
|
||||
|
||||
export interface LanguageParser {
|
||||
|
|
@ -218,6 +219,11 @@ export async function loadRequiredLanguageParsers(filesToParse: string[], source
|
|||
language = await loadLanguage("elixir", sourceDirectory)
|
||||
query = new Query(language, elixirQuery)
|
||||
break
|
||||
case "sh":
|
||||
case "bash":
|
||||
language = await loadLanguage("bash", sourceDirectory)
|
||||
query = new Query(language, bashQuery)
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported language: ${ext}`)
|
||||
}
|
||||
|
|
|
|||
84
src/services/tree-sitter/queries/bash.ts
Normal file
84
src/services/tree-sitter/queries/bash.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
Bash Tree-sitter Query Patterns
|
||||
*/
|
||||
export default `
|
||||
; Function definitions
|
||||
(function_definition
|
||||
name: (word) @name.definition.function) @definition.function
|
||||
|
||||
; Variable declarations and assignments
|
||||
(variable_assignment
|
||||
name: (variable_name) @name.definition.variable) @definition.variable
|
||||
|
||||
; Export statements
|
||||
(declaration_command
|
||||
name: (simple_expansion
|
||||
(variable_name) @name.definition.export)) @definition.export
|
||||
|
||||
; Alias definitions
|
||||
(declaration_command
|
||||
name: "alias"
|
||||
value: (concatenation
|
||||
(word) @name.definition.alias)) @definition.alias
|
||||
|
||||
(declaration_command
|
||||
name: "alias"
|
||||
value: (word) @name.definition.alias) @definition.alias
|
||||
|
||||
; Source/dot commands (file includes)
|
||||
(command
|
||||
name: (command_name (word) @source_cmd (#match? @source_cmd "^(source|\\.)$"))
|
||||
argument: (_) @name.definition.source) @definition.source
|
||||
|
||||
; Here documents
|
||||
(redirected_statement
|
||||
body: (command)
|
||||
redirect: (heredoc_redirect
|
||||
(heredoc_start) @name.definition.heredoc)) @definition.heredoc
|
||||
|
||||
; Case statements
|
||||
(case_statement
|
||||
value: (_) @name.definition.case) @definition.case
|
||||
|
||||
; If statements
|
||||
(if_statement) @definition.if_statement
|
||||
|
||||
; While loops
|
||||
(while_statement) @definition.while_loop
|
||||
|
||||
; For loops
|
||||
(for_statement
|
||||
variable: (variable_name) @name.definition.for_variable) @definition.for_loop
|
||||
|
||||
; Array declarations
|
||||
(variable_assignment
|
||||
name: (variable_name) @name.definition.array
|
||||
value: (array)) @definition.array
|
||||
|
||||
; Command substitutions
|
||||
(command_substitution) @definition.command_substitution
|
||||
|
||||
; Pipeline commands
|
||||
(pipeline) @definition.pipeline
|
||||
|
||||
; Redirections
|
||||
(command
|
||||
redirect: (_)) @definition.redirection
|
||||
|
||||
; Test commands ([ ] and [[ ]])
|
||||
(test_command) @definition.test_command
|
||||
|
||||
; Arithmetic expressions
|
||||
(arithmetic_expansion) @definition.arithmetic
|
||||
|
||||
; Parameter expansions
|
||||
(expansion
|
||||
(variable_name) @name.reference.variable) @reference.variable
|
||||
|
||||
; Comments (for documentation purposes)
|
||||
(comment) @comment
|
||||
|
||||
; Shebang
|
||||
(program
|
||||
. (comment) @shebang (#match? @shebang "^#!/"))
|
||||
`
|
||||
|
|
@ -26,3 +26,4 @@ export { zigQuery } from "./zig"
|
|||
export { default as embeddedTemplateQuery } from "./embedded_template"
|
||||
export { elispQuery } from "./elisp"
|
||||
export { scalaQuery } from "./scala"
|
||||
export { default as bashQuery } from "./bash"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue