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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/vale-3-24-0.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@
---

Update the bundled Vale to 3.24.0.

Vale 3.24.0 warns about any key it does not recognise inside a matcher, including the `tskl) rule` breadcrumb every Vale rule config carries. The CLI now comments the breadcrumb out of the config it assembles for Vale, so `check` no longer prints one notice per matcher, and a file Vale cannot parse is again set aside with its own finding instead of failing the whole Vale run. Vale's warnings now arrive as one notice each.

Two findings change for existing rules: C# `// ReSharper disable` and `// NOSONAR` comments are no longer linted, and a word after a link and a non-breaking space is now matched. `update` (topic v18) walks both.

`validate` and `verify` now reject a Vale rule's `tests:` key. Vale 3.24.0 runs those cases with `vale test`, which Taskless never calls; a Vale rule is verified by its `.tests/pass` and `.tests/fail` documents. `update --rules` moves each case into one of those fixtures for you.
49 changes: 47 additions & 2 deletions packages/cli/src/agent/update.md
Original file line number Diff line number Diff line change
Expand Up @@ -497,8 +497,8 @@ loaded it without complaint.

### Migrating to 0.13.3

The vendored ast-grep moves from 0.45.3 to 0.51.0. Nothing here needs
work by hand.
The vendored ast-grep moves from 0.45.3 to 0.51.0, and Vale from 3.23.0
to 3.24.0. Nothing here needs work by hand.

**A Haskell type synonym is `type_synonym` now, not `type_synomym`, and
`update --rules` renames it for you.** ast-grep's Haskell grammar moved
Expand Down Expand Up @@ -526,6 +526,51 @@ files that was routed to the runtime tier, or not written because
ast-grep could not parse Zig, can now be an sg rule. No existing rule
changes.

Two things in Vale 3.24.0 change which findings an existing rule
reports, each measured against both binaries on the same fixture.

**A C# tool directive is no longer read.** A `// ReSharper disable`,
`// ReSharper restore` or `// NOSONAR` line in a `.cs` file was linted
as an ordinary comment, so a rule over comments fired on a suppression
list. It is now skipped, as 3.23.0 already did for `//nolint`, `# noqa`
and `eslint-disable`. Findings disappear; none appear.

**A word after a link and a non-breaking space is matched.** Through
3.23.0 a U+00A0 or U+202F after inline markup was dropped, fusing the
link text and the next word, so a token there went unreported. It is
now a word break, and a rule over that word starts reporting.

What the CLI absorbed, so you do not have to: Vale 3.24.0 warns `W101`
about every key it does not recognise inside a matcher, which includes
the `tskl) rule` breadcrumb every rule config carries. Keep writing the
breadcrumb; the CLI comments it out of the file it hands Vale, so no
notice appears. A file no rule runs on is no longer parsed, which
`check` already approximated by setting converter-dependent files
aside, so nothing changes there.

Additions a rule config can use: a matcher may carry a condition,
`[*.md if .Meta.draft]`, which applies it only to the matched files
whose front matter satisfies it. `validate` accepts it.

**A Vale rule's `tests:` cases move into its fixtures, and
`update --rules` moves them for you.** Vale 3.24.0 ships `vale test`, which runs
cases written in a rule's `tests:` list. Taskless never runs it:
`verify` judges a Vale rule by the documents under `.tests/pass` and
`.tests/fail`. So `validate` and `verify` now reject the key, which
0.12.0 had started accepting, rather than let a rule look tested by
cases nothing runs. `check` still runs such a rule.

Do not move them by hand. Step 5 writes each case's `input` as a
document named for the case, under `.tests/fail` when the case expects
an alert and `.tests/pass` when it expects none, with the case's
`format` as its extension. It then deletes the `tests:` block, leaves
everything else in the rule as it was, never overwrites a fixture, and
names each file it wrote. No rule Taskless issued carries `tests:`, so
it never touches a signed rule. A rule with a case that reads through a
`view`, or names another rule file in `rule:`, is left as it is, and
`validate` keeps naming it; move that rule's cases by hand the same
way.

## Errors

With `--json`, `--rules` failures emit `{ ok: false, code, message }`:
Expand Down
30 changes: 25 additions & 5 deletions packages/cli/src/rules/assemble.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,25 +83,45 @@ function valeHeader(): string {
}

/**
* A rule's assembled block: its breadcrumb comment, then its config verbatim.
* A rule's assembled block: its breadcrumb comment, then its config verbatim
* except that each breadcrumb is commented out.
*
* The config is the generator's structured input and is never re-serialized.
* The schema has already read it as an AST, for validation and for the matcher
* list; what Vale reads is the file's own bytes, so matcher order, comments and
* spacing survive exactly as the author wrote them. Matcher order inside a rule
* is the author's expression of precedence, so anything short of verbatim
* would be a semantic edit. The only addition is a newline after a source that
* would be a semantic edit. The one addition is a newline after a source that
* lacks one, so the next rule's breadcrumb starts its own line.
*
* The one edit is `# ` in front of each `tskl) rule` line, at the lines the
* schema located (`breadcrumbLines`), so nothing here searches the text for
* it. That is not a semantic edit: Vale read the key as a rule named
* `tskl) rule` switched off, which is nothing. What changed in 3.24.0 is that
* it says so, `W101 'tskl) rule' isn't a section option`, once per matcher on
* every run, and that line on stderr in front of a real error's JSON is what
* kept a file Vale cannot parse from being narrowed out of the run
* (taskless/cli#300). Commenting rather than deleting keeps every line where
* the author put it, so a line number Vale reports is still the author's.
*
* This is also why the schema, not this module, turns away a copied-in
* `StylesPath` or `MinAlertLevel`. Assembly used to strip those two by
* splitting each line on `=`; that was the one string edit it performed, and
* it meant the file Vale read was not the file the author wrote. A run-level
* key above the first matcher is now a rejection, so there is nothing left to
* strip.
*/
function valeRuleBlock(ruleId: string, source: string): string {
const terminated = source.endsWith("\n") ? source : `${source}\n`;
function valeRuleBlock(
ruleId: string,
source: string,
breadcrumbLines: readonly number[]
): string {
const lines = source.split("\n");
for (const line of breadcrumbLines) {
lines[line - 1] = `# ${lines[line - 1] ?? ""}`;
}
const commented = lines.join("\n");
const terminated = commented.endsWith("\n") ? commented : `${commented}\n`;
return `# tskl) rule = ${ruleId}\n${terminated}`;
}

Expand Down Expand Up @@ -243,7 +263,7 @@ export async function assembleValeConfig(
if (selected !== undefined && !selected.has(ruleId)) continue;
for (const pattern of verdict.sections) sections.add(pattern);
advisories.push(...verdict.advisories);
blocks.push(valeRuleBlock(ruleId, source));
blocks.push(valeRuleBlock(ruleId, source, verdict.breadcrumbLines));
}

if (refusals.length > 0) return { status: "refused", refusals };
Expand Down
40 changes: 22 additions & 18 deletions packages/cli/src/rules/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,30 +569,34 @@ export const VALE_CONVERTER_DEPENDENT: readonly ValeConverterFormat[] =
groupByConverter();

/**
* Vale's checker tag per converter-dependent extension, from the `E100` text.
*
* This is the host-independent half of the failure. The prose after the tag is
* not: `.xml` reports `xsltproc not found` where the program is absent and
* `no XSLT transform provided` where it is present, and the two are split by
* platform — macOS ships `/usr/bin/xsltproc`, the Linux CI image does not. A
* contract test that matched on the program name therefore passed locally and
* failed in CI, which is how this list came to exist.
* The format Vale names per converter-dependent extension, from the `E100` text.
*
* Since 3.24.0 the error reads `asciidoctor not found; it's needed to read
* AsciiDoc`, and the format at the end is the host-independent half. Through
* 3.23.0 that half was a checker tag, `E100 [lintAdoc] Runtime error`, which
* 3.24.0's typed errors dropped. The program name is not host-independent:
* `.xml` reports `xsltproc not found` where the program is absent and `no XSLT
* transform provided` where it is present, and the two are split by platform —
* macOS ships `/usr/bin/xsltproc`, the Linux CI image does not. A contract
* test that matched on the program name therefore passed locally and failed in
* CI, which is how this list came to exist.
*
* `.xml` is also the one entry whose converter is not sufficient on its own. An
* XSLT transform is document-specific, so there is no default to ship and
* installing `xsltproc` does not make `.xml` lintable — unlike `asciidoctor`,
* which genuinely fixes `.adoc`. That is why its `converter` names the
* stylesheet as well as the program.
* stylesheet as well as the program, and why the stylesheet message, which
* names no format, is the one case the contract test accepts in its place.
*/
export const VALE_CONVERTER_CHECKERS: Readonly<Record<string, string>> = {
".rst": "lintRST",
".rest": "lintRST",
".adoc": "lintAdoc",
".asciidoc": "lintAdoc",
".asc": "lintAdoc",
".xml": "lintXML",
".dita": "lintDITA",
".typ": "lintTypst",
export const VALE_CONVERTER_READERS: Readonly<Record<string, string>> = {
".rst": "reStructuredText",
".rest": "reStructuredText",
".adoc": "AsciiDoc",
".asciidoc": "AsciiDoc",
".asc": "AsciiDoc",
".xml": "XML",
".dita": "DITA",
".typ": "Typst",
};

/** Every converter-dependent extension, flattened. */
Expand Down
6 changes: 5 additions & 1 deletion packages/cli/src/rules/rule-migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { join } from "node:path";

import { AST_GREP_VERSION, VALE_VERSION } from "./capabilities";
import haskellTypeSynonym from "./rule-migrations/sg-0-50-0-haskell-type-synonym";
import valeTestsToFixtures from "./rule-migrations/vale-3-24-0-tests-to-fixtures";
import { compareVersions } from "../util/version-compare";

/**
Expand Down Expand Up @@ -60,7 +61,10 @@ export interface AppliedRuleMigration {
files: string[];
}

const RULE_MIGRATIONS: readonly RuleMigration[] = [haskellTypeSynonym];
const RULE_MIGRATIONS: readonly RuleMigration[] = [
haskellTypeSynonym,
valeTestsToFixtures,
];

const BUNDLED: Record<RuleEngine, string> = {
sg: AST_GREP_VERSION,
Expand Down
Loading
Loading