Skip to content

Commit b2ee429

Browse files
authored
Merge pull request #8 from docker/cm/eslint-fix
Scripts
2 parents 664eec4 + cb6e463 commit b2ee429

File tree

13 files changed

+300
-44
lines changed

13 files changed

+300
-44
lines changed

functions/tree_sitter/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ FROM node:alpine3.20
1515
COPY main.js /main.js
1616
COPY --from=build /tree-sitter/node_modules /node_modules
1717

18-
ENTRYPOINT [ "node", "main.js" ]
18+
ENTRYPOINT [ "node", "/main.js" ]

functions/tree_sitter/main.js

+77-13
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,56 @@
11
const TreeSitter = require('tree-sitter');
22
const Python = require('tree-sitter-python');
3+
const TypeScript = require('tree-sitter-typescript');
4+
const JavaScript = require('tree-sitter-javascript');
35
const fs = require('fs');
46

57
const args = JSON.parse(process.argv[2])
68

79
// Load the Python parser
810
const parser = new TreeSitter();
911
// Set the language to the parser
10-
parser.setLanguage(Python);
12+
const ext = args.path.split('.').pop();
13+
14+
let language
15+
16+
if (ext === 'py') {
17+
language = Python
18+
}
19+
else if (ext === 'ts') {
20+
language = TypeScript.typescript
21+
}
22+
else if (ext === 'tsx') {
23+
language = TypeScript.tsx
24+
}
25+
else if (ext === 'js' || ext === 'jsx') {
26+
language = JavaScript
27+
}
28+
29+
parser.setLanguage(language);
30+
1131
// Read the code file content
1232
const codeContent = fs.readFileSync(args.path, 'utf8');
1333
// Parse the code using the chosen parser
1434
const parsed = parser.parse(codeContent);
1535

1636
const line_to_grab = args.line
1737

38+
if (line_to_grab === undefined) {
39+
console.log('No line number provided')
40+
process.exit(1)
41+
}
42+
43+
if (line_to_grab > codeContent.split('\n').length) {
44+
console.log('Line number provided is greater than the number of lines in the file')
45+
process.exit(1)
46+
}
47+
1848
// Look for node where node.startPosition.row and node.endPosition.row are equal to line_to_grab
1949
const search_node = (node) => {
20-
if (node.startPosition.row === line_to_grab && node.endPosition.row === line_to_grab) {
50+
if (node.startPosition.row === line_to_grab - 1 && node.endPosition.row === line_to_grab - 1) {
2151
return node
2252
}
53+
2354
for (const child of node.children) {
2455
const result = search_node(child)
2556
if (result) {
@@ -31,19 +62,52 @@ const search_node = (node) => {
3162

3263
const line_node = search_node(parsed.rootNode)
3364

34-
const parent = line_node.parent
65+
if (!line_node) {
66+
console.log('No node found for line', line_to_grab)
67+
console.log('Code:', codeContent.split('\n').slice(line_to_grab - 5, line_to_grab + 5).join('\n'))
68+
process.exit(1)
69+
}
3570

36-
if (parent) {
37-
const start_line = parent.startPosition.row
38-
const end_line = parent.endPosition.row
39-
// Return codeContent from start_line to end_line
40-
const lines = codeContent.split('\n').slice(start_line, end_line + 1)
41-
parent.content = lines.join('\n')
71+
const parseParent = (childNode) => {
72+
const parent = childNode.parent
73+
if (!parent) {
74+
return childNode
75+
}
76+
const MIN_CONTEXT_LENGTH = 700
77+
const MAX_CONTEXT_LENGTH = 1500
78+
if (parent.text.length > MAX_CONTEXT_LENGTH) {
79+
console.log('Parent node text is too long, truncating')
80+
const index = parent.text.indexOf(line_node.text)
81+
const start = index - MAX_CONTEXT_LENGTH / 2
82+
const end = index + MAX_CONTEXT_LENGTH / 2
83+
parent.text = parent.text.slice(start, end)
84+
return parent
85+
}
86+
if (parent.text.length < MIN_CONTEXT_LENGTH) {
87+
if (parent.parent) {
88+
return parseParent(parent.parent)
89+
}
90+
else {
91+
return parent
92+
}
93+
}
94+
return parent
4295
}
4396

97+
const ancestor = parseParent(line_node)
98+
99+
100+
const siblings = line_node.parent ? line_node.parent.children : [line_node]
101+
102+
const offending_line = siblings.map(sibling => {
103+
if (sibling.startPosition.row === line_to_grab - 1 && sibling.endPosition.row === line_to_grab - 1) {
104+
return sibling.text
105+
}
106+
}).join('')
107+
44108
console.log({
45-
offending_line: line_node.text,
46-
line_node: line_node,
47-
parent: parent,
48-
parent_text: parent.text
109+
offending_line,
110+
line_node,
111+
ancestor,
112+
ancestor_text: ancestor.text
49113
})

prompts/eslint/020_user_prompt.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
Lint my project's JS/TS files.
1+
Lint my project's JS/TS files using `summary` output.
22

33
Report what you did, broken down by each tool.

prompts/eslint/scripts/lint-standardjs.sh

+9-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
PROJECT_DIR="/project"
44

5+
THREAD_DIR="/thread"
6+
57
# First arg
68
ARGS="$1"
79

@@ -25,13 +27,15 @@ fi
2527

2628
# If typescript is false, run standard
2729
if [ $TYPESCRIPT == 'false' ]; then
28-
$LINT_ARGS="$FILES"
30+
LINT_ARGS="$FILES"
2931
# If FIX
3032
if [ $FIX == "true" ]; then
3133
LINT_ARGS="--fix $FILES"
3234
fi
3335
# Pass files array as args to standard
34-
echo standard 2>/dev/null | standard-json | /remap_lint.sh "$OUTPUT_LEVEL"
36+
STANDARD_JSON=$(standard 2>/dev/null | standard-json)
37+
echo $STANDARD_JSON | /remap_lint.sh "$OUTPUT_LEVEL"
38+
echo $STANDARD_JSON > $THREAD_DIR/eslint.json
3539
exit $?
3640
fi
3741

@@ -66,7 +70,9 @@ for TS_ROOT in $TS_ROOTS; do
6670
else
6771
LINT_ARGS="$TS_FILES_IN_ROOT"
6872
fi
69-
TS_OUTPUT+=$(ts-standard 2>/dev/null | standard-json | /remap_lint.sh "$OUTPUT_LEVEL")
73+
TS_JSON=$(ts-standard 2>/dev/null | standard-json)
74+
echo $TS_JSON >> $THREAD_DIR/eslint.json
75+
TS_OUTPUT+=$($TS_JSON | /remap_lint.sh "$OUTPUT_LEVEL")
7076
# If ts-standard failed and EXIT_CODE is 0, set EXIT_CODE
7177
if [ $? -ne 0 ] && [ $EXIT_CODE -eq 0 ]; then
7278
EXIT_CODE=$?

prompts/eslint/scripts/lint.sh

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
#!/bin/bash
22

3+
34
PROJECT_DIR="/project"
45

6+
THREAD_DIR="/thread"
7+
58
# First arg
69
ARGS="$1"
710

811
# args[eslint_args] args [eslint_version]
912

13+
set -f
14+
# Without pathname expansion
1015
ESLINT_ARGS=$(echo $ARGS | jq -r '.args')
1116

1217
ESLINT_VERSION=$(echo $ARGS | jq -r '.version')
@@ -30,4 +35,11 @@ echo "Running npx with args: $ARGS"
3035

3136
ESLINT_JSON=$(npx --no-install $ARGS )
3237

33-
echo $ESLINT_JSON | /remap_lint.sh "$OUTPUT_LEVEL"
38+
echo $ESLINT_JSON | /remap_lint.sh "$OUTPUT_LEVEL"
39+
echo $ESLINT_JSON > $THREAD_DIR/eslint.json
40+
41+
# If --fix is in args, copy the files back to the project directory
42+
if [[ $ESLINT_ARGS == *"--fix"* ]]; then
43+
cp -r $TEMP_DIR/. $PROJECT_DIR
44+
fi
45+

prompts/eslint/scripts/remap_lint.sh

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ AFFECTED_FILE_COUNT=$(echo $INPUT | jq -r 'length')
2323
# Iterate over file paths
2424
for index in "${!FILE_PATHS[@]}"; do
2525
file_path=${FILE_PATHS[$index]}
26+
# Strip $TEMP_DIR from the path
27+
file_path=$(echo $file_path | sed "s/\/eslint-temp\///g")
2628
# Get the messages for the file path
2729
messages=${ALL_MESSAGES[$index]}
2830

+18-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,18 @@
1-
You are an AI assistant who specializes in resolving lint violations in projects.
1+
You are an AI assistant who specializes in resolving lint violations in projects. Use the tools available to quickly take action and be very brief. You must always respond with a real code snippet that will resolve the violation.
2+
3+
1. Run lint.
4+
2. Evaluate total violations.
5+
<10 violations: Parse output with complaints output.
6+
10+ violations: Parse output with condensed output.
7+
3. Fix the violations using the following steps:
8+
9+
## Condensed:
10+
11+
For each file:
12+
13+
{>fixing}
14+
15+
## Complaints:
16+
17+
Just report the json
18+

prompts/eslint_fix/020_user_prompt.md

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
1. Run ESLint summary in the project
2-
2. Evaluate total violations.
3-
<10 violations: Re-run eslint with complaints output.
4-
10+ violations: Re-run eslint with condensed output.
5-
3. Fix the violations.
1+
Fix any `no-async-promise-executer` violations in my project.
2+
3+
I need fixes in the form of new code to use instead.

prompts/eslint_fix/Dockerfile

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM alpine:3.20
2+
3+
RUN apk add --no-cache jq bash
4+
5+
COPY entrypoint.sh /entrypoint.sh
6+
7+
# Depends on eslint prompt sibling directory
8+
COPY scripts/remap_lint.sh /remap_lint.sh
9+
10+
RUN chmod +x /entrypoint.sh
11+
RUN chmod +x /remap_lint.sh
12+
13+
ENTRYPOINT ["/entrypoint.sh"]

prompts/eslint_fix/README.md

+43-19
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,58 @@
11
---
22
functions:
3-
- name: eslint
3+
- name: run_lint
44
type: prompt
55
ref: github:docker/labs-ai-tools-for-devs?ref=main&path=prompts/eslint
6-
- name: write_files
7-
description: Write a set of files to my project
6+
- name: parse_lint_results
7+
description: Loads lint violations grouped by type.
88
parameters:
99
type: object
1010
properties:
11-
files:
12-
type: array
13-
items:
14-
type: object
15-
properties:
16-
path:
17-
type: string
18-
description: the relative path to the file that the script should run in
19-
script:
11+
outputLevel:
2012
type: string
21-
description: The script to run in the files.
13+
description: Supports `condensed` or `complaints`
2214
container:
23-
image: vonwig/apply_script:latest
24-
- name: read_eslint
25-
description: Loads ESLint violations
15+
image: vonwig/read_eslint
16+
- name: violations_for_file
17+
description: Loads lint violations for a file.
2618
parameters:
2719
type: object
2820
properties:
29-
output_level:
21+
path:
3022
type: string
31-
description: "`condensed` or `complaints`"
23+
description: Path to read violations for. Useful for getting line numbers. Always uses full `json` output level for most context.
3224
container:
3325
image: vonwig/read_eslint
34-
---
26+
- name: run_tree_sitter
27+
description: Gets context from source code for a given line.
28+
parameters:
29+
type: object
30+
properties:
31+
path:
32+
type: string
33+
description: The filepath of the affected code
34+
line:
35+
type: number
36+
description: The affected line to load context from
37+
required:
38+
- path
39+
- line
40+
container:
41+
image: vonwig/tree_sitter
42+
---
43+
44+
```sh
45+
docker run --rm \
46+
-it \
47+
-v /var/run/docker.sock:/var/run/docker.sock \
48+
--mount type=bind,source=$PROMPTS_DIR,target=/app/eslint_fix \
49+
--workdir /app \
50+
--mount type=volume,source=docker-prompts,target=/prompts \
51+
--mount type=bind,source=$HOME/.openai-api-key,target=/root/.openai-api-key \
52+
vonwig/prompts:latest \
53+
run \
54+
$PWD \
55+
$USER \
56+
"$(uname -o)" \
57+
eslint_fix
58+
```

prompts/eslint_fix/entrypoint.sh

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/bin/bash
2+
3+
ARGS_JSON=$1
4+
5+
THREAD_DIR="/thread"
6+
7+
# Check for eslint.json
8+
if [ ! -f $THREAD_DIR/eslint.json ]; then
9+
echo "No eslint.json found. Exiting."
10+
exit 0
11+
fi
12+
13+
# Get eslint.json
14+
ESLINT_JSON=$(cat $THREAD_DIR/eslint.json)
15+
16+
# Replace all eslint-temp with empty string
17+
ESLINT_JSON=$(echo $ESLINT_JSON | sed 's/\/eslint-temp\///g')
18+
19+
OUTPUT_LEVEL=$(echo $ARGS_JSON | jq -r '.outputLevel')
20+
FILEPATH=$(echo $ARGS_JSON | jq -r '.path')
21+
# If path is not null
22+
if [ "$FILEPATH" != "null" ]; then
23+
echo "Getting eslint.json for path: $FILEPATH"
24+
ESLINT_JSON_FOR_PATH=$(echo $ESLINT_JSON | jq -r --arg path "$FILEPATH" '.[] | select(.filePath == $path)')
25+
echo "ESLint violations for $FILEPATH:"
26+
# Strip source key if it exists
27+
ESLINT_JSON_FOR_PATH=$(echo $ESLINT_JSON_FOR_PATH | jq -r 'del(.source)')
28+
echo $ESLINT_JSON_FOR_PATH
29+
exit 0
30+
fi
31+
32+
echo $ESLINT_JSON | /remap_lint.sh "$OUTPUT_LEVEL"

prompts/eslint_fix/fixing.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
1. **Get the line**: Use read_eslint with the `path` arg to get all of the violations for a file.
2+
2. **Make the correction**: Respond with an edit in JSON format:
3+
4+
```json
5+
{
6+
"start": [1, 4], // row,col for start character
7+
"end": [2, 4], // row,col for end character
8+
"edit": "Lorem ipsum" // The edit to make
9+
}
10+
```

0 commit comments

Comments
 (0)