@dwp/govuk-casa 9.4.0 → 9.4.1

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 (120) hide show
  1. package/dist/casa.js +17 -7
  2. package/dist/casa.js.map +1 -1
  3. package/dist/core-plugins/edit-snapshot/src/configure.d.ts +6 -1
  4. package/dist/core-plugins/edit-snapshot/src/configure.js +8 -0
  5. package/dist/core-plugins/edit-snapshot/src/configure.js.map +1 -1
  6. package/dist/core-plugins/edit-snapshot/src/post-steer-hook.js.map +1 -1
  7. package/dist/core-plugins/edit-snapshot/src/pre-steer-hook.js.map +1 -1
  8. package/dist/core-plugins/edit-snapshot/src/utils.js +4 -2
  9. package/dist/core-plugins/edit-snapshot/src/utils.js.map +1 -1
  10. package/dist/lib/CasaTemplateLoader.js +0 -1
  11. package/dist/lib/CasaTemplateLoader.js.map +1 -1
  12. package/dist/lib/JourneyContext.d.ts +10 -6
  13. package/dist/lib/JourneyContext.js +11 -9
  14. package/dist/lib/JourneyContext.js.map +1 -1
  15. package/dist/lib/MutableRouter.d.ts +1 -1
  16. package/dist/lib/MutableRouter.js +0 -1
  17. package/dist/lib/MutableRouter.js.map +1 -1
  18. package/dist/lib/Plan.d.ts +3 -3
  19. package/dist/lib/ValidationError.d.ts +1 -1
  20. package/dist/lib/ValidationError.js +1 -1
  21. package/dist/lib/ValidatorFactory.js +0 -3
  22. package/dist/lib/ValidatorFactory.js.map +1 -1
  23. package/dist/lib/configuration-ingestor.d.ts +75 -14
  24. package/dist/lib/configuration-ingestor.js +119 -26
  25. package/dist/lib/configuration-ingestor.js.map +1 -1
  26. package/dist/lib/configure.js.map +1 -1
  27. package/dist/lib/context-id-generators.js +1 -1
  28. package/dist/lib/context-id-generators.js.map +1 -1
  29. package/dist/lib/field.d.ts +8 -8
  30. package/dist/lib/field.js +6 -10
  31. package/dist/lib/field.js.map +1 -1
  32. package/dist/lib/index.js +17 -7
  33. package/dist/lib/index.js.map +1 -1
  34. package/dist/lib/nunjucks-filters.d.ts +5 -1
  35. package/dist/lib/nunjucks-filters.js +13 -1
  36. package/dist/lib/nunjucks-filters.js.map +1 -1
  37. package/dist/lib/utils.js +8 -5
  38. package/dist/lib/utils.js.map +1 -1
  39. package/dist/lib/validators/dateObject.d.ts +1 -1
  40. package/dist/lib/validators/dateObject.js +0 -2
  41. package/dist/lib/validators/dateObject.js.map +1 -1
  42. package/dist/lib/validators/email.js +1 -2
  43. package/dist/lib/validators/email.js.map +1 -1
  44. package/dist/lib/validators/inArray.js +0 -1
  45. package/dist/lib/validators/inArray.js.map +1 -1
  46. package/dist/lib/validators/nino.js +0 -1
  47. package/dist/lib/validators/nino.js.map +1 -1
  48. package/dist/lib/validators/postalAddressObject.js +5 -5
  49. package/dist/lib/validators/postalAddressObject.js.map +1 -1
  50. package/dist/lib/validators/range.js +0 -1
  51. package/dist/lib/validators/range.js.map +1 -1
  52. package/dist/lib/validators/regex.js +0 -1
  53. package/dist/lib/validators/regex.js.map +1 -1
  54. package/dist/lib/validators/required.js +0 -1
  55. package/dist/lib/validators/required.js.map +1 -1
  56. package/dist/lib/validators/strlen.js +0 -1
  57. package/dist/lib/validators/strlen.js.map +1 -1
  58. package/dist/lib/validators/wordCount.js +0 -1
  59. package/dist/lib/validators/wordCount.js.map +1 -1
  60. package/dist/lib/waypoint-url.d.ts +4 -5
  61. package/dist/lib/waypoint-url.js +4 -5
  62. package/dist/lib/waypoint-url.js.map +1 -1
  63. package/dist/middleware/body-parser.d.ts +26 -4
  64. package/dist/middleware/body-parser.js +31 -0
  65. package/dist/middleware/body-parser.js.map +1 -1
  66. package/dist/middleware/csrf.d.ts +15 -1
  67. package/dist/middleware/csrf.js +13 -3
  68. package/dist/middleware/csrf.js.map +1 -1
  69. package/dist/middleware/data.d.ts +21 -4
  70. package/dist/middleware/data.js +33 -4
  71. package/dist/middleware/data.js.map +1 -1
  72. package/dist/middleware/gather-fields.js +1 -1
  73. package/dist/middleware/i18n.d.ts +11 -2
  74. package/dist/middleware/i18n.js +14 -3
  75. package/dist/middleware/i18n.js.map +1 -1
  76. package/dist/middleware/post.d.ts +3 -1
  77. package/dist/middleware/post.js +5 -0
  78. package/dist/middleware/post.js.map +1 -1
  79. package/dist/middleware/session.d.ts +27 -8
  80. package/dist/middleware/session.js +39 -12
  81. package/dist/middleware/session.js.map +1 -1
  82. package/dist/routes/journey.js +2 -3
  83. package/dist/routes/journey.js.map +1 -1
  84. package/package.json +27 -30
  85. package/src/core-plugins/edit-snapshot/src/configure.js +19 -7
  86. package/src/core-plugins/edit-snapshot/src/post-steer-hook.js +3 -1
  87. package/src/core-plugins/edit-snapshot/src/pre-steer-hook.js +14 -4
  88. package/src/core-plugins/edit-snapshot/src/utils.js +19 -7
  89. package/src/lib/CasaTemplateLoader.js +0 -1
  90. package/src/lib/JourneyContext.js +16 -11
  91. package/src/lib/MutableRouter.js +0 -1
  92. package/src/lib/ValidationError.js +1 -1
  93. package/src/lib/ValidatorFactory.js +0 -3
  94. package/src/lib/configuration-ingestor.js +116 -19
  95. package/src/lib/configure.js +2 -2
  96. package/src/lib/context-id-generators.js +1 -1
  97. package/src/lib/field.js +7 -10
  98. package/src/lib/nunjucks-filters.js +15 -1
  99. package/src/lib/utils.js +9 -5
  100. package/src/lib/validators/dateObject.js +1 -2
  101. package/src/lib/validators/email.js +1 -2
  102. package/src/lib/validators/inArray.js +0 -1
  103. package/src/lib/validators/nino.js +0 -1
  104. package/src/lib/validators/postalAddressObject.js +5 -5
  105. package/src/lib/validators/range.js +0 -2
  106. package/src/lib/validators/regex.js +0 -1
  107. package/src/lib/validators/required.js +0 -1
  108. package/src/lib/validators/strlen.js +0 -1
  109. package/src/lib/validators/wordCount.js +0 -1
  110. package/src/lib/waypoint-url.js +4 -5
  111. package/src/middleware/body-parser.js +34 -0
  112. package/src/middleware/csrf.js +13 -3
  113. package/src/middleware/data.js +37 -5
  114. package/src/middleware/gather-fields.js +1 -1
  115. package/src/middleware/i18n.js +15 -3
  116. package/src/middleware/post.js +6 -0
  117. package/src/middleware/session.js +24 -5
  118. package/src/routes/journey.js +6 -4
  119. package/src/core-plugins/edit-snapshot/readme.md +0 -19
  120. 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,41 @@ 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").PageField} PageField
34
+ * @access private
35
+ */
36
+
37
+ /**
38
+ * @typedef {import("../casa").PageHook} PageHook
39
+ * @access private
40
+ */
41
+
42
+ /**
43
+ * @typedef {import("../casa").GlobalHook} GlobalHook
44
+ * @access private
45
+ */
46
+
47
+ /**
48
+ * @typedef {import("../casa").IPlugin} IPlugin
49
+ * @access private
50
+ */
51
+
52
+ /**
53
+ * @typedef {import("../casa").ContextEventHandler} ContextEventHandler
54
+ * @access private
55
+ */
56
+
57
+ /**
58
+ * @typedef {import("../casa").ContextIdGenerator} ContextIdGenerator
59
+ * @access private
60
+ */
61
+
28
62
  const log = logger("lib:configuration-ingestor");
29
63
 
30
64
  const echo = (a) => a;
@@ -58,11 +92,12 @@ export function validateI18nDirs(dirs = []) {
58
92
  if (!Array.isArray(dirs)) {
59
93
  throw new TypeError("I18n directories must be an array (i18n.dirs)");
60
94
  }
61
- for (let i = 0; i < dirs.length; i++) {
62
- const dir = dirs[i];
95
+
96
+ let i = 0;
97
+ for (const dir of dirs) {
63
98
  if (typeof dir !== "string") {
64
99
  throw new TypeError(
65
- `I18n directory must be a string, got ${typeof dir} (i18n.dirs[${i}])`,
100
+ `I18n directory must be a string, got ${typeof dir} (i18n.dirs[${i++}])`,
66
101
  );
67
102
  }
68
103
  }
@@ -82,11 +117,12 @@ export function validateI18nLocales(locales = ["en", "cy"]) {
82
117
  if (!Array.isArray(locales)) {
83
118
  throw new TypeError("I18n locales must be an array (i18n.locales)");
84
119
  }
85
- for (let i = 0; i < locales.length; i++) {
86
- const locale = locales[i];
120
+
121
+ let i = 0;
122
+ for (const locale of locales) {
87
123
  if (typeof locale !== "string") {
88
124
  throw new TypeError(
89
- `I18n locale must be a string, got ${typeof locale} (i18n.locales[${i}])`,
125
+ `I18n locale must be a string, got ${typeof locale} (i18n.locales[${i++}])`,
90
126
  );
91
127
  }
92
128
  }
@@ -148,11 +184,12 @@ export function validateViews(dirs = []) {
148
184
  if (!Array.isArray(dirs)) {
149
185
  throw new TypeError("View directories must be an array (views)");
150
186
  }
151
- for (let i = 0; i < dirs.length; i++) {
152
- const dir = dirs[i];
187
+
188
+ let i = 0;
189
+ for (const dir of dirs) {
153
190
  if (typeof dir !== "string") {
154
191
  throw new TypeError(
155
- `View directory must be a string, got ${typeof dir} (views[${i}])`,
192
+ `View directory must be a string, got ${typeof dir} (views[${i++}])`,
156
193
  );
157
194
  }
158
195
  }
@@ -196,7 +233,7 @@ export function validateSessionTtl(ttl = 3600) {
196
233
  /**
197
234
  * Validates and sanitises sessions name.
198
235
  *
199
- * @param {string} [name=casa-session] Session name. Default is `casa-session`
236
+ * @param {string} [name] Session name. Default is `casa-session`
200
237
  * @returns {string} Name.
201
238
  * @throws {ReferenceError} For missing value type.
202
239
  * @throws {TypeError} For invalid value.
@@ -300,6 +337,11 @@ export function validateErrorVisibility(
300
337
  );
301
338
  }
302
339
 
340
+ /**
341
+ * @param {boolean | string} cookieSameSite Cookie SameSite value
342
+ * @param {boolean | string} defaultFlag Default value
343
+ * @returns {boolean | string} Validated value
344
+ */
303
345
  export function validateSessionCookieSameSite(cookieSameSite, defaultFlag) {
304
346
  const validValues = [true, false, "Strict", "Lax", "None"];
305
347
 
@@ -335,12 +377,18 @@ const validatePageHook = (hook, index) => {
335
377
  }
336
378
  };
337
379
 
380
+ /**
381
+ * @param {PageHook[]} hooks Page hook functions
382
+ * @returns {PageHook[]} Validated page hooks
383
+ */
338
384
  export function validatePageHooks(hooks) {
339
385
  if (!Array.isArray(hooks)) {
340
386
  throw new TypeError("Hooks must be an array");
341
387
  }
342
- for (let i = 0; i < hooks.length; i++) {
343
- validatePageHook(hooks[i], i);
388
+
389
+ let i = 0;
390
+ for (const hook of hooks) {
391
+ validatePageHook(hook, i++);
344
392
  }
345
393
  return hooks;
346
394
  }
@@ -358,12 +406,18 @@ const validateField = (field, index) => {
358
406
  }
359
407
  };
360
408
 
409
+ /**
410
+ * @param {PageField[]} fields Page fields
411
+ * @returns {PageField[]} Validated fields
412
+ */
361
413
  export function validateFields(fields) {
362
414
  if (!Array.isArray(fields)) {
363
415
  throw new TypeError("Page fields must be an array (page[].fields)");
364
416
  }
365
- for (let i = 0; i < fields.length; i++) {
366
- validateField(fields[i], i);
417
+
418
+ let i = 0;
419
+ for (const field of fields) {
420
+ validateField(field, i++);
367
421
  }
368
422
  return fields;
369
423
  }
@@ -387,16 +441,26 @@ const validatePage = (page, index) => {
387
441
  }
388
442
  };
389
443
 
444
+ /**
445
+ * @param {Page[]} [pages] Pages
446
+ * @returns {Page[]} Validated pages
447
+ */
390
448
  export function validatePages(pages = []) {
391
449
  if (!Array.isArray(pages)) {
392
450
  throw new TypeError("Pages must be an array (pages)");
393
451
  }
394
- for (let i = 0; i < pages.length; i++) {
395
- validatePage(pages[i], i);
452
+
453
+ let i = 0;
454
+ for (const page of pages) {
455
+ validatePage(page, i++);
396
456
  }
397
457
  return pages;
398
458
  }
399
459
 
460
+ /**
461
+ * @param {Plan} plan Plan
462
+ * @returns {Plan} Validated plan
463
+ */
400
464
  export function validatePlan(plan) {
401
465
  if (plan === undefined) {
402
466
  return plan;
@@ -424,6 +488,10 @@ const validateGlobalHook = (hook, index) => {
424
488
  }
425
489
  };
426
490
 
491
+ /**
492
+ * @param {GlobalHook[]} hooks Global hook functions
493
+ * @returns {GlobalHook[]} Validated global hooks
494
+ */
427
495
  export function validateGlobalHooks(hooks) {
428
496
  if (hooks === undefined) {
429
497
  return [];
@@ -431,16 +499,26 @@ export function validateGlobalHooks(hooks) {
431
499
  if (!Array.isArray(hooks)) {
432
500
  throw new TypeError("Hooks must be an array");
433
501
  }
434
- for (let i = 0; i < hooks.length; i++) {
435
- validateGlobalHook(hooks[i], i);
502
+
503
+ let i = 0;
504
+ for (const hook of hooks) {
505
+ validateGlobalHook(hook, i++);
436
506
  }
437
507
  return hooks;
438
508
  }
439
509
 
510
+ /**
511
+ * @param {IPlugin[]} plugins Plugins
512
+ * @returns {IPlugin[]} Validated plugins
513
+ */
440
514
  export function validatePlugins(plugins) {
441
515
  return plugins;
442
516
  }
443
517
 
518
+ /**
519
+ * @param {ContextEventHandler[]} events Event handlers
520
+ * @returns {ContextEventHandler[]} Validated event handlers
521
+ */
444
522
  export function validateEvents(events) {
445
523
  return events;
446
524
  }
@@ -464,6 +542,13 @@ export function validateHelmetConfigurator(helmetConfigurator) {
464
542
  return helmetConfigurator;
465
543
  }
466
544
 
545
+ /**
546
+ * @param {number} value Max params value
547
+ * @param {number} [defaultValue] Default value
548
+ * @returns {number} Valid value
549
+ * @throws {TypeError} If not an integer
550
+ * @throws {RangeError} If out of bounds
551
+ */
467
552
  export function validateFormMaxParams(value, defaultValue = 25) {
468
553
  // CASA needs to send certain hidden form fields (see `sanitise-fields`
469
554
  // middleware), plus some padding here.
@@ -482,6 +567,13 @@ export function validateFormMaxParams(value, defaultValue = 25) {
482
567
  return value;
483
568
  }
484
569
 
570
+ /**
571
+ * @param {number} value Max bytes value
572
+ * @param {number} [defaultValue] Default value
573
+ * @returns {number} Valid value
574
+ * @throws {TypeError} If not an integer
575
+ * @throws {RangeError} If out of bounds
576
+ */
485
577
  export function validateFormMaxBytes(value, defaultValue = 1024 * 50) {
486
578
  const MIN_BYTES = 1024;
487
579
 
@@ -502,6 +594,11 @@ export function validateFormMaxBytes(value, defaultValue = 1024 * 50) {
502
594
  return parsedValue;
503
595
  }
504
596
 
597
+ /**
598
+ * @param {ContextIdGenerator} generator ID generator function
599
+ * @returns {ContextIdGenerator} Validated generator
600
+ * @throws {TypeError} If not a function
601
+ */
505
602
  export function validateContextIdGenerator(generator) {
506
603
  if (generator === undefined) {
507
604
  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";