diff options
author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2023-12-20 00:47:15 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-20 05:47:15 +0000 |
commit | 8b943cc97fab890085448122e7c13dd035d0e238 (patch) | |
tree | a7a749a44771c6a82b1b72bb35cc0c81d57ddb54 | |
parent | b13fbd47941fc20cf623871396e34a6dfe9b4dba (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
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[]; |