Skip to content

Commit 259bf69

Browse files
authored
Refactor LanguageTool workflow for reviewdog integration
1 parent d24353f commit 259bf69

1 file changed

Lines changed: 12 additions & 292 deletions

File tree

.github/workflows/languagetool.yml

Lines changed: 12 additions & 292 deletions
Original file line numberDiff line numberDiff line change
@@ -1,295 +1,15 @@
1-
# .github/workflows/languagetool.yml
2-
name: LanguageTool (PR comment)
3-
4-
on:
5-
pull_request_target:
6-
types: [opened, reopened, synchronize, labeled]
7-
issue_comment:
8-
types: [created]
9-
10-
permissions:
11-
contents: read
12-
pull-requests: write
13-
issues: write
14-
15-
concurrency:
16-
group: languagetool-${{ github.event.pull_request.number || github.event.issue.number || github.run_id }}
17-
cancel-in-progress: true
18-
19-
env:
20-
LT_LANGUAGE: en-US
21-
RERUN_LABEL: languagetool:rerun
22-
1+
name: reviewdog
2+
on: [pull_request]
233
jobs:
24-
# Comment command -> toggles a label to trigger the PR job
25-
rerun_on_comment:
26-
if: |
27-
github.event_name == 'issue_comment' &&
28-
github.event.issue.pull_request &&
29-
startsWith(github.event.comment.body, '/languagetool') &&
30-
(github.event.comment.author_association == 'MEMBER' ||
31-
github.event.comment.author_association == 'OWNER' ||
32-
github.event.comment.author_association == 'COLLABORATOR')
4+
linter_name:
5+
name: LanguageTool grammar check
336
runs-on: ubuntu-latest
347
steps:
35-
- name: Toggle rerun label on PR
36-
uses: actions/github-script@v7
37-
with:
38-
script: |
39-
const owner = context.repo.owner;
40-
const repo = context.repo.repo;
41-
const issue_number = context.issue.number;
42-
const label = process.env.RERUN_LABEL;
43-
44-
// Ensure label exists
45-
try {
46-
await github.rest.issues.getLabel({ owner, repo, name: label });
47-
} catch {
48-
await github.rest.issues.createLabel({
49-
owner, repo, name: label, color: '0e8a16',
50-
description: 'Rerun LanguageTool on this PR'
51-
});
52-
}
53-
54-
// Remove if present (ignore errors), then add to force a new "labeled" event
55-
try {
56-
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: label });
57-
} catch {}
58-
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: [label] });
59-
60-
languagetool:
61-
if: |
62-
github.event_name == 'pull_request_target' &&
63-
(
64-
github.event.action == 'opened' ||
65-
github.event.action == 'reopened' ||
66-
github.event.action == 'synchronize' ||
67-
(github.event.action == 'labeled' && github.event.label.name == 'languagetool:rerun')
68-
)
69-
runs-on: ubuntu-latest
70-
71-
steps:
72-
- name: Checkout PR head (safe)
73-
uses: actions/checkout@v4
74-
with:
75-
repository: ${{ github.event.pull_request.head.repo.full_name }}
76-
ref: ${{ github.event.pull_request.head.sha }}
77-
fetch-depth: 0
78-
persist-credentials: false
79-
80-
- name: Fetch base SHA for diff
81-
run: |
82-
set -euo pipefail
83-
git remote add upstream "https://github.com/${{ github.event.pull_request.base.repo.full_name }}.git" || true
84-
git fetch --no-tags --depth=1 upstream "${{ github.event.pull_request.base.sha }}"
85-
86-
- name: Setup Java 17
87-
uses: actions/setup-java@v4
88-
with:
89-
distribution: temurin
90-
java-version: "17"
91-
92-
- name: Download LanguageTool CLI (latest snapshot)
93-
run: |
94-
set -euo pipefail
95-
curl -fsSL -o lt.zip "https://internal1.languagetool.org/snapshots/LanguageTool-latest-snapshot.zip"
96-
rm -rf .lt
97-
mkdir -p .lt
98-
unzip -q lt.zip -d .lt
99-
100-
LT_JAR="$(ls -1 .lt/**/languagetool-commandline.jar 2>/dev/null | head -n1 || true)"
101-
if [ -z "${LT_JAR}" ]; then
102-
echo "Could not find languagetool-commandline.jar in snapshot" >&2
103-
find .lt -maxdepth 4 -type f -name "*languagetool*jar" -print >&2 || true
104-
exit 1
105-
fi
106-
107-
echo "LT_JAR=${LT_JAR}" >> "$GITHUB_ENV"
108-
109-
- name: Run LanguageTool + build PR comment (collapsible + exact word)
110-
env:
111-
BASE_SHA: ${{ github.event.pull_request.base.sha }}
112-
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
113-
run: |
114-
set -euo pipefail
115-
116-
# jq is present on ubuntu-latest, but install if your runner image differs
117-
command -v jq >/dev/null || (sudo apt-get update && sudo apt-get install -y jq)
118-
119-
# Choose files to check
120-
mapfile -t FILES < <(git diff --name-only "$BASE_SHA" "$HEAD_SHA" \
121-
| grep -E '\.(md|mdx|txt|rst|adoc|asciidoc|tex)$' || true)
122-
123-
# Load custom words (optional)
124-
WORDS_FILE=".languagetool/words.txt"
125-
if [ -f "$WORDS_FILE" ]; then
126-
WORDS_JSON="$(jq -R -s '
127-
split("\n")
128-
| map(gsub("\r";""))
129-
| map(select(length>0 and (startswith("#")|not)))
130-
| map(ascii_downcase)
131-
' "$WORDS_FILE")"
132-
else
133-
WORDS_JSON='[]'
134-
fi
135-
136-
: > results.jsonl
137-
138-
if [ "${#FILES[@]}" -eq 0 ]; then
139-
echo '{"file":"(none)","issues":[]}' >> results.jsonl
140-
else
141-
for f in "${FILES[@]}"; do
142-
[ -f "$f" ] || continue
143-
144-
# LT can print banner lines; keep JSON only (accepts either { or [)
145-
java -jar "$LT_JAR" -l "${LT_LANGUAGE}" --json "$f" 2>/dev/null \
146-
| sed -n '/^[{[]/,$p' > lt.json || true
147-
148-
# Extract issues and filter spelling-ish matches for custom words
149-
jq -c \
150-
--arg file "$f" \
151-
--argjson words "$WORDS_JSON" '
152-
def bad_raw:
153-
(.context.offset // 0) as $o
154-
| (.context.length // 0) as $l
155-
| (.context.text // "") as $t
156-
| ($t[$o:($o+$l)]);
157-
158-
def badtoken:
159-
(bad_raw
160-
| gsub("^[^[:alnum:]]+|[^[:alnum:]]+$";"")
161-
| ascii_downcase);
162-
163-
(.matches // [])
164-
| map(
165-
. as $m
166-
| ($m.rule.id // "") as $rid
167-
| ($m.rule.category.id // "") as $cat
168-
| (badtoken) as $bt
169-
| select(
170-
( (( $cat == "TYPOS") or ($rid|test("MORFOLOGIK";"i")) )
171-
and (($words|index($bt)) != null)
172-
) | not
173-
)
174-
| {
175-
message: ($m.message // "LanguageTool finding"),
176-
rule: $rid,
177-
bad: (bad_raw),
178-
replacements: (($m.replacements // []) | map(.value) | .[0:3]),
179-
context: ($m.context.text // ""),
180-
context_offset: ($m.context.offset // 0),
181-
context_length: ($m.context.length // 0)
182-
}
183-
)
184-
| {file:$file, issues:.}
185-
' lt.json >> results.jsonl
186-
done
187-
fi
188-
189-
# Build markdown body (stored as a file) - collapsible per file
190-
node <<'NODE'
191-
const fs = require("fs");
192-
193-
const marker = "<!-- languagetool-report -->";
194-
const raw = fs.readFileSync("results.jsonl","utf8").trim();
195-
const lines = raw ? raw.split("\n").filter(Boolean) : [];
196-
const parsed = lines.map(l => JSON.parse(l));
197-
198-
const checkedFiles = parsed.map(p => p.file).filter(f => f && f !== "(none)");
199-
const byFile = parsed
200-
.filter(p => Array.isArray(p.issues) && p.issues.length > 0)
201-
.reduce((acc, p) => { acc[p.file] = p.issues; return acc; }, {});
202-
203-
let total = 0;
204-
for (const f of Object.keys(byFile)) total += byFile[f].length;
205-
206-
function inlineCode(s) {
207-
if (s == null) return "";
208-
return String(s).replace(/`/g, "\\`").replace(/\n/g, " ").trim();
209-
}
210-
211-
function shortContext(text, maxLen=220) {
212-
const t = (text || "").replace(/\s+/g, " ").trim();
213-
if (t.length <= maxLen) return t;
214-
return t.slice(0, maxLen - 1) + "…";
215-
}
216-
217-
let body =
218-
`${marker}
219-
## LanguageTool report
220-
221-
**Language:** \`${process.env.LT_LANGUAGE || "en-US"}\`
222-
**Checked files:** ${checkedFiles.length}
223-
**Findings:** ${total}
224-
`;
225-
226-
if (!checkedFiles.length) {
227-
body += `\nNo supported text files changed in this PR (based on configured extensions).\n`;
228-
} else if (total === 0) {
229-
body += `\n✅ No issues found in the changed text files.\n`;
230-
} else {
231-
body += `\n---\n`;
232-
for (const [file, issues] of Object.entries(byFile)) {
233-
body += `\n<details>\n<summary><strong>${file}</strong> — ${issues.length} finding(s)</summary>\n\n`;
234-
for (const it of issues.slice(0, 200)) {
235-
const found = inlineCode(it.bad);
236-
const ctx = shortContext(it.context);
237-
const sug = (it.replacements && it.replacements.length)
238-
? `Suggested: ${it.replacements.map(s => `\`${inlineCode(s)}\``).join(", ")}\n`
239-
: "";
240-
body +=
241-
`- **${inlineCode(it.rule || "RULE")}**: ${inlineCode(it.message)}
242-
- Found: \`${found}\`
243-
- ${sug ? sug.trimEnd() : "Suggested: (none)"}
244-
- Context: ${ctx ? `> ${ctx}` : "(none)"}
245-
246-
`;
247-
}
248-
if (issues.length > 200) body += `…(${issues.length - 200} more in this file)\n\n`;
249-
body += `</details>\n`;
250-
}
251-
}
252-
253-
fs.writeFileSync("comment.md", body.trim() + "\n");
254-
NODE
255-
256-
- name: Post or update PR comment
257-
uses: actions/github-script@v7
258-
with:
259-
script: |
260-
const fs = require('fs');
261-
const owner = context.repo.owner;
262-
const repo = context.repo.repo;
263-
const issue_number = context.payload.pull_request.number;
264-
265-
const body = fs.readFileSync('comment.md', 'utf8');
266-
const marker = '<!-- languagetool-report -->';
267-
268-
const { data: comments } = await github.rest.issues.listComments({
269-
owner, repo, issue_number, per_page: 100
270-
});
271-
272-
const existing = comments.find(c => (c.body || '').includes(marker));
273-
274-
if (existing) {
275-
await github.rest.issues.updateComment({
276-
owner, repo, comment_id: existing.id, body
277-
});
278-
} else {
279-
await github.rest.issues.createComment({
280-
owner, repo, issue_number, body
281-
});
282-
}
283-
284-
- name: Remove rerun label
285-
if: github.event.action == 'labeled' && github.event.label.name == 'languagetool:rerun'
286-
continue-on-error: true
287-
uses: actions/github-script@v7
288-
with:
289-
script: |
290-
await github.rest.issues.removeLabel({
291-
owner: context.repo.owner,
292-
repo: context.repo.repo,
293-
issue_number: context.payload.pull_request.number,
294-
name: process.env.RERUN_LABEL,
295-
});
8+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
9+
- uses: reviewdog/action-languagetool@ea19c757470ce0dbfcbc34aec090317cef1ff0b5 # v1.22.0
10+
with:
11+
github_token: ${{ secrets.github_token }}
12+
# Change reviewdog reporter if you need [github-pr-check,github-check,github-pr-review].
13+
reporter: github-pr-review
14+
# Change reporter level if you need.
15+
level: info

0 commit comments

Comments
 (0)