aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.eslintrc.json2
-rw-r--r--ext/js/background/backend.js8
-rw-r--r--ext/js/data/database.js21
-rw-r--r--ext/js/data/options-util.js10
-rw-r--r--ext/js/dictionary/dictionary-importer.js7
-rw-r--r--ext/js/display/display-anki.js1
-rw-r--r--ext/js/display/display-history.js10
-rw-r--r--ext/js/display/display.js31
-rw-r--r--ext/js/dom/text-source-generator.js4
-rw-r--r--ext/js/general/regex-util.js15
-rw-r--r--ext/js/input/hotkey-help-controller.js15
-rw-r--r--ext/js/language/translator.js5
-rw-r--r--ext/js/media/audio-downloader.js12
-rw-r--r--ext/js/pages/settings/dictionary-controller.js1
-rw-r--r--ext/js/templates/sandbox/anki-template-renderer.js42
-rw-r--r--test/document-util.test.js3
-rw-r--r--test/dom-text-scanner.test.js10
-rw-r--r--test/eslint-config.test.js12
-rw-r--r--types/ext/template-renderer.d.ts2
-rw-r--r--types/test/eslint-config.d.ts29
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;
+};