From 4da4827bcbcdd1ef163f635d9b29416ff272b0bb Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 12:48:14 -0500 Subject: Add JSDoc type annotations to project (rebased) --- dev/bin/generate-css-json.js | 1 + dev/data-error.js | 35 +++++++ dev/jsconfig.json | 77 +++++++++++++++ dev/lint/global-declarations.js | 157 +++++++++++++++++++++++++++++++ dev/lint/html-scripts.js | 202 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 472 insertions(+) create mode 100644 dev/data-error.js create mode 100644 dev/jsconfig.json create mode 100644 dev/lint/global-declarations.js create mode 100644 dev/lint/html-scripts.js (limited to 'dev') diff --git a/dev/bin/generate-css-json.js b/dev/bin/generate-css-json.js index 48b42c65..73c406e0 100644 --- a/dev/bin/generate-css-json.js +++ b/dev/bin/generate-css-json.js @@ -19,6 +19,7 @@ import fs from 'fs'; import {formatRulesJson, generateRules, getTargets} from '../generate-css-json.js'; +/** */ function main() { for (const {cssFile, overridesCssFile, outputPath} of getTargets()) { const json = formatRulesJson(generateRules(cssFile, overridesCssFile)); diff --git a/dev/data-error.js b/dev/data-error.js new file mode 100644 index 00000000..5034e3fd --- /dev/null +++ b/dev/data-error.js @@ -0,0 +1,35 @@ +/* + * 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 . + */ + +class DataError extends Error { + /** + * @param {string} message + */ + constructor(message) { + super(message); + /** @type {unknown} */ + this._data = void 0; + } + + /** @type {unknown} */ + get data() { return this._data; } + set data(value) { this._data = value; } +} + +module.exports = { + DataError +}; diff --git a/dev/jsconfig.json b/dev/jsconfig.json new file mode 100644 index 00000000..58eba952 --- /dev/null +++ b/dev/jsconfig.json @@ -0,0 +1,77 @@ +{ + "compilerOptions": { + "module": "ES2015", + "target": "ES2022", + "checkJs": true, + "moduleResolution": "node", + "strict": true, + "strictNullChecks": true, + "noImplicitAny": true, + "strictPropertyInitialization": true, + "suppressImplicitAnyIndexErrors": false, + "skipLibCheck": false, + "baseUrl": ".", + "paths": { + "anki-templates": ["../types/ext/anki-templates"], + "anki-templates-internal": ["../types/ext/anki-templates-internal"], + "cache-map": ["../types/ext/cache-map"], + "core": ["../types/ext/core"], + "css-style-applier": ["../types/ext/css-style-applier"], + "database": ["../types/ext/database"], + "deinflector": ["../types/ext/deinflector"], + "dictionary": ["../types/ext/dictionary"], + "dictionary-data": ["../types/ext/dictionary-data"], + "dictionary-data-util": ["../types/ext/dictionary-data-util"], + "dictionary-database": ["../types/ext/dictionary-database"], + "dictionary-importer": ["../types/ext/dictionary-importer"], + "dictionary-importer-media-loader": ["../types/ext/dictionary-importer-media-loader"], + "dynamic-property": ["../types/ext/dynamic-property"], + "error": ["../types/ext/error"], + "event-listener-collection": ["../types/ext/event-listener-collection"], + "japanese-util": ["../types/ext/japanese-util"], + "json-schema": ["../types/ext/json-schema"], + "log": ["../types/ext/log"], + "settings": ["../types/ext/settings"], + "structured-content": ["../types/ext/structured-content"], + "translator": ["../types/ext/translator"], + "translation": ["../types/ext/translation"], + "translation-internal": ["../types/ext/translation-internal"], + "dev/*": ["../types/dev/*"] + }, + "types": [ + "node", + "events", + "browserify", + "jsdom", + "assert", + "css", + "chrome", + "ajv" + ] + }, + "include": [ + "**/*.js", + "../playwright.config.js", + "../ext/js/core.js", + "../ext/js/core/extension-error.js", + "../ext/js/data/database.js", + "../ext/js/data/json-schema.js", + "../ext/js/general/cache-map.js", + "../ext/js/data/sandbox/anki-note-data-creator.js", + "../ext/js/general/cache-map.js", + "../ext/js/general/regex-util.js", + "../ext/js/general/text-source-map.js", + "../ext/js/language/deinflector.js", + "../ext/js/language/dictionary-importer.js", + "../ext/js/language/dictionary-database.js", + "../ext/js/language/sandbox/dictionary-data-util.js", + "../ext/js/language/sandbox/japanese-util.js", + "../ext/js/language/translator.js", + "../ext/js/media/media-util.js", + "../types/dev/**/*.ts", + "../types/other/web-set-timeout.d.ts" + ], + "exclude": [ + "../node_modules" + ] +} \ No newline at end of file diff --git a/dev/lint/global-declarations.js b/dev/lint/global-declarations.js new file mode 100644 index 00000000..648ad368 --- /dev/null +++ b/dev/lint/global-declarations.js @@ -0,0 +1,157 @@ +/* + * 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 . + */ + +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); +const {getAllFiles} = require('../util'); + + +/** + * @param {string} string + * @returns {string} + */ +function escapeRegExp(string) { + return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); +} + +/** + * @param {string} string + * @param {RegExp} pattern + * @returns {number} + */ +function countOccurences(string, pattern) { + return (string.match(pattern) || []).length; +} + +/** + * @param {string} string + * @returns {'\r'|'\n'|'\r\n'} + */ +function getNewline(string) { + const count1 = countOccurences(string, /(?:^|[^\r])\n/g); + const count2 = countOccurences(string, /\r\n/g); + const count3 = countOccurences(string, /\r(?:[^\n]|$)/g); + if (count2 > count1) { + return (count3 > count2) ? '\r' : '\r\n'; + } else { + return (count3 > count1) ? '\r' : '\n'; + } +} + +/** + * @param {string} string + * @param {string} substring + * @returns {number} + */ +function getSubstringCount(string, substring) { + let count = 0; + const pattern = new RegExp(`\\b${escapeRegExp(substring)}\\b`, 'g'); + while (true) { + const match = pattern.exec(string); + if (match === null) { break; } + ++count; + } + return count; +} + + +/** + * @param {string} fileName + * @param {boolean} fix + * @returns {boolean} + */ +function validateGlobals(fileName, fix) { + const pattern = /\/\*\s*global\s+([\w\W]*?)\*\//g; + const trimPattern = /^[\s,*]+|[\s,*]+$/g; + const splitPattern = /[\s,*]+/; + const source = fs.readFileSync(fileName, {encoding: 'utf8'}); + let match; + let first = true; + let endIndex = 0; + let newSource = ''; + const allGlobals = []; + const newline = getNewline(source); + while ((match = pattern.exec(source)) !== null) { + if (!first) { + console.error(`Encountered more than one global declaration in ${fileName}`); + return false; + } + first = false; + + const parts = match[1].replace(trimPattern, '').split(splitPattern); + parts.sort(); + + const actual = match[0]; + const expected = `/* global${parts.map((v) => `${newline} * ${v}`).join('')}${newline} */`; + + try { + assert.strictEqual(actual, expected); + } catch (e) { + console.error(`Global declaration error encountered in ${fileName}:`); + console.error(e instanceof Error ? e.message : `${e}`); + if (!fix) { + return false; + } + } + + newSource += source.substring(0, match.index); + newSource += expected; + endIndex = match.index + match[0].length; + + allGlobals.push(...parts); + } + + newSource += source.substring(endIndex); + + // This is an approximate check to see if a global variable is unused. + // If the global appears in a comment, string, or similar, the check will pass. + let errorCount = 0; + for (const global of allGlobals) { + if (getSubstringCount(newSource, global) <= 1) { + console.error(`Global variable ${global} appears to be unused in ${fileName}`); + ++errorCount; + } + } + + if (fix) { + fs.writeFileSync(fileName, newSource, {encoding: 'utf8'}); + } + + return errorCount === 0; +} + + +/** */ +function main() { + const fix = (process.argv.length >= 2 && process.argv[2] === '--fix'); + const directory = path.resolve(__dirname, '..', '..', 'ext'); + const pattern = /\.js$/; + const ignorePattern = /^lib[\\/]/; + const fileNames = getAllFiles(directory, (f) => pattern.test(f) && !ignorePattern.test(f)); + for (const fileName of fileNames) { + if (!validateGlobals(path.join(directory, fileName), fix)) { + process.exit(-1); + return; + } + } + process.exit(0); +} + + +if (require.main === module) { main(); } diff --git a/dev/lint/html-scripts.js b/dev/lint/html-scripts.js new file mode 100644 index 00000000..da8c2c71 --- /dev/null +++ b/dev/lint/html-scripts.js @@ -0,0 +1,202 @@ +/* + * 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 . + */ + +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); +const {JSDOM} = require('jsdom'); +const {getAllFiles} = require('../util'); + + +/** + * @param {string} fileName + * @returns {?fs.Stats} + */ +function lstatSyncSafe(fileName) { + try { + return fs.lstatSync(fileName); + } catch (e) { + return null; + } +} + +/** + * @param {string} src + * @param {string} fileName + * @param {string} extDir + */ +function validatePath(src, fileName, extDir) { + assert.ok(typeof src === 'string', `