aboutsummaryrefslogtreecommitdiff
path: root/test/eslint-config.test.js
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2024-02-08 06:52:06 -0500
committerGitHub <noreply@github.com>2024-02-08 11:52:06 +0000
commitd4381831209dfbbbddd6d238c68461c9601573e2 (patch)
tree87839d80f838464ff92ebc7fa652029b9329cc96 /test/eslint-config.test.js
parent725a90dd6570044a3df6631051aaab8b026ca6c2 (diff)
Update eslint (#638)
* Add json test * Update vscode settings to better handle json * Collapse eslint rules for easier readability * Reorganize * Update no-multi-spaces rule for JSON * Rules updates * Switch to @stylistic/eslint-plugin * Update deprecated stylistic rules * Group stylistic rules * Simplify rules * Move eslint env overrides to end of file * Add test * Move promiseAnimationFrame to separate file * Remove unneeded eslint disable * Remove unneeded
Diffstat (limited to 'test/eslint-config.test.js')
-rw-r--r--test/eslint-config.test.js172
1 files changed, 172 insertions, 0 deletions
diff --git a/test/eslint-config.test.js b/test/eslint-config.test.js
new file mode 100644
index 00000000..bddde695
--- /dev/null
+++ b/test/eslint-config.test.js
@@ -0,0 +1,172 @@
+/*
+ * 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/>.
+ */
+
+import esbuild from 'esbuild';
+import {readFileSync} from 'fs';
+import {dirname, join} from 'path';
+import {fileURLToPath} from 'url';
+import {describe, test} from 'vitest';
+import {parseJson} from '../dev/json.js';
+
+const rootDir = join(dirname(fileURLToPath(import.meta.url)), '..');
+
+/**
+ * @param {string[]} scriptPaths
+ * @returns {Promise<string[]>}
+ */
+async function getDependencies(scriptPaths) {
+ const v = await esbuild.build({
+ entryPoints: scriptPaths,
+ bundle: true,
+ minify: false,
+ sourcemap: true,
+ target: 'es2022',
+ format: 'esm',
+ write: false,
+ metafile: true
+ });
+ const dependencies = Object.keys(v.metafile.inputs);
+ const stringComparer = new Intl.Collator('en-US'); // Invariant locale
+ dependencies.sort((a, b) => stringComparer.compare(a, b));
+ return dependencies;
+}
+
+/**
+ * @param {string[]} dependencies
+ * @returns {string[]}
+ */
+function removeLibraryDependencies(dependencies) {
+ const pattern = /^ext\/lib\//;
+ return dependencies.filter((v) => !pattern.test(v));
+}
+
+/**
+ * @param {{[key: string]: boolean}|undefined} env1
+ * @param {{[key: string]: boolean}} env2
+ * @returns {boolean}
+ */
+function envMatches(env1, env2) {
+ if (typeof env1 !== 'object' || env1 === null) { return false; }
+ const map1 = new Map(Object.entries(env1));
+ const map2 = new Map(Object.entries(env2));
+ if (map1.size !== map2.size) { return false; }
+ for (const [k1, v1] of map1) {
+ if (map2.get(k1) !== v1) { return false; }
+ }
+ return true;
+}
+
+/**
+ * @param {string[]} files1
+ * @param {string[]} files2
+ * @returns {boolean}
+ */
+function filesMatch(files1, files2) {
+ if (!Array.isArray(files1)) { return false; }
+ const set1 = new Set(files1);
+ const set2 = new Set(files2);
+ if (set1.size !== set2.size) { return false; }
+ for (const v of set1) {
+ if (!set2.has(v)) { return false; }
+ }
+ return true;
+}
+
+const targets = [
+ {
+ name: 'sandbox',
+ paths: [
+ 'ext/js/templates/sandbox/template-renderer-frame-main.js'
+ ],
+ /** @type {{[key: string]: boolean}} */
+ env: {
+ webextensions: false
+ }
+ },
+ {
+ name: 'worker',
+ paths: [
+ 'ext/js/dictionary/dictionary-worker-main.js'
+ ],
+ /** @type {{[key: string]: boolean}} */
+ env: {
+ browser: false,
+ worker: true
+ }
+ },
+ {
+ name: 'serviceworker',
+ paths: [
+ 'ext/js/background/background-main.js'
+ ],
+ /** @type {{[key: string]: boolean}} */
+ env: {
+ browser: false,
+ serviceworker: true
+ }
+ }
+];
+
+describe('Eslint configuration', () => {
+ const eslintConfigPath = '.eslintrc.json';
+ /** @type {import('core').SafeAny} */
+ const eslintConfig = parseJson(readFileSync(join(rootDir, eslintConfigPath), 'utf8'));
+ describe.each(targets)('Environment is $name', ({name, paths, env}) => {
+ test('Entry exists', async ({expect}) => {
+ const fullPaths = paths.map((v) => join(rootDir, v));
+ const dependencies = removeLibraryDependencies(await getDependencies(fullPaths));
+
+ let okay = false;
+ const candidates = [];
+ const {overrides} = eslintConfig;
+ for (let i = 0, ii = overrides.length; i < ii; ++i) {
+ const override = overrides[i];
+ if (!envMatches(override.env, env)) { continue; }
+ const {files} = override;
+ if (!Array.isArray(files)) { continue; }
+ candidates.push(i);
+ if (filesMatch(files, dependencies)) {
+ okay = true;
+ break;
+ }
+ }
+
+ if (okay) { return; }
+ switch (candidates.length) {
+ case 0:
+ {
+ const message = `No override found with "${name}" environment: ${JSON.stringify(env)}`;
+ expect(false, message).toStrictEqual(true);
+ }
+ break;
+ case 1:
+ {
+ const index = candidates[0];
+ const message = `Override at index ${index} does not have the expected files for the "${name}" environment.`;
+ expect(overrides[index].files, message).toStrictEqual(dependencies);
+ }
+ break;
+ default:
+ {
+ const message = `No override found with the correct file list for the "${name}" environment; candidate indices: [${candidates.join(', ')}].`;
+ expect([], message).toStrictEqual(dependencies);
+ }
+ break;
+ }
+ });
+ });
+});