diff --git a/.eslintrc.json b/.eslintrc.json index 32bbdaec26..30d495d276 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -32,7 +32,10 @@ "jsonc", "unused-imports", "@typescript-eslint", - "@stylistic" + "@stylistic", + "unicorn", + "sonarjs", + "import" ], "ignorePatterns": [ "/ext/lib/", @@ -247,7 +250,107 @@ "eslint-comments/no-unused-disable": "error", - "unused-imports/no-unused-imports": "error" + "unused-imports/no-unused-imports": "error", + + "import/extensions": ["error", "ignorePackages"], + + "unicorn/catch-error-name": ["error", {"ignore": ["^(e|error2?)$"]}], + "unicorn/custom-error-definition": "error", + "unicorn/empty-brace-spaces": "error", + "unicorn/error-message": "error", + "unicorn/expiring-todo-comments": "error", + "unicorn/explicit-length-check": "error", + "unicorn/new-for-builtins": "error", + "unicorn/no-abusive-eslint-disable": "error", + "unicorn/no-array-for-each": "error", + "unicorn/no-array-method-this-argument": "error", + "unicorn/no-array-push-push": "error", + "unicorn/no-array-reduce": "error", + "unicorn/no-console-spaces": "error", + "unicorn/no-document-cookie": "error", + "unicorn/no-empty-file": "error", + "unicorn/no-hex-escape": "error", + "unicorn/no-instanceof-array": "error", + "unicorn/no-invalid-remove-event-listener": "error", + "unicorn/no-lonely-if": "error", + "unicorn/no-nested-ternary": "error", + "unicorn/no-new-buffer": "error", + "unicorn/no-object-as-default-parameter": "error", + "unicorn/no-static-only-class": "error", + "unicorn/no-thenable": "error", + "unicorn/no-unnecessary-await": "error", + "unicorn/no-unnecessary-polyfills": "error", + "unicorn/no-unreadable-array-destructuring": "error", + "unicorn/no-unreadable-iife": "error", + "unicorn/no-useless-fallback-in-spread": "error", + "unicorn/no-useless-length-check": "error", + "unicorn/no-useless-promise-resolve-reject": "error", + "unicorn/no-useless-spread": "error", + "unicorn/no-useless-switch-case": "error", + "unicorn/no-useless-undefined": "error", + "unicorn/no-zero-fractions": "error", + "unicorn/prefer-array-find": "error", + "unicorn/prefer-array-flat": "error", + "unicorn/prefer-array-flat-map": "error", + "unicorn/prefer-array-index-of": "error", + "unicorn/prefer-array-some": "error", + "unicorn/prefer-date-now": "error", + "unicorn/prefer-default-parameters": "error", + "unicorn/prefer-dom-node-dataset": "error", + "unicorn/prefer-dom-node-text-content": "error", + "unicorn/prefer-event-target": "error", + "unicorn/prefer-export-from": "error", + "unicorn/prefer-includes": "error", + "unicorn/prefer-keyboard-event-key": "error", + "unicorn/prefer-logical-operator-over-ternary": "error", + "unicorn/prefer-modern-math-apis": "error", + "unicorn/prefer-module": "error", + "unicorn/prefer-native-coercion-functions": "error", + "unicorn/prefer-negative-index": "error", + "unicorn/prefer-number-properties": "error", + "unicorn/prefer-object-from-entries": "error", + "unicorn/prefer-prototype-methods": "error", + "unicorn/prefer-reflect-apply": "error", + "unicorn/prefer-regexp-test": "error", + "unicorn/prefer-set-has": "error", + "unicorn/prefer-set-size": "error", + "unicorn/prefer-spread": "error", + "unicorn/prefer-string-starts-ends-with": "error", + "unicorn/prefer-string-trim-start-end": "error", + "unicorn/prefer-switch": "error", + "unicorn/prefer-ternary": "error", + "unicorn/relative-url-style": "error", + "unicorn/require-array-join-separator": "error", + "unicorn/require-number-to-fixed-digits-argument": "error", + "unicorn/template-indent": "error", + "unicorn/throw-new-error": "error", + + "sonarjs/max-switch-cases": "error", + "sonarjs/no-all-duplicated-branches": "error", + "sonarjs/no-collapsible-if": "error", + "sonarjs/no-collection-size-mischeck": "error", + "sonarjs/no-duplicated-branches": "error", + "sonarjs/no-element-overwrite": "error", + "sonarjs/no-empty-collection": "error", + "sonarjs/no-extra-arguments": "error", + "sonarjs/no-gratuitous-expressions": "error", + "sonarjs/no-identical-conditions": "error", + "sonarjs/no-identical-expressions": "error", + "sonarjs/no-identical-functions": "error", + "sonarjs/no-ignored-return": "error", + "sonarjs/no-inverted-boolean-check": "error", + "sonarjs/no-one-iteration-loop": "error", + "sonarjs/no-redundant-boolean": "error", + "sonarjs/no-redundant-jump": "error", + "sonarjs/no-same-line-conditional": "error", + "sonarjs/no-unused-collection": "error", + "sonarjs/no-use-of-empty-return-value": "error", + "sonarjs/no-useless-catch": "error", + "sonarjs/non-existent-operator": "error", + "sonarjs/prefer-immediate-return": "error", + "sonarjs/prefer-object-literal": "error", + "sonarjs/prefer-single-boolean-return": "error", + "sonarjs/prefer-while": "error" }, "overrides": [ { diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ea950b841..ea42aab43b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,6 +53,11 @@ jobs: env: CI: true + - name: Test JSON + run: npm run test-json + env: + CI: true + - name: Build Legal run: npm run license-report diff --git a/dev/bin/build.js b/dev/bin/build.js index bc0a8cb801..e22159d6b5 100644 --- a/dev/bin/build.js +++ b/dev/bin/build.js @@ -195,12 +195,10 @@ async function build(buildDir, extDir, manifestUtil, variantNames, manifestPath, await createZip(extDir, excludeFiles, fullFileName, sevenZipExes, onUpdate, dryRun); } - if (!dryRun) { - if (Array.isArray(fileCopies)) { - for (const fileName2 of fileCopies) { - const fileName2Safe = path.basename(fileName2); - fs.copyFileSync(fullFileName, path.join(buildDir, fileName2Safe)); - } + if (!dryRun && Array.isArray(fileCopies)) { + for (const fileName2 of fileCopies) { + const fileName2Safe = path.basename(fileName2); + fs.copyFileSync(fullFileName, path.join(buildDir, fileName2Safe)); } } } diff --git a/dev/build-libs.js b/dev/build-libs.js index 15ab3c8d9d..f1570fad58 100644 --- a/dev/build-libs.js +++ b/dev/build-libs.js @@ -64,6 +64,7 @@ export async function buildLibs() { const schemaFileNames = fs.readdirSync(schemaDir); const schemas = schemaFileNames.map((schemaFileName) => { /** @type {import('ajv').AnySchema} */ + // eslint-disable-next-line sonarjs/prefer-immediate-return const result = parseJson(fs.readFileSync(path.join(schemaDir, schemaFileName), {encoding: 'utf8'})); return result; }); diff --git a/dev/data-error.js b/dev/data-error.js index 5659245b81..eb7f71bc70 100644 --- a/dev/data-error.js +++ b/dev/data-error.js @@ -18,12 +18,14 @@ /** * Schema validation error type. */ -class DataError extends Error { +export class DataError extends Error { /** * @param {string} message */ constructor(message) { super(message); + /** @type {string} */ + this.name = 'DataError'; /** @type {unknown} */ this._data = void 0; } @@ -32,7 +34,3 @@ class DataError extends Error { get data() { return this._data; } set data(value) { this._data = value; } } - -module.exports = { - DataError -}; diff --git a/dev/generate-css-json.js b/dev/generate-css-json.js index 26bdfa6425..a837c9e2d6 100644 --- a/dev/generate-css-json.js +++ b/dev/generate-css-json.js @@ -101,11 +101,11 @@ export function formatRulesJson(rules) { for (const {selectors, styles} of rules) { if (ruleIndex > 0) { result += ','; } result += `\n${indent1}{\n${indent2}"selectors": `; - if (selectors.length === 1) { - result += `[${JSON.stringify(selectors[0], null, 4)}]`; - } else { - result += JSON.stringify(selectors, null, 4).replace(/\n/g, '\n' + indent2); - } + result += ( + selectors.length === 1 ? + `[${JSON.stringify(selectors[0], null, 4)}]` : + JSON.stringify(selectors, null, 4).replace(/\n/g, '\n' + indent2) + ); result += `,\n${indent2}"styles": [`; let styleIndex = 0; for (const [key, value] of styles) { diff --git a/dev/lib/dexie.js b/dev/lib/dexie.js index ffbd292342..83015e25e1 100644 --- a/dev/lib/dexie.js +++ b/dev/lib/dexie.js @@ -15,7 +15,6 @@ * along with this program. If not, see . */ -import Dexie from 'dexie'; -import 'dexie-export-import'; +export {default as Dexie} from 'dexie'; -export {Dexie}; +import 'dexie-export-import'; diff --git a/dev/manifest-util.js b/dev/manifest-util.js index 741f8b31ad..40bde911fc 100644 --- a/dev/manifest-util.js +++ b/dev/manifest-util.js @@ -24,15 +24,6 @@ import {parseJson} from './json.js'; const dirname = path.dirname(fileURLToPath(import.meta.url)); -/** - * @template [T=unknown] - * @param {T} value - * @returns {T} - */ -function clone(value) { - return parseJson(JSON.stringify(value)); -} - export class ManifestUtil { constructor() { @@ -70,7 +61,7 @@ export class ManifestUtil { } } - return clone(this._manifest); + return structuredClone(this._manifest); } /** @@ -189,7 +180,7 @@ export class ManifestUtil { const {start, deleteCount, items} = modification; /** @type {unknown[]} */ const value = this._getObjectProperties(manifest, path2, path2.length); - const itemsNew = items.map((v) => clone(v)); + const itemsNew = items.map((v) => structuredClone(v)); value.splice(start, deleteCount, ...itemsNew); } break; @@ -229,7 +220,7 @@ export class ManifestUtil { const {items} = modification; /** @type {unknown[]} */ const value = this._getObjectProperties(manifest, path2, path2.length); - const itemsNew = items.map((v) => clone(v)); + const itemsNew = items.map((v) => structuredClone(v)); value.push(...itemsNew); } break; @@ -335,7 +326,7 @@ export class ManifestUtil { * @returns {import('dev/manifest').Manifest} */ _createVariantManifest(manifest, variant) { - let modifiedManifest = clone(manifest); + let modifiedManifest = structuredClone(manifest); for (const {modifications} of this._getInheritanceChain(variant)) { modifiedManifest = this._applyModifications(modifiedManifest, modifications); } diff --git a/dev/schema-validate.js b/dev/schema-validate.js index d1ffcf8202..3ad247d903 100644 --- a/dev/schema-validate.js +++ b/dev/schema-validate.js @@ -18,6 +18,7 @@ import Ajv from 'ajv'; import {readFileSync} from 'fs'; +import {fileURLToPath} from 'url'; import {JsonSchema} from '../ext/js/data/json-schema.js'; import {DataError} from './data-error.js'; import {parseJson} from './json.js'; @@ -32,7 +33,7 @@ class JsonSchemaAjv { strictTuples: false, allowUnionTypes: true }); - const metaSchemaPath = require.resolve('ajv/dist/refs/json-schema-draft-07.json'); + const metaSchemaPath = fileURLToPath(import.meta.resolve('ajv/dist/refs/json-schema-draft-07.json')); /** @type {import('ajv').AnySchemaObject} */ const metaSchema = parseJson(readFileSync(metaSchemaPath, {encoding: 'utf8'})); ajv.addMetaSchema(metaSchema); diff --git a/dev/util.js b/dev/util.js index 89bd95dae2..eee1696427 100644 --- a/dev/util.js +++ b/dev/util.js @@ -40,10 +40,8 @@ export function getAllFiles(baseDirectory, predicate = null) { if (typeof predicate !== 'function' || predicate(relativeFileName, false)) { results.push(relativeFileName); } - } else if (stats.isDirectory()) { - if (typeof predicate !== 'function' || predicate(relativeFileName, true)) { - directories.push(fullFileName); - } + } else if (stats.isDirectory() && (typeof predicate !== 'function' || predicate(relativeFileName, true))) { + directories.push(fullFileName); } } } diff --git a/docs/anki-integration.md b/docs/anki-integration.md index 3773009abd..2bd9fad99c 100644 --- a/docs/anki-integration.md +++ b/docs/anki-integration.md @@ -47,6 +47,7 @@ Flashcard fields can be configured with the following steps: | `{glossary-brief}` | List of definitions for the term in a more compact format. | | `{glossary-no-dictionary}` | List of definitions for the term, except the dictionary tag is omitted. | | `{part-of-speech}` | Part of speech information for the term. | + | `{phonetic-transcriptions}`| List of phonetic transcriptions for the term. | | `{pitch-accents}` | List of pitch accent downstep notations for the term. | | `{pitch-accent-graphs}` | List of pitch accent graphs for the term. | | `{pitch-accent-positions}` | List of accent downstep positions for the term as a number. | diff --git a/ext/data/schemas/options-schema.json b/ext/data/schemas/options-schema.json index 24f3a6b0e4..ea7caf0fcd 100644 --- a/ext/data/schemas/options-schema.json +++ b/ext/data/schemas/options-schema.json @@ -1187,7 +1187,7 @@ {"action": "addNoteTermKanji", "argument": "", "key": "KeyE", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, {"action": "addNoteTermKana", "argument": "", "key": "KeyR", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, {"action": "playAudio", "argument": "", "key": "KeyP", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, - {"action": "viewNote", "argument": "", "key": "KeyV", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, + {"action": "viewNotes", "argument": "", "key": "KeyV", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, {"action": "copyHostSelection", "argument": "", "key": "KeyC", "modifiers": ["ctrl"], "scopes": ["popup"], "enabled": true} ] } diff --git a/ext/display-templates.html b/ext/display-templates.html index a50cea3b9d..caf5920c8e 100644 --- a/ext/display-templates.html +++ b/ext/display-templates.html @@ -8,7 +8,7 @@ - @@ -111,7 +111,7 @@
-
-