diff options
author | (quasar) nebula <qznebula@protonmail.com> | 2023-12-29 13:31:39 -0400 |
---|---|---|
committer | (quasar) nebula <qznebula@protonmail.com> | 2023-12-30 13:23:07 -0400 |
commit | 3ec9473bf3fb6438a331890138c0f81264495c63 (patch) | |
tree | 3c0f4fe5765247285dc44eff84360138c6d6b9c6 /src/data | |
parent | 2ea4de9ad3a7187dea5cab3ef1bb33f9a158819f (diff) |
data: language: compare missing/misplaced options more rigorously
Diffstat (limited to 'src/data')
-rw-r--r-- | src/data/things/language.js | 40 |
1 files changed, 32 insertions, 8 deletions
diff --git a/src/data/things/language.js b/src/data/things/language.js index 6c15e282..60d089b7 100644 --- a/src/data/things/language.js +++ b/src/data/things/language.js @@ -2,6 +2,7 @@ import { Temporal, toTemporalInstant } from '@js-temporal/polyfill'; import {isLanguageCode} from '#validators'; import {Tag} from '#html'; +import {empty, withAggregate} from '#sugar'; import { getExternalLinkStringOfStyleFromDescriptors, @@ -188,13 +189,42 @@ export class Language extends Thing { const template = this.strings[key]; + const providedOptionNames = + (hasOptions + ? Object.keys(options) + .map(name => name.replace(/[A-Z]/g, '_$&')) + .map(name => name.toUpperCase()) + : []); + + const expectedOptionNames = + Array.from(template.matchAll(/{(?<name>[A-Z0-9_]+)}/g)) + .map(({groups}) => groups.name); + + const missingOptionNames = + expectedOptionNames.filter(name => !providedOptionNames.includes(name)); + + const misplacedOptionNames = + providedOptionNames.filter(name => !expectedOptionNames.includes(name)); + + withAggregate({message: `Errors in options for string "${key}"`}, ({push}) => { + if (!empty(missingOptionNames)) { + const names = missingOptionNames.join(`, `); + push(new Error(`Missing options: ${names}`)); + } + + if (!empty(misplacedOptionNames)) { + const names = misplacedOptionNames.join(`, `); + push(new Error(`Unexpected options: ${names}`)); + } + }); + let output; if (hasOptions) { // Convert the keys on the options dict from camelCase to CONSTANT_CASE. // (This isn't an OUTRAGEOUSLY versatile algorithm for doing that, 8ut // like, who cares, dude?) Also, this is an array, 8ecause it's handy - // for the iterating we're a8out to do. Also strip HTML from arguments + // for the iterating we're a8out to do. Also strip HTML from options // that are literal strings - real HTML content should always be proper // HTML objects (see html.js). const processedOptions = @@ -210,17 +240,11 @@ export class Language extends Thing { template); } else { // Without any options provided, just use the template as-is. This will - // still error if the template expected arguments, and otherwise will be + // have errored if the template expected options, and otherwise will be // the right value. output = template; } - // Post-processing: if any expected arguments *weren't* replaced, that - // is almost definitely an error. - if (output.match(/\{[A-Z][A-Z0-9_]*\}/)) { - throw new Error(`Args in ${key} were missing - output: ${output}`); - } - // Last caveat: Wrap the output in an HTML tag so that it doesn't get // treated as unsanitized HTML if *it* gets passed as an argument to // *another* formatString call. |