From 1ced9aafc00c10992bab8bd3f1b6b1397f05b7b9 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 19 Dec 2023 00:33:38 -0500 Subject: Make JSON.parse usage safer (#373) * Make JSON.parse usage safer * Fix any type * Add readResponseJson * Use readResponseJson * Additional updates * Rename files * Add types --- test/anki-note-builder.test.js | 8 +++++--- test/database.test.js | 7 +++++-- test/deinflector.test.js | 4 +++- test/dom-text-scanner.test.js | 18 ++++++++++++------ test/fixtures/translator-test.js | 6 ++++-- test/json-schema.test.js | 3 ++- test/options-util.test.js | 3 ++- test/translator.test.js | 11 +++++++---- 8 files changed, 40 insertions(+), 20 deletions(-) (limited to 'test') diff --git a/test/anki-note-builder.test.js b/test/anki-note-builder.test.js index cc136957..bdf3f8e4 100644 --- a/test/anki-note-builder.test.js +++ b/test/anki-note-builder.test.js @@ -22,6 +22,7 @@ import {readFileSync} from 'fs'; import {fileURLToPath} from 'node:url'; import path from 'path'; import {describe, vi} from 'vitest'; +import {parseJson} from '../dev/json.js'; import {AnkiNoteBuilder} from '../ext/js/data/anki-note-builder.js'; import {JapaneseUtil} from '../ext/js/language/sandbox/japanese-util.js'; import {createTranslatorTest} from './fixtures/translator-test.js'; @@ -170,11 +171,12 @@ async function getRenderResults(dictionaryEntries, type, mode, template, expect) const testInputsFilePath = path.join(dirname, 'data/translator-test-inputs.json'); -/** @type {import('test/anki-note-builder').TranslatorTestInputs} */ -const {optionsPresets, tests} = JSON.parse(readFileSync(testInputsFilePath, {encoding: 'utf8'})); +/** @type {import('test/translator').TranslatorTestInputs} */ +const {optionsPresets, tests} = parseJson(readFileSync(testInputsFilePath, {encoding: 'utf8'})); const testResults1FilePath = path.join(dirname, 'data/anki-note-builder-test-results.json'); -const expectedResults1 = JSON.parse(readFileSync(testResults1FilePath, {encoding: 'utf8'})); +/** @type {import('test/translator').AnkiNoteBuilderTestResults} */ +const expectedResults1 = parseJson(readFileSync(testResults1FilePath, {encoding: 'utf8'})); const template = readFileSync(path.join(dirname, '../ext/data/templates/default-anki-field-templates.handlebars'), {encoding: 'utf8'}); diff --git a/test/database.test.js b/test/database.test.js index 2fdea99c..702de9f8 100644 --- a/test/database.test.js +++ b/test/database.test.js @@ -20,6 +20,7 @@ import {IDBFactory, IDBKeyRange} from 'fake-indexeddb'; import {fileURLToPath} from 'node:url'; import path from 'path'; import {beforeEach, describe, expect, test, vi} from 'vitest'; +import {parseJson} from '../dev/json.js'; import {createDictionaryArchive} from '../dev/util.js'; import {DictionaryDatabase} from '../ext/js/language/dictionary-database.js'; import {DictionaryImporter} from '../ext/js/language/dictionary-importer.js'; @@ -109,7 +110,8 @@ async function testDatabase1() { // Load dictionary data const testDictionary = createTestDictionaryArchive('valid-dictionary1'); const testDictionarySource = await testDictionary.generateAsync({type: 'arraybuffer'}); - const testDictionaryIndex = JSON.parse(await testDictionary.files['index.json'].async('string')); + /** @type {import('dictionary-data').Index} */ + const testDictionaryIndex = parseJson(await testDictionary.files['index.json'].async('string')); const title = testDictionaryIndex.title; const titles = new Map([ @@ -852,7 +854,8 @@ async function testDatabase2() { // Load dictionary data const testDictionary = createTestDictionaryArchive('valid-dictionary1'); const testDictionarySource = await testDictionary.generateAsync({type: 'arraybuffer'}); - const testDictionaryIndex = JSON.parse(await testDictionary.files['index.json'].async('string')); + /** @type {import('dictionary-data').Index} */ + const testDictionaryIndex = parseJson(await testDictionary.files['index.json'].async('string')); const title = testDictionaryIndex.title; const titles = new Map([ diff --git a/test/deinflector.test.js b/test/deinflector.test.js index adb347f1..1d7a39cf 100644 --- a/test/deinflector.test.js +++ b/test/deinflector.test.js @@ -22,6 +22,7 @@ import fs from 'fs'; import {fileURLToPath} from 'node:url'; import path from 'path'; import {describe, expect, test} from 'vitest'; +import {parseJson} from '../dev/json.js'; import {Deinflector} from '../ext/js/language/deinflector.js'; const dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -929,7 +930,8 @@ function testDeinflections() { } ]; - const deinflectionReasons = JSON.parse(fs.readFileSync(path.join(dirname, '..', 'ext', 'data/deinflect.json'), {encoding: 'utf8'})); + /** @type {import('deinflector').ReasonsRaw} */ + const deinflectionReasons = parseJson(fs.readFileSync(path.join(dirname, '..', 'ext', 'data/deinflect.json'), {encoding: 'utf8'})); const deinflector = new Deinflector(deinflectionReasons); describe('deinflections', () => { diff --git a/test/dom-text-scanner.test.js b/test/dom-text-scanner.test.js index d62e334d..f53e326d 100644 --- a/test/dom-text-scanner.test.js +++ b/test/dom-text-scanner.test.js @@ -19,6 +19,7 @@ import {fileURLToPath} from 'node:url'; import path from 'path'; import {describe, expect} from 'vitest'; +import {parseJson} from '../dev/json.js'; import {DOMTextScanner} from '../ext/js/dom/dom-text-scanner.js'; import {createDomTest} from './fixtures/dom-test.js'; @@ -109,28 +110,33 @@ describe('DOMTextScanner', () => { window.getComputedStyle = createAbsoluteGetComputedStyle(window); for (const testElement of /** @type {NodeListOf} */ (document.querySelectorAll('y-test'))) { - let testData = JSON.parse(/** @type {string} */ (testElement.dataset.testData)); + /** @type {import('test/dom-text-scanner').TestData|import('test/dom-text-scanner').TestData[]} */ + let testData = parseJson(/** @type {string} */ (testElement.dataset.testData)); if (!Array.isArray(testData)) { testData = [testData]; } for (const testDataItem of testData) { - let { - node, + const { + node: nodeSelector, offset, length, forcePreserveWhitespace, generateLayoutContent, reversible, expected: { - node: expectedNode, + node: expectedNodeSelector, offset: expectedOffset, content: expectedContent, remainder: expectedRemainder } } = testDataItem; - node = querySelectorTextNode(testElement, node); - expectedNode = querySelectorTextNode(testElement, expectedNode); + const node = querySelectorTextNode(testElement, nodeSelector); + const expectedNode = querySelectorTextNode(testElement, expectedNodeSelector); + + expect(node).not.toEqual(null); + expect(expectedNode).not.toEqual(null); + if (node === null || expectedNode === null) { continue; } // Standard test { diff --git a/test/fixtures/translator-test.js b/test/fixtures/translator-test.js index b17c37d9..cb1a3ef5 100644 --- a/test/fixtures/translator-test.js +++ b/test/fixtures/translator-test.js @@ -21,6 +21,7 @@ import {readFileSync} from 'fs'; import {fileURLToPath, pathToFileURL} from 'node:url'; import {dirname, join, resolve} from 'path'; import {expect, vi} from 'vitest'; +import {parseJson} from '../../dev/json.js'; import {createDictionaryArchive} from '../../dev/util.js'; import {AnkiNoteDataCreator} from '../../ext/js/data/sandbox/anki-note-data-creator.js'; import {DictionaryDatabase} from '../../ext/js/language/dictionary-database.js'; @@ -60,7 +61,7 @@ async function fetch(url) { status: 200, statusText: 'OK', text: async () => content.toString('utf8'), - json: async () => JSON.parse(content.toString('utf8')) + json: async () => parseJson(content.toString('utf8')) }; } @@ -96,7 +97,8 @@ async function createTranslatorContext(dictionaryDirectory, dictionaryName) { // Setup translator const japaneseUtil = new JapaneseUtil(null); const translator = new Translator({japaneseUtil, database: dictionaryDatabase}); - const deinflectionReasons = JSON.parse(readFileSync(deinflectionReasonsPath, {encoding: 'utf8'})); + /** @type {import('deinflector').ReasonsRaw} */ + const deinflectionReasons = parseJson(readFileSync(deinflectionReasonsPath, {encoding: 'utf8'})); translator.prepare(deinflectionReasons); // Assign properties diff --git a/test/json-schema.test.js b/test/json-schema.test.js index a93e5002..e6817d23 100644 --- a/test/json-schema.test.js +++ b/test/json-schema.test.js @@ -19,6 +19,7 @@ /* eslint-disable no-multi-spaces */ import {expect, test} from 'vitest'; +import {parseJson} from '../dev/json.js'; import {JsonSchema} from '../ext/js/data/json-schema.js'; /** @@ -54,7 +55,7 @@ function createProxy(schema, value) { * @returns {T} */ function clone(value) { - return JSON.parse(JSON.stringify(value)); + return parseJson(JSON.stringify(value)); } diff --git a/test/options-util.test.js b/test/options-util.test.js index f2ffa36c..41185756 100644 --- a/test/options-util.test.js +++ b/test/options-util.test.js @@ -22,6 +22,7 @@ import fs from 'fs'; import url, {fileURLToPath} from 'node:url'; import path from 'path'; import {expect, test, vi} from 'vitest'; +import {parseJson} from '../dev/json.js'; import {OptionsUtil} from '../ext/js/data/options-util.js'; import {TemplatePatcher} from '../ext/js/templates/template-patcher.js'; @@ -40,7 +41,7 @@ async function fetch(url2) { status: 200, statusText: 'OK', text: async () => Promise.resolve(content.toString('utf8')), - json: async () => Promise.resolve(JSON.parse(content.toString('utf8'))) + json: async () => Promise.resolve(parseJson(content.toString('utf8'))) }; } /** @type {import('dev/vm').PseudoChrome} */ diff --git a/test/translator.test.js b/test/translator.test.js index 59887d7e..42a9076e 100644 --- a/test/translator.test.js +++ b/test/translator.test.js @@ -20,6 +20,7 @@ import {readFileSync} from 'fs'; import {fileURLToPath} from 'node:url'; import path from 'path'; import {describe} from 'vitest'; +import {parseJson} from '../dev/json.js'; import {createTranslatorTest} from './fixtures/translator-test.js'; import {createTestAnkiNoteData} from './utilities/anki.js'; import {createFindOptions} from './utilities/translator.js'; @@ -27,14 +28,16 @@ import {createFindOptions} from './utilities/translator.js'; const dirname = path.dirname(fileURLToPath(import.meta.url)); const testInputsFilePath = path.join(dirname, 'data/translator-test-inputs.json'); -/** @type {import('test/anki-note-builder').TranslatorTestInputs} */ -const {optionsPresets, tests} = JSON.parse(readFileSync(testInputsFilePath, {encoding: 'utf8'})); +/** @type {import('test/translator').TranslatorTestInputs} */ +const {optionsPresets, tests} = parseJson(readFileSync(testInputsFilePath, {encoding: 'utf8'})); const testResults1FilePath = path.join(dirname, 'data/translator-test-results.json'); -const expectedResults1 = JSON.parse(readFileSync(testResults1FilePath, {encoding: 'utf8'})); +/** @type {import('test/translator').TranslatorTestResults} */ +const expectedResults1 = parseJson(readFileSync(testResults1FilePath, {encoding: 'utf8'})); const testResults2FilePath = path.join(dirname, 'data/translator-test-results-note-data1.json'); -const expectedResults2 = JSON.parse(readFileSync(testResults2FilePath, {encoding: 'utf8'})); +/** @type {import('test/translator').TranslatorTestNoteDataResults} */ +const expectedResults2 = parseJson(readFileSync(testResults2FilePath, {encoding: 'utf8'})); const dictionaryName = 'Test Dictionary 2'; const test = await createTranslatorTest(void 0, path.join(dirname, 'data/dictionaries/valid-dictionary1'), dictionaryName); -- cgit v1.2.3