@glion/lint-profile-extra-components 0.22.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
@@ -4,7 +4,7 @@ Lint rule to warn when a composite field contains more components than its datat
4
4
 
5
5
  ## What it does
6
6
 
7
- Flags fields whose repetitions contain more components than the datatype's profile allows. For composite datatypes, the maximum is the highest sequence key in the datatype definition; for primitive datatypes the maximum is 1 (the value itself). The rule reads profile context attached by `@glion/annotate-profile-context`. Empty fields, fields on unknown segments, and fields whose datatype definition is missing are silently skipped.
7
+ Flags a field repetition with more components than its datatype defines in the message's version. A primitive datatype defines one component. Empty fields, fields on unknown segments, and fields with no datatype are skipped. 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,6 +16,7 @@ npm install @glion/lint-profile-extra-components
16
16
 
17
17
  ```ts
18
18
  import { hl7v2AnnotateProfileContext } from "@glion/annotate-profile-context";
19
+ import { hl7v2AnnotateProfileFields } from "@glion/annotate-profile-fields";
19
20
  import { hl7v2Parser } from "@glion/parser";
20
21
  import hl7v2LintExtraComponents from "@glion/lint-profile-extra-components";
21
22
  import { unified } from "unified";
@@ -29,6 +30,7 @@ const message = [
29
30
  const file = await unified()
30
31
  .use(hl7v2Parser)
31
32
  .use(hl7v2AnnotateProfileContext)
33
+ .use(hl7v2AnnotateProfileFields)
32
34
  .use(hl7v2LintExtraComponents)
33
35
  .process(message);
34
36
 
@@ -39,9 +41,7 @@ console.error(reporter([file]));
39
41
 
40
42
  ### `unified().use(hl7v2LintExtraComponents)`
41
43
 
42
- A `unified` lint rule plugin. Takes no options.
43
-
44
- Reads `file.data.profile`. For each non-empty field it resolves the datatype definition. For primitives the allowed component count is 1; for composites it is the highest key in `dtDef.componentsBySequence`. Every component at a position greater than that max produces one message per offending repetition.
44
+ A `unified` lint rule plugin. Takes no options. Reports one message for each repetition with too many components, with the number of components in `actual`.
45
45
 
46
46
  ```ts
47
47
  import type { Plugin } from "unified";
@@ -53,7 +53,7 @@ export default hl7v2LintExtraComponents;
53
53
 
54
54
  ## What it checks
55
55
 
56
- This rule flags `^`-delimited components past the profile's defined range for a datatype. `PID-8` (Administrative Sex) is a primitive field in v2.5, so it allows exactly one component.
56
+ A field repetition has at most as many components as its datatype defines. `PID-8` (Administrative Sex) is a primitive field in v2.5, so it allows one component.
57
57
 
58
58
  ### Valid
59
59
 
@@ -76,10 +76,14 @@ PID|1||PATID1234^^^HOSP^MR||DOE^JANE||19800101|F^EXTRA
76
76
  Reported message:
77
77
 
78
78
  ```
79
- Component PID-8.2 is beyond the defined components for IS (max: 1 in v2.5)
79
+ Field `PID-8` (Administrative Sex) has 2 components; it allows at most 1 component.
80
80
  ```
81
81
 
82
- The datatype code (for example `IS`, `CX`, `XPN`) comes from the field's profile. For composite datatypes the same rule applies — a `CX` field in v2.5 defines up to 10 components, so a value carrying an 11th is reported against that datatype.
82
+ The limit comes from the field's datatype in the message's version. `PID-3` is a `CX` field, which defines 10 components in v2.5, so a repetition with 11 is reported as:
83
+
84
+ ```
85
+ Field `PID-3` (Patient Identifier List) has 11 components; it allows at most 10 components.
86
+ ```
83
87
 
84
88
  ## Part of Glion
85
89
 
package/dist/index.d.ts CHANGED
@@ -1,18 +1,19 @@
1
1
  import type { Root } from "@glion/ast";
2
2
  /**
3
- * Lint rule that warns when a field contains more components than its
4
- * datatype profile defines.
3
+ * Lint rule that reports a field repetition with more components than its
4
+ * datatype defines.
5
5
  *
6
- * For composite datatypes, checks against the defined component count.
7
- * For primitive datatypes, the maximum is 1 — any additional components
8
- * are flagged as extra.
6
+ * A primitive datatype defines one component. Empty fields are not checked.
9
7
  *
10
- * Empty fields are skipped.
11
- * Segments without a known profile (e.g., Z-segments) are silently skipped.
8
+ * Reads `id` and `datatype` from `field.data`, so
9
+ * `@glion/annotate-profile-fields` MUST run before it.
12
10
  *
13
11
  * @example
14
12
  * ```typescript
15
- * unified().use(hl7v2LintExtraComponents);
13
+ * unified()
14
+ * .use(hl7v2AnnotateProfileContext)
15
+ * .use(hl7v2AnnotateProfileFields)
16
+ * .use(hl7v2LintExtraComponents);
16
17
  * ```;
17
18
  */
18
19
  declare const hl7v2LintExtraComponents: 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,EAAS,IAAI,EAAW,MAAM,YAAY,CAAC;AAKvD;;;;;;;;;;;;;;;GAeG;AACH,QAAA,MAAM,wBAAwB,mDA0D7B,CAAC;AAaF,eAAe,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAgBvC;;;;;;;;;;;;;;;;GAgBG;AACH,QAAA,MAAM,wBAAwB,mDAwC7B,CAAC;AAEF,eAAe,wBAAwB,CAAC"}
package/dist/index.js CHANGED
@@ -1,55 +1,55 @@
1
1
  import { SKIP, visit } from "@glion/util-visit";
2
2
  import { isEmptyNode } 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 = { tooManyComponents: (field, name, count, max) => `Field \`${field}\` (${name}) has ${pluralize("component", count, true)}; it allows at most ${pluralize("component", max, true)}.` };
5
7
  /**
6
- * Lint rule that warns when a field contains more components than its
7
- * datatype profile defines.
8
+ * Lint rule that reports a field repetition with more components than its
9
+ * datatype defines.
8
10
  *
9
- * For composite datatypes, checks against the defined component count.
10
- * For primitive datatypes, the maximum is 1 — any additional components
11
- * are flagged as extra.
11
+ * A primitive datatype defines one component. Empty fields are not checked.
12
12
  *
13
- * Empty fields are skipped.
14
- * Segments without a known profile (e.g., Z-segments) are silently skipped.
13
+ * Reads `id` and `datatype` from `field.data`, so
14
+ * `@glion/annotate-profile-fields` MUST run before it.
15
15
  *
16
16
  * @example
17
17
  * ```typescript
18
- * unified().use(hl7v2LintExtraComponents);
18
+ * unified()
19
+ * .use(hl7v2AnnotateProfileContext)
20
+ * .use(hl7v2AnnotateProfileFields)
21
+ * .use(hl7v2LintExtraComponents);
19
22
  * ```;
20
23
  */
21
- const hl7v2LintExtraComponents = lintRule({ origin: "hl7v2-lint:extra-components" }, (tree, file) => {
24
+ const hl7v2LintExtraComponents = lintRule({
25
+ origin: "hl7v2-lint:extra-components",
26
+ url: "https://github.com/rethinkhealth/glion/tree/main/packages/lint-profile-extra-components#readme"
27
+ }, (tree, file) => {
22
28
  const ctx = file.data.profile;
23
29
  if (!ctx) return;
24
- visit(tree, "field", (fieldNode, ancestors, info) => {
25
- if (isEmptyNode(fieldNode)) return SKIP;
26
- const segment = ancestors.at(-1);
27
- if (!segment || segment.type !== "segment") return SKIP;
28
- const fieldDef = ctx.fields.get(segment.name);
29
- if (!fieldDef) return SKIP;
30
- const fieldProfile = fieldDef.bySequence.get(info.sequence);
31
- if (!fieldProfile) return SKIP;
32
- const dtDef = ctx.datatypes.get(fieldProfile.datatype);
33
- if (!dtDef) return SKIP;
34
- const maxComponent = dtDef.kind === "primitive" ? 1 : maxKey(dtDef.componentsBySequence);
35
- if (maxComponent === 0) return SKIP;
36
- for (const repetition of fieldNode.children) for (let i = maxComponent + 1; i <= repetition.children.length; i++) file.message(`Component ${segment.name}-${info.sequence}.${i} is beyond the defined components for ${fieldProfile.datatype} (max: ${maxComponent} in v${ctx.version})`, {
37
- ancestors: [
38
- ...ancestors,
39
- fieldNode,
40
- repetition
41
- ],
42
- place: repetition.position ?? fieldNode.position
43
- });
30
+ visit(tree, "field", (field, ancestors) => {
31
+ const id = field.data?.id;
32
+ const datatypeId = field.data?.datatype;
33
+ const datatype = datatypeId === void 0 ? void 0 : ctx.datatypes.get(datatypeId);
34
+ if (id === void 0 || datatype === void 0 || isEmptyNode(field)) return SKIP;
35
+ const max = Math.max(1, ...datatype.componentsBySequence.keys());
36
+ for (const repetition of field.children) {
37
+ const count = repetition.children.length;
38
+ if (count > max) {
39
+ const message = file.message(messages.tooManyComponents(id, field.data?.name ?? "", count, max), {
40
+ ancestors: [
41
+ ...ancestors,
42
+ field,
43
+ repetition
44
+ ],
45
+ place: repetition.position ?? field.position
46
+ });
47
+ message.actual = String(count);
48
+ }
49
+ }
44
50
  return SKIP;
45
51
  });
46
52
  });
47
- /** Return the highest numeric key in a Map, or 0 if empty. */
48
- function maxKey(map) {
49
- let max = 0;
50
- for (const key of map.keys()) if (key > max) max = key;
51
- return max;
52
- }
53
53
  //#endregion
54
54
  export { hl7v2LintExtraComponents as default };
55
55
 
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 { Field, Root, Segment } 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 warns when a field contains more components than its\n * datatype profile defines.\n *\n * For composite datatypes, checks against the defined component count.\n * For primitive datatypes, the maximum is 1 — any additional components\n * are flagged as extra.\n *\n * 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(hl7v2LintExtraComponents);\n * ```;\n */\nconst hl7v2LintExtraComponents = lintRule<Root>(\n { origin: \"hl7v2-lint:extra-components\" },\n (tree, file) => {\n const ctx = file.data.profile;\n if (!ctx) {\n return;\n }\n\n visit(tree, \"field\", (fieldNode, ancestors, info) => {\n if (isEmptyNode(fieldNode as Field)) {\n return SKIP;\n }\n\n const segment = ancestors.at(-1) as Segment | undefined;\n if (!segment || segment.type !== \"segment\") {\n return SKIP;\n }\n\n const fieldDef = ctx.fields.get(segment.name);\n if (!fieldDef) {\n return SKIP;\n }\n\n const fieldProfile = fieldDef.bySequence.get(info.sequence);\n if (!fieldProfile) {\n return SKIP;\n }\n\n const dtDef = ctx.datatypes.get(fieldProfile.datatype);\n if (!dtDef) {\n return SKIP;\n }\n\n // Primitives allow exactly 1 component (the value itself).\n // Composites: the highest sequence key in the profile is the max\n // component number (keys are 1-based HL7 sequence numbers).\n const maxComponent =\n dtDef.kind === \"primitive\" ? 1 : maxKey(dtDef.componentsBySequence);\n\n if (maxComponent === 0) {\n return SKIP;\n }\n\n for (const repetition of (fieldNode as Field).children) {\n for (let i = maxComponent + 1; i <= repetition.children.length; i++) {\n file.message(\n `Component ${segment.name}-${info.sequence}.${i} is beyond the defined components for ${fieldProfile.datatype} (max: ${maxComponent} in v${ctx.version})`,\n {\n ancestors: [...ancestors, fieldNode, repetition],\n place: repetition.position ?? fieldNode.position,\n }\n );\n }\n }\n\n return SKIP;\n });\n }\n);\n\n/** Return the highest numeric key in a Map, or 0 if empty. */\nfunction maxKey(map: ReadonlyMap<number, unknown>): number {\n let max = 0;\n for (const key of map.keys()) {\n if (key > max) {\n max = key;\n }\n }\n return max;\n}\n\nexport default hl7v2LintExtraComponents;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuBA,MAAM,2BAA2B,SAC/B,EAAE,QAAQ,+BAA+B,GACxC,MAAM,SAAS;CACd,MAAM,MAAM,KAAK,KAAK;AACtB,KAAI,CAAC,IACH;AAGF,OAAM,MAAM,UAAU,WAAW,WAAW,SAAS;AACnD,MAAI,YAAY,UAAmB,CACjC,QAAO;EAGT,MAAM,UAAU,UAAU,GAAG,GAAG;AAChC,MAAI,CAAC,WAAW,QAAQ,SAAS,UAC/B,QAAO;EAGT,MAAM,WAAW,IAAI,OAAO,IAAI,QAAQ,KAAK;AAC7C,MAAI,CAAC,SACH,QAAO;EAGT,MAAM,eAAe,SAAS,WAAW,IAAI,KAAK,SAAS;AAC3D,MAAI,CAAC,aACH,QAAO;EAGT,MAAM,QAAQ,IAAI,UAAU,IAAI,aAAa,SAAS;AACtD,MAAI,CAAC,MACH,QAAO;EAMT,MAAM,eACJ,MAAM,SAAS,cAAc,IAAI,OAAO,MAAM,qBAAqB;AAErE,MAAI,iBAAiB,EACnB,QAAO;AAGT,OAAK,MAAM,cAAe,UAAoB,SAC5C,MAAK,IAAI,IAAI,eAAe,GAAG,KAAK,WAAW,SAAS,QAAQ,IAC9D,MAAK,QACH,aAAa,QAAQ,KAAK,GAAG,KAAK,SAAS,GAAG,EAAE,wCAAwC,aAAa,SAAS,SAAS,aAAa,OAAO,IAAI,QAAQ,IACvJ;GACE,WAAW;IAAC,GAAG;IAAW;IAAW;IAAW;GAChD,OAAO,WAAW,YAAY,UAAU;GACzC,CACF;AAIL,SAAO;GACP;EAEL;;AAGD,SAAS,OAAO,KAA2C;CACzD,IAAI,MAAM;AACV,MAAK,MAAM,OAAO,IAAI,MAAM,CAC1B,KAAI,MAAM,IACR,OAAM;AAGV,QAAO"}
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\";\n// 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 { isEmptyNode } from \"@glion/utils\";\nimport pluralize from \"pluralize\";\nimport { lintRule } from \"unified-lint-rule\";\n\nconst messages = {\n tooManyComponents: (\n field: string,\n name: string,\n count: number,\n max: number\n ) =>\n `Field \\`${field}\\` (${name}) has ${pluralize(\"component\", count, true)}; it allows at most ${pluralize(\"component\", max, true)}.`,\n} as const;\n\n/**\n * Lint rule that reports a field repetition with more components than its\n * datatype defines.\n *\n * A primitive datatype defines one component. Empty fields are not checked.\n *\n * Reads `id` and `datatype` from `field.data`, so\n * `@glion/annotate-profile-fields` MUST run before it.\n *\n * @example\n * ```typescript\n * unified()\n * .use(hl7v2AnnotateProfileContext)\n * .use(hl7v2AnnotateProfileFields)\n * .use(hl7v2LintExtraComponents);\n * ```;\n */\nconst hl7v2LintExtraComponents = lintRule<Root>(\n {\n origin: \"hl7v2-lint:extra-components\",\n url: \"https://github.com/rethinkhealth/glion/tree/main/packages/lint-profile-extra-components#readme\",\n },\n (tree, file) => {\n const ctx = file.data.profile;\n if (!ctx) {\n return;\n }\n\n visit(tree, \"field\", (field, ancestors) => {\n const id = field.data?.id;\n const datatypeId = field.data?.datatype;\n const datatype =\n datatypeId === undefined ? undefined : ctx.datatypes.get(datatypeId);\n if (id === undefined || datatype === undefined || isEmptyNode(field)) {\n return SKIP;\n }\n\n // A primitive datatype has no component definitions; its value is\n // component 1.\n const max = Math.max(1, ...datatype.componentsBySequence.keys());\n\n for (const repetition of field.children) {\n const count = repetition.children.length;\n if (count > max) {\n const message = file.message(\n messages.tooManyComponents(id, field.data?.name ?? \"\", count, max),\n {\n ancestors: [...ancestors, field, repetition],\n place: repetition.position ?? field.position,\n }\n );\n message.actual = String(count);\n }\n }\n return SKIP;\n });\n }\n);\n\nexport default hl7v2LintExtraComponents;\n"],"mappings":";;;;;AAUA,MAAM,WAAW,EACf,oBACE,OACA,MACA,OACA,QAEA,WAAW,MAAM,MAAM,KAAK,QAAQ,UAAU,aAAa,OAAO,KAAK,CAAC,sBAAsB,UAAU,aAAa,KAAK,KAAK,CAAC,IACnI;;;;;;;;;;;;;;;;;;AAmBD,MAAM,2BAA2B,SAC/B;CACE,QAAQ;CACR,KAAK;CACN,GACA,MAAM,SAAS;CACd,MAAM,MAAM,KAAK,KAAK;AACtB,KAAI,CAAC,IACH;AAGF,OAAM,MAAM,UAAU,OAAO,cAAc;EACzC,MAAM,KAAK,MAAM,MAAM;EACvB,MAAM,aAAa,MAAM,MAAM;EAC/B,MAAM,WACJ,eAAe,KAAA,IAAY,KAAA,IAAY,IAAI,UAAU,IAAI,WAAW;AACtE,MAAI,OAAO,KAAA,KAAa,aAAa,KAAA,KAAa,YAAY,MAAM,CAClE,QAAO;EAKT,MAAM,MAAM,KAAK,IAAI,GAAG,GAAG,SAAS,qBAAqB,MAAM,CAAC;AAEhE,OAAK,MAAM,cAAc,MAAM,UAAU;GACvC,MAAM,QAAQ,WAAW,SAAS;AAClC,OAAI,QAAQ,KAAK;IACf,MAAM,UAAU,KAAK,QACnB,SAAS,kBAAkB,IAAI,MAAM,MAAM,QAAQ,IAAI,OAAO,IAAI,EAClE;KACE,WAAW;MAAC,GAAG;MAAW;MAAO;MAAW;KAC5C,OAAO,WAAW,YAAY,MAAM;KACrC,CACF;AACD,YAAQ,SAAS,OAAO,MAAM;;;AAGlC,SAAO;GACP;EAEL"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glion/lint-profile-extra-components",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "Lint rule to warn when a composite field contains more components than its datatype profile defines",
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.22.0",
38
- "@glion/util-visit": "0.22.0",
39
- "@glion/utils": "0.22.0"
38
+ "@glion/annotate-profile-fields": "0.24.0",
39
+ "@glion/ast": "0.24.0",
40
+ "@glion/util-visit": "0.24.0",
41
+ "@glion/utils": "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,11 @@
47
50
  "unified": "^11.0.5",
48
51
  "vfile": "^6.0.3",
49
52
  "vitest": "4.1.11",
50
- "@glion/annotate-profile-context": "0.22.0",
51
- "@glion/builder": "0.22.0",
53
+ "@glion/annotate-profile-context": "0.24.0",
54
+ "@glion/builder": "0.24.0",
55
+ "@glion/check-readme": "0.18.0",
52
56
  "@glion/testing": "0.18.0",
53
- "@glion/tsconfig": "0.18.0",
54
- "@glion/check-readme": "0.18.0"
57
+ "@glion/tsconfig": "0.18.0"
55
58
  },
56
59
  "engines": {
57
60
  "node": ">=22"