From 7133a1c0da6a81384055d2264e98c5e8bb23cedb Mon Sep 17 00:00:00 2001 From: VasiliyF <5789590+vfofanov@users.noreply.github.com> Date: Tue, 6 Oct 2026 14:27:54 +0200 Subject: [PATCH 1/4] Test the diagnostic catalog: one descriptor per code, in its part - docs-check: every reported code has exactly one BuildDiagnosticDescriptor item in the part that reports it, with the title, family, severity and opening sentence of its codes.md section and the text of its task - tests/docs.sh: a broken catalog must fail on each planted fault - tests/codes.sh: the items reach an evaluated project, each defined by its own part's file --- tests/codes.sh | 51 ++++++++++++- tests/docs.sh | 48 +++++++++++++ tools/docs-check.sh | 169 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 254 insertions(+), 14 deletions(-) diff --git a/tests/codes.sh b/tests/codes.sh index 3f6c937..43c5fe4 100644 --- a/tests/codes.sh +++ b/tests/codes.sh @@ -1,7 +1,7 @@ # Codes: every code is spelled MSKIT, and every diagnostic links to its section of # docs/reference/codes.md through MSKit_CodesHelpBaseUrl. The terminal logger prints the HelpLink as # an OSC 8 hyperlink on the code, which is how the link is read back here. -# Sourced by tests/run.sh: uses its pass, bad, $out, $here, $lib, $fixtures, $clean_env and $ci_env. +# Sourced by tests/run.sh: uses its pass, bad, $out, $here, $kit, $sample, $lib, $fixtures, $clean_env and $ci_env. if grep -rn 'MSKIT[_]' "$here/kit" > "$out/codes-old-spelling.log"; then bad "codes: the kit spells a code with an underscore (see $out/codes-old-spelling.log)"; head -n 5 "$out/codes-old-spelling.log" @@ -21,3 +21,52 @@ for code in mskitpkg001 mskitpkg010; do grep -qF "]8;;https://codes.example/kit.md#$code" "$out/codes-helplink-pkg.log" \ && pass "codes: MSKit_CodesHelpBaseUrl moves the $code link" || bad "codes: the $code link ignores MSKit_CodesHelpBaseUrl (see $out/codes-helplink-pkg.log)" done + +# The diagnostic catalog: every BuildDiagnosticDescriptor item reaches an evaluated project, defined +# by the file of the part that reports the code (a collector reads DefiningProjectFullPath). +catalog_items() { + proj="$1"; name="$2"; shift 2 + $clean_env dotnet msbuild "$proj" -nologo -getItem:BuildDiagnosticDescriptor "$@" 2> "$out/$name.err" | tr -d '\r' > "$out/$name.json" || true + awk ' + function val(l) { sub(/^[^:]*:[[:space:]]*"/, "", l); sub(/",?[[:space:]]*$/, "", l); gsub(/\\/, "/", l); return l } + /^[[:space:]]*"Identity":/ { id = val($0) } + /^[[:space:]]*"Title":/ { title = val($0) } + /^[[:space:]]*"DefaultSeverity":/ { severity = val($0) } + /^[[:space:]]*"HelpLink":/ { link = val($0) } + /^[[:space:]]*"DefiningProjectFullPath":/ { part = val($0); if (!sub(/^.*\/\.toolkit\/msbuild\//, "", part) || !sub(/\/diagnostic\.descriptors\.props$/, "", part)) part = "?" } + /^[[:space:]]*}/ { if (id != "") print id "\t" part "\t" severity "\t" link "\t" (title == "" ? "-" : "titled"); id = part = severity = link = title = "" } + ' "$out/$name.json" | sort > "$out/$name.tsv" +} +sh "$here/tools/docs-check.sh" --list descriptors > "$out/catalog-declared.tsv" 2> "$out/catalog-declared.err" || true + +catalog_root="$out/catalog" +rm -rf "$catalog_root"; mkdir -p "$catalog_root/App" +printf '\n \n net8.0\n \n\n' > "$catalog_root/App/App.csproj" +sh "$kit/.toolkit/update.sh" --source "$kit" --root "$catalog_root" --add PackageAsProj > "$out/catalog-install.log" 2>&1 || { bad "catalog: update.sh --add PackageAsProj failed (see $out/catalog-install.log)"; tail -n 5 "$out/catalog-install.log"; } + +catalog_items "$catalog_root/App/App.csproj" catalog-all +cut -f1,2 "$out/catalog-declared.tsv" > "$out/catalog-all.expected" +cut -f1,2 "$out/catalog-all.tsv" > "$out/catalog-all.actual" +catalog_count=$(grep -c . "$out/catalog-all.actual" || true) +[ "$catalog_count" -gt 0 ] && cmp -s "$out/catalog-all.expected" "$out/catalog-all.actual" \ + && pass "catalog: $catalog_count descriptors reach an evaluated project, each defined by the part that reports its code" \ + || { bad "catalog: the evaluated BuildDiagnosticDescriptor items differ from the kit's (diff $out/catalog-all.expected $out/catalog-all.actual)"; diff "$out/catalog-all.expected" "$out/catalog-all.actual" | head -n 10; } +grep -q " DragoAnt.MSBuildKit.PackageAsProj$" "$out/catalog-all.actual" && pass "catalog: an optional part brings its descriptors when it is installed" || bad "catalog: no descriptor is defined by the PackageAsProj part (see $out/catalog-all.tsv)" +catalog_incomplete=$(awk -F'\t' '($3 != "Warning" && $3 != "Error") || $5 != "titled"' "$out/catalog-all.tsv" | grep -c . || true) +[ "$catalog_count" -gt 0 ] && [ "$catalog_incomplete" -eq 0 ] && pass "catalog: every evaluated descriptor has a Title and a DefaultSeverity of Warning or Error" \ + || bad "catalog: $catalog_incomplete of $catalog_count evaluated descriptor(s) lack a Title or a Warning/Error DefaultSeverity (see $out/catalog-all.tsv)" +grep -q "^MSKITVER006 DragoAnt.MSBuildKit Error $codes_url#mskitver006 " "$out/catalog-all.tsv" \ + && pass "catalog: MSKITVER006 evaluates to an Error linked to $codes_url#mskitver006" || bad "catalog: MSKITVER006 is not an Error linked to its section (see $out/catalog-all.tsv)" + +catalog_items "$lib" catalog-sample +while IFS=' ' read -r code part where; do + [ -d "$sample/.toolkit/msbuild/$part" ] && printf '%s\t%s\n' "$code" "$part" +done < "$out/catalog-declared.tsv" > "$out/catalog-sample.expected" +cut -f1,2 "$out/catalog-sample.tsv" > "$out/catalog-sample.actual" +[ -s "$out/catalog-sample.actual" ] && cmp -s "$out/catalog-sample.expected" "$out/catalog-sample.actual" && ! grep -q "PackageAsProj" "$out/catalog-sample.actual" \ + && pass "catalog: the sample sees the $(grep -c . "$out/catalog-sample.actual") descriptors of its installed parts and none of a part it lacks" \ + || bad "catalog: the sample's descriptors are not those of its installed parts (diff $out/catalog-sample.expected $out/catalog-sample.actual)" + +catalog_items "$lib" catalog-moved -p:MSKit_CodesHelpBaseUrl=https://codes.example/kit.md +grep -q "^MSKITPKG001 DragoAnt.MSBuildKit.Packaging Warning https://codes.example/kit.md#mskitpkg001 " "$out/catalog-moved.tsv" \ + && pass "catalog: MSKit_CodesHelpBaseUrl moves a descriptor's HelpLink" || bad "catalog: the MSKITPKG001 descriptor ignores MSKit_CodesHelpBaseUrl (see $out/catalog-moved.tsv)" diff --git a/tests/docs.sh b/tests/docs.sh index 2aad5d2..c1a18bb 100644 --- a/tests/docs.sh +++ b/tests/docs.sh @@ -33,3 +33,51 @@ for needle in "property MSKit_SemVerRegex" "code MSKITVER004" "write the heading where=${needle%%|*}; what=${needle#*|} grep -F "$where" "$out/docs-check-broken.log" | grep -qF "$what" && pass "docs: the check reports '$needle'" || bad "docs: the check missed '$needle' (see $out/docs-check-broken.log)" done + +# The diagnostic catalog: a copy with one descriptor missing, doubled, orphaned, in the wrong part, +# in the wrong file and unimported, and with each piece of metadata wrong, must fail on each. +dc_cat="$out/docs-check-catalog" +dc_copy "$dc_cat" +dc_parts="$dc_cat/kit/.toolkit/msbuild" +dc_item() { sed "/Include=\"$2\"/,/\/>/ $3" "$dc_parts/$1/diagnostic.descriptors.props" > "$dc_cat/item.tmp" && mv "$dc_cat/item.tmp" "$dc_parts/$1/diagnostic.descriptors.props"; } +dc_fake() { printf ' ' "$1" "$2"; } +if [ -f "$dc_parts/DragoAnt.MSBuildKit/diagnostic.descriptors.props" ]; then + dc_item DragoAnt.MSBuildKit.Testing MSKITTEST031 d + dc_item DragoAnt.MSBuildKit.Packaging MSKITPKG019 d + dc_item DragoAnt.MSBuildKit.Testing.XUnit.v3 MSKITTEST005 d + sed "s|^| \n$(dc_fake MSKITVER003 mskitver003)\n$(dc_fake MSKITVER007 mskitver007)\n$(dc_fake MSKITTEST005 mskittest005)\n \n&|" \ + "$here/kit/.toolkit/msbuild/DragoAnt.MSBuildKit/diagnostic.descriptors.props" > "$dc_parts/DragoAnt.MSBuildKit/diagnostic.descriptors.props" + sed "s|^| \n$(dc_fake MSKITVER098 mskitver098)\n \n&|" \ + "$here/kit/.toolkit/msbuild/DragoAnt.MSBuildKit/audit/audit.version.targets" > "$dc_parts/DragoAnt.MSBuildKit/audit/audit.version.targets" + grep -v 'diagnostic.descriptors.props' "$here/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/init.props" > "$dc_parts/DragoAnt.MSBuildKit.Core/init.props" + grep -v 'DragoAnt.MSBuildKit.PackageAsProj/init.props' "$here/kit/.toolkit/msbuild/init.props" > "$dc_parts/init.props" + dc_item DragoAnt.MSBuildKit.Core MSKITCORE001 's/#mskitcore001"/#mskitroslyn001"/' + dc_item DragoAnt.MSBuildKit.Core MSKITROSLYN001 's/DefaultSeverity="Error"/DefaultSeverity="Warning"/' + dc_item DragoAnt.MSBuildKit.Core MSKITROSLYN002 's/DefaultSeverity="Error"/DefaultSeverity="Info"/' + dc_item DragoAnt.MSBuildKit MSKITPRE001 's/DefaultSeverity="Warning"/DefaultSeverity="Error"/' + dc_item DragoAnt.MSBuildKit MSKITRES001 's/Category="[^"]*"/Category="Versioning"/' + dc_item DragoAnt.MSBuildKit MSKITDUP001 's/Description="/Description="In short: /' + dc_item DragoAnt.MSBuildKit MSKITVER004 's/MessageFormat="/MessageFormat="Oops. /' + dc_item DragoAnt.MSBuildKit.Packaging MSKITPKG004 's/MessageFormat="/MessageFormat="Oops. /' + dc_item DragoAnt.MSBuildKit.Packaging MSKITPKG003 's/ Title="[^"]*"/ Title=""/' + dc_item DragoAnt.MSBuildKit.PackageAsProj MSKITPAP002 's/Title="/Title="Not /' + dc_item DragoAnt.MSBuildKit.Testing MSKITTEST010 's/%24(/$(/' +fi +sh "$dc_cat/tools/docs-check.sh" > "$out/docs-check-catalog.log" 2>&1 && bad "docs: the check passed a broken catalog (see $out/docs-check-catalog.log)" +for needle in "code MSKITTEST031 (|has no BuildDiagnosticDescriptor item; add one to kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Testing/diagnostic.descriptors.props" \ + "code MSKITPKG019 (|has no BuildDiagnosticDescriptor item" "MSKITVER007 already has a BuildDiagnosticDescriptor" \ + "MSKITVER003 has a BuildDiagnosticDescriptor, but no or reports it" \ + "MSKITTEST005 is described in part DragoAnt.MSBuildKit, but reported by DragoAnt.MSBuildKit.Testing.XUnit.v3" \ + "audit.version.targets:|declare MSKITVER098 in its part folder, in diagnostic.descriptors.props" \ + "diagnostic.descriptors.props is not imported by kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/init.props" \ + "does not import DragoAnt.MSBuildKit.PackageAsProj/init.props" \ + "MSKITCORE001 has HelpLink=" "MSKITROSLYN001 has DefaultSeverity=\"Warning\", but no reports it" \ + "MSKITROSLYN002 has DefaultSeverity=\"Info\"; use Warning or Error" \ + "MSKITPRE001 has DefaultSeverity=\"Error\", but its section in docs/reference/codes.md says (warning)" \ + "MSKITRES001 has Category=\"Versioning\", but its section in docs/reference/codes.md is under \"References\"" \ + "the Description of MSKITDUP001 is not the opening sentence(s)" "the MessageFormat of MSKITVER004 is not the text its task reports" \ + "the MessageFormat of MSKITPKG004 is not the text its task reports" "MSKITPKG003 has no Title" "MSKITPAP002 has Title=\"Not " \ + "MSKITTEST010 has metadata MSBuild would expand"; do + where=${needle%%|*}; what=${needle#*|} + grep -F "$where" "$out/docs-check-catalog.log" | grep -qF "$what" && pass "docs: the check reports '$needle'" || bad "docs: the check missed '$needle' (see $out/docs-check-catalog.log)" +done diff --git a/tools/docs-check.sh b/tools/docs-check.sh index 77b584e..f2dd3a5 100644 --- a/tools/docs-check.sh +++ b/tools/docs-check.sh @@ -1,9 +1,11 @@ #!/bin/sh # Keeps the documentation honest against the kit: every property and item the kit defines has a row # in docs/reference/properties.md, every code it reports a section headed by the code id in -# docs/reference/codes.md, every / a HelpLink to that section, every MSKit_ name and -# code the docs mention exists in the kit, no file spells a code the old way, and every link resolves. -# Usage: sh tools/docs-check.sh [--root DIR] [--list properties|items|codes|diagnostics] +# docs/reference/codes.md, every / a HelpLink to that section, every code exactly one +# BuildDiagnosticDescriptor item in the part that reports it, with the title, description, family +# and severity of its section and the text of its task, every MSKit_ name and code the docs mention +# exists in the kit, no file spells a code the old way, and every link resolves. +# Usage: sh tools/docs-check.sh [--root DIR] [--list properties|items|codes|diagnostics|descriptors] set -eu root=$(cd "$(dirname "$0")/.." && pwd) @@ -12,11 +14,11 @@ while [ $# -gt 0 ]; do case "$1" in --root) [ $# -ge 2 ] || { echo "docs-check: --root needs a value" >&2; exit 2; }; root=$(cd "$2" && pwd); shift 2 ;; --list) [ $# -ge 2 ] || { echo "docs-check: --list needs a value" >&2; exit 2; }; list="$2"; shift 2 ;; - -h|--help) sed -n '2,6p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + -h|--help) sed -n '2,8p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; *) echo "docs-check: unknown argument '$1'" >&2; exit 2 ;; esac done -case "$list" in ""|properties|items|codes|diagnostics) ;; *) echo "docs-check: --list takes properties, items, codes or diagnostics" >&2; exit 2 ;; esac +case "$list" in ""|properties|items|codes|diagnostics|descriptors) ;; *) echo "docs-check: --list takes properties, items, codes, diagnostics or descriptors" >&2; exit 2 ;; esac [ -d "$root/kit/.toolkit/msbuild" ] || { echo "docs-check: no kit at $root/kit/.toolkit/msbuild" >&2; exit 2; } work=$(mktemp -d) @@ -26,6 +28,11 @@ cd "$root" # Kit inventory with XML comments removed, one process for every file: # "P " property set, "I" item, "R" property read, "C" diagnostic code, # "W " a or task ("-" for a missing attribute). +# Tab-separated, for the diagnostic catalog: "T " the same task, +# "B <MessageFormat> <Description> <Category> <DefaultSeverity> <HelpLink>" a +# BuildDiagnosticDescriptor item, "F <where> <item> <code> <metadata> <value>" an item named after a +# code (what a task with a computed Code reports), "X <where> <Project>" an import and +# "Q <file> <name> <value>" a one-line private property (a task text kept in one). find kit/.toolkit/msbuild -type f \( -name '*.props' -o -name '*.targets' -o -name '*.cs.txt' \) -exec awk ' FNR == 1 { incomment = 0; pg = 0; ig = 0; inel = 0 } { sub(/\r$/, "") } @@ -53,12 +60,44 @@ find kit/.toolkit/msbuild -type f \( -name '*.props' -o -name '*.targets' -o -na rest = out while (match(rest, /\$\(MSKit_[A-Za-z0-9_]+/)) { print "R", substr(rest, RSTART + 2, RLENGTH - 2), FILENAME ":" FNR; rest = substr(rest, RSTART + RLENGTH) } rest = out - while (match(rest, /MSKIT[A-Z]+[0-9][0-9][0-9]/)) { print "C", substr(rest, RSTART, RLENGTH), FILENAME ":" FNR; rest = substr(rest, RSTART + RLENGTH) } - if (!inel && (match(out, /<(Warning|Error)[[:space:]\/>]/) || match(out, /<(Warning|Error)$/))) { inel = 1; el = substr(out, RSTART); elat = FILENAME ":" FNR } - else if (inel) el = el " " out - if (inel && (e = closed(el))) { el = substr(el, 1, e); print "W", elat, attr(el, "Code"), attr(el, "HelpLink"); inel = 0 } + while (FILENAME !~ /diagnostic\.descriptors\.props$/ && match(rest, /MSKIT[A-Z]+[0-9][0-9][0-9]/)) { print "C", substr(rest, RSTART, RLENGTH), FILENAME ":" FNR; rest = substr(rest, RSTART + RLENGTH) } + if (match(out, /<_MSKit_[A-Za-z0-9_]+>[^<]+<\/_MSKit_[A-Za-z0-9_]+>/)) { + q = substr(out, RSTART + 1, RLENGTH - 1); name = substr(q, 1, index(q, ">") - 1); q = substr(q, index(q, ">") + 1); sub(/<\/.*$/, "", q) + print "Q " FILENAME " " name " " q + } + rest = out + if (inel) { el = el " " rest; rest = "" } + while (1) { + if (!inel) { if (!match(rest, /<[A-Za-z_][A-Za-z0-9_.]*/)) break; el = substr(rest, RSTART); rest = ""; elat = FILENAME ":" FNR; inel = 1 } + e = closed(el); if (!e) break + rest = substr(el, e + 1); el = substr(el, 1, e); inel = 0; element(el, elat) + } + } + function element(el, at, tag, code, a, name) { + match(el, /^<[A-Za-z_][A-Za-z0-9_.]*/); tag = substr(el, 2, RLENGTH - 1); code = attr(el, "Include") + if (tag == "Warning" || tag == "Error") { + print "W", at, attr(el, "Code"), attr(el, "HelpLink") + print "T " at " " tag " " attr(el, "Code") " " attr(el, "Text") + } + else if (tag == "BuildDiagnosticDescriptor") + print "B " at " " code " " attr(el, "Title") " " attr(el, "MessageFormat") " " attr(el, "Description") " " attr(el, "Category") " " attr(el, "DefaultSeverity") " " attr(el, "HelpLink") + else if (tag == "Import") print "X " at " " attr(el, "Project") + else if (code ~ /^MSKIT[A-Z]+[0-9][0-9][0-9]$/) + while (match(el, /[[:space:]][A-Za-z_][A-Za-z0-9_]*="[^"]*"/)) { + a = substr(el, RSTART + 1, RLENGTH - 2); el = substr(el, RSTART + RLENGTH); name = substr(a, 1, index(a, "=") - 1) + print "F " at " " tag " " code " " name " " substr(a, index(a, "=") + 2) + } + } + function closed(s, at, q, e) { + at = 0 + while (1) { + q = index(s, "\""); e = index(s, ">") + if (!e) return 0 + if (!q || e < q) return at + e + at += q; s = substr(s, q + 1); q = index(s, "\""); if (!q) return 0 + at += q; s = substr(s, q + 1) + } } - function closed(s, i, c, q) { for (i = 1; i <= length(s); i++) { c = substr(s, i, 1); if (c == "\"") q = !q; else if (c == ">" && !q) return i }; return 0 } function attr(s, name, v) { if (!match(s, "[[:space:]]" name "=\"[^\"]*\"")) return "-" v = substr(s, RSTART, RLENGTH); sub(/^[[:space:]]*[A-Za-z]+="/, "", v); sub(/"$/, "", v) @@ -74,6 +113,7 @@ awk -v dir="$work" ' la = a; sub(/^.*:/, "", la); lb = b; sub(/^.*:/, "", lb) return fa < fb || (fa == fb && la + 0 < lb + 0) } + /^[TBFXQ]\t/ { print > (dir "/elements"); next } $1 == "W" { print $2 "\t" $3 "\t" $4 > (dir "/diagnostics"); next } $1 == "C" { if (better($3, c[$2])) c[$2] = $3; next } $1 == "I" { if (better($3, i[$2])) i[$2] = $3; next } @@ -85,7 +125,9 @@ awk -v dir="$work" ' for (n in set) print n "\t" set[n] "\t" (n in read ? "set, read" : "set") > (dir "/properties") for (n in read) if (!(n in set)) print n "\t" read[n] "\tread" > (dir "/properties") }' "$work/raw" -for k in properties items codes diagnostics; do touch "$work/$k"; sort -o "$work/$k" "$work/$k"; done +touch "$work/elements" +awk -F'\t' '$1 == "B" { part = $2; sub(/^kit\/\.toolkit\/msbuild\//, "", part); if (!sub(/\/.*$/, "", part)) part = "-"; print $3 "\t" part "\t" $2 }' "$work/elements" > "$work/descriptors" +for k in properties items codes diagnostics descriptors; do touch "$work/$k"; sort -o "$work/$k" "$work/$k"; done if [ -n "$list" ]; then cat "$work/$list"; exit 0; fi @@ -120,9 +162,17 @@ awk -v dir="$work" ' split($0, cell, "|"); c = cell[2] while (match(c, /`[^`]+`/)) { print "D\t" f "\t" substr(c, RSTART + 1, RLENGTH - 2); c = substr(c, RSTART + RLENGTH) } } + f == "docs/reference/codes.md" && /^##[[:space:]]/ { family = $0; sub(/^##[[:space:]]+/, "", family); section = "" } f == "docs/reference/codes.md" && /^#+[[:space:]]+`?MSKIT[_]?[A-Z]+[0-9][0-9][0-9]/ { match($0, /MSKIT[_]?[A-Z]+[0-9][0-9][0-9]/); c = substr($0, RSTART, RLENGTH) print (c ~ /^MSKIT[_]/ ? "U\t" f ":" FNR "\t" c : "D\t" f "\t" c) + section = c; print "K\t" c "\tfamily\t" family + } + f == "docs/reference/codes.md" && section != "" && /^\*\*.*\*\*$/ && !((section, "title") in sectionhas) { + sectionhas[section, "title"] = 1; t = substr($0, 3, length($0) - 4); gsub(/`/, "", t); print "K\t" section "\ttitle\t" t + } + f == "docs/reference/codes.md" && section != "" && index($0, "`" section "`") == 1 && !((section, "lead") in sectionhas) { + sectionhas[section, "lead"] = 1; print "K\t" section "\tlead\t" $0 } { line = $0; gsub(/`[^`]*`/, "", line) while (match(line, /\]\([^) ]+\)/)) { print "L\t" f "\t" FNR "\t" d "\t" substr(line, RSTART + 2, RLENGTH - 3); line = substr(line, RSTART + RLENGTH) } } @@ -145,6 +195,43 @@ awk -v dir="$work" -F'\t' ' return out } function problem(m) { print "docs-check: " m; problems++ } + function fileof(w) { sub(/:[0-9]+$/, "", w); return w } + function partof(w) { sub(/^kit\/\.toolkit\/msbuild\//, "", w); return sub(/\/.*$/, "", w) ? w : "" } + function unxml(t) { gsub(/</, "<", t); gsub(/>/, ">", t); gsub(/"/, "\"", t); gsub(/'/, "\047", t); gsub(/&/, "\\&", t); return t } + function squeeze(t) { gsub(/[[:space:]]+/, " ", t); sub(/^ /, "", t); sub(/ $/, "", t); return t } + # A task text with every MSBuild expression as {}, and a message format with every {n} as {}. + function skeleton(t, res, ch, depth) { + res = "" + while (match(t, /[$@%]\(/)) { + res = res substr(t, 1, RSTART - 1) "{}"; t = substr(t, RSTART + 1); depth = 0 + while (match(t, /[()]/)) { ch = substr(t, RSTART, 1); t = substr(t, RSTART + 1); if (ch == "(") depth++; else if (--depth == 0) break } + if (depth) t = "" + } + return squeeze(unxml(res t)) + } + function formatskeleton(t) { gsub(/[{][0-9]+[}]/, "\001", t); gsub(/[{][{]/, "{", t); gsub(/[}][}]/, "}", t); gsub(/\001/, "{}", t); return squeeze(unxml(t)) } + # Markdown as the plain text a descriptor carries: no code ticks, no bold, a link as its text. + function plain(t, label) { + gsub(/`/, "", t); gsub(/\*\*/, "", t) + while (match(t, /\[[^]]*\]\([^)]*\)/)) { label = substr(t, RSTART + 1, RLENGTH - 1); label = substr(label, 1, index(label, "](") - 1); t = substr(t, 1, RSTART - 1) label substr(t, RSTART + RLENGTH) } + return squeeze(t) + } + function unescape(t) { gsub(/%24/, "$", t); gsub(/%40/, "@", t); gsub(/%3[Bb]/, ";", t); gsub(/%25/, "%", t); return t } + function reports(code, w, kind, text, f) { + f = fileof(w) + if (!(code in rpart)) { rcode[++nr] = code; rwhere[code] = w } + rpart[code] = rpart[code] "|" partof(f) "|"; rkind[code] = rkind[code] "|" kind "|" + if (text ~ /^\$\(_MSKit_[A-Za-z0-9_]+\)$/ && ((f, substr(text, 3, length(text) - 3)) in private)) text = private[f, substr(text, 3, length(text) - 3)] + rtext[code, ++rtexts[code]] = skeleton(text) + } + FILENAME == dir "/elements" { + if ($1 == "T") { twhere[++nt] = $2; tkind[nt] = $3; tcode[nt] = $4; ttext[nt] = $5 } + else if ($1 == "B") { bwhere[++nb] = $2; bcode[nb] = $3; btitle[nb] = $4; bformat[nb] = $5; bdescription[nb] = $6; bcategory[nb] = $7; bseverity[nb] = $8; blink[nb] = $9 } + else if ($1 == "F") { finding[fileof($2), $3, $4, $5] = $6; if (!((fileof($2), $3, $4) in findingseen)) { findingseen[fileof($2), $3, $4] = 1; findingcodes[fileof($2), $3] = findingcodes[fileof($2), $3] " " $4 } } + else if ($1 == "X") imports[fileof($2)] = imports[fileof($2)] "|" $3 "|" + else if ($1 == "Q") private[$2, $3] = $4 + next + } FILENAME == dir "/properties" || FILENAME == dir "/items" { known[$1] = $2; what[$1] = (FILENAME == dir "/items" ? "item" : "property"); order[++n] = $1; next } FILENAME == dir "/codes" { code[$1] = $2; corder[++nc] = $1; next } FILENAME == dir "/diagnostics" { dwhere[++nd] = $1; dcode[nd] = $2; dlink[nd] = $3; next } @@ -157,6 +244,7 @@ awk -v dir="$work" -F'\t' ' $1 == "U" { problem($2 ": write the heading as " gensub_id($3) " so its anchor is the code id without the underscore"); next } function gensub_id(c) { sub(/_/, "", c); return c } $1 == "L" { links[++nl] = $0; next } + $1 == "K" { doc[$2, $3] = $4; next } END { for (i = 1; i <= n; i++) if (!(order[i] in documented)) problem(what[order[i]] " " order[i] " (" known[order[i]] ") has no row in docs/reference/properties.md") for (i = 1; i <= nc; i++) if (!(corder[i] in documentedCode)) problem("code " corder[i] " (" code[corder[i]] ") has no section in docs/reference/codes.md") @@ -170,6 +258,61 @@ awk -v dir="$work" -F'\t' ' else if (h != want) problem(dwhere[i] ": " c " has HelpLink=\"" h "\"; expected \"" want "\"") if (anchor != "" && !(("docs/reference/codes.md#" anchor) in slug)) problem(dwhere[i] ": HelpLink anchor #" anchor " has no heading in docs/reference/codes.md") } + # The diagnostic catalog: one BuildDiagnosticDescriptor per code, in the part that reports it. + for (i = 1; i <= nt; i++) { + c = tcode[i]; src = fileof(twhere[i]) + if (c ~ /^MSKIT[A-Z]+[0-9][0-9][0-9]$/) reports(c, twhere[i], tkind[i], ttext[i]) + else if (c ~ /^%\([A-Za-z_][A-Za-z0-9_]*\.Identity\)$/) { + item = substr(c, 3, length(c) - 12) + if (!((src, item) in findingcodes)) { problem(twhere[i] ": cannot tell which codes " c " reports; declare each as <" item " Include=\"MSKIT...\"> in the same file"); continue } + nf = split(findingcodes[src, item], fc, " ") + for (k = 1; k <= nf; k++) { + text = ttext[i] + while (match(text, "%\\(" item "\\.[A-Za-z_][A-Za-z0-9_]*\\)")) { + meta = substr(text, RSTART + length(item) + 3, RLENGTH - length(item) - 4) + text = substr(text, 1, RSTART - 1) (meta == "Identity" ? fc[k] : finding[src, item, fc[k], meta]) substr(text, RSTART + RLENGTH) + } + reports(fc[k], twhere[i], tkind[i], text) + } + } + } + for (i = 1; i <= nb; i++) { + c = bcode[i]; w = bwhere[i]; src = fileof(w); p = partof(src) + if (c !~ /^MSKIT[A-Z]+[0-9][0-9][0-9]$/) { problem(w ": a BuildDiagnosticDescriptor needs Include=\"MSKIT<FAMILY><nnn>\", not \"" c "\""); continue } + if (src !~ /\/diagnostic\.descriptors\.props$/ || p == "") problem(w ": declare " c " in its part folder, in diagnostic.descriptors.props") + if (c in described) { problem(w ": " c " already has a BuildDiagnosticDescriptor at " described[c]); continue } + described[c] = w; descriptorpart[p] = src + if (!(c in rpart)) { problem(w ": " c " has a BuildDiagnosticDescriptor, but no <Warning> or <Error> reports it"); continue } + if (!index(rpart[c], "|" p "|")) { owner = rpart[c]; gsub(/\|\|/, ", ", owner); gsub(/\|/, "", owner); problem(w ": " c " is described in part " p ", but reported by " owner "; move the item there") } + if (bseverity[i] != "Warning" && bseverity[i] != "Error") problem(w ": " c " has DefaultSeverity=\"" bseverity[i] "\"; use Warning or Error") + else if (!index(rkind[c], "|" bseverity[i] "|")) problem(w ": " c " has DefaultSeverity=\"" bseverity[i] "\", but no <" bseverity[i] "> reports it") + lead = doc[c, "lead"] + if (!match(lead, /\) \((warning|error)[,)]/)) problem("docs/reference/codes.md: the " c " section does not open with `" c "` (formerly ...) (warning) or (error)") + else if (tolower(bseverity[i]) != substr(lead, RSTART + 3, RLENGTH - 4)) problem(w ": " c " has DefaultSeverity=\"" bseverity[i] "\", but its section in docs/reference/codes.md says (" substr(lead, RSTART + 3, RLENGTH - 4) ")") + if (blink[i] != "$(MSKit_CodesHelpBaseUrl)#" tolower(c)) problem(w ": " c " has HelpLink=\"" blink[i] "\"; expected \"$(MSKit_CodesHelpBaseUrl)#" tolower(c) "\", as its task sets") + title = unescape(unxml(btitle[i])) + if (btitle[i] == "-") problem(w ": " c " has no Title") + else if (!((c, "title") in doc)) problem("docs/reference/codes.md: the " c " section has no **title** line; the descriptor says \"" title "\"") + else if (title != doc[c, "title"]) problem(w ": " c " has Title=\"" title "\", but its section in docs/reference/codes.md is titled \"" doc[c, "title"] "\"") + if (bcategory[i] != doc[c, "family"]) problem(w ": " c " has Category=\"" bcategory[i] "\", but its section in docs/reference/codes.md is under \"" doc[c, "family"] "\"") + body = (index(lead, " — ") ? plain(substr(lead, index(lead, " — ") + length(" — "))) : ""); d = squeeze(unescape(unxml(bdescription[i]))) + if (bdescription[i] == "-") problem(w ": " c " has no Description") + else if (tolower(substr(d, 1, 1)) != tolower(substr(body, 1, 1)) || substr(d, 2) != substr(body, 2, length(d) - 1) \ + || (length(d) < length(body) && (d !~ /\.$/ || substr(body, length(d) + 1, 1) != " "))) + problem(w ": the Description of " c " is not the opening sentence(s) of its section in docs/reference/codes.md") + ok = 0; for (k = 1; k <= rtexts[c]; k++) if (formatskeleton(bformat[i]) == rtext[c, k]) ok = 1 + if (bformat[i] == "-") problem(w ": " c " has no MessageFormat") + else if (!ok) problem(w ": the MessageFormat of " c " is not the text its task reports, with {0}, {1}, ... for the runtime values; expected the shape \"" rtext[c, 1] "\"") + if ((btitle[i] bformat[i] bdescription[i] bcategory[i]) ~ /[$@%]\(/) problem(w ": " c " has metadata MSBuild would expand; write $( as %24(, @( as %40( and %( as %25(") + } + for (i = 1; i <= nr; i++) if (!(rcode[i] in described)) { + p = partof(fileof(rwhere[rcode[i]])) + problem("code " rcode[i] " (" rwhere[rcode[i]] ") has no BuildDiagnosticDescriptor item; add one to kit/.toolkit/msbuild/" p "/diagnostic.descriptors.props") + } + for (p in descriptorpart) { + if (!index(imports["kit/.toolkit/msbuild/" p "/init.props"], "diagnostic.descriptors.props|")) problem(descriptorpart[p] " is not imported by kit/.toolkit/msbuild/" p "/init.props") + if (!index(imports["kit/.toolkit/msbuild/init.props"], ")" p "/init.props|")) problem("kit/.toolkit/msbuild/init.props does not import " p "/init.props, so the descriptors of " p " never load") + } relative = 0 for (i = 1; i <= nl; i++) { split(links[i], l, "\t"); src = l[2]; ln = l[3]; base = l[4]; target = l[5] @@ -186,5 +329,5 @@ awk -v dir="$work" -F'\t' ' if (anchor != "" && dest ~ /\.md$/ && !((dest "#" anchor) in slug)) problem(src ":" ln ": link " target " names a heading that does not exist") } if (problems) { print "docs-check: " problems " problem(s)"; exit 1 } - printf "docs-check: %d properties and items, %d codes documented, %d diagnostics with a HelpLink; %d relative links resolve\n", n, nc, nd, relative - }' "$work/properties" "$work/items" "$work/codes" "$work/diagnostics" "$work/oldspelling" "$work/paths" "$work/docs.tsv" + printf "docs-check: %d properties and items, %d codes documented, %d diagnostics with a HelpLink, %d described in the catalog; %d relative links resolve\n", n, nc, nd, nb, relative + }' "$work/properties" "$work/items" "$work/codes" "$work/diagnostics" "$work/elements" "$work/oldspelling" "$work/paths" "$work/docs.tsv" From 3cf1b2ed66d3fa4b33d05149a964ef783bd17fab Mon Sep 17 00:00:00 2001 From: VasiliyF <5789590+vfofanov@users.noreply.github.com> Date: Tue, 6 Oct 2026 14:32:19 +0200 Subject: [PATCH 2/4] Declare every code as a BuildDiagnosticDescriptor item in its part - one diagnostic.descriptors.props per part that reports a code, imported by the part's init.props; PackageAsProj gets an init.props for it - codes.md: each section opens with the code's title, and the PKG001-019 sections name their severity --- docs/reference/codes.md | 154 ++++++++++++++--- .../diagnostic.descriptors.props | 35 ++++ .../DragoAnt.MSBuildKit.Core/init.props | 2 + .../diagnostic.descriptors.props | 21 +++ .../init.props | 5 + .../diagnostic.descriptors.props | 161 ++++++++++++++++++ .../DragoAnt.MSBuildKit.Packaging/init.props | 2 + .../diagnostic.descriptors.props | 14 ++ .../init.props | 2 + .../diagnostic.descriptors.props | 91 ++++++++++ .../DragoAnt.MSBuildKit.Testing/init.props | 2 + .../diagnostic.descriptors.props | 126 ++++++++++++++ .../msbuild/DragoAnt.MSBuildKit/init.props | 2 + kit/.toolkit/msbuild/init.props | 1 + 14 files changed, 599 insertions(+), 19 deletions(-) create mode 100644 kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/diagnostic.descriptors.props create mode 100644 kit/.toolkit/msbuild/DragoAnt.MSBuildKit.PackageAsProj/diagnostic.descriptors.props create mode 100644 kit/.toolkit/msbuild/DragoAnt.MSBuildKit.PackageAsProj/init.props create mode 100644 kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/diagnostic.descriptors.props create mode 100644 kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Testing.XUnit.v3/diagnostic.descriptors.props create mode 100644 kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Testing/diagnostic.descriptors.props create mode 100644 kit/.toolkit/msbuild/DragoAnt.MSBuildKit/diagnostic.descriptors.props diff --git a/docs/reference/codes.md b/docs/reference/codes.md index e5add78..7fd57d0 100644 --- a/docs/reference/codes.md +++ b/docs/reference/codes.md @@ -20,90 +20,134 @@ Earlier releases put an underscore between the prefix and the family; each secti ### MSKITPKG001 -`MSKITPKG001` (formerly `MSKIT_PKG001`) — the package has no real `Description`: it is empty, the SDK's `Package Description`, the package id or the project name. Write one or two sentences on what the package does and what sets it apart; nuget.org search shows it first. +**Package has no real `Description`** + +`MSKITPKG001` (formerly `MSKIT_PKG001`) (warning, error on CI) — the package has no real `Description`: it is empty, the SDK's `Package Description`, the package id or the project name. Write one or two sentences on what the package does and what sets it apart; nuget.org search shows it first. ### MSKITPKG002 -`MSKITPKG002` (formerly `MSKIT_PKG002`) — `Description` is shorter than `MSKit_PackageDescriptionMinLength` (30 characters). Say what it does and for whom, or lower the bar. +**Package `Description` is too short** + +`MSKITPKG002` (formerly `MSKIT_PKG002`) (warning, error on CI) — `Description` is shorter than `MSKit_PackageDescriptionMinLength` (30 characters). Say what it does and for whom, or lower the bar. ### MSKITPKG003 -`MSKITPKG003` (formerly `MSKIT_PKG003`) — no README is packed. Add `package.readme.md` (or `README.md`) next to the csproj, or set `MSKit_PackageReadmeFrom`. +**Package has no README** + +`MSKITPKG003` (formerly `MSKIT_PKG003`) (warning, error on CI) — no README is packed. Add `package.readme.md` (or `README.md`) next to the csproj, or set `MSKit_PackageReadmeFrom`. ### MSKITPKG004 -`MSKITPKG004` (formerly `MSKIT_PKG004`) — no `PackageTags`. Add a few search terms that are not already in the package id. +**Package has no `PackageTags`** + +`MSKITPKG004` (formerly `MSKIT_PKG004`) (warning, error on CI) — no `PackageTags`. Add a few search terms that are not already in the package id. ### MSKITPKG005 -`MSKITPKG005` (formerly `MSKIT_PKG005`) — no icon. Set `PackageIconPath` to a 128×128 PNG or JPEG, packed as `icon<extension, lowercased>`; the owner layer sets one for every package. +**Package has no icon** + +`MSKITPKG005` (formerly `MSKIT_PKG005`) (warning, error on CI) — no icon. Set `PackageIconPath` to a 128×128 PNG or JPEG, packed as `icon<extension, lowercased>`; the owner layer sets one for every package. ### MSKITPKG006 -`MSKITPKG006` (formerly `MSKIT_PKG006`) — no licence. Set `PackageLicenseExpression` to an [SPDX id](https://spdx.org/licenses/) or `PackageLicenseFile`. +**Package has no licence** + +`MSKITPKG006` (formerly `MSKIT_PKG006`) (warning, error on CI) — no licence. Set `PackageLicenseExpression` to an [SPDX id](https://spdx.org/licenses/) or `PackageLicenseFile`. ### MSKITPKG007 -`MSKITPKG007` (formerly `MSKIT_PKG007`) — the deprecated `PackageLicenseUrl` is set. Use `PackageLicenseExpression` or `PackageLicenseFile`. +**`PackageLicenseUrl` is deprecated** + +`MSKITPKG007` (formerly `MSKIT_PKG007`) (warning, error on CI) — the deprecated `PackageLicenseUrl` is set. Use `PackageLicenseExpression` or `PackageLicenseFile`. ### MSKITPKG008 -`MSKITPKG008` (formerly `MSKIT_PKG008`) — the project sets the deprecated `PackageIconUrl`. Pack the image and use `PackageIcon` or `PackageIconPath`. To keep a URL for older clients, set `MSKit_DefaultPackageIconUrl` instead: the kit writes it only next to its own embedded icon, and that one is not reported. +**`PackageIconUrl` is deprecated** + +`MSKITPKG008` (formerly `MSKIT_PKG008`) (warning, error on CI) — the project sets the deprecated `PackageIconUrl`. Pack the image and use `PackageIcon` or `PackageIconPath`. To keep a URL for older clients, set `MSKit_DefaultPackageIconUrl` instead: the kit writes it only next to its own embedded icon, and that one is not reported. ### MSKITPKG009 -`MSKITPKG009` (formerly `MSKIT_PKG009`) — the package README has relative images, which nuget.org does not render. Use absolute `https` URLs from an [allowed host](https://learn.microsoft.com/nuget/nuget-org/package-readme-on-nuget-org#allowed-domains-for-images-and-badges), or generate the readme with `MSKit_PackageReadmeFrom`. +**Package README has relative images** + +`MSKITPKG009` (formerly `MSKIT_PKG009`) (warning, error on CI) — the package README has relative images, which nuget.org does not render. Use absolute `https` URLs from an [allowed host](https://learn.microsoft.com/nuget/nuget-org/package-readme-on-nuget-org#allowed-domains-for-images-and-badges), or generate the readme with `MSKit_PackageReadmeFrom`. ### MSKITPKG010 -`MSKITPKG010` (formerly `MSKIT_PKG010`) — the package README contains HTML, which nuget.org does not render. Use Markdown. +**Package README contains HTML** + +`MSKITPKG010` (formerly `MSKIT_PKG010`) (warning, error on CI) — the package README contains HTML, which nuget.org does not render. Use Markdown. ### MSKITPKG011 -`MSKITPKG011` (formerly `MSKIT_PKG011`) — the package README uses GitHub alerts (`> [!NOTE]`), which nuget.org shows as plain quotes. Use a bold lead-in such as `**Note:**`. +**Package README uses GitHub alerts** + +`MSKITPKG011` (formerly `MSKIT_PKG011`) (warning, error on CI) — the package README uses GitHub alerts (`> [!NOTE]`), which nuget.org shows as plain quotes. Use a bold lead-in such as `**Note:**`. ### MSKITPKG012 -`MSKITPKG012` (formerly `MSKIT_PKG012`) — the package README loads images from a host nuget.org blocks. Host them on an allowed domain (`img.shields.io`, `raw.githubusercontent.com`, …). +**Package README loads images from a blocked host** + +`MSKITPKG012` (formerly `MSKIT_PKG012`) (warning, error on CI) — the package README loads images from a host nuget.org blocks. Host them on an allowed domain (`img.shields.io`, `raw.githubusercontent.com`, …). ### MSKITPKG013 -`MSKITPKG013` (formerly `MSKIT_PKG013`) — the package version is not [SemVer 2.0](https://semver.org/) (`MSKit_SemVerRegex`). +**Package version is not SemVer 2.0** + +`MSKITPKG013` (formerly `MSKIT_PKG013`) (warning, error on CI) — the package version is not [SemVer 2.0](https://semver.org/) (`MSKit_SemVerRegex`). ### MSKITPKG014 -`MSKITPKG014` (formerly `MSKIT_PKG014`) — no repository or project URL. Set `RepositoryUrl`, or build from a git clone whose `origin` remote Source Link can read. +**Package has no repository or project URL** + +`MSKITPKG014` (formerly `MSKIT_PKG014`) (warning, error on CI) — no repository or project URL. Set `RepositoryUrl`, or build from a git clone whose `origin` remote Source Link can read. ### MSKITPKG015 -`MSKITPKG015` (formerly `MSKIT_PKG015`) — the icon is not a PNG or JPEG of `MSKit_PackageIconSize` × `MSKit_PackageIconSize` pixels (128); the size is checked on both formats. +**Package icon has the wrong format or size** + +`MSKITPKG015` (formerly `MSKIT_PKG015`) (warning, error on CI) — the icon is not a PNG or JPEG of `MSKit_PackageIconSize` × `MSKit_PackageIconSize` pixels (128); the size is checked on both formats. ### MSKITPKG016 -`MSKITPKG016` (formerly `MSKIT_PKG016`) — no `PackageReleaseNotes`. The kit fills them on `github.com`, and on any host the generated readme knows with `MSKit_PackageReadmeFrom`; elsewhere set them (a link to the changelog is enough). +**Package has no `PackageReleaseNotes`** + +`MSKITPKG016` (formerly `MSKIT_PKG016`) (warning, error on CI) — no `PackageReleaseNotes`. The kit fills them on `github.com`, and on any host the generated readme knows with `MSKit_PackageReadmeFrom`; elsewhere set them (a link to the changelog is enough). ### MSKITPKG017 -`MSKITPKG017` (formerly `MSKIT_PKG017`) — the package README has relative links, which break on nuget.org. Use absolute URLs, or generate the readme with `MSKit_PackageReadmeFrom`. +**Package README has relative links** + +`MSKITPKG017` (formerly `MSKIT_PKG017`) (warning, error on CI) — the package README has relative links, which break on nuget.org. Use absolute URLs, or generate the readme with `MSKit_PackageReadmeFrom`. ### MSKITPKG018 -`MSKITPKG018` (formerly `MSKIT_PKG018`) — the package has an open-source licence expression, but its `Copyright` says "all rights reserved". Use `Copyright (c) YEAR OWNER`. +**`Copyright` contradicts the open-source licence** + +`MSKITPKG018` (formerly `MSKIT_PKG018`) (warning, error on CI) — the package has an open-source licence expression, but its `Copyright` says "all rights reserved". Use `Copyright (c) YEAR OWNER`. ### MSKITPKG019 -`MSKITPKG019` (formerly `MSKIT_PKG019`) — the package README contains a Mermaid diagram, which nuget.org shows as code. Link to the diagram on the repository host instead. +**Package README contains a Mermaid diagram** + +`MSKITPKG019` (formerly `MSKIT_PKG019`) (warning, error on CI) — the package README contains a Mermaid diagram, which nuget.org shows as code. Link to the diagram on the repository host instead. ### MSKITPKG020 +**Package readme cannot be generated as asked** + `MSKITPKG020` (formerly `MSKIT_PKG020`) (warning) — the readme cannot be generated as asked: the `MSKit_PackageReadmeFrom` file is missing, a `nuget:skip` / `nuget:only` marker is unbalanced, a link leaves the repository, or links cannot be rewritten (no repository URL, an unknown host or provider, no commit). The message names the line. ### MSKITPKG021 +**Generated readme loads an image from a blocked host** + `MSKITPKG021` (formerly `MSKIT_PKG021`) (warning) — a generated readme loads an image from a host nuget.org does not render images from; the message names the image and its README line. ### MSKITPKG022 +**Repository is private or internal** + `MSKITPKG022` (formerly `MSKIT_PKG022`) (warning) — the repository is private or internal (`MSKit_RepositoryVisibility`, else GitLab's `CI_PROJECT_VISIBILITY`), so the readme's links will not open for package readers. ## Versioning @@ -112,22 +156,32 @@ Background: [Versioning](../versioning.md). ### MSKITVER001 +**`Version` is declared in the project** + `MSKITVER001` (formerly `MSKIT_VER001`) (error) — the csproj declares `<Version>` while a template strategy renders the version, so the value would be ignored. Remove it and declare `VersionPrefix` in `Directory.Version.props`, or set `MSKit_VersionStrategy=Manual`. ### MSKITVER002 +**Version template has an unknown placeholder** + `MSKITVER002` (formerly `MSKIT_VER002`) (error) — a version template uses an unknown placeholder. The message lists the valid ones ([placeholders](../versioning.md#placeholders)). ### MSKITVER004 +**`VersionTag` is empty** + `MSKITVER004` (formerly `MSKIT_VER004`) (error) — `MSKit_VersionStrategy=VersionTag` but `VersionTag` is empty. Pass `-p:VersionTag=1.2.3`, or use `ReleaseTag`. ### MSKITVER006 +**Release tag is not SemVer 2.0** + `MSKITVER006` (formerly `MSKIT_VER006`) (error, stops restore) — a tag build whose tag, after removing a leading `v`, is not [SemVer 2.0](https://semver.org/) (`MSKit_ReleaseTagRegex`). Delete the tag and its release and tag again (`v2.1.0`, `v2.1.0-beta.1`). ### MSKITVER007 +**Release tag differs from `VersionPrefix`** + `MSKITVER007` (formerly `MSKIT_VER007`) (warning) — the tag's `MAJOR.MINOR.PATCH` differs from the `VersionPrefix` the repository declares. Bump `VersionPrefix` after the release so branch builds sort above it; `MSKit_SkipAudit_ReleaseTagPrefix=True` silences it. ## References @@ -136,26 +190,38 @@ Background: [reference checks](../build.md#reference-checks), [central package v ### MSKITDUP001 +**`PackageVersion` repeats one the kit provides** + `MSKITDUP001` (formerly `MSKIT_DUP001`) (error, stops restore) — `Directory.Packages.props` declares a `PackageVersion` the kit already provides. Delete the line; to pin another version set the kit's `MSKit_PackageVersion_*` property, or turn the kit's versions off with `MSKit_ImplicitPackageVersions=False`. `MSKit_SkipAudit_ImplicitPackageDuplicates=True` skips the check. ### MSKITPRE001 +**Prerelease package on a stable branch** + `MSKITPRE001` (formerly `MSKIT_PRE001`) (warning) — a stable-branch build references a prerelease version of a package whose id starts with `MSKit_PrereleasePackagePrefix`. Use a stable version; `MSKit_PrereleasePackageCheckAsWarning=false` makes it an error. ### MSKITRES001 +**Package reference is prohibited** + `MSKITRES001` (formerly `MSKIT_RES001`) (error) — a referenced package is banned by an `MSKit_RestrictPackageReference` item with `Type="Error"`. Remove it or use the suggested alternative; `SkipGlobalRestriction="True"` on the one `PackageReference` turns it into a warning. ### MSKITRES002 +**Package reference is discouraged** + `MSKITRES002` (formerly `MSKIT_RES002`) (warning) — a referenced package is discouraged by an `MSKit_RestrictPackageReference` item with `Type="Warning"`. `SkipGlobalRestriction="True"` on the reference silences it. ### MSKITRES003 +**Project reference is not allowed** + `MSKITRES003` (formerly `MSKIT_RES003`) (error) — with `MSKit_RestrictProjectReferences=True` (or `MSKit_RestrictReferences=True`), a `ProjectReference` lacks `Allowed="True"`. ### MSKITRES004 +**Package reference is not allowed** + `MSKITRES004` (formerly `MSKIT_RES004`) (error) — with `MSKit_RestrictPackageReferences=True` (or `MSKit_RestrictReferences=True`), a `PackageReference` lacks `Allowed="True"`. ## Shared properties @@ -164,26 +230,38 @@ Background: [target frameworks declared once](../build.md#target-frameworks-decl ### MSKITSHARED006 +**`TargetFramework` repeats the shared value** + `MSKITSHARED006` (formerly `MSKIT_SHARED006`) (error) — the csproj declares the same `TargetFramework` as `Directory.Build.props`. Delete it from the csproj. ### MSKITSHARED007 +**`TargetFrameworks` repeats the shared value** + `MSKITSHARED007` (formerly `MSKIT_SHARED007`) (error) — the csproj declares the same `TargetFrameworks` as `Directory.Build.props`. Delete it from the csproj. ### MSKITSHARED008 +**`TargetFramework` overrides the shared value** + `MSKITSHARED008` (formerly `MSKIT_SHARED008`) (warning) — the csproj overrides the shared `TargetFramework` with another value. Remove it, or accept it with `MSKit_SkipAudit_TargetFrameworkOverride=True` in the csproj. ### MSKITSHARED009 +**`TargetFrameworks` overrides the shared value** + `MSKITSHARED009` (formerly `MSKIT_SHARED009`) (warning) — the csproj overrides the shared `TargetFrameworks` with another value. Remove it, or accept it with `MSKit_SkipAudit_TargetFrameworkOverride=True`. ### MSKITSHARED010 +**Both `TargetFramework` and `TargetFrameworks` are declared** + `MSKITSHARED010` (formerly `MSKIT_SHARED010`) (error) — the csproj declares both `TargetFramework` and `TargetFrameworks` (an empty `<TargetFramework></TargetFramework>` counts). Keep one. ### MSKITSHARED020 +**`TreatWarningsAsErrors` differs from the shared value** + `MSKITSHARED020` (formerly `MSKIT_SHARED020`) (error, developer machines only) — the csproj's final `TreatWarningsAsErrors` differs from the shared value. Align it, or set `MSKit_SkipAudit_TreatWarningsAsErrors=True`. ## Project types @@ -192,18 +270,26 @@ Background: [Roslyn components](../roslyn.md). ### MSKITCORE001 +**Project name matches several project types** + `MSKITCORE001` (formerly `MSKIT_CORE001`) (error) — the project name matches more than one detection regex (analyzer, code fix, source generator). Rename the project, narrow a `MSKit_*ProjectNameRegex`, or set the matching `MSKit_Disable*AutoDetect=true` in `Directory.Build.props` above the kit import. ### MSKITROSLYN001 +**`Project.CodeAnalyzer` part is not installed** + `MSKITROSLYN001` (formerly `MSKIT_ROSLYN001`) (error) — the name matches the analyzer regex, but the `Project.CodeAnalyzer` part is not installed. `sh .toolkit/update.sh --add Project.CodeAnalyzer`, or rename the project, or set `MSKit_DisableCodeAnalyzerAutoDetect=true` in `Directory.Build.props` above the kit import. ### MSKITROSLYN002 +**`Project.CodeFixer` part is not installed** + `MSKITROSLYN002` (formerly `MSKIT_ROSLYN002`) (error) — the name matches the code-fix regex, but `Project.CodeFixer` is not installed. Add the part, rename, or set `MSKit_DisableCodeFixerAutoDetect=true` in `Directory.Build.props` above the kit import. ### MSKITROSLYN003 +**`Project.SourceGenerator` part is not installed** + `MSKITROSLYN003` (formerly `MSKIT_ROSLYN003`) (error) — the name matches the source-generator regex, but `Project.SourceGenerator` is not installed. Add the part, rename, or set `MSKit_DisableSourceGeneratorAutoDetect=true` in `Directory.Build.props` above the kit import. ## Testing @@ -212,54 +298,80 @@ Background: [Testing](../testing.md). ### MSKITTEST005 +**xUnit v3 needs net8.0 or later** + `MSKITTEST005` (formerly `MSKIT_TEST005`) (error) — an xUnit v3 test project targets a framework older than net8.0. ### MSKITTEST010 +**Test project props imported before `MSKit_TestingFramework` is set** + `MSKITTEST010` (formerly `MSKIT_TEST010`) (error) — `$(TestsProjectCommonPropsPath)` was imported before `MSKit_TestingFramework` was set. Put the `PropertyGroup` above the `Import`. ### MSKITTEST011 +**`MSKit_TestingFramework` changed after the test project props import** + `MSKITTEST011` (formerly `MSKIT_TEST011`) (error) — `MSKit_TestingFramework` changed after `$(TestsProjectCommonPropsPath)` was imported. Move the `PropertyGroup` above the `Import`. ### MSKITTEST012 +**No wiring for the testing framework** + `MSKITTEST012` (formerly `MSKIT_TEST012`) (error) — no wiring exists for the `MSKit_TestingFramework` of an explicit test project. Use `xunit.v3`, or point `MSKit_TestingFramework_CommonPropsPath` at your own props file. ### MSKITTEST013 +**`IsTestsProject` is set in the project** + `MSKITTEST013` (formerly `MSKIT_TEST013`) (error) — the csproj sets `IsTestsProject` directly, too late for the props-phase wiring. Rename the project to match `MSKit_TestsProjectNameRegex`, or use the explicit endpoint `$(TestsProjectCommonPropsPath)`. ### MSKITTEST014 +**Test project is marked as a test helper library** + `MSKITTEST014` (formerly `MSKIT_TEST014`) (error) — the name matches the test-project regex, but the project is marked as a test helper library. Rename it (`Acme.TestUtils`), narrow the regex, or drop the helper-library flag. ### MSKITTEST020 +**Test library props imported before `MSKit_TestingFramework` is set** + `MSKITTEST020` (formerly `MSKIT_TEST020`) (error) — `$(TestsLibProjectCommonPropsPath)` was imported before `MSKit_TestingFramework` was set. ### MSKITTEST021 +**`MSKit_TestingFramework` changed after the test library props import** + `MSKITTEST021` (formerly `MSKIT_TEST021`) (error) — `MSKit_TestingFramework` changed after `$(TestsLibProjectCommonPropsPath)` was imported. ### MSKITTEST022 +**No helper-library wiring for the testing framework** + `MSKITTEST022` (formerly `MSKIT_TEST022`) (error) — no helper-library wiring exists for `MSKit_TestingFramework`. Use `xunit.v3`, or set `MSKit_TestingFramework_LibCommonPropsPath`. ### MSKITTEST025 +**`MSKit_TestingFramework` is not set** + `MSKITTEST025` (formerly `MSKIT_TEST025`) (error) — a test project or helper library has no `MSKit_TestingFramework`. Set it in `Directory.Build.props` (the owner layer sets `xunit.v3`). ### MSKITTEST026 +**No installed part wires the testing framework** + `MSKITTEST026` (formerly `MSKIT_TEST026`) (error) — no installed part wires the `MSKit_TestingFramework` value. Use `xunit.v3` (part `Testing.XUnit.v3`), or set `MSKit_TestingFramework_CommonPropsPath`. ### MSKITTEST030 +**`MSKit_TestsDir` is empty** + `MSKITTEST030` (formerly `MSKIT_TEST030`) (warning) — `MSKit_TestsDir` is empty, so `InternalsVisibleTo` cannot be added. Set it, or turn `InternalsVisibleToAllTestsProjects` off. ### MSKITTEST031 +**`MSKit_TestsDir` does not exist** + `MSKITTEST031` (formerly `MSKIT_TEST031`) (warning) — `MSKit_TestsDir` points at a folder that does not exist. ## PackageAsProj @@ -268,8 +380,12 @@ Background: [PackageAsProj](../optional-parts.md#packageasproj). ### MSKITPAP001 +**Package switched to a project is still resolved from the package** + `MSKITPAP001` (formerly `MSKIT_PAP001`) (error) — a package switched to a `ProjectReference` is still resolved from the package. Run `dotnet restore --force`. ### MSKITPAP002 +**Package switched back from a project is not restored** + `MSKITPAP002` (formerly `MSKIT_PAP002`) (error) — a package switched back from a project is not restored yet. Run `dotnet restore --force`, or set `PackageAsProj_SkipChecks=True`. diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/diagnostic.descriptors.props b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/diagnostic.descriptors.props new file mode 100644 index 0000000..4ef8828 --- /dev/null +++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/diagnostic.descriptors.props @@ -0,0 +1,35 @@ +<Project> + + <!-- The codes this part reports, one item each, for tools that read a build's diagnostics. --> + <ItemGroup> + <BuildDiagnosticDescriptor Include="MSKITCORE001" + Title="Project name matches several project types" + MessageFormat="Project '{0}' auto-detected as MULTIPLE project types: {1}.%0AA project can be exactly ONE type.%0AFix ONE of:%0A 1. Rename the project so its name matches only ONE auto-detect regex%0A 2. Override the conflicting MSKit_*ProjectNameRegex in Directory.Build.props to exclude this project name%0A 3. Set the corresponding MSKit_Disable*AutoDetect=true in the csproj and import the explicit endpoint instead (e.g. MSKit_DisableCodeAnalyzerAutoDetect=true + Import "{2}")" + Description="The project name matches more than one detection regex (analyzer, code fix, source generator)." + Category="Project types" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitcore001" /> + <BuildDiagnosticDescriptor Include="MSKITROSLYN001" + Title="Project.CodeAnalyzer part is not installed" + MessageFormat="Project '{0}' name matches the CodeAnalyzer auto-detect regex '{1}' but the kit part 'DragoAnt.MSBuildKit.Project.CodeAnalyzer' is not installed.%0AFix: pwsh .toolkit/update.ps1 -Add Project.CodeAnalyzer (or: sh .toolkit/update.sh --add Project.CodeAnalyzer)%0AOr, if this project is not a Roslyn analyzer, rename it or set MSKit_DisableCodeAnalyzerAutoDetect=true in the csproj." + Description="The name matches the analyzer regex, but the Project.CodeAnalyzer part is not installed." + Category="Project types" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitroslyn001" /> + <BuildDiagnosticDescriptor Include="MSKITROSLYN002" + Title="Project.CodeFixer part is not installed" + MessageFormat="Project '{0}' name matches the CodeFixer auto-detect regex '{1}' but the kit part 'DragoAnt.MSBuildKit.Project.CodeFixer' is not installed.%0AFix: pwsh .toolkit/update.ps1 -Add Project.CodeFixer (or: sh .toolkit/update.sh --add Project.CodeFixer)%0AOr, if this project is not a Roslyn code-fix provider, rename it or set MSKit_DisableCodeFixerAutoDetect=true in the csproj." + Description="The name matches the code-fix regex, but Project.CodeFixer is not installed." + Category="Project types" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitroslyn002" /> + <BuildDiagnosticDescriptor Include="MSKITROSLYN003" + Title="Project.SourceGenerator part is not installed" + MessageFormat="Project '{0}' name matches the SourceGenerator auto-detect regex '{1}' but the kit part 'DragoAnt.MSBuildKit.Project.SourceGenerator' is not installed.%0AFix: pwsh .toolkit/update.ps1 -Add Project.SourceGenerator (or: sh .toolkit/update.sh --add Project.SourceGenerator)%0AOr, if this project is not a Roslyn source generator, rename it or set MSKit_DisableSourceGeneratorAutoDetect=true in the csproj." + Description="The name matches the source-generator regex, but Project.SourceGenerator is not installed." + Category="Project types" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitroslyn003" /> + </ItemGroup> + +</Project> diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/init.props b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/init.props index edfb4ee..b51e803 100644 --- a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/init.props +++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/init.props @@ -7,4 +7,6 @@ <Import Project="$(MSBuildThisFileDirectory)core.locals.props" /> <Import Project="$(MSBuildThisFileDirectory)project-types.autodetect.props" /> + <Import Project="$(MSBuildThisFileDirectory)diagnostic.descriptors.props" /> + </Project> diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.PackageAsProj/diagnostic.descriptors.props b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.PackageAsProj/diagnostic.descriptors.props new file mode 100644 index 0000000..e8bf423 --- /dev/null +++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.PackageAsProj/diagnostic.descriptors.props @@ -0,0 +1,21 @@ +<Project> + + <!-- The codes this part reports, one item each, for tools that read a build's diagnostics. --> + <ItemGroup> + <BuildDiagnosticDescriptor Include="MSKITPAP001" + Title="Package switched to a project is still resolved from the package" + MessageFormat="A package switched to a ProjectReference is still resolved from the package. Fix: run 'dotnet restore --force'." + Description="A package switched to a ProjectReference is still resolved from the package." + Category="PackageAsProj" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpap001" /> + <BuildDiagnosticDescriptor Include="MSKITPAP002" + Title="Package switched back from a project is not restored" + MessageFormat="A package switched back from a ProjectReference is not restored yet. Fix: run 'dotnet restore --force', or set PackageAsProj_SkipChecks=True to skip this check." + Description="A package switched back from a project is not restored yet." + Category="PackageAsProj" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpap002" /> + </ItemGroup> + +</Project> diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.PackageAsProj/init.props b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.PackageAsProj/init.props new file mode 100644 index 0000000..8357582 --- /dev/null +++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.PackageAsProj/init.props @@ -0,0 +1,5 @@ +<Project> + + <Import Project="$(MSBuildThisFileDirectory)diagnostic.descriptors.props" /> + +</Project> diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/diagnostic.descriptors.props b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/diagnostic.descriptors.props new file mode 100644 index 0000000..cb84013 --- /dev/null +++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/diagnostic.descriptors.props @@ -0,0 +1,161 @@ +<Project> + + <!-- The codes this part reports, one item each, for tools that read a build's diagnostics. --> + <ItemGroup> + <BuildDiagnosticDescriptor Include="MSKITPKG001" + Title="Package has no real Description" + MessageFormat="Package '{0}' has no real Description ('{1}'). nuget.org search shows it first: say what the package does and what sets it apart, in one or two sentences. Rule: {2}#description (to skip: MSKit_SkipPackageChecks=MSKITPKG001)" + Description="The package has no real Description: it is empty, the SDK's Package Description, the package id or the project name." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg001" /> + <BuildDiagnosticDescriptor Include="MSKITPKG002" + Title="Package Description is too short" + MessageFormat="Package '{0}' Description is {1} characters ('{2}'); write at least {3}: what it does and for whom (MSKit_PackageDescriptionMinLength sets the bar). Rule: {4}#description (to skip: MSKit_SkipPackageChecks=MSKITPKG002)" + Description="Description is shorter than MSKit_PackageDescriptionMinLength (30 characters)." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg002" /> + <BuildDiagnosticDescriptor Include="MSKITPKG003" + Title="Package has no README" + MessageFormat="Package '{0}' ships no README. Add package.readme.md next to the csproj: what it is, how to start, a short example, where to give feedback. Rule: {1}#readme (to skip: MSKit_SkipPackageChecks=MSKITPKG003)" + Description="No README is packed. Add package.readme.md (or README.md) next to the csproj, or set MSKit_PackageReadmeFrom." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg003" /> + <BuildDiagnosticDescriptor Include="MSKITPKG004" + Title="Package has no PackageTags" + MessageFormat="Package '{0}' has no PackageTags. Add a few search terms that are not already in the package id. Rule: {1}#tags (to skip: MSKit_SkipPackageChecks=MSKITPKG004)" + Description="No PackageTags. Add a few search terms that are not already in the package id." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg004" /> + <BuildDiagnosticDescriptor Include="MSKITPKG005" + Title="Package has no icon" + MessageFormat="Package '{0}' has no icon. Set PackageIconPath to a 128x128 PNG or JPEG (the company layer of the kit sets one for every package). Rule: {1}#icon (to skip: MSKit_SkipPackageChecks=MSKITPKG005)" + Description="No icon. Set PackageIconPath to a 128×128 PNG or JPEG, packed as icon<extension, lowercased>; the owner layer sets one for every package." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg005" /> + <BuildDiagnosticDescriptor Include="MSKITPKG006" + Title="Package has no licence" + MessageFormat="Package '{0}' has no licence. Without one nobody may use it. Set PackageLicenseExpression to an SPDX id (for example MIT). Rule: {1}#licensing (to skip: MSKit_SkipPackageChecks=MSKITPKG006)" + Description="No licence. Set PackageLicenseExpression to an SPDX id or PackageLicenseFile." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg006" /> + <BuildDiagnosticDescriptor Include="MSKITPKG007" + Title="PackageLicenseUrl is deprecated" + MessageFormat="Package '{0}' uses the deprecated PackageLicenseUrl. Replace it with PackageLicenseExpression (an SPDX id) or PackageLicenseFile. Rule: {1}#licensing (to skip: MSKit_SkipPackageChecks=MSKITPKG007)" + Description="The deprecated PackageLicenseUrl is set." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg007" /> + <BuildDiagnosticDescriptor Include="MSKITPKG008" + Title="PackageIconUrl is deprecated" + MessageFormat="Package '{0}' uses the deprecated PackageIconUrl. Pack the image and point PackageIcon (or the kit's PackageIconPath) at it; for older clients, set MSKit_DefaultPackageIconUrl to write the URL next to the kit's icon. Rule: {1}#icon (to skip: MSKit_SkipPackageChecks=MSKITPKG008)" + Description="The project sets the deprecated PackageIconUrl." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg008" /> + <BuildDiagnosticDescriptor Include="MSKITPKG009" + Title="Package README has relative images" + MessageFormat="The package README of '{0}' has relative images, which nuget.org does not render: {1}. Use absolute https URLs from an allowed host. Rule: {2}#allowed-domains-for-images-and-badges (to skip: MSKit_SkipPackageChecks=MSKITPKG009)" + Description="The package README has relative images, which nuget.org does not render." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg009" /> + <BuildDiagnosticDescriptor Include="MSKITPKG010" + Title="Package README contains HTML" + MessageFormat="The package README of '{0}' contains HTML, which nuget.org does not render: {1}. Use Markdown instead. Rule: {2}#supported-markdown-features (to skip: MSKit_SkipPackageChecks=MSKITPKG010)" + Description="The package README contains HTML, which nuget.org does not render." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg010" /> + <BuildDiagnosticDescriptor Include="MSKITPKG011" + Title="Package README uses GitHub alerts" + MessageFormat="The package README of '{0}' uses GitHub alert blocks, which nuget.org shows as plain quotes: {1}. Use a bold lead-in such as **Note:** instead. Rule: {2}#supported-markdown-features (to skip: MSKit_SkipPackageChecks=MSKITPKG011)" + Description="The package README uses GitHub alerts (> [!NOTE]), which nuget.org shows as plain quotes." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg011" /> + <BuildDiagnosticDescriptor Include="MSKITPKG012" + Title="Package README loads images from a blocked host" + MessageFormat="The package README of '{0}' loads images from hosts nuget.org blocks: {1}. Host them on an allowed domain (img.shields.io, raw.githubusercontent.com, ...). Rule: {2}#allowed-domains-for-images-and-badges (to skip: MSKit_SkipPackageChecks=MSKITPKG012)" + Description="The package README loads images from a host nuget.org blocks." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg012" /> + <BuildDiagnosticDescriptor Include="MSKITPKG013" + Title="Package version is not SemVer 2.0" + MessageFormat="Package '{0}' version '{1}' is not SemVer 2.0 (MAJOR.MINOR.PATCH[-prerelease][+metadata]). Rule: {2}#package-version (to skip: MSKit_SkipPackageChecks=MSKITPKG013)" + Description="The package version is not SemVer 2.0 (MSKit_SemVerRegex)." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg013" /> + <BuildDiagnosticDescriptor Include="MSKITPKG014" + Title="Package has no repository or project URL" + MessageFormat="Package '{0}' has no repository or project URL. Set RepositoryUrl, or build from a git clone whose origin remote Source Link can read. Rule: {1}#repository-type-and-url (to skip: MSKit_SkipPackageChecks=MSKITPKG014)" + Description="No repository or project URL. Set RepositoryUrl, or build from a git clone whose origin remote Source Link can read." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg014" /> + <BuildDiagnosticDescriptor Include="MSKITPKG015" + Title="Package icon has the wrong format or size" + MessageFormat="Package '{0}' icon is not a {1}x{1} PNG or JPEG (format: {2}, size: {3}x{4}). Rule: {5}#icon (to skip: MSKit_SkipPackageChecks=MSKITPKG015)" + Description="The icon is not a PNG or JPEG of MSKit_PackageIconSize × MSKit_PackageIconSize pixels (128); the size is checked on both formats." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg015" /> + <BuildDiagnosticDescriptor Include="MSKITPKG016" + Title="Package has no PackageReleaseNotes" + MessageFormat="Package '{0}' has no PackageReleaseNotes. Set them, or a link to the changelog (the kit links the releases page by default on GitHub, and on any host the generated readme knows with MSKit_PackageReadmeFrom). Rule: {1}#release-notes (to skip: MSKit_SkipPackageChecks=MSKITPKG016)" + Description="No PackageReleaseNotes. The kit fills them on github.com, and on any host the generated readme knows with MSKit_PackageReadmeFrom; elsewhere set them (a link to the changelog is enough)." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg016" /> + <BuildDiagnosticDescriptor Include="MSKITPKG017" + Title="Package README has relative links" + MessageFormat="The package README of '{0}' has relative links, which break on nuget.org: {1}. Use absolute URLs. Rule: {2} (to skip: MSKit_SkipPackageChecks=MSKITPKG017)" + Description="The package README has relative links, which break on nuget.org." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg017" /> + <BuildDiagnosticDescriptor Include="MSKITPKG018" + Title="Copyright contradicts the open-source licence" + MessageFormat="Package '{0}' is licensed '{1}' but its Copyright says 'all rights reserved' ('{2}'). Use 'Copyright (c) YEAR OWNER'. Rule: {3}#copyright (to skip: MSKit_SkipPackageChecks=MSKITPKG018)" + Description="The package has an open-source licence expression, but its Copyright says "all rights reserved"." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg018" /> + <BuildDiagnosticDescriptor Include="MSKITPKG019" + Title="Package README contains a Mermaid diagram" + MessageFormat="The package README of '{0}' contains a Mermaid diagram, which nuget.org shows as code. Link to the diagram on GitHub instead. Rule: {1}#supported-markdown-features (to skip: MSKit_SkipPackageChecks=MSKITPKG019)" + Description="The package README contains a Mermaid diagram, which nuget.org shows as code." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg019" /> + <BuildDiagnosticDescriptor Include="MSKITPKG020" + Title="Package readme cannot be generated as asked" + MessageFormat="MSKit_PackageReadmeFrom names '{0}', but {1} does not exist, so no readme is generated for '{2}'. The path is relative to the git root ({3})." + Description="The readme cannot be generated as asked: the MSKit_PackageReadmeFrom file is missing, a nuget:skip / nuget:only marker is unbalanced, a link leaves the repository, or links cannot be rewritten (no repository URL, an unknown host or provider, no commit)." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg020" /> + <BuildDiagnosticDescriptor Include="MSKITPKG021" + Title="Generated readme loads an image from a blocked host" + MessageFormat="{0} (to skip: MSKit_SkipPackageChecks=MSKITPKG021)" + Description="A generated readme loads an image from a host nuget.org does not render images from; the message names the image and its README line." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg021" /> + <BuildDiagnosticDescriptor Include="MSKITPKG022" + Title="Repository is private or internal" + MessageFormat="{0} (to skip: MSKit_SkipPackageChecks=MSKITPKG022)" + Description="The repository is private or internal (MSKit_RepositoryVisibility, else GitLab's CI_PROJECT_VISIBILITY), so the readme's links will not open for package readers." + Category="Packaging" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpkg022" /> + </ItemGroup> + +</Project> diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/init.props b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/init.props index 91ed9f6..90f6c82 100644 --- a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/init.props +++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/init.props @@ -24,4 +24,6 @@ <MSKit_PackageReadmeTitle Condition="'$(MSKit_PackageReadmeTitle)'==''">auto</MSKit_PackageReadmeTitle> </PropertyGroup> + <Import Project="$(MSBuildThisFileDirectory)diagnostic.descriptors.props" /> + </Project> diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Testing.XUnit.v3/diagnostic.descriptors.props b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Testing.XUnit.v3/diagnostic.descriptors.props new file mode 100644 index 0000000..6ff1884 --- /dev/null +++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Testing.XUnit.v3/diagnostic.descriptors.props @@ -0,0 +1,14 @@ +<Project> + + <!-- The codes this part reports, one item each, for tools that read a build's diagnostics. --> + <ItemGroup> + <BuildDiagnosticDescriptor Include="MSKITTEST005" + Title="xUnit v3 needs net8.0 or later" + MessageFormat="MSKit_TestingFramework='xunit.v3' requires net8.0 or later. Current target framework: {0}.%0AFix: target net8.0 or later in the test project." + Description="An xUnit v3 test project targets a framework older than net8.0." + Category="Testing" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskittest005" /> + </ItemGroup> + +</Project> diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Testing.XUnit.v3/init.props b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Testing.XUnit.v3/init.props index 86ddd60..3ca5546 100644 --- a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Testing.XUnit.v3/init.props +++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Testing.XUnit.v3/init.props @@ -9,4 +9,6 @@ <Import Condition="('$(IsTestsProject)'=='True' Or '$(IsTestsLibProject)'=='True') AND '$(MSKit_TestingFramework)'=='xunit.v3'" Project="$(MSBuildThisFileDirectory)tests.project.xunit.v3.routine.props" /> + <Import Project="$(MSBuildThisFileDirectory)diagnostic.descriptors.props" /> + </Project> diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Testing/diagnostic.descriptors.props b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Testing/diagnostic.descriptors.props new file mode 100644 index 0000000..8a425ef --- /dev/null +++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Testing/diagnostic.descriptors.props @@ -0,0 +1,91 @@ +<Project> + + <!-- The codes this part reports, one item each, for tools that read a build's diagnostics. --> + <ItemGroup> + <BuildDiagnosticDescriptor Include="MSKITTEST010" + Title="Test project props imported before MSKit_TestingFramework is set" + MessageFormat="MSKit_TestingFramework was not set when {0} was imported.%0AFix: set it BEFORE the Import:%0A <PropertyGroup>%0A <MSKit_TestingFramework>xunit.v3</MSKit_TestingFramework>%0A </PropertyGroup>%0A <Import Project="{0}" />" + Description="%24(TestsProjectCommonPropsPath) was imported before MSKit_TestingFramework was set." + Category="Testing" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskittest010" /> + <BuildDiagnosticDescriptor Include="MSKITTEST011" + Title="MSKit_TestingFramework changed after the test project props import" + MessageFormat="MSKit_TestingFramework changed after {0} was imported (at import: '{1}', final: '{2}').%0AFix: move the framework PropertyGroup above the Import." + Description="MSKit_TestingFramework changed after %24(TestsProjectCommonPropsPath) was imported." + Category="Testing" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskittest011" /> + <BuildDiagnosticDescriptor Include="MSKITTEST012" + Title="No wiring for the testing framework" + MessageFormat="No framework wiring found for MSKit_TestingFramework='{0}'.%0AFix: use a framework the kit ships (xunit.v3), or point MSKit_TestingFramework_CommonPropsPath at your own props file." + Description="No wiring exists for the MSKit_TestingFramework of an explicit test project." + Category="Testing" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskittest012" /> + <BuildDiagnosticDescriptor Include="MSKITTEST013" + Title="IsTestsProject is set in the project" + MessageFormat="csproj '{0}' declares <IsTestsProject> directly. That is set too late for the Microsoft.Testing.Platform wiring, which happens in the props phase.%0AFix: rename the project to match MSKit_TestsProjectNameRegex (default: names ending in .Tests), or replace the property with the explicit endpoint:%0A <PropertyGroup>%0A <MSKit_TestingFramework>xunit.v3</MSKit_TestingFramework>%0A </PropertyGroup>%0A <Import Project="{1}" />" + Description="The csproj sets IsTestsProject directly, too late for the props-phase wiring." + Category="Testing" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskittest013" /> + <BuildDiagnosticDescriptor Include="MSKITTEST014" + Title="Test project is marked as a test helper library" + MessageFormat="Project name '{0}' matches the tests auto-detect regex '{1}' but the project is marked as a test helper library. A project cannot be both.%0AFix one of:%0A 1. Rename the project so it no longer matches (e.g. 'Foo.Tests' to 'Foo.TestUtils')%0A 2. Override MSKit_TestsProjectNameRegex in Directory.Build.props%0A 3. Drop the IsTestsLibProject flag if this is a runnable test project" + Description="The name matches the test-project regex, but the project is marked as a test helper library." + Category="Testing" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskittest014" /> + <BuildDiagnosticDescriptor Include="MSKITTEST020" + Title="Test library props imported before MSKit_TestingFramework is set" + MessageFormat="MSKit_TestingFramework was not set when {0} was imported.%0AFix: set the framework PropertyGroup BEFORE the Import:%0A <PropertyGroup>%0A <MSKit_TestingFramework>xunit.v3</MSKit_TestingFramework>%0A </PropertyGroup>%0A <Import Project="{0}" />" + Description="%24(TestsLibProjectCommonPropsPath) was imported before MSKit_TestingFramework was set." + Category="Testing" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskittest020" /> + <BuildDiagnosticDescriptor Include="MSKITTEST021" + Title="MSKit_TestingFramework changed after the test library props import" + MessageFormat="MSKit_TestingFramework changed after {0} was imported.%0AAt import: '{1}' csproj-final: '{2}'.%0AFix: move the framework PropertyGroup ABOVE the Import." + Description="MSKit_TestingFramework changed after %24(TestsLibProjectCommonPropsPath) was imported." + Category="Testing" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskittest021" /> + <BuildDiagnosticDescriptor Include="MSKITTEST022" + Title="No helper-library wiring for the testing framework" + MessageFormat="No LibCommonProps path resolved for MSKit_TestingFramework='{0}'.%0AFix: use a framework the kit ships (xunit.v3) or set MSKit_TestingFramework_LibCommonPropsPath to a custom file." + Description="No helper-library wiring exists for MSKit_TestingFramework." + Category="Testing" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskittest022" /> + <BuildDiagnosticDescriptor Include="MSKITTEST025" + Title="MSKit_TestingFramework is not set" + MessageFormat="Project '{0}' is a test project but MSKit_TestingFramework is not set.%0AFix: add to Directory.Build.props:%0A <MSKit_TestingFramework>xunit.v3</MSKit_TestingFramework>" + Description="A test project or helper library has no MSKit_TestingFramework." + Category="Testing" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskittest025" /> + <BuildDiagnosticDescriptor Include="MSKITTEST026" + Title="No installed part wires the testing framework" + MessageFormat="Project '{0}' has MSKit_TestingFramework='{1}' but no installed kit part wires that framework.%0AFix: use xunit.v3 (part DragoAnt.MSBuildKit.Testing.XUnit.v3), or set MSKit_TestingFramework_CommonPropsPath to your own wiring." + Description="No installed part wires the MSKit_TestingFramework value." + Category="Testing" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskittest026" /> + <BuildDiagnosticDescriptor Include="MSKITTEST030" + Title="MSKit_TestsDir is empty" + MessageFormat="MSKit_TestsDir is empty — cannot auto-emit InternalsVisibleTo for sibling .Tests projects.%0AFix: set MSKit_TestsDir in Directory.Build.props:%0A <PropertyGroup>%0A <MSKit_TestsDir>{0}tests</MSKit_TestsDir>%0A </PropertyGroup>%0ATo disable auto InternalsVisibleTo, add to csproj:%0A <PropertyGroup>%0A <InternalsVisibleToAllTestsProjects>False</InternalsVisibleToAllTestsProjects>%0A </PropertyGroup>" + Description="MSKit_TestsDir is empty, so InternalsVisibleTo cannot be added." + Category="Testing" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskittest030" /> + <BuildDiagnosticDescriptor Include="MSKITTEST031" + Title="MSKit_TestsDir does not exist" + MessageFormat="MSKit_TestsDir='{0}' does not exist.%0AFix: point MSKit_TestsDir to an existing directory in Directory.Build.props:%0A <PropertyGroup>%0A <MSKit_TestsDir>{1}tests</MSKit_TestsDir>%0A </PropertyGroup>%0ATo disable auto InternalsVisibleTo, add to csproj:%0A <PropertyGroup>%0A <InternalsVisibleToAllTestsProjects>False</InternalsVisibleToAllTestsProjects>%0A </PropertyGroup>" + Description="MSKit_TestsDir points at a folder that does not exist." + Category="Testing" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskittest031" /> + </ItemGroup> + +</Project> diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Testing/init.props b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Testing/init.props index 73ea576..17d75a7 100644 --- a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Testing/init.props +++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Testing/init.props @@ -12,4 +12,6 @@ <Import Project="$(MSBuildThisFileDirectory)tests.project.props" /> + <Import Project="$(MSBuildThisFileDirectory)diagnostic.descriptors.props" /> + </Project> diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit/diagnostic.descriptors.props b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit/diagnostic.descriptors.props new file mode 100644 index 0000000..e71d1ad --- /dev/null +++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit/diagnostic.descriptors.props @@ -0,0 +1,126 @@ +<Project> + + <!-- The codes this part reports, one item each, for tools that read a build's diagnostics. --> + <ItemGroup> + <BuildDiagnosticDescriptor Include="MSKITDUP001" + Title="PackageVersion repeats one the kit provides" + MessageFormat="Directory.Packages.props declares <PackageVersion> entries the kit already provides:%0A%0A{0}%0A%0AFix: remove those <PackageVersion Include="..." /> lines. To pin another version, set the kit's version property instead (for example <MSKit_PackageVersion_XunitV3>), or turn the kit's versions off with <MSKit_ImplicitPackageVersions>False</MSKit_ImplicitPackageVersions>.%0ATo bypass this check: <MSKit_SkipAudit_ImplicitPackageDuplicates>True</MSKit_SkipAudit_ImplicitPackageDuplicates>" + Description="Directory.Packages.props declares a PackageVersion the kit already provides." + Category="References" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitdup001" /> + <BuildDiagnosticDescriptor Include="MSKITPRE001" + Title="Prerelease package on a stable branch" + MessageFormat="Prerelease {0}* packages referenced on stable branch '{1}':%0A{2}%0AFix: replace them with stable versions in Directory.Packages.props or the csproj." + Description="A stable-branch build references a prerelease version of a package whose id starts with MSKit_PrereleasePackagePrefix." + Category="References" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitpre001" /> + <BuildDiagnosticDescriptor Include="MSKITRES001" + Title="Package reference is prohibited" + MessageFormat="Package '{0}' is prohibited. {1}%0AFix: remove the PackageReference or use the suggested alternative. To allow this one reference, add SkipGlobalRestriction="True" to it." + Description="A referenced package is banned by an MSKit_RestrictPackageReference item with Type="Error"." + Category="References" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitres001" /> + <BuildDiagnosticDescriptor Include="MSKITRES002" + Title="Package reference is discouraged" + MessageFormat="Package '{0}' is discouraged. {1}%0ATo silence this for one reference, add SkipGlobalRestriction="True" to it." + Description="A referenced package is discouraged by an MSKit_RestrictPackageReference item with Type="Warning"." + Category="References" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitres002" /> + <BuildDiagnosticDescriptor Include="MSKITRES003" + Title="Project reference is not allowed" + MessageFormat="ProjectReference '{0}' is not allowed in this project.%0AFix: remove it, or mark it allowed: <ProjectReference Include="{0}" Allowed="True" />" + Description="With MSKit_RestrictProjectReferences=True (or MSKit_RestrictReferences=True), a ProjectReference lacks Allowed="True"." + Category="References" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitres003" /> + <BuildDiagnosticDescriptor Include="MSKITRES004" + Title="Package reference is not allowed" + MessageFormat="PackageReference '{0}' is not allowed in this project.%0AFix: remove it, or mark it allowed: <PackageReference Include="{0}" Allowed="True" />" + Description="With MSKit_RestrictPackageReferences=True (or MSKit_RestrictReferences=True), a PackageReference lacks Allowed="True"." + Category="References" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitres004" /> + <BuildDiagnosticDescriptor Include="MSKITSHARED006" + Title="TargetFramework repeats the shared value" + MessageFormat="csproj '{0}' declares <TargetFramework>{1}</TargetFramework> which equals Directory.Build.props's <TargetFramework>{2}</TargetFramework> - the csproj declaration is redundant.%0AFix: remove this from the csproj <PropertyGroup>:%0A <TargetFramework>{1}</TargetFramework>%0ADirectory.Build.props is the single source of truth.%0AThis check is not bypassable; pure literal duplication is always a code smell." + Description="The csproj declares the same TargetFramework as Directory.Build.props." + Category="Shared properties" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitshared006" /> + <BuildDiagnosticDescriptor Include="MSKITSHARED007" + Title="TargetFrameworks repeats the shared value" + MessageFormat="csproj '{0}' declares <TargetFrameworks>{1}</TargetFrameworks> which equals Directory.Build.props's <TargetFrameworks>{2}</TargetFrameworks> - the csproj declaration is redundant.%0AFix: remove this from the csproj <PropertyGroup>:%0A <TargetFrameworks>{1}</TargetFrameworks>%0ADirectory.Build.props is the single source of truth.%0AThis check is not bypassable; pure literal duplication is always a code smell." + Description="The csproj declares the same TargetFrameworks as Directory.Build.props." + Category="Shared properties" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitshared007" /> + <BuildDiagnosticDescriptor Include="MSKITSHARED008" + Title="TargetFramework overrides the shared value" + MessageFormat="csproj '{0}' declares <TargetFramework>{1}</TargetFramework> but Directory.Build.props centralizes <TargetFramework>{2}</TargetFramework> as the default.%0ARecommended: remove the csproj-level <TargetFramework> declaration (DBP is the single source of truth).%0ATo override intentionally, add to the csproj <PropertyGroup>:%0A <MSKit_SkipAudit_TargetFrameworkOverride>True</MSKit_SkipAudit_TargetFrameworkOverride>" + Description="The csproj overrides the shared TargetFramework with another value." + Category="Shared properties" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitshared008" /> + <BuildDiagnosticDescriptor Include="MSKITSHARED009" + Title="TargetFrameworks overrides the shared value" + MessageFormat="csproj '{0}' declares <TargetFrameworks>{1}</TargetFrameworks> but Directory.Build.props centralizes <TargetFrameworks>{2}</TargetFrameworks> as the default.%0ARecommended: remove the csproj-level <TargetFrameworks> declaration (DBP is the single source of truth).%0ATo override intentionally, add to the csproj <PropertyGroup>:%0A <MSKit_SkipAudit_TargetFrameworkOverride>True</MSKit_SkipAudit_TargetFrameworkOverride>" + Description="The csproj overrides the shared TargetFrameworks with another value." + Category="Shared properties" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitshared009" /> + <BuildDiagnosticDescriptor Include="MSKITSHARED010" + Title="Both TargetFramework and TargetFrameworks are declared" + MessageFormat="csproj '{0}' declares BOTH <TargetFramework> AND <TargetFrameworks> in the same project. SDK silently ignores <TargetFramework> when <TargetFrameworks> is non-empty.%0AFix: remove one of these from the csproj <PropertyGroup> - pick the shape you actually want:%0A <TargetFramework>{1}</TargetFramework>%0A -- OR --%0A <TargetFrameworks>{2}</TargetFrameworks>%0AThis check is not bypassable; declaring both is always wrong." + Description="The csproj declares both TargetFramework and TargetFrameworks (an empty <TargetFramework></TargetFramework> counts)." + Category="Shared properties" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitshared010" /> + <BuildDiagnosticDescriptor Include="MSKITSHARED020" + Title="TreatWarningsAsErrors differs from the shared value" + MessageFormat="Expected <TreatWarningsAsErrors>{0}</TreatWarningsAsErrors> (from shared config / Directory.Build.props). Actual csproj-final value: '{1}'.%0AFix: align the csproj value with the shared one, or remove the csproj override.%0ATo bypass this audit, add to csproj or Directory.Build.props:%0A <PropertyGroup>%0A <MSKit_SkipAudit_TreatWarningsAsErrors>True</MSKit_SkipAudit_TreatWarningsAsErrors>%0A </PropertyGroup>" + Description="The csproj's final TreatWarningsAsErrors differs from the shared value." + Category="Shared properties" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitshared020" /> + <BuildDiagnosticDescriptor Include="MSKITVER001" + Title="Version is declared in the project" + MessageFormat="<Version> is declared in the project but MSKit_VersionStrategy='{0}' renders the version from a template, so the declared value is ignored.%0AFix: remove <Version> from the csproj (declare the next version as <VersionPrefix> in Directory.Version.props), or switch to manual versioning in Directory.Build.props:%0A <MSKit_VersionStrategy>Manual</MSKit_VersionStrategy>" + Description="The csproj declares <Version> while a template strategy renders the version, so the value would be ignored." + Category="Versioning" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitver001" /> + <BuildDiagnosticDescriptor Include="MSKITVER002" + Title="Version template has an unknown placeholder" + MessageFormat="A version template contains unknown placeholder(s): {0}.%0ATemplates: MSKit_VersionTemplate='{1}', MSKit_StableVersionTemplate='{2}', MSKit_ReleaseVersionTemplate='{3}', MSKit_PullRequestVersionTemplate='{4}'.%0A{5}" + Description="A version template uses an unknown placeholder." + Category="Versioning" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitver002" /> + <BuildDiagnosticDescriptor Include="MSKITVER004" + Title="VersionTag is empty" + MessageFormat="MSKit_VersionStrategy='VersionTag' but VersionTag is empty.%0AFix: pass it on the command line (-p:VersionTag=1.2.3) or use MSKit_VersionStrategy=ReleaseTag, which reads the version from the release tag." + Description="MSKit_VersionStrategy=VersionTag but VersionTag is empty." + Category="Versioning" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitver004" /> + <BuildDiagnosticDescriptor Include="MSKITVER006" + Title="Release tag is not SemVer 2.0" + MessageFormat="Release tag '{0}' is not a valid version. After removing a leading 'v' it must be SemVer 2.0: MAJOR.MINOR.PATCH with an optional -prerelease and +metadata (for example v2.1.0 or 2.1.0-beta.1).%0AFix: delete the tag and the GitHub release, then create a release whose tag is a version." + Description="A tag build whose tag, after removing a leading v, is not SemVer 2.0 (MSKit_ReleaseTagRegex)." + Category="Versioning" + DefaultSeverity="Error" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitver006" /> + <BuildDiagnosticDescriptor Include="MSKITVER007" + Title="Release tag differs from VersionPrefix" + MessageFormat="Release tag '{0}' has version core {1}, but the repository declares VersionPrefix {2}.%0AFix: bump <VersionPrefix> in Directory.Version.props after the release so branch and pull-request builds sort above it. Set MSKit_SkipAudit_ReleaseTagPrefix=True to silence this." + Description="The tag's MAJOR.MINOR.PATCH differs from the VersionPrefix the repository declares." + Category="Versioning" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitver007" /> + </ItemGroup> + +</Project> diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit/init.props b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit/init.props index ef44584..ad1bf33 100644 --- a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit/init.props +++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit/init.props @@ -14,4 +14,6 @@ <Import Condition="Exists('$(MSKit_SlnFileDirectory)Directory.Version.props')" Project="$(MSKit_SlnFileDirectory)Directory.Version.props" /> + <Import Project="$(MSBuildThisFileDirectory)diagnostic.descriptors.props" /> + </Project> diff --git a/kit/.toolkit/msbuild/init.props b/kit/.toolkit/msbuild/init.props index 580345c..bd6a614 100644 --- a/kit/.toolkit/msbuild/init.props +++ b/kit/.toolkit/msbuild/init.props @@ -20,6 +20,7 @@ <Import Project="$(MSBuildThisFileDirectory)DragoAnt.MSBuildKit.Testing/init.props" Condition="Exists('$(MSBuildThisFileDirectory)DragoAnt.MSBuildKit.Testing/init.props')" /> <Import Project="$(MSBuildThisFileDirectory)DragoAnt.MSBuildKit.Testing.XUnit.v3/init.props" Condition="Exists('$(MSBuildThisFileDirectory)DragoAnt.MSBuildKit.Testing.XUnit.v3/init.props')" /> <Import Project="$(MSBuildThisFileDirectory)DragoAnt.MSBuildKit.EF/init.props" Condition="Exists('$(MSBuildThisFileDirectory)DragoAnt.MSBuildKit.EF/init.props')" /> + <Import Project="$(MSBuildThisFileDirectory)DragoAnt.MSBuildKit.PackageAsProj/init.props" Condition="Exists('$(MSBuildThisFileDirectory)DragoAnt.MSBuildKit.PackageAsProj/init.props')" /> <Import Project="$(MSBuildThisFileDirectory)DragoAnt.MSBuildKit/init.last.props" Condition="Exists('$(MSBuildThisFileDirectory)DragoAnt.MSBuildKit/init.last.props')" /> <Import Project="$(MSKit_AfterInitProps)" Condition="'$(MSKit_AfterInitProps)' != '' and Exists('$(MSKit_AfterInitProps)')" /> From c23ed0b0f1d52f7a5a13f2bb31cfe93710267635 Mon Sep 17 00:00:00 2001 From: VasiliyF <5789590+vfofanov@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:49:35 +0200 Subject: [PATCH 3/4] Document the diagnostic catalog - docs/reference/diagnostic-catalog.md: the item, where the files live, how to add your own, why both sides stay hand-written - property reference row, changelog, contributing checklist - tests/codes.sh: normalise Windows paths in the evaluated items --- CHANGELOG.md | 1 + CONTRIBUTING.md | 2 +- docs/README.md | 3 +- docs/parts.md | 2 +- docs/reference/codes.md | 2 +- docs/reference/diagnostic-catalog.md | 50 ++++++++++++++++++++++++++++ docs/reference/properties.md | 1 + tests/codes.sh | 2 +- tools/docs-check.sh | 2 +- 9 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 docs/reference/diagnostic-catalog.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 6deed4d..84594a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to this project are documented here. The format follows [Kee ### Added - Every warning and error links to its section of the [code reference](./docs/reference/codes.md) (`HelpLink`, shown by the terminal logger and IDEs). `MSKit_CodesHelpBaseUrl` points the links at another copy of the page. +- A machine-readable catalog of the codes: every part declares each code it reports as a `BuildDiagnosticDescriptor` item (`Title`, `MessageFormat`, `Description`, `Category`, `DefaultSeverity`, `HelpLink`) in its own `diagnostic.descriptors.props`, so a tool can read them with `dotnet msbuild -getItem:BuildDiagnosticDescriptor` and tell the owning part from the item's `DefiningProjectFullPath`. Each section of the [code reference](./docs/reference/codes.md) now opens with the code's title. See the [diagnostic catalog](./docs/reference/diagnostic-catalog.md). - `MSKit_DefaultPackageIconUrl`: when the kit packs its own icon, this URL is also written as `PackageIconUrl`, so clients that predate embedded icons show it; nuget.org keeps showing the embedded one. Empty by default; an owner sets it in its owner layer. `MSKITPKG008` now reports only a `PackageIconUrl` the project sets itself. See [docs/packaging.md](./docs/packaging.md#package-metadata). - `manager/`: the first build of `mskit-manager`, the `DragoAnt.MSBuildKit.Manager` .NET tool (`net8.0`, `net10.0`, `RollForward=Major`) that will install, update and migrate the kit. This build has one command, `status [--json]`, which prints the tool version; the logo goes to stderr, only on a terminal and never with `--no-logo`, so `--json` output always parses. Each run writes a log under `<system temp>/mskit-manager/logs/`, named after the command, newest 20 kept. Not published yet. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bf87858..d7891f3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,7 +41,7 @@ Both scripts restore into a global-packages folder of their own, `dist/selftest- ## Changing the kit - A new property defaults with `Condition="'$(Name)'==''"`, so a consumer's value always wins, and gets a row in [docs/reference/properties.md](./docs/reference/properties.md) plus a mention on its topic page. -- A new check gets an `MSKIT<AREA><nnn>` code with no separator (a shipped code is never renumbered or reused), a `HelpLink="$(MSKit_CodesHelpBaseUrl)#<code, lower case>"`, a message that says how to fix it, a section in [docs/reference/codes.md](./docs/reference/codes.md) headed by the code, a fixture that triggers it and a line in `tests/run.sh`. +- A new check gets an `MSKIT<AREA><nnn>` code with no separator (a shipped code is never renumbered or reused), a `HelpLink="$(MSKit_CodesHelpBaseUrl)#<code, lower case>"`, a message that says how to fix it, a section in [docs/reference/codes.md](./docs/reference/codes.md) headed by the code, a `BuildDiagnosticDescriptor` item in its part's `diagnostic.descriptors.props` ([diagnostic catalog](./docs/reference/diagnostic-catalog.md)), a fixture that triggers it and a line in `tests/run.sh`. - `sh tools/docs-check.sh` fails on a property, item or code without its reference entry, on a name the docs mention that the kit lacks, and on a broken relative link; `--list properties|items|codes` prints the kit's inventory with the file and line of each. - The README stays short: key features, install, links. Detail goes to the topic page in `docs/`. - A new part needs a line in `kit/.toolkit/kit.parts` and its `init.props` / `init.targets` imports in the entry points. diff --git a/docs/README.md b/docs/README.md index b3055a7..e57cd62 100644 --- a/docs/README.md +++ b/docs/README.md @@ -19,6 +19,7 @@ Read in this order; each page stands on its own, so jump to the one you need. | 13 | [Troubleshooting](./troubleshooting.md) | fix a failing build or update | | 14 | [Code reference](./reference/codes.md) | look up any warning or error the kit reports | | 15 | [Property reference](./reference/properties.md) | look up any property the kit sets or reads | -| 16 | [Migrating from MSBuild.Routine](./migrating-from-msbuild-routine.md) | move a repository off the older submodule | +| 16 | [Diagnostic catalog](./reference/diagnostic-catalog.md) | read the kit's codes from a tool, or add your own to the catalog | +| 17 | [Migrating from MSBuild.Routine](./migrating-from-msbuild-routine.md) | move a repository off the older submodule | Contributing to the kit itself: [CONTRIBUTING.md](../CONTRIBUTING.md). diff --git a/docs/parts.md b/docs/parts.md index 0f92001..04fba74 100644 --- a/docs/parts.md +++ b/docs/parts.md @@ -27,7 +27,7 @@ The kit is split into parts, one folder each under `.toolkit/msbuild/` (`DragoAn | Phase | Order | | --- | --- | -| props | `MSKit_BeforeInitProps` → owner layer (`init.company.props`) → Core → Vcs.GitHub → Locals.Compile → Locals.DirectorySecrets → Locals.Secrets → Project.RoslynComponent → Project.CodeFixer → Project.CodeAnalyzer → Project.SourceGenerator → TfmConstants → Trunk → Packaging → Testing → Testing.XUnit.v3 → EF → Trunk `init.last.props` (version engine) → `MSKit_AfterInitProps` | +| props | `MSKit_BeforeInitProps` → owner layer (`init.company.props`) → Core → Vcs.GitHub → Locals.Compile → Locals.DirectorySecrets → Locals.Secrets → Project.RoslynComponent → Project.CodeFixer → Project.CodeAnalyzer → Project.SourceGenerator → TfmConstants → Trunk → Packaging → Testing → Testing.XUnit.v3 → EF → PackageAsProj → Trunk `init.last.props` (version engine) → `MSKit_AfterInitProps` | | targets | `MSKit_BeforeInitTargets` → TfmConstants → owner layer (`init.company.targets`) → Core → Locals.* → Project.CodeAnalyzer → Project.SourceGenerator → Trunk → Packaging → Testing → Testing.XUnit.v3 → the `init.last.targets` of Trunk, Project.RoslynComponent, Testing.XUnit.v3, Testing, Project.CodeAnalyzer, PrivateAssets, PackageAsProj, ProjMetadata → every part's `audit/*.targets` → `MSKit_AfterInitTargets` | In the props phase a default is written as `<X Condition="'$(X)'==''">`, so the **first** writer wins: a value you set in `Directory.Build.props` above the kit import beats the owner layer, which beats the parts. The exceptions are the owner layer's `ManufacturerName`, `FullManufacturerName` and `NoWarn`, which it sets unconditionally ([Customizing](./customizing.md#the-owner-layer)). The csproj body runs after all props, so a value set there wins too, except for the few properties the kit reads in the props phase (the test-project switches, `TargetFramework` detection); those pages say so. How to hook in your own files: [Customizing](./customizing.md). diff --git a/docs/reference/codes.md b/docs/reference/codes.md index 7fd57d0..a30b3df 100644 --- a/docs/reference/codes.md +++ b/docs/reference/codes.md @@ -1,6 +1,6 @@ # Code reference -Every warning and error the kit reports, one section per code. A code is the kit prefix, a family and a three-digit number with no separator (`MSKITVER006`); its section anchor is the code in lower case (`#mskitver006`). Every warning and error carries a `HelpLink` to its section, which the terminal logger and IDEs show as a link on the code; `MSKit_CodesHelpBaseUrl` names the page the links point at ([property reference](./properties.md#machine-ci-and-paths)). Severity is the default; the section says how to change or skip it. +Every warning and error the kit reports, one section per code. A code is the kit prefix, a family and a three-digit number with no separator (`MSKITVER006`); its section anchor is the code in lower case (`#mskitver006`). Every warning and error carries a `HelpLink` to its section, which the terminal logger and IDEs show as a link on the code; `MSKit_CodesHelpBaseUrl` names the page the links point at ([property reference](./properties.md#machine-ci-and-paths)). Each section opens with the code's short title and names its default severity; the section says how to change or skip it. Tools read the same title, severity and opening sentence from the kit itself, as `BuildDiagnosticDescriptor` items ([diagnostic catalog](./diagnostic-catalog.md)). Earlier releases put an underscore between the prefix and the family; each section names its former spelling, and `NoWarn`, `WarningsAsErrors`, `WarningsNotAsErrors` or `MSKit_SkipPackageChecks` entries that use it no longer match. A code is never renumbered or reused: `VER003` is reserved, and no release reported it. diff --git a/docs/reference/diagnostic-catalog.md b/docs/reference/diagnostic-catalog.md new file mode 100644 index 0000000..84cd690 --- /dev/null +++ b/docs/reference/diagnostic-catalog.md @@ -0,0 +1,50 @@ +# Diagnostic catalog + +Every code the kit reports is also declared as an MSBuild item, `BuildDiagnosticDescriptor`, so a tool that collects a build's warnings and errors can name, group and explain them without parsing this documentation. The [code reference](./codes.md) is the same catalog for readers. + +## The item + +```xml +<BuildDiagnosticDescriptor Include="MSKITVER007" + Title="Release tag differs from VersionPrefix" + MessageFormat="Release tag '{0}' has version core {1}, but the repository declares VersionPrefix {2}.%0AFix: ..." + Description="The tag's MAJOR.MINOR.PATCH differs from the VersionPrefix the repository declares." + Category="Versioning" + DefaultSeverity="Warning" + HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitver007" /> +``` + +| Metadata | Value | +| --- | --- | +| `Include` | The code | +| `Title` | The code's short name: the bold line that opens its section of the code reference | +| `MessageFormat` | The text the build reports, with `{0}`, `{1}`, … where it inserts a value such as a project name, a path or a list; a value used twice keeps its number, and `%0A` is a line break | +| `Description` | The opening sentence or two of the code's section, as plain text | +| `Category` | The family the code's section is under: `Packaging`, `Versioning`, `References`, `Shared properties`, `Project types`, `Testing` or `PackageAsProj` | +| `DefaultSeverity` | `Warning` or `Error`: how the code is reported on a developer machine with the kit's defaults. The section says what changes it; the `MSKITPKG001`-`MSKITPKG019` checks, for one, become errors on CI | +| `HelpLink` | The code's section, the same link the warning or error carries; it follows `MSKit_CodesHelpBaseUrl` | + +`Category` is the family rather than the letters of the code because a family is what a reader groups by, and two of them hold more than one prefix (`References` has `DUP`, `PRE` and `RES`); the prefix is already in the code. + +## Where the items live + +Each part declares the codes it reports in its own folder, `.toolkit/msbuild/DragoAnt.MSBuildKit.<Part>/diagnostic.descriptors.props`, which the part's `init.props` imports. A project therefore sees the descriptors of the parts it has installed and no others, and the `DefiningProjectFullPath` of an item names the part that owns the code; `.toolkit/kit.json` holds the kit version. The [code reference](./codes.md) lists which part reports which family. + +```sh +dotnet msbuild src/MyLibrary/MyLibrary.csproj -getItem:BuildDiagnosticDescriptor +``` + +prints the catalog of one project as JSON. It lists every code the installed parts can report; which of them a build did report is in that build's own output. + +A reserved code has no item: `VER003` was never reported and never will be. + +## Adding your own + +The item is not tied to the kit's codes or prefix, so your own checks can join the same catalog. + +- **Checks in a repository or in a company's shared build files:** put the items in a props file that ships next to the targets that report the codes, and import it from your props (for instance through `MSKit_AfterInitProps`, [Customizing](../customizing.md#hooks-around-the-kit)). Keep one file per set of checks that is versioned together, never one central file for all of them: a collector attributes a code to the file that defines its item. +- **A part added to a fork of the kit:** add `diagnostic.descriptors.props` to the part's folder and import it from the part's `init.props` ([CONTRIBUTING](../../CONTRIBUTING.md#changing-the-kit)). + +## How the catalog stays true + +The items and the code reference are both written by hand, and `sh tools/docs-check.sh`, which CI runs, fails when they drift. The reference cannot be generated from the items, because its sections carry what a descriptor has no place for: the fix, the switches, the links. The items cannot be generated from the reference, because the kit is installed as plain files, with no build step that could run a generator. So the check compares them instead: every reported code has exactly one item, in the part that reports it; `Title`, `Category`, `DefaultSeverity` and `Description` equal the title line, family, `(warning)` / `(error)` mark and opening sentences of the code's section; `MessageFormat` equals the text of the `<Warning>` or `<Error>` that reports the code, expressions replaced by placeholders; `HelpLink` equals the task's. diff --git a/docs/reference/properties.md b/docs/reference/properties.md index 5ccdde9..e5a3ef6 100644 --- a/docs/reference/properties.md +++ b/docs/reference/properties.md @@ -29,6 +29,7 @@ Every property and item the kit sets or reads, grouped by topic. **Set** = a val | `MSKit_ProjectObjDir` | out | | The project's `obj` folder, absolute | | `MSKit_Templates` | set | `.toolkit/.local/` | Templates for local files ([Local files](../local-files.md)) | | `MSKit_Diagnostic` | set | `false` | Reserved; nothing reads it in this version | +| `BuildDiagnosticDescriptor` | item | one per code of each installed part | A code the kit reports, with its `Title`, `MessageFormat`, `Description`, `Category`, `DefaultSeverity` and `HelpLink`, for tools that read a build's diagnostics ([diagnostic catalog](./diagnostic-catalog.md)) | | `MSKit_CodesHelpBaseUrl` | set | `https://github.com/DragoAnt/MSBuildKit/blob/main/docs/reference/codes.md` | The page every warning and error links to (`HelpLink`); the link adds `#` and the code in lower case ([code reference](./codes.md)). Point it at your own copy of the page | ## Versioning diff --git a/tests/codes.sh b/tests/codes.sh index 43c5fe4..b644274 100644 --- a/tests/codes.sh +++ b/tests/codes.sh @@ -28,7 +28,7 @@ catalog_items() { proj="$1"; name="$2"; shift 2 $clean_env dotnet msbuild "$proj" -nologo -getItem:BuildDiagnosticDescriptor "$@" 2> "$out/$name.err" | tr -d '\r' > "$out/$name.json" || true awk ' - function val(l) { sub(/^[^:]*:[[:space:]]*"/, "", l); sub(/",?[[:space:]]*$/, "", l); gsub(/\\/, "/", l); return l } + function val(l) { sub(/^[^:]*:[[:space:]]*"/, "", l); sub(/",?[[:space:]]*$/, "", l); gsub(/\\+/, "/", l); return l } /^[[:space:]]*"Identity":/ { id = val($0) } /^[[:space:]]*"Title":/ { title = val($0) } /^[[:space:]]*"DefaultSeverity":/ { severity = val($0) } diff --git a/tools/docs-check.sh b/tools/docs-check.sh index f2dd3a5..28aa31e 100644 --- a/tools/docs-check.sh +++ b/tools/docs-check.sh @@ -55,7 +55,7 @@ find kit/.toolkit/msbuild -type f \( -name '*.props' -o -name '*.targets' -o -na else if (tag == "ItemGroup") ig++ else if (tag == "/ItemGroup") ig-- else if (substr(tag, 1, 1) != "/" && pg > 0) print "P", tag, FILENAME ":" FNR - else if (substr(tag, 1, 1) != "/" && ig > 0 && tag ~ /^MSKit_/) print "I", tag, FILENAME ":" FNR + else if (substr(tag, 1, 1) != "/" && ig > 0 && (tag ~ /^MSKit_/ || tag == "BuildDiagnosticDescriptor")) print "I", tag, FILENAME ":" FNR } rest = out while (match(rest, /\$\(MSKit_[A-Za-z0-9_]+/)) { print "R", substr(rest, RSTART + 2, RLENGTH - 2), FILENAME ":" FNR; rest = substr(rest, RSTART + RLENGTH) } From f35f10b007f4ccb01d9882f3ba8a5f555f084c8f Mon Sep 17 00:00:00 2001 From: VasiliyF <5789590+vfofanov@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:50:54 +0200 Subject: [PATCH 4/4] Say why the catalog items are not generated from the code reference --- docs/reference/diagnostic-catalog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/diagnostic-catalog.md b/docs/reference/diagnostic-catalog.md index 84cd690..c91d65d 100644 --- a/docs/reference/diagnostic-catalog.md +++ b/docs/reference/diagnostic-catalog.md @@ -47,4 +47,4 @@ The item is not tied to the kit's codes or prefix, so your own checks can join t ## How the catalog stays true -The items and the code reference are both written by hand, and `sh tools/docs-check.sh`, which CI runs, fails when they drift. The reference cannot be generated from the items, because its sections carry what a descriptor has no place for: the fix, the switches, the links. The items cannot be generated from the reference, because the kit is installed as plain files, with no build step that could run a generator. So the check compares them instead: every reported code has exactly one item, in the part that reports it; `Title`, `Category`, `DefaultSeverity` and `Description` equal the title line, family, `(warning)` / `(error)` mark and opening sentences of the code's section; `MessageFormat` equals the text of the `<Warning>` or `<Error>` that reports the code, expressions replaced by placeholders; `HelpLink` equals the task's. +The items and the code reference are both written by hand, and `sh tools/docs-check.sh`, which CI runs, fails when they drift. The reference cannot be generated from the items, because its sections carry what a descriptor has no place for: the fix, the switches, the links. Generating the items from the reference would mean parsing prose into XML and committing the output beside its source, which is two copies again with a generator to maintain. So the check compares them instead: every reported code has exactly one item, in the part that reports it; `Title`, `Category`, `DefaultSeverity` and `Description` equal the title line, family, `(warning)` / `(error)` mark and opening sentences of the code's section; `MessageFormat` equals the text of the `<Warning>` or `<Error>` that reports the code, expressions replaced by placeholders; `HelpLink` equals the task's.