jtlt 0.12.0 → 0.13.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.
Files changed (57) hide show
  1. package/CHANGES.md +5 -0
  2. package/README.md +9 -5
  3. package/demo/calltemplate-params-demo.js +19 -15
  4. package/demo/codemirror.esm.js +140 -70
  5. package/demo/codemirror.js +9 -5
  6. package/demo/index.js +2 -1
  7. package/demo/vendor/fontoxpath/dist/fontoxpath.esm.js +508 -499
  8. package/demo/vendor/jamilih/dist/jml.mjs +516 -225
  9. package/demo/vendor/jsonpath-plus/dist/index-browser-esm.js +11 -5
  10. package/dist/AbstractJoiningTransformer.d.ts +12 -20
  11. package/dist/AbstractJoiningTransformer.d.ts.map +1 -1
  12. package/dist/DOMJoiningTransformer.d.ts +502 -19
  13. package/dist/DOMJoiningTransformer.d.ts.map +1 -1
  14. package/dist/JSONJoiningTransformer.d.ts +41 -25
  15. package/dist/JSONJoiningTransformer.d.ts.map +1 -1
  16. package/dist/JSONPathTransformer.d.ts +20 -20
  17. package/dist/JSONPathTransformer.d.ts.map +1 -1
  18. package/dist/JSONPathTransformerContext.d.ts +103 -80
  19. package/dist/JSONPathTransformerContext.d.ts.map +1 -1
  20. package/dist/StringJoiningTransformer.d.ts +42 -28
  21. package/dist/StringJoiningTransformer.d.ts.map +1 -1
  22. package/dist/XPathTransformer.d.ts +11 -11
  23. package/dist/XPathTransformer.d.ts.map +1 -1
  24. package/dist/XPathTransformerContext.d.ts +47 -45
  25. package/dist/XPathTransformerContext.d.ts.map +1 -1
  26. package/dist/XSLTStyleJSONPathResolver.d.ts +1 -1
  27. package/dist/XSLTStyleJSONPathResolver.d.ts.map +1 -1
  28. package/dist/index-browser.d.ts +2 -2
  29. package/dist/index-browser.d.ts.map +1 -1
  30. package/dist/index-node.d.ts +2 -2
  31. package/dist/index-node.d.ts.map +1 -1
  32. package/dist/index.d.ts +126 -167
  33. package/dist/index.d.ts.map +1 -1
  34. package/docs/API.expanded.md +6 -4
  35. package/docs/API.md +1 -1
  36. package/docs/TO-DO.md +4 -2
  37. package/eslint.config.js +17 -1
  38. package/package.json +23 -22
  39. package/pnpm-workspace.yaml +32 -4
  40. package/rollup.config.js +42 -0
  41. package/src/AbstractJoiningTransformer.js +8 -1
  42. package/src/DOMJoiningTransformer.js +56 -27
  43. package/src/JSONJoiningTransformer.js +64 -44
  44. package/src/JSONPathTransformer.js +86 -85
  45. package/src/JSONPathTransformerContext.js +95 -48
  46. package/src/StringJoiningTransformer.js +48 -23
  47. package/src/XPathTransformer.js +18 -14
  48. package/src/XPathTransformerContext.js +173 -123
  49. package/src/index-browser.js +1 -0
  50. package/src/index-node.js +2 -0
  51. package/src/index.js +125 -68
  52. package/test.html +17 -0
  53. package/test.js +45 -0
  54. package/tsconfig-prod.json +1 -0
  55. package/tsconfig.json +3 -2
  56. package/dist/types.d.ts +0 -204
  57. package/dist/types.d.ts.map +0 -1
package/eslint.config.js CHANGED
@@ -16,12 +16,17 @@ export default [
16
16
 
17
17
  // Temporary only:
18
18
  'no-unused-vars': 0,
19
+ 'unicorn/prefer-private-class-fields': 0,
20
+ 'unicorn/no-undeclared-class-members': 0,
19
21
 
20
22
  // AI was frequently making egregious mistakes here, so make fixable
21
23
  'jsdoc/check-alignment': 'error',
22
24
 
23
25
  // We frequently use callbacks for nested interactions
24
- 'promise/prefer-await-to-callbacks': 0
26
+ 'promise/prefer-await-to-callbacks': 0,
27
+
28
+ // A strong basis of this library is being able to reference `this`
29
+ 'unicorn/no-this-outside-of-class': 0
25
30
  }
26
31
  },
27
32
  {
@@ -33,5 +38,16 @@ export default [
33
38
  'no-undef': 'off',
34
39
  'sonarjs/no-global-this': 'off'
35
40
  }
41
+ },
42
+ {
43
+ files: ['test/**/*.js', 'test.js'],
44
+ rules: {
45
+ 'unicorn/prefer-https': 0, // Old namespaces
46
+ // Too many are JSON properties called "children"
47
+ 'unicorn/better-dom-traversing': 0,
48
+ 'mocha/handle-done-callback': 0, // Buggy?
49
+ 'mocha/no-done-twice': 0,
50
+ 'mocha/no-code-after-done': 0 // Buggy
51
+ }
36
52
  }
37
53
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jtlt",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "author": "Brett Zamir",
6
6
  "contributors": [],
@@ -25,39 +25,40 @@
25
25
  "url": "https://github.com/brettz9/jtlt.git"
26
26
  },
27
27
  "dependencies": {
28
- "@codemirror/lang-javascript": "^6.2.4",
28
+ "@codemirror/lang-javascript": "^6.2.5",
29
29
  "@codemirror/lang-json": "^6.0.2",
30
30
  "@codemirror/lang-xml": "^6.1.0",
31
- "@codemirror/language": "^6.11.3",
32
- "@codemirror/state": "^6.5.2",
31
+ "@codemirror/language": "^6.12.4",
32
+ "@codemirror/state": "^6.7.1",
33
33
  "@lezer/highlight": "^1.2.3",
34
34
  "codemirror": "^6.0.2",
35
- "fontoxpath": "^3.33.2",
36
- "jamilih": "0.62.1",
37
- "jhtml": "0.7.1",
38
- "jsdom": "27.1.0",
39
- "jsonpath-plus": "10.3.0",
35
+ "fontoxpath": "^3.34.0",
36
+ "jamilih": "0.69.0",
37
+ "jhtml": "0.7.3",
38
+ "jsdom": "30.0.1",
39
+ "jsonpath-plus": "10.4.0",
40
40
  "prsc": "^4.0.0",
41
- "simple-get-json": "10.0.0",
41
+ "simple-get-json": "11.0.1",
42
42
  "whynot": "^5.0.0",
43
43
  "xpath2.js": "1.0.0-alpha-6",
44
44
  "xspattern": "^3.1.0"
45
45
  },
46
46
  "devDependencies": {
47
- "@arethetypeswrong/cli": "^0.18.2",
48
- "@node-static/node-static": "^0.8.1",
47
+ "@arethetypeswrong/cli": "^0.18.5",
48
+ "@node-static/node-static": "^0.9.1",
49
49
  "@rollup/plugin-node-resolve": "^16.0.3",
50
50
  "@types/chai": "^5.2.3",
51
- "@types/jsdom": "^27.0.0",
51
+ "@types/jsdom": "^30.0.0",
52
52
  "@types/mocha": "^10.0.10",
53
- "c8": "^10.1.3",
54
- "chai": "^6.2.0",
55
- "eslint": "^9.39.1",
56
- "eslint-config-ash-nazg": "^39.8.0",
57
- "mocha": "^11.7.5",
58
- "open": "^10.2.0",
59
- "rollup": "^4.53.2",
60
- "typescript": "^5.9.3"
53
+ "baseline-browser-mapping": "^2.11.19",
54
+ "c8": "^12.0.0",
55
+ "chai": "^6.2.2",
56
+ "eslint": "^10.9.1",
57
+ "eslint-config-ash-nazg": "^42.6.0",
58
+ "mocha": "^11.8.0",
59
+ "open": "^11.0.1",
60
+ "rollup": "^4.63.0",
61
+ "typescript": "^7.0.2"
61
62
  },
62
63
  "bugs": "https://github.com/brettz9/jtlt/issues",
63
64
  "description": "Uses an approach similar to [XSLT](https://www.w3.org/Style/XSL/) for declarative, linear declaration of templates, but with JSON or JavaScript object data sources. As with XSLT, can be transformed into different formats (e.g., HTML strings, JSON, DOM objects, etc.).",
@@ -69,7 +70,7 @@
69
70
  ],
70
71
  "scripts": {
71
72
  "start": "open http://localhost:8021/demo/ && static -p 8021",
72
- "attw": "attw --pack",
73
+ "attw": "attw --pack . --profile esm-only",
73
74
  "tsc": "tsc",
74
75
  "build": "tsc -p tsconfig-prod.json && npm run rollup",
75
76
  "rollup": "rollup -c",
@@ -1,4 +1,32 @@
1
- onlyBuiltDependencies:
2
- - ejs
3
- - is-hidden-file
4
- - unrs-resolver
1
+ allowBuilds:
2
+ is-hidden-file: true
3
+ libxmljs: true
4
+ unrs-resolver: true
5
+ minimumReleaseAgeExclude:
6
+ - '@rollup/rollup-android-arm-eabi@4.63.0'
7
+ - '@rollup/rollup-android-arm64@4.63.0'
8
+ - '@rollup/rollup-darwin-arm64@4.63.0'
9
+ - '@rollup/rollup-darwin-x64@4.63.0'
10
+ - '@rollup/rollup-freebsd-arm64@4.63.0'
11
+ - '@rollup/rollup-freebsd-x64@4.63.0'
12
+ - '@rollup/rollup-linux-arm-gnueabihf@4.63.0'
13
+ - '@rollup/rollup-linux-arm-musleabihf@4.63.0'
14
+ - '@rollup/rollup-linux-arm64-gnu@4.63.0'
15
+ - '@rollup/rollup-linux-arm64-musl@4.63.0'
16
+ - '@rollup/rollup-linux-loong64-gnu@4.63.0'
17
+ - '@rollup/rollup-linux-loong64-musl@4.63.0'
18
+ - '@rollup/rollup-linux-ppc64-gnu@4.63.0'
19
+ - '@rollup/rollup-linux-ppc64-musl@4.63.0'
20
+ - '@rollup/rollup-linux-riscv64-gnu@4.63.0'
21
+ - '@rollup/rollup-linux-riscv64-musl@4.63.0'
22
+ - '@rollup/rollup-linux-s390x-gnu@4.63.0'
23
+ - '@rollup/rollup-linux-x64-gnu@4.63.0'
24
+ - '@rollup/rollup-linux-x64-musl@4.63.0'
25
+ - '@rollup/rollup-openbsd-x64@4.63.0'
26
+ - '@rollup/rollup-openharmony-arm64@4.63.0'
27
+ - '@rollup/rollup-win32-arm64-msvc@4.63.0'
28
+ - '@rollup/rollup-win32-ia32-msvc@4.63.0'
29
+ - '@rollup/rollup-win32-x64-gnu@4.63.0'
30
+ - '@rollup/rollup-win32-x64-msvc@4.63.0'
31
+ - rollup@4.63.0
32
+ - jhtml@0.7.2 || 0.7.3
package/rollup.config.js CHANGED
@@ -1,5 +1,46 @@
1
1
  import {nodeResolve} from '@rollup/plugin-node-resolve';
2
2
 
3
+ // Store resolved paths for deduplication
4
+ const resolvedCodemirror = new Map();
5
+
6
+ /**
7
+ * @returns {import('rollup').Plugin}
8
+ */
9
+ function dedupeCodemirror () {
10
+ return {
11
+ name: 'dedupe-codemirror',
12
+ /**
13
+ * @param {string} source
14
+ * @param {string|undefined} importer
15
+ * @param {any} options
16
+ * @returns {Promise<string|null>|null}
17
+ */
18
+ resolveId (source, importer, options) {
19
+ if (source === 'codemirror' ||
20
+ source.startsWith('@codemirror/') ||
21
+ source.startsWith('@lezer/')
22
+ ) {
23
+ if (!resolvedCodemirror.has(source)) {
24
+ /* eslint-disable promise/prefer-await-to-then -- sync */
25
+ const promise = this.resolve(
26
+ source, importer, {skipSelf: true, ...options}
27
+ ).then(
28
+ /**
29
+ * @param {any} res
30
+ * @returns {string|null}
31
+ */
32
+ (res) => (res ? res.id : null)
33
+ );
34
+ /* eslint-enable promise/prefer-await-to-then -- sync */
35
+ resolvedCodemirror.set(source, promise);
36
+ }
37
+ return resolvedCodemirror.get(source);
38
+ }
39
+ return null;
40
+ }
41
+ };
42
+ }
43
+
3
44
  export default {
4
45
  input: 'demo/codemirror.js',
5
46
  output: {
@@ -8,6 +49,7 @@ export default {
8
49
  banner: '// Do not edit this file; it was auto-generated'
9
50
  },
10
51
  plugins: [
52
+ dedupeCodemirror(),
11
53
  nodeResolve()
12
54
  ]
13
55
  };
@@ -212,6 +212,8 @@ class AbstractJoiningTransformer {
212
212
  if (!name.includes(':') && !name.startsWith('Q{')) {
213
213
  throw new Error(
214
214
  `Function name "${name}" must be in a namespace ` +
215
+ // eslint-disable-next-line @stylistic/max-len -- Long
216
+ // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- Not an HTML template
215
217
  `(use prefix:name or Q{uri}name)`
216
218
  );
217
219
  }
@@ -406,7 +408,12 @@ class AbstractJoiningTransformer {
406
408
  _replaceCharacterMaps (str) {
407
409
  this._outputConfig?.useCharacterMaps?.forEach((name) => {
408
410
  this._characterMap[name].forEach(({character, string}) => {
409
- str = str.replaceAll(character, string);
411
+ str = str.replaceAll(
412
+ character,
413
+ // eslint-disable-next-line @stylistic/max-len -- Long
414
+ // eslint-disable-next-line unicorn/no-unsafe-string-replacement -- Safe with replacement
415
+ string.replaceAll('$', '$$$$')
416
+ );
410
417
  });
411
418
  });
412
419
  return str;
@@ -7,6 +7,7 @@ import AbstractJoiningTransformer from './AbstractJoiningTransformer.js';
7
7
  * @returns {void}
8
8
  */
9
9
 
10
+ /* eslint-disable @stylistic/max-len -- Long */
10
11
  /**
11
12
  * Joining transformer that accumulates into a DOM tree.
12
13
  *
@@ -15,14 +16,35 @@ import AbstractJoiningTransformer from './AbstractJoiningTransformer.js';
15
16
  * attribute(), and text()), though string/number/boolean will append text
16
17
  * nodes for convenience.
17
18
  * @extends {AbstractJoiningTransformer<"dom">}
19
+ * @template {import('./AbstractJoiningTransformer.js').DOMJoiningTransformerConfig} [TConfig=import('./AbstractJoiningTransformer.js').DOMJoiningTransformerConfig]
18
20
  */
19
21
  class DOMJoiningTransformer extends AbstractJoiningTransformer {
22
+ /* eslint-enable @stylistic/max-len -- Long */
20
23
  /**
21
24
  * @param {DocumentFragment|Element} o - Initial DOM node
22
25
  * @param {import('./AbstractJoiningTransformer.js').
23
26
  * DOMJoiningTransformerConfig} cfg - Configuration object
24
27
  */
28
+
29
+ /* eslint-disable @stylistic/max-len -- Long */
30
+ /**
31
+ * @template {import('./AbstractJoiningTransformer.js').DOMJoiningTransformerConfig} T
32
+ * @param {DocumentFragment|Element|undefined} dom - DOM element or fragment
33
+ * @param {T} cfg - Configuration options
34
+ * @returns {DOMJoiningTransformer<T>}
35
+ */
36
+ static create (dom, cfg) {
37
+ /* eslint-enable @stylistic/max-len -- Long */
38
+ return new DOMJoiningTransformer(dom, cfg);
39
+ }
40
+
41
+ /* eslint-disable @stylistic/max-len -- Long */
42
+ /**
43
+ * @param {DocumentFragment|Element|undefined} o - DOM element or fragment
44
+ * @param {import('./AbstractJoiningTransformer.js').DOMJoiningTransformerConfig} cfg - Configuration options
45
+ */
25
46
  constructor (o, cfg) {
47
+ /* eslint-enable @stylistic/max-len -- Long */
26
48
  super(cfg);
27
49
  this._dom = o || cfg.document.createDocumentFragment();
28
50
  /** @type {any} */
@@ -62,13 +84,16 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
62
84
  }
63
85
 
64
86
  /**
65
- * @returns {DocumentFragment|Element|XMLDocument[]}
87
+ * @returns {TConfig['exposeDocuments'] extends true ?
88
+ * XMLDocument[] : DocumentFragment|Element}
66
89
  */
67
90
  get () {
91
+ /* eslint-disable @stylistic/max-len -- Long */
68
92
  if (this._cfg.exposeDocuments) {
69
- return this._docs;
93
+ return /** @type {TConfig['exposeDocuments'] extends true ? XMLDocument[] : DocumentFragment | Element} */ (/** @type {unknown} */ (this._docs));
70
94
  }
71
- return this._dom;
95
+ return /** @type {TConfig['exposeDocuments'] extends true ? XMLDocument[] : DocumentFragment | Element} */ (/** @type {unknown} */ (this._dom));
96
+ /* eslint-enable @stylistic/max-len -- Long */
72
97
  }
73
98
 
74
99
  /**
@@ -93,7 +118,8 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
93
118
 
94
119
  /**
95
120
  * @param {Record<string, unknown>} obj - Object to serialize
96
- * @param {(this: DOMJoiningTransformer) => void} [cb] - Callback function.
121
+ * @param {((this: DOMJoiningTransformer) => void)|null} [cb] - Callback
122
+ * function.
97
123
  * @param {any[]} [usePropertySets] - Property sets to use
98
124
  * @param {Record<string, unknown>} [propSets] - Additional property sets
99
125
  * @returns {DOMJoiningTransformer}
@@ -234,6 +260,7 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
234
260
  * @param {Record<string, string>} atts
235
261
  * @param {(Node|string)[]} childNodes
236
262
  * @param {(this: DOMJoiningTransformer) => void} cb
263
+ * @param {string[]} [useAttributeSets] - Attribute set names to apply
237
264
  * @returns {DOMJoiningTransformer}
238
265
  */
239
266
  /**
@@ -339,12 +366,10 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
339
366
  ({xmlns} = atts ?? {});
340
367
  }
341
368
 
342
- const doc = /** @type {XMLDocument} */ (
343
- this._cfg.document.implementation.createDocument(
344
- xmlns ?? null,
345
- elementName,
346
- dtd
347
- )
369
+ const doc = this._cfg.document.implementation.createDocument(
370
+ xmlns ?? null,
371
+ elementName,
372
+ dtd
348
373
  );
349
374
  /* c8 ignore stop */
350
375
 
@@ -381,13 +406,15 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
381
406
  // Apply attribute sets if specified
382
407
  if (useAttributeSets && useAttributeSets.length) {
383
408
  useAttributeSets.forEach((setName) => {
384
- if (this._attributeSet[setName]) {
385
- for (const att in this._attributeSet[setName]) {
386
- if (Object.hasOwn(this._attributeSet[setName], att)) {
387
- el.setAttribute(att, this._replaceCharacterMaps(
388
- this._attributeSet[setName][att]
389
- ));
390
- }
409
+ if (Object.hasOwn(this._attributeSet, setName)) {
410
+ // eslint-disable-next-line @stylistic/max-len -- Long
411
+ // eslint-disable-next-line unicorn/no-unreadable-for-of-expression -- Long
412
+ for (const [att, attVal] of Object.entries(
413
+ this._attributeSet[setName]
414
+ )) {
415
+ el.setAttribute(att, this._replaceCharacterMaps(
416
+ attVal
417
+ ));
391
418
  }
392
419
  }
393
420
  });
@@ -431,13 +458,15 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
431
458
  // Apply attribute sets if specified
432
459
  if (useAttributeSets && useAttributeSets.length) {
433
460
  useAttributeSets.forEach((setName) => {
434
- if (this._attributeSet[setName]) {
435
- for (const att in this._attributeSet[setName]) {
436
- if (Object.hasOwn(this._attributeSet[setName], att)) {
437
- el.setAttribute(att, this._replaceCharacterMaps(
438
- this._attributeSet[setName][att]
439
- ));
440
- }
461
+ if (Object.hasOwn(this._attributeSet, setName)) {
462
+ // eslint-disable-next-line @stylistic/max-len -- Long
463
+ // eslint-disable-next-line unicorn/no-unreadable-for-of-expression -- Not complex
464
+ for (const [att, attVal] of Object.entries(
465
+ this._attributeSet[setName]
466
+ )) {
467
+ el.setAttribute(att, this._replaceCharacterMaps(
468
+ attVal
469
+ ));
441
470
  }
442
471
  }
443
472
  });
@@ -673,9 +702,7 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
673
702
  // or extract from the current DOM state
674
703
  const resultDoc = this._docs.length > 0
675
704
  ? /** @type {XMLDocument} */ (this._docs.at(-1))
676
- : /** @type {XMLDocument} */ (
677
- this._cfg.document.implementation.createDocument(null, 'root', null)
678
- );
705
+ : this._cfg.document.implementation.createDocument(null, 'root', null);
679
706
 
680
707
  // Store with metadata, using the output config that was set during callback
681
708
  this._resultDocuments.push({
@@ -700,6 +727,8 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
700
727
  */
701
728
  _usePropertySets (obj, psName) {
702
729
  // Merge named property set from this.propertySets into obj
730
+ // eslint-disable-next-line @stylistic/max-len -- Long
731
+ // eslint-disable-next-line unicorn/no-computed-property-existence-check -- TS
703
732
  if (this.propertySets && this.propertySets[psName]) {
704
733
  return {
705
734
  ...obj,
@@ -47,6 +47,7 @@ function _makeDatasetAttribute (n0) {
47
47
  * }} ElementInfo
48
48
  */
49
49
 
50
+ /* eslint-disable @stylistic/max-len -- Long */
50
51
  /**
51
52
  * JSON-based joining transformer for building JSON/JavaScript objects.
52
53
  *
@@ -55,14 +56,27 @@ function _makeDatasetAttribute (n0) {
55
56
  * boolean/null add primitives accordingly. It does not perform HTML escaping
56
57
  * or string serialization; it builds real JS values.
57
58
  * @extends {AbstractJoiningTransformer<"json">}
59
+ * @template {import('./AbstractJoiningTransformer.js').JSONJoiningTransformerConfig} [TConfig=import('./AbstractJoiningTransformer.js').JSONJoiningTransformerConfig]
58
60
  */
59
61
  class JSONJoiningTransformer extends AbstractJoiningTransformer {
60
62
  /**
61
- * @param {any[]|Record<string, unknown>} [o] - Initial object or array
62
- * @param {import('./AbstractJoiningTransformer.js').
63
- * JSONJoiningTransformerConfig} [cfg] - Configuration object
63
+ * @template {import('./AbstractJoiningTransformer.js').JSONJoiningTransformerConfig} T
64
+ * @param {any[]|Record<string, unknown>} [o] - Initial JSON object or array
65
+ * @param {T} [cfg] - Configuration options
66
+ * @returns {JSONJoiningTransformer<T>}
67
+ */
68
+ static create (o, cfg) {
69
+ /* eslint-enable @stylistic/max-len -- Long */
70
+ return new JSONJoiningTransformer(o, cfg);
71
+ }
72
+
73
+ /* eslint-disable @stylistic/max-len -- Long */
74
+ /**
75
+ * @param {any[]|Record<string, unknown>} [o] - Initial JSON object or array
76
+ * @param {import('./AbstractJoiningTransformer.js').JSONJoiningTransformerConfig} [cfg] - Configuration options
64
77
  */
65
78
  constructor (o, cfg) {
79
+ /* eslint-enable @stylistic/max-len -- Long */
66
80
  super(cfg);
67
81
  /** @type {any[]|Record<string, unknown>} */
68
82
  this._obj = o || [];
@@ -119,16 +133,17 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
119
133
  return this;
120
134
  }
121
135
 
136
+ /* eslint-disable @stylistic/max-len -- Long */
122
137
  /**
123
138
  * Gets the current object or array. If unwrapSingleResult config option is
124
139
  * enabled and the root array contains exactly one element, returns that
125
140
  * element directly (unwrapped).
126
- * @returns {any[]|Record<string, unknown>|any}
141
+ * @returns {TConfig['exposeDocuments'] extends true ? any[] : any[]|Record<string, unknown>|any}
127
142
  */
128
143
  get () {
129
144
  // If exposeDocuments is set, return the array of documents
130
145
  if (this._cfg.exposeDocuments) {
131
- return this._docs;
146
+ return /** @type {TConfig['exposeDocuments'] extends true ? any[] : any[]|Record<string, unknown>|any} */ (/** @type {unknown} */ (this._docs));
132
147
  }
133
148
  // Removed this._doc logic; use this._docs only
134
149
  // Unwrap single-element arrays at the root level if configured
@@ -136,7 +151,8 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
136
151
  Array.isArray(this._obj) && this._obj.length === 1) {
137
152
  return this._obj[0];
138
153
  }
139
- return this._obj;
154
+ return /** @type {TConfig['exposeDocuments'] extends true ? any[] : any[]|Record<string, unknown>|any} */ (/** @type {unknown} */ (this._obj));
155
+ /* eslint-enable @stylistic/max-len -- Long */
140
156
  }
141
157
 
142
158
  /**
@@ -166,7 +182,7 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
166
182
 
167
183
  /* c8 ignore next 13 -- JSDoc block incorrectly counted as coverable by c8 */
168
184
  /**
169
- * @param {Record<string, unknown>|ObjectCallback} [objOrCb]
185
+ * @param {null|Record<string, unknown>|ObjectCallback} [objOrCb]
170
186
  * Seed object or callback.
171
187
  * @param {ObjectCallback|any[]} [cbOrUsePropertySets] Callback or sets.
172
188
  * @param {any[]|Record<string, unknown>} [usePropertySetsOrPropSets]
@@ -247,7 +263,7 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
247
263
 
248
264
  /**
249
265
  * Creates a new array and executes a callback in its context.
250
- * @param {any[]|ArrayCallback} [arrOrCb] Seed array or callback.
266
+ * @param {any[]|ArrayCallback|null} [arrOrCb] Seed array or callback.
251
267
  * @param {ArrayCallback} [cb] Callback when first arg was array.
252
268
  * @returns {JSONJoiningTransformer}
253
269
  */
@@ -424,7 +440,7 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
424
440
  if (useAttributeSets && useAttributeSets.length) {
425
441
  const mergedAtts = {};
426
442
  useAttributeSets.forEach((setName) => {
427
- if (this._attributeSet[setName]) {
443
+ if (Object.hasOwn(this._attributeSet, setName)) {
428
444
  Object.assign(mergedAtts, this._attributeSet[setName]);
429
445
  }
430
446
  });
@@ -436,11 +452,10 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
436
452
  !Array.isArray(attsObj.dataset)
437
453
  ) {
438
454
  const ds = attsObj.dataset;
439
- for (const k in ds) {
440
- if (Object.hasOwn(ds, k)) {
441
- const dashed = k.replaceAll(camelCase, _makeDatasetAttribute);
442
- attsObj['data-' + dashed] = this._replaceCharacterMaps(ds[k]);
443
- }
455
+ for (const [k, val] of Object.entries(ds)) {
456
+ // eslint-disable-next-line unicorn/no-unsafe-string-replacement -- Safe
457
+ const dashed = k.replaceAll(camelCase, _makeDatasetAttribute);
458
+ attsObj['data-' + dashed] = this._replaceCharacterMaps(val);
444
459
  }
445
460
  delete attsObj.dataset;
446
461
  }
@@ -454,9 +469,9 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
454
469
  }
455
470
 
456
471
  // Apply character maps to all regular attribute values
457
- for (const key in attsObj) {
458
- if (Object.hasOwn(attsObj, key) && typeof attsObj[key] === 'string') {
459
- attsObj[key] = this._replaceCharacterMaps(attsObj[key]);
472
+ for (const [key, val] of Object.entries(attsObj)) {
473
+ if (typeof val === 'string') {
474
+ attsObj[key] = this._replaceCharacterMaps(val);
460
475
  }
461
476
  }
462
477
 
@@ -493,14 +508,16 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
493
508
  if (isRoot) {
494
509
  // todo: indent, cdataSectionElements
495
510
  const {
496
- omitXmlDeclaration, doctypePublic, doctypeSystem, method
511
+ omitXmlDeclaration, bareDoctype, doctypePublic, doctypeSystem, method
497
512
  } = this._outputConfig ?? {};
498
513
 
499
- const dtd = {$DOCTYPE: {
500
- name: elementName,
501
- publicId: doctypePublic ?? null, // Public ID (optional)
502
- systemId: doctypeSystem ?? null // System ID (optional)
503
- }};
514
+ const dtd = bareDoctype !== false
515
+ ? [{$DOCTYPE: {
516
+ name: elementName,
517
+ publicId: doctypePublic ?? null, // Public ID (optional)
518
+ systemId: doctypeSystem ?? null // System ID (optional)
519
+ }}]
520
+ : [];
504
521
 
505
522
  let xmlDeclaration;
506
523
  /* c8 ignore start -- third OR condition short-circuits */
@@ -520,7 +537,7 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
520
537
  const doc = {$document: {
521
538
  ...(xmlDeclaration ? {xmlDeclaration} : {}),
522
539
  childNodes: [
523
- ...(method === 'xml' || method === 'xhtml' ? [dtd] : []),
540
+ ...(method === 'xml' || method === 'xhtml' ? dtd : []),
524
541
  jmlEl
525
542
  ]
526
543
  }};
@@ -630,24 +647,25 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
630
647
  !Array.isArray(val)
631
648
  ) {
632
649
  const datasetObj = /** @type {Record<string, unknown>} */ (val);
633
- for (const k in datasetObj) {
634
- if (Object.hasOwn(datasetObj, k)) {
635
- const dashed = k.replaceAll(camelCase, _makeDatasetAttribute);
636
- attsObj['data-' + dashed] = this._replaceCharacterMaps(
637
- /** @type {string} */ (datasetObj[k])
638
- );
639
- }
650
+ for (const [k, v] of Object.entries(datasetObj)) {
651
+ // eslint-disable-next-line unicorn/no-unsafe-string-replacement -- Safe
652
+ const dashed = k.replaceAll(camelCase, _makeDatasetAttribute);
653
+ attsObj['data-' + dashed] = this._replaceCharacterMaps(
654
+ /** @type {string} */ (v)
655
+ );
640
656
  }
641
657
  return this;
642
658
  }
643
659
  if (name === '$a' && Array.isArray(val)) {
644
660
  val.forEach((pair) => {
645
- if (Array.isArray(pair) && pair.length > 1) {
646
- const attrName = String(pair[0]);
647
- // Track each ordered attribute
648
- this._trackAttributePrefix(attrName);
649
- attsObj[attrName] = this._replaceCharacterMaps(pair[1]);
661
+ if (!(Array.isArray(pair) && pair.length > 1)) {
662
+ return;
650
663
  }
664
+
665
+ const attrName = String(pair[0]);
666
+ // Track each ordered attribute
667
+ this._trackAttributePrefix(attrName);
668
+ attsObj[attrName] = this._replaceCharacterMaps(pair[1]);
651
669
  });
652
670
  return this;
653
671
  }
@@ -806,7 +824,7 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
806
824
  // We have output config but document wasn't pushed to _docs
807
825
  // Create the document wrapper manually
808
826
  const {
809
- omitXmlDeclaration, doctypePublic, doctypeSystem, method
827
+ omitXmlDeclaration, bareDoctype, doctypePublic, doctypeSystem, method
810
828
  } = this._outputConfig;
811
829
 
812
830
  const elementData = Array.isArray(this._obj) && this._obj.length > 0
@@ -815,11 +833,13 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
815
833
 
816
834
  const elementName = Array.isArray(elementData) ? elementData[0] : 'root';
817
835
 
818
- const dtd = {$DOCTYPE: {
819
- name: elementName,
820
- publicId: doctypePublic ?? null,
821
- systemId: doctypeSystem ?? null
822
- }};
836
+ const dtd = bareDoctype !== false
837
+ ? [{$DOCTYPE: {
838
+ name: elementName,
839
+ publicId: doctypePublic ?? null,
840
+ systemId: doctypeSystem ?? null
841
+ }}]
842
+ : [];
823
843
 
824
844
  let xmlDeclaration;
825
845
  /* c8 ignore start -- third OR condition short-circuits */
@@ -838,7 +858,7 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
838
858
  resultDoc = {$document: {
839
859
  ...(xmlDeclaration ? {xmlDeclaration} : {}),
840
860
  childNodes: [
841
- ...(method === 'xml' || method === 'xhtml' ? [dtd] : []),
861
+ ...(method === 'xml' || method === 'xhtml' ? dtd : []),
842
862
  elementData
843
863
  ]
844
864
  }};
@@ -872,7 +892,7 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
872
892
  */
873
893
  _usePropertySets (obj, psName) {
874
894
  // Merge the named property set (if present) into the provided object
875
- if (this.propertySets[psName]) {
895
+ if (Object.hasOwn(this.propertySets, psName)) {
876
896
  return Object.assign(obj, this.propertySets[psName]);
877
897
  }
878
898
  return obj;