@glion/lint-profile-field-max-length 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-max-length
2
2
 
3
- Lint rule to validate field value lengths against HL7v2 profile maxLength.
3
+ Lint rule that reports a field value longer than HL7v2 allows.
4
4
 
5
5
  ## What it does
6
6
 
7
- Flags field values that exceed the `maxLength` declared in the HL7v2 profile for the message's version. Length is computed by recursively summing the string lengths of all subcomponent values in a repetition, so delimiters (`^`, `&`, `~`) are not counted. The rule reads profile context attached by `@glion/annotate-profile-context` and reports once per offending repetition. Fields without a declared `maxLength`, empty fields, and Z-segments are skipped.
7
+ Flags a field value longer than the maximum length the HL7v2 standard gives that field in the message's version. Each repetition of a field is measured on its own, and delimiters (`^`, `&`) are not counted. 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-max-length
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 hl7v2LintFieldMaxLength from "@glion/lint-profile-field-max-length";
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
- "MSH|^~\\&|SENDER|FAC|RECV|RFAC|20250601120000||ADT^A01^ADT_A01|MSG00001|P|2.5",
27
+ "MSH|^~\\&|SENDER|FAC|RECV|RFAC|20250601120000||ADT^A01^ADT_A01|MSG0000000000000000000000001|P|2.5",
26
28
  "PID|1||PATID1234^^^HOSP^MR||DOE^JANE||19800101|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(hl7v2LintFieldMaxLength)
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(hl7v2LintFieldMaxLength)`
41
44
 
42
- A `unified` lint rule plugin. Takes no options.
43
-
44
- Reads `file.data.profile`. For each segment in the tree it walks its fields, looks up the profile entry by sequence, and — if a `maxLength` is declared — compares it against `getLength()` from `@glion/utils` for every repetition. Each repetition over the limit produces one message.
45
+ A `unified` lint rule plugin. Takes no options. Reports one message for each field repetition over the limit.
45
46
 
46
47
  ```ts
47
48
  import type { Plugin } from "unified";
@@ -53,11 +54,11 @@ export default hl7v2LintFieldMaxLength;
53
54
 
54
55
  ## What it checks
55
56
 
56
- This rule flags fields whose measured content length is greater than the `maxLength` declared for that field in the HL7v2 profile. In v2.5, `MSH-10` (Message Control ID) has `maxLength: 20`.
57
+ Each repetition of a field is at most as long as the field's maximum length in the message's version. For example, `MSH-10` (Message Control ID) allows 20 characters in v2.5.
57
58
 
58
59
  ### Valid
59
60
 
60
- `MSH-10` carries a 9-character Message Control ID, well within the v2.5 limit of 20:
61
+ `MSH-10` has 8 characters:
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
- `MSH-10` is 28 characters long, exceeding the v2.5 limit of 20:
70
+ `MSH-10` has 28 characters:
70
71
 
71
72
  ```hl7
72
73
  MSH|^~\&|SENDER|FAC|RECV|RFAC|20250601120000||ADT^A01^ADT_A01|MSG0000000000000000000000001|P|2.5
@@ -76,10 +77,10 @@ PID|1||PATID1234^^^HOSP^MR||DOE^JANE||19800101|F
76
77
  Reported message:
77
78
 
78
79
  ```
79
- Field MSH-10 exceeds max length of 20 (actual: 28)
80
+ Field `MSH-10` (Message Control ID) is 28 characters long; it allows at most 20 characters.
80
81
  ```
81
82
 
82
- One message is produced per offending repetition. Delimiters are not included in the measured length — a composite value like `DOE^JANE` counts as 7 characters, not 8.
83
+ A composite value such as `DOE^JANE` counts as 7 characters, not 8.
83
84
 
84
85
  ## Part of Glion
85
86
 
package/dist/index.d.ts CHANGED
@@ -1,20 +1,17 @@
1
1
  import type { Root } from "@glion/ast";
2
2
  /**
3
- * Lint rule that validates field value lengths against HL7v2 profile maxLength.
3
+ * Lint rule that reports a field repetition longer than the field's LEN.
4
4
  *
5
- * For each field with a defined `maxLength` in the profile, checks that
6
- * each repetition's content length does not exceed the limit.
7
- *
8
- * The length is measured using `getLength()` from `hl7v2-utils`, which
9
- * recursively sums the string lengths of all subcomponent values.
10
- * Delimiters are not included in the count.
11
- *
12
- * Fields without `maxLength` in the profile and empty fields are skipped.
13
- * Segments without a known profile (e.g., Z-segments) are silently skipped.
5
+ * Reads `maxLength` from `field.data`, so `@glion/annotate-profile-fields`
6
+ * MUST run before it. The length of a repetition is the sum of its values,
7
+ * delimiters excluded.
14
8
  *
15
9
  * @example
16
10
  * ```typescript
17
- * unified().use(hl7v2LintFieldMaxLength);
11
+ * unified()
12
+ * .use(hl7v2AnnotateProfileContext)
13
+ * .use(hl7v2AnnotateProfileFields)
14
+ * .use(hl7v2LintFieldMaxLength);
18
15
  * ```;
19
16
  */
20
17
  declare const hl7v2LintFieldMaxLength: 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;AAKvC;;;;;;;;;;;;;;;;;GAiBG;AACH,QAAA,MAAM,uBAAuB,mDAqD5B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAWvC;;;;;;;;;;;;;;GAcG;AACH,QAAA,MAAM,uBAAuB,mDA8B5B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -1,50 +1,46 @@
1
1
  import { SKIP, visit } from "@glion/util-visit";
2
- import { getLength, isEmptyNode } from "@glion/utils";
2
+ import { getLength } from "@glion/utils";
3
+ import pluralize from "pluralize";
3
4
  import { lintRule } from "unified-lint-rule";
4
5
  //#region src/index.ts
6
+ const messages = { tooLong: (field, name, length, max) => `Field \`${field}\` (${name}) is ${pluralize("character", length, true)} long; it allows at most ${pluralize("character", max, true)}.` };
5
7
  /**
6
- * Lint rule that validates field value lengths against HL7v2 profile maxLength.
8
+ * Lint rule that reports a field repetition longer than the field's LEN.
7
9
  *
8
- * For each field with a defined `maxLength` in the profile, checks that
9
- * each repetition's content length does not exceed the limit.
10
- *
11
- * The length is measured using `getLength()` from `hl7v2-utils`, which
12
- * recursively sums the string lengths of all subcomponent values.
13
- * Delimiters are not included in the count.
14
- *
15
- * Fields without `maxLength` in the profile and empty fields are skipped.
16
- * Segments without a known profile (e.g., Z-segments) are silently skipped.
10
+ * Reads `maxLength` from `field.data`, so `@glion/annotate-profile-fields`
11
+ * MUST run before it. The length of a repetition is the sum of its values,
12
+ * delimiters excluded.
17
13
  *
18
14
  * @example
19
15
  * ```typescript
20
- * unified().use(hl7v2LintFieldMaxLength);
16
+ * unified()
17
+ * .use(hl7v2AnnotateProfileContext)
18
+ * .use(hl7v2AnnotateProfileFields)
19
+ * .use(hl7v2LintFieldMaxLength);
21
20
  * ```;
22
21
  */
23
- const hl7v2LintFieldMaxLength = lintRule({ origin: "hl7v2-lint:field-max-length" }, (tree, file) => {
24
- const ctx = file.data.profile;
25
- if (!ctx) return;
26
- visit(tree, "segment", (segment, segmentAncestors) => {
27
- const fieldDef = ctx.fields.get(segment.name);
28
- if (!fieldDef) return SKIP;
29
- visit(segment, "field", (fieldNode, _fieldAncestors, info) => {
30
- if (isEmptyNode(fieldNode)) return SKIP;
31
- const profile = fieldDef.bySequence.get(info.sequence);
32
- if (!profile?.maxLength) return SKIP;
33
- for (const repetition of fieldNode.children) {
34
- const len = getLength(repetition);
35
- if (len === 0) continue;
36
- if (len > profile.maxLength) file.message(`Field ${segment.name}-${info.sequence} exceeds max length of ${profile.maxLength} (actual: ${len})`, {
22
+ const hl7v2LintFieldMaxLength = lintRule({
23
+ origin: "hl7v2-lint:field-max-length",
24
+ url: "https://github.com/rethinkhealth/glion/tree/main/packages/lint-profile-field-max-length#readme"
25
+ }, (tree, file) => {
26
+ visit(tree, "field", (field, ancestors) => {
27
+ const id = field.data?.id;
28
+ const maxLength = field.data?.maxLength;
29
+ if (id === void 0 || maxLength === void 0) return SKIP;
30
+ for (const repetition of field.children) {
31
+ const length = getLength(repetition);
32
+ if (length > maxLength) {
33
+ const message = file.message(messages.tooLong(id, field.data?.name ?? "", length, maxLength), {
37
34
  ancestors: [
38
- ...segmentAncestors,
39
- segment,
40
- fieldNode,
35
+ ...ancestors,
36
+ field,
41
37
  repetition
42
38
  ],
43
- place: repetition.position ?? fieldNode.position
39
+ place: repetition.position ?? field.position
44
40
  });
41
+ message.actual = String(length);
45
42
  }
46
- return SKIP;
47
- });
43
+ }
48
44
  return SKIP;
49
45
  });
50
46
  });
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 { getLength, isEmptyNode } from \"@glion/utils\";\nimport { lintRule } from \"unified-lint-rule\";\n\n/**\n * Lint rule that validates field value lengths against HL7v2 profile maxLength.\n *\n * For each field with a defined `maxLength` in the profile, checks that\n * each repetition's content length does not exceed the limit.\n *\n * The length is measured using `getLength()` from `hl7v2-utils`, which\n * recursively sums the string lengths of all subcomponent values.\n * Delimiters are not included in the count.\n *\n * Fields without `maxLength` in the profile and empty fields are skipped.\n * Segments without a known profile (e.g., Z-segments) are silently skipped.\n *\n * @example\n * ```typescript\n * unified().use(hl7v2LintFieldMaxLength);\n * ```;\n */\nconst hl7v2LintFieldMaxLength = lintRule<Root>(\n { origin: \"hl7v2-lint:field-max-length\" },\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 if (isEmptyNode(fieldNode)) {\n return SKIP;\n }\n\n const profile = fieldDef.bySequence.get(info.sequence);\n\n if (!profile?.maxLength) {\n return SKIP;\n }\n\n for (const repetition of fieldNode.children) {\n const len = getLength(repetition);\n if (len === 0) {\n continue;\n }\n\n if (len > profile.maxLength) {\n file.message(\n `Field ${segment.name}-${info.sequence} exceeds max length of ${profile.maxLength} (actual: ${len})`,\n {\n ancestors: [\n ...segmentAncestors,\n segment,\n fieldNode,\n repetition,\n ],\n place: repetition.position ?? fieldNode.position,\n }\n );\n }\n }\n\n return SKIP;\n });\n\n return SKIP;\n });\n }\n);\n\nexport default hl7v2LintFieldMaxLength;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAM,0BAA0B,SAC9B,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;AAC5D,OAAI,YAAY,UAAU,CACxB,QAAO;GAGT,MAAM,UAAU,SAAS,WAAW,IAAI,KAAK,SAAS;AAEtD,OAAI,CAAC,SAAS,UACZ,QAAO;AAGT,QAAK,MAAM,cAAc,UAAU,UAAU;IAC3C,MAAM,MAAM,UAAU,WAAW;AACjC,QAAI,QAAQ,EACV;AAGF,QAAI,MAAM,QAAQ,UAChB,MAAK,QACH,SAAS,QAAQ,KAAK,GAAG,KAAK,SAAS,yBAAyB,QAAQ,UAAU,YAAY,IAAI,IAClG;KACE,WAAW;MACT,GAAG;MACH;MACA;MACA;MACD;KACD,OAAO,WAAW,YAAY,UAAU;KACzC,CACF;;AAIL,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 { getLength } from \"@glion/utils\";\nimport pluralize from \"pluralize\";\nimport { lintRule } from \"unified-lint-rule\";\n\nconst messages = {\n tooLong: (field: string, name: string, length: number, max: number) =>\n `Field \\`${field}\\` (${name}) is ${pluralize(\"character\", length, true)} long; it allows at most ${pluralize(\"character\", max, true)}.`,\n} as const;\n\n/**\n * Lint rule that reports a field repetition longer than the field's LEN.\n *\n * Reads `maxLength` from `field.data`, so `@glion/annotate-profile-fields`\n * MUST run before it. The length of a repetition is the sum of its values,\n * delimiters excluded.\n *\n * @example\n * ```typescript\n * unified()\n * .use(hl7v2AnnotateProfileContext)\n * .use(hl7v2AnnotateProfileFields)\n * .use(hl7v2LintFieldMaxLength);\n * ```;\n */\nconst hl7v2LintFieldMaxLength = lintRule<Root>(\n {\n origin: \"hl7v2-lint:field-max-length\",\n url: \"https://github.com/rethinkhealth/glion/tree/main/packages/lint-profile-field-max-length#readme\",\n },\n (tree, file) => {\n visit(tree, \"field\", (field, ancestors) => {\n const id = field.data?.id;\n const maxLength = field.data?.maxLength;\n if (id === undefined || maxLength === undefined) {\n return SKIP;\n }\n\n // HL7v2 applies LEN to each repetition, not to the whole field.\n for (const repetition of field.children) {\n const length = getLength(repetition);\n if (length > maxLength) {\n const message = file.message(\n messages.tooLong(id, field.data?.name ?? \"\", length, maxLength),\n {\n ancestors: [...ancestors, field, repetition],\n place: repetition.position ?? field.position,\n }\n );\n message.actual = String(length);\n }\n }\n return SKIP;\n });\n }\n);\n\nexport default hl7v2LintFieldMaxLength;\n"],"mappings":";;;;;AAQA,MAAM,WAAW,EACf,UAAU,OAAe,MAAc,QAAgB,QACrD,WAAW,MAAM,MAAM,KAAK,OAAO,UAAU,aAAa,QAAQ,KAAK,CAAC,2BAA2B,UAAU,aAAa,KAAK,KAAK,CAAC,IACxI;;;;;;;;;;;;;;;;AAiBD,MAAM,0BAA0B,SAC9B;CACE,QAAQ;CACR,KAAK;CACN,GACA,MAAM,SAAS;AACd,OAAM,MAAM,UAAU,OAAO,cAAc;EACzC,MAAM,KAAK,MAAM,MAAM;EACvB,MAAM,YAAY,MAAM,MAAM;AAC9B,MAAI,OAAO,KAAA,KAAa,cAAc,KAAA,EACpC,QAAO;AAIT,OAAK,MAAM,cAAc,MAAM,UAAU;GACvC,MAAM,SAAS,UAAU,WAAW;AACpC,OAAI,SAAS,WAAW;IACtB,MAAM,UAAU,KAAK,QACnB,SAAS,QAAQ,IAAI,MAAM,MAAM,QAAQ,IAAI,QAAQ,UAAU,EAC/D;KACE,WAAW;MAAC,GAAG;MAAW;MAAO;MAAW;KAC5C,OAAO,WAAW,YAAY,MAAM;KACrC,CACF;AACD,YAAQ,SAAS,OAAO,OAAO;;;AAGnC,SAAO;GACP;EAEL"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glion/lint-profile-field-max-length",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "Lint rule to validate field value lengths against HL7v2 profile maxLength",
5
5
  "keywords": [
6
6
  "health",
@@ -33,13 +33,16 @@
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",
39
- "@glion/utils": "0.23.0"
38
+ "@glion/util-visit": "0.24.0",
39
+ "@glion/annotate-profile-fields": "0.24.0",
40
+ "@glion/utils": "0.24.0",
41
+ "@glion/ast": "0.24.0"
40
42
  },
41
43
  "devDependencies": {
42
44
  "@types/node": "25.6.0",
45
+ "@types/pluralize": "^0.0.33",
43
46
  "@vitest/coverage-v8": "4.1.11",
44
47
  "publint": "0.3.18",
45
48
  "tsdown": "0.21.10",
@@ -47,11 +50,12 @@
47
50
  "unified": "^11.0.5",
48
51
  "vfile": "^6.0.3",
49
52
  "vitest": "4.1.11",
50
- "@glion/annotate-profile-context": "0.23.0",
51
- "@glion/builder": "0.23.0",
53
+ "@glion/builder": "0.24.0",
54
+ "@glion/annotate-profile-context": "0.24.0",
52
55
  "@glion/check-readme": "0.18.0",
53
56
  "@glion/testing": "0.18.0",
54
- "@glion/tsconfig": "0.18.0"
57
+ "@glion/tsconfig": "0.18.0",
58
+ "@glion/parser": "0.24.0"
55
59
  },
56
60
  "engines": {
57
61
  "node": ">=22"