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
11 changes: 11 additions & 0 deletions .changeset/vale-3-24-0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@taskless/cli": patch
---

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.
12 changes: 6 additions & 6 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,11 @@
"@ast-grep/cli-win32-arm64-msvc": "0.51.0",
"@ast-grep/cli-win32-ia32-msvc": "0.51.0",
"@ast-grep/cli-win32-x64-msvc": "0.51.0",
"@taskless/vale-darwin-arm64": "3.23.0-20260930045834",
"@taskless/vale-darwin-x64": "3.23.0-20260930045834",
"@taskless/vale-linux-arm64": "3.23.0-20260930045834",
"@taskless/vale-linux-x64": "3.23.0-20260930045834",
"@taskless/vale-win32-arm64": "3.23.0-20260930045834",
"@taskless/vale-win32-x64": "3.23.0-20260930045834"
"@taskless/vale-darwin-arm64": "3.24.0-20261005234241",
"@taskless/vale-darwin-x64": "3.24.0-20261005234241",
"@taskless/vale-linux-arm64": "3.24.0-20261005234241",
"@taskless/vale-linux-x64": "3.24.0-20261005234241",
"@taskless/vale-win32-arm64": "3.24.0-20261005234241",
"@taskless/vale-win32-x64": "3.24.0-20261005234241"
}
}
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
12 changes: 6 additions & 6 deletions packages/cli/src/generated/vale-vocabulary-report.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Vale 3.23.0 vocabulary: divergence report
# Vale 3.24.0 vocabulary: divergence report

GENERATED FILE — DO NOT EDIT. Produced by `pnpm generate:vale-schema`
alongside `vale-vocabulary.ts`.

Every value in the vocabulary is the recorded answer of the vendored Vale
3.23.0 binary. This file is what the binary said that its own
3.24.0 binary. This file is what the binary said that its own
documentation does not, in both directions. A generator that dropped these
would be quietly deciding which of the two to believe.

Expand All @@ -28,25 +28,25 @@ the worse failure.

### `scope: meta`

Vale 3.23.0 documents this operand and it never fired, on any fixture probed (.md).
Vale 3.24.0 documents this operand and it never fired, on any fixture probed (.md).

**Consequence.** It is omitted from the vocabulary, so `validate` rejects it. A rule written from the documentation would otherwise load, run, and match nothing, with no error reported anywhere.

### `scope: meta.class.title`

Vale 3.23.0 documents this operand and it never fired, on any fixture probed (.md).
Vale 3.24.0 documents this operand and it never fired, on any fixture probed (.md).

**Consequence.** It is omitted from the vocabulary, so `validate` rejects it. A rule written from the documentation would otherwise load, run, and match nothing, with no error reported anywhere.

### `scope: frontmatter`

This operand fired and Vale 3.23.0 documents it nowhere.
This operand fired and Vale 3.24.0 documents it nowhere.

**Consequence.** It is included in the vocabulary. It is also the standing counterexample to trusting the candidate list: a real operand nobody proposes is simply absent, and the schema then rejects a rule the binary honors.

### `scope: frontmatter.title`

This operand fired and Vale 3.23.0 documents it nowhere.
This operand fired and Vale 3.24.0 documents it nowhere.

**Consequence.** It is included in the vocabulary. It is also the standing counterexample to trusting the candidate list: a real operand nobody proposes is simply absent, and the schema then rejects a rule the binary honors.

Expand Down
14 changes: 7 additions & 7 deletions packages/cli/src/generated/vale-vocabulary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* `scripts/generate-vale-schema.ts`, and its header explains what each value
* below was measured with and what it is worth.
*
* Derived against Vale 3.23.0. Every value here is the recorded answer
* Derived against Vale 3.24.0. Every value here is the recorded answer
* of that binary to a rule the generator wrote and ran; nothing is transcribed
* from documentation. Where the binary and the documentation disagree, the
* disagreement is in `vale-vocabulary-report.md` rather than dropped.
Expand All @@ -15,7 +15,7 @@
*/

/** The binary this vocabulary was derived from. */
export const VALE_VOCABULARY_VERSION = "3.23.0";
export const VALE_VOCABULARY_VERSION = "3.24.0";

/**
* Vale's check types, self-enumerated: an unknown `extends` makes the binary
Expand Down Expand Up @@ -199,7 +199,7 @@ export const VALE_SCOPE_PREFIXES = [
] as const;

/**
* Where Vale 3.23.0 and its documentation disagree.
* Where Vale 3.24.0 and its documentation disagree.
*
* Carried in the artifact rather than only in the report, so that a consumer
* can render them and a reviewer cannot miss them in a diff.
Expand All @@ -208,26 +208,26 @@ export const VALE_DIVERGENCES = [
{
subject: "scope: meta",
finding:
"Vale 3.23.0 documents this operand and it never fired, on any fixture probed (.md).",
"Vale 3.24.0 documents this operand and it never fired, on any fixture probed (.md).",
consequence:
"It is omitted from the vocabulary, so `validate` rejects it. A rule written from the documentation would otherwise load, run, and match nothing, with no error reported anywhere.",
},
{
subject: "scope: meta.class.title",
finding:
"Vale 3.23.0 documents this operand and it never fired, on any fixture probed (.md).",
"Vale 3.24.0 documents this operand and it never fired, on any fixture probed (.md).",
consequence:
"It is omitted from the vocabulary, so `validate` rejects it. A rule written from the documentation would otherwise load, run, and match nothing, with no error reported anywhere.",
},
{
subject: "scope: frontmatter",
finding: "This operand fired and Vale 3.23.0 documents it nowhere.",
finding: "This operand fired and Vale 3.24.0 documents it nowhere.",
consequence:
"It is included in the vocabulary. It is also the standing counterexample to trusting the candidate list: a real operand nobody proposes is simply absent, and the schema then rejects a rule the binary honors.",
},
{
subject: "scope: frontmatter.title",
finding: "This operand fired and Vale 3.23.0 documents it nowhere.",
finding: "This operand fired and Vale 3.24.0 documents it nowhere.",
consequence:
"It is included in the vocabulary. It is also the standing counterexample to trusting the candidate list: a real operand nobody proposes is simply absent, and the schema then rejects a rule the binary honors.",
},
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
42 changes: 23 additions & 19 deletions packages/cli/src/rules/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export const AST_GREP_TSX_SPLIT: Readonly<
* Pinned against the binary by `test/vale-vendor-contract.test.ts`
* ("engine capabilities" → "reports the pinned version").
*/
export const VALE_VERSION = "3.23.0";
export const VALE_VERSION = "3.24.0";

/**
* Which tier Vale routes an extension to.
Expand Down 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