diff options
| -rw-r--r-- | .eslintrc.json | 2 | ||||
| -rw-r--r-- | ext/js/background/backend.js | 8 | ||||
| -rw-r--r-- | ext/js/data/database.js | 21 | ||||
| -rw-r--r-- | ext/js/data/options-util.js | 10 | ||||
| -rw-r--r-- | ext/js/dictionary/dictionary-importer.js | 7 | ||||
| -rw-r--r-- | ext/js/display/display-anki.js | 1 | ||||
| -rw-r--r-- | ext/js/display/display-history.js | 10 | ||||
| -rw-r--r-- | ext/js/display/display.js | 31 | ||||
| -rw-r--r-- | ext/js/dom/text-source-generator.js | 4 | ||||
| -rw-r--r-- | ext/js/general/regex-util.js | 15 | ||||
| -rw-r--r-- | ext/js/input/hotkey-help-controller.js | 15 | ||||
| -rw-r--r-- | ext/js/language/translator.js | 5 | ||||
| -rw-r--r-- | ext/js/media/audio-downloader.js | 12 | ||||
| -rw-r--r-- | ext/js/pages/settings/dictionary-controller.js | 1 | ||||
| -rw-r--r-- | ext/js/templates/sandbox/anki-template-renderer.js | 42 | ||||
| -rw-r--r-- | test/document-util.test.js | 3 | ||||
| -rw-r--r-- | test/dom-text-scanner.test.js | 10 | ||||
| -rw-r--r-- | test/eslint-config.test.js | 12 | ||||
| -rw-r--r-- | types/ext/template-renderer.d.ts | 2 | ||||
| -rw-r--r-- | types/test/eslint-config.d.ts | 29 | 
20 files changed, 195 insertions, 45 deletions
| diff --git a/.eslintrc.json b/.eslintrc.json index bc1f2940..f2faff59 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -397,7 +397,7 @@                  "@typescript-eslint/no-floating-promises": ["error", {"ignoreIIFE": true}],                  "@typescript-eslint/no-misused-promises": "off",                  "@typescript-eslint/no-redundant-type-constituents": "error", -                "@typescript-eslint/no-unsafe-argument": "off", +                "@typescript-eslint/no-unsafe-argument": "error",                  "@typescript-eslint/no-unsafe-assignment": "off",                  "@typescript-eslint/no-unsafe-call": "off",                  "@typescript-eslint/no-unsafe-enum-comparison": "off", diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 03c0b5fe..79023ac9 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -349,12 +349,18 @@ export class Backend {      _onWebExtensionEventWrapper(handler) {          return /** @type {T} */ ((...args) => {              if (this._isPrepared) { +                // This is using SafeAny to just forward the parameters +                // eslint-disable-next-line @typescript-eslint/no-unsafe-argument                  handler(...args);                  return;              }              this._prepareCompletePromise.then( -                () => { handler(...args); }, +                () => { +                    // This is using SafeAny to just forward the parameters +                    // eslint-disable-next-line @typescript-eslint/no-unsafe-argument +                    handler(...args); +                },                  () => {} // NOP              );          }); diff --git a/ext/js/data/database.js b/ext/js/data/database.js index e8575be2..7f37347b 100644 --- a/ext/js/data/database.js +++ b/ext/js/data/database.js @@ -194,7 +194,8 @@ export class Database {          request.onsuccess = (e) => {              const cursor = /** @type {IDBRequest<?IDBCursorWithValue>} */ (e.target).result;              if (cursor) { -                const {value} = cursor; +                /** @type {TResult} */ +                const value = cursor.value;                  if (noPredicate || predicate(value, predicateArg)) {                      resolve(value, data);                  } else { @@ -353,7 +354,9 @@ export class Database {          for (const {version, stores} of upgrades) {              if (oldVersion >= version) { continue; } -            for (const [objectStoreName, {primaryKey, indices}] of Object.entries(stores)) { +            /** @type {[objectStoreName: string, value: import('database').StoreDefinition][]} */ +            const entries = Object.entries(stores); +            for (const [objectStoreName, {primaryKey, indices}] of entries) {                  const existingObjectStoreNames = transaction.objectStoreNames || db.objectStoreNames;                  const objectStore = (                      this._listContains(existingObjectStoreNames, objectStoreName) ? @@ -394,8 +397,14 @@ export class Database {       */      _getAllFast(objectStoreOrIndex, query, onSuccess, onReject, data) {          const request = objectStoreOrIndex.getAll(query); -        request.onerror = (e) => onReject(/** @type {IDBRequest<import('core').SafeAny[]>} */ (e.target).error, data); -        request.onsuccess = (e) => onSuccess(/** @type {IDBRequest<import('core').SafeAny[]>} */ (e.target).result, data); +        request.onerror = (e) => { +            const target = /** @type {IDBRequest<TResult[]>} */ (e.target); +            onReject(target.error, data); +        }; +        request.onsuccess = (e) => { +            const target = /** @type {IDBRequest<TResult[]>} */ (e.target); +            onSuccess(target.result, data); +        };      }      /** @@ -415,7 +424,9 @@ export class Database {          request.onsuccess = (e) => {              const cursor = /** @type {IDBRequest<?IDBCursorWithValue>} */ (e.target).result;              if (cursor) { -                results.push(cursor.value); +                /** @type {TResult} */ +                const value = cursor.value; +                results.push(value);                  cursor.continue();              } else {                  onSuccess(results, data); diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index 8ef52972..de30f52a 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -22,6 +22,11 @@ import {escapeRegExp, isObject} from '../core/utilities.js';  import {TemplatePatcher} from '../templates/template-patcher.js';  import {JsonSchema} from './json-schema.js'; +// Some type safety rules are disabled for this file since it deals with upgrading an older format +// of the options object to a newer format. SafeAny is used for much of this, since every single +// legacy format does not contain type definitions. +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +  export class OptionsUtil {      constructor() {          /** @type {?TemplatePatcher} */ @@ -119,6 +124,9 @@ export class OptionsUtil {                      }                  });              }); +            if (typeof optionsStr !== 'string') { +                throw new Error('Invalid value for options'); +            }              options = parseJson(optionsStr);          } catch (e) {              // NOP @@ -1197,3 +1205,5 @@ export class OptionsUtil {          });      }  } + +/* eslint-enable @typescript-eslint/no-unsafe-argument */ diff --git a/ext/js/dictionary/dictionary-importer.js b/ext/js/dictionary/dictionary-importer.js index fbfe40d0..16998a8f 100644 --- a/ext/js/dictionary/dictionary-importer.js +++ b/ext/js/dictionary/dictionary-importer.js @@ -734,7 +734,8 @@ export class DictionaryImporter {          const results = [];          for (const file of files) {              const content = await this._getData(file, new TextWriter()); -            const entries = /** @type {unknown} */ (parseJson(content)); +            /** @type {unknown} */ +            const entries = parseJson(content);              startIndex = progressData.index;              this._progress(); @@ -748,8 +749,8 @@ export class DictionaryImporter {              this._progress();              if (Array.isArray(entries)) { -                for (const entry of entries) { -                    results.push(convertEntry(/** @type {TEntry} */ (entry), dictionaryTitle)); +                for (const entry of /** @type {TEntry[]} */ (entries)) { +                    results.push(convertEntry(entry, dictionaryTitle));                  }              }          } diff --git a/ext/js/display/display-anki.js b/ext/js/display/display-anki.js index fb7bd85f..9a6b96c7 100644 --- a/ext/js/display/display-anki.js +++ b/ext/js/display/display-anki.js @@ -376,6 +376,7 @@ export class DisplayAnki {      _updateAdderButtons(dictionaryEntryDetails) {          const displayTags = this._displayTags;          for (let i = 0, ii = dictionaryEntryDetails.length; i < ii; ++i) { +            /** @type {?Set<number>} */              let allNoteIds = null;              for (const {mode, canAdd, noteIds, noteInfos, ankiError} of dictionaryEntryDetails[i].modeMap.values()) {                  const button = this._adderButtonFind(i, mode); diff --git a/ext/js/display/display-history.js b/ext/js/display/display-history.js index 67690219..bc4f1539 100644 --- a/ext/js/display/display-history.js +++ b/ext/js/display/display-history.js @@ -37,9 +37,15 @@ export class DisplayHistory extends EventDispatcher {          this._historyMap = new Map();          const historyState = history.state; -        const {id, state} = isObject(historyState) ? historyState : {id: null, state: null}; +        const {id, state} = ( +            typeof historyState === 'object' && historyState !== null ? +            historyState : +            {id: null, state: null} +        ); +        /** @type {?import('display-history').EntryState} */ +        const stateObject = typeof state === 'object' || state === null ? state : null;          /** @type {import('display-history').Entry} */ -        this._current = this._createHistoryEntry(id, location.href, state, null, null); +        this._current = this._createHistoryEntry(id, location.href, stateObject, null, null);      }      /** @type {?import('display-history').EntryState} */ diff --git a/ext/js/display/display.js b/ext/js/display/display.js index 750e0d69..f6efb5ac 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -1934,20 +1934,28 @@ export class Display extends EventDispatcher {          this._hotkeyHandler.setHotkeys(this._pageType, options.inputs.hotkeys);      } -    /** */ -    async _closeTab() { -        const tab = await new Promise((resolve, reject) => { +    /** +     * @returns {Promise<?chrome.tabs.Tab>} +     */ +    _getCurrentTab() { +        return new Promise((resolve, reject) => {              chrome.tabs.getCurrent((result) => {                  const e = chrome.runtime.lastError;                  if (e) {                      reject(new Error(e.message));                  } else { -                    resolve(result); +                    resolve(typeof result !== 'undefined' ? result : null);                  }              });          }); -        const tabId = tab.id; -        await /** @type {Promise<void>} */ (new Promise((resolve, reject) => { +    } + +    /** +     * @param {number} tabId +     * @returns {Promise<void>} +     */ +    _removeTab(tabId) { +        return new Promise((resolve, reject) => {              chrome.tabs.remove(tabId, () => {                  const e = chrome.runtime.lastError;                  if (e) { @@ -1956,7 +1964,16 @@ export class Display extends EventDispatcher {                      resolve();                  }              }); -        })); +        }); +    } + +    /** */ +    async _closeTab() { +        const tab = await this._getCurrentTab(); +        if (tab === null) { return; } +        const tabId = tab.id; +        if (typeof tabId === 'undefined') { return; } +        await this._removeTab(tabId);      }      /** */ diff --git a/ext/js/dom/text-source-generator.js b/ext/js/dom/text-source-generator.js index 68bf036a..c4711411 100644 --- a/ext/js/dom/text-source-generator.js +++ b/ext/js/dom/text-source-generator.js @@ -462,6 +462,7 @@ export class TextSourceGenerator {       * @returns {?Range}       */      _caretPositionFromPointNormalizeStyles(x, y, nextElement) { +        /** @type {Map<Element, ?string>} */          const previousStyles = new Map();          try {              while (true) { @@ -490,7 +491,7 @@ export class TextSourceGenerator {                          // Elements with user-select: all will return the element                          // instead of a text point inside the element.                          if (this._isElementUserSelectAll(/** @type {Element} */ (node))) { -                            if (previousStyles.has(node)) { +                            if (previousStyles.has(/** @type {Element} */ (node))) {                                  // Recursive                                  return null;                              } @@ -524,6 +525,7 @@ export class TextSourceGenerator {       * @returns {?Range}       */      _caretRangeFromPointExt(x, y, elements, normalizeCssZoom) { +        /** @type {?Map<Element, ?string>} */          let previousStyles = null;          try {              let i = 0; diff --git a/ext/js/general/regex-util.js b/ext/js/general/regex-util.js index f6eca3b6..e0982154 100644 --- a/ext/js/general/regex-util.js +++ b/ext/js/general/regex-util.js @@ -46,7 +46,9 @@ export function applyTextReplacement(text, sourceMap, pattern, replacement) {          pattern.lastIndex += delta;          if (actualReplacementLength > 0) { -            sourceMap.insert(index, ...(new Array(actualReplacementLength).fill(0))); +            /** @type {number[]} */ +            const zeroes = new Array(actualReplacementLength).fill(0); +            sourceMap.insert(index, ...zeroes);              sourceMap.combine(index - 1 + actualReplacementLength, matchText.length);          } else {              sourceMap.combine(index, matchText.length); @@ -65,7 +67,13 @@ export function applyTextReplacement(text, sourceMap, pattern, replacement) {  export function applyMatchReplacement(replacement, match) {      const pattern = matchReplacementPattern;      pattern.lastIndex = 0; -    return replacement.replace(pattern, (g0, g1, g2) => { +    /** +     * @param {string} g0 +     * @param {string} g1 +     * @param {string} g2 +     * @returns {string} +     */ +    const replacer = (g0, g1, g2) => {          if (typeof g1 !== 'undefined') {              const matchIndex = Number.parseInt(g1, 10);              if (matchIndex >= 1 && matchIndex <= match.length) { @@ -87,5 +95,6 @@ export function applyMatchReplacement(replacement, match) {              }          }          return g0; -    }); +    }; +    return replacement.replace(pattern, replacer);  } diff --git a/ext/js/input/hotkey-help-controller.js b/ext/js/input/hotkey-help-controller.js index dbb430dc..1fa9372f 100644 --- a/ext/js/input/hotkey-help-controller.js +++ b/ext/js/input/hotkey-help-controller.js @@ -89,10 +89,10 @@ export class HotkeyHelpController {      // Private      /** -     * @param {Map<string, string>} commandMap +     * @returns {Promise<chrome.commands.Command[]>}       */ -    async _setupGlobalCommands(commandMap) { -        const commands = await new Promise((resolve, reject) => { +    _getAllCommands() { +        return new Promise((resolve, reject) => {              if (!(isObject(chrome.commands) && typeof chrome.commands.getAll === 'function')) {                  resolve([]);                  return; @@ -107,10 +107,17 @@ export class HotkeyHelpController {                  }              });          }); +    } + +    /** +     * @param {Map<string, string>} commandMap +     */ +    async _setupGlobalCommands(commandMap) { +        const commands = await this._getAllCommands();          commandMap.clear();          for (const {name, shortcut} of commands) { -            if (shortcut.length === 0) { continue; } +            if (typeof name !== 'string' || typeof shortcut !== 'string' || shortcut.length === 0) { continue; }              const {key, modifiers} = this._hotkeyUtil.convertCommandToInput(shortcut);              commandMap.set(name, this._hotkeyUtil.getInputDisplayValue(key, modifiers));          } diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js index 568c12bd..b10af226 100644 --- a/ext/js/language/translator.js +++ b/ext/js/language/translator.js @@ -137,6 +137,7 @@ export class Translator {              text = this._getJapaneseOnlyText(text);          }          const {enabledDictionaryMap} = options; +        /** @type {Set<string>} */          const kanjiUnique = new Set();          for (const c of text) {              kanjiUnique.add(c); @@ -702,6 +703,7 @@ export class Translator {       * @returns {import('dictionary').TermDictionaryEntry[]}       */      _groupDictionaryEntriesByHeadword(dictionaryEntries, tagAggregator) { +        /** @type {Map<string, import('dictionary').TermDictionaryEntry[]>} */          const groups = new Map();          for (const dictionaryEntry of dictionaryEntries) {              const {inflectionRuleChainCandidates, headwords: [{term, reading}]} = dictionaryEntry; @@ -1039,8 +1041,11 @@ export class Translator {       * @param {TranslatorTagAggregator} tagAggregator       */      async _addTermMeta(dictionaryEntries, enabledDictionaryMap, tagAggregator) { +        /** @type {Map<string, Map<string, {headwordIndex: number, pronunciations: import('dictionary').TermPronunciation[], frequencies: import('dictionary').TermFrequency[]}[]>>} */          const headwordMap = new Map(); +        /** @type {string[]} */          const headwordMapKeys = []; +        /** @type {Map<string, {headwordIndex: number, pronunciations: import('dictionary').TermPronunciation[], frequencies: import('dictionary').TermFrequency[]}[]>[]} */          const headwordReadingMaps = [];          for (const {headwords, pronunciations, frequencies} of dictionaryEntries) { diff --git a/ext/js/media/audio-downloader.js b/ext/js/media/audio-downloader.js index d3bec75a..4cb3eb81 100644 --- a/ext/js/media/audio-downloader.js +++ b/ext/js/media/audio-downloader.js @@ -304,7 +304,17 @@ export class AudioDownloader {              throw new Error('No custom URL defined');          }          const data = {term, reading}; -        return url.replace(/\{([^}]*)\}/g, (m0, m1) => (Object.prototype.hasOwnProperty.call(data, m1) ? `${data[/** @type {'term'|'reading'} */ (m1)]}` : m0)); +        /** +         * @param {string} m0 +         * @param {string} m1 +         * @returns {string} +         */ +        const replacer = (m0, m1) => ( +            Object.prototype.hasOwnProperty.call(data, m1) ? +            `${data[/** @type {'term'|'reading'} */ (m1)]}` : +            m0 +        ); +        return url.replace(/\{([^}]*)\}/g, replacer);      }      /** diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index a6760268..a0e0c196 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -548,6 +548,7 @@ export class DictionaryController {              optionsFull = await settingsController.getOptionsFull();          } +        /** @type {Set<string>} */          const installedDictionaries = new Set();          for (const {title} of dictionaries) {              installedDictionaries.add(title); diff --git a/ext/js/templates/sandbox/anki-template-renderer.js b/ext/js/templates/sandbox/anki-template-renderer.js index ad2b0042..d2bb25d5 100644 --- a/ext/js/templates/sandbox/anki-template-renderer.js +++ b/ext/js/templates/sandbox/anki-template-renderer.js @@ -674,6 +674,7 @@ export class AnkiTemplateRenderer {       */      _formatGlossary(args, _context, options) {          const [dictionary, content] = /** @type {[dictionary: string, content: import('dictionary-data').TermGlossaryContent]} */ (args); +        /** @type {import('anki-templates').NoteData} */          const data = options.data.root;          if (typeof content === 'string') { return this._stringToMultiLineHtml(this._escape(content)); }          if (!(typeof content === 'object' && content !== null)) { return ''; } @@ -713,31 +714,42 @@ export class AnkiTemplateRenderer {       * @type {import('template-renderer').HelperFunction<boolean>}       */      _hasMedia(args, _context, options) { -        return this._mediaProvider.hasMedia(options.data.root, args, options.hash); +        /** @type {import('anki-templates').NoteData} */ +        const data = options.data.root; +        return this._mediaProvider.hasMedia(data, args, options.hash);      }      /**       * @type {import('template-renderer').HelperFunction<?string>}       */      _getMedia(args, _context, options) { -        return this._mediaProvider.getMedia(options.data.root, args, options.hash); +        /** @type {import('anki-templates').NoteData} */ +        const data = options.data.root; +        return this._mediaProvider.getMedia(data, args, options.hash);      }      /**       * @type {import('template-renderer').HelperFunction<string>}       */      _pronunciation(_args, _context, options) { -        let {format, reading, downstepPosition, nasalPositions, devoicePositions} = options.hash; - -        if (typeof reading !== 'string' || reading.length === 0) { return ''; } -        if (typeof downstepPosition !== 'number') { return ''; } -        if (!Array.isArray(nasalPositions)) { nasalPositions = []; } -        if (!Array.isArray(devoicePositions)) { devoicePositions = []; } +        const {format, reading, downstepPosition} = options.hash; + +        if ( +            typeof reading !== 'string' || +            reading.length === 0 || +            typeof downstepPosition !== 'number' +        ) { +            return ''; +        }          const morae = getKanaMorae(reading);          switch (format) {              case 'text': +            { +                const nasalPositions = this._getValidNumberArray(options.hash.nasalPositions); +                const devoicePositions = this._getValidNumberArray(options.hash.devoicePositions);                  return this._getPronunciationHtml(createPronunciationText(morae, downstepPosition, nasalPositions, devoicePositions)); +            }              case 'graph':                  return this._getPronunciationHtml(createPronunciationGraph(morae, downstepPosition));              case 'position': @@ -748,6 +760,20 @@ export class AnkiTemplateRenderer {      }      /** +     * @param {unknown} value +     * @returns {number[]} +     */ +    _getValidNumberArray(value) { +        const result = []; +        if (Array.isArray(value)) { +            for (const item of value) { +                if (typeof item === 'number') { result.push(item); } +            } +        } +        return result; +    } + +    /**       * @type {import('template-renderer').HelperFunction<string>}       */      _hiragana(args, context, options) { diff --git a/test/document-util.test.js b/test/document-util.test.js index c2b2edca..0f541e12 100644 --- a/test/document-util.test.js +++ b/test/document-util.test.js @@ -206,12 +206,15 @@ describe('Document utility tests', () => {                      // Sentence info                      const terminatorString = '…。..??!!'; +                    /** @type {import('text-scanner').SentenceTerminatorMap} */                      const terminatorMap = new Map();                      for (const char of terminatorString) {                          terminatorMap.set(char, [false, true]);                      }                      const quoteArray = [['「', '」'], ['『', '』'], ['\'', '\''], ['"', '"']]; +                    /** @type {import('text-scanner').SentenceForwardQuoteMap} */                      const forwardQuoteMap = new Map(); +                    /** @type {import('text-scanner').SentenceBackwardQuoteMap} */                      const backwardQuoteMap = new Map();                      for (const [char1, char2] of quoteArray) {                          forwardQuoteMap.set(char1, [char2, false]); diff --git a/test/dom-text-scanner.test.js b/test/dom-text-scanner.test.js index 1ec7cab7..b366cadd 100644 --- a/test/dom-text-scanner.test.js +++ b/test/dom-text-scanner.test.js @@ -88,10 +88,16 @@ function createAbsoluteGetComputedStyle(window) {              get: (target, property) => {                  let result = /** @type {import('core').SafeAny} */ (target)[property];                  if (typeof result === 'string') { -                    result = result.replace(/([-+]?\d(?:\.\d)?(?:[eE][-+]?\d+)?)em/g, (g0, g1) => { +                    /** +                     * @param {string} g0 +                     * @param {string} g1 +                     * @returns {string} +                     */ +                    const replacer = (g0, g1) => {                          const fontSize = getComputedFontSizeInPixels(window, getComputedStyleOld, element);                          return `${Number.parseFloat(g1) * fontSize}px`; -                    }); +                    }; +                    result = result.replace(/([-+]?\d(?:\.\d)?(?:[eE][-+]?\d+)?)em/g, replacer);                  }                  return result;              } diff --git a/test/eslint-config.test.js b/test/eslint-config.test.js index bddde695..47e347dd 100644 --- a/test/eslint-config.test.js +++ b/test/eslint-config.test.js @@ -55,8 +55,8 @@ function removeLibraryDependencies(dependencies) {  }  /** - * @param {{[key: string]: boolean}|undefined} env1 - * @param {{[key: string]: boolean}} env2 + * @param {import('test/eslint-config').MinimalEslintConfigEnv|undefined} env1 + * @param {import('test/eslint-config').MinimalEslintConfigEnv} env2   * @returns {boolean}   */  function envMatches(env1, env2) { @@ -92,7 +92,7 @@ const targets = [          paths: [              'ext/js/templates/sandbox/template-renderer-frame-main.js'          ], -        /** @type {{[key: string]: boolean}} */ +        /** @type {import('test/eslint-config').MinimalEslintConfigEnv} */          env: {              webextensions: false          } @@ -102,7 +102,7 @@ const targets = [          paths: [              'ext/js/dictionary/dictionary-worker-main.js'          ], -        /** @type {{[key: string]: boolean}} */ +        /** @type {import('test/eslint-config').MinimalEslintConfigEnv} */          env: {              browser: false,              worker: true @@ -113,7 +113,7 @@ const targets = [          paths: [              'ext/js/background/background-main.js'          ], -        /** @type {{[key: string]: boolean}} */ +        /** @type {import('test/eslint-config').MinimalEslintConfigEnv} */          env: {              browser: false,              serviceworker: true @@ -123,7 +123,7 @@ const targets = [  describe('Eslint configuration', () => {      const eslintConfigPath = '.eslintrc.json'; -    /** @type {import('core').SafeAny} */ +    /** @type {import('test/eslint-config').MinimalEslintConfig} */      const eslintConfig = parseJson(readFileSync(join(rootDir, eslintConfigPath), 'utf8'));      describe.each(targets)('Environment is $name', ({name, paths, env}) => {          test('Entry exists', async ({expect}) => { diff --git a/types/ext/template-renderer.d.ts b/types/ext/template-renderer.d.ts index 3f63ea42..335dc61a 100644 --- a/types/ext/template-renderer.d.ts +++ b/types/ext/template-renderer.d.ts @@ -57,7 +57,7 @@ export type HelperOptionsFunction<TResult = unknown> = (context: unknown) => TRe  export type HelperOptions = {      fn?: HelperOptionsFunction;      inverse?: HelperOptionsFunction; -    hash: Core.SafeAny; +    hash: Core.SerializableObject;      data?: Core.SafeAny;  }; diff --git a/types/test/eslint-config.d.ts b/types/test/eslint-config.d.ts new file mode 100644 index 00000000..e93343b8 --- /dev/null +++ b/types/test/eslint-config.d.ts @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024  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/>. + */ + +export type MinimalEslintConfig = { +    overrides: MinimalEslintConfigOverride[]; +}; + +export type MinimalEslintConfigOverride = { +    env?: MinimalEslintConfigEnv; +    files?: string[]; +}; + +export type MinimalEslintConfigEnv = { +    [key: string]: boolean; +}; |