@glion/lint-profile-required-fields 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-required-fields
2
2
 
3
- Lint rule to validate required fields per HL7v2 profile.
3
+ Lint rule that reports a required field that is missing or empty.
4
4
 
5
5
  ## What it does
6
6
 
7
- Flags segments whose required fields (as declared by the HL7v2 profile for the message's version) are missing or empty. The rule reads profile context attached to the file by `@glion/annotate-profile-context` and reports one message per missing required field. Segments without a known profile (for example Z-segments like `ZPD`) are silently skipped.
7
+ Flags a field that the HL7v2 standard for the message's version marks `R` (required) when it is missing or empty. Segments the standard does not define, such as Z-segments, are skipped. It needs the plugin `@glion/annotate-profile-context` to run before it; `@glion/preset-lint-profile-recommended` includes it.
8
8
 
9
9
  ## Install
10
10
 
@@ -16,21 +16,22 @@ npm install @glion/lint-profile-required-fields
16
16
 
17
17
  ```ts
18
18
  import { hl7v2AnnotateProfileContext } from "@glion/annotate-profile-context";
19
- import { hl7v2Parser } from "@glion/parser";
20
19
  import hl7v2LintRequiredFields from "@glion/lint-profile-required-fields";
20
+ import { parseHL7v2 } from "@glion/parser";
21
21
  import { unified } from "unified";
22
+ import { VFile } from "vfile";
22
23
  import { reporter } from "vfile-reporter";
23
24
 
24
25
  const message = [
25
26
  "MSH|^~\\&|SENDER|FAC|RECV|RFAC|20250601120000||ADT^A01^ADT_A01|MSG00001|P|2.5",
26
- "PID|1||PATID1234^^^HOSP^MR||DOE^JANE||19800101|F",
27
+ "PID|1||||DOE^JANE||19800101|F",
27
28
  ].join("\r");
28
29
 
29
- const file = await unified()
30
- .use(hl7v2Parser)
30
+ const file = new VFile();
31
+ await unified()
31
32
  .use(hl7v2AnnotateProfileContext)
32
33
  .use(hl7v2LintRequiredFields)
33
- .process(message);
34
+ .run(parseHL7v2(message), file);
34
35
 
35
36
  console.error(reporter([file]));
36
37
  ```
@@ -39,9 +40,7 @@ console.error(reporter([file]));
39
40
 
40
41
  ### `unified().use(hl7v2LintRequiredFields)`
41
42
 
42
- A `unified` lint rule plugin. Takes no options.
43
-
44
- The plugin reads `file.data.profile` (populated by `@glion/annotate-profile-context`). If no profile context is present the rule exits silently. For each segment, it iterates `fieldDef.requiredSequences` and checks that the corresponding field at that 1-based position is present and non-empty.
43
+ A `unified` lint rule plugin. Takes no options. Reports one message for each required field that is missing or empty.
45
44
 
46
45
  ```ts
47
46
  import type { Plugin } from "unified";
@@ -53,11 +52,11 @@ export default hl7v2LintRequiredFields;
53
52
 
54
53
  ## What it checks
55
54
 
56
- This rule flags fields declared `required: true` in the HL7v2 profile for the message's version when those fields are missing or empty on the corresponding segment.
55
+ Every field marked `R` in the message's version is present and not empty. For example, in v2.5 `PID-3` (Patient Identifier List) and `PID-5` (Patient Name) are required.
57
56
 
58
57
  ### Valid
59
58
 
60
- `PID-3` (Patient Identifier List) and `PID-5` (Patient Name) are required in v2.5 and both are populated:
59
+ `PID-3` and `PID-5` both have values:
61
60
 
62
61
  ```hl7
63
62
  MSH|^~\&|SENDER|FAC|RECV|RFAC|20250601120000||ADT^A01^ADT_A01|MSG00001|P|2.5
@@ -66,7 +65,7 @@ PID|1||PATID1234^^^HOSP^MR||DOE^JANE||19800101|F
66
65
 
67
66
  ### Invalid
68
67
 
69
- `PID-3` is empty, which violates the v2.5 profile for the `PID` segment:
68
+ `PID-3` is empty:
70
69
 
71
70
  ```hl7
72
71
  MSH|^~\&|SENDER|FAC|RECV|RFAC|20250601120000||ADT^A01^ADT_A01|MSG00001|P|2.5
@@ -76,10 +75,15 @@ PID|1||||DOE^JANE||19800101|F
76
75
  Reported message:
77
76
 
78
77
  ```
79
- Required field PID-3 (Patient Identifier List) is missing or empty
78
+ Field `PID-3` (Patient Identifier List) is empty; it is required.
80
79
  ```
81
80
 
82
- When the profile attaches a field name, it appears in parentheses after the position. Segments shorter than the highest required sequence produce one message per missing required field.
81
+ A segment that ends before a required field, such as `PID|1`, produces one message per required field it does not reach:
82
+
83
+ ```
84
+ Field `PID-3` (Patient Identifier List) is not present; it is required.
85
+ Field `PID-5` (Patient Name) is not present; it is required.
86
+ ```
83
87
 
84
88
  ## Part of Glion
85
89
 
package/dist/index.d.ts CHANGED
@@ -2,8 +2,9 @@ import type { Root } from "@glion/ast";
2
2
  /**
3
3
  * Lint rule that validates required fields per HL7v2 profile.
4
4
  *
5
- * For each segment, checks that all fields marked `required: true`
6
- * in the field definition are present and non-empty.
5
+ * For each segment, checks that every field whose optionality is `R` is
6
+ * present and non-empty. Fields whose optionality is `C`, `B`, or `W` are not
7
+ * checked.
7
8
  *
8
9
  * Segments without a known profile (e.g., Z-segments) are silently skipped.
9
10
  *
@@ -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;;;;;;;;;;;;;;;GAeG;AACH,QAAA,MAAM,uBAAuB,mDAiC5B,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;AAYvC;;;;;;;;;;;;;;;;GAgBG;AACH,QAAA,MAAM,uBAAuB,mDAyC5B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -2,11 +2,16 @@ import { SKIP, visit } from "@glion/util-visit";
2
2
  import { isEmptyNode } from "@glion/utils";
3
3
  import { lintRule } from "unified-lint-rule";
4
4
  //#region src/index.ts
5
+ const messages = {
6
+ absentField: (field, name) => `Field \`${field}\` (${name}) is not present; it is required.`,
7
+ emptyField: (field, name) => `Field \`${field}\` (${name}) is empty; it is required.`
8
+ };
5
9
  /**
6
10
  * Lint rule that validates required fields per HL7v2 profile.
7
11
  *
8
- * For each segment, checks that all fields marked `required: true`
9
- * in the field definition are present and non-empty.
12
+ * For each segment, checks that every field whose optionality is `R` is
13
+ * present and non-empty. Fields whose optionality is `C`, `B`, or `W` are not
14
+ * checked.
10
15
  *
11
16
  * Segments without a known profile (e.g., Z-segments) are silently skipped.
12
17
  *
@@ -18,26 +23,31 @@ import { lintRule } from "unified-lint-rule";
18
23
  * unified().use(hl7v2AnnotateProfileContext).use(hl7v2LintRequiredFields);
19
24
  * ```;
20
25
  */
21
- const hl7v2LintRequiredFields = lintRule({ origin: "hl7v2-lint:required-fields" }, (tree, file) => {
26
+ const hl7v2LintRequiredFields = lintRule({
27
+ origin: "hl7v2-lint:required-fields",
28
+ url: "https://github.com/rethinkhealth/glion/tree/main/packages/lint-profile-required-fields#readme"
29
+ }, (tree, file) => {
22
30
  const ctx = file.data.profile;
23
31
  if (!ctx) return;
24
- visit(tree, "segment", (node, parents) => {
25
- const fieldDef = ctx.fields.get(node.name);
26
- if (!fieldDef) return SKIP;
27
- for (const sequence of fieldDef.requiredSequences) {
28
- const fieldNode = node.children[sequence - 1];
29
- if (!fieldNode || isEmptyNode(fieldNode)) {
30
- const profile = fieldDef.bySequence.get(sequence);
31
- const name = profile?.name ? ` (${profile.name})` : "";
32
- file.message(`Required field ${node.name}-${sequence}${name} is missing or empty`, {
33
- ancestors: [
34
- ...parents,
35
- node,
36
- ...fieldNode ? [fieldNode] : []
37
- ],
38
- place: fieldNode?.position ?? node.position
39
- });
40
- }
32
+ visit(tree, "segment", (segment, segmentAncestors) => {
33
+ const fields = ctx.fields.get(segment.name);
34
+ if (!fields) return SKIP;
35
+ for (const sequence of fields.requiredSequences) {
36
+ const id = `${segment.name}-${sequence}`;
37
+ const name = fields.bySequence.get(sequence)?.name ?? "";
38
+ const field = segment.children[sequence - 1];
39
+ if (!field) file.message(messages.absentField(id, name), {
40
+ ancestors: [...segmentAncestors, segment],
41
+ place: segment.position
42
+ });
43
+ else if (isEmptyNode(field)) file.message(messages.emptyField(id, name), {
44
+ ancestors: [
45
+ ...segmentAncestors,
46
+ segment,
47
+ field
48
+ ],
49
+ place: field.position
50
+ });
41
51
  }
42
52
  return SKIP;
43
53
  });
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 { isEmptyNode } from \"@glion/utils\";\nimport { lintRule } from \"unified-lint-rule\";\n\n/**\n * Lint rule that validates required fields per HL7v2 profile.\n *\n * For each segment, checks that all fields marked `required: true`\n * in the field definition are present and non-empty.\n *\n * Segments without a known profile (e.g., Z-segments) are silently skipped.\n *\n * Requires `@glion/annotate-profile-context` to run first\n * so that `file.data.fields` is populated.\n *\n * @example\n * ```typescript\n * unified().use(hl7v2AnnotateProfileContext).use(hl7v2LintRequiredFields);\n * ```;\n */\nconst hl7v2LintRequiredFields = lintRule<Root>(\n { origin: \"hl7v2-lint:required-fields\" },\n (tree, file) => {\n const ctx = file.data.profile;\n if (!ctx) {\n return;\n }\n\n visit(tree, \"segment\", (node, parents) => {\n const fieldDef = ctx.fields.get(node.name);\n if (!fieldDef) {\n return SKIP;\n }\n\n for (const sequence of fieldDef.requiredSequences) {\n const fieldNode = node.children[sequence - 1];\n\n if (!fieldNode || isEmptyNode(fieldNode)) {\n const profile = fieldDef.bySequence.get(sequence);\n const name = profile?.name ? ` (${profile.name})` : \"\";\n file.message(\n `Required field ${node.name}-${sequence}${name} is missing or empty`,\n {\n ancestors: [...parents, node, ...(fieldNode ? [fieldNode] : [])],\n place: fieldNode?.position ?? node.position,\n }\n );\n }\n }\n\n return SKIP;\n });\n }\n);\n\nexport default hl7v2LintRequiredFields;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuBA,MAAM,0BAA0B,SAC9B,EAAE,QAAQ,8BAA8B,GACvC,MAAM,SAAS;CACd,MAAM,MAAM,KAAK,KAAK;AACtB,KAAI,CAAC,IACH;AAGF,OAAM,MAAM,YAAY,MAAM,YAAY;EACxC,MAAM,WAAW,IAAI,OAAO,IAAI,KAAK,KAAK;AAC1C,MAAI,CAAC,SACH,QAAO;AAGT,OAAK,MAAM,YAAY,SAAS,mBAAmB;GACjD,MAAM,YAAY,KAAK,SAAS,WAAW;AAE3C,OAAI,CAAC,aAAa,YAAY,UAAU,EAAE;IACxC,MAAM,UAAU,SAAS,WAAW,IAAI,SAAS;IACjD,MAAM,OAAO,SAAS,OAAO,KAAK,QAAQ,KAAK,KAAK;AACpD,SAAK,QACH,kBAAkB,KAAK,KAAK,GAAG,WAAW,KAAK,uBAC/C;KACE,WAAW;MAAC,GAAG;MAAS;MAAM,GAAI,YAAY,CAAC,UAAU,GAAG,EAAE;MAAE;KAChE,OAAO,WAAW,YAAY,KAAK;KACpC,CACF;;;AAIL,SAAO;GACP;EAEL"}
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 { isEmptyNode } from \"@glion/utils\";\nimport { lintRule } from \"unified-lint-rule\";\n\nconst messages = {\n absentField: (field: string, name: string) =>\n `Field \\`${field}\\` (${name}) is not present; it is required.`,\n emptyField: (field: string, name: string) =>\n `Field \\`${field}\\` (${name}) is empty; it is required.`,\n} as const;\n\n/**\n * Lint rule that validates required fields per HL7v2 profile.\n *\n * For each segment, checks that every field whose optionality is `R` is\n * present and non-empty. Fields whose optionality is `C`, `B`, or `W` are not\n * checked.\n *\n * Segments without a known profile (e.g., Z-segments) are silently skipped.\n *\n * Requires `@glion/annotate-profile-context` to run first\n * so that `file.data.fields` is populated.\n *\n * @example\n * ```typescript\n * unified().use(hl7v2AnnotateProfileContext).use(hl7v2LintRequiredFields);\n * ```;\n */\nconst hl7v2LintRequiredFields = lintRule<Root>(\n {\n origin: \"hl7v2-lint:required-fields\",\n url: \"https://github.com/rethinkhealth/glion/tree/main/packages/lint-profile-required-fields#readme\",\n },\n (tree, file) => {\n const ctx = file.data.profile;\n if (!ctx) {\n return;\n }\n\n visit(tree, \"segment\", (segment, segmentAncestors) => {\n const fields = ctx.fields.get(segment.name);\n if (!fields) {\n return SKIP;\n }\n\n // Loop over the profile's required fields, not the message's: a field\n // the segment ends before has no node to visit.\n for (const sequence of fields.requiredSequences) {\n const id = `${segment.name}-${sequence}`;\n const name = fields.bySequence.get(sequence)?.name ?? \"\";\n const field = segment.children[sequence - 1];\n\n if (!field) {\n // Reported on the segment, the closest node the message has.\n file.message(messages.absentField(id, name), {\n ancestors: [...segmentAncestors, segment],\n place: segment.position,\n });\n } else if (isEmptyNode(field)) {\n file.message(messages.emptyField(id, name), {\n ancestors: [...segmentAncestors, segment, field],\n place: field.position,\n });\n }\n }\n\n return SKIP;\n });\n }\n);\n\nexport default hl7v2LintRequiredFields;\n"],"mappings":";;;;AAOA,MAAM,WAAW;CACf,cAAc,OAAe,SAC3B,WAAW,MAAM,MAAM,KAAK;CAC9B,aAAa,OAAe,SAC1B,WAAW,MAAM,MAAM,KAAK;CAC/B;;;;;;;;;;;;;;;;;;AAmBD,MAAM,0BAA0B,SAC9B;CACE,QAAQ;CACR,KAAK;CACN,GACA,MAAM,SAAS;CACd,MAAM,MAAM,KAAK,KAAK;AACtB,KAAI,CAAC,IACH;AAGF,OAAM,MAAM,YAAY,SAAS,qBAAqB;EACpD,MAAM,SAAS,IAAI,OAAO,IAAI,QAAQ,KAAK;AAC3C,MAAI,CAAC,OACH,QAAO;AAKT,OAAK,MAAM,YAAY,OAAO,mBAAmB;GAC/C,MAAM,KAAK,GAAG,QAAQ,KAAK,GAAG;GAC9B,MAAM,OAAO,OAAO,WAAW,IAAI,SAAS,EAAE,QAAQ;GACtD,MAAM,QAAQ,QAAQ,SAAS,WAAW;AAE1C,OAAI,CAAC,MAEH,MAAK,QAAQ,SAAS,YAAY,IAAI,KAAK,EAAE;IAC3C,WAAW,CAAC,GAAG,kBAAkB,QAAQ;IACzC,OAAO,QAAQ;IAChB,CAAC;YACO,YAAY,MAAM,CAC3B,MAAK,QAAQ,SAAS,WAAW,IAAI,KAAK,EAAE;IAC1C,WAAW;KAAC,GAAG;KAAkB;KAAS;KAAM;IAChD,OAAO,MAAM;IACd,CAAC;;AAIN,SAAO;GACP;EAEL"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glion/lint-profile-required-fields",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "Lint rule to validate required fields per HL7v2 profile",
5
5
  "keywords": [
6
6
  "health",
@@ -34,9 +34,9 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "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"
37
+ "@glion/ast": "0.24.0",
38
+ "@glion/util-visit": "0.24.0",
39
+ "@glion/utils": "0.24.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/node": "25.6.0",
@@ -47,8 +47,8 @@
47
47
  "unified": "^11.0.5",
48
48
  "vfile": "^6.0.3",
49
49
  "vitest": "4.1.11",
50
- "@glion/annotate-profile-context": "0.23.0",
51
- "@glion/builder": "0.23.0",
50
+ "@glion/annotate-profile-context": "0.24.0",
51
+ "@glion/builder": "0.24.0",
52
52
  "@glion/check-readme": "0.18.0",
53
53
  "@glion/testing": "0.18.0",
54
54
  "@glion/tsconfig": "0.18.0"