@saykit/config 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,15 +2,51 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_hash = require("../../hash-51ce8YiG.cjs");
3
3
  //#region src/features/messages/identifier.ts
4
4
  const AUTO_INCREMENT_IDENTIFIER = Symbol("auto-increment");
5
- function assignSequenceIdentifiers(message, sequence = { current: 0 }) {
6
- if (message instanceof ArgumentMessage || message instanceof ElementMessage || message instanceof ChoiceMessage) {
7
- if (message.identifier === AUTO_INCREMENT_IDENTIFIER) message.identifier = `${sequence.current++}`;
5
+ function assignSequenceIdentifiers(message, sequence = { current: 0 }, equivalent = () => false) {
6
+ const reserved = collectAssignedIdentifiers(message, equivalent);
7
+ function next() {
8
+ let identifier = `${sequence.current++}`;
9
+ while (reserved.has(identifier)) identifier = `${sequence.current++}`;
10
+ return identifier;
8
11
  }
9
- if (message instanceof CompositeMessage || message instanceof ElementMessage) for (const child of message.children) assignSequenceIdentifiers(child, sequence);
10
- if (message instanceof ChoiceMessage) for (const branch of message.branches) {
11
- if (branch.identifier === AUTO_INCREMENT_IDENTIFIER) branch.identifier = `${sequence.current++}`;
12
- assignSequenceIdentifiers(branch.value, sequence);
12
+ function walk(message) {
13
+ if (message instanceof ArgumentMessage || message instanceof ElementMessage || message instanceof ChoiceMessage) {
14
+ if (message.identifier === AUTO_INCREMENT_IDENTIFIER) message.identifier = next();
15
+ }
16
+ if (message instanceof CompositeMessage || message instanceof ElementMessage) for (const child of message.children) walk(child);
17
+ if (message instanceof ChoiceMessage) for (const branch of message.branches) {
18
+ if (branch.identifier === AUTO_INCREMENT_IDENTIFIER) branch.identifier = next();
19
+ walk(branch.value);
20
+ }
21
+ }
22
+ walk(message);
23
+ }
24
+ /**
25
+ * Collect the identifiers already assigned before this pass runs, so generated
26
+ * sequence numbers never shadow an explicit one (e.g. an element tagged `0`).
27
+ *
28
+ * A name is claimed by what produced it, not by the name alone. Two that differ
29
+ * each compile to their own prop, and a translator moving them around a sentence
30
+ * has to be able to tell them apart, so they are a build error. Repeats are fine
31
+ * when nothing distinguishes them: the same variable interpolated twice is one
32
+ * value, and two identical elements are one tag.
33
+ */
34
+ function collectAssignedIdentifiers(message, equivalent) {
35
+ const tags = /* @__PURE__ */ new Map();
36
+ const values = /* @__PURE__ */ new Map();
37
+ function claim(claimed, identifier, expression, conflict) {
38
+ if (claimed.has(identifier) && !equivalent(claimed.get(identifier), expression)) throw new Error(conflict);
39
+ claimed.set(identifier, expression);
40
+ }
41
+ function walk(message) {
42
+ if (message instanceof ElementMessage && typeof message.identifier === "string") claim(tags, message.identifier, message.expression, `Duplicate element tag '${message.identifier}', give each element in a message its own tag unless they are identical`);
43
+ if ((message instanceof ArgumentMessage || message instanceof ChoiceMessage) && typeof message.identifier === "string") claim(values, message.identifier, message.expression, `Duplicate placeholder name '${message.identifier}', give each value in a message its own name unless they are identical`);
44
+ if (message instanceof CompositeMessage || message instanceof ElementMessage) for (const child of message.children) walk(child);
45
+ if (message instanceof ChoiceMessage) for (const branch of message.branches) walk(branch.value);
13
46
  }
47
+ walk(message);
48
+ for (const tag of tags.keys()) if (values.has(tag)) throw new Error(`Element tag '${tag}' collides with an argument of the same name`);
49
+ return /* @__PURE__ */ new Set([...tags.keys(), ...values.keys()]);
14
50
  }
15
51
  //#endregion
16
52
  //#region src/features/messages/types.ts
@@ -88,6 +124,7 @@ function convertMessageToIcu(message) {
88
124
  case message instanceof LiteralMessage: return String(message.text);
89
125
  case message instanceof ArgumentMessage: return `{${String(message.identifier)}}`;
90
126
  case message instanceof ElementMessage: {
127
+ if (message.children.length === 0) return `<${String(message.identifier)}/>`;
91
128
  const children = message.children.map((m) => internalConvertMessageToIcu(m)).join("");
92
129
  return `<${String(message.identifier)}>${children}</${String(message.identifier)}>`;
93
130
  }
@@ -1,8 +1,14 @@
1
1
  //#region src/features/messages/identifier.d.ts
2
2
  declare const AUTO_INCREMENT_IDENTIFIER: unique symbol;
3
+ /**
4
+ * Decides whether two placeholders sharing a name are the same placeholder, and
5
+ * so may share it. Only the syntax that produced them can answer that, so the
6
+ * caller supplies the comparison; by default nothing is interchangeable.
7
+ */
8
+ type PlaceholderEquivalence = (a: any, b: any) => boolean;
3
9
  declare function assignSequenceIdentifiers(message: Message, sequence?: {
4
10
  current: number;
5
- }): void;
11
+ }, equivalent?: PlaceholderEquivalence): void;
6
12
  //#endregion
7
13
  //#region src/features/messages/types.d.ts
8
14
  declare abstract class Base {
@@ -60,4 +66,4 @@ declare function convertMessageToIcu(message: Message): string;
60
66
  //#region src/features/messages/hash.d.ts
61
67
  declare function generateHash(input: string, context?: string): string;
62
68
  //#endregion
63
- export { AUTO_INCREMENT_IDENTIFIER, ArgumentMessage, ChoiceMessage, CompositeMessage, ElementMessage, LiteralMessage, Message, assignSequenceIdentifiers, convertMessageToIcu, generateHash };
69
+ export { AUTO_INCREMENT_IDENTIFIER, ArgumentMessage, ChoiceMessage, CompositeMessage, ElementMessage, LiteralMessage, Message, PlaceholderEquivalence, assignSequenceIdentifiers, convertMessageToIcu, generateHash };
@@ -1,8 +1,14 @@
1
1
  //#region src/features/messages/identifier.d.ts
2
2
  declare const AUTO_INCREMENT_IDENTIFIER: unique symbol;
3
+ /**
4
+ * Decides whether two placeholders sharing a name are the same placeholder, and
5
+ * so may share it. Only the syntax that produced them can answer that, so the
6
+ * caller supplies the comparison; by default nothing is interchangeable.
7
+ */
8
+ type PlaceholderEquivalence = (a: any, b: any) => boolean;
3
9
  declare function assignSequenceIdentifiers(message: Message, sequence?: {
4
10
  current: number;
5
- }): void;
11
+ }, equivalent?: PlaceholderEquivalence): void;
6
12
  //#endregion
7
13
  //#region src/features/messages/types.d.ts
8
14
  declare abstract class Base {
@@ -60,4 +66,4 @@ declare function convertMessageToIcu(message: Message): string;
60
66
  //#region src/features/messages/hash.d.ts
61
67
  declare function generateHash(input: string, context?: string): string;
62
68
  //#endregion
63
- export { AUTO_INCREMENT_IDENTIFIER, ArgumentMessage, ChoiceMessage, CompositeMessage, ElementMessage, LiteralMessage, Message, assignSequenceIdentifiers, convertMessageToIcu, generateHash };
69
+ export { AUTO_INCREMENT_IDENTIFIER, ArgumentMessage, ChoiceMessage, CompositeMessage, ElementMessage, LiteralMessage, Message, PlaceholderEquivalence, assignSequenceIdentifiers, convertMessageToIcu, generateHash };
@@ -1,15 +1,51 @@
1
1
  import { t as generateHash } from "../../hash-DvzpieJD.mjs";
2
2
  //#region src/features/messages/identifier.ts
3
3
  const AUTO_INCREMENT_IDENTIFIER = Symbol("auto-increment");
4
- function assignSequenceIdentifiers(message, sequence = { current: 0 }) {
5
- if (message instanceof ArgumentMessage || message instanceof ElementMessage || message instanceof ChoiceMessage) {
6
- if (message.identifier === AUTO_INCREMENT_IDENTIFIER) message.identifier = `${sequence.current++}`;
4
+ function assignSequenceIdentifiers(message, sequence = { current: 0 }, equivalent = () => false) {
5
+ const reserved = collectAssignedIdentifiers(message, equivalent);
6
+ function next() {
7
+ let identifier = `${sequence.current++}`;
8
+ while (reserved.has(identifier)) identifier = `${sequence.current++}`;
9
+ return identifier;
7
10
  }
8
- if (message instanceof CompositeMessage || message instanceof ElementMessage) for (const child of message.children) assignSequenceIdentifiers(child, sequence);
9
- if (message instanceof ChoiceMessage) for (const branch of message.branches) {
10
- if (branch.identifier === AUTO_INCREMENT_IDENTIFIER) branch.identifier = `${sequence.current++}`;
11
- assignSequenceIdentifiers(branch.value, sequence);
11
+ function walk(message) {
12
+ if (message instanceof ArgumentMessage || message instanceof ElementMessage || message instanceof ChoiceMessage) {
13
+ if (message.identifier === AUTO_INCREMENT_IDENTIFIER) message.identifier = next();
14
+ }
15
+ if (message instanceof CompositeMessage || message instanceof ElementMessage) for (const child of message.children) walk(child);
16
+ if (message instanceof ChoiceMessage) for (const branch of message.branches) {
17
+ if (branch.identifier === AUTO_INCREMENT_IDENTIFIER) branch.identifier = next();
18
+ walk(branch.value);
19
+ }
20
+ }
21
+ walk(message);
22
+ }
23
+ /**
24
+ * Collect the identifiers already assigned before this pass runs, so generated
25
+ * sequence numbers never shadow an explicit one (e.g. an element tagged `0`).
26
+ *
27
+ * A name is claimed by what produced it, not by the name alone. Two that differ
28
+ * each compile to their own prop, and a translator moving them around a sentence
29
+ * has to be able to tell them apart, so they are a build error. Repeats are fine
30
+ * when nothing distinguishes them: the same variable interpolated twice is one
31
+ * value, and two identical elements are one tag.
32
+ */
33
+ function collectAssignedIdentifiers(message, equivalent) {
34
+ const tags = /* @__PURE__ */ new Map();
35
+ const values = /* @__PURE__ */ new Map();
36
+ function claim(claimed, identifier, expression, conflict) {
37
+ if (claimed.has(identifier) && !equivalent(claimed.get(identifier), expression)) throw new Error(conflict);
38
+ claimed.set(identifier, expression);
39
+ }
40
+ function walk(message) {
41
+ if (message instanceof ElementMessage && typeof message.identifier === "string") claim(tags, message.identifier, message.expression, `Duplicate element tag '${message.identifier}', give each element in a message its own tag unless they are identical`);
42
+ if ((message instanceof ArgumentMessage || message instanceof ChoiceMessage) && typeof message.identifier === "string") claim(values, message.identifier, message.expression, `Duplicate placeholder name '${message.identifier}', give each value in a message its own name unless they are identical`);
43
+ if (message instanceof CompositeMessage || message instanceof ElementMessage) for (const child of message.children) walk(child);
44
+ if (message instanceof ChoiceMessage) for (const branch of message.branches) walk(branch.value);
12
45
  }
46
+ walk(message);
47
+ for (const tag of tags.keys()) if (values.has(tag)) throw new Error(`Element tag '${tag}' collides with an argument of the same name`);
48
+ return /* @__PURE__ */ new Set([...tags.keys(), ...values.keys()]);
13
49
  }
14
50
  //#endregion
15
51
  //#region src/features/messages/types.ts
@@ -87,6 +123,7 @@ function convertMessageToIcu(message) {
87
123
  case message instanceof LiteralMessage: return String(message.text);
88
124
  case message instanceof ArgumentMessage: return `{${String(message.identifier)}}`;
89
125
  case message instanceof ElementMessage: {
126
+ if (message.children.length === 0) return `<${String(message.identifier)}/>`;
90
127
  const children = message.children.map((m) => internalConvertMessageToIcu(m)).join("");
91
128
  return `<${String(message.identifier)}>${children}</${String(message.identifier)}>`;
92
129
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saykit/config",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "CLI and configuration tooling for saykit",
5
5
  "keywords": [
6
6
  "cli",