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

The `prefer-use-over-utilize` entry in `@taskless/cli/reference.json` now flags `utilized` and `utilization`, as its prompt asks, and carries a fail case for each. Previously the reference rule missed both, so a rule generated correctly from the prompt was graded against a reference that did not fire on those cases. The prompt now names the forms it expects. The corpus `version` is unchanged: the shape is the same, only the content of one entry moved.
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ ignorecase: true
swap:
utilize: use
utilizes: uses
utilized: used
utilizing: using
utilization: use
3 changes: 2 additions & 1 deletion packages/cli/assets/reference-vale/prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ suggests "use" instead.

"Utilize" is longer than "use" and means the same thing in nearly every
sentence a reader will meet. It should match case-insensitively and cover the
inflected forms.
inflected forms ("utilizes", "utilized", "utilizing") as well as the noun
"utilization".

This is a prose rule. It applies to markdown, and it must not look at source
code.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Limits

Memory utilization grows with the size of the batch.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Migration

We utilized the API to export the records.
24 changes: 22 additions & 2 deletions packages/cli/assets/reference.json
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,11 @@
"id": "prefer-use-over-utilize",
"directory": ".taskless/rules/vale/prefer-use-over-utilize",
"ruleFile": "prefer-use-over-utilize.yml",
"prompt": "Create a rule that flags the word \"utilize\" in markdown documentation and\nsuggests \"use\" instead.\n\n\"Utilize\" is longer than \"use\" and means the same thing in nearly every\nsentence a reader will meet. It should match case-insensitively and cover the\ninflected forms.\n\nThis is a prose rule. It applies to markdown, and it must not look at source\ncode.\n\nProse that should NOT be flagged:\n\n```md\nUse the installer to write the config file.\n```\n\nProse that SHOULD be flagged:\n\n```md\nUtilize the installer to write the config file.\n```\n",
"prompt": "Create a rule that flags the word \"utilize\" in markdown documentation and\nsuggests \"use\" instead.\n\n\"Utilize\" is longer than \"use\" and means the same thing in nearly every\nsentence a reader will meet. It should match case-insensitively and cover the\ninflected forms (\"utilizes\", \"utilized\", \"utilizing\") as well as the noun\n\"utilization\".\n\nThis is a prose rule. It applies to markdown, and it must not look at source\ncode.\n\nProse that should NOT be flagged:\n\n```md\nUse the installer to write the config file.\n```\n\nProse that SHOULD be flagged:\n\n```md\nUtilize the installer to write the config file.\n```\n",
"rule": [
{
"path": "prefer-use-over-utilize.yml",
"content": "extends: substitution\nmessage: \"Prefer '%s' over '%s'.\"\nlevel: warning\nignorecase: true\nswap:\n utilize: use\n utilizes: uses\n utilizing: using\n"
"content": "extends: substitution\nmessage: \"Prefer '%s' over '%s'.\"\nlevel: warning\nignorecase: true\nswap:\n utilize: use\n utilizes: uses\n utilized: used\n utilizing: using\n utilization: use\n"
},
{
"path": ".vale.ini",
Expand All @@ -206,6 +206,14 @@
{
"path": "tests/fail/utilize-in-prose/README.md",
"content": "# Setup\n\nUtilize the installer to write the config file.\n"
},
{
"path": "tests/fail/utilized-past/README.md",
"content": "# Migration\n\nWe utilized the API to export the records.\n"
},
{
"path": "tests/fail/utilization-noun/README.md",
"content": "# Limits\n\nMemory utilization grows with the size of the batch.\n"
}
],
"cases": [
Expand All @@ -220,6 +228,18 @@
"name": "utilize-in-prose",
"path": "tests/fail/utilize-in-prose",
"files": ["tests/fail/utilize-in-prose/README.md"]
},
{
"bucket": "fail",
"name": "utilized-past",
"path": "tests/fail/utilized-past",
"files": ["tests/fail/utilized-past/README.md"]
},
{
"bucket": "fail",
"name": "utilization-noun",
"path": "tests/fail/utilization-noun",
"files": ["tests/fail/utilization-noun/README.md"]
}
]
}
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/rules/corpus/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ export const CORPUS_MANIFESTS: readonly CorpusManifest[] = [
testPaths: [
"tests/pass/use-in-prose/README.md",
"tests/fail/utilize-in-prose/README.md",
"tests/fail/utilized-past/README.md",
"tests/fail/utilization-noun/README.md",
],
},
{
Expand Down
69 changes: 69 additions & 0 deletions packages/cli/test/reference-verify.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { readFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";

import { afterEach, describe, expect, it } from "vitest";

import type { Reference } from "../src/rules/reference";
import { findValeBinary } from "../src/rules/vale/binary";
import { verifyValeRule } from "../src/rules/vale/verify";

/**
* A reference rule passes its own held-out cases.
*
* Other teams grade a generated rule by running OUR rule against THEIR cases,
* so a reference narrower than its prompt marks a correct generation wrong.
* `prefer-use-over-utilize` shipped with a prompt asking for the inflected
* forms and a swap that lacked `utilized` (#591). Nothing ran the reference
* against its own cases, so nothing noticed. This stages the published entry
* exactly as a consumer would and verifies it.
*/

const referencePath = join(
import.meta.dirname,
"..",
"assets",
"reference.json"
);

const withVale = findValeBinary().path === undefined ? describe.skip : describe;

const workspaces: string[] = [];
afterEach(() => {
for (const workspace of workspaces.splice(0)) {
rmSync(workspace, { recursive: true, force: true });
}
});

/** Write a reference entry's rule and cases into a fresh project. */
function stage(rule: Reference["rules"][number]): string {
const cwd = mkdtempSync(join(tmpdir(), "taskless-reference-"));
workspaces.push(cwd);
for (const file of [...rule.rule, ...rule.tests.files]) {
const target = join(cwd, rule.directory, file.path);
mkdirSync(dirname(target), { recursive: true });
writeFileSync(target, file.content);
}
return cwd;
}

withVale("the vale reference rule", () => {
it("fires on every fail case and stays quiet on every pass case", async () => {
const reference = JSON.parse(
await readFile(referencePath, "utf8")
) as Reference;
const rule = reference.rules.find((entry) => entry.engine === "vale");
if (rule === undefined) throw new Error("reference has no vale rule");

const result = await verifyValeRule(stage(rule), rule.id);
if ("outcome" in result) {
throw new Error(
`expected a verification, got Vale ${result.outcome.status}`
);
}

expect(result.errors).toEqual([]);
expect(result.passed).toBe(true);
});
});
Loading