-
Notifications
You must be signed in to change notification settings - Fork 342
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SPIKE] Throw attempting to override an object value with a non-object #4809
Closed
romaricpascal
wants to merge
3
commits into
flatten-configs-nested
from
flatten-configs-nested-errors
Closed
[SPIKE] Throw attempting to override an object value with a non-object #4809
romaricpascal
wants to merge
3
commits into
flatten-configs-nested
from
flatten-configs-nested-errors
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
📋 StatsFile sizes
Modules
View stats and visualisations on the review app Action run for 6c63991 |
JavaScript changes to npm packagediff --git a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
index 85682bbeb..5ab2300b5 100644
--- a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
+++ b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
@@ -17,15 +17,20 @@ function normaliseString(e, t) {
return i
}
-function mergeConfigs(...e) {
- const t = {};
- for (const n of e)
- for (const e of Object.keys(n)) {
- const i = t[e],
- s = n[e];
- isObject(i) && !isObject(s) || (isObject(i) && isObject(s) ? t[e] = mergeConfigs(i, s) : t[e] = s)
+function mergeConfigs(e, {
+ path: t = []
+} = {}) {
+ const n = {};
+ for (const i of e)
+ for (const e of Object.keys(i)) {
+ const s = n[e],
+ o = i[e];
+ if (isObject(s) && !isObject(o)) throw new TypeError(`Trying to merge a non-object value over an object value for \`${[...t,e].join(".")}\``);
+ isObject(s) && isObject(o) ? n[e] = mergeConfigs([s, o], {
+ path: [...t, e]
+ }) : n[e] = o
}
- return t
+ return n
}
function extractConfigByNamespace(e, t) {
@@ -35,12 +40,16 @@ function extractConfigByNamespace(e, t) {
const i = e[s],
o = s.split(".");
if (o[0] === t) {
- o.shift();
+ if (o.shift(), !o.length) throw new TypeError(`\`data-${t}\` cannot exist on its own`);
let e = n;
- for (const t of o) {
- isObject(e[t]) || (e[t] = {});
- const n = e[t];
- t === o[o.length - 1] && (e[t] = normaliseString(i)), "object" == typeof n && (e = n)
+ const s = [];
+ for (const n of o) {
+ if (s.push(n), !isObject(e[n])) {
+ if (n in e) throw new TypeError(`\`data-${t}.${o.join(".")}\` cannot exist if \`data-${t}.${s.join(".")}\` is present`);
+ e[n] = {}
+ }
+ const r = e[n];
+ n === o[o.length - 1] && (e[n] = normaliseString(i)), "object" == typeof r && (e = r)
}
}
}
@@ -222,7 +231,13 @@ class Accordion extends GOVUKFrontendComponent {
element: t,
identifier: "Root element (`$module`)"
});
- this.$module = t, this.config = mergeConfigs(Accordion.defaults, n, normaliseDataset(t.dataset, Accordion.schema)), this.i18n = new I18n(this.config.i18n);
+ this.$module = t;
+ try {
+ this.config = mergeConfigs([Accordion.defaults, n, normaliseDataset(t.dataset, Accordion.schema)])
+ } catch (o) {
+ throw new ConfigError(`Accordion: ${o instanceof Error?o.message:String(o)}`)
+ }
+ this.i18n = new I18n(this.config.i18n);
const i = this.$module.querySelectorAll(`.${this.sectionClass}`);
if (!i.length) throw new ElementError({
componentName: "Accordion",
@@ -391,7 +406,13 @@ class Button extends GOVUKFrontendComponent {
element: e,
identifier: "Root element (`$module`)"
});
- this.$module = e, this.config = mergeConfigs(Button.defaults, t, normaliseDataset(e.dataset, Button.schema)), this.$module.addEventListener("keydown", (e => this.handleKeyDown(e))), this.$module.addEventListener("click", (e => this.debounce(e)))
+ this.$module = e;
+ try {
+ this.config = mergeConfigs([Button.defaults, t, normaliseDataset(e.dataset, Button.schema)])
+ } catch (n) {
+ throw new ConfigError(`Button: ${n instanceof Error?n.message:String(n)}`)
+ }
+ this.$module.addEventListener("keydown", (e => this.handleKeyDown(e))), this.$module.addEventListener("click", (e => this.debounce(e)))
}
handleKeyDown(e) {
const t = e.target;
@@ -437,7 +458,12 @@ class CharacterCount extends GOVUKFrontendComponent {
("maxwords" in o || "maxlength" in o) && (r = {
maxlength: void 0,
maxwords: void 0
- }), this.config = mergeConfigs(CharacterCount.defaults, t, r, o);
+ });
+ try {
+ this.config = mergeConfigs([CharacterCount.defaults, t, r, o])
+ } catch (d) {
+ throw new ConfigError(`Character count: ${d instanceof Error?d.message:String(d)}`)
+ }
const a = function(e, t) {
const n = [];
for (const [i, s] of Object.entries(e)) {
@@ -632,7 +658,13 @@ class ErrorSummary extends GOVUKFrontendComponent {
element: e,
identifier: "Root element (`$module`)"
});
- this.$module = e, this.config = mergeConfigs(ErrorSummary.defaults, t, normaliseDataset(e.dataset, ErrorSummary.schema)), this.config.disableAutoFocus || setFocus(this.$module), this.$module.addEventListener("click", (e => this.handleClick(e)))
+ this.$module = e;
+ try {
+ this.config = mergeConfigs([ErrorSummary.defaults, t, normaliseDataset(e.dataset, ErrorSummary.schema)])
+ } catch (n) {
+ throw new ConfigError(`Error summary: ${n instanceof Error?n.message:String(n)}`)
+ }
+ this.config.disableAutoFocus || setFocus(this.$module), this.$module.addEventListener("click", (e => this.handleClick(e)))
}
handleClick(e) {
const t = e.target;
@@ -690,7 +722,12 @@ class ExitThisPage extends GOVUKFrontendComponent {
expectedType: "HTMLAnchorElement",
identifier: "Button (`.govuk-exit-this-page__button`)"
});
- this.config = mergeConfigs(ExitThisPage.defaults, t, normaliseDataset(e.dataset, ExitThisPage.schema)), this.i18n = new I18n(this.config.i18n), this.$module = e, this.$button = n;
+ try {
+ this.config = mergeConfigs([ExitThisPage.defaults, t, normaliseDataset(e.dataset, ExitThisPage.schema)])
+ } catch (s) {
+ throw new ConfigError(`Exit this page: ${s instanceof Error?s.message:String(s)}`)
+ }
+ this.i18n = new I18n(this.config.i18n), this.$module = e, this.$button = n;
const i = document.querySelector(".govuk-js-exit-this-page-skiplink");
i instanceof HTMLAnchorElement && (this.$skiplinkButton = i), this.buildIndicator(), this.initUpdateSpan(), this.initButtonClickHandler(), "govukFrontendExitThisPageKeypress" in document.body.dataset || (document.addEventListener("keyup", this.handleKeypress.bind(this), !0), document.body.dataset.govukFrontendExitThisPageKeypress = "true"), window.addEventListener("pageshow", this.resetPage.bind(this))
}
@@ -799,7 +836,13 @@ class NotificationBanner extends GOVUKFrontendComponent {
element: e,
identifier: "Root element (`$module`)"
});
- this.$module = e, this.config = mergeConfigs(NotificationBanner.defaults, t, normaliseDataset(e.dataset, NotificationBanner.schema)), "alert" !== this.$module.getAttribute("role") || this.config.disableAutoFocus || setFocus(this.$module)
+ this.$module = e;
+ try {
+ this.config = mergeConfigs([NotificationBanner.defaults, t, normaliseDataset(e.dataset, NotificationBanner.schema)])
+ } catch (n) {
+ throw new ConfigError(`Notification banner: ${n instanceof Error?n.message:String(n)}`)
+ }
+ "alert" !== this.$module.getAttribute("role") || this.config.disableAutoFocus || setFocus(this.$module)
}
}
NotificationBanner.moduleName = "govuk-notification-banner", NotificationBanner.defaults = Object.freeze({
Action run for 6c63991 |
Other changes to npm packagediff --git a/packages/govuk-frontend/dist/govuk/all.bundle.js b/packages/govuk-frontend/dist/govuk/all.bundle.js
index 5a96ed573..c1f832167 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.js
@@ -39,19 +39,23 @@
* @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
*/
- function mergeConfigs(...configObjects) {
+ function mergeConfigs(configObjects, {
+ path = []
+ } = {}) {
const formattedConfigObject = {};
for (const configObject of configObjects) {
for (const key of Object.keys(configObject)) {
const option = formattedConfigObject[key];
const override = configObject[key];
- const nonObjectOverridesObject = isObject(option) && !isObject(override);
- if (!nonObjectOverridesObject) {
- if (isObject(option) && isObject(override)) {
- formattedConfigObject[key] = mergeConfigs(option, override);
- } else {
- formattedConfigObject[key] = override;
- }
+ if (isObject(option) && !isObject(override)) {
+ throw new TypeError(`Trying to merge a non-object value over an object value for \`${[...path, key].join('.')}\``);
+ }
+ if (isObject(option) && isObject(override)) {
+ formattedConfigObject[key] = mergeConfigs([option, override], {
+ path: [...path, key]
+ });
+ } else {
+ formattedConfigObject[key] = override;
}
}
}
@@ -65,9 +69,17 @@
const keyParts = key.split('.');
if (keyParts[0] === namespace) {
keyParts.shift();
+ if (!keyParts.length) {
+ throw new TypeError(`\`data-${namespace}\` cannot exist on its own`);
+ }
let current = newObject;
+ const path = [];
for (const name of keyParts) {
+ path.push(name);
if (!isObject(current[name])) {
+ if (name in current) {
+ throw new TypeError(`\`data-${namespace}.${keyParts.join('.')}\` cannot exist if \`data-${namespace}.${path.join('.')}\` is present`);
+ }
current[name] = {};
}
const next = current[name];
@@ -492,7 +504,11 @@
});
}
this.$module = $module;
- this.config = mergeConfigs(Accordion.defaults, config, normaliseDataset($module.dataset, Accordion.schema));
+ try {
+ this.config = mergeConfigs([Accordion.defaults, config, normaliseDataset($module.dataset, Accordion.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Accordion: ${error instanceof Error ? error.message : String(error)}`);
+ }
this.i18n = new I18n(this.config.i18n);
const $sections = this.$module.querySelectorAll(`.${this.sectionClass}`);
if (!$sections.length) {
@@ -822,7 +838,11 @@
});
}
this.$module = $module;
- this.config = mergeConfigs(Button.defaults, config, normaliseDataset($module.dataset, Button.schema));
+ try {
+ this.config = mergeConfigs([Button.defaults, config, normaliseDataset($module.dataset, Button.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Button: ${error instanceof Error ? error.message : String(error)}`);
+ }
this.$module.addEventListener('keydown', event => this.handleKeyDown(event));
this.$module.addEventListener('click', event => this.debounce(event));
}
@@ -932,7 +952,11 @@
maxwords: undefined
};
}
- this.config = mergeConfigs(CharacterCount.defaults, config, configOverrides, datasetConfig);
+ try {
+ this.config = mergeConfigs([CharacterCount.defaults, config, configOverrides, datasetConfig]);
+ } catch (error) {
+ throw new ConfigError(`Character count: ${error instanceof Error ? error.message : String(error)}`);
+ }
const errors = validateConfig(CharacterCount.schema, this.config);
if (errors[0]) {
throw new ConfigError(`Character count: ${errors[0]}`);
@@ -1313,7 +1337,11 @@
});
}
this.$module = $module;
- this.config = mergeConfigs(ErrorSummary.defaults, config, normaliseDataset($module.dataset, ErrorSummary.schema));
+ try {
+ this.config = mergeConfigs([ErrorSummary.defaults, config, normaliseDataset($module.dataset, ErrorSummary.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Error summary: ${error instanceof Error ? error.message : String(error)}`);
+ }
if (!this.config.disableAutoFocus) {
setFocus(this.$module);
}
@@ -1435,7 +1463,11 @@
identifier: 'Button (`.govuk-exit-this-page__button`)'
});
}
- this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset($module.dataset, ExitThisPage.schema));
+ try {
+ this.config = mergeConfigs([ExitThisPage.defaults, config, normaliseDataset($module.dataset, ExitThisPage.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Exit this page: ${error instanceof Error ? error.message : String(error)}`);
+ }
this.i18n = new I18n(this.config.i18n);
this.$module = $module;
this.$button = $button;
@@ -1734,7 +1766,11 @@
});
}
this.$module = $module;
- this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset($module.dataset, NotificationBanner.schema));
+ try {
+ this.config = mergeConfigs([NotificationBanner.defaults, config, normaliseDataset($module.dataset, NotificationBanner.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Notification banner: ${error instanceof Error ? error.message : String(error)}`);
+ }
if (this.$module.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
setFocus(this.$module);
}
diff --git a/packages/govuk-frontend/dist/govuk/all.bundle.mjs b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
index 232be4bb5..c7b316ddd 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
@@ -33,19 +33,23 @@ function normaliseString(value, options) {
* @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
*/
-function mergeConfigs(...configObjects) {
+function mergeConfigs(configObjects, {
+ path = []
+} = {}) {
const formattedConfigObject = {};
for (const configObject of configObjects) {
for (const key of Object.keys(configObject)) {
const option = formattedConfigObject[key];
const override = configObject[key];
- const nonObjectOverridesObject = isObject(option) && !isObject(override);
- if (!nonObjectOverridesObject) {
- if (isObject(option) && isObject(override)) {
- formattedConfigObject[key] = mergeConfigs(option, override);
- } else {
- formattedConfigObject[key] = override;
- }
+ if (isObject(option) && !isObject(override)) {
+ throw new TypeError(`Trying to merge a non-object value over an object value for \`${[...path, key].join('.')}\``);
+ }
+ if (isObject(option) && isObject(override)) {
+ formattedConfigObject[key] = mergeConfigs([option, override], {
+ path: [...path, key]
+ });
+ } else {
+ formattedConfigObject[key] = override;
}
}
}
@@ -59,9 +63,17 @@ function extractConfigByNamespace(dataset, namespace) {
const keyParts = key.split('.');
if (keyParts[0] === namespace) {
keyParts.shift();
+ if (!keyParts.length) {
+ throw new TypeError(`\`data-${namespace}\` cannot exist on its own`);
+ }
let current = newObject;
+ const path = [];
for (const name of keyParts) {
+ path.push(name);
if (!isObject(current[name])) {
+ if (name in current) {
+ throw new TypeError(`\`data-${namespace}.${keyParts.join('.')}\` cannot exist if \`data-${namespace}.${path.join('.')}\` is present`);
+ }
current[name] = {};
}
const next = current[name];
@@ -486,7 +498,11 @@ class Accordion extends GOVUKFrontendComponent {
});
}
this.$module = $module;
- this.config = mergeConfigs(Accordion.defaults, config, normaliseDataset($module.dataset, Accordion.schema));
+ try {
+ this.config = mergeConfigs([Accordion.defaults, config, normaliseDataset($module.dataset, Accordion.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Accordion: ${error instanceof Error ? error.message : String(error)}`);
+ }
this.i18n = new I18n(this.config.i18n);
const $sections = this.$module.querySelectorAll(`.${this.sectionClass}`);
if (!$sections.length) {
@@ -816,7 +832,11 @@ class Button extends GOVUKFrontendComponent {
});
}
this.$module = $module;
- this.config = mergeConfigs(Button.defaults, config, normaliseDataset($module.dataset, Button.schema));
+ try {
+ this.config = mergeConfigs([Button.defaults, config, normaliseDataset($module.dataset, Button.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Button: ${error instanceof Error ? error.message : String(error)}`);
+ }
this.$module.addEventListener('keydown', event => this.handleKeyDown(event));
this.$module.addEventListener('click', event => this.debounce(event));
}
@@ -926,7 +946,11 @@ class CharacterCount extends GOVUKFrontendComponent {
maxwords: undefined
};
}
- this.config = mergeConfigs(CharacterCount.defaults, config, configOverrides, datasetConfig);
+ try {
+ this.config = mergeConfigs([CharacterCount.defaults, config, configOverrides, datasetConfig]);
+ } catch (error) {
+ throw new ConfigError(`Character count: ${error instanceof Error ? error.message : String(error)}`);
+ }
const errors = validateConfig(CharacterCount.schema, this.config);
if (errors[0]) {
throw new ConfigError(`Character count: ${errors[0]}`);
@@ -1307,7 +1331,11 @@ class ErrorSummary extends GOVUKFrontendComponent {
});
}
this.$module = $module;
- this.config = mergeConfigs(ErrorSummary.defaults, config, normaliseDataset($module.dataset, ErrorSummary.schema));
+ try {
+ this.config = mergeConfigs([ErrorSummary.defaults, config, normaliseDataset($module.dataset, ErrorSummary.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Error summary: ${error instanceof Error ? error.message : String(error)}`);
+ }
if (!this.config.disableAutoFocus) {
setFocus(this.$module);
}
@@ -1429,7 +1457,11 @@ class ExitThisPage extends GOVUKFrontendComponent {
identifier: 'Button (`.govuk-exit-this-page__button`)'
});
}
- this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset($module.dataset, ExitThisPage.schema));
+ try {
+ this.config = mergeConfigs([ExitThisPage.defaults, config, normaliseDataset($module.dataset, ExitThisPage.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Exit this page: ${error instanceof Error ? error.message : String(error)}`);
+ }
this.i18n = new I18n(this.config.i18n);
this.$module = $module;
this.$button = $button;
@@ -1728,7 +1760,11 @@ class NotificationBanner extends GOVUKFrontendComponent {
});
}
this.$module = $module;
- this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset($module.dataset, NotificationBanner.schema));
+ try {
+ this.config = mergeConfigs([NotificationBanner.defaults, config, normaliseDataset($module.dataset, NotificationBanner.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Notification banner: ${error instanceof Error ? error.message : String(error)}`);
+ }
if (this.$module.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
setFocus(this.$module);
}
diff --git a/packages/govuk-frontend/dist/govuk/common/index.mjs b/packages/govuk-frontend/dist/govuk/common/index.mjs
index 9e1ecab6d..d6babf353 100644
--- a/packages/govuk-frontend/dist/govuk/common/index.mjs
+++ b/packages/govuk-frontend/dist/govuk/common/index.mjs
@@ -1,18 +1,22 @@
import { normaliseString } from './normalise-string.mjs';
-function mergeConfigs(...configObjects) {
+function mergeConfigs(configObjects, {
+ path = []
+} = {}) {
const formattedConfigObject = {};
for (const configObject of configObjects) {
for (const key of Object.keys(configObject)) {
const option = formattedConfigObject[key];
const override = configObject[key];
- const nonObjectOverridesObject = isObject(option) && !isObject(override);
- if (!nonObjectOverridesObject) {
- if (isObject(option) && isObject(override)) {
- formattedConfigObject[key] = mergeConfigs(option, override);
- } else {
- formattedConfigObject[key] = override;
- }
+ if (isObject(option) && !isObject(override)) {
+ throw new TypeError(`Trying to merge a non-object value over an object value for \`${[...path, key].join('.')}\``);
+ }
+ if (isObject(option) && isObject(override)) {
+ formattedConfigObject[key] = mergeConfigs([option, override], {
+ path: [...path, key]
+ });
+ } else {
+ formattedConfigObject[key] = override;
}
}
}
@@ -26,9 +30,17 @@ function extractConfigByNamespace(dataset, namespace) {
const keyParts = key.split('.');
if (keyParts[0] === namespace) {
keyParts.shift();
+ if (!keyParts.length) {
+ throw new TypeError(`\`data-${namespace}\` cannot exist on its own`);
+ }
let current = newObject;
+ const path = [];
for (const name of keyParts) {
+ path.push(name);
if (!isObject(current[name])) {
+ if (name in current) {
+ throw new TypeError(`\`data-${namespace}.${keyParts.join('.')}\` cannot exist if \`data-${namespace}.${path.join('.')}\` is present`);
+ }
current[name] = {};
}
const next = current[name];
diff --git a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js
index 595c58e08..ac102fefd 100644
--- a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js
@@ -37,19 +37,23 @@
* @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
*/
- function mergeConfigs(...configObjects) {
+ function mergeConfigs(configObjects, {
+ path = []
+ } = {}) {
const formattedConfigObject = {};
for (const configObject of configObjects) {
for (const key of Object.keys(configObject)) {
const option = formattedConfigObject[key];
const override = configObject[key];
- const nonObjectOverridesObject = isObject(option) && !isObject(override);
- if (!nonObjectOverridesObject) {
- if (isObject(option) && isObject(override)) {
- formattedConfigObject[key] = mergeConfigs(option, override);
- } else {
- formattedConfigObject[key] = override;
- }
+ if (isObject(option) && !isObject(override)) {
+ throw new TypeError(`Trying to merge a non-object value over an object value for \`${[...path, key].join('.')}\``);
+ }
+ if (isObject(option) && isObject(override)) {
+ formattedConfigObject[key] = mergeConfigs([option, override], {
+ path: [...path, key]
+ });
+ } else {
+ formattedConfigObject[key] = override;
}
}
}
@@ -63,9 +67,17 @@
const keyParts = key.split('.');
if (keyParts[0] === namespace) {
keyParts.shift();
+ if (!keyParts.length) {
+ throw new TypeError(`\`data-${namespace}\` cannot exist on its own`);
+ }
let current = newObject;
+ const path = [];
for (const name of keyParts) {
+ path.push(name);
if (!isObject(current[name])) {
+ if (name in current) {
+ throw new TypeError(`\`data-${namespace}.${keyParts.join('.')}\` cannot exist if \`data-${namespace}.${path.join('.')}\` is present`);
+ }
current[name] = {};
}
const next = current[name];
@@ -148,6 +160,12 @@
this.name = 'SupportError';
}
}
+ class ConfigError extends GOVUKFrontendError {
+ constructor(...args) {
+ super(...args);
+ this.name = 'ConfigError';
+ }
+ }
class ElementError extends GOVUKFrontendError {
constructor(messageOrOptions) {
let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
@@ -426,7 +444,11 @@
});
}
this.$module = $module;
- this.config = mergeConfigs(Accordion.defaults, config, normaliseDataset($module.dataset, Accordion.schema));
+ try {
+ this.config = mergeConfigs([Accordion.defaults, config, normaliseDataset($module.dataset, Accordion.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Accordion: ${error instanceof Error ? error.message : String(error)}`);
+ }
this.i18n = new I18n(this.config.i18n);
const $sections = this.$module.querySelectorAll(`.${this.sectionClass}`);
if (!$sections.length) {
diff --git a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs
index 6940ede63..eb4a6a71c 100644
--- a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs
@@ -31,19 +31,23 @@ function normaliseString(value, options) {
* @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
*/
-function mergeConfigs(...configObjects) {
+function mergeConfigs(configObjects, {
+ path = []
+} = {}) {
const formattedConfigObject = {};
for (const configObject of configObjects) {
for (const key of Object.keys(configObject)) {
const option = formattedConfigObject[key];
const override = configObject[key];
- const nonObjectOverridesObject = isObject(option) && !isObject(override);
- if (!nonObjectOverridesObject) {
- if (isObject(option) && isObject(override)) {
- formattedConfigObject[key] = mergeConfigs(option, override);
- } else {
- formattedConfigObject[key] = override;
- }
+ if (isObject(option) && !isObject(override)) {
+ throw new TypeError(`Trying to merge a non-object value over an object value for \`${[...path, key].join('.')}\``);
+ }
+ if (isObject(option) && isObject(override)) {
+ formattedConfigObject[key] = mergeConfigs([option, override], {
+ path: [...path, key]
+ });
+ } else {
+ formattedConfigObject[key] = override;
}
}
}
@@ -57,9 +61,17 @@ function extractConfigByNamespace(dataset, namespace) {
const keyParts = key.split('.');
if (keyParts[0] === namespace) {
keyParts.shift();
+ if (!keyParts.length) {
+ throw new TypeError(`\`data-${namespace}\` cannot exist on its own`);
+ }
let current = newObject;
+ const path = [];
for (const name of keyParts) {
+ path.push(name);
if (!isObject(current[name])) {
+ if (name in current) {
+ throw new TypeError(`\`data-${namespace}.${keyParts.join('.')}\` cannot exist if \`data-${namespace}.${path.join('.')}\` is present`);
+ }
current[name] = {};
}
const next = current[name];
@@ -142,6 +154,12 @@ class SupportError extends GOVUKFrontendError {
this.name = 'SupportError';
}
}
+class ConfigError extends GOVUKFrontendError {
+ constructor(...args) {
+ super(...args);
+ this.name = 'ConfigError';
+ }
+}
class ElementError extends GOVUKFrontendError {
constructor(messageOrOptions) {
let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
@@ -420,7 +438,11 @@ class Accordion extends GOVUKFrontendComponent {
});
}
this.$module = $module;
- this.config = mergeConfigs(Accordion.defaults, config, normaliseDataset($module.dataset, Accordion.schema));
+ try {
+ this.config = mergeConfigs([Accordion.defaults, config, normaliseDataset($module.dataset, Accordion.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Accordion: ${error instanceof Error ? error.message : String(error)}`);
+ }
this.i18n = new I18n(this.config.i18n);
const $sections = this.$module.querySelectorAll(`.${this.sectionClass}`);
if (!$sections.length) {
diff --git a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.mjs b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.mjs
index 33e3201f9..e8b287b3b 100644
--- a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.mjs
@@ -1,6 +1,6 @@
import { mergeConfigs } from '../../common/index.mjs';
import { normaliseDataset } from '../../common/normalise-dataset.mjs';
-import { ElementError } from '../../errors/index.mjs';
+import { ElementError, ConfigError } from '../../errors/index.mjs';
import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs';
import { I18n } from '../../i18n.mjs';
@@ -60,7 +60,11 @@ class Accordion extends GOVUKFrontendComponent {
});
}
this.$module = $module;
- this.config = mergeConfigs(Accordion.defaults, config, normaliseDataset($module.dataset, Accordion.schema));
+ try {
+ this.config = mergeConfigs([Accordion.defaults, config, normaliseDataset($module.dataset, Accordion.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Accordion: ${error instanceof Error ? error.message : String(error)}`);
+ }
this.i18n = new I18n(this.config.i18n);
const $sections = this.$module.querySelectorAll(`.${this.sectionClass}`);
if (!$sections.length) {
diff --git a/packages/govuk-frontend/dist/govuk/components/button/button.bundle.js b/packages/govuk-frontend/dist/govuk/components/button/button.bundle.js
index 19aa821bd..d9b3c9c65 100644
--- a/packages/govuk-frontend/dist/govuk/components/button/button.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/button/button.bundle.js
@@ -37,19 +37,23 @@
* @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
*/
- function mergeConfigs(...configObjects) {
+ function mergeConfigs(configObjects, {
+ path = []
+ } = {}) {
const formattedConfigObject = {};
for (const configObject of configObjects) {
for (const key of Object.keys(configObject)) {
const option = formattedConfigObject[key];
const override = configObject[key];
- const nonObjectOverridesObject = isObject(option) && !isObject(override);
- if (!nonObjectOverridesObject) {
- if (isObject(option) && isObject(override)) {
- formattedConfigObject[key] = mergeConfigs(option, override);
- } else {
- formattedConfigObject[key] = override;
- }
+ if (isObject(option) && !isObject(override)) {
+ throw new TypeError(`Trying to merge a non-object value over an object value for \`${[...path, key].join('.')}\``);
+ }
+ if (isObject(option) && isObject(override)) {
+ formattedConfigObject[key] = mergeConfigs([option, override], {
+ path: [...path, key]
+ });
+ } else {
+ formattedConfigObject[key] = override;
}
}
}
@@ -63,9 +67,17 @@
const keyParts = key.split('.');
if (keyParts[0] === namespace) {
keyParts.shift();
+ if (!keyParts.length) {
+ throw new TypeError(`\`data-${namespace}\` cannot exist on its own`);
+ }
let current = newObject;
+ const path = [];
for (const name of keyParts) {
+ path.push(name);
if (!isObject(current[name])) {
+ if (name in current) {
+ throw new TypeError(`\`data-${namespace}.${keyParts.join('.')}\` cannot exist if \`data-${namespace}.${path.join('.')}\` is present`);
+ }
current[name] = {};
}
const next = current[name];
@@ -148,6 +160,12 @@
this.name = 'SupportError';
}
}
+ class ConfigError extends GOVUKFrontendError {
+ constructor(...args) {
+ super(...args);
+ this.name = 'ConfigError';
+ }
+ }
class ElementError extends GOVUKFrontendError {
constructor(messageOrOptions) {
let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
@@ -203,7 +221,11 @@
});
}
this.$module = $module;
- this.config = mergeConfigs(Button.defaults, config, normaliseDataset($module.dataset, Button.schema));
+ try {
+ this.config = mergeConfigs([Button.defaults, config, normaliseDataset($module.dataset, Button.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Button: ${error instanceof Error ? error.message : String(error)}`);
+ }
this.$module.addEventListener('keydown', event => this.handleKeyDown(event));
this.$module.addEventListener('click', event => this.debounce(event));
}
diff --git a/packages/govuk-frontend/dist/govuk/components/button/button.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/button/button.bundle.mjs
index c5f81233b..8448bade6 100644
--- a/packages/govuk-frontend/dist/govuk/components/button/button.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/button/button.bundle.mjs
@@ -31,19 +31,23 @@ function normaliseString(value, options) {
* @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
*/
-function mergeConfigs(...configObjects) {
+function mergeConfigs(configObjects, {
+ path = []
+} = {}) {
const formattedConfigObject = {};
for (const configObject of configObjects) {
for (const key of Object.keys(configObject)) {
const option = formattedConfigObject[key];
const override = configObject[key];
- const nonObjectOverridesObject = isObject(option) && !isObject(override);
- if (!nonObjectOverridesObject) {
- if (isObject(option) && isObject(override)) {
- formattedConfigObject[key] = mergeConfigs(option, override);
- } else {
- formattedConfigObject[key] = override;
- }
+ if (isObject(option) && !isObject(override)) {
+ throw new TypeError(`Trying to merge a non-object value over an object value for \`${[...path, key].join('.')}\``);
+ }
+ if (isObject(option) && isObject(override)) {
+ formattedConfigObject[key] = mergeConfigs([option, override], {
+ path: [...path, key]
+ });
+ } else {
+ formattedConfigObject[key] = override;
}
}
}
@@ -57,9 +61,17 @@ function extractConfigByNamespace(dataset, namespace) {
const keyParts = key.split('.');
if (keyParts[0] === namespace) {
keyParts.shift();
+ if (!keyParts.length) {
+ throw new TypeError(`\`data-${namespace}\` cannot exist on its own`);
+ }
let current = newObject;
+ const path = [];
for (const name of keyParts) {
+ path.push(name);
if (!isObject(current[name])) {
+ if (name in current) {
+ throw new TypeError(`\`data-${namespace}.${keyParts.join('.')}\` cannot exist if \`data-${namespace}.${path.join('.')}\` is present`);
+ }
current[name] = {};
}
const next = current[name];
@@ -142,6 +154,12 @@ class SupportError extends GOVUKFrontendError {
this.name = 'SupportError';
}
}
+class ConfigError extends GOVUKFrontendError {
+ constructor(...args) {
+ super(...args);
+ this.name = 'ConfigError';
+ }
+}
class ElementError extends GOVUKFrontendError {
constructor(messageOrOptions) {
let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
@@ -197,7 +215,11 @@ class Button extends GOVUKFrontendComponent {
});
}
this.$module = $module;
- this.config = mergeConfigs(Button.defaults, config, normaliseDataset($module.dataset, Button.schema));
+ try {
+ this.config = mergeConfigs([Button.defaults, config, normaliseDataset($module.dataset, Button.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Button: ${error instanceof Error ? error.message : String(error)}`);
+ }
this.$module.addEventListener('keydown', event => this.handleKeyDown(event));
this.$module.addEventListener('click', event => this.debounce(event));
}
diff --git a/packages/govuk-frontend/dist/govuk/components/button/button.mjs b/packages/govuk-frontend/dist/govuk/components/button/button.mjs
index 7907e2411..a31c2380b 100644
--- a/packages/govuk-frontend/dist/govuk/components/button/button.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/button/button.mjs
@@ -1,6 +1,6 @@
import { mergeConfigs } from '../../common/index.mjs';
import { normaliseDataset } from '../../common/normalise-dataset.mjs';
-import { ElementError } from '../../errors/index.mjs';
+import { ElementError, ConfigError } from '../../errors/index.mjs';
import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs';
const KEY_SPACE = 32;
@@ -29,7 +29,11 @@ class Button extends GOVUKFrontendComponent {
});
}
this.$module = $module;
- this.config = mergeConfigs(Button.defaults, config, normaliseDataset($module.dataset, Button.schema));
+ try {
+ this.config = mergeConfigs([Button.defaults, config, normaliseDataset($module.dataset, Button.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Button: ${error instanceof Error ? error.message : String(error)}`);
+ }
this.$module.addEventListener('keydown', event => this.handleKeyDown(event));
this.$module.addEventListener('click', event => this.debounce(event));
}
diff --git a/packages/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js b/packages/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js
index 8a9a10c54..efe221585 100644
--- a/packages/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js
@@ -42,19 +42,23 @@
* @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
*/
- function mergeConfigs(...configObjects) {
+ function mergeConfigs(configObjects, {
+ path = []
+ } = {}) {
const formattedConfigObject = {};
for (const configObject of configObjects) {
for (const key of Object.keys(configObject)) {
const option = formattedConfigObject[key];
const override = configObject[key];
- const nonObjectOverridesObject = isObject(option) && !isObject(override);
- if (!nonObjectOverridesObject) {
- if (isObject(option) && isObject(override)) {
- formattedConfigObject[key] = mergeConfigs(option, override);
- } else {
- formattedConfigObject[key] = override;
- }
+ if (isObject(option) && !isObject(override)) {
+ throw new TypeError(`Trying to merge a non-object value over an object value for \`${[...path, key].join('.')}\``);
+ }
+ if (isObject(option) && isObject(override)) {
+ formattedConfigObject[key] = mergeConfigs([option, override], {
+ path: [...path, key]
+ });
+ } else {
+ formattedConfigObject[key] = override;
}
}
}
@@ -68,9 +72,17 @@
const keyParts = key.split('.');
if (keyParts[0] === namespace) {
keyParts.shift();
+ if (!keyParts.length) {
+ throw new TypeError(`\`data-${namespace}\` cannot exist on its own`);
+ }
let current = newObject;
+ const path = [];
for (const name of keyParts) {
+ path.push(name);
if (!isObject(current[name])) {
+ if (name in current) {
+ throw new TypeError(`\`data-${namespace}.${keyParts.join('.')}\` cannot exist if \`data-${namespace}.${path.join('.')}\` is present`);
+ }
current[name] = {};
}
const next = current[name];
@@ -455,7 +467,11 @@
maxwords: undefined
};
}
- this.config = mergeConfigs(CharacterCount.defaults, config, configOverrides, datasetConfig);
+ try {
+ this.config = mergeConfigs([CharacterCount.defaults, config, configOverrides, datasetConfig]);
+ } catch (error) {
+ throw new ConfigError(`Character count: ${error instanceof Error ? error.message : String(error)}`);
+ }
const errors = validateConfig(CharacterCount.schema, this.config);
if (errors[0]) {
throw new ConfigError(`Character count: ${errors[0]}`);
diff --git a/packages/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs
index 221daad80..ba708a86e 100644
--- a/packages/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs
@@ -36,19 +36,23 @@ function normaliseString(value, options) {
* @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
*/
-function mergeConfigs(...configObjects) {
+function mergeConfigs(configObjects, {
+ path = []
+} = {}) {
const formattedConfigObject = {};
for (const configObject of configObjects) {
for (const key of Object.keys(configObject)) {
const option = formattedConfigObject[key];
const override = configObject[key];
- const nonObjectOverridesObject = isObject(option) && !isObject(override);
- if (!nonObjectOverridesObject) {
- if (isObject(option) && isObject(override)) {
- formattedConfigObject[key] = mergeConfigs(option, override);
- } else {
- formattedConfigObject[key] = override;
- }
+ if (isObject(option) && !isObject(override)) {
+ throw new TypeError(`Trying to merge a non-object value over an object value for \`${[...path, key].join('.')}\``);
+ }
+ if (isObject(option) && isObject(override)) {
+ formattedConfigObject[key] = mergeConfigs([option, override], {
+ path: [...path, key]
+ });
+ } else {
+ formattedConfigObject[key] = override;
}
}
}
@@ -62,9 +66,17 @@ function extractConfigByNamespace(dataset, namespace) {
const keyParts = key.split('.');
if (keyParts[0] === namespace) {
keyParts.shift();
+ if (!keyParts.length) {
+ throw new TypeError(`\`data-${namespace}\` cannot exist on its own`);
+ }
let current = newObject;
+ const path = [];
for (const name of keyParts) {
+ path.push(name);
if (!isObject(current[name])) {
+ if (name in current) {
+ throw new TypeError(`\`data-${namespace}.${keyParts.join('.')}\` cannot exist if \`data-${namespace}.${path.join('.')}\` is present`);
+ }
current[name] = {};
}
const next = current[name];
@@ -449,7 +461,11 @@ class CharacterCount extends GOVUKFrontendComponent {
maxwords: undefined
};
}
- this.config = mergeConfigs(CharacterCount.defaults, config, configOverrides, datasetConfig);
+ try {
+ this.config = mergeConfigs([CharacterCount.defaults, config, configOverrides, datasetConfig]);
+ } catch (error) {
+ throw new ConfigError(`Character count: ${error instanceof Error ? error.message : String(error)}`);
+ }
const errors = validateConfig(CharacterCount.schema, this.config);
if (errors[0]) {
throw new ConfigError(`Character count: ${errors[0]}`);
diff --git a/packages/govuk-frontend/dist/govuk/components/character-count/character-count.mjs b/packages/govuk-frontend/dist/govuk/components/character-count/character-count.mjs
index d33d26cc7..376efdcb5 100644
--- a/packages/govuk-frontend/dist/govuk/components/character-count/character-count.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/character-count/character-count.mjs
@@ -59,7 +59,11 @@ class CharacterCount extends GOVUKFrontendComponent {
maxwords: undefined
};
}
- this.config = mergeConfigs(CharacterCount.defaults, config, configOverrides, datasetConfig);
+ try {
+ this.config = mergeConfigs([CharacterCount.defaults, config, configOverrides, datasetConfig]);
+ } catch (error) {
+ throw new ConfigError(`Character count: ${error instanceof Error ? error.message : String(error)}`);
+ }
const errors = validateConfig(CharacterCount.schema, this.config);
if (errors[0]) {
throw new ConfigError(`Character count: ${errors[0]}`);
diff --git a/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js b/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js
index a7893611f..971b6d98a 100644
--- a/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js
@@ -37,19 +37,23 @@
* @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
*/
- function mergeConfigs(...configObjects) {
+ function mergeConfigs(configObjects, {
+ path = []
+ } = {}) {
const formattedConfigObject = {};
for (const configObject of configObjects) {
for (const key of Object.keys(configObject)) {
const option = formattedConfigObject[key];
const override = configObject[key];
- const nonObjectOverridesObject = isObject(option) && !isObject(override);
- if (!nonObjectOverridesObject) {
- if (isObject(option) && isObject(override)) {
- formattedConfigObject[key] = mergeConfigs(option, override);
- } else {
- formattedConfigObject[key] = override;
- }
+ if (isObject(option) && !isObject(override)) {
+ throw new TypeError(`Trying to merge a non-object value over an object value for \`${[...path, key].join('.')}\``);
+ }
+ if (isObject(option) && isObject(override)) {
+ formattedConfigObject[key] = mergeConfigs([option, override], {
+ path: [...path, key]
+ });
+ } else {
+ formattedConfigObject[key] = override;
}
}
}
@@ -63,9 +67,17 @@
const keyParts = key.split('.');
if (keyParts[0] === namespace) {
keyParts.shift();
+ if (!keyParts.length) {
+ throw new TypeError(`\`data-${namespace}\` cannot exist on its own`);
+ }
let current = newObject;
+ const path = [];
for (const name of keyParts) {
+ path.push(name);
if (!isObject(current[name])) {
+ if (name in current) {
+ throw new TypeError(`\`data-${namespace}.${keyParts.join('.')}\` cannot exist if \`data-${namespace}.${path.join('.')}\` is present`);
+ }
current[name] = {};
}
const next = current[name];
@@ -178,6 +190,12 @@
this.name = 'SupportError';
}
}
+ class ConfigError extends GOVUKFrontendError {
+ constructor(...args) {
+ super(...args);
+ this.name = 'ConfigError';
+ }
+ }
class ElementError extends GOVUKFrontendError {
constructor(messageOrOptions) {
let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
@@ -232,7 +250,11 @@
});
}
this.$module = $module;
- this.config = mergeConfigs(ErrorSummary.defaults, config, normaliseDataset($module.dataset, ErrorSummary.schema));
+ try {
+ this.config = mergeConfigs([ErrorSummary.defaults, config, normaliseDataset($module.dataset, ErrorSummary.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Error summary: ${error instanceof Error ? error.message : String(error)}`);
+ }
if (!this.config.disableAutoFocus) {
setFocus(this.$module);
}
diff --git a/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs
index b027bb049..a64e7fb0a 100644
--- a/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs
@@ -31,19 +31,23 @@ function normaliseString(value, options) {
* @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
*/
-function mergeConfigs(...configObjects) {
+function mergeConfigs(configObjects, {
+ path = []
+} = {}) {
const formattedConfigObject = {};
for (const configObject of configObjects) {
for (const key of Object.keys(configObject)) {
const option = formattedConfigObject[key];
const override = configObject[key];
- const nonObjectOverridesObject = isObject(option) && !isObject(override);
- if (!nonObjectOverridesObject) {
- if (isObject(option) && isObject(override)) {
- formattedConfigObject[key] = mergeConfigs(option, override);
- } else {
- formattedConfigObject[key] = override;
- }
+ if (isObject(option) && !isObject(override)) {
+ throw new TypeError(`Trying to merge a non-object value over an object value for \`${[...path, key].join('.')}\``);
+ }
+ if (isObject(option) && isObject(override)) {
+ formattedConfigObject[key] = mergeConfigs([option, override], {
+ path: [...path, key]
+ });
+ } else {
+ formattedConfigObject[key] = override;
}
}
}
@@ -57,9 +61,17 @@ function extractConfigByNamespace(dataset, namespace) {
const keyParts = key.split('.');
if (keyParts[0] === namespace) {
keyParts.shift();
+ if (!keyParts.length) {
+ throw new TypeError(`\`data-${namespace}\` cannot exist on its own`);
+ }
let current = newObject;
+ const path = [];
for (const name of keyParts) {
+ path.push(name);
if (!isObject(current[name])) {
+ if (name in current) {
+ throw new TypeError(`\`data-${namespace}.${keyParts.join('.')}\` cannot exist if \`data-${namespace}.${path.join('.')}\` is present`);
+ }
current[name] = {};
}
const next = current[name];
@@ -172,6 +184,12 @@ class SupportError extends GOVUKFrontendError {
this.name = 'SupportError';
}
}
+class ConfigError extends GOVUKFrontendError {
+ constructor(...args) {
+ super(...args);
+ this.name = 'ConfigError';
+ }
+}
class ElementError extends GOVUKFrontendError {
constructor(messageOrOptions) {
let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
@@ -226,7 +244,11 @@ class ErrorSummary extends GOVUKFrontendComponent {
});
}
this.$module = $module;
- this.config = mergeConfigs(ErrorSummary.defaults, config, normaliseDataset($module.dataset, ErrorSummary.schema));
+ try {
+ this.config = mergeConfigs([ErrorSummary.defaults, config, normaliseDataset($module.dataset, ErrorSummary.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Error summary: ${error instanceof Error ? error.message : String(error)}`);
+ }
if (!this.config.disableAutoFocus) {
setFocus(this.$module);
}
diff --git a/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.mjs b/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.mjs
index 75bd427cd..bf138be77 100644
--- a/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.mjs
@@ -1,6 +1,6 @@
import { mergeConfigs, setFocus, getFragmentFromUrl } from '../../common/index.mjs';
import { normaliseDataset } from '../../common/normalise-dataset.mjs';
-import { ElementError } from '../../errors/index.mjs';
+import { ElementError, ConfigError } from '../../errors/index.mjs';
import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs';
/**
@@ -28,7 +28,11 @@ class ErrorSummary extends GOVUKFrontendComponent {
});
}
this.$module = $module;
- this.config = mergeConfigs(ErrorSummary.defaults, config, normaliseDataset($module.dataset, ErrorSummary.schema));
+ try {
+ this.config = mergeConfigs([ErrorSummary.defaults, config, normaliseDataset($module.dataset, ErrorSummary.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Error summary: ${error instanceof Error ? error.message : String(error)}`);
+ }
if (!this.config.disableAutoFocus) {
setFocus(this.$module);
}
diff --git a/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js b/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js
index 200424d86..10c1fec07 100644
--- a/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js
@@ -37,19 +37,23 @@
* @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
*/
- function mergeConfigs(...configObjects) {
+ function mergeConfigs(configObjects, {
+ path = []
+ } = {}) {
const formattedConfigObject = {};
for (const configObject of configObjects) {
for (const key of Object.keys(configObject)) {
const option = formattedConfigObject[key];
const override = configObject[key];
- const nonObjectOverridesObject = isObject(option) && !isObject(override);
- if (!nonObjectOverridesObject) {
- if (isObject(option) && isObject(override)) {
- formattedConfigObject[key] = mergeConfigs(option, override);
- } else {
- formattedConfigObject[key] = override;
- }
+ if (isObject(option) && !isObject(override)) {
+ throw new TypeError(`Trying to merge a non-object value over an object value for \`${[...path, key].join('.')}\``);
+ }
+ if (isObject(option) && isObject(override)) {
+ formattedConfigObject[key] = mergeConfigs([option, override], {
+ path: [...path, key]
+ });
+ } else {
+ formattedConfigObject[key] = override;
}
}
}
@@ -63,9 +67,17 @@
const keyParts = key.split('.');
if (keyParts[0] === namespace) {
keyParts.shift();
+ if (!keyParts.length) {
+ throw new TypeError(`\`data-${namespace}\` cannot exist on its own`);
+ }
let current = newObject;
+ const path = [];
for (const name of keyParts) {
+ path.push(name);
if (!isObject(current[name])) {
+ if (name in current) {
+ throw new TypeError(`\`data-${namespace}.${keyParts.join('.')}\` cannot exist if \`data-${namespace}.${path.join('.')}\` is present`);
+ }
current[name] = {};
}
const next = current[name];
@@ -148,6 +160,12 @@
this.name = 'SupportError';
}
}
+ class ConfigError extends GOVUKFrontendError {
+ constructor(...args) {
+ super(...args);
+ this.name = 'ConfigError';
+ }
+ }
class ElementError extends GOVUKFrontendError {
constructor(messageOrOptions) {
let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
@@ -411,7 +429,11 @@
identifier: 'Button (`.govuk-exit-this-page__button`)'
});
}
- this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset($module.dataset, ExitThisPage.schema));
+ try {
+ this.config = mergeConfigs([ExitThisPage.defaults, config, normaliseDataset($module.dataset, ExitThisPage.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Exit this page: ${error instanceof Error ? error.message : String(error)}`);
+ }
this.i18n = new I18n(this.config.i18n);
this.$module = $module;
this.$button = $button;
diff --git a/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs
index 5e1b111d2..d5956610d 100644
--- a/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs
@@ -31,19 +31,23 @@ function normaliseString(value, options) {
* @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
*/
-function mergeConfigs(...configObjects) {
+function mergeConfigs(configObjects, {
+ path = []
+} = {}) {
const formattedConfigObject = {};
for (const configObject of configObjects) {
for (const key of Object.keys(configObject)) {
const option = formattedConfigObject[key];
const override = configObject[key];
- const nonObjectOverridesObject = isObject(option) && !isObject(override);
- if (!nonObjectOverridesObject) {
- if (isObject(option) && isObject(override)) {
- formattedConfigObject[key] = mergeConfigs(option, override);
- } else {
- formattedConfigObject[key] = override;
- }
+ if (isObject(option) && !isObject(override)) {
+ throw new TypeError(`Trying to merge a non-object value over an object value for \`${[...path, key].join('.')}\``);
+ }
+ if (isObject(option) && isObject(override)) {
+ formattedConfigObject[key] = mergeConfigs([option, override], {
+ path: [...path, key]
+ });
+ } else {
+ formattedConfigObject[key] = override;
}
}
}
@@ -57,9 +61,17 @@ function extractConfigByNamespace(dataset, namespace) {
const keyParts = key.split('.');
if (keyParts[0] === namespace) {
keyParts.shift();
+ if (!keyParts.length) {
+ throw new TypeError(`\`data-${namespace}\` cannot exist on its own`);
+ }
let current = newObject;
+ const path = [];
for (const name of keyParts) {
+ path.push(name);
if (!isObject(current[name])) {
+ if (name in current) {
+ throw new TypeError(`\`data-${namespace}.${keyParts.join('.')}\` cannot exist if \`data-${namespace}.${path.join('.')}\` is present`);
+ }
current[name] = {};
}
const next = current[name];
@@ -142,6 +154,12 @@ class SupportError extends GOVUKFrontendError {
this.name = 'SupportError';
}
}
+class ConfigError extends GOVUKFrontendError {
+ constructor(...args) {
+ super(...args);
+ this.name = 'ConfigError';
+ }
+}
class ElementError extends GOVUKFrontendError {
constructor(messageOrOptions) {
let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
@@ -405,7 +423,11 @@ class ExitThisPage extends GOVUKFrontendComponent {
identifier: 'Button (`.govuk-exit-this-page__button`)'
});
}
- this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset($module.dataset, ExitThisPage.schema));
+ try {
+ this.config = mergeConfigs([ExitThisPage.defaults, config, normaliseDataset($module.dataset, ExitThisPage.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Exit this page: ${error instanceof Error ? error.message : String(error)}`);
+ }
this.i18n = new I18n(this.config.i18n);
this.$module = $module;
this.$button = $button;
diff --git a/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs b/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs
index 4dd128bf6..c510258c0 100644
--- a/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.mjs
@@ -1,6 +1,6 @@
import { mergeConfigs } from '../../common/index.mjs';
import { normaliseDataset } from '../../common/normalise-dataset.mjs';
-import { ElementError } from '../../errors/index.mjs';
+import { ElementError, ConfigError } from '../../errors/index.mjs';
import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs';
import { I18n } from '../../i18n.mjs';
@@ -45,7 +45,11 @@ class ExitThisPage extends GOVUKFrontendComponent {
identifier: 'Button (`.govuk-exit-this-page__button`)'
});
}
- this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset($module.dataset, ExitThisPage.schema));
+ try {
+ this.config = mergeConfigs([ExitThisPage.defaults, config, normaliseDataset($module.dataset, ExitThisPage.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Exit this page: ${error instanceof Error ? error.message : String(error)}`);
+ }
this.i18n = new I18n(this.config.i18n);
this.$module = $module;
this.$button = $button;
diff --git a/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js b/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js
index 64db9e782..8f53f484b 100644
--- a/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js
@@ -37,19 +37,23 @@
* @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
*/
- function mergeConfigs(...configObjects) {
+ function mergeConfigs(configObjects, {
+ path = []
+ } = {}) {
const formattedConfigObject = {};
for (const configObject of configObjects) {
for (const key of Object.keys(configObject)) {
const option = formattedConfigObject[key];
const override = configObject[key];
- const nonObjectOverridesObject = isObject(option) && !isObject(override);
- if (!nonObjectOverridesObject) {
- if (isObject(option) && isObject(override)) {
- formattedConfigObject[key] = mergeConfigs(option, override);
- } else {
- formattedConfigObject[key] = override;
- }
+ if (isObject(option) && !isObject(override)) {
+ throw new TypeError(`Trying to merge a non-object value over an object value for \`${[...path, key].join('.')}\``);
+ }
+ if (isObject(option) && isObject(override)) {
+ formattedConfigObject[key] = mergeConfigs([option, override], {
+ path: [...path, key]
+ });
+ } else {
+ formattedConfigObject[key] = override;
}
}
}
@@ -63,9 +67,17 @@
const keyParts = key.split('.');
if (keyParts[0] === namespace) {
keyParts.shift();
+ if (!keyParts.length) {
+ throw new TypeError(`\`data-${namespace}\` cannot exist on its own`);
+ }
let current = newObject;
+ const path = [];
for (const name of keyParts) {
+ path.push(name);
if (!isObject(current[name])) {
+ if (name in current) {
+ throw new TypeError(`\`data-${namespace}.${keyParts.join('.')}\` cannot exist if \`data-${namespace}.${path.join('.')}\` is present`);
+ }
current[name] = {};
}
const next = current[name];
@@ -172,6 +184,12 @@
this.name = 'SupportError';
}
}
+ class ConfigError extends GOVUKFrontendError {
+ constructor(...args) {
+ super(...args);
+ this.name = 'ConfigError';
+ }
+ }
class ElementError extends GOVUKFrontendError {
constructor(messageOrOptions) {
let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
@@ -223,7 +241,11 @@
});
}
this.$module = $module;
- this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset($module.dataset, NotificationBanner.schema));
+ try {
+ this.config = mergeConfigs([NotificationBanner.defaults, config, normaliseDataset($module.dataset, NotificationBanner.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Notification banner: ${error instanceof Error ? error.message : String(error)}`);
+ }
if (this.$module.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
setFocus(this.$module);
}
diff --git a/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs
index aafa22795..4b5224392 100644
--- a/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs
@@ -31,19 +31,23 @@ function normaliseString(value, options) {
* @typedef {import('./index.mjs').SchemaProperty} SchemaProperty
*/
-function mergeConfigs(...configObjects) {
+function mergeConfigs(configObjects, {
+ path = []
+} = {}) {
const formattedConfigObject = {};
for (const configObject of configObjects) {
for (const key of Object.keys(configObject)) {
const option = formattedConfigObject[key];
const override = configObject[key];
- const nonObjectOverridesObject = isObject(option) && !isObject(override);
- if (!nonObjectOverridesObject) {
- if (isObject(option) && isObject(override)) {
- formattedConfigObject[key] = mergeConfigs(option, override);
- } else {
- formattedConfigObject[key] = override;
- }
+ if (isObject(option) && !isObject(override)) {
+ throw new TypeError(`Trying to merge a non-object value over an object value for \`${[...path, key].join('.')}\``);
+ }
+ if (isObject(option) && isObject(override)) {
+ formattedConfigObject[key] = mergeConfigs([option, override], {
+ path: [...path, key]
+ });
+ } else {
+ formattedConfigObject[key] = override;
}
}
}
@@ -57,9 +61,17 @@ function extractConfigByNamespace(dataset, namespace) {
const keyParts = key.split('.');
if (keyParts[0] === namespace) {
keyParts.shift();
+ if (!keyParts.length) {
+ throw new TypeError(`\`data-${namespace}\` cannot exist on its own`);
+ }
let current = newObject;
+ const path = [];
for (const name of keyParts) {
+ path.push(name);
if (!isObject(current[name])) {
+ if (name in current) {
+ throw new TypeError(`\`data-${namespace}.${keyParts.join('.')}\` cannot exist if \`data-${namespace}.${path.join('.')}\` is present`);
+ }
current[name] = {};
}
const next = current[name];
@@ -166,6 +178,12 @@ class SupportError extends GOVUKFrontendError {
this.name = 'SupportError';
}
}
+class ConfigError extends GOVUKFrontendError {
+ constructor(...args) {
+ super(...args);
+ this.name = 'ConfigError';
+ }
+}
class ElementError extends GOVUKFrontendError {
constructor(messageOrOptions) {
let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
@@ -217,7 +235,11 @@ class NotificationBanner extends GOVUKFrontendComponent {
});
}
this.$module = $module;
- this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset($module.dataset, NotificationBanner.schema));
+ try {
+ this.config = mergeConfigs([NotificationBanner.defaults, config, normaliseDataset($module.dataset, NotificationBanner.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Notification banner: ${error instanceof Error ? error.message : String(error)}`);
+ }
if (this.$module.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
setFocus(this.$module);
}
diff --git a/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.mjs b/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.mjs
index 772d37bc0..eed779b96 100644
--- a/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.mjs
@@ -1,6 +1,6 @@
import { mergeConfigs, setFocus } from '../../common/index.mjs';
import { normaliseDataset } from '../../common/normalise-dataset.mjs';
-import { ElementError } from '../../errors/index.mjs';
+import { ElementError, ConfigError } from '../../errors/index.mjs';
import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs';
/**
@@ -25,7 +25,11 @@ class NotificationBanner extends GOVUKFrontendComponent {
});
}
this.$module = $module;
- this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset($module.dataset, NotificationBanner.schema));
+ try {
+ this.config = mergeConfigs([NotificationBanner.defaults, config, normaliseDataset($module.dataset, NotificationBanner.schema)]);
+ } catch (error) {
+ throw new ConfigError(`Notification banner: ${error instanceof Error ? error.message : String(error)}`);
+ }
if (this.$module.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) {
setFocus(this.$module);
}
Action run for 6c63991 |
Closing to avoid noise in the list of PRs now that #4792 is merged. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Make both
mergeConfig
andextractConfigByNamespace
throw if they try to merge a non-object value on a key that already stores an object value.The errors would let users know that they are (likely unintentionally) passing a property or data-attribute that conflicts with deeper values (for example setting
data-i18n
which would default with the configuration for the default message).While this sounds a handy information:
Implementation note
Was a bit shy to pass
componentName
to the helper functions, but that could be an alternative solution that avoids catching and re-throwing the errors. Doesn't feel super tidy from a separation of responsibility point of view.