Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 49 additions & 7 deletions .github/workflows/developer-guide-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ jobs:
echo "ASCII_DOC_LINT_REPORT=$REPORT_FILE" >> "$GITHUB_ENV"
echo "ASCII_DOC_LINT_STATUS=$STATUS" >> "$GITHUB_ENV"
if [ "$STATUS" -ne 0 ]; then
echo "Asciidoctor exited with status $STATUS" >&2
echo "Asciidoctor exited with status $STATUS — the final quality-gate step will fail the build." >&2
fi

- name: Build Developer Guide HTML and PDF
Expand Down Expand Up @@ -257,15 +257,17 @@ jobs:
HTML_REPORT="${REPORT_DIR}/vale-report.html"
mkdir -p "$REPORT_DIR"
set +e
vale --config docs/developer-guide/.vale.ini --output=JSON docs/developer-guide > "$REPORT_FILE"
vale --config docs/developer-guide/.vale.ini --minAlertLevel=suggestion --output=JSON docs/developer-guide > "$REPORT_FILE"
STATUS=$?
set -e
python3 scripts/developer-guide/vale_report_to_html.py --input "$REPORT_FILE" --output "$HTML_REPORT"
ISSUE_COUNT="$(python3 -c "import json, sys; data = json.load(open(sys.argv[1])); print(sum(len(v) for v in data.values() if isinstance(v, list)))" "$REPORT_FILE" 2>/dev/null || echo 0)"
echo "VALE_REPORT=$REPORT_FILE" >> "$GITHUB_ENV"
echo "VALE_HTML_REPORT=$HTML_REPORT" >> "$GITHUB_ENV"
echo "VALE_STATUS=$STATUS" >> "$GITHUB_ENV"
if [ "$STATUS" -ne 0 ]; then
echo "Vale exited with status $STATUS" >&2
echo "VALE_ISSUE_COUNT=$ISSUE_COUNT" >> "$GITHUB_ENV"
if [ "$STATUS" -ne 0 ] || [ "$ISSUE_COUNT" != "0" ]; then
echo "Vale exited with status $STATUS and reported $ISSUE_COUNT issue(s). The final quality-gate step will fail the build." >&2
fi

- name: Check for unused developer guide images
Expand All @@ -275,12 +277,20 @@ jobs:
JSON_REPORT="${REPORT_DIR}/unused-images.json"
TEXT_REPORT="${REPORT_DIR}/unused-images.txt"
mkdir -p "$REPORT_DIR"
set +e
python3 scripts/developer-guide/find_unused_images.py docs/developer-guide --output "$JSON_REPORT" | tee "$TEXT_REPORT"
STATUS=${PIPESTATUS[0]}
set -e
echo "UNUSED_IMAGES_JSON=$JSON_REPORT" >> "$GITHUB_ENV"
echo "UNUSED_IMAGES_TEXT=$TEXT_REPORT" >> "$GITHUB_ENV"
echo "UNUSED_IMAGES_STATUS=$STATUS" >> "$GITHUB_ENV"
if [ "$STATUS" -ne 0 ]; then
echo "Unused images detected — the final quality-gate step will fail the build." >&2
fi

- name: Summarize AsciiDoc linter findings
id: summarize_asciidoc_lint
if: always()
run: |
python3 scripts/developer-guide/summarize_reports.py ascii \
--report "${ASCII_DOC_LINT_REPORT}" \
Expand All @@ -289,6 +299,7 @@ jobs:

- name: Summarize Vale findings
id: summarize_vale
if: always()
run: |
python3 scripts/developer-guide/summarize_reports.py vale \
--report "${VALE_REPORT}" \
Expand All @@ -297,6 +308,7 @@ jobs:

- name: Summarize unused image findings
id: summarize_unused_images
if: always()
run: |
python3 scripts/developer-guide/summarize_reports.py unused-images \
--report "${UNUSED_IMAGES_JSON}" \
Expand All @@ -305,27 +317,31 @@ jobs:
--preview-limit 10

- name: Upload HTML artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: developer-guide-html
path: build/developer-guide/developer-guide-html.zip
if-no-files-found: error
if-no-files-found: warn

- name: Upload PDF artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: developer-guide-pdf
path: build/developer-guide/pdf/developer-guide.pdf
if-no-files-found: error
if-no-files-found: warn

- name: Upload AsciiDoc linter report
if: always()
uses: actions/upload-artifact@v4
with:
name: developer-guide-asciidoc-lint
path: ${{ env.ASCII_DOC_LINT_REPORT }}
if-no-files-found: warn

- name: Upload Vale report
if: always()
uses: actions/upload-artifact@v4
with:
name: developer-guide-vale-report
Expand All @@ -335,6 +351,7 @@ jobs:
if-no-files-found: warn

- name: Upload unused image report
if: always()
uses: actions/upload-artifact@v4
with:
name: developer-guide-unused-images
Expand All @@ -343,8 +360,33 @@ jobs:
${{ env.UNUSED_IMAGES_TEXT }}
if-no-files-found: warn

- name: Fail build on developer guide quality issues
if: always()
run: |
set -euo pipefail
FAIL=0
if [ "${ASCII_DOC_LINT_STATUS:-0}" != "0" ]; then
echo "AsciiDoc lint exited with status ${ASCII_DOC_LINT_STATUS}. See the developer-guide-asciidoc-lint artifact." >&2
FAIL=1
fi
if [ "${VALE_STATUS:-0}" != "0" ] || [ "${VALE_ISSUE_COUNT:-0}" != "0" ]; then
echo "Vale reported ${VALE_ISSUE_COUNT:-?} issue(s) (exit status ${VALE_STATUS:-?}). See the developer-guide-vale-report artifact." >&2
echo "Either fix the prose, add the term to docs/developer-guide/styles/config/vocabularies/CodenameOne/accept.txt, or document an exception per docs/developer-guide/.vale.ini." >&2
FAIL=1
fi
if [ "${UNUSED_IMAGES_STATUS:-0}" != "0" ]; then
echo "Unused image check reported orphan files (exit status ${UNUSED_IMAGES_STATUS}). See the developer-guide-unused-images artifact." >&2
echo "Either delete the orphan image or add an image:: reference for it from a developer-guide asciidoc file." >&2
FAIL=1
fi
if [ "$FAIL" -ne 0 ]; then
echo "Developer guide quality gates failed. Warnings from vale and asciidoctor are treated as build-breaking errors." >&2
exit 1
fi
echo "Developer guide quality gates passed (vale issues=${VALE_ISSUE_COUNT:-0}, asciidoctor status=${ASCII_DOC_LINT_STATUS:-0}, unused-image status=${UNUSED_IMAGES_STATUS:-0})."

- name: Comment with artifact download links
if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork }}
if: ${{ always() && github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork }}
uses: actions/github-script@v7
env:
ASCII_SUMMARY: ${{ steps.summarize_asciidoc_lint.outputs.summary }}
Expand Down
10 changes: 9 additions & 1 deletion docs/developer-guide/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
book-cover.generated.svg
book-cover.generated.png
styles/
# Vale syncs upstream style packages here, but the project-specific vocabulary
# (config/vocabularies/CodenameOne) is tracked so CI runs see the same word
# list local contributors do.
styles/*
!styles/config/
styles/config/*
!styles/config/vocabularies/
styles/config/vocabularies/*
!styles/config/vocabularies/CodenameOne/
157 changes: 157 additions & 0 deletions docs/developer-guide/.vale.ini
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,164 @@ Packages = https://github.com/errata-ai/packages/releases/download/v0.2.0/Micros
# - For longer/multi-line exceptions, place a // vale-skip comment before each
# line that needs protection.
# ─────────────────────────────────────────────────────────────────────────────
#
# Disabling a rule should be a last resort. The rules below are disabled
# because their recommendations are *wrong* for this audience or fire on
# structures that aren't prose — not because cleaning up the prose would be
# laborious. Anything else stays enabled, and individual false positives are
# whitelisted with `// vale-skip:` comments so the rule keeps catching real
# defects (the "IDEA IDEA" / "IDE IDE" repetition that the write-good.Illusions
# rule found is exactly the kind of LLM-generated drift we want to keep
# blocking).
#
# Rule disables — each entry explains *why* the rule's recommendation is
# inapplicable to a developer reference, not just *how many* hits it would
# produce.
#
# write-good.Passive / Microsoft.Passive
# Technical reference prose is the textbook case for passive voice:
# "the bytecode is translated to C", "the value is stored under the key",
# "the layer is configured with sRGB". Forcing active voice would require
# inventing actors ("Codename One translates the bytecode…") that distract
# from the noun phrase the reader actually cares about. The rule's
# recommendation is wrong for reference docs, not merely noisy.
#
# write-good.E-Prime
# Bans every form of the verb "to be". E-Prime is a philosophical exercise,
# not a usable style for reference documentation that constantly defines
# what things are.
#
# write-good.Weasel
# Flags "few", "many", "only", "some", "various" as weasel words. These are
# ordinary quantifiers in technical writing — "the framework supports a
# few platforms" is precise, not evasive. The rule's signal in this domain
# is essentially noise.
#
# write-good.TooWordy
# Flags "additional", "equivalent", "portion", "exclusively". None of these
# have a shorter exact synonym in technical contexts: "additional features"
# does not equal "more features" (the latter is comparative), "equivalent"
# does not equal "the same" (it allows different representations), etc.
# The rule's substitutions damage precision.
#
# Microsoft.GeneralURL
# Suggests "address" instead of "URL" "for a general audience". Our audience
# is developers wiring up HTTP requests; "URL" is the term every other API
# reference uses, and "address" is ambiguous with IP addresses and memory
# addresses in the surrounding text.
#
# Microsoft.HeadingAcronyms
# "Avoid using acronyms in a title or heading." UI, API, JSON, CSS, IDE,
# EDT, PSD, UIID are the primary nouns for things the guide documents.
# They must appear in section titles; that's where readers scan for them.
#
# Microsoft.Terms
# Microsoft's term substitution table is targeted at consumer copy:
# "agent" → "personal digital assistant" (wrong for "user agent"),
# "the cloud" → "cloud" (a debatable style choice). The substitutions
# would actively make the guide less correct.
#
# Microsoft.ComplexWords
# The flagged "complex" word is most often "component", which is the
# public class name `Component` — the rule cannot tell a class noun from
# prose. Replacing it with "part" would break references to the API.
#
# Microsoft.Acronyms
# Requires every acronym to have an inline definition. The guide uses
# 100+ acronyms (API, SDK, JVM, HTTP, REST, JSON, XML, CSS, RPC, IDE,
# PSD, UIID, EDT, DOM, …) and is read non-linearly. Re-defining each one
# per chapter would inflate every page for readers who already know them.
#
# Microsoft.Vocab
# The rule's only action is "verify your use of this word with the A-Z
# word list" — it never decides whether the word is actually wrong. Most
# matches are words like "allows" or "Author" that are unambiguous.
#
# Microsoft.Headings
# Enforces sentence-style capitalization, which the guide already follows.
# Almost every match is a heading that embeds a code identifier
# (`AndroidNativeUtil's checkForPermission`, `BrowserComponent`) or a
# proper noun (`Marshmallow`, `Material`), neither of which the rule can
# tell apart from the prose words it would lowercase.
#
# Microsoft.SentenceLength
# Caps sentences at 30 words. Sentences over 30 words are common in
# API descriptions that thread several constraints together; splitting
# them either repeats the subject noun or introduces ambiguity about
# which clause modifies which. The rule's heuristic is too coarse for
# reference prose.
#
# Microsoft.Ranges / Microsoft.RangeFormat
# The flagged "ranges" are ISO standard identifiers (ISO 639-1, ISO
# 3166-1), color-channel values (0-255), and ISBNs (99999999-8). Rewriting
# them as "from X through Y" or with en dashes either changes their
# meaning or breaks lookup against the upstream standard.
#
# Microsoft.Negative
# Wants "-1" written as "–1" (en dash). Every flagged instance is a
# sentinel return value (`return -1`, `getX() == -1`) from a documented
# API; substituting an en dash would corrupt the example.
#
# Microsoft.Dashes
# Microsoft wants em dashes flush against the surrounding words. The
# guide uses spaced em dashes in prose and spaced en dashes in bullet
# lists ("* Topic – Description"); both are standard editorial choices
# and the spaced form reads better at our column widths.
#
# Microsoft.Spacing
# The match pattern (lowercase letter, period, uppercase letter, no
# space) fires on dotted code identifiers like `android.permission.X`,
# `androidx.activity.ComponentActivity`, and `Manifest.permission`. The
# rule cannot tell code from prose.
#
# Microsoft.Units
# Fires on bare phrases like "in millimeters" / "of pixels" when no
# numeral is adjacent. The rule's premise — "don't spell out the number"
# — depends on there being a number to substitute for, and there often
# isn't one.
#
# Microsoft.Accessibility
# Flags "disabled" and "normal" as bias language. In this guide both are
# documented UI states — the `disabled` style state on `Button`, the
# `Display.NORMAL` rotation constant — not descriptions of people. The
# rule has no way to distinguish a state machine value from human
# identification.
#
# Microsoft.Semicolon
# A blanket "Try to simplify this sentence" suggestion fired on every
# semicolon. Two problems make the signal effectively zero in this guide:
# (1) vale's adoc tokenizer still feeds the rule lines inside
# `[source,java]` and `[source,bash]` blocks (`drawer.setSafeArea(true);`,
# `addToQueueAndWait(request);`), where the semicolon is the Java
# statement terminator, not a sentence linker; (2) for the legitimate
# prose matches, the semicolon links closely related independent clauses
# ("Most new devices contain one version of sqlite or another; sqlite is
# a lightweight SQL database…") which Microsoft's own technical
# documentation routinely uses. Splitting them into separate sentences
# removes the conjunction the reader relies on.

[*.{adoc,asciidoc}]
BasedOnStyles = Microsoft, proselint, write-good
TokenIgnores = (?s)// vale-skip:[^\n]*\n[^\n]+\n

write-good.Passive = NO
Microsoft.Passive = NO
write-good.E-Prime = NO
write-good.Weasel = NO
write-good.TooWordy = NO
Microsoft.GeneralURL = NO
Microsoft.HeadingAcronyms = NO
Microsoft.Terms = NO
Microsoft.ComplexWords = NO
Microsoft.Acronyms = NO
Microsoft.Vocab = NO
Microsoft.Headings = NO
Microsoft.SentenceLength = NO
Microsoft.Ranges = NO
Microsoft.RangeFormat = NO
Microsoft.Negative = NO
Microsoft.Dashes = NO
Microsoft.Spacing = NO
Microsoft.Units = NO
Microsoft.Accessibility = NO
Microsoft.Semicolon = NO
Loading
Loading