Skip to content

Commit

Permalink
Use strict types
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Nov 8, 2021
1 parent 5999297 commit 9a600f5
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 81 deletions.
101 changes: 75 additions & 26 deletions build.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {normal} from 'bcp-47/lib/normal.js'

const own = {}.hasOwnProperty

/** @type {{supplemental: {likelySubtags: Object.<string, string>}}} */
/** @type {{supplemental: {likelySubtags: Record<string, string>}}} */
const data = JSON.parse(
String(
fs.readFileSync(
Expand All @@ -31,7 +31,7 @@ const data = JSON.parse(

const likelySubtags = data.supplemental.likelySubtags

/** @type {Object.<string, string>} */
/** @type {Record<string, string>} */
const likely = {}
/** @type {string} */
let key
Expand All @@ -42,7 +42,16 @@ for (key in likelySubtags) {
}
}

write('likely', likely)
fs.writeFileSync(
path.join('lib', 'likely.js'),
[
'/**',
' * @type {Record<string, string>}',
' */',
'export const likely = ' + JSON.stringify(likely, null, 2),
''
].join('\n')
)

const endpoint =
'https://raw.githubusercontent.com/unicode-org/cldr/HEAD/common/supplemental/supplementalMetadata.xml'
Expand All @@ -55,11 +64,11 @@ fetch(endpoint)
* @param {string} doc
*/
function onbody(doc) {
/** @type {Array.<Field>} */
/** @type {Array<Field>} */
const fields = []
/** @type {Object.<string, Object.<string, Array.<string>>>} */
/** @type {Record<string, Record<string, Array<string>>>} */
const many = {}
/** @type {Array.<Match>} */
/** @type {Array<Match>} */
const match = []
const suffix = 'Alias'
let seenHeploc = false
Expand All @@ -72,14 +81,66 @@ function onbody(doc) {

visit(fromXml(doc), 'element', onelement)

write('fields', fields)
write('many', many)
write('matches', match)
fs.writeFileSync(
path.join('lib', 'fields.js'),
[
'/**',
" * @typedef {'script'|'region'|'variants'} Field",
' *',
' * @typedef AddOrRemove',
' * @property {Field} field',
' * @property {string} value',
' *',
' * @typedef Change',
' * @property {AddOrRemove} from',
' * @property {AddOrRemove} to',
' */',
'',
'/**',
' * @type {Array<Change>}',
' */',
'export const fields = ' + JSON.stringify(fields, null, 2),
''
].join('\n')
)

fs.writeFileSync(
path.join('lib', 'many.js'),
[
'/**',
" * @typedef {'script'|'region'|'variants'} Field",
' */',
'',
'/**',
' * @type {{region: Record<string, Array<string>>}}',
' */',
'export const many = ' + JSON.stringify(many, null, 2),
''
].join('\n')
)

fs.writeFileSync(
path.join('lib', 'matches.js'),
[
'/**',
' * @typedef Change',
' * @property {string} from',
' * @property {string} to',
' */',
'',
'/**',
' * @type {Array<Change>}',
' */',
'export const matches = ' + JSON.stringify(match, null, 2),
''
].join('\n')
)

/** @param {Element} node */
/* eslint-disable-next-line complexity */
function onelement(node) {
let name = node.name
const attributes = node.attributes || {}
const pos = name.indexOf(suffix)

if (pos === -1) {
Expand All @@ -100,8 +161,8 @@ function onbody(doc) {
return
}

const allFrom = clean(node.attributes.type)
const allTo = clean(node.attributes.replacement)
const allFrom = clean(attributes.type)
const allTo = clean(attributes.replacement)
/** @type {string} */
let from
/** @type {string} */
Expand Down Expand Up @@ -182,26 +243,14 @@ function onbody(doc) {
}

/**
* @param {string} value
* @returns {Array.<string>}
* @param {string|null|undefined} value
* @returns {Array<string>}
*/
function clean(value) {
return value
return String(value || '')
.toLowerCase()
.replace(/_/g, '-')
.replace(/\s+/g, ' ')
.trim()
.split(' ')
}

/**
* @param {string} name
* @param {unknown} values
* @returns {void}
*/
function write(name, values) {
fs.writeFileSync(
path.join('lib', name + '.js'),
'export const ' + name + ' = ' + JSON.stringify(values, null, 2) + '\n'
)
}
15 changes: 15 additions & 0 deletions lib/fields.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
/**
* @typedef {'script'|'region'|'variants'} Field
*
* @typedef AddOrRemove
* @property {Field} field
* @property {string} value
*
* @typedef Change
* @property {AddOrRemove} from
* @property {AddOrRemove} to
*/

/**
* @type {Array<Change>}
*/
export const fields = [
{
from: {
Expand Down
95 changes: 51 additions & 44 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @typedef {import('bcp-47/lib/parse.js').ParseOptions['warning']} Warning
* @typedef {import('bcp-47/lib/parse.js').Schema} Schema
* @typedef {import('bcp-47/lib/parse.js').Extension} Extension
* @typedef {import('bcp-47').Warning} Warning
* @typedef {import('bcp-47').Schema} Schema
* @typedef {import('bcp-47').Extension} Extension
*
* @typedef Options
* @property {boolean} [forgiving]
Expand Down Expand Up @@ -43,7 +43,7 @@ function merge(base, changes) {
*/
function addLikelySubtags(schema) {
const {language, script, region} = schema
/** @type {string} */
/** @type {string|undefined} */
let match

if (
Expand All @@ -57,7 +57,7 @@ function addLikelySubtags(schema) {
schema.script = undefined
} else if (region && (match = likely[stringify({language, region})])) {
schema.region = undefined
} else if ((match = likely[language])) {
} else if (language && (match = likely[language])) {
// Empty.
}

Expand Down Expand Up @@ -148,22 +148,27 @@ export function bcp47Normalize(value, options) {
// 6. Warn if fields (currently only regions) should be updated but have
// multiple choices.
if (settings.warning) {
/** @type {string} */
/** @type {keyof many} */
let key

for (key in many) {
if (own.call(many[key], schema[key])) {
settings.warning(
'Deprecated ' +
key +
' `' +
schema[key] +
'`, expected one of `' +
many[key][schema[key]].join('`, `') +
'`',
null,
7
)
if (own.call(many, key)) {
const map = many[key]
const value = schema[key]
if (value && own.call(map, value)) {
const replacements = map[value]
settings.warning(
'Deprecated ' +
key +
' `' +
value +
'`, expected one of `' +
replacements.join('`, `') +
'`',
-1,
7
)
}
}
}
}
Expand Down Expand Up @@ -193,63 +198,63 @@ export function bcp47Normalize(value, options) {
function replace(schema, from, to) {
const left = parse(from)
const right = parse(to)
/** @type {Array.<string>} */
/** @type {Array<string>} */
const removed = []
/** @type {string} */
/** @type {string|null|undefined} */
const lang = left.language
/** @type {string} */
/** @type {keyof schema} */
let key

// Remove values from `from`:
for (key in left) {
if (left[key] && remove(schema, key, left[key])) {
removed.push(key)
if (own.call(left, key)) {
const value = left[key]
if (value && remove(schema, key, value)) {
removed.push(key)
}
}
}

// Add values from `to`:
for (key in right) {
// Only add values that are defined on `to`, and that were either removed by
// `from` or are currently empty.
if (right[key] && (removed.includes(key) || !schema[key])) {
add(
schema,
key,
key === 'language' && right[key] === 'und' ? lang : right[key]
)
if (own.call(right, key)) {
const value = right[key]
// Only add values that are defined on `to`, and that were either removed by
// `from` or are currently empty.
if (lang && value && (removed.includes(key) || !schema[key])) {
add(schema, key, key === 'language' && value === 'und' ? lang : value)
}
}
}
}

/**
* @param {Schema} object
* @param {string} key
* @param {string|Array.<string>} value
* @param {keyof Schema} key
* @param {string|Array<string>|Array<Extension>} value
* @returns {boolean}
*/
function remove(object, key, value) {
let removed = false
/** @type {string|Array.<string>} */
/** @type {string|Array<string>|Array<Extension>|null|undefined} */
let result

/* istanbul ignore else - this is currently done by wrapping code, so else is
* never reached.
* However, that could change in the future, so leave this guard here. */
if (value) {
/** @type {string|Array.<string>} */
const current = object[key]
result = current

if (current && typeof current === 'object') {
if (Array.isArray(current)) {
result = []
let index = -1

while (++index < current.length) {
const item = current[index]

// @ts-expect-error: TS can’t handle the two lists.
if (value.includes(item)) {
removed = true
} else {
// @ts-expect-error: TS can’t handle the two lists.
result.push(item)
}
}
Expand All @@ -258,6 +263,7 @@ function remove(object, key, value) {
removed = true
}

// @ts-expect-error: Assume the value matches.
object[key] = result
}

Expand All @@ -266,12 +272,12 @@ function remove(object, key, value) {

/**
* @param {Schema} object
* @param {string} key
* @param {string|Array.<string>} value
* @param {keyof Schema} key
* @param {string|Array<string>|Array<Extension>} value
* @returns {void}
*/
function add(object, key, value) {
/** @type {string|Array.<string>} */
/** @type {string|Array<string>|Array<Extension>|null|undefined} */
const current = object[key]

if (Array.isArray(current)) {
Expand All @@ -282,13 +288,14 @@ function add(object, key, value) {
while (++index < list.length) {
const item = list[index]

/* istanbul ignore else - this currently can’t happen, but guard for the
* future. */
// @ts-expect-error: TS can’t handle the two lists.
if (!current.includes(item)) {
// @ts-expect-error: TS can’t handle the two lists.
current.push(item)
}
}
} else {
// @ts-expect-error: Assume the value matches.
object[key] = value
}
}
Expand Down
3 changes: 3 additions & 0 deletions lib/likely.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* @type {Record<string, string>}
*/
export const likely = {
aa: 'aa-latn-et',
aai: 'aai-latn-zz',
Expand Down
7 changes: 7 additions & 0 deletions lib/many.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
/**
* @typedef {'script'|'region'|'variants'} Field
*/

/**
* @type {{region: Record<string, Array<string>>}}
*/
export const many = {
region: {
172: [
Expand Down
Loading

0 comments on commit 9a600f5

Please sign in to comment.