summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2023-12-20 00:47:15 -0500
committerGitHub <noreply@github.com>2023-12-20 05:47:15 +0000
commit8b943cc97fab890085448122e7c13dd035d0e238 (patch)
treea7a749a44771c6a82b1b72bb35cc0c81d57ddb54
parentb13fbd47941fc20cf623871396e34a6dfe9b4dba (diff)
JSON validation (#394)
* Set up JSON testing * Add schema validation * Use parseJson * Finish types * Disambiguate ext/json-schema from node dependency with the same name * Add support for specifying the jsconfig file * Don't expose types * Update types * Use dictionary map type * Fix types * Fix AJV warnings * Move types * Move anb rename file * Move common mocks * Simplify types
-rw-r--r--dev/data/manifest-variants.json1
-rw-r--r--dev/jsconfig.json2
-rw-r--r--dev/manifest-util.js2
-rw-r--r--dev/schema-validate.js2
-rw-r--r--dev/util.js8
-rw-r--r--ext/js/background/profile-conditions-util.js38
-rw-r--r--ext/js/data/json-schema.js198
-rw-r--r--ext/js/data/options-util.js2
-rw-r--r--ext/js/display/sandbox/structured-content-generator.js2
-rw-r--r--ext/js/media/audio-downloader.js2
-rw-r--r--jsconfig.json3
-rw-r--r--package-lock.json158
-rw-r--r--package.json1
-rw-r--r--test/data/json.json174
-rw-r--r--test/data/translator-test-inputs.json12
-rw-r--r--test/fixtures/translator-test.js36
-rw-r--r--test/jsconfig.json4
-rw-r--r--test/json-schema.test.js22
-rw-r--r--test/json.test.js189
-rw-r--r--test/mocks/common.js52
-rw-r--r--test/options-util.test.js29
-rw-r--r--test/profile-conditions-util.test.js2
-rw-r--r--test/utilities/translator.js5
-rw-r--r--types/dev/manifest.d.ts2
-rw-r--r--types/ext/dictionary-data.d.ts20
-rw-r--r--types/ext/structured-content.d.ts8
-rw-r--r--types/ext/translation.d.ts2
-rw-r--r--types/test/json.d.ts42
-rw-r--r--types/test/mocks.d.ts (renamed from types/dev/vm.d.ts)14
-rw-r--r--types/test/translator.d.ts45
30 files changed, 842 insertions, 235 deletions
diff --git a/dev/data/manifest-variants.json b/dev/data/manifest-variants.json
index ce82d0e3..119b47c2 100644
--- a/dev/data/manifest-variants.json
+++ b/dev/data/manifest-variants.json
@@ -311,7 +311,6 @@
},
{
"name": "safari",
- "fileName": null,
"modifications": [
{
"action": "remove",
diff --git a/dev/jsconfig.json b/dev/jsconfig.json
index a52153a8..d613e88d 100644
--- a/dev/jsconfig.json
+++ b/dev/jsconfig.json
@@ -29,7 +29,7 @@
"error": ["../types/ext/error"],
"event-listener-collection": ["../types/ext/event-listener-collection"],
"japanese-util": ["../types/ext/japanese-util"],
- "json-schema": ["../types/ext/json-schema"],
+ "ext/json-schema": ["../types/ext/json-schema"],
"log": ["../types/ext/log"],
"settings": ["../types/ext/settings"],
"structured-content": ["../types/ext/structured-content"],
diff --git a/dev/manifest-util.js b/dev/manifest-util.js
index 6a53c8d6..e4487035 100644
--- a/dev/manifest-util.js
+++ b/dev/manifest-util.js
@@ -121,7 +121,7 @@ export class ManifestUtil {
/**
* @param {import('dev/manifest').Manifest} manifest
- * @param {import('dev/manifest').Modification[]} modifications
+ * @param {import('dev/manifest').Modification[]|undefined} modifications
* @returns {import('dev/manifest').Manifest}
*/
_applyModifications(manifest, modifications) {
diff --git a/dev/schema-validate.js b/dev/schema-validate.js
index 57faf96c..d4e977e1 100644
--- a/dev/schema-validate.js
+++ b/dev/schema-validate.js
@@ -62,6 +62,6 @@ class JsonSchemaAjv {
export function createJsonSchema(mode, schema) {
switch (mode) {
case 'ajv': return new JsonSchemaAjv(schema);
- default: return new JsonSchema(/** @type {import('json-schema').Schema} */ (schema));
+ default: return new JsonSchema(/** @type {import('ext/json-schema').Schema} */ (schema));
}
}
diff --git a/dev/util.js b/dev/util.js
index 6a7fa8f5..542ad6a2 100644
--- a/dev/util.js
+++ b/dev/util.js
@@ -72,7 +72,7 @@ export function getArgs(args, argMap) {
/**
* @param {string} baseDirectory
- * @param {?(fileName: string) => boolean} predicate
+ * @param {?(fileName: string, isDirectory: boolean) => boolean} predicate
* @returns {string[]}
*/
export function getAllFiles(baseDirectory, predicate = null) {
@@ -86,11 +86,13 @@ export function getAllFiles(baseDirectory, predicate = null) {
const relativeFileName = path.relative(baseDirectory, fullFileName);
const stats = fs.lstatSync(fullFileName);
if (stats.isFile()) {
- if (typeof predicate !== 'function' || predicate(relativeFileName)) {
+ if (typeof predicate !== 'function' || predicate(relativeFileName, false)) {
results.push(relativeFileName);
}
} else if (stats.isDirectory()) {
- directories.push(fullFileName);
+ if (typeof predicate !== 'function' || predicate(relativeFileName, true)) {
+ directories.push(fullFileName);
+ }
}
}
}
diff --git a/ext/js/background/profile-conditions-util.js b/ext/js/background/profile-conditions-util.js
index b1bbe1d5..8d477094 100644
--- a/ext/js/background/profile-conditions-util.js
+++ b/ext/js/background/profile-conditions-util.js
@@ -158,7 +158,7 @@ export class ProfileConditionsUtil {
/**
* @param {string} value
- * @returns {import('json-schema').Schema}
+ * @returns {import('ext/json-schema').Schema}
*/
_createSchemaPopupLevelEqual(value) {
const number = this._stringToNumber(value);
@@ -172,7 +172,7 @@ export class ProfileConditionsUtil {
/**
* @param {string} value
- * @returns {import('json-schema').Schema}
+ * @returns {import('ext/json-schema').Schema}
*/
_createSchemaPopupLevelNotEqual(value) {
return {
@@ -184,7 +184,7 @@ export class ProfileConditionsUtil {
/**
* @param {string} value
- * @returns {import('json-schema').Schema}
+ * @returns {import('ext/json-schema').Schema}
*/
_createSchemaPopupLevelLessThan(value) {
const number = this._stringToNumber(value);
@@ -198,7 +198,7 @@ export class ProfileConditionsUtil {
/**
* @param {string} value
- * @returns {import('json-schema').Schema}
+ * @returns {import('ext/json-schema').Schema}
*/
_createSchemaPopupLevelGreaterThan(value) {
const number = this._stringToNumber(value);
@@ -212,7 +212,7 @@ export class ProfileConditionsUtil {
/**
* @param {string} value
- * @returns {import('json-schema').Schema}
+ * @returns {import('ext/json-schema').Schema}
*/
_createSchemaPopupLevelLessThanOrEqual(value) {
const number = this._stringToNumber(value);
@@ -226,7 +226,7 @@ export class ProfileConditionsUtil {
/**
* @param {string} value
- * @returns {import('json-schema').Schema}
+ * @returns {import('ext/json-schema').Schema}
*/
_createSchemaPopupLevelGreaterThanOrEqual(value) {
const number = this._stringToNumber(value);
@@ -242,7 +242,7 @@ export class ProfileConditionsUtil {
/**
* @param {string} value
- * @returns {import('json-schema').Schema}
+ * @returns {import('ext/json-schema').Schema}
*/
_createSchemaUrlMatchDomain(value) {
const oneOf = [];
@@ -261,7 +261,7 @@ export class ProfileConditionsUtil {
/**
* @param {string} value
- * @returns {import('json-schema').Schema}
+ * @returns {import('ext/json-schema').Schema}
*/
_createSchemaUrlMatchRegExp(value) {
return {
@@ -276,7 +276,7 @@ export class ProfileConditionsUtil {
/**
* @param {string} value
- * @returns {import('json-schema').Schema}
+ * @returns {import('ext/json-schema').Schema}
*/
_createSchemaModifierKeysAre(value) {
return this._createSchemaArrayCheck('modifierKeys', value, true, false);
@@ -284,7 +284,7 @@ export class ProfileConditionsUtil {
/**
* @param {string} value
- * @returns {import('json-schema').Schema}
+ * @returns {import('ext/json-schema').Schema}
*/
_createSchemaModifierKeysAreNot(value) {
return {
@@ -296,7 +296,7 @@ export class ProfileConditionsUtil {
/**
* @param {string} value
- * @returns {import('json-schema').Schema}
+ * @returns {import('ext/json-schema').Schema}
*/
_createSchemaModifierKeysInclude(value) {
return this._createSchemaArrayCheck('modifierKeys', value, false, false);
@@ -304,7 +304,7 @@ export class ProfileConditionsUtil {
/**
* @param {string} value
- * @returns {import('json-schema').Schema}
+ * @returns {import('ext/json-schema').Schema}
*/
_createSchemaModifierKeysNotInclude(value) {
return this._createSchemaArrayCheck('modifierKeys', value, false, true);
@@ -314,7 +314,7 @@ export class ProfileConditionsUtil {
/**
* @param {string} value
- * @returns {import('json-schema').Schema}
+ * @returns {import('ext/json-schema').Schema}
*/
_createSchemaFlagsAre(value) {
return this._createSchemaArrayCheck('flags', value, true, false);
@@ -322,7 +322,7 @@ export class ProfileConditionsUtil {
/**
* @param {string} value
- * @returns {import('json-schema').Schema}
+ * @returns {import('ext/json-schema').Schema}
*/
_createSchemaFlagsAreNot(value) {
return {
@@ -334,7 +334,7 @@ export class ProfileConditionsUtil {
/**
* @param {string} value
- * @returns {import('json-schema').Schema}
+ * @returns {import('ext/json-schema').Schema}
*/
_createSchemaFlagsInclude(value) {
return this._createSchemaArrayCheck('flags', value, false, false);
@@ -342,7 +342,7 @@ export class ProfileConditionsUtil {
/**
* @param {string} value
- * @returns {import('json-schema').Schema}
+ * @returns {import('ext/json-schema').Schema}
*/
_createSchemaFlagsNotInclude(value) {
return this._createSchemaArrayCheck('flags', value, false, true);
@@ -355,10 +355,10 @@ export class ProfileConditionsUtil {
* @param {string} value
* @param {boolean} exact
* @param {boolean} none
- * @returns {import('json-schema').Schema}
+ * @returns {import('ext/json-schema').Schema}
*/
_createSchemaArrayCheck(key, value, exact, none) {
- /** @type {import('json-schema').Schema[]} */
+ /** @type {import('ext/json-schema').Schema[]} */
const containsList = [];
for (const item of this._split(value)) {
if (item.length === 0) { continue; }
@@ -369,7 +369,7 @@ export class ProfileConditionsUtil {
});
}
const containsListCount = containsList.length;
- /** @type {import('json-schema').Schema} */
+ /** @type {import('ext/json-schema').Schema} */
const schema = {
type: 'array'
};
diff --git a/ext/js/data/json-schema.js b/ext/js/data/json-schema.js
index 52a55d85..08414164 100644
--- a/ext/js/data/json-schema.js
+++ b/ext/js/data/json-schema.js
@@ -22,47 +22,47 @@ import {CacheMap} from '../general/cache-map.js';
export class JsonSchemaError extends Error {
/**
* @param {string} message
- * @param {import('json-schema').ValueStackItem[]} valueStack
- * @param {import('json-schema').SchemaStackItem[]} schemaStack
+ * @param {import('ext/json-schema').ValueStackItem[]} valueStack
+ * @param {import('ext/json-schema').SchemaStackItem[]} schemaStack
*/
constructor(message, valueStack, schemaStack) {
super(message);
- /** @type {import('json-schema').ValueStackItem[]} */
+ /** @type {import('ext/json-schema').ValueStackItem[]} */
this._valueStack = valueStack;
- /** @type {import('json-schema').SchemaStackItem[]} */
+ /** @type {import('ext/json-schema').SchemaStackItem[]} */
this._schemaStack = schemaStack;
}
/** @type {unknown|undefined} */
get value() { return this._valueStack.length > 0 ? this._valueStack[this._valueStack.length - 1].value : void 0; }
- /** @type {import('json-schema').Schema|import('json-schema').Schema[]|undefined} */
+ /** @type {import('ext/json-schema').Schema|import('ext/json-schema').Schema[]|undefined} */
get schema() { return this._schemaStack.length > 0 ? this._schemaStack[this._schemaStack.length - 1].schema : void 0; }
- /** @type {import('json-schema').ValueStackItem[]} */
+ /** @type {import('ext/json-schema').ValueStackItem[]} */
get valueStack() { return this._valueStack; }
- /** @type {import('json-schema').SchemaStackItem[]} */
+ /** @type {import('ext/json-schema').SchemaStackItem[]} */
get schemaStack() { return this._schemaStack; }
}
export class JsonSchema {
/**
- * @param {import('json-schema').Schema} schema
- * @param {import('json-schema').Schema} [rootSchema]
+ * @param {import('ext/json-schema').Schema} schema
+ * @param {import('ext/json-schema').Schema} [rootSchema]
*/
constructor(schema, rootSchema) {
- /** @type {import('json-schema').Schema} */
+ /** @type {import('ext/json-schema').Schema} */
this._startSchema = schema;
- /** @type {import('json-schema').Schema} */
+ /** @type {import('ext/json-schema').Schema} */
this._rootSchema = typeof rootSchema !== 'undefined' ? rootSchema : schema;
/** @type {?CacheMap<string, RegExp>} */
this._regexCache = null;
- /** @type {?Map<string, {schema: import('json-schema').Schema, stack: import('json-schema').SchemaStackItem[]}>} */
+ /** @type {?Map<string, {schema: import('ext/json-schema').Schema, stack: import('ext/json-schema').SchemaStackItem[]}>} */
this._refCache = null;
- /** @type {import('json-schema').ValueStackItem[]} */
+ /** @type {import('ext/json-schema').ValueStackItem[]} */
this._valueStack = [];
- /** @type {import('json-schema').SchemaStackItem[]} */
+ /** @type {import('ext/json-schema').SchemaStackItem[]} */
this._schemaStack = [];
/** @type {?(jsonSchema: JsonSchema) => void} */
this._progress = null;
@@ -72,12 +72,12 @@ export class JsonSchema {
this._progressInterval = 1;
}
- /** @type {import('json-schema').Schema} */
+ /** @type {import('ext/json-schema').Schema} */
get schema() {
return this._startSchema;
}
- /** @type {import('json-schema').Schema} */
+ /** @type {import('ext/json-schema').Schema} */
get rootSchema() {
return this._rootSchema;
}
@@ -101,8 +101,8 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').Value} value
- * @returns {import('json-schema').Value}
+ * @param {import('ext/json-schema').Value} value
+ * @returns {import('ext/json-schema').Value}
*/
createProxy(value) {
return (
@@ -142,7 +142,7 @@ export class JsonSchema {
/**
* @param {unknown} [value]
- * @returns {import('json-schema').Value}
+ * @returns {import('ext/json-schema').Value}
*/
getValidValueOrDefault(value) {
const schema = this._startSchema;
@@ -195,7 +195,7 @@ export class JsonSchema {
// Internal state functions for error construction and progress callback
/**
- * @returns {import('json-schema').ValueStackItem[]}
+ * @returns {import('ext/json-schema').ValueStackItem[]}
*/
getValueStack() {
const result = [];
@@ -206,7 +206,7 @@ export class JsonSchema {
}
/**
- * @returns {import('json-schema').SchemaStackItem[]}
+ * @returns {import('ext/json-schema').SchemaStackItem[]}
*/
getSchemaStack() {
const result = [];
@@ -225,7 +225,7 @@ export class JsonSchema {
/**
* @param {number} index
- * @returns {import('json-schema').ValueStackItem}
+ * @returns {import('ext/json-schema').ValueStackItem}
*/
getValueStackItem(index) {
const {value, path} = this._valueStack[index + 1];
@@ -241,7 +241,7 @@ export class JsonSchema {
/**
* @param {number} index
- * @returns {import('json-schema').SchemaStackItem}
+ * @returns {import('ext/json-schema').SchemaStackItem}
*/
getSchemaStackItem(index) {
const {schema, path} = this._schemaStack[index + 1];
@@ -275,7 +275,7 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').Schema|import('json-schema').Schema[]} schema
+ * @param {import('ext/json-schema').Schema|import('ext/json-schema').Schema[]} schema
* @param {string|number|null} path
*/
_schemaPush(schema, path) {
@@ -283,7 +283,7 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').SchemaStackItem[]} items
+ * @param {import('ext/json-schema').SchemaStackItem[]} items
*/
_schemaPushMultiple(items) {
this._schemaStack.push(...items);
@@ -337,9 +337,9 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').Schema} schema
+ * @param {import('ext/json-schema').Schema} schema
* @param {string} property
- * @returns {{schema: import('json-schema').Schema, stack: import('json-schema').SchemaStackItem[]}}
+ * @returns {{schema: import('ext/json-schema').Schema, stack: import('ext/json-schema').SchemaStackItem[]}}
*/
_getObjectPropertySchemaInfo(schema, property) {
if (typeof schema === 'boolean') {
@@ -362,9 +362,9 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').Schema} schema
+ * @param {import('ext/json-schema').Schema} schema
* @param {number} index
- * @returns {{schema: import('json-schema').Schema, stack: import('json-schema').SchemaStackItem[]}}
+ * @returns {{schema: import('ext/json-schema').Schema, stack: import('ext/json-schema').SchemaStackItem[]}}
*/
_getArrayItemSchemaInfo(schema, index) {
if (typeof schema === 'boolean') {
@@ -411,9 +411,9 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').Schema|undefined} schema
+ * @param {import('ext/json-schema').Schema|undefined} schema
* @param {string|number|null} path
- * @returns {{schema: import('json-schema').Schema, stack: import('json-schema').SchemaStackItem[]}}
+ * @returns {{schema: import('ext/json-schema').Schema, stack: import('ext/json-schema').SchemaStackItem[]}}
*/
_getOptionalSchemaInfo(schema, path) {
switch (typeof schema) {
@@ -430,7 +430,7 @@ export class JsonSchema {
/**
* @param {unknown} value
- * @returns {?import('json-schema').Type}
+ * @returns {?import('ext/json-schema').Type}
* @throws {Error}
*/
_getValueType(value) {
@@ -451,8 +451,8 @@ export class JsonSchema {
/**
* @param {unknown} value
- * @param {?import('json-schema').Type} type
- * @param {import('json-schema').Type|import('json-schema').Type[]|undefined} schemaTypes
+ * @param {?import('ext/json-schema').Type} type
+ * @param {import('ext/json-schema').Type|import('ext/json-schema').Type[]|undefined} schemaTypes
* @returns {boolean}
*/
_isValueTypeAny(value, type, schemaTypes) {
@@ -471,8 +471,8 @@ export class JsonSchema {
/**
* @param {unknown} value
- * @param {?import('json-schema').Type} type
- * @param {import('json-schema').Type} schemaType
+ * @param {?import('ext/json-schema').Type} type
+ * @param {import('ext/json-schema').Type} schemaType
* @returns {boolean}
*/
_isValueType(value, type, schemaType) {
@@ -484,7 +484,7 @@ export class JsonSchema {
/**
* @param {unknown} value1
- * @param {import('json-schema').Value[]} valueList
+ * @param {import('ext/json-schema').Value[]} valueList
* @returns {boolean}
*/
_valuesAreEqualAny(value1, valueList) {
@@ -498,7 +498,7 @@ export class JsonSchema {
/**
* @param {unknown} value1
- * @param {import('json-schema').Value} value2
+ * @param {import('ext/json-schema').Value} value2
* @returns {boolean}
*/
_valuesAreEqual(value1, value2) {
@@ -506,9 +506,9 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').Schema} schema
- * @param {import('json-schema').SchemaStackItem[]} stack
- * @returns {{schema: import('json-schema').Schema, stack: import('json-schema').SchemaStackItem[]}}
+ * @param {import('ext/json-schema').Schema} schema
+ * @param {import('ext/json-schema').SchemaStackItem[]} stack
+ * @returns {{schema: import('ext/json-schema').Schema, stack: import('ext/json-schema').SchemaStackItem[]}}
*/
_getResolvedSchemaInfo(schema, stack) {
if (typeof schema !== 'boolean') {
@@ -526,7 +526,7 @@ export class JsonSchema {
/**
* @param {string} ref
- * @returns {{schema: import('json-schema').Schema, stack: import('json-schema').SchemaStackItem[]}}
+ * @returns {{schema: import('ext/json-schema').Schema, stack: import('ext/json-schema').SchemaStackItem[]}}
* @throws {Error}
*/
_getReference(ref) {
@@ -534,7 +534,7 @@ export class JsonSchema {
throw this._createError(`Unsupported reference path: ${ref}`);
}
- /** @type {{schema: import('json-schema').Schema, stack: import('json-schema').SchemaStackItem[]}|undefined} */
+ /** @type {{schema: import('ext/json-schema').Schema, stack: import('ext/json-schema').SchemaStackItem[]}|undefined} */
let info;
if (this._refCache !== null) {
info = this._refCache.get(ref);
@@ -553,13 +553,13 @@ export class JsonSchema {
/**
* @param {string} ref
- * @returns {{schema: import('json-schema').Schema, stack: import('json-schema').SchemaStackItem[]}}
+ * @returns {{schema: import('ext/json-schema').Schema, stack: import('ext/json-schema').SchemaStackItem[]}}
* @throws {Error}
*/
_getReferenceUncached(ref) {
/** @type {Set<string>} */
const visited = new Set();
- /** @type {import('json-schema').SchemaStackItem[]} */
+ /** @type {import('ext/json-schema').SchemaStackItem[]} */
const stack = [];
while (true) {
if (visited.has(ref)) {
@@ -594,11 +594,11 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').SchemaStackItem[]} schemaStack
- * @returns {import('json-schema').SchemaStackItem[]}
+ * @param {import('ext/json-schema').SchemaStackItem[]} schemaStack
+ * @returns {import('ext/json-schema').SchemaStackItem[]}
*/
_copySchemaStack(schemaStack) {
- /** @type {import('json-schema').SchemaStackItem[]} */
+ /** @type {import('ext/json-schema').SchemaStackItem[]} */
const results = [];
for (const {schema, path} of schemaStack) {
results.push({schema, path});
@@ -609,7 +609,7 @@ export class JsonSchema {
// Validation
/**
- * @param {import('json-schema').SchemaObject} schema
+ * @param {import('ext/json-schema').SchemaObject} schema
* @param {unknown} value
* @returns {boolean}
*/
@@ -623,7 +623,7 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').Schema} schema
+ * @param {import('ext/json-schema').Schema} schema
* @param {unknown} value
*/
_validate(schema, value) {
@@ -643,7 +643,7 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').Schema} schema
+ * @param {import('ext/json-schema').Schema} schema
* @param {unknown} value
* @throws {Error}
*/
@@ -659,7 +659,7 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').SchemaObject} schema
+ * @param {import('ext/json-schema').SchemaObject} schema
* @param {unknown} value
*/
_validateConditional(schema, value) {
@@ -688,7 +688,7 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').SchemaObject} schema
+ * @param {import('ext/json-schema').SchemaObject} schema
* @param {unknown} value
*/
_validateAllOf(schema, value) {
@@ -712,7 +712,7 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').SchemaObject} schema
+ * @param {import('ext/json-schema').SchemaObject} schema
* @param {unknown} value
*/
_validateAnyOf(schema, value) {
@@ -741,7 +741,7 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').SchemaObject} schema
+ * @param {import('ext/json-schema').SchemaObject} schema
* @param {unknown} value
*/
_validateOneOf(schema, value) {
@@ -773,7 +773,7 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').SchemaObject} schema
+ * @param {import('ext/json-schema').SchemaObject} schema
* @param {unknown} value
* @throws {Error}
*/
@@ -797,7 +797,7 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').SchemaObject} schema
+ * @param {import('ext/json-schema').SchemaObject} schema
* @param {unknown} value
* @throws {Error}
*/
@@ -824,16 +824,16 @@ export class JsonSchema {
this._validateString(schema, /** @type {string} */ (value));
break;
case 'array':
- this._validateArray(schema, /** @type {import('json-schema').Value[]} */ (value));
+ this._validateArray(schema, /** @type {import('ext/json-schema').Value[]} */ (value));
break;
case 'object':
- this._validateObject(schema, /** @type {import('json-schema').ValueObject} */ (value));
+ this._validateObject(schema, /** @type {import('ext/json-schema').ValueObject} */ (value));
break;
}
}
/**
- * @param {import('json-schema').SchemaObject} schema
+ * @param {import('ext/json-schema').SchemaObject} schema
* @param {number} value
* @throws {Error}
*/
@@ -861,7 +861,7 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').SchemaObject} schema
+ * @param {import('ext/json-schema').SchemaObject} schema
* @param {string} value
* @throws {Error}
*/
@@ -893,7 +893,7 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').SchemaObject} schema
+ * @param {import('ext/json-schema').SchemaObject} schema
* @param {unknown[]} value
* @throws {Error}
*/
@@ -931,7 +931,7 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').SchemaObject} schema
+ * @param {import('ext/json-schema').SchemaObject} schema
* @param {unknown[]} value
* @throws {Error}
*/
@@ -960,8 +960,8 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').SchemaObject} schema
- * @param {import('json-schema').ValueObject} value
+ * @param {import('ext/json-schema').SchemaObject} schema
+ * @param {import('ext/json-schema').ValueObject} value
* @throws {Error}
*/
_validateObject(schema, value) {
@@ -1008,8 +1008,8 @@ export class JsonSchema {
// Creation
/**
- * @param {import('json-schema').Type|import('json-schema').Type[]|undefined} type
- * @returns {import('json-schema').Value}
+ * @param {import('ext/json-schema').Type|import('ext/json-schema').Type[]|undefined} type
+ * @returns {import('ext/json-schema').Value}
*/
_getDefaultTypeValue(type) {
if (Array.isArray(type)) { type = type[0]; }
@@ -1034,8 +1034,8 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').SchemaObject} schema
- * @returns {import('json-schema').Value}
+ * @param {import('ext/json-schema').SchemaObject} schema
+ * @returns {import('ext/json-schema').Value}
*/
_getDefaultSchemaValue(schema) {
const {type: schemaType, default: schemaDefault} = schema;
@@ -1048,11 +1048,11 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').Schema} schema
+ * @param {import('ext/json-schema').Schema} schema
* @param {string|number|null} path
* @param {unknown} value
- * @param {import('json-schema').SchemaStackItem[]} stack
- * @returns {import('json-schema').Value}
+ * @param {import('ext/json-schema').SchemaStackItem[]} stack
+ * @returns {import('ext/json-schema').Value}
*/
_getValidValueOrDefault(schema, path, value, stack) {
({schema, stack} = this._getResolvedSchemaInfo(schema, stack));
@@ -1067,14 +1067,14 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').Schema} schema
+ * @param {import('ext/json-schema').Schema} schema
* @param {unknown} value
- * @returns {import('json-schema').Value}
+ * @returns {import('ext/json-schema').Value}
*/
_getValidValueOrDefaultInner(schema, value) {
let type = this._getValueType(value);
if (typeof schema === 'boolean') {
- return type !== null ? /** @type {import('json-schema').ValueObject} */ (value) : null;
+ return type !== null ? /** @type {import('ext/json-schema').ValueObject} */ (value) : null;
}
if (typeof value === 'undefined' || !this._isValueTypeAny(value, type, schema.type)) {
value = this._getDefaultSchemaValue(schema);
@@ -1083,9 +1083,9 @@ export class JsonSchema {
switch (type) {
case 'object':
- return this._populateObjectDefaults(schema, /** @type {import('json-schema').ValueObject} */ (value));
+ return this._populateObjectDefaults(schema, /** @type {import('ext/json-schema').ValueObject} */ (value));
case 'array':
- return this._populateArrayDefaults(schema, /** @type {import('json-schema').Value[]} */ (value));
+ return this._populateArrayDefaults(schema, /** @type {import('ext/json-schema').Value[]} */ (value));
default:
if (!this._isValidCurrent(schema, value)) {
const schemaDefault = this._getDefaultSchemaValue(schema);
@@ -1096,13 +1096,13 @@ export class JsonSchema {
break;
}
- return /** @type {import('json-schema').ValueObject} */ (value);
+ return /** @type {import('ext/json-schema').ValueObject} */ (value);
}
/**
- * @param {import('json-schema').SchemaObject} schema
- * @param {import('json-schema').ValueObject} value
- * @returns {import('json-schema').ValueObject}
+ * @param {import('ext/json-schema').SchemaObject} schema
+ * @param {import('ext/json-schema').ValueObject} value
+ * @returns {import('ext/json-schema').ValueObject}
*/
_populateObjectDefaults(schema, value) {
const properties = new Set(Object.getOwnPropertyNames(value));
@@ -1131,9 +1131,9 @@ export class JsonSchema {
}
/**
- * @param {import('json-schema').SchemaObject} schema
- * @param {import('json-schema').Value[]} value
- * @returns {import('json-schema').Value[]}
+ * @param {import('ext/json-schema').SchemaObject} schema
+ * @param {import('ext/json-schema').Value[]} value
+ * @returns {import('ext/json-schema').Value[]}
*/
_populateArrayDefaults(schema, value) {
for (let i = 0, ii = value.length; i < ii; ++i) {
@@ -1162,7 +1162,7 @@ export class JsonSchema {
}
/**
- * @implements {ProxyHandler<import('json-schema').ValueObjectOrArray>}
+ * @implements {ProxyHandler<import('ext/json-schema').ValueObjectOrArray>}
*/
class JsonSchemaProxyHandler {
/**
@@ -1176,7 +1176,7 @@ class JsonSchemaProxyHandler {
}
/**
- * @param {import('json-schema').ValueObjectOrArray} target
+ * @param {import('ext/json-schema').ValueObjectOrArray} target
* @returns {?import('core').UnknownObject}
*/
getPrototypeOf(target) {
@@ -1184,14 +1184,14 @@ class JsonSchemaProxyHandler {
}
/**
- * @type {(target: import('json-schema').ValueObjectOrArray, newPrototype: ?unknown) => boolean}
+ * @type {(target: import('ext/json-schema').ValueObjectOrArray, newPrototype: ?unknown) => boolean}
*/
setPrototypeOf() {
throw new Error('setPrototypeOf not supported');
}
/**
- * @param {import('json-schema').ValueObjectOrArray} target
+ * @param {import('ext/json-schema').ValueObjectOrArray} target
* @returns {boolean}
*/
isExtensible(target) {
@@ -1199,7 +1199,7 @@ class JsonSchemaProxyHandler {
}
/**
- * @param {import('json-schema').ValueObjectOrArray} target
+ * @param {import('ext/json-schema').ValueObjectOrArray} target
* @returns {boolean}
*/
preventExtensions(target) {
@@ -1208,7 +1208,7 @@ class JsonSchemaProxyHandler {
}
/**
- * @param {import('json-schema').ValueObjectOrArray} target
+ * @param {import('ext/json-schema').ValueObjectOrArray} target
* @param {string|symbol} property
* @returns {PropertyDescriptor|undefined}
*/
@@ -1217,14 +1217,14 @@ class JsonSchemaProxyHandler {
}
/**
- * @type {(target: import('json-schema').ValueObjectOrArray, property: string|symbol, attributes: PropertyDescriptor) => boolean}
+ * @type {(target: import('ext/json-schema').ValueObjectOrArray, property: string|symbol, attributes: PropertyDescriptor) => boolean}
*/
defineProperty() {
throw new Error('defineProperty not supported');
}
/**
- * @param {import('json-schema').ValueObjectOrArray} target
+ * @param {import('ext/json-schema').ValueObjectOrArray} target
* @param {string|symbol} property
* @returns {boolean}
*/
@@ -1233,7 +1233,7 @@ class JsonSchemaProxyHandler {
}
/**
- * @param {import('json-schema').ValueObjectOrArray} target
+ * @param {import('ext/json-schema').ValueObjectOrArray} target
* @param {string|symbol} property
* @param {import('core').SafeAny} _receiver
* @returns {import('core').SafeAny}
@@ -1257,11 +1257,11 @@ class JsonSchemaProxyHandler {
if (propertySchema === null) { return void 0; }
const value = /** @type {import('core').UnknownObject} */ (target)[property];
- return value !== null && typeof value === 'object' ? propertySchema.createProxy(/** @type {import('json-schema').Value} */ (value)) : value;
+ return value !== null && typeof value === 'object' ? propertySchema.createProxy(/** @type {import('ext/json-schema').Value} */ (value)) : value;
}
/**
- * @param {import('json-schema').ValueObjectOrArray} target
+ * @param {import('ext/json-schema').ValueObjectOrArray} target
* @param {string|number|symbol} property
* @param {import('core').SafeAny} value
* @returns {boolean}
@@ -1297,7 +1297,7 @@ class JsonSchemaProxyHandler {
}
/**
- * @param {import('json-schema').ValueObjectOrArray} target
+ * @param {import('ext/json-schema').ValueObjectOrArray} target
* @param {string|symbol} property
* @returns {boolean}
* @throws {Error}
@@ -1315,7 +1315,7 @@ class JsonSchemaProxyHandler {
}
/**
- * @param {import('json-schema').ValueObjectOrArray} target
+ * @param {import('ext/json-schema').ValueObjectOrArray} target
* @returns {ArrayLike<string|symbol>}
*/
ownKeys(target) {
@@ -1323,14 +1323,14 @@ class JsonSchemaProxyHandler {
}
/**
- * @type {(target: import('json-schema').ValueObjectOrArray, thisArg: import('core').SafeAny, argArray: import('core').SafeAny[]) => import('core').SafeAny}
+ * @type {(target: import('ext/json-schema').ValueObjectOrArray, thisArg: import('core').SafeAny, argArray: import('core').SafeAny[]) => import('core').SafeAny}
*/
apply() {
throw new Error('apply not supported');
}
/**
- * @type {(target: import('json-schema').ValueObjectOrArray, argArray: import('core').SafeAny[], newTarget: import('core').SafeFunction) => import('json-schema').ValueObjectOrArray}
+ * @type {(target: import('ext/json-schema').ValueObjectOrArray, argArray: import('core').SafeAny[], newTarget: import('core').SafeFunction) => import('ext/json-schema').ValueObjectOrArray}
*/
construct() {
throw new Error('construct not supported');
diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js
index a17763e9..c219bed3 100644
--- a/ext/js/data/options-util.js
+++ b/ext/js/data/options-util.js
@@ -31,7 +31,7 @@ export class OptionsUtil {
/** */
async prepare() {
- /** @type {import('json-schema').Schema} */
+ /** @type {import('ext/json-schema').Schema} */
const schema = await this._fetchJson('/data/schemas/options-schema.json');
this._optionsSchema = new JsonSchema(schema);
}
diff --git a/ext/js/display/sandbox/structured-content-generator.js b/ext/js/display/sandbox/structured-content-generator.js
index 5a91b01c..a28464ae 100644
--- a/ext/js/display/sandbox/structured-content-generator.js
+++ b/ext/js/display/sandbox/structured-content-generator.js
@@ -53,7 +53,7 @@ export class StructuredContentGenerator {
}
/**
- * @param {import('structured-content').ImageElementBase} data
+ * @param {import('structured-content').ImageElement|import('dictionary-data').TermGlossaryImage} data
* @param {string} dictionary
* @returns {HTMLAnchorElement}
*/
diff --git a/ext/js/media/audio-downloader.js b/ext/js/media/audio-downloader.js
index a9b2133b..4e602f8c 100644
--- a/ext/js/media/audio-downloader.js
+++ b/ext/js/media/audio-downloader.js
@@ -278,7 +278,7 @@ export class AudioDownloader {
if (this._customAudioListSchema === null) {
const schema = await this._getCustomAudioListSchema();
- this._customAudioListSchema = new JsonSchema(/** @type {import('json-schema').Schema} */ (schema));
+ this._customAudioListSchema = new JsonSchema(/** @type {import('ext/json-schema').Schema} */ (schema));
}
this._customAudioListSchema.validate(responseJson);
diff --git a/jsconfig.json b/jsconfig.json
index 82048af1..4ab13106 100644
--- a/jsconfig.json
+++ b/jsconfig.json
@@ -12,7 +12,8 @@
"skipLibCheck": false,
"baseUrl": ".",
"paths": {
- "*": ["./types/ext/*"]
+ "*": ["./types/ext/*"],
+ "ext/json-schema": ["./types/ext/json-schema"]
},
"types": [
"chrome",
diff --git a/package-lock.json b/package-lock.json
index 1e291cf2..75f696d3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -52,6 +52,7 @@
"stylelint": "^15.11.0",
"stylelint-config-recommended": "^13.0.0",
"stylelint-stylistic": "^0.4.3",
+ "ts-json-schema-generator": "^1.5.0",
"typescript": "5.3.3",
"vitest": "^0.34.6"
}
@@ -2112,6 +2113,15 @@
"node": ">= 0.8"
}
},
+ "node_modules/commander": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
+ "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/comment-parser": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz",
@@ -3812,6 +3822,18 @@
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/jsonc-eslint-parser": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.0.tgz",
@@ -5011,6 +5033,15 @@
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
},
+ "node_modules/safe-stable-stringify": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz",
+ "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -5719,6 +5750,67 @@
"typescript": ">=4.2.0"
}
},
+ "node_modules/ts-json-schema-generator": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-1.5.0.tgz",
+ "integrity": "sha512-RkiaJ6YxGc5EWVPfyHxszTmpGxX8HC2XBvcFlAl1zcvpOG4tjjh+eXioStXJQYTvr9MoK8zCOWzAUlko3K0DiA==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.12",
+ "commander": "^11.0.0",
+ "glob": "^8.0.3",
+ "json5": "^2.2.3",
+ "normalize-path": "^3.0.0",
+ "safe-stable-stringify": "^2.4.3",
+ "typescript": "~5.3.2"
+ },
+ "bin": {
+ "ts-json-schema-generator": "bin/ts-json-schema-generator"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/ts-json-schema-generator/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/ts-json-schema-generator/node_modules/glob": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^5.0.1",
+ "once": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/ts-json-schema-generator/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
@@ -7662,6 +7754,12 @@
"delayed-stream": "~1.0.0"
}
},
+ "commander": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
+ "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+ "dev": true
+ },
"comment-parser": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz",
@@ -8882,6 +8980,12 @@
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
+ "json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true
+ },
"jsonc-eslint-parser": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.0.tgz",
@@ -9739,6 +9843,12 @@
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
},
+ "safe-stable-stringify": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz",
+ "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==",
+ "dev": true
+ },
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -10295,6 +10405,54 @@
"dev": true,
"requires": {}
},
+ "ts-json-schema-generator": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-1.5.0.tgz",
+ "integrity": "sha512-RkiaJ6YxGc5EWVPfyHxszTmpGxX8HC2XBvcFlAl1zcvpOG4tjjh+eXioStXJQYTvr9MoK8zCOWzAUlko3K0DiA==",
+ "dev": true,
+ "requires": {
+ "@types/json-schema": "^7.0.12",
+ "commander": "^11.0.0",
+ "glob": "^8.0.3",
+ "json5": "^2.2.3",
+ "normalize-path": "^3.0.0",
+ "safe-stable-stringify": "^2.4.3",
+ "typescript": "~5.3.2"
+ },
+ "dependencies": {
+ "brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "glob": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^5.0.1",
+ "once": "^1.3.0"
+ }
+ },
+ "minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^2.0.1"
+ }
+ }
+ }
+ },
"tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
diff --git a/package.json b/package.json
index adffa549..6be76caf 100644
--- a/package.json
+++ b/package.json
@@ -76,6 +76,7 @@
"stylelint": "^15.11.0",
"stylelint-config-recommended": "^13.0.0",
"stylelint-stylistic": "^0.4.3",
+ "ts-json-schema-generator": "^1.5.0",
"typescript": "5.3.3",
"vitest": "^0.34.6"
},
diff --git a/test/data/json.json b/test/data/json.json
new file mode 100644
index 00000000..6d806263
--- /dev/null
+++ b/test/data/json.json
@@ -0,0 +1,174 @@
+{
+ "files": [
+ {"path": "package.json", "ignore": true},
+ {"path": "package-lock.json", "ignore": true},
+ {"path": "jsconfig.json", "ignore": true},
+ {"path": ".stylelintrc.json", "ignore": true},
+ {"path": ".htmlvalidate.json", "ignore": true},
+ {"path": ".eslintrc.json", "ignore": true},
+ {"path": ".vscode/settings.json", "ignore": true},
+ {"path": ".vscode/extensions.json", "ignore": true},
+ {"path": "dev/jsconfig.json", "ignore": true},
+ {"path": "ext/manifest.json", "ignore": true},
+ {"path": "test/data/dictionaries/invalid-dictionary1/index.json", "ignore": true},
+ {"path": "test/data/dictionaries/invalid-dictionary2/kanji_bank_1.json", "ignore": true},
+ {"path": "test/data/dictionaries/invalid-dictionary2/index.json", "ignore": true},
+ {"path": "test/data/dictionaries/invalid-dictionary3/kanji_meta_bank_1.json", "ignore": true},
+ {"path": "test/data/dictionaries/invalid-dictionary3/index.json", "ignore": true},
+ {"path": "test/data/dictionaries/invalid-dictionary4/tag_bank_1.json", "ignore": true},
+ {"path": "test/data/dictionaries/invalid-dictionary4/index.json", "ignore": true},
+ {"path": "test/data/dictionaries/invalid-dictionary5/term_bank_1.json", "ignore": true},
+ {"path": "test/data/dictionaries/invalid-dictionary5/index.json", "ignore": true},
+ {"path": "test/data/dictionaries/invalid-dictionary6/term_meta_bank_1.json", "ignore": true},
+ {"path": "test/data/dictionaries/invalid-dictionary6/index.json", "ignore": true},
+ {"path": "test/jsconfig.json", "ignore": true},
+
+ {
+ "path": "dev/data/manifest-variants.json",
+ "typeFile": "types/dev/manifest.d.ts",
+ "type": "ManifestConfig"
+ },
+ {
+ "path": "ext/data/deinflect.json",
+ "typeFile": "types/ext/deinflector.d.ts",
+ "type": "ReasonsRaw"
+ },
+ {
+ "path": "ext/data/pronunciation-style.json",
+ "typeFile": "types/ext/css-style-applier.d.ts",
+ "type": "RawStyleData"
+ },
+ {
+ "path": "ext/data/structured-content-style.json",
+ "typeFile": "types/ext/css-style-applier.d.ts",
+ "type": "RawStyleData"
+ },
+ {
+ "path": "ext/data/schemas/dictionary-index-schema.json",
+ "typeFile": "types/test/json.d.ts",
+ "type": "AjvSchema"
+ },
+ {
+ "path": "ext/data/schemas/dictionary-kanji-bank-v1-schema.json",
+ "typeFile": "types/test/json.d.ts",
+ "type": "AjvSchema"
+ },
+ {
+ "path": "ext/data/schemas/dictionary-kanji-bank-v3-schema.json",
+ "typeFile": "types/test/json.d.ts",
+ "type": "AjvSchema"
+ },
+ {
+ "path": "ext/data/schemas/dictionary-kanji-meta-bank-v3-schema.json",
+ "typeFile": "types/test/json.d.ts",
+ "type": "AjvSchema"
+ },
+ {
+ "path": "ext/data/schemas/dictionary-tag-bank-v3-schema.json",
+ "typeFile": "types/test/json.d.ts",
+ "type": "AjvSchema"
+ },
+ {
+ "path": "ext/data/schemas/dictionary-term-bank-v1-schema.json",
+ "typeFile": "types/test/json.d.ts",
+ "type": "AjvSchema"
+ },
+ {
+ "path": "ext/data/schemas/dictionary-term-bank-v3-schema.json",
+ "typeFile": "types/test/json.d.ts",
+ "type": "AjvSchema"
+ },
+ {
+ "path": "ext/data/schemas/dictionary-term-meta-bank-v3-schema.json",
+ "typeFile": "types/test/json.d.ts",
+ "type": "AjvSchema"
+ },
+ {
+ "path": "ext/data/schemas/custom-audio-list-schema.json",
+ "typeFile": "types/test/json.d.ts",
+ "type": "AjvSchema"
+ },
+ {
+ "path": "ext/data/schemas/options-schema.json",
+ "typeFile": "types/test/json.d.ts",
+ "type": "AjvSchema"
+ },
+ {
+ "path": "test/data/translator-test-inputs.json",
+ "typeFile": "types/test/translator.d.ts",
+ "type": "TranslatorTestInputs",
+ "jsconfig": "test"
+ },
+ {
+ "path": "test/data/translator-test-results.json",
+ "typeFile": "types/test/translator.d.ts",
+ "type": "TranslatorTestResults",
+ "jsconfig": "test"
+ },
+ {
+ "path": "test/data/translator-test-results-note-data1.json",
+ "typeFile": "types/test/translator.d.ts",
+ "type": "TranslatorTestNoteDataResults",
+ "jsconfig": "test"
+ },
+ {
+ "path": "test/data/anki-note-builder-test-results.json",
+ "typeFile": "types/test/translator.d.ts",
+ "type": "AnkiNoteBuilderTestResults",
+ "jsconfig": "test"
+ },
+ {
+ "path": "test/data/json.json",
+ "typeFile": "types/test/json.d.ts",
+ "type": "JsonInfo"
+ },
+ {
+ "path": "test/data/dictionaries/valid-dictionary1/index.json",
+ "typeFile": "types/ext/dictionary-data.d.ts",
+ "type": "Index",
+ "schema": "ext/data/schemas/dictionary-index-schema.json"
+ },
+ {
+ "path": "test/data/dictionaries/valid-dictionary1/kanji_bank_1.json",
+ "typeFile": "types/ext/dictionary-data.d.ts",
+ "type": "KanjiV3Array",
+ "schema": "ext/data/schemas/dictionary-kanji-bank-v3-schema.json"
+ },
+ {
+ "path": "test/data/dictionaries/valid-dictionary1/kanji_meta_bank_1.json",
+ "typeFile": "types/ext/dictionary-data.d.ts",
+ "type": "KanjiMetaArray",
+ "schema": "ext/data/schemas/dictionary-kanji-meta-bank-v3-schema.json"
+ },
+ {
+ "path": "test/data/dictionaries/valid-dictionary1/tag_bank_1.json",
+ "typeFile": "types/ext/dictionary-data.d.ts",
+ "type": "TagArray",
+ "schema": "ext/data/schemas/dictionary-tag-bank-v3-schema.json"
+ },
+ {
+ "path": "test/data/dictionaries/valid-dictionary1/tag_bank_2.json",
+ "typeFile": "types/ext/dictionary-data.d.ts",
+ "type": "TagArray",
+ "schema": "ext/data/schemas/dictionary-tag-bank-v3-schema.json"
+ },
+ {
+ "path": "test/data/dictionaries/valid-dictionary1/tag_bank_3.json",
+ "typeFile": "types/ext/dictionary-data.d.ts",
+ "type": "TagArray",
+ "schema": "ext/data/schemas/dictionary-tag-bank-v3-schema.json"
+ },
+ {
+ "path": "test/data/dictionaries/valid-dictionary1/term_bank_1.json",
+ "typeFile": "types/ext/dictionary-data.d.ts",
+ "type": "TermV3Array",
+ "schema": "ext/data/schemas/dictionary-term-bank-v3-schema.json"
+ },
+ {
+ "path": "test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json",
+ "typeFile": "types/ext/dictionary-data.d.ts",
+ "type": "TermMetaArray",
+ "schema": "ext/data/schemas/dictionary-term-meta-bank-v3-schema.json"
+ }
+ ]
+} \ No newline at end of file
diff --git a/test/data/translator-test-inputs.json b/test/data/translator-test-inputs.json
index cf4b8f6a..5afb6a60 100644
--- a/test/data/translator-test-inputs.json
+++ b/test/data/translator-test-inputs.json
@@ -19,12 +19,12 @@
"sortFrequencyDictionary": null,
"sortFrequencyDictionaryOrder": "descending",
"removeNonJapaneseCharacters": true,
- "convertHalfWidthCharacters": false,
- "convertNumericCharacters": false,
- "convertAlphabeticCharacters": false,
- "convertHiraganaToKatakana": false,
- "convertKatakanaToHiragana": false,
- "collapseEmphaticSequences": false,
+ "convertHalfWidthCharacters": "false",
+ "convertNumericCharacters": "false",
+ "convertAlphabeticCharacters": "false",
+ "convertHiraganaToKatakana": "false",
+ "convertKatakanaToHiragana": "false",
+ "collapseEmphaticSequences": "false",
"textReplacements": [
null
],
diff --git a/test/fixtures/translator-test.js b/test/fixtures/translator-test.js
index cb1a3ef5..3304c587 100644
--- a/test/fixtures/translator-test.js
+++ b/test/fixtures/translator-test.js
@@ -18,8 +18,8 @@
import {IDBKeyRange, indexedDB} from 'fake-indexeddb';
import {readFileSync} from 'fs';
-import {fileURLToPath, pathToFileURL} from 'node:url';
-import {dirname, join, resolve} from 'path';
+import {fileURLToPath} from 'node:url';
+import {dirname, join} from 'path';
import {expect, vi} from 'vitest';
import {parseJson} from '../../dev/json.js';
import {createDictionaryArchive} from '../../dev/util.js';
@@ -28,43 +28,13 @@ import {DictionaryDatabase} from '../../ext/js/language/dictionary-database.js';
import {DictionaryImporter} from '../../ext/js/language/dictionary-importer.js';
import {JapaneseUtil} from '../../ext/js/language/sandbox/japanese-util.js';
import {Translator} from '../../ext/js/language/translator.js';
+import {chrome, fetch} from '../mocks/common.js';
import {DictionaryImporterMediaLoader} from '../mocks/dictionary-importer-media-loader.js';
import {createDomTest} from './dom-test.js';
const extDir = join(dirname(fileURLToPath(import.meta.url)), '../../ext');
const deinflectionReasonsPath = join(extDir, 'data/deinflect.json');
-/** @type {import('dev/vm').PseudoChrome} */
-const chrome = {
- runtime: {
- getURL: (path) => {
- return pathToFileURL(join(extDir, path.replace(/^\//, ''))).href;
- }
- }
-};
-
-/**
- * @param {string} url
- * @returns {Promise<import('dev/vm').PseudoFetchResponse>}
- */
-async function fetch(url) {
- let filePath;
- try {
- filePath = fileURLToPath(url);
- } catch (e) {
- filePath = resolve(extDir, url.replace(/^[/\\]/, ''));
- }
- await Promise.resolve();
- const content = readFileSync(filePath, {encoding: null});
- return {
- ok: true,
- status: 200,
- statusText: 'OK',
- text: async () => content.toString('utf8'),
- json: async () => parseJson(content.toString('utf8'))
- };
-}
-
vi.stubGlobal('indexedDB', indexedDB);
vi.stubGlobal('IDBKeyRange', IDBKeyRange);
vi.stubGlobal('fetch', fetch);
diff --git a/test/jsconfig.json b/test/jsconfig.json
index 9ab0c332..a9845861 100644
--- a/test/jsconfig.json
+++ b/test/jsconfig.json
@@ -15,7 +15,9 @@
"*": ["../types/ext/*"],
"dev/*": ["../types/dev/*"],
"test/*": ["../types/test/*"],
- "rollup/parseAst": ["../types/other/rollup-parse-ast"]
+ "rollup/parseAst": ["../types/other/rollup-parse-ast"],
+ "ext/json-schema": ["../types/ext/json-schema"],
+ "json-schema": ["json-schema"]
},
"types": [
"chrome",
diff --git a/test/json-schema.test.js b/test/json-schema.test.js
index fb7644de..0c1a483b 100644
--- a/test/json-schema.test.js
+++ b/test/json-schema.test.js
@@ -23,7 +23,7 @@ import {parseJson} from '../dev/json.js';
import {JsonSchema} from '../ext/js/data/json-schema.js';
/**
- * @param {import('json-schema').Schema} schema
+ * @param {import('ext/json-schema').Schema} schema
* @param {unknown} value
* @returns {boolean}
*/
@@ -32,18 +32,18 @@ function schemaValidate(schema, value) {
}
/**
- * @param {import('json-schema').Schema} schema
+ * @param {import('ext/json-schema').Schema} schema
* @param {unknown} value
- * @returns {import('json-schema').Value}
+ * @returns {import('ext/json-schema').Value}
*/
function getValidValueOrDefault(schema, value) {
return new JsonSchema(schema).getValidValueOrDefault(value);
}
/**
- * @param {import('json-schema').Schema} schema
- * @param {import('json-schema').Value} value
- * @returns {import('json-schema').Value}
+ * @param {import('ext/json-schema').Schema} schema
+ * @param {import('ext/json-schema').Value} value
+ * @returns {import('ext/json-schema').Value}
*/
function createProxy(schema, value) {
return new JsonSchema(schema).createProxy(value);
@@ -62,7 +62,7 @@ function clone(value) {
/** */
function testValidate1() {
test('Validate1', () => {
- /** @type {import('json-schema').Schema} */
+ /** @type {import('ext/json-schema').Schema} */
const schema = {
allOf: [
{
@@ -123,7 +123,7 @@ function testValidate1() {
/** */
function testValidate2() {
test('Validate2', () => {
- /** @type {{schema: import('json-schema').Schema, inputs: {expected: boolean, value: unknown}[]}[]} */
+ /** @type {{schema: import('ext/json-schema').Schema, inputs: {expected: boolean, value: unknown}[]}[]} */
const data = [
// String tests
{
@@ -530,7 +530,7 @@ function testValidate2() {
/** */
function testGetValidValueOrDefault1() {
test('GetValidValueOrDefault1', () => {
- /** @type {{schema: import('json-schema').Schema, inputs: [value: unknown, expected: unknown][]}[]} */
+ /** @type {{schema: import('ext/json-schema').Schema, inputs: [value: unknown, expected: unknown][]}[]} */
const data = [
// Test value defaulting on objects with additionalProperties=false
{
@@ -702,7 +702,7 @@ function testGetValidValueOrDefault1() {
type: 'object',
required: ['toString'],
properties: {
- toString: /** @type {import('json-schema').SchemaObject} */ ({
+ toString: /** @type {import('ext/json-schema').SchemaObject} */ ({
type: 'string',
default: 'default'
})
@@ -888,7 +888,7 @@ function testGetValidValueOrDefault1() {
/** */
function testProxy1() {
test('Proxy1', () => {
- /** @type {{schema: import('json-schema').Schema, tests: {error: boolean, value?: import('json-schema').Value, action: (value: import('core').SafeAny) => void}[]}[]} */
+ /** @type {{schema: import('ext/json-schema').Schema, tests: {error: boolean, value?: import('ext/json-schema').Value, action: (value: import('core').SafeAny) => void}[]}[]} */
const data = [
// Object tests
{
diff --git a/test/json.test.js b/test/json.test.js
new file mode 100644
index 00000000..8cf01491
--- /dev/null
+++ b/test/json.test.js
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2023 Yomitan Authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+import Ajv from 'ajv';
+import {readFileSync} from 'fs';
+import {join, dirname as pathDirname} from 'path';
+import {createGenerator} from 'ts-json-schema-generator';
+import {fileURLToPath} from 'url';
+import {describe, expect, test} from 'vitest';
+import {parseJson} from '../dev/json.js';
+import {getAllFiles} from '../dev/util.js';
+
+const dirname = pathDirname(fileURLToPath(import.meta.url));
+const rootDir = join(dirname, '..');
+
+/**
+ * @param {import('test/json').JsconfigType|undefined} jsconfigType
+ * @returns {string}
+ */
+function getJsconfigPath(jsconfigType) {
+ let path;
+ switch (jsconfigType) {
+ case 'dev': path = '../dev/jsconfig.json'; break;
+ case 'test': path = '../test/jsconfig.json'; break;
+ default: path = '../jsconfig.json'; break;
+ }
+ return join(dirname, path);
+}
+
+/**
+ * @returns {Ajv}
+ */
+function createAjv() {
+ return new Ajv({
+ meta: true,
+ strictTuples: false,
+ allowUnionTypes: true
+ });
+}
+
+/**
+ * @param {string} path
+ * @param {string} type
+ * @param {import('test/json').JsconfigType|undefined} jsconfigType
+ * @returns {import('ajv').ValidateFunction<unknown>}
+ */
+function createValidatorFunctionFromTypeScript(path, type, jsconfigType) {
+ /** @type {import('ts-json-schema-generator/dist/src/Config').Config} */
+ const config = {
+ path,
+ tsconfig: getJsconfigPath(jsconfigType),
+ type,
+ jsDoc: 'none',
+ additionalProperties: false,
+ minify: false,
+ expose: 'none',
+ strictTuples: true
+ };
+ const schema = createGenerator(config).createSchema(config.type);
+ const ajv = createAjv();
+ return ajv.compile(schema);
+}
+
+/**
+ * @param {string} path
+ * @returns {import('ajv').ValidateFunction<unknown>}
+ */
+function createValidatorFunctionFromSchemaJson(path) {
+ /** @type {import('ajv').Schema} */
+ const schema = parseJson(readFileSync(path, {encoding: 'utf8'}));
+ const ajv = createAjv();
+ return ajv.compile(schema);
+}
+
+/**
+ * @param {string} value
+ * @returns {string}
+ */
+function normalizePathDirectorySeparators(value) {
+ return value.replace(/\\/g, '/');
+}
+
+
+describe.concurrent('JSON validation', () => {
+ const ignoreDirectories = new Set([
+ 'builds',
+ 'dictionaries',
+ 'node_modules',
+ 'playwright-report',
+ 'playwright',
+ 'test-results',
+ 'dev/lib',
+ 'test/playwright'
+ ]);
+
+ const existingJsonFiles = getAllFiles(rootDir, (path, isDirectory) => {
+ const fileNameNormalized = normalizePathDirectorySeparators(path);
+ if (isDirectory) {
+ return !ignoreDirectories.has(fileNameNormalized);
+ } else {
+ return /\.json$/i.test(fileNameNormalized);
+ }
+ });
+ /** @type {Set<string>} */
+ const existingJsonFileSet = new Set();
+ for (const path of existingJsonFiles) {
+ existingJsonFileSet.add(normalizePathDirectorySeparators(path));
+ }
+
+ const jsonFileName = 'json.json';
+
+ /** @type {import('test/json').JsonInfo} */
+ const jsonFileData = parseJson(readFileSync(join(dirname, `data/${jsonFileName}`), {encoding: 'utf8'}));
+
+ test(`Each item in ${jsonFileName} must have a unique path`, () => {
+ /** @type {Set<string>} */
+ const set = new Set();
+ for (const {path} of jsonFileData.files) {
+ set.add(path);
+ }
+ expect(set.size).toBe(jsonFileData.files.length);
+ });
+
+ /** @type {Map<string, import('test/json').JsonFileInfo>} */
+ const jsonFileMap = new Map();
+ for (const item of jsonFileData.files) {
+ jsonFileMap.set(item.path, item);
+ }
+
+ // Validate file existance
+ const requiredFiles = jsonFileData.files.filter((v) => !v.ignore);
+ test.each(requiredFiles)('File must exist in project: $path', ({path}) => {
+ expect(existingJsonFileSet.has(path)).toBe(true);
+ });
+
+ // Validate new files
+ const existingJsonFiles2 = existingJsonFiles.map((path) => ({path: normalizePathDirectorySeparators(path)}));
+ test.each(existingJsonFiles2)(`File must exist in ${jsonFileName}: $path`, ({path}) => {
+ expect(jsonFileMap.has(path)).toBe(true);
+ });
+
+ // Validate schemas 1
+ /** @type {import('test/json').JsonFileParseInfo[]} */
+ const schemaValidationTargets1 = [];
+ for (const info of jsonFileData.files) {
+ if (info.ignore || !existingJsonFileSet.has(info.path)) { continue; }
+ schemaValidationTargets1.push(info);
+ }
+ test.each(schemaValidationTargets1)('Validating file against TypeScript: $path', ({path, typeFile, type, jsconfig}) => {
+ const validate = createValidatorFunctionFromTypeScript(join(rootDir, typeFile), type, jsconfig);
+ const data = parseJson(readFileSync(join(rootDir, path), {encoding: 'utf8'}));
+ const valid = validate(data);
+ const {errors} = validate;
+ expect(errors).toBe(null);
+ expect(valid).toBe(true);
+ });
+
+ // Validate schemas 2
+ /** @type {{path: string, schema: string}[]} */
+ const schemaValidationTargets2 = [];
+ for (const info of jsonFileData.files) {
+ if (info.ignore || !existingJsonFileSet.has(info.path)) { continue; }
+ const {schema, path} = info;
+ if (typeof schema !== 'string') { continue; }
+ schemaValidationTargets2.push({schema, path});
+ }
+ test.each(schemaValidationTargets2)('Validating file against schema: $path', ({path, schema}) => {
+ const validate = createValidatorFunctionFromSchemaJson(join(rootDir, schema));
+ const data = parseJson(readFileSync(join(rootDir, path), {encoding: 'utf8'}));
+ const valid = validate(data);
+ const {errors} = validate;
+ expect(errors).toBe(null);
+ expect(valid).toBe(true);
+ });
+});
diff --git a/test/mocks/common.js b/test/mocks/common.js
new file mode 100644
index 00000000..7fe30a3e
--- /dev/null
+++ b/test/mocks/common.js
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 Yomitan Authors
+ * Copyright (C) 2020-2022 Yomichan Authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+import {readFileSync} from 'fs';
+import {fileURLToPath, pathToFileURL} from 'node:url';
+import {dirname, join, resolve} from 'path';
+import {parseJson} from '../../dev/json.js';
+
+const extDir = join(dirname(fileURLToPath(import.meta.url)), '../../ext');
+
+/** @type {import('test/mocks').ChromeMock} */
+export const chrome = {
+ runtime: {
+ getURL: (path) => {
+ return pathToFileURL(join(extDir, path.replace(/^\//, ''))).href;
+ }
+ }
+};
+
+/** @type {import('test/mocks').FetchMock} */
+export async function fetch(url) {
+ let filePath;
+ try {
+ filePath = fileURLToPath(url);
+ } catch (e) {
+ filePath = resolve(extDir, url.replace(/^[/\\]/, ''));
+ }
+ await Promise.resolve();
+ const content = readFileSync(filePath, {encoding: null});
+ return {
+ ok: true,
+ status: 200,
+ statusText: 'OK',
+ text: async () => content.toString('utf8'),
+ json: async () => parseJson(content.toString('utf8'))
+ };
+}
diff --git a/test/options-util.test.js b/test/options-util.test.js
index 41185756..7bb9767a 100644
--- a/test/options-util.test.js
+++ b/test/options-util.test.js
@@ -19,40 +19,15 @@
/* eslint-disable no-multi-spaces */
import fs from 'fs';
-import url, {fileURLToPath} from 'node:url';
+import {fileURLToPath} from 'node:url';
import path from 'path';
import {expect, test, vi} from 'vitest';
-import {parseJson} from '../dev/json.js';
import {OptionsUtil} from '../ext/js/data/options-util.js';
import {TemplatePatcher} from '../ext/js/templates/template-patcher.js';
+import {chrome, fetch} from './mocks/common.js';
const dirname = path.dirname(fileURLToPath(import.meta.url));
-/**
- * @param {string} url2
- * @returns {Promise<import('dev/vm').PseudoFetchResponse>}
- */
-async function fetch(url2) {
- const filePath = url.fileURLToPath(url2);
- await Promise.resolve();
- const content = fs.readFileSync(filePath, {encoding: null});
- return {
- ok: true,
- status: 200,
- statusText: 'OK',
- text: async () => Promise.resolve(content.toString('utf8')),
- json: async () => Promise.resolve(parseJson(content.toString('utf8')))
- };
-}
-/** @type {import('dev/vm').PseudoChrome} */
-const chrome = {
- runtime: {
- getURL(path2) {
- return url.pathToFileURL(path.join(dirname, '..', 'ext', path2.replace(/^\//, ''))).href;
- }
- }
-};
-
vi.stubGlobal('fetch', fetch);
vi.stubGlobal('chrome', chrome);
diff --git a/test/profile-conditions-util.test.js b/test/profile-conditions-util.test.js
index f64ce79c..d5c8f8d2 100644
--- a/test/profile-conditions-util.test.js
+++ b/test/profile-conditions-util.test.js
@@ -62,7 +62,7 @@ function testNormalizeContext() {
/** */
function testSchemas() {
test('Schemas', () => {
- /** @type {{conditionGroups: import('settings').ProfileConditionGroup[], expectedSchema?: import('json-schema').Schema, inputs?: {expected: boolean, context: import('settings').OptionsContext}[]}[]} */
+ /** @type {{conditionGroups: import('settings').ProfileConditionGroup[], expectedSchema?: import('ext/json-schema').Schema, inputs?: {expected: boolean, context: import('settings').OptionsContext}[]}[]} */
const data = [
// Empty
{
diff --git a/test/utilities/translator.js b/test/utilities/translator.js
index 9073b206..81081af6 100644
--- a/test/utilities/translator.js
+++ b/test/utilities/translator.js
@@ -18,10 +18,11 @@
/**
+ * TODO : This function is not very type safe at the moment, could be improved.
* @template {import('translation').FindTermsOptions|import('translation').FindKanjiOptions} T
* @param {string} dictionaryName
- * @param {import('dev/vm').OptionsPresetObject} optionsPresets
- * @param {string|import('dev/vm').OptionsPresetObject|(string|import('dev/vm').OptionsPresetObject)[]} optionsArray
+ * @param {import('test/translator').OptionsPresetObject} optionsPresets
+ * @param {import('test/translator').OptionsList} optionsArray
* @returns {T}
* @throws {Error}
*/
diff --git a/types/dev/manifest.d.ts b/types/dev/manifest.d.ts
index e455208f..af475ee9 100644
--- a/types/dev/manifest.d.ts
+++ b/types/dev/manifest.d.ts
@@ -30,7 +30,7 @@ export type ManifestVariant = {
fileName?: string;
fileCopies?: string[];
excludeFiles?: string[];
- modifications: Modification[];
+ modifications?: Modification[];
};
export type Modification = (
diff --git a/types/ext/dictionary-data.d.ts b/types/ext/dictionary-data.d.ts
index f594f913..b194c190 100644
--- a/types/ext/dictionary-data.d.ts
+++ b/types/ext/dictionary-data.d.ts
@@ -44,6 +44,8 @@ export type IndexTag = {
score: number;
};
+export type TermV1Array = TermV1[];
+
export type TermV1 = [
expression: string,
reading: string,
@@ -53,6 +55,8 @@ export type TermV1 = [
...glossary: string[],
];
+export type TermV3Array = TermV3[];
+
export type TermV3 = [
expression: string,
reading: string,
@@ -64,6 +68,8 @@ export type TermV3 = [
termTags: string,
];
+export type KanjiV1Array = KanjiV1[];
+
export type KanjiV1 = [
character: string,
onyomi: string,
@@ -72,6 +78,8 @@ export type KanjiV1 = [
...meanings: string[],
];
+export type KanjiV3Array = KanjiV3[];
+
export type KanjiV3 = [
character: string,
onyomi: string,
@@ -93,7 +101,13 @@ export type TermGlossaryText = {type: 'text', text: string};
export type TermGlossaryImage = {type: 'image'} & TermImage;
export type TermGlossaryStructuredContent = {type: 'structured-content', content: StructuredContent.Content};
-export type TermImage = StructuredContent.ImageElementBase;
+export type TermImage = StructuredContent.ImageElementBase & {
+ // Compatibility properties
+ verticalAlign?: undefined;
+ sizeUnits?: undefined;
+};
+
+export type TagArray = Tag[];
export type Tag = [
name: string,
@@ -109,6 +123,8 @@ export type GenericFrequencyData = string | number | {
reading?: undefined; // Used for type disambiguation, field does not actually exist
};
+export type TermMetaArray = TermMeta[];
+
export type TermMeta = TermMetaFrequency | TermMetaPitch;
export type TermMetaFrequencyDataWithReading = {
@@ -138,6 +154,8 @@ export type TermMetaPitch = [
data: TermMetaPitchData,
];
+export type KanjiMetaArray = KanjiMeta[];
+
export type KanjiMeta = KanjiMetaFrequency;
export type KanjiMetaFrequency = [
diff --git a/types/ext/structured-content.d.ts b/types/ext/structured-content.d.ts
index 09755c88..572b827a 100644
--- a/types/ext/structured-content.d.ts
+++ b/types/ext/structured-content.d.ts
@@ -161,14 +161,6 @@ export type ImageElementBase = {
* Whether or not the image can be collapsed.
*/
collapsible?: boolean;
- /**
- * This property is not defined on the base class.
- */
- verticalAlign?: undefined;
- /**
- * This property is not defined on the base class.
- */
- sizeUnits?: undefined;
};
export type ImageElement = ImageElementBase & {
diff --git a/types/ext/translation.d.ts b/types/ext/translation.d.ts
index 3adfc673..595a5a35 100644
--- a/types/ext/translation.d.ts
+++ b/types/ext/translation.d.ts
@@ -28,7 +28,7 @@ export type FindKanjiOptions = {
* The mapping of dictionaries to search for kanji in.
* The key is the dictionary name.
*/
- enabledDictionaryMap: Map<string, FindKanjiDictionary>;
+ enabledDictionaryMap: KanjiEnabledDictionaryMap;
/**
* Whether or not non-Japanese characters should be searched.
*/
diff --git a/types/test/json.d.ts b/types/test/json.d.ts
new file mode 100644
index 00000000..9ae21b0f
--- /dev/null
+++ b/types/test/json.d.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 Yomitan Authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+import type {Schema} from 'ajv';
+
+export type JsonInfo = {
+ files: JsonFileInfo[];
+};
+
+export type JsonFileInfo = JsonFileIgnoreInfo | JsonFileParseInfo;
+
+export type JsonFileIgnoreInfo = {
+ path: string;
+ ignore: true;
+};
+
+export type JsonFileParseInfo = {
+ path: string;
+ ignore?: undefined;
+ typeFile: string;
+ type: string;
+ schema?: string;
+ jsconfig?: JsconfigType;
+};
+
+export type AjvSchema = Schema;
+
+export type JsconfigType = 'main' | 'dev' | 'test';
diff --git a/types/dev/vm.d.ts b/types/test/mocks.d.ts
index 3eb0949f..13b56ac6 100644
--- a/types/dev/vm.d.ts
+++ b/types/test/mocks.d.ts
@@ -15,24 +15,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import type * as Translation from '../ext/translation';
-
-export type PseudoChrome = {
+export type ChromeMock = {
runtime: {
getURL(path: string): string;
};
};
-export type PseudoFetchResponse = {
+export type FetchMock = (url: string) => Promise<FetchResponseMock>;
+
+export type FetchResponseMock = {
ok: boolean;
status: number;
statusText: string;
text(): Promise<string>;
json(): Promise<unknown>;
};
-
-export type OptionsPresetObject = {
- [key: string]: OptionsPreset;
-};
-
-export type OptionsPreset = Partial<Translation.FindTermsOptions>;
diff --git a/types/test/translator.d.ts b/types/test/translator.d.ts
index 3e4c8b9d..7dff116e 100644
--- a/types/test/translator.d.ts
+++ b/types/test/translator.d.ts
@@ -15,12 +15,49 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import type {OptionsPresetObject} from 'dev/vm';
+import type {FindTermsMatchType, FindTermsSortOrder, FindTermsVariantMode, FindTermsEmphaticSequencesMode, FindKanjiDictionary, FindTermDictionary} from '../ext/translation';
import type {FindTermsMode} from 'translator';
import type {DictionaryEntry} from 'dictionary';
import type {NoteData} from 'anki-templates';
import type {NoteFields} from 'anki';
+export type OptionsPresetObject = {
+ [key: string]: OptionsPreset;
+};
+
+export type OptionsList = string | (string | OptionsPreset)[];
+
+export type OptionsPreset = FindKanjiOptionsPreset | FindTermsOptionsPreset;
+
+export type FindKanjiOptionsPreset = {
+ enabledDictionaryMap?: [key: string, value: FindKanjiDictionary][];
+ removeNonJapaneseCharacters?: boolean;
+};
+
+export type FindTermsOptionsPreset = {
+ matchType?: FindTermsMatchType;
+ deinflect?: boolean;
+ mainDictionary?: string;
+ sortFrequencyDictionary?: string | null;
+ sortFrequencyDictionaryOrder?: FindTermsSortOrder;
+ removeNonJapaneseCharacters?: boolean;
+ convertHalfWidthCharacters?: FindTermsVariantMode;
+ convertNumericCharacters?: FindTermsVariantMode;
+ convertAlphabeticCharacters?: FindTermsVariantMode;
+ convertHiraganaToKatakana?: FindTermsVariantMode;
+ convertKatakanaToHiragana?: FindTermsVariantMode;
+ collapseEmphaticSequences?: FindTermsEmphaticSequencesMode;
+ textReplacements?: (FindTermsTextReplacement[] | null)[];
+ enabledDictionaryMap?: [key: string, value: FindTermDictionary][];
+ excludeDictionaryDefinitions?: string[] | null;
+};
+
+export type FindTermsTextReplacement = {
+ pattern: string;
+ flags: string;
+ replacement: string;
+};
+
export type TranslatorTestInputs = {
optionsPresets: OptionsPresetObject;
tests: TestInput[];
@@ -32,7 +69,7 @@ export type TestInputFindKanji = {
func: 'findKanji';
name: string;
text: string;
- options: string;
+ options: OptionsList;
};
export type TestInputFindTerm = {
@@ -40,7 +77,7 @@ export type TestInputFindTerm = {
name: string;
mode: FindTermsMode;
text: string;
- options: string;
+ options: OptionsList;
};
export type TranslatorTestResults = TranslatorTestResult[];
@@ -55,7 +92,7 @@ export type TranslatorTestNoteDataResults = TranslatorTestNoteDataResult[];
export type TranslatorTestNoteDataResult = {
name: string;
- noteDataList: NoteData[];
+ noteDataList: Omit<NoteData, 'dictionaryEntry'>[] | null;
};
export type AnkiNoteBuilderTestResults = AnkiNoteBuilderTestResult[];