@dwp/govuk-casa 9.4.0 → 9.4.2

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 (122) hide show
  1. package/dist/assets/css/casa.css +1 -1
  2. package/dist/assets/css/casa.css.map +1 -1
  3. package/dist/casa.js +17 -7
  4. package/dist/casa.js.map +1 -1
  5. package/dist/core-plugins/edit-snapshot/src/configure.d.ts +6 -1
  6. package/dist/core-plugins/edit-snapshot/src/configure.js +8 -0
  7. package/dist/core-plugins/edit-snapshot/src/configure.js.map +1 -1
  8. package/dist/core-plugins/edit-snapshot/src/post-steer-hook.js.map +1 -1
  9. package/dist/core-plugins/edit-snapshot/src/pre-steer-hook.js.map +1 -1
  10. package/dist/core-plugins/edit-snapshot/src/utils.js +4 -2
  11. package/dist/core-plugins/edit-snapshot/src/utils.js.map +1 -1
  12. package/dist/lib/CasaTemplateLoader.js +0 -1
  13. package/dist/lib/CasaTemplateLoader.js.map +1 -1
  14. package/dist/lib/JourneyContext.d.ts +10 -6
  15. package/dist/lib/JourneyContext.js +11 -9
  16. package/dist/lib/JourneyContext.js.map +1 -1
  17. package/dist/lib/MutableRouter.d.ts +1 -1
  18. package/dist/lib/MutableRouter.js +0 -1
  19. package/dist/lib/MutableRouter.js.map +1 -1
  20. package/dist/lib/Plan.d.ts +3 -3
  21. package/dist/lib/ValidationError.d.ts +1 -1
  22. package/dist/lib/ValidationError.js +1 -1
  23. package/dist/lib/ValidatorFactory.js +0 -3
  24. package/dist/lib/ValidatorFactory.js.map +1 -1
  25. package/dist/lib/configuration-ingestor.d.ts +74 -14
  26. package/dist/lib/configuration-ingestor.js +115 -26
  27. package/dist/lib/configuration-ingestor.js.map +1 -1
  28. package/dist/lib/configure.js.map +1 -1
  29. package/dist/lib/context-id-generators.js +1 -1
  30. package/dist/lib/context-id-generators.js.map +1 -1
  31. package/dist/lib/field.d.ts +8 -8
  32. package/dist/lib/field.js +6 -10
  33. package/dist/lib/field.js.map +1 -1
  34. package/dist/lib/index.js +17 -7
  35. package/dist/lib/index.js.map +1 -1
  36. package/dist/lib/nunjucks-filters.d.ts +5 -1
  37. package/dist/lib/nunjucks-filters.js +13 -1
  38. package/dist/lib/nunjucks-filters.js.map +1 -1
  39. package/dist/lib/utils.js +8 -5
  40. package/dist/lib/utils.js.map +1 -1
  41. package/dist/lib/validators/dateObject.d.ts +2 -1
  42. package/dist/lib/validators/dateObject.js +0 -2
  43. package/dist/lib/validators/dateObject.js.map +1 -1
  44. package/dist/lib/validators/email.js +1 -2
  45. package/dist/lib/validators/email.js.map +1 -1
  46. package/dist/lib/validators/inArray.js +0 -1
  47. package/dist/lib/validators/inArray.js.map +1 -1
  48. package/dist/lib/validators/nino.js +0 -1
  49. package/dist/lib/validators/nino.js.map +1 -1
  50. package/dist/lib/validators/postalAddressObject.js +5 -5
  51. package/dist/lib/validators/postalAddressObject.js.map +1 -1
  52. package/dist/lib/validators/range.js +0 -1
  53. package/dist/lib/validators/range.js.map +1 -1
  54. package/dist/lib/validators/regex.js +0 -1
  55. package/dist/lib/validators/regex.js.map +1 -1
  56. package/dist/lib/validators/required.js +0 -1
  57. package/dist/lib/validators/required.js.map +1 -1
  58. package/dist/lib/validators/strlen.js +0 -1
  59. package/dist/lib/validators/strlen.js.map +1 -1
  60. package/dist/lib/validators/wordCount.js +0 -1
  61. package/dist/lib/validators/wordCount.js.map +1 -1
  62. package/dist/lib/waypoint-url.d.ts +4 -5
  63. package/dist/lib/waypoint-url.js +4 -5
  64. package/dist/lib/waypoint-url.js.map +1 -1
  65. package/dist/middleware/body-parser.d.ts +26 -4
  66. package/dist/middleware/body-parser.js +31 -0
  67. package/dist/middleware/body-parser.js.map +1 -1
  68. package/dist/middleware/csrf.d.ts +15 -1
  69. package/dist/middleware/csrf.js +13 -3
  70. package/dist/middleware/csrf.js.map +1 -1
  71. package/dist/middleware/data.d.ts +21 -4
  72. package/dist/middleware/data.js +33 -4
  73. package/dist/middleware/data.js.map +1 -1
  74. package/dist/middleware/gather-fields.js +1 -1
  75. package/dist/middleware/i18n.d.ts +11 -2
  76. package/dist/middleware/i18n.js +14 -3
  77. package/dist/middleware/i18n.js.map +1 -1
  78. package/dist/middleware/post.d.ts +3 -1
  79. package/dist/middleware/post.js +5 -0
  80. package/dist/middleware/post.js.map +1 -1
  81. package/dist/middleware/session.d.ts +27 -8
  82. package/dist/middleware/session.js +39 -12
  83. package/dist/middleware/session.js.map +1 -1
  84. package/dist/routes/journey.js +2 -3
  85. package/dist/routes/journey.js.map +1 -1
  86. package/package.json +31 -33
  87. package/src/core-plugins/edit-snapshot/src/configure.js +19 -7
  88. package/src/core-plugins/edit-snapshot/src/post-steer-hook.js +3 -1
  89. package/src/core-plugins/edit-snapshot/src/pre-steer-hook.js +14 -4
  90. package/src/core-plugins/edit-snapshot/src/utils.js +19 -7
  91. package/src/lib/CasaTemplateLoader.js +0 -1
  92. package/src/lib/JourneyContext.js +16 -11
  93. package/src/lib/MutableRouter.js +0 -1
  94. package/src/lib/ValidationError.js +1 -1
  95. package/src/lib/ValidatorFactory.js +0 -3
  96. package/src/lib/configuration-ingestor.js +111 -19
  97. package/src/lib/configure.js +2 -2
  98. package/src/lib/context-id-generators.js +1 -1
  99. package/src/lib/field.js +7 -10
  100. package/src/lib/nunjucks-filters.js +15 -1
  101. package/src/lib/utils.js +9 -5
  102. package/src/lib/validators/dateObject.js +1 -2
  103. package/src/lib/validators/email.js +1 -2
  104. package/src/lib/validators/inArray.js +0 -1
  105. package/src/lib/validators/nino.js +0 -1
  106. package/src/lib/validators/postalAddressObject.js +5 -5
  107. package/src/lib/validators/range.js +0 -2
  108. package/src/lib/validators/regex.js +0 -1
  109. package/src/lib/validators/required.js +0 -1
  110. package/src/lib/validators/strlen.js +0 -1
  111. package/src/lib/validators/wordCount.js +0 -1
  112. package/src/lib/waypoint-url.js +4 -5
  113. package/src/middleware/body-parser.js +34 -0
  114. package/src/middleware/csrf.js +13 -3
  115. package/src/middleware/data.js +37 -5
  116. package/src/middleware/gather-fields.js +1 -1
  117. package/src/middleware/i18n.js +15 -3
  118. package/src/middleware/post.js +6 -0
  119. package/src/middleware/session.js +24 -5
  120. package/src/routes/journey.js +6 -4
  121. package/src/core-plugins/edit-snapshot/readme.md +0 -19
  122. package/src/core-plugins/readme.md +0 -3
@@ -51,6 +51,10 @@ const clone = rfdc({ proto: false });
51
51
  * @access private
52
52
  */
53
53
 
54
+ /**
55
+ * @param {any} key Object key to validate
56
+ * @returns {string} Validated key
57
+ */
54
58
  export function validateObjectKey(key = "") {
55
59
  const keyLower = String.prototype.toLowerCase.call(key);
56
60
  if (
@@ -269,7 +273,7 @@ export default class JourneyContext {
269
273
  * @returns {JourneyContext} Chain.
270
274
  */
271
275
  removeValidationStateForPage(pageId) {
272
- /* eslint-disable-next-line no-unused-vars */
276
+ /* eslint-disable-next-line sonarjs/no-unused-vars,no-unused-vars */
273
277
  const { [pageId]: dummy, ...remaining } = this.#validation;
274
278
  this.#validation = { ...remaining };
275
279
  return this;
@@ -567,14 +571,14 @@ export default class JourneyContext {
567
571
  log.trace(
568
572
  "Session context list already initialised as an object (legacy structure). Will convert from object to array.",
569
573
  );
570
- /* eslint-disable-next-line no-param-reassign */
574
+
571
575
  session.journeyContextList = Object.entries(session.journeyContextList);
572
576
  }
573
577
 
574
578
  // Initialise new context list in the session
575
579
  if (!Object.hasOwn(session, "journeyContextList")) {
576
580
  log.trace("Initialising session with a default journey context list");
577
- /* eslint-disable-next-line no-param-reassign */
581
+
578
582
  session.journeyContextList = [];
579
583
 
580
584
  const defaultContext = new JourneyContext();
@@ -697,7 +701,7 @@ export default class JourneyContext {
697
701
  const list = new Map(session?.journeyContextList);
698
702
  if (list.has(id)) {
699
703
  // ESLint disabled as `id` has been verified as an "own" property
700
- /* eslint-disable-next-line security/detect-object-injection */
704
+
701
705
  return JourneyContext.fromObject(list.get(id));
702
706
  }
703
707
 
@@ -799,7 +803,7 @@ export default class JourneyContext {
799
803
 
800
804
  const list = new Map(session.journeyContextList);
801
805
  list.set(context.identity.id, context.toObject());
802
- /* eslint-disable-next-line no-param-reassign */
806
+
803
807
  session.journeyContextList = [...list.entries()];
804
808
  }
805
809
 
@@ -926,6 +930,7 @@ export default class JourneyContext {
926
930
  * @param {string} opts.to Waypoint to skip to.
927
931
  */
928
932
  setSkipped(waypoint, opts) {
933
+ /* eslint-disable security/detect-object-injection */
929
934
  // Unset, with setSkipped(a, false)
930
935
  if (opts === false) {
931
936
  this.data[waypoint] ??= Object.create(null);
@@ -948,25 +953,25 @@ export default class JourneyContext {
948
953
  `setSkipped opts must be a boolean or object with a "to" prop of waypoint to skip to, got: ${typeof opts}`,
949
954
  );
950
955
  }
956
+ /* eslint-enable security/detect-object-injection */
951
957
  }
952
958
 
953
959
  /**
954
960
  * Tests if a page has been skipped.
955
961
  *
956
- * @param {string} page Page ID (waypoint).
962
+ * @param {string} waypoint Page ID (waypoint).
957
963
  * @param {object} opts Skip ptions.
958
964
  * @param {string} opts.to Waypoint that should be skipped to.
959
965
  * @returns {boolean} True if the page has been skipped, or if it has been
960
966
  * skipped to a specific page.
961
967
  */
962
968
  isSkipped(waypoint, opts) {
969
+ const wpData = this.data[String(waypoint)];
970
+
963
971
  if (opts === undefined) {
964
- return (
965
- this.data[waypoint]?.__skipped__ === true ||
966
- this.data[waypoint]?.__skip__ !== undefined
967
- );
972
+ return wpData?.__skipped__ === true || wpData?.__skip__ !== undefined;
968
973
  } else if (typeof opts.to === "string") {
969
- return this.data[waypoint]?.__skip__?.to === opts.to;
974
+ return wpData?.__skip__?.to === opts.to;
970
975
  }
971
976
  }
972
977
  }
@@ -1,4 +1,3 @@
1
- /* eslint-disable sonarjs/no-duplicate-string,class-methods-use-this */
2
1
  import { Router } from "express";
3
2
 
4
3
  /** @memberof module:@dwp/govuk-casa */
@@ -41,7 +41,7 @@ export default class ValidationError {
41
41
  * @param {object} args Arguments
42
42
  * @param {ErrorMessageConfig} args.errorMsg Error message config to seed
43
43
  * ValidationError
44
- * @param {ValidateContext} [args.dataContext={}] Data for error msg function.
44
+ * @param {ValidateContext} [args.dataContext] Data for error msg function.
45
45
  * Default is `{}`
46
46
  * @returns {ValidationError} Error instance
47
47
  * @throws {TypeError} If errorMsg is not in a valid type
@@ -1,4 +1,3 @@
1
- /* eslint-disable class-methods-use-this */
2
1
  import lodash from "lodash";
3
2
 
4
3
  const { isPlainObject } = lodash; // CommonJS
@@ -54,7 +53,6 @@ export default class ValidatorFactory {
54
53
 
55
54
  const validator = Reflect.construct(this, [config]);
56
55
 
57
- /* eslint-disable-next-line sonarjs/prefer-object-literal */
58
56
  const instance = {};
59
57
  instance.name = validator.name || "unknown";
60
58
  instance.config = config;
@@ -96,7 +94,6 @@ export default class ValidatorFactory {
96
94
  throw new Error("validate() method has not been implemented");
97
95
  }
98
96
 
99
- /* eslint-disable-next-line jsdoc/require-returns-check */
100
97
  /**
101
98
  * Sanitise the given value.
102
99
  *
@@ -1,4 +1,3 @@
1
- /* eslint-disable sonarjs/no-duplicate-string */
2
1
  import bytes from "bytes";
3
2
  import { PageField } from "./field.js";
4
3
  import Plan from "./Plan.js";
@@ -25,6 +24,36 @@ import {
25
24
  * @access private
26
25
  */
27
26
 
27
+ /**
28
+ * @typedef {import("../casa").Page} Page
29
+ * @access private
30
+ */
31
+
32
+ /**
33
+ * @typedef {import("../casa").PageHook} PageHook
34
+ * @access private
35
+ */
36
+
37
+ /**
38
+ * @typedef {import("../casa").GlobalHook} GlobalHook
39
+ * @access private
40
+ */
41
+
42
+ /**
43
+ * @typedef {import("../casa").IPlugin} IPlugin
44
+ * @access private
45
+ */
46
+
47
+ /**
48
+ * @typedef {import("../casa").ContextEventHandler} ContextEventHandler
49
+ * @access private
50
+ */
51
+
52
+ /**
53
+ * @typedef {import("../casa").ContextIdGenerator} ContextIdGenerator
54
+ * @access private
55
+ */
56
+
28
57
  const log = logger("lib:configuration-ingestor");
29
58
 
30
59
  const echo = (a) => a;
@@ -58,11 +87,12 @@ export function validateI18nDirs(dirs = []) {
58
87
  if (!Array.isArray(dirs)) {
59
88
  throw new TypeError("I18n directories must be an array (i18n.dirs)");
60
89
  }
61
- for (let i = 0; i < dirs.length; i++) {
62
- const dir = dirs[i];
90
+
91
+ let i = 0;
92
+ for (const dir of dirs) {
63
93
  if (typeof dir !== "string") {
64
94
  throw new TypeError(
65
- `I18n directory must be a string, got ${typeof dir} (i18n.dirs[${i}])`,
95
+ `I18n directory must be a string, got ${typeof dir} (i18n.dirs[${i++}])`,
66
96
  );
67
97
  }
68
98
  }
@@ -82,11 +112,12 @@ export function validateI18nLocales(locales = ["en", "cy"]) {
82
112
  if (!Array.isArray(locales)) {
83
113
  throw new TypeError("I18n locales must be an array (i18n.locales)");
84
114
  }
85
- for (let i = 0; i < locales.length; i++) {
86
- const locale = locales[i];
115
+
116
+ let i = 0;
117
+ for (const locale of locales) {
87
118
  if (typeof locale !== "string") {
88
119
  throw new TypeError(
89
- `I18n locale must be a string, got ${typeof locale} (i18n.locales[${i}])`,
120
+ `I18n locale must be a string, got ${typeof locale} (i18n.locales[${i++}])`,
90
121
  );
91
122
  }
92
123
  }
@@ -148,11 +179,12 @@ export function validateViews(dirs = []) {
148
179
  if (!Array.isArray(dirs)) {
149
180
  throw new TypeError("View directories must be an array (views)");
150
181
  }
151
- for (let i = 0; i < dirs.length; i++) {
152
- const dir = dirs[i];
182
+
183
+ let i = 0;
184
+ for (const dir of dirs) {
153
185
  if (typeof dir !== "string") {
154
186
  throw new TypeError(
155
- `View directory must be a string, got ${typeof dir} (views[${i}])`,
187
+ `View directory must be a string, got ${typeof dir} (views[${i++}])`,
156
188
  );
157
189
  }
158
190
  }
@@ -196,7 +228,7 @@ export function validateSessionTtl(ttl = 3600) {
196
228
  /**
197
229
  * Validates and sanitises sessions name.
198
230
  *
199
- * @param {string} [name=casa-session] Session name. Default is `casa-session`
231
+ * @param {string} [name] Session name. Default is `casa-session`
200
232
  * @returns {string} Name.
201
233
  * @throws {ReferenceError} For missing value type.
202
234
  * @throws {TypeError} For invalid value.
@@ -300,6 +332,11 @@ export function validateErrorVisibility(
300
332
  );
301
333
  }
302
334
 
335
+ /**
336
+ * @param {boolean | string} cookieSameSite Cookie SameSite value
337
+ * @param {boolean | string} defaultFlag Default value
338
+ * @returns {boolean | string} Validated value
339
+ */
303
340
  export function validateSessionCookieSameSite(cookieSameSite, defaultFlag) {
304
341
  const validValues = [true, false, "Strict", "Lax", "None"];
305
342
 
@@ -335,12 +372,18 @@ const validatePageHook = (hook, index) => {
335
372
  }
336
373
  };
337
374
 
375
+ /**
376
+ * @param {PageHook[]} hooks Page hook functions
377
+ * @returns {PageHook[]} Validated page hooks
378
+ */
338
379
  export function validatePageHooks(hooks) {
339
380
  if (!Array.isArray(hooks)) {
340
381
  throw new TypeError("Hooks must be an array");
341
382
  }
342
- for (let i = 0; i < hooks.length; i++) {
343
- validatePageHook(hooks[i], i);
383
+
384
+ let i = 0;
385
+ for (const hook of hooks) {
386
+ validatePageHook(hook, i++);
344
387
  }
345
388
  return hooks;
346
389
  }
@@ -358,12 +401,18 @@ const validateField = (field, index) => {
358
401
  }
359
402
  };
360
403
 
404
+ /**
405
+ * @param {PageField[]} fields Page fields
406
+ * @returns {PageField[]} Validated fields
407
+ */
361
408
  export function validateFields(fields) {
362
409
  if (!Array.isArray(fields)) {
363
410
  throw new TypeError("Page fields must be an array (page[].fields)");
364
411
  }
365
- for (let i = 0; i < fields.length; i++) {
366
- validateField(fields[i], i);
412
+
413
+ let i = 0;
414
+ for (const field of fields) {
415
+ validateField(field, i++);
367
416
  }
368
417
  return fields;
369
418
  }
@@ -387,16 +436,26 @@ const validatePage = (page, index) => {
387
436
  }
388
437
  };
389
438
 
439
+ /**
440
+ * @param {Page[]} [pages] Pages
441
+ * @returns {Page[]} Validated pages
442
+ */
390
443
  export function validatePages(pages = []) {
391
444
  if (!Array.isArray(pages)) {
392
445
  throw new TypeError("Pages must be an array (pages)");
393
446
  }
394
- for (let i = 0; i < pages.length; i++) {
395
- validatePage(pages[i], i);
447
+
448
+ let i = 0;
449
+ for (const page of pages) {
450
+ validatePage(page, i++);
396
451
  }
397
452
  return pages;
398
453
  }
399
454
 
455
+ /**
456
+ * @param {Plan} plan Plan
457
+ * @returns {Plan} Validated plan
458
+ */
400
459
  export function validatePlan(plan) {
401
460
  if (plan === undefined) {
402
461
  return plan;
@@ -424,6 +483,10 @@ const validateGlobalHook = (hook, index) => {
424
483
  }
425
484
  };
426
485
 
486
+ /**
487
+ * @param {GlobalHook[]} hooks Global hook functions
488
+ * @returns {GlobalHook[]} Validated global hooks
489
+ */
427
490
  export function validateGlobalHooks(hooks) {
428
491
  if (hooks === undefined) {
429
492
  return [];
@@ -431,16 +494,26 @@ export function validateGlobalHooks(hooks) {
431
494
  if (!Array.isArray(hooks)) {
432
495
  throw new TypeError("Hooks must be an array");
433
496
  }
434
- for (let i = 0; i < hooks.length; i++) {
435
- validateGlobalHook(hooks[i], i);
497
+
498
+ let i = 0;
499
+ for (const hook of hooks) {
500
+ validateGlobalHook(hook, i++);
436
501
  }
437
502
  return hooks;
438
503
  }
439
504
 
505
+ /**
506
+ * @param {IPlugin[]} plugins Plugins
507
+ * @returns {IPlugin[]} Validated plugins
508
+ */
440
509
  export function validatePlugins(plugins) {
441
510
  return plugins;
442
511
  }
443
512
 
513
+ /**
514
+ * @param {ContextEventHandler[]} events Event handlers
515
+ * @returns {ContextEventHandler[]} Validated event handlers
516
+ */
444
517
  export function validateEvents(events) {
445
518
  return events;
446
519
  }
@@ -464,6 +537,13 @@ export function validateHelmetConfigurator(helmetConfigurator) {
464
537
  return helmetConfigurator;
465
538
  }
466
539
 
540
+ /**
541
+ * @param {number} value Max params value
542
+ * @param {number} [defaultValue] Default value
543
+ * @returns {number} Valid value
544
+ * @throws {TypeError} If not an integer
545
+ * @throws {RangeError} If out of bounds
546
+ */
467
547
  export function validateFormMaxParams(value, defaultValue = 25) {
468
548
  // CASA needs to send certain hidden form fields (see `sanitise-fields`
469
549
  // middleware), plus some padding here.
@@ -482,6 +562,13 @@ export function validateFormMaxParams(value, defaultValue = 25) {
482
562
  return value;
483
563
  }
484
564
 
565
+ /**
566
+ * @param {number} value Max bytes value
567
+ * @param {number} [defaultValue] Default value
568
+ * @returns {number} Valid value
569
+ * @throws {TypeError} If not an integer
570
+ * @throws {RangeError} If out of bounds
571
+ */
485
572
  export function validateFormMaxBytes(value, defaultValue = 1024 * 50) {
486
573
  const MIN_BYTES = 1024;
487
574
 
@@ -502,6 +589,11 @@ export function validateFormMaxBytes(value, defaultValue = 1024 * 50) {
502
589
  return parsedValue;
503
590
  }
504
591
 
592
+ /**
593
+ * @param {ContextIdGenerator} generator ID generator function
594
+ * @returns {ContextIdGenerator} Validated generator
595
+ * @throws {TypeError} If not a function
596
+ */
505
597
  export function validateContextIdGenerator(generator) {
506
598
  if (generator === undefined) {
507
599
  return contextIdGenerators.uuid();
@@ -87,7 +87,7 @@ export default function configure(config = {}) {
87
87
  for (const page of pages) {
88
88
  if (page?.hooks) {
89
89
  for (const h of page.hooks) {
90
- h.hook = `journey.${h.hook}`
90
+ h.hook = `journey.${h.hook}`;
91
91
  }
92
92
  }
93
93
  }
@@ -211,7 +211,7 @@ export default function configure(config = {}) {
211
211
 
212
212
  // Bootstrap all plugins
213
213
  for (const plugin of plugins.filter((p) => p.bootstrap)) {
214
- plugin?.bootstrap(configOutput)
214
+ plugin?.bootstrap(configOutput);
215
215
  }
216
216
 
217
217
  // Finished configuration
@@ -1,4 +1,3 @@
1
- /* eslint-disable import/no-cycle */
2
1
  import { randomUUID } from "node:crypto";
3
2
 
4
3
  /** @typedef {import("../casa.js").ContextIdGenerator} ContextIdGenerator */
@@ -50,6 +49,7 @@ const shortGuid =
50
49
  do {
51
50
  id = Array(length)
52
51
  .fill(0)
52
+ /* eslint-disable-next-line sonarjs/pseudo-random */
53
53
  .map(() => pool.charAt(Math.floor(Math.random() * poolSize)))
54
54
  .join("");
55
55
  attempts--;
package/src/lib/field.js CHANGED
@@ -66,9 +66,9 @@ export class PageField {
66
66
  *
67
67
  * @param {string} name Field name
68
68
  * @param {object} [opts] Options
69
- * @param {boolean} [opts.optional=false] Whether this field is optional.
70
- * Default is `false`
71
- * @param {boolean} [opts.persist=true] Whether this field will persist in
69
+ * @param {boolean} [opts.optional] Whether this field is optional. Default is
70
+ * `false`
71
+ * @param {boolean} [opts.persist] Whether this field will persist in
72
72
  * `req.body`. Default is `true`
73
73
  */
74
74
  constructor(
@@ -95,7 +95,7 @@ export class PageField {
95
95
  };
96
96
 
97
97
  // Apply name
98
- /* eslint-disable-next-line security/detect-non-literal-fs-filename */
98
+
99
99
  this.rename(name);
100
100
  }
101
101
 
@@ -153,13 +153,11 @@ export class PageField {
153
153
  */
154
154
  putValue(obj = Object.create(null), value = undefined) {
155
155
  if (this.#meta.complex) {
156
- /* eslint-disable-next-line no-param-reassign */
157
156
  obj[this.#meta.complexFieldName] = {
158
157
  ...(obj[this.#meta.complexFieldName] ?? {}),
159
158
  [this.#meta.complexFieldProperty]: value,
160
159
  };
161
160
  } else {
162
- /* eslint-disable-next-line no-param-reassign */
163
161
  obj[this.#name] = value;
164
162
  }
165
163
 
@@ -315,7 +313,6 @@ export class PageField {
315
313
  for (let i = 0, l = this.#validators.length; i < l; i++) {
316
314
  // ESLint disabled as `i` is an integer
317
315
  /* eslint-disable security/detect-object-injection */
318
- // TODO: Replace `value` with `context.fieldValue` here
319
316
  let fieldErrors = this.#validators[i].validate(
320
317
  context.fieldValue,
321
318
  context,
@@ -441,9 +438,9 @@ export class PageField {
441
438
  * @memberof module:@dwp/govuk-casa
442
439
  * @param {string} name Field name
443
440
  * @param {object} [opts] Options
444
- * @param {boolean} [opts.optional=false] Whether this field is optional.
445
- * Default is `false`
446
- * @param {boolean} [opts.persist=true] Whether this field will persist in
441
+ * @param {boolean} [opts.optional] Whether this field is optional. Default is
442
+ * `false`
443
+ * @param {boolean} [opts.persist] Whether this field will persist in
447
444
  * `req.body`. Default is `true`
448
445
  * @returns {PageField} A PageField
449
446
  */
@@ -12,9 +12,10 @@ const combineMerge = (target, source, options) => {
12
12
  const destination = target.slice();
13
13
 
14
14
  for (let index = 0; index < source.length; index++) {
15
- const item = source[index];
16
15
  // ESLint disabled as `index` is only an integer
17
16
  /* eslint-disable security/detect-object-injection */
17
+ const item = source[index];
18
+
18
19
  if (typeof destination[index] === "undefined") {
19
20
  destination[index] = options.cloneUnlessOtherwiseSpecified(item, options);
20
21
  } else if (options.isMergeableObject(item)) {
@@ -30,10 +31,18 @@ const combineMerge = (target, source, options) => {
30
31
  // Allows objects to be deepmerged and retain their type, without becoming [object Object]
31
32
  // ref: https://github.com/jonschlinkert/is-plain-object/blob/master/is-plain-object.js
32
33
 
34
+ /**
35
+ * @param {any} o Value to test
36
+ * @returns {boolean} True if an object
37
+ */
33
38
  function isObject(o) {
34
39
  return Object.prototype.toString.call(o) === "[object Object]";
35
40
  }
36
41
 
42
+ /**
43
+ * @param {any} o Value to test
44
+ * @returns {boolean} True if a plain object or array
45
+ */
37
46
  function isPlainObjectOrArray(o) {
38
47
  if (Array.isArray(o)) {
39
48
  return true;
@@ -52,12 +61,17 @@ function isPlainObjectOrArray(o) {
52
61
  return Object.hasOwn(prot, "isPrototypeOf");
53
62
  }
54
63
 
64
+ /**
65
+ * @param {...any} objects Objects to merge
66
+ * @returns {object} Merged object
67
+ */
55
68
  function mergeObjects(...objects) {
56
69
  return deepmergeAll([Object.create(null), ...objects], {
57
70
  arrayMerge: combineMerge,
58
71
  isMergeableObject: isPlainObjectOrArray,
59
72
  });
60
73
  }
74
+
61
75
  /**
62
76
  * Determine whether a value exists in a list.
63
77
  *
package/src/lib/utils.js CHANGED
@@ -48,7 +48,6 @@ export function isStringable(value) {
48
48
  * @access private
49
49
  */
50
50
  export function resolveMiddlewareHooks(hookName, path, hooks = []) {
51
- /* eslint-disable-next-line max-len */
52
51
  const pathMatch = (h) =>
53
52
  h.path === undefined ||
54
53
  (h.path instanceof RegExp && h.path.test(path)) ||
@@ -119,10 +118,15 @@ export function stripWhitespace(value, options) {
119
118
  throw new TypeError("nested must be a string");
120
119
  }
121
120
 
122
- return value
123
- .replace(/^\s+/, opts.leading)
124
- .replace(/\s+$/, opts.trailing)
125
- .replace(/\s+/g, opts.nested);
121
+ // This approach avoids using `/s+$/` regex, which triggers the
122
+ // `sonarjs/slow-regex` eslint rule
123
+ let newValue = value.replace(/^\s+/, opts.leading);
124
+ if (newValue.match(/\s$/)) {
125
+ newValue = `${newValue.trimEnd()}${opts.trailing}`;
126
+ }
127
+ newValue = newValue.replace(/\s+/g, opts.nested);
128
+
129
+ return newValue;
126
130
  }
127
131
 
128
132
  /* ------------------------------------------------ validation / sanitisation */
@@ -1,4 +1,3 @@
1
- /* eslint-disable class-methods-use-this */
2
1
  import { DateTime } from "luxon";
3
2
  import lodash from "lodash";
4
3
  import ValidationError from "../ValidationError.js";
@@ -102,7 +101,7 @@ export default class DateObject extends ValidatorFactory {
102
101
  if (test.flags.every((v) => v === true)) {
103
102
  formats = [...formats, ...test.formats];
104
103
  }
105
- };
104
+ }
106
105
 
107
106
  if (typeof value === "object") {
108
107
  formats.find((format) => {
@@ -1,4 +1,3 @@
1
- /* eslint-disable class-methods-use-this */
2
1
  import validatorPkg from "validator";
3
2
  import ValidationError from "../ValidationError.js";
4
3
  import ValidatorFactory from "../ValidatorFactory.js";
@@ -34,7 +33,7 @@ export default class Email extends ValidatorFactory {
34
33
  let isValid;
35
34
  try {
36
35
  isValid = isEmail(value);
37
- } catch (e) {
36
+ } catch {
38
37
  isValid = false;
39
38
  }
40
39
 
@@ -1,4 +1,3 @@
1
- /* eslint-disable class-methods-use-this */
2
1
  /**
3
2
  * Test if a value is present in an array.
4
3
  *
@@ -1,4 +1,3 @@
1
- /* eslint-disable class-methods-use-this */
2
1
  import ValidationError from "../ValidationError.js";
3
2
  import ValidatorFactory from "../ValidatorFactory.js";
4
3
  import { stringifyInput } from "../utils.js";
@@ -1,4 +1,3 @@
1
- /* eslint-disable class-methods-use-this */
2
1
  import lodash from "lodash";
3
2
  import ValidationError from "../ValidationError.js";
4
3
  import ValidatorFactory from "../ValidatorFactory.js";
@@ -83,7 +82,6 @@ export default class PostalAddressObject extends ValidatorFactory {
83
82
  ...this.config,
84
83
  };
85
84
 
86
- /* eslint-disable-next-line require-jsdoc */
87
85
  const objectifyError = (err) =>
88
86
  typeof err === "string"
89
87
  ? {
@@ -105,12 +103,14 @@ export default class PostalAddressObject extends ValidatorFactory {
105
103
  const errorMsgs = [];
106
104
 
107
105
  if (typeof value === "object") {
108
- const reAddr = /^[^\s]+[a-z0-9\-,.&#()/\\:;'" ]+$/i;
109
- const reAddrLine1 = /^\d+|[^\s]+[a-z0-9\-,.&#()/\\:;'" ]+$/i;
106
+ const reAddr = /^[a-z0-9\-,.&#()/\\:;'" ]{2,}$/i;
107
+ const reAddrLine1 = /^(\d+|[a-z0-9\-,.&#()/\\:;'" ]{2,})$/i;
110
108
  // UK Postcode regex taken from the dwp java pc checker
111
109
  // https://github.com/dwp/postcode-format-validation
110
+ /* eslint-disable sonarjs/regex-complexity */
112
111
  const rePostcode =
113
- /^(?![QVX])[A-Z]((?![IJZ])[A-Z][0-9](([0-9]?)|([ABEHMNPRVWXY]?))|([0-9]([0-9]?|[ABCDEFGHJKPSTUW]?))) ?[0-9]((?![CIKMOV])[A-Z]){2}$|^(BFPO)[ ]?[0-9]{1,4}$/i;
112
+ /^(?![QVX])[A-Z]((?![IJZ])[A-Z]\d((\d?)|([ABEHMNPRVWXY]?))|(\d(\d?|[ABCDEFGHJKPSTUW]?))) ?\d((?![CIKMOV])[A-Z]){2}$|^(BFPO) ?\d{1,4}$/i;
113
+ /* eslint-enable sonarjs/regex-complexity */
114
114
 
115
115
  // [required, regex, strlenmax, error message]
116
116
  const attributes = {
@@ -1,5 +1,3 @@
1
- /* eslint-disable class-methods-use-this */
2
-
3
1
  import ValidatorFactory from "../ValidatorFactory.js";
4
2
  import ValidationError from "../ValidationError.js";
5
3
  import { coerceInputToInteger } from "../utils.js";
@@ -1,4 +1,3 @@
1
- /* eslint-disable class-methods-use-this */
2
1
  import ValidatorFactory from "../ValidatorFactory.js";
3
2
  import ValidationError from "../ValidationError.js";
4
3
  import { stringifyInput } from "../utils.js";
@@ -1,4 +1,3 @@
1
- /* eslint-disable class-methods-use-this */
2
1
  import lodash from "lodash";
3
2
  import { isEmpty, isStringable, stringifyInput } from "../utils.js";
4
3
  import ValidatorFactory from "../ValidatorFactory.js";
@@ -1,4 +1,3 @@
1
- /* eslint-disable class-methods-use-this */
2
1
  import ValidatorFactory from "../ValidatorFactory.js";
3
2
  import ValidationError from "../ValidationError.js";
4
3
  import { stringifyInput } from "../utils.js";
@@ -1,4 +1,3 @@
1
- /* eslint-disable class-methods-use-this */
2
1
  import ValidatorFactory from "../ValidatorFactory.js";
3
2
  import ValidationError from "../ValidationError.js";
4
3
  import { stringifyInput } from "../utils.js";
@@ -61,14 +61,13 @@ const sanitiseWaypointWithAllowedParams = (w) => {
61
61
  * });
62
62
  *
63
63
  * @param {object} obj Options
64
- * @param {string} [obj.waypoint=""] Waypoint. Default is `""`
65
- * @param {string} [obj.mountUrl="/"] Mount URL. Default is `"/"`
64
+ * @param {string} [obj.waypoint] Waypoint. Default is `""`
65
+ * @param {string} [obj.mountUrl] Mount URL. Default is `"/"`
66
66
  * @param {JourneyContext} [obj.journeyContext] JourneyContext
67
- * @param {boolean} [obj.edit=false] Turn edit mode on or off. Default is
68
- * `false`
67
+ * @param {boolean} [obj.edit] Turn edit mode on or off. Default is `false`
69
68
  * @param {string} [obj.editOrigin] Edit mode original URL
70
69
  * @param {boolean} [obj.skipTo] Skip to this waypoint from the current one
71
- * @param {string} [obj.routeName=next] Plan route name; next | prev. Default is
70
+ * @param {string} [obj.routeName] Plan route name; next | prev. Default is
72
71
  * `next`
73
72
  * @returns {string} URL
74
73
  */