Skip to content

Commit

Permalink
Add inhibit-rules tool that filters Vale JSON output using rule prece…
Browse files Browse the repository at this point in the history
…dence

Signed-off-by: Jack Baldry <[email protected]>
  • Loading branch information
jdbaldry committed Jan 9, 2025
1 parent 7ed0517 commit b2adb4e
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 19 deletions.
21 changes: 4 additions & 17 deletions .github/workflows/validate-documentation.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Validate documentation
name: Documentation CI
on:
pull_request:
paths: ["docs/sources/**", "vale/**"]
Expand Down Expand Up @@ -73,22 +73,9 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Sync packages
run: vale sync
- name: Run linter
run: >
vale
'--glob=*.md'
--minAlertLevel=warning
--output=/etc/vale/rdjsonl.tmpl
docs/sources
| reviewdog
-f=rdjsonl
--fail-on-error
--name=vale
--reporter=github-pr-review
env:
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: ./vale-action
with:
token: ${{ secrets.GITHUB_TOKEN }}

report-readability:
if: ${{ github.repository == 'grafana/writers-toolkit' }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
build.conf
/dist
/vale/Grafana.zip
vale/tools/inhibit-rules
39 changes: 39 additions & 0 deletions vale-action/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Run Vale
description: |
Lints documentation with Vale.
Note that the action assumes you are running the steps using the grafana/vale container image.
inputs:
directory:
default: docs/sources
description: Directory containing documentation for linting.
required: false
filter:
default: ""
description: |
Vale filter that allows you to report an arbitrary subset of the Grafana style.
For more information, refer to https://vale.sh/docs/filters.
token:
default: ${{ github.token }}
description: Used by reviewdog to comment on pull requests.
required: false
runs:
using: composite
steps:
- name: Vale
env:
DIRECTORY: ${{ inputs.directory }}
FILTER: --filter=${{ inputs.filter }}
REVIEWDOG_GITHUB_API_TOKEN: ${{ inputs.token }}
run: |
(cd /etc/vale && vale sync)
vale ${FILTER} --output=/etc/vale/rdjsonl.tmpl ${DIRECTORY} | \
inhibit-rules | \
/bin/reviewdog \
--conf=/etc/vale/.reviewdog.yml \
--fail-on-error \
--f=rdjsonl \
--name=vale \
--reporter=github-pr-review
shell: sh
12 changes: 12 additions & 0 deletions vale/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,22 @@ WORKDIR "/src/reviewdog-${REVIEWDOG_VERSION}"
RUN GOOS="${TARGETOS}" GOARCH="${TARGETARCH}" go build -o reviewdog ./cmd/reviewdog
RUN mv reviewdog /out

################################################################################
FROM --platform=${BUILDPLATFORM} golang:alpine AS inhibit-rules
ARG TARGETOS TARGETARCH

RUN mkdir /src /out
COPY /tools /src

WORKDIR /src
RUN GOOS="${TARGETOS}" GOARCH="${TARGETARCH}" go build -o inhibit-rules ./cmd/inhibit-rules
RUN mv inhibit-rules /out

################################################################################
FROM jdkato/vale:v3.8.0

COPY --from=reviewdog /out/reviewdog /bin
COPY --from=inhibit-rules /out/inhibit-rules /bin

# Git is useful for reporting with reviewdog.
RUN apk add --no-cache git
Expand Down
6 changes: 4 additions & 2 deletions vale/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ $(DOCUMENTATION): $(wildcard Grafana/styles/Grafana/*) $(wildcard ../tools/cmd/g
Grafana/styles/config/dictionaries/en_US-grafana.%: dictionary.libsonnet
$(MAKE) -C $(@D) $(@F)

IMAGE_PREREQUISITES := Grafana/styles/config/dictionaries/en_US-grafana.aff Grafana/styles/config/dictionaries/en_US-grafana.dic Grafana/styles/Grafana/Google .vale.ini

.PHONY: grafana/vale
grafana/vale: ## Builds a container image for Vale with the Grafana style loaded.
grafana/vale: Grafana/styles/config/dictionaries/en_US-grafana.aff Grafana/styles/config/dictionaries/en_US-grafana.dic Grafana/styles/Grafana/Google .vale.ini
grafana/vale: $(IMAGE_PREREQUISITES)
docker buildx build \
--platform linux/x86_64,linux/arm64 \
--progress=plain \
Expand All @@ -40,7 +42,7 @@ grafana/vale: Grafana/styles/config/dictionaries/en_US-grafana.aff Grafana/style

.PHONY: grafana/vale/push
grafana/vale/push: ## Builds and pushes container image for Vale with the Grafana style loaded.
grafana/vale/push:
grafana/vale/push: $(IMAGE_PREREQUISITES)
docker buildx build \
--platform linux/x86_64,linux/arm64 \
--progress=plain \
Expand Down
122 changes: 122 additions & 0 deletions vale/tools/cmd/inhibit-rules/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package main

import (
"bufio"
"encoding/json"
"flag"
"fmt"
"io"
"os"
"strings"

"github.com/grafana/writers-toolkit/tools/exit"
)

const command = "inhibit-rules"

func usage(w io.Writer, fs *flag.FlagSet) {
fmt.Fprintf(w, "Usage of %s:\n", command)
fs.SetOutput(w)
fs.PrintDefaults()

fmt.Fprintf(w, "\n")
fmt.Fprintln(w, "Reads JSON formatted error messages from standard input and filters them based on rule precedence.")
}

func main() {
fs := flag.NewFlagSet(command, flag.ExitOnError)
flag.Parse()

if flag.NArg() > 0 {
usage(os.Stderr, fs)
os.Exit(exit.UsageError)
}

r := bufio.NewReader(os.Stdin)
scanner := bufio.NewScanner(r)
diags := make([]diagnostic, 0)

var errs bool
for scanner.Scan() {
diag := diagnostic{}
if err := json.Unmarshal(scanner.Bytes(), &diag); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
errs = true
}

diags = append(diags, diag)
}

if err := scanner.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
errs = true
}

diags = inhibit(diags)
for _, d := range diags {
if err := json.NewEncoder(os.Stdout).Encode(d); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
errs = true
}
}

if errs {
os.Exit(exit.RuntimeError)
}
}

// {"message": "[{{ $check }}] {{ $message | jsonEscape }}", "location": {"path": "{{ $path }}", "range": {"start": {"line": {{ $line }}, "column": {{ $col }}}}}, "severity": "{{ $error }}"}
type diagnostic struct {
Message string `json:"message"`
Location struct {
Path string `json:"path"`
Range struct {
Start struct {
Line int `json:"line"`
Column int `json:"column"`
} `json:"start"`
} `json:"range"`
} `json:"location"`
Severity string `json:"severity"`
}

// Each error has the rule name in square brackets at the beginning of the message.
// For example: {"message": "[Grafana.Spelling] ..." ...}
func (d diagnostic) Rule() string {
if len(d.Message) == 0 || d.Message[0] != '[' {
return ""
}

end := strings.Index(d.Message, "]")
if end == -1 {
return ""
}

return d.Message[1:end]
}

func (d diagnostic) LocationKey() string {
return fmt.Sprintf("%s:%d:%d", d.Location.Path, d.Location.Range.Start.Line, d.Location.Range.Start.Column)
}

func inhibit(in []diagnostic) []diagnostic {
filtered := make(map[string]diagnostic)
precedence := map[string]int{
"Grafana.ProductPossessives": 0,
"Grafana.WordList": 1,
"Grafana.Spelling": 2,
}

for _, d := range in {
if _, ok := filtered[d.LocationKey()]; !ok || precedence[d.Rule()] < precedence[filtered[d.LocationKey()].Rule()] {
filtered[d.LocationKey()] = d
}
}

out := make([]diagnostic, 0, len(filtered))
for _, d := range filtered {
out = append(out, d)
}

return out
}
5 changes: 5 additions & 0 deletions vale/tools/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/grafana/writers-toolkit/vale/tools

go 1.23.3

require github.com/grafana/writers-toolkit/tools v0.0.0-20250108111324-7ed051741521 // indirect
2 changes: 2 additions & 0 deletions vale/tools/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/grafana/writers-toolkit/tools v0.0.0-20250108111324-7ed051741521 h1:sJw5cYvUHjysFlOEN5MLaJC9XR0Is3S1rLlUuv3L83s=
github.com/grafana/writers-toolkit/tools v0.0.0-20250108111324-7ed051741521/go.mod h1:f8r529z0g/keWOXl33WHJr4uYcv2bp7iwzFKeG+AukA=

0 comments on commit b2adb4e

Please sign in to comment.