@glion/lint-profile-field-repetition 0.23.0 → 0.24.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.
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # @glion/lint-profile-field-repetition
2
2
 
3
- Lint rule that flags non-repeatable fields with multiple repetitions.
3
+ Lint rule that reports a field with more repetitions than HL7v2 allows.
4
4
 
5
5
  ## What it does
6
6
 
7
- Flags fields that contain more than one repetition (delimited by `~`) when the HL7v2 profile for the message's version declares `repeatable: false` for that field. The rule reads profile context attached by `@glion/annotate-profile-context`. A single repetition is always valid regardless of the `repeatable` flag; only 2+ repetitions on a non-repeatable field are reported. Segments without a known profile are silently skipped.
7
+ Flags a field with more repetitions (separated by `~`) than the HL7v2 standard for the message's version allows: one for a field that does not repeat, or a set number such as 2 for `OBR-17` in v2.6. Fields with no limit on repetitions are never reported. It needs the plugins `@glion/annotate-profile-context` and `@glion/annotate-profile-fields` to run before it; `@glion/preset-lint-profile-recommended` includes them.
8
8
 
9
9
  ## Install
10
10
 
@@ -16,21 +16,24 @@ npm install @glion/lint-profile-field-repetition
16
16
 
17
17
  ```ts
18
18
  import { hl7v2AnnotateProfileContext } from "@glion/annotate-profile-context";
19
- import { hl7v2Parser } from "@glion/parser";
19
+ import { hl7v2AnnotateProfileFields } from "@glion/annotate-profile-fields";
20
20
  import hl7v2LintFieldRepetition from "@glion/lint-profile-field-repetition";
21
+ import { parseHL7v2 } from "@glion/parser";
21
22
  import { unified } from "unified";
23
+ import { VFile } from "vfile";
22
24
  import { reporter } from "vfile-reporter";
23
25
 
24
26
  const message = [
25
27
  "MSH|^~\\&|SENDER|FAC|RECV|RFAC|20250601120000||ADT^A01^ADT_A01|MSG00001|P|2.5",
26
- "PID|1||PATID1234^^^HOSP^MR||DOE^JANE||19800101|F",
28
+ "PID|1||PATID1234^^^HOSP^MR||DOE^JANE||19800101~19800102|F",
27
29
  ].join("\r");
28
30
 
29
- const file = await unified()
30
- .use(hl7v2Parser)
31
+ const file = new VFile();
32
+ await unified()
31
33
  .use(hl7v2AnnotateProfileContext)
34
+ .use(hl7v2AnnotateProfileFields)
32
35
  .use(hl7v2LintFieldRepetition)
33
- .process(message);
36
+ .run(parseHL7v2(message), file);
34
37
 
35
38
  console.error(reporter([file]));
36
39
  ```
@@ -39,9 +42,7 @@ console.error(reporter([file]));
39
42
 
40
43
  ### `unified().use(hl7v2LintFieldRepetition)`
41
44
 
42
- A `unified` lint rule plugin. Takes no options.
43
-
44
- Reads `file.data.profile`. For each field under a segment with a known profile it looks up the field entry by sequence. If the field's profile sets `repeatable: false` and the AST has more than one repetition, the rule emits one message for that field.
45
+ A `unified` lint rule plugin. Takes no options. Reports one message for each field with too many repetitions.
45
46
 
46
47
  ```ts
47
48
  import type { Plugin } from "unified";
@@ -53,11 +54,11 @@ export default hl7v2LintFieldRepetition;
53
54
 
54
55
  ## What it checks
55
56
 
56
- This rule flags fields with multiple `~`-delimited repetitions when the profile marks the field as non-repeatable. For example, `PID-7` (Date of Birth) is not repeatable in v2.5.
57
+ A field has at most as many repetitions as its repetition limit in the message's version. For example, `PID-7` (Date/Time of Birth) does not repeat in v2.5.
57
58
 
58
59
  ### Valid
59
60
 
60
- `PID-7` carries a single Date of Birth:
61
+ `PID-7` has one value:
61
62
 
62
63
  ```hl7
63
64
  MSH|^~\&|SENDER|FAC|RECV|RFAC|20250601120000||ADT^A01^ADT_A01|MSG00001|P|2.5
@@ -66,7 +67,7 @@ PID|1||PATID1234^^^HOSP^MR||DOE^JANE||19800101|F
66
67
 
67
68
  ### Invalid
68
69
 
69
- `PID-7` carries two `~`-separated values, which violates the v2.5 profile for the `PID-7` field:
70
+ `PID-7` has two values:
70
71
 
71
72
  ```hl7
72
73
  MSH|^~\&|SENDER|FAC|RECV|RFAC|20250601120000||ADT^A01^ADT_A01|MSG00001|P|2.5
@@ -76,11 +77,9 @@ PID|1||PATID1234^^^HOSP^MR||DOE^JANE||19800101~19800102|F
76
77
  Reported message:
77
78
 
78
79
  ```
79
- Field PID-7 (Date/Time of Birth) is not repeatable but has 2 repetitions
80
+ Field `PID-7` (Date/Time of Birth) has 2 repetitions; it allows at most 1 repetition.
80
81
  ```
81
82
 
82
- When the field name is available in the profile it appears in parentheses after the position. The reported count equals the number of repetitions present in the field.
83
-
84
83
  ## Part of Glion
85
84
 
86
85
  `@glion/lint-profile-field-repetition` is part of **[Glion]**, the application framework for HL7v2. See the [Glion README] for the full package catalog and architecture.
package/dist/index.d.ts CHANGED
@@ -1,17 +1,17 @@
1
1
  import type { Root } from "@glion/ast";
2
2
  /**
3
- * Lint rule that flags fields with multiple repetitions when the profile
4
- * declares `repeatable: false`.
3
+ * Lint rule that reports a field with more repetitions than its RP/# allows.
5
4
  *
6
- * A field with a single repetition is always valid regardless of the
7
- * `repeatable` flag. Only fields with 2+ repetitions on a non-repeatable
8
- * field are reported.
9
- *
10
- * Segments without a known profile (e.g., Z-segments) are silently skipped.
5
+ * Reads `repetitions` from `field.data`, so `@glion/annotate-profile-fields`
6
+ * MUST run before it. A field whose `repetitions` is `"unbounded"` is never
7
+ * reported.
11
8
  *
12
9
  * @example
13
10
  * ```typescript
14
- * unified().use(hl7v2LintFieldRepetition);
11
+ * unified()
12
+ * .use(hl7v2AnnotateProfileContext)
13
+ * .use(hl7v2AnnotateProfileFields)
14
+ * .use(hl7v2LintFieldRepetition);
15
15
  * ```;
16
16
  */
17
17
  declare const hl7v2LintFieldRepetition: import("unified-lint-rule").Plugin<Root, unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIvC;;;;;;;;;;;;;;GAcG;AACH,QAAA,MAAM,wBAAwB,mDAuC7B,CAAC;AAEF,eAAe,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAevC;;;;;;;;;;;;;;GAcG;AACH,QAAA,MAAM,wBAAwB,mDAuB7B,CAAC;AAEF,eAAe,wBAAwB,CAAC"}
package/dist/index.js CHANGED
@@ -1,44 +1,37 @@
1
1
  import { SKIP, visit } from "@glion/util-visit";
2
+ import pluralize from "pluralize";
2
3
  import { lintRule } from "unified-lint-rule";
3
4
  //#region src/index.ts
5
+ const messages = { tooManyRepetitions: (field, name, count, max) => `Field \`${field}\` (${name}) has ${pluralize("repetition", count, true)}; it allows at most ${pluralize("repetition", max, true)}.` };
4
6
  /**
5
- * Lint rule that flags fields with multiple repetitions when the profile
6
- * declares `repeatable: false`.
7
+ * Lint rule that reports a field with more repetitions than its RP/# allows.
7
8
  *
8
- * A field with a single repetition is always valid regardless of the
9
- * `repeatable` flag. Only fields with 2+ repetitions on a non-repeatable
10
- * field are reported.
11
- *
12
- * Segments without a known profile (e.g., Z-segments) are silently skipped.
9
+ * Reads `repetitions` from `field.data`, so `@glion/annotate-profile-fields`
10
+ * MUST run before it. A field whose `repetitions` is `"unbounded"` is never
11
+ * reported.
13
12
  *
14
13
  * @example
15
14
  * ```typescript
16
- * unified().use(hl7v2LintFieldRepetition);
15
+ * unified()
16
+ * .use(hl7v2AnnotateProfileContext)
17
+ * .use(hl7v2AnnotateProfileFields)
18
+ * .use(hl7v2LintFieldRepetition);
17
19
  * ```;
18
20
  */
19
- const hl7v2LintFieldRepetition = lintRule({ origin: "hl7v2-lint:field-repetition" }, (tree, file) => {
20
- const ctx = file.data.profile;
21
- if (!ctx) return;
22
- visit(tree, "segment", (segment, segmentAncestors) => {
23
- const fieldDef = ctx.fields.get(segment.name);
24
- if (!fieldDef) return SKIP;
25
- visit(segment, "field", (fieldNode, _fieldAncestors, info) => {
26
- const profile = fieldDef.bySequence.get(info.sequence);
27
- if (!profile || profile.repeatable) return SKIP;
28
- const count = fieldNode.children.length;
29
- if (count > 1) {
30
- const name = profile.name ? ` (${profile.name})` : "";
31
- file.message(`Field ${segment.name}-${info.sequence}${name} is not repeatable but has ${count} repetitions`, {
32
- ancestors: [
33
- ...segmentAncestors,
34
- segment,
35
- fieldNode
36
- ],
37
- place: fieldNode.position
38
- });
39
- }
40
- return SKIP;
21
+ const hl7v2LintFieldRepetition = lintRule({
22
+ origin: "hl7v2-lint:field-repetition",
23
+ url: "https://github.com/rethinkhealth/glion/tree/main/packages/lint-profile-field-repetition#readme"
24
+ }, (tree, file) => {
25
+ visit(tree, "field", (field, ancestors) => {
26
+ const id = field.data?.id;
27
+ const limit = field.data?.repetitions;
28
+ const count = field.children.length;
29
+ if (id === void 0 || typeof limit !== "number" || count <= limit) return SKIP;
30
+ const message = file.message(messages.tooManyRepetitions(id, field.data?.name ?? "", count, limit), {
31
+ ancestors: [...ancestors, field],
32
+ place: field.position
41
33
  });
34
+ message.actual = String(count);
42
35
  return SKIP;
43
36
  });
44
37
  });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["// oxlint-disable-next-line no-unused-vars -- triggers VFile DataMap augmentation\nimport type { ProfileContext } from \"@glion/annotate-profile-context\";\nimport type { Root } from \"@glion/ast\";\nimport { SKIP, visit } from \"@glion/util-visit\";\nimport { lintRule } from \"unified-lint-rule\";\n\n/**\n * Lint rule that flags fields with multiple repetitions when the profile\n * declares `repeatable: false`.\n *\n * A field with a single repetition is always valid regardless of the\n * `repeatable` flag. Only fields with 2+ repetitions on a non-repeatable\n * field are reported.\n *\n * Segments without a known profile (e.g., Z-segments) are silently skipped.\n *\n * @example\n * ```typescript\n * unified().use(hl7v2LintFieldRepetition);\n * ```;\n */\nconst hl7v2LintFieldRepetition = lintRule<Root>(\n { origin: \"hl7v2-lint:field-repetition\" },\n (tree, file) => {\n const ctx = file.data.profile;\n if (!ctx) {\n return;\n }\n\n visit(tree, \"segment\", (segment, segmentAncestors) => {\n const fieldDef = ctx.fields.get(segment.name);\n if (!fieldDef) {\n return SKIP;\n }\n\n visit(segment, \"field\", (fieldNode, _fieldAncestors, info) => {\n const profile = fieldDef.bySequence.get(info.sequence);\n\n if (!profile || profile.repeatable) {\n return SKIP;\n }\n\n const count = fieldNode.children.length;\n if (count > 1) {\n const name = profile.name ? ` (${profile.name})` : \"\";\n file.message(\n `Field ${segment.name}-${info.sequence}${name} is not repeatable but has ${count} repetitions`,\n {\n ancestors: [...segmentAncestors, segment, fieldNode],\n place: fieldNode.position,\n }\n );\n }\n\n return SKIP;\n });\n\n return SKIP;\n });\n }\n);\n\nexport default hl7v2LintFieldRepetition;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqBA,MAAM,2BAA2B,SAC/B,EAAE,QAAQ,+BAA+B,GACxC,MAAM,SAAS;CACd,MAAM,MAAM,KAAK,KAAK;AACtB,KAAI,CAAC,IACH;AAGF,OAAM,MAAM,YAAY,SAAS,qBAAqB;EACpD,MAAM,WAAW,IAAI,OAAO,IAAI,QAAQ,KAAK;AAC7C,MAAI,CAAC,SACH,QAAO;AAGT,QAAM,SAAS,UAAU,WAAW,iBAAiB,SAAS;GAC5D,MAAM,UAAU,SAAS,WAAW,IAAI,KAAK,SAAS;AAEtD,OAAI,CAAC,WAAW,QAAQ,WACtB,QAAO;GAGT,MAAM,QAAQ,UAAU,SAAS;AACjC,OAAI,QAAQ,GAAG;IACb,MAAM,OAAO,QAAQ,OAAO,KAAK,QAAQ,KAAK,KAAK;AACnD,SAAK,QACH,SAAS,QAAQ,KAAK,GAAG,KAAK,WAAW,KAAK,6BAA6B,MAAM,eACjF;KACE,WAAW;MAAC,GAAG;MAAkB;MAAS;MAAU;KACpD,OAAO,UAAU;KAClB,CACF;;AAGH,UAAO;IACP;AAEF,SAAO;GACP;EAEL"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["// oxlint-disable-next-line no-unused-vars -- triggers FieldData augmentation\nimport type { hl7v2AnnotateProfileFields } from \"@glion/annotate-profile-fields\";\nimport type { Root } from \"@glion/ast\";\nimport { SKIP, visit } from \"@glion/util-visit\";\nimport pluralize from \"pluralize\";\nimport { lintRule } from \"unified-lint-rule\";\n\nconst messages = {\n tooManyRepetitions: (\n field: string,\n name: string,\n count: number,\n max: number\n ) =>\n `Field \\`${field}\\` (${name}) has ${pluralize(\"repetition\", count, true)}; it allows at most ${pluralize(\"repetition\", max, true)}.`,\n} as const;\n\n/**\n * Lint rule that reports a field with more repetitions than its RP/# allows.\n *\n * Reads `repetitions` from `field.data`, so `@glion/annotate-profile-fields`\n * MUST run before it. A field whose `repetitions` is `\"unbounded\"` is never\n * reported.\n *\n * @example\n * ```typescript\n * unified()\n * .use(hl7v2AnnotateProfileContext)\n * .use(hl7v2AnnotateProfileFields)\n * .use(hl7v2LintFieldRepetition);\n * ```;\n */\nconst hl7v2LintFieldRepetition = lintRule<Root>(\n {\n origin: \"hl7v2-lint:field-repetition\",\n url: \"https://github.com/rethinkhealth/glion/tree/main/packages/lint-profile-field-repetition#readme\",\n },\n (tree, file) => {\n visit(tree, \"field\", (field, ancestors) => {\n const id = field.data?.id;\n const limit = field.data?.repetitions;\n const count = field.children.length;\n // A limit of \"unbounded\" is never exceeded.\n if (id === undefined || typeof limit !== \"number\" || count <= limit) {\n return SKIP;\n }\n\n const message = file.message(\n messages.tooManyRepetitions(id, field.data?.name ?? \"\", count, limit),\n { ancestors: [...ancestors, field], place: field.position }\n );\n message.actual = String(count);\n return SKIP;\n });\n }\n);\n\nexport default hl7v2LintFieldRepetition;\n"],"mappings":";;;;AAOA,MAAM,WAAW,EACf,qBACE,OACA,MACA,OACA,QAEA,WAAW,MAAM,MAAM,KAAK,QAAQ,UAAU,cAAc,OAAO,KAAK,CAAC,sBAAsB,UAAU,cAAc,KAAK,KAAK,CAAC,IACrI;;;;;;;;;;;;;;;;AAiBD,MAAM,2BAA2B,SAC/B;CACE,QAAQ;CACR,KAAK;CACN,GACA,MAAM,SAAS;AACd,OAAM,MAAM,UAAU,OAAO,cAAc;EACzC,MAAM,KAAK,MAAM,MAAM;EACvB,MAAM,QAAQ,MAAM,MAAM;EAC1B,MAAM,QAAQ,MAAM,SAAS;AAE7B,MAAI,OAAO,KAAA,KAAa,OAAO,UAAU,YAAY,SAAS,MAC5D,QAAO;EAGT,MAAM,UAAU,KAAK,QACnB,SAAS,mBAAmB,IAAI,MAAM,MAAM,QAAQ,IAAI,OAAO,MAAM,EACrE;GAAE,WAAW,CAAC,GAAG,WAAW,MAAM;GAAE,OAAO,MAAM;GAAU,CAC5D;AACD,UAAQ,SAAS,OAAO,MAAM;AAC9B,SAAO;GACP;EAEL"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glion/lint-profile-field-repetition",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "Lint rule that flags non-repeatable fields with multiple repetitions",
5
5
  "keywords": [
6
6
  "health",
@@ -33,12 +33,15 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
+ "pluralize": "8.0.0",
36
37
  "unified-lint-rule": "3.0.1",
37
- "@glion/ast": "0.23.0",
38
- "@glion/util-visit": "0.23.0"
38
+ "@glion/annotate-profile-fields": "0.24.0",
39
+ "@glion/ast": "0.24.0",
40
+ "@glion/util-visit": "0.24.0"
39
41
  },
40
42
  "devDependencies": {
41
43
  "@types/node": "25.6.0",
44
+ "@types/pluralize": "^0.0.33",
42
45
  "@vitest/coverage-v8": "4.1.11",
43
46
  "publint": "0.3.18",
44
47
  "tsdown": "0.21.10",
@@ -46,9 +49,10 @@
46
49
  "unified": "^11.0.5",
47
50
  "vfile": "^6.0.3",
48
51
  "vitest": "4.1.11",
49
- "@glion/annotate-profile-context": "0.23.0",
50
- "@glion/builder": "0.23.0",
52
+ "@glion/annotate-profile-context": "0.24.0",
53
+ "@glion/builder": "0.24.0",
51
54
  "@glion/check-readme": "0.18.0",
55
+ "@glion/parser": "0.24.0",
52
56
  "@glion/testing": "0.18.0",
53
57
  "@glion/tsconfig": "0.18.0"
54
58
  },