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
4 changes: 3 additions & 1 deletion docs/e/codes/COOKLANG_E9401.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ title: Ratio cannot be computed

## What this error means

The ratio between two quantities cannot be computed because one of them is not numeric. This aborts the whole `parse()`/`scaleBy()`/`convertTo()` call; it is not collected alongside other diagnostics.
The ratio between two quantities cannot be computed because one of them is not numeric.

## Common causes

- Merging equivalents where one of the equivalent quantities is text-only, e.g. `@flour{1%cup|a bit}` then `@&flour{2%cup|a lot}`

During `Recipe.parse()`, this is collected with the other diagnostics and thrown after the source has been processed. During `scaleBy()` or `convertTo()`, it is thrown directly by the operation that needs the ratio.

## How to fix it

Use numeric quantities for every equivalent that needs to be merged with another occurrence of the same ingredient.
8 changes: 7 additions & 1 deletion src/classes/recipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ import {
ReferencedItemCannotBeRedefinedError,
NoTabAsIndentError,
BadIndentationError,
RatioNotComputableError,
DiagnosticError,
ParseStoppedError,
} from "../errors";
Expand Down Expand Up @@ -2095,7 +2096,12 @@ export class Recipe {
this.choices.variants = [...allVariants];
}

this._populateIngredientQuantities(true);
try {
this._populateIngredientQuantities(true);
} catch (error) {
if (!(error instanceof RatioNotComputableError)) throw error;
this.diagnostics.push(error.diagnostic);
}

// Diagnostics are reported in source order (sort is stable)
this.diagnostics.sort(
Expand Down
26 changes: 26 additions & 0 deletions test/diagnostics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,4 +399,30 @@ describe("Recipe.parse() throwing", () => {
times: 1,
});
});

it("collects COOKLANG_E9401 with the source during final quantity aggregation", () => {
const source = "Add @flour{1%cup|a bit}\nAdd @&flour{2%cup|a lot}";
const recipe = new Recipe();
let thrown: unknown;

try {
recipe.parse(source);
} catch (error) {
thrown = error;
}

expect(thrown).toBeInstanceOf(DiagnosticError);
expect(thrown).toMatchObject({
source,
diagnostics: [
{
code: "COOKLANG_E9401",
severity: "error",
},
],
});
expect(recipe.diagnostics).toEqual((thrown as DiagnosticError).diagnostics);
expect(recipe.ingredients).toHaveLength(1);
expect(recipe.sections).toHaveLength(1);
});
});
6 changes: 3 additions & 3 deletions test/parser_helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -940,9 +940,9 @@ describe("findAndUpsertCookware", () => {
},
};

expect(() => findAndUpsertCookware(cookware, newCookware, true)).toThrow(
DivisionByZeroError,
);
expect(() =>
findAndUpsertCookware(cookware, newCookware, true, []),
).toThrow(DivisionByZeroError);
});

it("should throw a ReferencedCookwareNotFoundError if a reference cookware does not exist", () => {
Expand Down
12 changes: 11 additions & 1 deletion test/recipe_parsing.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, it, expect } from "vitest";
import { Recipe } from "../src/classes/recipe";
import { ParseStoppedError } from "../src/errors";
import { DiagnosticError, ParseStoppedError } from "../src/errors";
import { parseCollect } from "./mocks/parse_collect";
import {
simpleRecipe,
Expand Down Expand Up @@ -77,6 +77,16 @@ describe("parse function", () => {
});
});

it("rethrows unexpected errors during final quantity aggregation", () => {
const recipe = new Recipe();
const unexpected = new Error("Unexpected aggregation failure");
Reflect.set(recipe, "_populateIngredientQuantities", () => {
throw unexpected;
});

expect(() => recipe.parse("@flour")).toThrow(unexpected);
});

it("stops immediately with COOKLANG_E1003 for a zero-denominator fraction", () => {
const source = "Add @flour{1/0}\nAdd @sugar{2}";
const recipe = new Recipe();
Expand Down
Loading