diff --git a/.github/workflows/vale-comment.yml b/.github/workflows/vale-comment.yml new file mode 100644 index 00000000000..7e1aea8c978 --- /dev/null +++ b/.github/workflows/vale-comment.yml @@ -0,0 +1,67 @@ +name: Vale Comments + +# Posts Vale results from vale.yml as PR review comments, including for fork PRs. + +on: + workflow_run: + workflows: ['Vale Linting'] + types: [completed] + +permissions: + actions: read + contents: read + pull-requests: write + +jobs: + comment: + if: github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + steps: + # SECURITY: This workflow has write permissions, so never add a step that checks out or runs code from the PR. + + - name: Download results + uses: actions/download-artifact@v8 + with: + name: vale-results + path: vale-results + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ github.token }} + + - name: Validate PR number + id: pr + run: | + PR_NUMBER=$(head -n 1 vale-results/pr-number) + # The artifact is untrusted: accept only a plain integer + if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then + echo "Invalid PR number" + exit 1 + fi + # Confirm the PR's head commit is the commit that was linted + PR_SHA=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER" --jq '.head.sha') + if [ "$PR_SHA" != "${{ github.event.workflow_run.head_sha }}" ]; then + echo "PR head ($PR_SHA) differs from linted commit; skipping stale results" + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "number=$PR_NUMBER" >> "$GITHUB_OUTPUT" + if [ -s vale-results/results.rdjsonl ]; then echo "has_results=true" >> "$GITHUB_OUTPUT"; fi + env: + GH_TOKEN: ${{ github.token }} + + - name: Set up reviewdog + if: steps.pr.outputs.has_results == 'true' + # Pin to a commit (v1.5.0) because this job has write permissions + uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f + + - name: Post review comments + if: steps.pr.outputs.has_results == 'true' + run: | + # reviewdog needs a git repo; filter-mode=added comments only on lines the PR changed + git init -q + env -u GITHUB_ACTIONS reviewdog -f=rdjsonl -name=vale -reporter=github-pr-review -filter-mode=added -fail-on-error=false -level=info < vale-results/results.rdjsonl + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ github.token }} + CI_PULL_REQUEST: ${{ steps.pr.outputs.number }} + CI_COMMIT: ${{ github.event.workflow_run.head_sha }} + CI_REPO_OWNER: ${{ github.repository_owner }} + CI_REPO_NAME: ${{ github.event.repository.name }} diff --git a/.github/workflows/vale.yml b/.github/workflows/vale.yml index 42f84432fb0..735d81f18df 100644 --- a/.github/workflows/vale.yml +++ b/.github/workflows/vale.yml @@ -7,13 +7,12 @@ on: paths: - 'content/en/docs/**/*.md' -# Allow reading repo contents and posting PR comments -# Note: Fork PRs from org members run automatically but get read-only tokens, -# so reviewdog falls back to check annotations instead of review comments. -# External contributors require workflow approval per repo settings. +# Fork PRs don't have write permissions, so posting review comments happens in vale-comment.yml after this workflow finishes. +# This workflow only runs Vale and uploads the results as an artifact. + permissions: contents: read - pull-requests: write + pull-requests: read jobs: vale: @@ -21,7 +20,7 @@ jobs: if: github.repository_owner == 'mendix' && github.event.pull_request.draft == false runs-on: ubuntu-latest env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + VALE_VERSION: 3.26.0 # Cancel old runs when new commits are pushed concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} @@ -39,18 +38,38 @@ jobs: - name: Get changed files id: changed-files run: | - FILES=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files --paginate --jq '.[] | select(.status != "removed") | .filename' | { grep '^content/en/docs/.*\.md$' || true; } | paste -sd, -) - echo "files=$FILES" >> $GITHUB_OUTPUT + # Also writes the PR number and an empty results file for vale-comment.yml + mkdir -p vale-results + echo "${{ github.event.pull_request.number }}" > vale-results/pr-number + gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files --paginate --jq '.[] | select(.status != "removed") | .filename' | { grep '^content/en/docs/.*\.md$' || true; } > changed-files.txt + : > vale-results/results.rdjsonl + if [ -s changed-files.txt ]; then echo "has_files=true" >> "$GITHUB_OUTPUT"; fi env: GH_TOKEN: ${{ github.token }} + - name: Install Vale + if: steps.changed-files.outputs.has_files == 'true' + run: | + cd "$RUNNER_TEMP" + ARCHIVE="vale_${VALE_VERSION}_Linux_64-bit.tar.gz" + BASE_URL="https://github.com/vale-cli/vale/releases/download/v${VALE_VERSION}" + curl -sSfLO "$BASE_URL/$ARCHIVE" + mkdir bin && tar -xzf "$ARCHIVE" -C bin vale + echo "$PWD/bin" >> "$GITHUB_PATH" + cd "$GITHUB_WORKSPACE" && "$RUNNER_TEMP/bin/vale" sync + - name: Run Vale - if: steps.changed-files.outputs.files != '' - uses: vale-cli/vale-action@v2 + run: | + if [ -s changed-files.txt ]; then + # Only flag errors, not warnings or suggestions. Vale exits non-zero on errors; that is expected. + xargs -d '\n' vale --output=JSON --minAlertLevel=error < changed-files.txt > vale.json || true + # Convert Vale JSON to reviewdog's rdjsonl format + jq -c 'to_entries[] | .key as $f | .value[] | {message: ("[" + .Check + "] " + .Message), location: {path: $f, range: {start: {line: .Line, column: .Span[0]}}}, severity: (.Severity | ascii_upcase)}' vale.json > vale-results/results.rdjsonl + fi + + - name: Upload results + uses: actions/upload-artifact@v7 with: - files: ${{ steps.changed-files.outputs.files }} - separator: ',' - version: 3.14.2 - fail_on_error: false - reporter: github-pr-review # Uses filter_mode=added (default): only comment on modified lines - vale_flags: '--minAlertLevel=error' # Only flag errors, not warnings or suggestions + name: vale-results + path: vale-results/ + retention-days: 3 diff --git a/.vale.ini b/.vale.ini index 5a7722f13a6..1f17adaa80b 100644 --- a/.vale.ini +++ b/.vale.ini @@ -6,7 +6,8 @@ MinAlertLevel = suggestion # Packages to sync from vale.sh # Microsoft = Microsoft Writing Style Guide -Packages = Microsoft +# To update pinned version, change the version in the URL, run `vale sync`, and check the Mendix rules that extend Microsoft rules +Packages = https://github.com/errata-ai/Microsoft/releases/download/v0.15.1/Microsoft.zip # Ignore patterns [*.{md,mdx}] @@ -26,8 +27,8 @@ BasedOnStyles = Microsoft, Mendix # requirements documented in content/en/docs/community-tools/contribute-to-mendix-docs/style-guide/ # Acronyms +# Extended by Mendix.Acronyms # Microsoft requires title case for acronym definitions; Mendix allows sentence case too -# Replacement: Mendix.Acronyms accepts sentence-case definitions Microsoft.Acronyms = NO # Auto @@ -47,7 +48,7 @@ Microsoft.Contractions = NO # Microsoft removes all spaces around em/en dashes; Mendix has context-specific rules # Mendix style guide: Em dashes (—) have no spaces; en dashes (–) have spaces except in number ranges # Replacement: Mendix.Dashes removes spaces around em dashes -# Mendix.ListIntroductions enforces en dashes (not em dashes or spaced hyphens) for list item introductions +# Mendix.ListIntroductions enforces en dashes (not em dashes or spaced hyphens) for list item introductions Microsoft.Dashes = NO # Foreign @@ -103,8 +104,7 @@ Microsoft.UIVerbs = NO Microsoft.Vocab = NO # Wordiness -# Replaced some of the substitutions -# Replacement: Mendix.Wordiness +# Extended by Mendix.Wordiness Microsoft.Wordiness = NO # ============================================================================ diff --git a/.vale/SETUP.md b/.vale/SETUP.md index 5fd1ed990c0..00ba97c29c7 100644 --- a/.vale/SETUP.md +++ b/.vale/SETUP.md @@ -52,6 +52,10 @@ After installing Vale: vale --version ``` + Vale 3.20 or later is required. If you have an older version, update it: + * On macOS, run `brew upgrade vale` + * On Windows, download the latest version from [the releases page](https://github.com/vale-cli/vale/releases) and replace `vale.exe` + 2. **Sync the Microsoft Style Guide rules:** Navigate to the root of your docs repository (for example, `cd ~/docs`), then run: diff --git a/.vale/styles/Mendix/Acronyms.yml b/.vale/styles/Mendix/Acronyms.yml index 836160cda74..460a1bdf987 100644 --- a/.vale/styles/Mendix/Acronyms.yml +++ b/.vale/styles/Mendix/Acronyms.yml @@ -1,87 +1,27 @@ -extends: conditional +# Extends Microsoft.Acronyms (requires Vale 3.20 or later). +# Ensures that the existence of 'first' implies the existence of 'second'. +extends: Microsoft.Acronyms message: "Define '%s' on first use." link: https://docs.mendix.com/community-tools/contribute-to-mendix-docs/style-guide/grammar-formatting/#acronyms-and-initialisms -level: suggestion -ignorecase: false scope: - paragraph - heading - list - table - blockquote -# Ensures that the existence of 'first' implies the existence of 'second'. -# Known issue: Doesn't recognize hyperlinks for acronym definitions -# Known issue: Doesn't recognize CLOB (Character Large Object) always expects definition first—"Character Large Object (CLOB)" even if acronym is more common than definition # Modified from Microsoft.Acronyms to accept sentence-case definitions (e.g., "personal access token (PAT)" not just "Personal Access Token (PAT)") -first: '\b([A-Z]{3,5})\b' second: '(?:\b[A-Za-z][a-z]+ )+\(([A-Z]{3,5})\)' -# ... with the exception of these: -exceptions: - - API - - ASP +# ... with the exception of these (and the Microsoft.Acronyms exceptions): +exceptions+: - BTP - - CLI - - CPU - - CSS - - CSV - - DEBUG - - DOM - - DPI - - FAQ - - GCC - - GDB - - GET - GIF - - GPU - - GTK - - GUI - HANA - - HTML - - HTTP - - HTTPS - - IDE - - JAR - JDBC - JPEG - JPG - - JSON - - JSX - - LESS - - LLDB - - LTS - MCP - MDA - MPR - - MTS - - NET - - NOTE - - NVDA - - OSS - - PATH - - PDF - - PHP - PNG - - POST - - RAM - - REPL - REST - - RSA - SAP - - SCM - - SCSS - - SDK - - SQL - - SSH - - SSL - - SVG - - TBD - - TCP - - TODO - - URI - - URL - - USB - - UTF - - XML - - XSS - - YAML - - ZIP diff --git a/.vale/styles/Mendix/Dashes.yml b/.vale/styles/Mendix/Dashes.yml index 0ea8ec546e9..06d94f1b589 100644 --- a/.vale/styles/Mendix/Dashes.yml +++ b/.vale/styles/Mendix/Dashes.yml @@ -1,5 +1,4 @@ # Reference: content/en/docs/community-tools/contribute-to-mendix-docs/style-guide/grammar-formatting.md (Dashes) -# Known issue: False positive around links, code snippets. Also check Bold and Italics to see if they are also problematic. extends: substitution message: "Remove spaces around the em dash (—). Or, to set off a list item intro or number range, use an en dash (–) instead." link: https://docs.mendix.com/community-tools/contribute-to-mendix-docs/style-guide/grammar-formatting/#em-dash diff --git a/.vale/styles/Mendix/IconShortcodes.yml b/.vale/styles/Mendix/IconShortcodes.yml index 5a9b3d5d2bf..b1e47ccce66 100644 --- a/.vale/styles/Mendix/IconShortcodes.yml +++ b/.vale/styles/Mendix/IconShortcodes.yml @@ -1,6 +1,6 @@ # Mendix custom rule: Use icon shortcodes with tooltips when describing clickable icons # Reference: content/en/docs/community-tools/contribute-to-mendix-docs/icon-demo.md -# Known issue: Only matches plaintext +# Known issue: Doesn't match code font icon descriptions. extends: substitution message: "Use the icon shortcode and tooltip: '%s' instead of '%s'" link: https://docs.mendix.com/icons/ diff --git a/.vale/styles/Mendix/ListIntroductions.yml b/.vale/styles/Mendix/ListIntroductions.yml index f52c794a138..20f90a3e4a2 100644 --- a/.vale/styles/Mendix/ListIntroductions.yml +++ b/.vale/styles/Mendix/ListIntroductions.yml @@ -1,5 +1,5 @@ # Reference: content/en/docs/community-tools/contribute-to-mendix-docs/style-guide/grammar-formatting.md (Lists) -# Known issue: Only matches plaintext list intros +# Known issue: Doesn't match code font list intros extends: substitution message: "Set off list item introductions with an en dash (–), not an em dash (—)." link: https://docs.mendix.com/community-tools/contribute-to-mendix-docs/style-guide/grammar-formatting/#en-dash diff --git a/.vale/styles/Mendix/Wordiness.yml b/.vale/styles/Mendix/Wordiness.yml index 311eea916f6..d5928f67424 100644 --- a/.vale/styles/Mendix/Wordiness.yml +++ b/.vale/styles/Mendix/Wordiness.yml @@ -1,128 +1,17 @@ -extends: substitution -message: "Consider using '%s' instead of '%s'." -link: https://docs.microsoft.com/en-us/style-guide/word-choice/use-simple-words-concise-sentences +# Extends Microsoft.Wordiness (requires Vale 3.20 or later). +extends: Microsoft.Wordiness ignorecase: false -level: suggestion -action: - name: replace -swap: - "sufficient number(?: of)?": enough - (?:take away|eliminate): remove - (?:in order to|as a means to): to - (?:inform): tell - (?:previous|prior) to: before - (?:utilize|make use of): use - a (?:large)? majority of: most - a (?:large)? number of: many - a myriad of: myriad - adversely impact: hurt - all across: across - all of a sudden: suddenly - all of these: these - all of(?! a sudden| these): all - all-time record: record +link: https://learn.microsoft.com/en-us/style-guide/word-choice/use-simple-words-concise-sentences +swap+: + # Added/changed entries + "(?:take away|eliminate)": remove + "(?:inform)": tell allows you to: lets you - almost all: most - almost never: seldom - along the lines of: similar to - an adequate number of: enough - an appreciable number of: many - an estimated: about - any and all: all - are in agreement: agree - as a matter of fact: in fact - as a means of: to - as a result of: because of - as of yet: yet - as per: per - at a later date: later - at all times: always - at the present time: now - at this point in time: at this point - based in large part on: based on - based on the fact that: because - basic necessity: necessity - because of the fact that: because - came to a realization: realized - came to an abrupt end: ended abruptly - carry out an evaluation of: evaluate - close down: close - closed down: closed - complete stranger: stranger - completely separate: separate - concerning the matter of: regarding - conduct a review of: review - conduct an investigation: investigate - conduct experiments: experiment - continue on: continue - despite the fact that: although - disappear from sight: disappear + in case of: if doomed to fail: will fail - drag and drop: drag - drag-and-drop: drag - due to the fact that: because - during the period of: during - during the time that: while - emergency situation: emergency - establish connectivity: connect - except when: unless - excessive number: too many - extend an invitation: invite - fall down: fall - fell down: fell - for the duration of: during - gather together: gather - has the ability to: can - has the capacity to: can - has the opportunity to: could - hold a meeting: meet - if this is not the case: if not - in a careful manner: carefully - in a thoughtful manner: thoughtfully in a timely manner: on time - in addition: also - in an effort to: to - in between: between - in case of: if - in lieu of: instead of - in many cases: often - in most cases: usually - in order to: to - in some cases: sometimes - in spite of the fact that: although - in spite of: despite - in the (?:very)? near future: soon - in the event that: if - in the neighborhood of: roughly - in the vicinity of: close to - it would appear that: apparently - lift up: lift - made reference to: referred to - make reference to: refer to - mix together: mix - none at all: none - not in a position to: unable - not possible: impossible - of major importance: important - perform an assessment of: assess - pertaining to: about - place an order: order - plays a key role in: is essential to - present time: now - readily apparent: apparent - some of the: some - span across: span - subsequent to: after - successfully complete: complete - take action: act - take into account: consider - the question as to whether: whether - there is no doubt but that: doubtless - this day and age: this age - this is a subject that: this subject - time (?:frame|period): time - under the provisions of: under - until such time as: until - whether or not: whether - with regard to: regarding - with the exception of: except for +swap-: + # Replaced/removed entries + - "(?:extract|take away|eliminate)" + - "(?:inform|let me know)" + - used for fuel purposes