fix: cycle references resolve to Label (symbol) instead of empty
When a {id:description} reference forms a cycle, the back-reference
now expands to 'Label (symbol)' rather than being stripped.
So pound ↔ dickload renders as 'See dickload (dl)' and 'See pound (lb)'.
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
"symbol": "lb",
|
||||
"group": "dickloads",
|
||||
"toBase": 0.038,
|
||||
"description": "{dickload:description}"
|
||||
"description": "My description is {dickload:description}"
|
||||
},
|
||||
{
|
||||
"id": "dickload",
|
||||
|
||||
@@ -15,14 +15,13 @@ import type { Unit, Group } from './types';
|
||||
* Replace {id:description} tokens with a sentinel \x00DESC:id\x00
|
||||
* (null-byte delimited — can't appear in user-authored text).
|
||||
* Pass 2: replace each sentinel with the Pass 1 output for the referenced id.
|
||||
* Since Pass 1 output contains only sentinels (not live {id:description}
|
||||
* tokens), this is one substitution deep. Any remaining sentinels after
|
||||
* Pass 2 (self-references, unresolvable cycles) are stripped to empty.
|
||||
* Any sentinels that survive after that substitution are cycles — they
|
||||
* are resolved as "Label (symbol)" (the {id} expansion) rather than
|
||||
* leaving an empty slot.
|
||||
*
|
||||
* Result for a mutual reference (id0 ↔ id2):
|
||||
* - id0's description contains id2's Pass-1 text (with id2's back-reference stripped)
|
||||
* - id2's description contains id0's Pass-1 text (with id0's back-reference stripped)
|
||||
* One level of expansion, then stops cleanly.
|
||||
* Result for a mutual reference (pound ↔ dickload):
|
||||
* pound's description: "See dickload (dl)"
|
||||
* dickload's description: "See pound (lb)"
|
||||
*/
|
||||
|
||||
// Null-byte delimiters can't appear in user-authored strings.
|
||||
@@ -94,15 +93,21 @@ export function resolveAllDescriptions(
|
||||
}
|
||||
|
||||
// Pass 2: replace sentinels with Pass 1 output of the referenced id.
|
||||
// Strip any remaining sentinels (self-refs, unresolvable cycles) → empty string.
|
||||
// Any sentinels surviving after that substitution are cycles — resolve them
|
||||
// as Label (symbol) instead of leaving an empty slot.
|
||||
const result = new Map<string, string>();
|
||||
for (const [id, p1] of pass1) {
|
||||
result.set(
|
||||
id,
|
||||
p1
|
||||
.replace(SENTINEL_RE, (_match, refId: string) => pass1.get(refId) ?? '')
|
||||
// Strip any sentinels that survived (self-references or nested cycles)
|
||||
.replace(SENTINEL_RE, '')
|
||||
.replace(SENTINEL_RE, (_match, refId: string) => {
|
||||
const u = unitMap.get(refId);
|
||||
if (u) return `${u.label} (${u.symbol})`;
|
||||
const g = groupMap.get(refId);
|
||||
if (g) return g.label;
|
||||
return '';
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -127,5 +132,11 @@ export function resolveDescription(
|
||||
const unit = unitMap.get(refId);
|
||||
return unit?.description ? resolveFields(unit.description, unitMap, groupMap) : '';
|
||||
})
|
||||
.replace(SENTINEL_RE, ''); // strip surviving sentinels
|
||||
.replace(SENTINEL_RE, (_match, refId: string) => {
|
||||
const u = unitMap.get(refId);
|
||||
if (u) return `${u.label} (${u.symbol})`;
|
||||
const g = groupMap.get(refId);
|
||||
if (g) return g.label;
|
||||
return '';
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user