From 7ae2684d14a70286f61fde07b206a36cec050d0e Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sat, 11 Jul 2026 17:03:13 +0200 Subject: [PATCH 01/14] Run script for converting most function headers --- lib/Compile.js | 45 ++++++++---- lib/DependencyProvider.js | 114 +++++++++++++++++++----------- lib/ElmCompiler.js | 17 ++++- lib/ElmHome.js | 14 +++- lib/ElmJson.js | 85 ++++++++++++++++------- lib/FindTests.js | 67 +++++++++++++----- lib/Generate.js | 141 ++++++++++++++++++++++++++------------ lib/Install.js | 18 +++-- lib/Parser.js | 74 ++++++++++++++------ lib/Project.js | 26 +++++-- lib/Report.js | 6 +- lib/RunTests.js | 117 +++++++++++++++++++------------ lib/Solve.js | 24 ++++--- lib/Supervisor.js | 26 ++++--- lib/SyncGetWorker.js | 6 +- lib/chalk.js | 12 +++- lib/elm-test.js | 24 +++++-- 17 files changed, 568 insertions(+), 248 deletions(-) diff --git a/lib/Compile.js b/lib/Compile.js index 307335df..f658d855 100644 --- a/lib/Compile.js +++ b/lib/Compile.js @@ -5,13 +5,15 @@ const spawn = require('cross-spawn'); const ElmCompiler = require('./ElmCompiler'); const Report = require('./Report'); -function compile( - cwd /*: string */, - testFile /*: string */, - dest /*: string */, - pathToElmBinary /*: string */, - report /*: typeof Report.Report */ -) /*: Promise */ { +/** + * @param { string } cwd + * @param { string } testFile + * @param { string } dest + * @param { string } pathToElmBinary + * @param { typeof Report.Report } report + * @returns { Promise } + */ +function compile(cwd, testFile, dest, pathToElmBinary, report) { return new Promise((resolve, reject) => { const compileProcess = ElmCompiler.compile([testFile], { output: dest, @@ -30,12 +32,19 @@ function compile( }); } +/** + * @param { Array } testFilePaths + * @param { string } projectRootDir + * @param { string } pathToElmBinary + * @param { typeof Report.Report } report + * @returns { Promise } + */ function compileSources( - testFilePaths /*: Array */, - projectRootDir /*: string */, - pathToElmBinary /*: string */, - report /*: typeof Report.Report */ -) /*: Promise */ { + testFilePaths, + projectRootDir, + pathToElmBinary, + report +) { return new Promise((resolve, reject) => { const compilerReport = report === 'json' ? report : undefined; @@ -58,6 +67,10 @@ function compileSources( }); } +/** + * @param { TODO } { ignoreStdout, cwd } + * @returns { TODO } + */ function spawnCompiler({ ignoreStdout, cwd }) { return ( pathToElm /*: string */, @@ -94,9 +107,11 @@ function spawnCompiler({ ignoreStdout, cwd }) { }; } -function processOptsForReporter( - report /*: typeof Report.Report */ -) /*: child_process$spawnOpts */ { +/** + * @param { typeof Report.Report } report + * @returns { child_process$spawnOpts } + */ +function processOptsForReporter(report) { if (Report.isMachineReadable(report)) { return { env: process.env, stdio: ['ignore', 'ignore', process.stderr] }; } else { diff --git a/lib/DependencyProvider.js b/lib/DependencyProvider.js index afd1fbcd..e982f98a 100644 --- a/lib/DependencyProvider.js +++ b/lib/DependencyProvider.js @@ -151,7 +151,11 @@ class OfflineAvailableVersionLister { } } -function readVersionsInElmHomeAndSort(pkg /*: string */) /*: Array */ { +/** + * @param { string } pkg + * @returns { Array } + */ +function readVersionsInElmHomeAndSort(pkg) { const pkgPath = ElmHome.packagePath(pkg); let offlineVersions; try { @@ -225,10 +229,12 @@ class DependencyProvider { } } -function fetchElmJsonOnline( - pkg /*: string */, - version /*: string */ -) /*: string */ { +/** + * @param { string } pkg + * @param { string } version + * @returns { string } + */ +function fetchElmJsonOnline(pkg, version) { try { return fetchElmJsonOffline(pkg, version); } catch (_) { @@ -246,10 +252,12 @@ function fetchElmJsonOnline( } } -function fetchElmJsonOffline( - pkg /*: string */, - version /*: string */ -) /*: string */ { +/** + * @param { string } pkg + * @param { string } version + * @returns { string } + */ +function fetchElmJsonOffline(pkg, version) { try { return fs.readFileSync(homeElmJsonPath(pkg, version), 'utf8'); } catch (_) { @@ -262,12 +270,15 @@ function fetchElmJsonOffline( } } -// Reset the cache of existing versions from scratch -// with a request to the package server. -function onlineVersionsFromScratch( - cachePath /*: string */, - remotePackagesUrl /*: string */ -) /*: Map> */ { +/** + * Reset the cache of existing versions from scratch + * with a request to the package server. + * + * @param { string } cachePath + * @param { string } remotePackagesUrl + * @returns { Map> } + */ +function onlineVersionsFromScratch(cachePath, remotePackagesUrl) { const onlineVersionsJson = syncGetWorker().get(remotePackagesUrl); fs.writeFileSync(cachePath, onlineVersionsJson); const onlineVersions = JSON.parse(onlineVersionsJson); @@ -299,10 +310,12 @@ function onlineVersionsFromScratch( * * Assumes versions is sorted descending (newest -> oldest). */ -function prioritizePinnedIndirectVersion( - versions /*: Array */, - pinnedVersion /*: void | string */ -) /*: Array */ { +/** + * @param { Array } versions + * @param { void | string } pinnedVersion + * @returns { Array } + */ +function prioritizePinnedIndirectVersion(versions, pinnedVersion) { if (pinnedVersion === undefined || !versions.includes(pinnedVersion)) { return versions; } @@ -319,13 +332,20 @@ function prioritizePinnedIndirectVersion( } /* Compares two versions so that newer versions appear first when sorting with this function. */ -function flippedSemverCompare(a /*: string */, b /*: string */) /*: number */ { +/** + * @param { string } a + * @param { string } b + * @returns { number } + */ +function flippedSemverCompare(a, b) { return collator.compare(b, a); } -function parseOnlineVersions( - json /*: mixed */ -) /*: Map> */ { +/** + * @param { mixed } json + * @returns { Map> } + */ +function parseOnlineVersions(json) { if (typeof json !== 'object' || json === null || Array.isArray(json)) { throw new Error( `Expected an object, but got: ${ @@ -343,10 +363,12 @@ function parseOnlineVersions( return result; } -function parseVersions( - key /*: string */, - json /*: mixed */ -) /*: Array */ { +/** + * @param { string } key + * @param { mixed } json + * @returns { Array } + */ +function parseVersions(key, json) { if (!Array.isArray(json)) { throw new Error( `Expected ${JSON.stringify(key)} to be an array, but got: ${typeof json}` @@ -367,17 +389,21 @@ function parseVersions( return json; } -function remoteElmJsonUrl( - pkg /*: string */, - version /*: string */ -) /*: string */ { +/** + * @param { string } pkg + * @param { string } version + * @returns { string } + */ +function remoteElmJsonUrl(pkg, version) { return `https://package.elm-lang.org/packages/${pkg}/${version}/elm.json`; } -function cacheElmJsonPath( - pkg /*: string */, - version /*: string */ -) /*: string */ { +/** + * @param { string } pkg + * @param { string } version + * @returns { string } + */ +function cacheElmJsonPath(pkg, version) { const parts = ElmHome.splitAuthorPkg(pkg); return path.join( ElmHome.elmHome(), @@ -390,17 +416,23 @@ function cacheElmJsonPath( ); } -function homeElmJsonPath( - pkg /*: string */, - version /*: string */ -) /*: string */ { +/** + * @param { string } pkg + * @param { string } version + * @returns { string } + */ +function homeElmJsonPath(pkg, version) { return path.join(ElmHome.packagePath(pkg), version, 'elm.json'); } -function splitPkgVersion(str /*: string */) /*: { +/** + * @param { string } str + * @returns { { pkg: string, version: string, -} */ { +} } + */ +function splitPkgVersion(str) { const parts = str.split('@'); return { pkg: parts[0], version: parts[1] }; } diff --git a/lib/ElmCompiler.js b/lib/ElmCompiler.js index ad2e450e..a0f6ebda 100644 --- a/lib/ElmCompiler.js +++ b/lib/ElmCompiler.js @@ -13,8 +13,13 @@ var defaultOptions = { processOpts: undefined, }; -// Converts an object of key/value pairs to an array of arguments suitable -// to be passed to child_process.spawn for elm-make. +/** + * Converts an object of key/value pairs to an array of arguments suitable + * to be passed to child_process.spawn for elm-make. + * + * @param { TODO } options + * @returns { TODO } + */ function compilerArgsFromOptions(options) { var args = []; @@ -29,6 +34,10 @@ function compilerArgsFromOptions(options) { return args; } +/** + * @param { TODO } sources, options + * @returns { TODO } + */ function runCompiler(sources, options) { var pathToElm = options.pathToElm; var processArgs = ['make'].concat(sources, compilerArgsFromOptions(options)); @@ -46,6 +55,10 @@ function runCompiler(sources, options) { return options.spawn(pathToElm, processArgs, processOpts); } +/** + * @param { TODO } err, pathToElm + * @returns { TODO } + */ function compilerErrorToString(err, pathToElm) { if (typeof err === 'object' && typeof err.code === 'string') { switch (err.code) { diff --git a/lib/ElmHome.js b/lib/ElmHome.js index 12a0b36e..698fb56f 100644 --- a/lib/ElmHome.js +++ b/lib/ElmHome.js @@ -27,15 +27,23 @@ function defaultWindowsElmHome() /*: string */ { return path.join(dir, 'elm'); } -function packagePath(pkg /*: string */) /*: string */ { +/** + * @param { string } pkg + * @returns { string } + */ +function packagePath(pkg) { const parts = splitAuthorPkg(pkg); return path.join(elmHome(), '0.19.2', 'packages', parts.author, parts.pkg); } -function splitAuthorPkg(pkgIdentifier /*: string */) /*: { +/** + * @param { string } pkgIdentifier + * @returns { { author: string, pkg: string, -} */ { +} } + */ +function splitAuthorPkg(pkgIdentifier) { const parts = pkgIdentifier.split('/'); return { author: parts[0], pkg: parts[1] }; } diff --git a/lib/ElmJson.js b/lib/ElmJson.js index afc3544e..8b3c41af 100644 --- a/lib/ElmJson.js +++ b/lib/ElmJson.js @@ -31,11 +31,20 @@ const ElmJson /*: 'test-dependencies': Dependencies, }; -function getPath(dir /*: string */) /*: string */ { +/** + * @param { string } dir + * @returns { string } + */ +function getPath(dir) { return path.join(dir, 'elm.json'); } -function write(dir /*: string */, elmJson /*: typeof ElmJson */) /*: void */ { +/** + * @param { string } dir + * @param { typeof ElmJson } elmJson + * @returns { void } + */ +function write(dir, elmJson) { const elmJsonPath = getPath(dir); try { @@ -47,7 +56,11 @@ function write(dir /*: string */, elmJson /*: typeof ElmJson */) /*: void */ { } } -function read(dir /*: string */) /*: typeof ElmJson */ { +/** + * @param { string } dir + * @returns { typeof ElmJson } + */ +function read(dir) { const elmJsonPath = getPath(dir); try { @@ -59,7 +72,11 @@ function read(dir /*: string */) /*: typeof ElmJson */ { } } -function readHelper(elmJsonPath /*: string */) /*: typeof ElmJson */ { +/** + * @param { string } elmJsonPath + * @returns { typeof ElmJson } + */ +function readHelper(elmJsonPath) { const json = parseObject( JSON.parse(fs.readFileSync(elmJsonPath, 'utf8')), 'the file' @@ -103,7 +120,11 @@ function readHelper(elmJsonPath /*: string */) /*: typeof ElmJson */ { } } -function parseSourceDirectories(json /*: mixed */) /*: Array */ { +/** + * @param { mixed } json + * @returns { Array } + */ +function parseSourceDirectories(json) { if (!Array.isArray(json)) { throw new Error( `Expected "source-directories" to be an array, but got: ${stringify( @@ -134,10 +155,12 @@ function parseSourceDirectories(json /*: mixed */) /*: Array */ { return result; } -function parseDirectAndIndirectDependencies( - json /*: mixed */, - what /*: string */ -) /*: typeof DirectAndIndirectDependencies */ { +/** + * @param { mixed } json + * @param { string } what + * @returns { typeof DirectAndIndirectDependencies } + */ +function parseDirectAndIndirectDependencies(json, what) { const jsonObject = parseObject(json, what); return { direct: parseDependencies(jsonObject.direct, `${what}->"direct"`), @@ -145,10 +168,12 @@ function parseDirectAndIndirectDependencies( }; } -function parseDependencies( - json /*: mixed */, - what /*: string */ -) /*: typeof Dependencies */ { +/** + * @param { mixed } json + * @param { string } what + * @returns { typeof Dependencies } + */ +function parseDependencies(json, what) { const jsonObject = parseObject(json, what); const result = {}; @@ -166,10 +191,12 @@ function parseDependencies( return result; } -function parseObject( - json /*: mixed */, - what /*: string */ -) /*: { +[string]: mixed } */ { +/** + * @param { mixed } json + * @param { string } what + * @returns { { +[string]: mixed } } + */ +function parseObject(json, what) { if (json == null || typeof json !== 'object' || Array.isArray(json)) { throw new Error( `Expected ${what} to be an object, but got: ${stringify(json)}` @@ -178,17 +205,23 @@ function parseObject( return json; } -function stringify(json /*: mixed */) /*: string */ { +/** + * @param { mixed } json + * @returns { string } + */ +function stringify(json) { const maybeString = JSON.stringify(json); return maybeString === undefined ? 'undefined' : maybeString; } const ELM_TEST_PACKAGE = 'elm-explorations/test'; -function requireElmTestPackage( - dir /*: string */, - elmJson /*: typeof ElmJson */ -) /*: void */ { +/** + * @param { string } dir + * @param { typeof ElmJson } elmJson + * @returns { void } + */ +function requireElmTestPackage(dir, elmJson) { const elmJsonPath = getPath(dir); const versionOrRange = getElmExplorationsTestPackageVersionOrRange(elmJson); @@ -205,9 +238,11 @@ function requireElmTestPackage( } } -function getElmExplorationsTestPackageVersionOrRange( - elmJson /*: typeof ElmJson */ -) /*: string | void */ { +/** + * @param { typeof ElmJson } elmJson + * @returns { string | void } + */ +function getElmExplorationsTestPackageVersionOrRange(elmJson) { switch (elmJson.type) { case 'application': return ( diff --git a/lib/FindTests.js b/lib/FindTests.js index d8b5076d..a6287502 100644 --- a/lib/FindTests.js +++ b/lib/FindTests.js @@ -15,10 +15,12 @@ void Project; // https://github.com/isaacs/node-glob/blob/f5a57d3d6e19b324522a3fa5bdd5075fd1aa79d1/common.js#L222-L231 const ignoredDirsGlobs = ['**/elm-stuff/**', '**/node_modules/**']; -function resolveGlobs( - fileGlobs /*: Array */, - projectRootDir /*: string */ -) /*: Array */ { +/** + * @param { Array } fileGlobs + * @param { string } projectRootDir + * @returns { Array } + */ +function resolveGlobs(fileGlobs, projectRootDir) { return Array.from( new Set( fileGlobs.flatMap((fileGlob) => { @@ -48,10 +50,12 @@ function resolveGlobs( ); } -function resolveCliArgGlob( - fileGlob /*: string */, - projectRootDir /*: string */ -) /*: Array */ { +/** + * @param { string } fileGlob + * @param { string } projectRootDir + * @returns { Array } + */ +function resolveCliArgGlob(fileGlob, projectRootDir) { // Globs passed as CLI arguments are relative to CWD, while elm-test // operates from the project root dir. const globRelativeToProjectRoot = path.relative( @@ -84,8 +88,13 @@ function resolveCliArgGlob( ); } -// Recursively search for *.elm files. -function findAllElmFilesInDir(dir /*: string */) /*: Array */ { +/** + * Recursively search for *.elm files. + * + * @param { string } dir + * @returns { Array } + */ +function findAllElmFilesInDir(dir) { return globSync('**/*.elm', { cwd: dir, caseSensitiveMatch: false, @@ -95,10 +104,12 @@ function findAllElmFilesInDir(dir /*: string */) /*: Array */ { }); } -function findTests( - testFilePaths /*: Array */, - project /*: typeof Project.Project */ -) /*: Promise }>> */ { +/** + * @param { Array } testFilePaths + * @param { typeof Project.Project } project + * @returns { Promise }>> } + */ +function findTests(testFilePaths, project) { return Promise.all( testFilePaths.map((filePath) => { const matchingSourceDirs = project.testsSourceDirs.filter((dir) => @@ -164,6 +175,10 @@ function findTests( ); } +/** + * @param { TODO } filePath, isPackageProject + * @returns { TODO } + */ function missingSourceDirectoryError(filePath, isPackageProject) { return ` This file: @@ -180,6 +195,12 @@ ${ `.trim(); } +/** + * @param { TODO } filePath, + matchingSourceDirs, + testsDir + * @returns { TODO } + */ function multipleSourceDirectoriesError( filePath, matchingSourceDirs, @@ -204,6 +225,10 @@ ${note} `.trim(); } +/** + * @param { TODO } filePath, sourceDir, moduleName + * @returns { TODO } + */ function badModuleNameError(filePath, sourceDir, moduleName) { return ` This file: @@ -227,10 +252,12 @@ Make sure that all parts start with an uppercase letter and don't contain any sp `.trim(); } -function noFilesFoundError( - projectRootDir /*: string */, - testFileGlobs /*: Array */ -) /*: string */ { +/** + * @param { string } projectRootDir + * @param { Array } testFileGlobs + * @returns { string } + */ +function noFilesFoundError(projectRootDir, testFileGlobs) { return testFileGlobs.length === 0 ? ` ${noFilesFoundInTestsDir(projectRootDir)} @@ -249,6 +276,10 @@ Are the above patterns correct? Maybe try running elm-test with no arguments? `.trim(); } +/** + * @param { TODO } projectRootDir + * @returns { TODO } + */ function noFilesFoundInTestsDir(projectRootDir) { const testsDir = path.join(projectRootDir, 'tests'); try { diff --git a/lib/Generate.js b/lib/Generate.js index 5874f079..eb35b438 100644 --- a/lib/Generate.js +++ b/lib/Generate.js @@ -25,10 +25,12 @@ const after = fs.readFileSync( 'utf8' ); -function prepareCompiledJsFile( - pipeFilename /*: string */, - dest /*: string */ -) /*: void */ { +/** + * @param { string } pipeFilename + * @param { string } dest + * @returns { void } + */ +function prepareCompiledJsFile(pipeFilename, dest) { const content = fs.readFileSync(dest, 'utf8'); const finalContent = ` ${before} @@ -61,8 +63,13 @@ const testVariantDefinition = const checkDefinition = /^(var\s+\$author\$project\$Test\$Runner\$Node\$check)\s*=\s*\$author\$project\$Test\$Runner\$Node\$checkHelperReplaceMe___;?$/m; -// Create a symbol, tag all `Test` constructors with it and make the `check` -// function look for it. +/** + * Create a symbol, tag all `Test` constructors with it and make the `check` + * function look for it. + * + * @param { TODO } content + * @returns { TODO } + */ function addKernelTestChecking(content) { return ( 'var __elmTestSymbol = Symbol("elmTestSymbol");\n' + @@ -75,14 +82,20 @@ function addKernelTestChecking(content) { ); } -function getGeneratedSrcDir(generatedCodeDir /*: string */) /*: string */ { +/** + * @param { string } generatedCodeDir + * @returns { string } + */ +function getGeneratedSrcDir(generatedCodeDir) { return path.join(generatedCodeDir, 'src'); } -function generateElmJson( - dependencyProvider /*: DependencyProvider */, - project /*: typeof Project.Project */ -) /*: void */ { +/** + * @param { DependencyProvider } dependencyProvider + * @param { typeof Project.Project } project + * @returns { void } + */ +function generateElmJson(dependencyProvider, project) { const generatedSrc = getGeneratedSrcDir(project.generatedCodeDir); fs.mkdirSync(generatedSrc, { recursive: true }); @@ -138,10 +151,14 @@ function generateElmJson( } } -function getMainModule(generatedCodeDir /*: string */) /*: { +/** + * @param { string } generatedCodeDir + * @returns { { moduleName: string, path: string, -} */ { +} } + */ +function getMainModule(generatedCodeDir) { const moduleName = ['Test', 'Generated', 'Main']; return { moduleName: moduleName.join('.'), @@ -153,19 +170,30 @@ function getMainModule(generatedCodeDir /*: string */) /*: { }; } -function generateMainModule( - fuzz /*: number */, - seed /*: number */, - report /*: typeof Report.Report */, - testFileGlobs /*: Array */, - testFilePaths /*: Array */, - testModules /*: Array<{ +/** + * @param { number } fuzz + * @param { number } seed + * @param { typeof Report.Report } report + * @param { Array } testFileGlobs + * @param { Array } testFilePaths + * @param { Array<{ moduleName: string, possiblyTests: Array, - }> */, - mainModule /*: { moduleName: string, path: string } */, - processes /*: number */ -) /*: void */ { + }> } testModules + * @param { { moduleName: string, path: string } } mainModule + * @param { number } processes + * @returns { void } + */ +function generateMainModule( + fuzz, + seed, + report, + testFileGlobs, + testFilePaths, + testModules, + mainModule, + processes +) { const testFileBody = makeTestFileBody( testModules, makeOptsCode(fuzz, seed, report, testFileGlobs, testFilePaths, processes) @@ -178,13 +206,15 @@ function generateMainModule( fs.writeFileSync(mainModule.path, testFileContents); } -function makeTestFileBody( - testModules /*: Array<{ +/** + * @param { Array<{ moduleName: string, possiblyTests: Array, - }> */, - optsCode /*: string */ -) /*: string */ { + }> } testModules + * @param { string } optsCode + * @returns { string } + */ +function makeTestFileBody(testModules, optsCode) { const imports = testModules.map((mod) => `import ${mod.moduleName}`); const possiblyTestsList = makeList(testModules.map(makeModuleTuple)); @@ -205,10 +235,14 @@ main = `.trim(); } -function makeModuleTuple(mod /*: { +/** + * @param { { moduleName: string, possiblyTests: Array, -} */) /*: string */ { +} } mod + * @returns { string } + */ +function makeModuleTuple(mod) { const list = mod.possiblyTests.map( (test) => `Test.Runner.Node.check ${mod.moduleName}.${test}` ); @@ -220,7 +254,11 @@ function makeModuleTuple(mod /*: { `.trim(); } -function makeList(parts /*: Array */) /*: string */ { +/** + * @param { Array } parts + * @returns { string } + */ +function makeList(parts) { if (parts.length === 0) { return '[]'; } @@ -236,6 +274,10 @@ function makeList(parts /*: Array */) /*: string */ { `.trim(); } +/** + * @param { TODO } indent, string + * @returns { TODO } + */ function indentAllButFirstLine(indent, string) { return string .split('\n') @@ -243,14 +285,23 @@ function indentAllButFirstLine(indent, string) { .join('\n'); } +/** + * @param { number } fuzz + * @param { number } seed + * @param { typeof Report.Report } report + * @param { Array } testFileGlobs + * @param { Array } testFilePaths + * @param { number } processes + * @returns { string } + */ function makeOptsCode( - fuzz /*: number */, - seed /*: number */, - report /*: typeof Report.Report */, - testFileGlobs /*: Array */, - testFilePaths /*: Array */, - processes /*: number */ -) /*: string */ { + fuzz, + seed, + report, + testFileGlobs, + testFilePaths, + processes +) { return ` { runs = ${fuzz} , report = ${generateElmReportVariant(report)} @@ -264,9 +315,11 @@ function makeOptsCode( `.trim(); } -function generateElmReportVariant( - report /*: typeof Report.Report */ -) /*: string */ { +/** + * @param { typeof Report.Report } report + * @returns { string } + */ +function generateElmReportVariant(report) { switch (report) { case 'json': return 'JsonReport'; @@ -281,6 +334,10 @@ function generateElmReportVariant( } } +/** + * @param { TODO } string + * @returns { TODO } + */ function makeElmString(string) { return `"${string .replace(/[\\"]/g, '\\$&') diff --git a/lib/Install.js b/lib/Install.js index dee30a25..1cd6ac6c 100644 --- a/lib/Install.js +++ b/lib/Install.js @@ -8,7 +8,11 @@ const Project = require('./Project'); void Project; -function rmDirSync(dir /*: string */) /*: void */ { +/** + * @param { string } dir + * @returns { void } + */ +function rmDirSync(dir) { // We can replace this with just `fs.rmSync(dir, { recursive: true, force: true })` // when Node.js 12 is EOL 2022-04-30 and support for Node.js 12 is dropped. // `fs.rmSync` was added in Node.js 14.14.0, which is also when the @@ -21,11 +25,13 @@ function rmDirSync(dir /*: string */) /*: void */ { } } -function install( - project /*: typeof Project.Project */, - pathToElmBinary /*: string */, - packageName /*: string */ -) /*: 'SuccessfullyInstalled' | 'AlreadyInstalled' */ { +/** + * @param { typeof Project.Project } project + * @param { string } pathToElmBinary + * @param { string } packageName + * @returns { 'SuccessfullyInstalled' | 'AlreadyInstalled' } + */ +function install(project, pathToElmBinary, packageName) { const installationScratchDir = path.join(project.generatedCodeDir, 'install'); try { diff --git a/lib/Parser.js b/lib/Parser.js index 270c14d1..09ad46f3 100644 --- a/lib/Parser.js +++ b/lib/Parser.js @@ -273,15 +273,27 @@ const backslashableChars = new Set([ 'u', ]); +/** + * @param { TODO } string + * @returns { TODO } + */ function isLowerName(string) { return lowerName.test(string) && !reservedWords.has(string); } -function isUpperName(string /*: string */) /*: boolean */ { +/** + * @param { string } string + * @returns { boolean } + */ +function isUpperName(string) { return upperName.test(string); } -function unreachable(value /*: empty */) /*: empty */ { +/** + * @param { empty } value + * @returns { empty } + */ +function unreachable(value) { throw new Error(`Unreachable: ${value}`); } @@ -313,21 +325,31 @@ const OnParserTokenResult /*: | { tag: 'StopParsing' } */ = ParseError; void OnParserTokenResult; -function expected( - expectedDescription /*: string */, - actual /*: mixed */ -) /*: typeof ParseError */ { +/** + * @param { string } expectedDescription + * @param { mixed } actual + * @returns { typeof ParseError } + */ +function expected(expectedDescription, actual) { return { tag: 'ParseError', message: `Expected ${expectedDescription} but got: ${stringify(actual)}`, }; } -function stringify(json /*: mixed */) /*: string */ { +/** + * @param { mixed } json + * @returns { string } + */ +function stringify(json) { const maybeString = JSON.stringify(json); return maybeString === undefined ? 'undefined' : maybeString; } +/** + * @param { TODO } actual + * @returns { TODO } + */ function backslashError(actual) { return expected( `one of \`${Array.from(backslashableChars).join(' ')}\``, @@ -377,15 +399,17 @@ const TokenizerState /*: }; void TokenizerState; -function tokenize( - char /*: string */, - tokenizerState /*: typeof TokenizerState */ -) /*: +/** + * @param { string } char + * @param { typeof TokenizerState } tokenizerState + * @returns { | [ typeof TokenizerState, Array<{ tag: 'Flush' } | { tag: 'FlushToken', token: typeof Token }> ] - | typeof ParseError */ { + | typeof ParseError } + */ +function tokenize(char, tokenizerState) { switch (tokenizerState.tag) { case 'Initial': switch (char) { @@ -694,6 +718,10 @@ function tokenize( } } +/** + * @param { TODO } previousChar, char, cmds + * @returns { TODO } + */ function tokenizeInitial(previousChar, char, cmds) { const result = tokenize(char, { tag: 'Initial', @@ -735,13 +763,15 @@ const ModuleDeclarationLastToken /*: | ',' */ = 'Nothing'; void ModuleDeclarationLastToken; -function parseModuleDeclaration( - token /*: typeof Token */, - lastToken /*: typeof ModuleDeclarationLastToken */ -) /*: +/** + * @param { typeof Token } token + * @param { typeof ModuleDeclarationLastToken } lastToken + * @returns { | typeof ModuleDeclarationLastToken | { tag: 'NextParserState' } - | typeof OnParserTokenResult */ { + | typeof OnParserTokenResult } + */ +function parseModuleDeclaration(token, lastToken) { switch (lastToken) { case 'Nothing': if (token.tag === 'NewChunk') { @@ -883,10 +913,12 @@ const RestLastToken /*: | 'Ignore' */ = 'Initial'; void RestLastToken; -function parseRest( - token /*: typeof Token */, - lastToken /*: typeof RestLastToken */ -) /*: typeof RestLastToken | typeof ParseError */ { +/** + * @param { typeof Token } token + * @param { typeof RestLastToken } lastToken + * @returns { typeof RestLastToken | typeof ParseError } + */ +function parseRest(token, lastToken) { switch (lastToken) { case 'Initial': if (token.tag === 'NewChunk') { diff --git a/lib/Project.js b/lib/Project.js index 1e15ae0b..9687fe92 100644 --- a/lib/Project.js +++ b/lib/Project.js @@ -20,14 +20,20 @@ const Project /*: { elmJson: ElmJson.ElmJson, }; -function getTestsDir(rootDir /*: string */) /*: string */ { +/** + * @param { string } rootDir + * @returns { string } + */ +function getTestsDir(rootDir) { return path.join(rootDir, 'tests'); } -function init( - rootDir /*: string */, - version /*: string */ -) /*: typeof Project */ { +/** + * @param { string } rootDir + * @param { string } version + * @returns { typeof Project } + */ +function init(rootDir, version) { const testsDir = getTestsDir(rootDir); // The tests/ directory is not required. You can also co-locate tests with @@ -78,7 +84,11 @@ following directory: I cannot find it though. Is it missing? Is there a typo? */ -function validateTestsSourceDirs(project /*: typeof Project */) /*: void */ { +/** + * @param { typeof Project } project + * @returns { void } + */ +function validateTestsSourceDirs(project) { for (const dir of project.testsSourceDirs) { const fullDir = path.resolve(project.rootDir, dir); let stats; @@ -105,6 +115,10 @@ function validateTestsSourceDirs(project /*: typeof Project */) /*: void */ { } } +/** + * @param { TODO } dir, message + * @returns { TODO } + */ function validateTestsSourceDirsError(dir, message) { return ` The "source-directories" field in your elm.json lists the following directory: diff --git a/lib/Report.js b/lib/Report.js index 09582148..5b1bc417 100644 --- a/lib/Report.js +++ b/lib/Report.js @@ -6,7 +6,11 @@ const Report /*: 'console' | 'json' | 'junit' */ = 'console'; const all = ['json', 'junit', 'console']; -function isMachineReadable(report /*: typeof Report */) /*: boolean */ { +/** + * @param { typeof Report } report + * @returns { boolean } + */ +function isMachineReadable(report) { switch (report) { case 'json': case 'junit': diff --git a/lib/RunTests.js b/lib/RunTests.js index 6128f5b7..6890f8cb 100644 --- a/lib/RunTests.js +++ b/lib/RunTests.js @@ -17,35 +17,43 @@ const Supervisor = require('./Supervisor'); // void operator to keep eslint happy. void DependencyProvider; -// Incorporate the process PID into the socket name, so elm-test processes can -// be run parallel without accidentally sharing each others' sockets. -// -// See https://github.com/rtfeldman/node-test-runner/pull/231 -// Also incorporate a salt number into it on Windows, to avoid EADDRINUSE - -// see https://github.com/rtfeldman/node-test-runner/issues/275 - because the -// alternative approach of deleting the file before creating a new one doesn't -// work on Windows. We have to let Windows clean up the named pipe. This is -// essentially a band-aid fix. The alternative is to rewrite a ton of stuff. -function getPipeFilename(runsExecuted /*: number */) /*: string */ { +/** + * Incorporate the process PID into the socket name, so elm-test processes can + * be run parallel without accidentally sharing each others' sockets. + * + * See https://github.com/rtfeldman/node-test-runner/pull/231 + * Also incorporate a salt number into it on Windows, to avoid EADDRINUSE - + * see https://github.com/rtfeldman/node-test-runner/issues/275 - because the + * alternative approach of deleting the file before creating a new one doesn't + * work on Windows. We have to let Windows clean up the named pipe. This is + * essentially a band-aid fix. The alternative is to rewrite a ton of stuff. + * + * @param { number } runsExecuted + * @returns { string } + */ +function getPipeFilename(runsExecuted) { return process.platform === 'win32' ? `\\\\.\\pipe\\elm_test-${process.pid}-${runsExecuted}` : `/tmp/elm_test-${process.pid}.sock`; } -// This could have been a class, but I couldn’t figure out how to type it with -// Flow comments and Prettier. -// This lets you log something like `elm.json changed > Compiling > Starting tests` -// where each segment appears over time. -// `\r` moves the cursor to the start of the line. This is important when there -// are Elm compilation errors - they overwrite the progress text rather than -// weirdly ending up with stuff like: -// `elm.json changed > Compiling-- NAMING ERROR - File.elm` -// Also note that using too much `clearLine` or `clearConsole` causes flickering -// on Windows, so it's nicer to cleverly overwrite old output when possible. -function makeProgressLogger( - report /*: typeof Report.Report */, - clearConsole /*: boolean */ -) { +/** + * This could have been a class, but I couldn’t figure out how to type it with + * Flow comments and Prettier. + * This lets you log something like `elm.json changed > Compiling > Starting tests` + * where each segment appears over time. + * `\r` moves the cursor to the start of the line. This is important when there + * are Elm compilation errors - they overwrite the progress text rather than + * weirdly ending up with stuff like: + * `elm.json changed > Compiling-- NAMING ERROR - File.elm` + * Also note that using too much `clearLine` or `clearConsole` causes flickering + * on Windows, so it's nicer to cleverly overwrite old output when possible. + * + * @param { typeof Report.Report } report + * @param { boolean } clearConsole + * @returns { TODO } + */ +function makeProgressLogger(report, clearConsole) { const items = []; return { log(message) { @@ -86,17 +94,23 @@ function makeProgressLogger( }; } -function diffArrays/*:: */( - from /*: Array */, - to /*: Array */ -) /*: { added: Array, removed: Array } */ { +/**TODO generics: T + * @param { Array } from + * @param { Array } to + * @returns { { added: Array, removed: Array } } + */ +function diffArrays(from, to) { return { added: to.filter((item) => !from.includes(item)), removed: from.filter((item) => !to.includes(item)), }; } -function delay(ms /*: number */) /*: Promise */ { +/** + * @param { number } ms + * @returns { Promise } + */ +function delay(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); @@ -108,7 +122,11 @@ const Queue /*: Array<{ }> */ = []; void Queue; -function watcherEventMessage(queue /*: typeof Queue */) /*: string */ { +/** + * @param { typeof Queue } queue + * @returns { string } + */ +function watcherEventMessage(queue) { const filePaths = Array.from(new Set(queue.map(({ filePath }) => filePath))); if (filePaths.length === 1) { const { event, filePath } = queue[0]; @@ -119,26 +137,35 @@ function watcherEventMessage(queue /*: typeof Queue */) /*: string */ { return `${filePaths.length} files ${events.join('/')}`; } -function runTests( - dependencyProvider /*: DependencyProvider */, - projectRootDir /*: string */, - pathToElmBinary /*: string */, - testFileGlobs /*: Array */, - processes /*: number */, - { - watch, - clearConsole, - report, - seed, - fuzz, - } /*: { +/** + * @param { DependencyProvider } dependencyProvider + * @param { string } projectRootDir + * @param { string } pathToElmBinary + * @param { Array } testFileGlobs + * @param { number } processes + * @param { { watch: boolean, clearConsole: boolean, report: typeof Report.Report, seed: number, fuzz: number, - } */ -) /*: Promise */ { + } } { + watch, + clearConsole, + report, + seed, + fuzz, + } + * @returns { Promise } + */ +function runTests( + dependencyProvider, + projectRootDir, + pathToElmBinary, + testFileGlobs, + processes, + { watch, clearConsole, report, seed, fuzz } +) { let watcher = undefined; let watchedPaths /*: Array */ = []; let runsExecuted /*: number */ = 0; diff --git a/lib/Solve.js b/lib/Solve.js index 21a7e397..ecf641e7 100644 --- a/lib/Solve.js +++ b/lib/Solve.js @@ -12,14 +12,20 @@ const { DependencyProvider } = require('./DependencyProvider.js'); void Project; void DependencyProvider; +/** + * @param { TODO } string + * @returns { TODO } + */ function sha256(string) { return crypto.createHash('sha256').update(string).digest('hex'); } -function getDependenciesCached( - dependencyProvider /*: DependencyProvider */, - project /*: typeof Project.Project */ -) /*: typeof ElmJson.DirectAndIndirectDependencies */ { +/** + * @param { DependencyProvider } dependencyProvider + * @param { typeof Project.Project } project + * @returns { typeof ElmJson.DirectAndIndirectDependencies } + */ +function getDependenciesCached(dependencyProvider, project) { const hash = sha256( JSON.stringify({ dependencies: project.elmJson.dependencies, @@ -52,10 +58,12 @@ function getDependenciesCached( ); } -function getDependencies( - dependencyProvider /*: DependencyProvider */, - elmJson /*: typeof ElmJson.ElmJson */ -) /*: string */ { +/** + * @param { DependencyProvider } dependencyProvider + * @param { typeof ElmJson.ElmJson } elmJson + * @returns { string } + */ +function getDependencies(dependencyProvider, elmJson) { const useTest = true; // Note: These are the dependencies listed in `elm/elm.json`, except // `elm-explorations/test`. `elm/elm.json` is only used during development of diff --git a/lib/Supervisor.js b/lib/Supervisor.js index fc651678..333c94ea 100644 --- a/lib/Supervisor.js +++ b/lib/Supervisor.js @@ -7,14 +7,16 @@ const net = require('net'); const split = require('split'); const Report = require('./Report'); -function run( - elmTestVersion /*: string */, - pipeFilename /*: string */, - report /*: typeof Report.Report */, - processes /*: number */, - dest /*: string */, - watch /*: boolean */ -) /*: Promise */ { +/** + * @param { string } elmTestVersion + * @param { string } pipeFilename + * @param { typeof Report.Report } report + * @param { number } processes + * @param { string } dest + * @param { boolean } watch + * @returns { Promise } + */ +function run(elmTestVersion, pipeFilename, report, processes, dest, watch) { return new Promise(function (resolve) { var nextResultToPrint = null; var finishedWorkers = 0; @@ -323,6 +325,10 @@ function run( }); } +/** + * @param { TODO } text + * @returns { TODO } + */ function makeWindowsSafe(text) { return process.platform === 'win32' ? windowsify(text) : text; } @@ -336,6 +342,10 @@ var windowsSubstitutions = [ [/✔/g, '√'], ]; +/** + * @param { TODO } str + * @returns { TODO } + */ function windowsify(str) { return windowsSubstitutions.reduce(function (result /*: string */, sub) { return result.replace(sub[0], sub[1]); diff --git a/lib/SyncGetWorker.js b/lib/SyncGetWorker.js index 0b7f547d..859b2f9d 100644 --- a/lib/SyncGetWorker.js +++ b/lib/SyncGetWorker.js @@ -17,7 +17,11 @@ parentPort.on('message', async (url) => { Atomics.notify(sharedLockArray, 0, Infinity); }); -async function getBody(url /*: string */) /*: Promise */ { +/** + * @param { string } url + * @returns { Promise } + */ +async function getBody(url) { return new Promise(function (resolve, reject) { https .get(url, function (res) { diff --git a/lib/chalk.js b/lib/chalk.js index 2f439597..1ce0fde7 100644 --- a/lib/chalk.js +++ b/lib/chalk.js @@ -4,11 +4,19 @@ const supportsColor = require('./supports-color'); // Find more colors/styles in // https://github.com/chalk/ansi-styles/blob/main/index.js -function red(string /*: string */) /*: string */ { +/** + * @param { string } string + * @returns { string } + */ +function red(string) { return supportsColor ? `\x1B[31m${string}\x1B[39m` : string; } -function blue(string /*: string */) /*: string */ { +/** + * @param { string } string + * @returns { string } + */ +function blue(string) { return supportsColor ? `\x1B[34m${string}\x1B[39m` : string; } diff --git a/lib/elm-test.js b/lib/elm-test.js index 1127b77c..f07a9c17 100644 --- a/lib/elm-test.js +++ b/lib/elm-test.js @@ -33,7 +33,11 @@ const parsePositiveInteger = } }; -function findClosestElmJson(dir /*: string */) /*: string | void */ { +/** + * @param { string } dir + * @returns { string | void } + */ +function findClosestElmJson(dir) { const entry = ElmJson.getPath(dir); return fs.existsSync(entry) ? entry @@ -42,7 +46,11 @@ function findClosestElmJson(dir /*: string */) /*: string | void */ { : findClosestElmJson(path.dirname(dir)); } -function getProjectRootDir(subcommand /*: string */) /*: string */ { +/** + * @param { string } subcommand + * @returns { string } + */ +function getProjectRootDir(subcommand) { const elmJsonPath = findClosestElmJson(process.cwd()); if (elmJsonPath === undefined) { const command = @@ -55,7 +63,11 @@ function getProjectRootDir(subcommand /*: string */) /*: string */ { return path.dirname(elmJsonPath); } -function getProject(subcommand /*: string */) /*: typeof Project.Project */ { +/** + * @param { string } subcommand + * @returns { typeof Project.Project } + */ +function getProject(subcommand) { try { return Project.init(getProjectRootDir(subcommand), packageInfo.version); } catch (error) { @@ -64,7 +76,11 @@ function getProject(subcommand /*: string */) /*: typeof Project.Project */ { } } -function getPathToElmBinary(compiler /*: string | void */) /*: string */ { +/** + * @param { string | void } compiler + * @returns { string } + */ +function getPathToElmBinary(compiler) { const name = compiler === undefined ? 'elm' : compiler; try { return path.resolve(which.sync(name)); From 1f5321466a14e872eb30f3f412836d4f418b4221 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sat, 11 Jul 2026 17:22:53 +0200 Subject: [PATCH 02/14] Fix all fake typedefs and surrounding code --- lib/Compile.js | 20 ++-- lib/DependencyProvider.js | 4 +- lib/ElmJson.js | 64 +++++----- lib/Parser.js | 246 +++++++++++++++++++------------------- lib/Project.js | 34 +++--- lib/Report.js | 9 +- lib/SyncGet.js | 28 ++--- lib/SyncGetWorker.js | 1 - 8 files changed, 193 insertions(+), 213 deletions(-) diff --git a/lib/Compile.js b/lib/Compile.js index f658d855..b6095376 100644 --- a/lib/Compile.js +++ b/lib/Compile.js @@ -10,7 +10,7 @@ const Report = require('./Report'); * @param { string } testFile * @param { string } dest * @param { string } pathToElmBinary - * @param { typeof Report.Report } report + * @param { import('./Report').Report } report * @returns { Promise } */ function compile(cwd, testFile, dest, pathToElmBinary, report) { @@ -36,7 +36,7 @@ function compile(cwd, testFile, dest, pathToElmBinary, report) { * @param { Array } testFilePaths * @param { string } projectRootDir * @param { string } pathToElmBinary - * @param { typeof Report.Report } report + * @param { import('./Report').Report } report * @returns { Promise } */ function compileSources( @@ -68,15 +68,15 @@ function compileSources( } /** - * @param { TODO } { ignoreStdout, cwd } - * @returns { TODO } + * @param { { ignoreStdout: boolean, cwd: string } } options + * @returns { ( + pathToElm: string, + processArgs: Array, + processOpts: TODO_child_process$spawnOpts + ) => TODO } */ function spawnCompiler({ ignoreStdout, cwd }) { - return ( - pathToElm /*: string */, - processArgs /*: Array */, - processOpts /*: child_process$spawnOpts */ - ) => { + return (pathToElm, processArgs, processOpts) => { // It might seem useless to specify 'pipe' and then just write all data to // `process.stdout`/`process.stderr`, but it does make a difference: The Elm // compiler turns off colors when it’s used in a pipe. In summary: @@ -108,7 +108,7 @@ function spawnCompiler({ ignoreStdout, cwd }) { } /** - * @param { typeof Report.Report } report + * @param { import('./Report').Report } report * @returns { child_process$spawnOpts } */ function processOptsForReporter(report) { diff --git a/lib/DependencyProvider.js b/lib/DependencyProvider.js index e982f98a..abd2fed6 100644 --- a/lib/DependencyProvider.js +++ b/lib/DependencyProvider.js @@ -342,7 +342,7 @@ function flippedSemverCompare(a, b) { } /** - * @param { mixed } json + * @param { unknown } json * @returns { Map> } */ function parseOnlineVersions(json) { @@ -365,7 +365,7 @@ function parseOnlineVersions(json) { /** * @param { string } key - * @param { mixed } json + * @param { unknown } json * @returns { Array } */ function parseVersions(key, json) { diff --git a/lib/ElmJson.js b/lib/ElmJson.js index 8b3c41af..7f975fb2 100644 --- a/lib/ElmJson.js +++ b/lib/ElmJson.js @@ -3,33 +3,30 @@ const fs = require('fs'); const path = require('path'); -// Poor man’s type alias. We can’t use /*:: type Dependencies = ... */ because of: -// https://github.com/prettier/prettier/issues/2597 -const Dependencies /*: { [string]: string } */ = {}; - -const DirectAndIndirectDependencies /*: { - direct: typeof Dependencies, - indirect: typeof Dependencies, -} */ = { direct: {}, indirect: {} }; - -const ElmJson /*: +/** + * @typedef { Record } Dependencies + * + * @typedef { { + direct: Dependencies, + indirect: Dependencies, + } } DirectAndIndirectDependencies + * + * @typedef { | { type: 'application', 'source-directories': Array, - dependencies: typeof DirectAndIndirectDependencies, - 'test-dependencies': typeof DirectAndIndirectDependencies, - [string]: mixed, + dependencies: DirectAndIndirectDependencies, + 'test-dependencies': DirectAndIndirectDependencies, + [key: string]: unknown, // TODO } | { type: 'package', - dependencies: typeof Dependencies, - 'test-dependencies': typeof Dependencies, - [string]: mixed, - } */ = { - type: 'package', - dependencies: Dependencies, - 'test-dependencies': Dependencies, -}; + dependencies: Dependencies, + 'test-dependencies': Dependencies, + [key: string]: unknown, // TODO + } + } ElmJson + */ /** * @param { string } dir @@ -74,7 +71,7 @@ function read(dir) { /** * @param { string } elmJsonPath - * @returns { typeof ElmJson } + * @returns { ElmJson } */ function readHelper(elmJsonPath) { const json = parseObject( @@ -121,7 +118,7 @@ function readHelper(elmJsonPath) { } /** - * @param { mixed } json + * @param { unknown } json * @returns { Array } */ function parseSourceDirectories(json) { @@ -156,9 +153,9 @@ function parseSourceDirectories(json) { } /** - * @param { mixed } json + * @param { unknown } json * @param { string } what - * @returns { typeof DirectAndIndirectDependencies } + * @returns { DirectAndIndirectDependencies } */ function parseDirectAndIndirectDependencies(json, what) { const jsonObject = parseObject(json, what); @@ -169,9 +166,9 @@ function parseDirectAndIndirectDependencies(json, what) { } /** - * @param { mixed } json + * @param { unknown } json * @param { string } what - * @returns { typeof Dependencies } + * @returns { Dependencies } */ function parseDependencies(json, what) { const jsonObject = parseObject(json, what); @@ -192,9 +189,9 @@ function parseDependencies(json, what) { } /** - * @param { mixed } json + * @param { unknown } json * @param { string } what - * @returns { { +[string]: mixed } } + * @returns { Record } */ function parseObject(json, what) { if (json == null || typeof json !== 'object' || Array.isArray(json)) { @@ -206,7 +203,7 @@ function parseObject(json, what) { } /** - * @param { mixed } json + * @param { unknown } json * @returns { string } */ function stringify(json) { @@ -218,7 +215,7 @@ const ELM_TEST_PACKAGE = 'elm-explorations/test'; /** * @param { string } dir - * @param { typeof ElmJson } elmJson + * @param { ElmJson } elmJson * @returns { void } */ function requireElmTestPackage(dir, elmJson) { @@ -240,7 +237,7 @@ function requireElmTestPackage(dir, elmJson) { /** * @param { typeof ElmJson } elmJson - * @returns { string | void } + * @returns { string | undefined } */ function getElmExplorationsTestPackageVersionOrRange(elmJson) { switch (elmJson.type) { @@ -259,9 +256,6 @@ function getElmExplorationsTestPackageVersionOrRange(elmJson) { module.exports = { ELM_TEST_PACKAGE, - Dependencies, - DirectAndIndirectDependencies, - ElmJson, getPath, parseDirectAndIndirectDependencies, read, diff --git a/lib/Parser.js b/lib/Parser.js index 09ad46f3..4a996201 100644 --- a/lib/Parser.js +++ b/lib/Parser.js @@ -297,38 +297,28 @@ function unreachable(value) { throw new Error(`Unreachable: ${value}`); } -// Poor man’s type alias. We can’t use /*:: type ParseError = ... */ because of: -// https://github.com/prettier/prettier/issues/2597 -// There are a couple of more of this workaround throughout the file. -const ParseError /*: { - tag: 'ParseError', - message: string, - -[mixed]: empty, // https://github.com/facebook/flow/issues/7859 -} */ = { - tag: 'ParseError', - message: '', -}; -void ParseError; - -const CriticalParseError /*: { - tag: 'CriticalParseError', - message: string, -} */ = { - tag: 'CriticalParseError', - message: '', -}; -void CriticalParseError; - -const OnParserTokenResult /*: - | typeof ParseError - | typeof CriticalParseError - | { tag: 'StopParsing' } */ = ParseError; -void OnParserTokenResult; +/** + * @typedef { { + tag: 'ParseError', + message: string, + } } ParseError + * + * @typedef { { + tag: 'CriticalParseError', + message: string, + } } CriticalParseError + * + * @typedef { + | ParseError + | CriticalParseError + | { tag: 'StopParsing' } + } OnParserTokenResult + */ /** * @param { string } expectedDescription - * @param { mixed } actual - * @returns { typeof ParseError } + * @param { unknown } actual + * @returns { ParseError } */ function expected(expectedDescription, actual) { return { @@ -338,7 +328,7 @@ function expected(expectedDescription, actual) { } /** - * @param { mixed } json + * @param { unknown } json * @returns { string } */ function stringify(json) { @@ -347,8 +337,8 @@ function stringify(json) { } /** - * @param { TODO } actual - * @returns { TODO } + * @param { unknown } actual + * @returns { string } */ function backslashError(actual) { return expected( @@ -357,57 +347,56 @@ function backslashError(actual) { ); } -const Token /*: - | { tag: '(' } - | { tag: ')' } - | { tag: ',' } - | { tag: '=' } - | { tag: '.' } - | { tag: '..' } - | { tag: 'Char' } - | { tag: 'String' } - | { tag: 'NewChunk' } - | { tag: 'LowerName', value: string } - | { tag: 'UpperName', value: string } - | { tag: 'Other', value: string } */ = { tag: '(' }; -void Token; - -const TokenizerState /*: - | { tag: 'Initial', otherTokenChars: string } - | { tag: 'MaybeNewChunk' } - | { tag: 'MaybeMultilineComment{' } - | { tag: 'MultilineComment', level: number } - | { tag: 'MultilineComment{', level: number } - | { tag: 'MultilineComment-', level: number } - | { tag: 'MaybeSinglelineComment-' } - | { tag: 'SinglelineComment' } - | { tag: 'Maybe..' } - | { tag: 'CharStart' } - | { tag: 'CharBackslash' } - | { tag: 'CharUnicodeEscape' } - | { tag: 'CharEnd' } - | { tag: 'StringStart' } - | { tag: 'StringContent' } - | { tag: 'StringBackslash' } - | { tag: 'EmptyStringMaybeTriple' } - | { tag: 'MultilineString' } - | { tag: 'MultilineStringBackslash' } - | { tag: 'MultilineString"' } - | { tag: 'MultilineString""' } */ = { - tag: 'Initial', - otherTokenChars: '', -}; -void TokenizerState; +/** + * @typedef { + | { tag: '(' } + | { tag: ')' } + | { tag: ',' } + | { tag: '=' } + | { tag: '.' } + | { tag: '..' } + | { tag: 'Char' } + | { tag: 'String' } + | { tag: 'NewChunk' } + | { tag: 'LowerName', value: string } + | { tag: 'UpperName', value: string } + | { tag: 'Other', value: string } + } Token + * + * @typedef { + | { tag: 'Initial', otherTokenChars: string } + | { tag: 'MaybeNewChunk' } + | { tag: 'MaybeMultilineComment{' } + | { tag: 'MultilineComment', level: number } + | { tag: 'MultilineComment{', level: number } + | { tag: 'MultilineComment-', level: number } + | { tag: 'MaybeSinglelineComment-' } + | { tag: 'SinglelineComment' } + | { tag: 'Maybe..' } + | { tag: 'CharStart' } + | { tag: 'CharBackslash' } + | { tag: 'CharUnicodeEscape' } + | { tag: 'CharEnd' } + | { tag: 'StringStart' } + | { tag: 'StringContent' } + | { tag: 'StringBackslash' } + | { tag: 'EmptyStringMaybeTriple' } + | { tag: 'MultilineString' } + | { tag: 'MultilineStringBackslash' } + | { tag: 'MultilineString"' } + | { tag: 'MultilineString""' } + } TokenizerState + * + * @typedef { + | { tag: 'Flush' } + | { tag: 'FlushToken', token: Token } + } Cmd + */ /** * @param { string } char - * @param { typeof TokenizerState } tokenizerState - * @returns { - | [ - typeof TokenizerState, - Array<{ tag: 'Flush' } | { tag: 'FlushToken', token: typeof Token }> - ] - | typeof ParseError } + * @param { TokenizerState } tokenizerState + * @returns { [ TokenizerState, Array ] | ParseError } */ function tokenize(char, tokenizerState) { switch (tokenizerState.tag) { @@ -719,8 +708,10 @@ function tokenize(char, tokenizerState) { } /** - * @param { TODO } previousChar, char, cmds - * @returns { TODO } + * @param { string } previousChar + * @param { string } char + * @param { Array } cmds + * @returns { [ TokenizerState, Array ] } */ function tokenizeInitial(previousChar, char, cmds) { const result = tokenize(char, { @@ -734,42 +725,45 @@ function tokenizeInitial(previousChar, char, cmds) { return [nextTokenizerState, cmds.concat(nextCmds)]; } -const ParserState /*: - | { - tag: 'ModuleDeclaration', - lastToken: typeof ModuleDeclarationLastToken, - } - | { - tag: 'Rest', - lastToken: typeof RestLastToken, - } */ = { tag: 'ModuleDeclaration', lastToken: 'Nothing' }; -void ParserState; - -const ModuleDeclarationLastToken /*: - | 'Nothing' - | 'NewChunk' - | 'port' - | 'module' - | 'ModuleName' - | 'ModuleName.' - | 'exposing' - | 'exposing(' - | 'exposing..' - | 'LowerName' - | 'UpperName' - | 'UpperName(' - | 'UpperName..' - | 'UpperName)' - | ',' */ = 'Nothing'; -void ModuleDeclarationLastToken; +/** + * @typedef { + | { + tag: 'ModuleDeclaration', + lastToken: ModuleDeclarationLastToken, + } + | { + tag: 'Rest', + lastToken: RestLastToken, + } + } ParserState + * + * @typedef { + | 'Nothing' + | 'NewChunk' + | 'port' + | 'module' + | 'ModuleName' + | 'ModuleName.' + | 'exposing' + | 'exposing(' + | 'exposing..' + | 'LowerName' + | 'UpperName' + | 'UpperName(' + | 'UpperName..' + | 'UpperName)' + | ',' + } ModuleDeclarationLastToken + */ /** - * @param { typeof Token } token - * @param { typeof ModuleDeclarationLastToken } lastToken + * @param { Token } token + * @param { ModuleDeclarationLastToken } lastToken * @returns { - | typeof ModuleDeclarationLastToken - | { tag: 'NextParserState' } - | typeof OnParserTokenResult } + | ModuleDeclarationLastToken + | { tag: 'NextParserState' } + | OnParserTokenResult + } */ function parseModuleDeclaration(token, lastToken) { switch (lastToken) { @@ -905,18 +899,20 @@ function parseModuleDeclaration(token, lastToken) { } } -const RestLastToken /*: - | 'Initial' - | 'NewChunk' - | 'PotentialTestDeclarationName' - | 'PotentialTestDeclaration=' - | 'Ignore' */ = 'Initial'; -void RestLastToken; +/** + * @typedef { + | 'Initial' + | 'NewChunk' + | 'PotentialTestDeclarationName' + | 'PotentialTestDeclaration=' + | 'Ignore' + } RestLastToken + */ /** - * @param { typeof Token } token - * @param { typeof RestLastToken } lastToken - * @returns { typeof RestLastToken | typeof ParseError } + * @param { Token } token + * @param { RestLastToken } lastToken + * @returns { RestLastToken | ParseError } */ function parseRest(token, lastToken) { switch (lastToken) { diff --git a/lib/Project.js b/lib/Project.js index 9687fe92..7a266275 100644 --- a/lib/Project.js +++ b/lib/Project.js @@ -4,21 +4,15 @@ const fs = require('fs'); const path = require('path'); const ElmJson = require('./ElmJson'); -// Poor man’s type alias. We can’t use /*:: type Project = ... */ because of: -// https://github.com/prettier/prettier/issues/2597 -const Project /*: { - rootDir: string, - testsDir: string, - generatedCodeDir: string, - testsSourceDirs: Array, - elmJson: typeof ElmJson.ElmJson, -} */ = { - rootDir: '', - testsDir: '', - generatedCodeDir: '', - testsSourceDirs: [], - elmJson: ElmJson.ElmJson, -}; +/** + * @typedef { { + rootDir: string, + testsDir: string, + generatedCodeDir: string, + testsSourceDirs: Array, + elmJson: TODO ElmJson.ElmJson, + } } Project + */ /** * @param { string } rootDir @@ -31,7 +25,7 @@ function getTestsDir(rootDir) { /** * @param { string } rootDir * @param { string } version - * @returns { typeof Project } + * @returns { Project } */ function init(rootDir, version) { const testsDir = getTestsDir(rootDir); @@ -85,7 +79,7 @@ following directory: I cannot find it though. Is it missing? Is there a typo? */ /** - * @param { typeof Project } project + * @param { Project } project * @returns { void } */ function validateTestsSourceDirs(project) { @@ -116,8 +110,9 @@ function validateTestsSourceDirs(project) { } /** - * @param { TODO } dir, message - * @returns { TODO } + * @param { string } dir + * @param { string } message + * @returns { string } */ function validateTestsSourceDirsError(dir, message) { return ` @@ -130,7 +125,6 @@ ${message} } module.exports = { - Project, getTestsDir, init, validateTestsSourceDirs, diff --git a/lib/Report.js b/lib/Report.js index 5b1bc417..5ba1d9fc 100644 --- a/lib/Report.js +++ b/lib/Report.js @@ -1,13 +1,13 @@ // @flow -// Poor man’s type alias. We can’t use /*:: type Report = ... */ because of: -// https://github.com/prettier/prettier/issues/2597 -const Report /*: 'console' | 'json' | 'junit' */ = 'console'; +/** + * @typedef { 'console' | 'json' | 'junit' } Report + */ const all = ['json', 'junit', 'console']; /** - * @param { typeof Report } report + * @param { Report } report * @returns { boolean } */ function isMachineReadable(report) { @@ -21,7 +21,6 @@ function isMachineReadable(report) { } module.exports = { - Report, all, isMachineReadable, }; diff --git a/lib/SyncGet.js b/lib/SyncGet.js index 32f5f427..3aa33a46 100644 --- a/lib/SyncGet.js +++ b/lib/SyncGet.js @@ -5,25 +5,24 @@ const { Worker, MessageChannel, receiveMessageOnPort, - // $FlowFixMe[cannot-resolve-module]: Flow doesn’t seem to know about the `worker_threads` module yet. } = require('worker_threads'); -// Poor man’s type alias. We can’t use /*:: type SyncGetWorker = ... */ because of: -// https://github.com/prettier/prettier/issues/2597 -const SyncGetWorker /*: { - get: (string) => string, - shutDown: () => void, -} */ = { - get: (string) => string, - shutDown: () => {}, -}; +/** + * @typedef { { + get: (key: string) => string, + shutDown: () => void, + } } SyncGetWorker + */ -// Start a worker thread and return a `syncGetWorker` -// capable of making sync requests until shut down. -function startWorker() /*: typeof SyncGetWorker */ { +/** + * Start a worker thread and return a `syncGetWorker` + * capable of making sync requests until shut down. + * + * @returns { SyncGetWorker } + */ +function startWorker() { const { port1: localPort, port2: workerPort } = new MessageChannel(); const sharedLock = new SharedArrayBuffer(4); - // $FlowFixMe[incompatible-call]: Flow is wrong and says `sharedLock` is not an accepted parameter here. const sharedLockArray = new Int32Array(sharedLock); const workerPath = path.resolve(__dirname, 'SyncGetWorker.js'); const worker = new Worker(workerPath, { @@ -48,6 +47,5 @@ function startWorker() /*: typeof SyncGetWorker */ { } module.exports = { - SyncGetWorker, startWorker, }; diff --git a/lib/SyncGetWorker.js b/lib/SyncGetWorker.js index 859b2f9d..701a85c3 100644 --- a/lib/SyncGetWorker.js +++ b/lib/SyncGetWorker.js @@ -1,6 +1,5 @@ // @flow -// $FlowFixMe[cannot-resolve-module]: Flow doesn’t seem to know about the `worker_threads` module yet. const { parentPort, workerData } = require('worker_threads'); const https = require('https'); From 9fefa2e120b7b0d400b2cdd29cebf5cc7a7a6a3e Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sat, 11 Jul 2026 17:24:42 +0200 Subject: [PATCH 03/14] Fix all type imports --- lib/DependencyProvider.js | 4 ++-- lib/FindTests.js | 2 +- lib/Generate.js | 8 ++++---- lib/Install.js | 2 +- lib/RunTests.js | 4 ++-- lib/Solve.js | 6 +++--- lib/Supervisor.js | 2 +- lib/elm-test.js | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/DependencyProvider.js b/lib/DependencyProvider.js index abd2fed6..26a24c72 100644 --- a/lib/DependencyProvider.js +++ b/lib/DependencyProvider.js @@ -11,8 +11,8 @@ const collator = new Intl.Collator('en', { numeric: true }); // for sorting SemV wasm.init(); // Lazily start the worker until needed. // This is important for the tests, which never exit otherwise. -let syncGetWorker_ /*: void | typeof SyncGet.SyncGetWorker */ = undefined; -function syncGetWorker() /*: typeof SyncGet.SyncGetWorker */ { +let syncGetWorker_ /*: void | import('./SyncGet').SyncGetWorker */ = undefined; +function syncGetWorker() /*: import('./SyncGet').SyncGetWorker */ { if (syncGetWorker_ === undefined) { syncGetWorker_ = SyncGet.startWorker(); } diff --git a/lib/FindTests.js b/lib/FindTests.js index a6287502..595734ef 100644 --- a/lib/FindTests.js +++ b/lib/FindTests.js @@ -106,7 +106,7 @@ function findAllElmFilesInDir(dir) { /** * @param { Array } testFilePaths - * @param { typeof Project.Project } project + * @param { import('./Project').Project } project * @returns { Promise }>> } */ function findTests(testFilePaths, project) { diff --git a/lib/Generate.js b/lib/Generate.js index eb35b438..50aebfde 100644 --- a/lib/Generate.js +++ b/lib/Generate.js @@ -92,7 +92,7 @@ function getGeneratedSrcDir(generatedCodeDir) { /** * @param { DependencyProvider } dependencyProvider - * @param { typeof Project.Project } project + * @param { import('./Project').Project } project * @returns { void } */ function generateElmJson(dependencyProvider, project) { @@ -173,7 +173,7 @@ function getMainModule(generatedCodeDir) { /** * @param { number } fuzz * @param { number } seed - * @param { typeof Report.Report } report + * @param { import('./Report').Report } report * @param { Array } testFileGlobs * @param { Array } testFilePaths * @param { Array<{ @@ -288,7 +288,7 @@ function indentAllButFirstLine(indent, string) { /** * @param { number } fuzz * @param { number } seed - * @param { typeof Report.Report } report + * @param { import('./Report').Report } report * @param { Array } testFileGlobs * @param { Array } testFilePaths * @param { number } processes @@ -316,7 +316,7 @@ function makeOptsCode( } /** - * @param { typeof Report.Report } report + * @param { import('./Report').Report } report * @returns { string } */ function generateElmReportVariant(report) { diff --git a/lib/Install.js b/lib/Install.js index 1cd6ac6c..94fbd41a 100644 --- a/lib/Install.js +++ b/lib/Install.js @@ -26,7 +26,7 @@ function rmDirSync(dir) { } /** - * @param { typeof Project.Project } project + * @param { import('./Project').Project } project * @param { string } pathToElmBinary * @param { string } packageName * @returns { 'SuccessfullyInstalled' | 'AlreadyInstalled' } diff --git a/lib/RunTests.js b/lib/RunTests.js index 6890f8cb..0e4a2133 100644 --- a/lib/RunTests.js +++ b/lib/RunTests.js @@ -49,7 +49,7 @@ function getPipeFilename(runsExecuted) { * Also note that using too much `clearLine` or `clearConsole` causes flickering * on Windows, so it's nicer to cleverly overwrite old output when possible. * - * @param { typeof Report.Report } report + * @param { import('./Report').Report } report * @param { boolean } clearConsole * @returns { TODO } */ @@ -146,7 +146,7 @@ function watcherEventMessage(queue) { * @param { { watch: boolean, clearConsole: boolean, - report: typeof Report.Report, + report: import('./Report').Report, seed: number, fuzz: number, } } { diff --git a/lib/Solve.js b/lib/Solve.js index ecf641e7..02fefa4b 100644 --- a/lib/Solve.js +++ b/lib/Solve.js @@ -22,8 +22,8 @@ function sha256(string) { /** * @param { DependencyProvider } dependencyProvider - * @param { typeof Project.Project } project - * @returns { typeof ElmJson.DirectAndIndirectDependencies } + * @param { import('./Project').Project } project + * @returns { import('./ElmJson').DirectAndIndirectDependencies } */ function getDependenciesCached(dependencyProvider, project) { const hash = sha256( @@ -60,7 +60,7 @@ function getDependenciesCached(dependencyProvider, project) { /** * @param { DependencyProvider } dependencyProvider - * @param { typeof ElmJson.ElmJson } elmJson + * @param { import('./ElmJson').ElmJson } elmJson * @returns { string } */ function getDependencies(dependencyProvider, elmJson) { diff --git a/lib/Supervisor.js b/lib/Supervisor.js index 333c94ea..ea909f82 100644 --- a/lib/Supervisor.js +++ b/lib/Supervisor.js @@ -10,7 +10,7 @@ const Report = require('./Report'); /** * @param { string } elmTestVersion * @param { string } pipeFilename - * @param { typeof Report.Report } report + * @param { import('./Report').Report } report * @param { number } processes * @param { string } dest * @param { boolean } watch diff --git a/lib/elm-test.js b/lib/elm-test.js index f07a9c17..4c9a222a 100644 --- a/lib/elm-test.js +++ b/lib/elm-test.js @@ -65,7 +65,7 @@ function getProjectRootDir(subcommand) { /** * @param { string } subcommand - * @returns { typeof Project.Project } + * @returns { import('./Project').Project } */ function getProject(subcommand) { try { From 5607361dec279b7f33f37b8031fd3f27b3458ef2 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sat, 11 Jul 2026 18:59:13 +0200 Subject: [PATCH 04/14] Go through all files --- lib/DependencyProvider.js | 128 ++++++++++++++++++++++++-------------- lib/ElmCompiler.js | 50 +++++++++------ lib/ElmHome.js | 26 +++++--- lib/FindTests.js | 26 ++++---- lib/Generate.js | 34 ++++------ lib/Install.js | 3 - lib/Parser.js | 104 ++++++++++++++++++------------- lib/RunTests.js | 56 +++++++++-------- lib/Solve.js | 11 +--- lib/Supervisor.js | 10 +-- lib/elm-test.js | 29 ++++----- lib/supports-color.js | 7 ++- 12 files changed, 272 insertions(+), 212 deletions(-) diff --git a/lib/DependencyProvider.js b/lib/DependencyProvider.js index 26a24c72..2ee8efb5 100644 --- a/lib/DependencyProvider.js +++ b/lib/DependencyProvider.js @@ -11,8 +11,12 @@ const collator = new Intl.Collator('en', { numeric: true }); // for sorting SemV wasm.init(); // Lazily start the worker until needed. // This is important for the tests, which never exit otherwise. -let syncGetWorker_ /*: void | import('./SyncGet').SyncGetWorker */ = undefined; -function syncGetWorker() /*: import('./SyncGet').SyncGetWorker */ { +/** @type { undefined | import('./SyncGet').SyncGetWorker } */ +let syncGetWorker_ = undefined; +/** + * @returns { import('./SyncGet').SyncGetWorker } + */ +function syncGetWorker() { if (syncGetWorker_ === undefined) { syncGetWorker_ = SyncGet.startWorker(); } @@ -21,8 +25,12 @@ function syncGetWorker() /*: import('./SyncGet').SyncGetWorker */ { // Cache of existing versions according to the package website. class OnlineVersionsCache { - map /*: Map> */ = new Map(); + /** @type { Map> } */ + map = new Map(); + /** + * @returns { void } + */ update() { const pubgrubHome = path.join(ElmHome.elmHome(), 'pubgrub'); fs.mkdirSync(pubgrubHome, { recursive: true }); @@ -49,11 +57,14 @@ class OnlineVersionsCache { this.updateWithRequestSince(cachePath, remotePackagesUrl); } - // Update the cache with a request to the package server. - updateWithRequestSince( - cachePath /*: string */, - remotePackagesUrl /*: string */ - ) /*: void */ { + /** + * Update the cache with a request to the package server. + * + * @param { string } cachePath + * @param { string } remotePackagesUrl + * @returns { void } + */ + updateWithRequestSince(cachePath, remotePackagesUrl) { // Count existing versions. let versionsCount = 0; for (const versions of this.map.values()) { @@ -95,26 +106,39 @@ class OnlineVersionsCache { } } - getVersions(pkg /*: string */) /*: Array */ { + /** + * @param { string } pkg + * @returns { Array } + */ + getVersions(pkg) { const versions = this.map.get(pkg); return versions === undefined ? [] : versions; } } class OnlineAvailableVersionLister { - // Memoization cache to avoid doing the same work twice in list. - memoCache /*: Map> */ = new Map(); - onlineCache /*: OnlineVersionsCache */; - - constructor(onlineCache /*: OnlineVersionsCache */) { + /** + * Memoization cache to avoid doing the same work twice in list. + * @type { Map> } + */ + memoCache = new Map(); + /** @type { OnlineVersionsCache } */ + onlineCache; + + /** + * @param {OnlineVersionsCache} onlineCache + */ + constructor(onlineCache) { onlineCache.update(); this.onlineCache = onlineCache; } - list( - pkg /*: string */, - pinnedVersion /*: void | string */ - ) /*: Array */ { + /** + * @param { string } pkg + * @param { undefined | string } pinnedVersion + * @returns { Array } + */ + list(pkg, pinnedVersion) { const memoVersions = this.memoCache.get(pkg); if (memoVersions !== undefined) { return prioritizePinnedIndirectVersion(memoVersions, pinnedVersion); @@ -132,13 +156,18 @@ class OnlineAvailableVersionLister { } class OfflineAvailableVersionLister { - // Memoization cache to avoid doing the same work twice in list. - cache /*: Map> */ = new Map(); - - list( - pkg /*: string */, - pinnedVersion /*: void | string */ - ) /*: Array */ { + /** + * Memoization cache to avoid doing the same work twice in list. + * @type { Map> } + */ + cache = new Map(); + + /** + * @param { string } pkg + * @param { undefined | string } pinnedVersion + * @returns { Array } + */ + list(pkg, pinnedVersion) { const memoVersions = this.cache.get(pkg); if (memoVersions !== undefined) { return prioritizePinnedIndirectVersion(memoVersions, pinnedVersion); @@ -170,14 +199,18 @@ function readVersionsInElmHomeAndSort(pkg) { } class DependencyProvider { - cache /*: OnlineVersionsCache */ = new OnlineVersionsCache(); - - // Solve dependencies completely offline, without any http request. - solveOffline( - elmJson /*: string */, - useTest /*: boolean */, - extra /*: { [string]: string } */ - ) /*: string */ { + /** @type { OnlineVersionsCache } */ + cache = new OnlineVersionsCache(); + + /** + * Solve dependencies completely offline, without any http request. + * + * @param { string } elmJson + * @param { boolean } useTest + * @param { Record } extra + * @returns { string } + */ + solveOffline(elmJson, useTest, extra) { const lister = new OfflineAvailableVersionLister(); const dependencies = JSON.parse(elmJson).dependencies; const indirectDeps = @@ -200,12 +233,15 @@ class DependencyProvider { } } - // Solve dependencies with http requests when required. - solveOnline( - elmJson /*: string */, - useTest /*: boolean */, - extra /*: { [string]: string } */ - ) /*: string */ { + /** + * Solve dependencies with http requests when required. + * + * @param { string } elmJson + * @param { boolean } useTest + * @param { Record } extra + * @returns { string } + */ + solveOnline(elmJson, useTest, extra) { const lister = new OnlineAvailableVersionLister(this.cache); const dependencies = JSON.parse(elmJson).dependencies; const indirectDeps = @@ -309,8 +345,7 @@ function onlineVersionsFromScratch(cachePath, remotePackagesUrl) { * the real live application. * * Assumes versions is sorted descending (newest -> oldest). - */ -/** + * * @param { Array } versions * @param { void | string } pinnedVersion * @returns { Array } @@ -331,8 +366,9 @@ function prioritizePinnedIndirectVersion(versions, pinnedVersion) { return desirableVersions.concat(olderVersions); } -/* Compares two versions so that newer versions appear first when sorting with this function. */ /** + * Compares two versions so that newer versions appear first when sorting with this function. + * * @param { string } a * @param { string } b * @returns { number } @@ -385,7 +421,7 @@ function parseVersions(key, json) { } } - // $FlowFixMe[incompatible-return]: We dynamically checked that `json` is an `Array`. + // TODO $FlowFixMe[incompatible-return]: We dynamically checked that `json` is an `Array`. return json; } @@ -428,9 +464,9 @@ function homeElmJsonPath(pkg, version) { /** * @param { string } str * @returns { { - pkg: string, - version: string, -} } + pkg: string, + version: string, + } } */ function splitPkgVersion(str) { const parts = str.split('@'); diff --git a/lib/ElmCompiler.js b/lib/ElmCompiler.js index a0f6ebda..861f4540 100644 --- a/lib/ElmCompiler.js +++ b/lib/ElmCompiler.js @@ -4,6 +4,22 @@ var spawn = require('cross-spawn'); +/** + * @typedef { { + spawn?: ( + string, + Array, + child_process$spawnOpts + ) => child_process$ChildProcess, + cwd?: string, + pathToElm?: string, + output?: string, + report?: 'json', + processOpts?: child_process$spawnOpts, + } } Options + */ + +/** @type { Options } */ var defaultOptions = { spawn: spawn, cwd: undefined, @@ -17,8 +33,8 @@ var defaultOptions = { * Converts an object of key/value pairs to an array of arguments suitable * to be passed to child_process.spawn for elm-make. * - * @param { TODO } options - * @returns { TODO } + * @param { Options } options + * @returns { Array } */ function compilerArgsFromOptions(options) { var args = []; @@ -35,7 +51,8 @@ function compilerArgsFromOptions(options) { } /** - * @param { TODO } sources, options + * @param { Array } sources + * @param { Options } options * @returns { TODO } */ function runCompiler(sources, options) { @@ -56,8 +73,9 @@ function runCompiler(sources, options) { } /** - * @param { TODO } err, pathToElm - * @returns { TODO } + * @param { unknown } err + * @param { string } pathToElm + * @returns { string } */ function compilerErrorToString(err, pathToElm) { if (typeof err === 'object' && typeof err.code === 'string') { @@ -89,21 +107,13 @@ function compilerErrorToString(err, pathToElm) { } } -function compile( - sources /*: Array */, - options /*: {| - spawn?: ( - string, - Array, - child_process$spawnOpts - ) => child_process$ChildProcess, - cwd?: string, - pathToElm?: string, - output?: string, - report?: 'json', - processOpts?: child_process$spawnOpts, - |} */ -) /*: child_process$ChildProcess */ { +/** + * + * @param { Array } sources + * @param { Options } options + * @returns { TODO child_process$ChildProcess } + */ +function compile(sources, options) { var optionsWithDefaults = Object.assign({}, defaultOptions, options); try { return runCompiler(sources, optionsWithDefaults).on( diff --git a/lib/ElmHome.js b/lib/ElmHome.js index 698fb56f..99d1df80 100644 --- a/lib/ElmHome.js +++ b/lib/ElmHome.js @@ -3,22 +3,34 @@ const path = require('path'); const os = require('os'); -function elmHome() /*: string */ { +/** + * @returns { string } + */ +function elmHome() { const elmHomeEnv = process.env['ELM_HOME']; return elmHomeEnv === undefined ? defaultElmHome() : elmHomeEnv; } -function defaultElmHome() /*: string */ { +/** + * @returns { string } + */ +function defaultElmHome() { return process.platform === 'win32' ? defaultWindowsElmHome() : defaultUnixElmHome(); } -function defaultUnixElmHome() /*: string */ { +/** + * @returns { string } + */ +function defaultUnixElmHome() { return path.join(os.homedir(), '.elm'); } -function defaultWindowsElmHome() /*: string */ { +/** + * @returns { string } + */ +function defaultWindowsElmHome() { const appData = process.env.APPDATA; const dir = appData === undefined @@ -39,9 +51,9 @@ function packagePath(pkg) { /** * @param { string } pkgIdentifier * @returns { { - author: string, - pkg: string, -} } + author: string, + pkg: string, + } } */ function splitAuthorPkg(pkgIdentifier) { const parts = pkgIdentifier.split('/'); diff --git a/lib/FindTests.js b/lib/FindTests.js index 595734ef..bf734027 100644 --- a/lib/FindTests.js +++ b/lib/FindTests.js @@ -5,9 +5,6 @@ const fs = require('fs'); const { globSync } = require('tinyglobby'); const path = require('path'); const Parser = require('./Parser'); -const Project = require('./Project'); - -void Project; // Double stars at the start and end is the correct way to ignore directories in // the `glob` package. @@ -176,8 +173,9 @@ function findTests(testFilePaths, project) { } /** - * @param { TODO } filePath, isPackageProject - * @returns { TODO } + * @param { string } filePath + * @param { boolean } isPackageProject + * @returns { string } */ function missingSourceDirectoryError(filePath, isPackageProject) { return ` @@ -196,10 +194,10 @@ ${ } /** - * @param { TODO } filePath, - matchingSourceDirs, - testsDir - * @returns { TODO } + * @param { string } filePath + * @param { string } matchingSourceDirs + * @param { string } testsDir + * @returns { string } */ function multipleSourceDirectoriesError( filePath, @@ -226,8 +224,10 @@ ${note} } /** - * @param { TODO } filePath, sourceDir, moduleName - * @returns { TODO } + * @param { string } filePath + * @param { string } sourceDir + * @param { string } moduleName + * @returns { string } */ function badModuleNameError(filePath, sourceDir, moduleName) { return ` @@ -277,8 +277,8 @@ Are the above patterns correct? Maybe try running elm-test with no arguments? } /** - * @param { TODO } projectRootDir - * @returns { TODO } + * @param { string } projectRootDir + * @returns { string } */ function noFilesFoundInTestsDir(projectRootDir) { const testsDir = path.join(projectRootDir, 'tests'); diff --git a/lib/Generate.js b/lib/Generate.js index 50aebfde..32d17b35 100644 --- a/lib/Generate.js +++ b/lib/Generate.js @@ -3,18 +3,9 @@ const { supportsColor } = require('./chalk'); const fs = require('fs'); const path = require('path'); -const { DependencyProvider } = require('./DependencyProvider.js'); const ElmJson = require('./ElmJson'); -const Project = require('./Project'); -const Report = require('./Report'); const Solve = require('./Solve'); -// These values are used _only_ in flow types. 'use' them with the javascript -// void operator to keep eslint happy. -void DependencyProvider; -void Project; -void Report; - const before = fs.readFileSync( path.join(__dirname, '..', 'templates', 'before.js'), 'utf8' @@ -67,8 +58,8 @@ const checkDefinition = * Create a symbol, tag all `Test` constructors with it and make the `check` * function look for it. * - * @param { TODO } content - * @returns { TODO } + * @param { string } content + * @returns { string } */ function addKernelTestChecking(content) { return ( @@ -154,9 +145,9 @@ function generateElmJson(dependencyProvider, project) { /** * @param { string } generatedCodeDir * @returns { { - moduleName: string, - path: string, -} } + moduleName: string, + path: string, + } } */ function getMainModule(generatedCodeDir) { const moduleName = ['Test', 'Generated', 'Main']; @@ -237,9 +228,9 @@ main = /** * @param { { - moduleName: string, - possiblyTests: Array, -} } mod + moduleName: string, + possiblyTests: Array, + } } mod * @returns { string } */ function makeModuleTuple(mod) { @@ -275,8 +266,9 @@ function makeList(parts) { } /** - * @param { TODO } indent, string - * @returns { TODO } + * @param { number } indent + * @param { string } string + * @returns { string } */ function indentAllButFirstLine(indent, string) { return string @@ -335,8 +327,8 @@ function generateElmReportVariant(report) { } /** - * @param { TODO } string - * @returns { TODO } + * @param { string } string + * @returns { string } */ function makeElmString(string) { return `"${string diff --git a/lib/Install.js b/lib/Install.js index 94fbd41a..84cef6bc 100644 --- a/lib/Install.js +++ b/lib/Install.js @@ -4,9 +4,6 @@ const spawn = require('cross-spawn'); const fs = require('fs'); const path = require('path'); const ElmJson = require('./ElmJson'); -const Project = require('./Project'); - -void Project; /** * @param { string } dir diff --git a/lib/Parser.js b/lib/Parser.js index 4a996201..a36d5cd1 100644 --- a/lib/Parser.js +++ b/lib/Parser.js @@ -3,38 +3,44 @@ // Useful for debugging. const LOG_ERRORS = 'ELM_TEST_LOG_PARSE_ERRORS' in process.env; -// For valid Elm files, this extracts _all_ (no more, no less) names that: -// 1. Are exposed. -// 2. _Might_ be tests. So capitalized names are excluded, for example. -// -// For invalid Elm files, this probably returns an empty list. It could also -// return a list of things it _thinks_ are exposed values, but it doesn’t -// matter. The idea is to bail early and still import the file. Then Elm gets a -// chance to show its nice error messages. -// -// The only times the returned promise is rejected are: -// -// - When there’s a problem reading the file from disk. -// - If there’s a bug and an unexpected exception is thrown somewhere. -// - If `effect module` is encountered. That’s a real edge case. The gist of it -// is that applications can’t contain effect modules and since the tests are -// compiled as an application we can’t include effect modules. -// -// The tokenizer reads the file character by character. As soon as it’s produced -// a whole token it feeds it to the parser, which works token by token. Both -// parse just enough to be able to extract all exposed names that could be tests -// without false positives. -function extractExposedPossiblyTests( - filePath /*: string */, - createReadStream /*: ( +/** + * For valid Elm files, this extracts _all_ (no more, no less) names that: + * 1. Are exposed. + * 2. _Might_ be tests. So capitalized names are excluded, for example. + * + * For invalid Elm files, this probably returns an empty list. It could also + * return a list of things it _thinks_ are exposed values, but it doesn’t + * matter. The idea is to bail early and still import the file. Then Elm gets a + * chance to show its nice error messages. + * + * The only times the returned promise is rejected are: + * + * - When there’s a problem reading the file from disk. + * - If there’s a bug and an unexpected exception is thrown somewhere. + * - If `effect module` is encountered. That’s a real edge case. The gist of it + * is that applications can’t contain effect modules and since the tests are + * compiled as an application we can’t include effect modules. + * + * The tokenizer reads the file character by character. As soon as it’s produced + * a whole token it feeds it to the parser, which works token by token. Both + * parse just enough to be able to extract all exposed names that could be tests + * without false positives. + * + * @param { string } filePath + * @param { ( path: string, - options?: { encoding?: string, ... } - ) => stream$Readable & { close(): void } */ -) /*: Promise> */ { + options?: { encoding?: string } + ) => TODO stream$Readable & { close(): void } } createReadStream + * @returns { Promise> } + */ +function extractExposedPossiblyTests(filePath, createReadStream) { return new Promise((resolve, reject) => { - const exposedPossiblyTests /*: Array */ = []; - let tokenizerState /*: typeof TokenizerState */ = { tag: 'MaybeNewChunk' }; - let parserState /*: typeof ParserState */ = { + /** @type { Array } */ + const exposedPossiblyTests = []; + /** @type { TokenizerState } */ + let tokenizerState = { tag: 'MaybeNewChunk' }; + /** @type { ParserState } */ + let parserState = { tag: 'ModuleDeclaration', lastToken: 'Nothing', }; @@ -51,7 +57,11 @@ function extractExposedPossiblyTests( resolve(exposedPossiblyTests); }); - function onData(chunk /*: string */) /*: void */ { + /** + * @param { string } chunk + * @returns { void } + */ + function onData(chunk) { for (let index = 0; index < chunk.length; index++) { const char = chunk[index]; @@ -113,9 +123,11 @@ function extractExposedPossiblyTests( } } - function flush( - token /*: typeof Token | void */ - ) /*: typeof OnParserTokenResult | void */ { + /** + * @param { Token | undefined } token + * @returns { OnParserTokenResult | undefined } + */ + function flush(token) { if ( tokenizerState.tag === 'Initial' && tokenizerState.otherTokenChars !== '' @@ -139,9 +151,11 @@ function extractExposedPossiblyTests( return undefined; } - function stop( - result /*: typeof OnParserTokenResult */ - ) /*: Array */ { + /** + * @param { OnParserTokenResult } result + * @returns { Array } + */ + function stop(result) { switch (result.tag) { case 'ParseError': if (LOG_ERRORS) { @@ -162,9 +176,11 @@ function extractExposedPossiblyTests( } } - function onParserToken( - token /*: typeof Token */ - ) /*: typeof OnParserTokenResult | void */ { + /** + * @param { Token } token + * @returns { OnParserTokenResult | undefined } + */ + function onParserToken(token) { if (token.tag === 'LowerName') { lastLowerName = token.value; } @@ -274,8 +290,8 @@ const backslashableChars = new Set([ ]); /** - * @param { TODO } string - * @returns { TODO } + * @param { string } string + * @returns { boolean } */ function isLowerName(string) { return lowerName.test(string) && !reservedWords.has(string); @@ -290,8 +306,8 @@ function isUpperName(string) { } /** - * @param { empty } value - * @returns { empty } + * @param { never } value + * @returns { never } */ function unreachable(value) { throw new Error(`Unreachable: ${value}`); diff --git a/lib/RunTests.js b/lib/RunTests.js index 0e4a2133..0f73bb4e 100644 --- a/lib/RunTests.js +++ b/lib/RunTests.js @@ -5,7 +5,6 @@ const path = require('path'); const readline = require('readline'); const packageInfo = require('../package.json'); const Compile = require('./Compile'); -const { DependencyProvider } = require('./DependencyProvider.js'); const ElmJson = require('./ElmJson'); const FindTests = require('./FindTests'); const Generate = require('./Generate'); @@ -13,10 +12,6 @@ const Project = require('./Project'); const Report = require('./Report'); const Supervisor = require('./Supervisor'); -// This value is used _only_ in flow types. 'use' it with the javascript -// void operator to keep eslint happy. -void DependencyProvider; - /** * Incorporate the process PID into the socket name, so elm-test processes can * be run parallel without accidentally sharing each others' sockets. @@ -51,7 +46,13 @@ function getPipeFilename(runsExecuted) { * * @param { import('./Report').Report } report * @param { boolean } clearConsole - * @returns { TODO } + * @returns { { + log: (message: string) => void, + newLine: () => void, + overwrite: (message: string) => void, + clearLine: () => void, + clearConsole: () => void, + } } */ function makeProgressLogger(report, clearConsole) { const items = []; @@ -94,7 +95,8 @@ function makeProgressLogger(report, clearConsole) { }; } -/**TODO generics: T +/** + * @template { T } * @param { Array } from * @param { Array } to * @returns { { added: Array, removed: Array } } @@ -116,14 +118,15 @@ function delay(ms) { }); } -const Queue /*: Array<{ - event: 'added' | 'changed' | 'removed', - filePath: string, -}> */ = []; -void Queue; +/** + * @typedef { Array<{ + event: 'added' | 'changed' | 'removed', + filePath: string, + }> } Queue + */ /** - * @param { typeof Queue } queue + * @param { Queue } queue * @returns { string } */ function watcherEventMessage(queue) { @@ -149,13 +152,7 @@ function watcherEventMessage(queue) { report: import('./Report').Report, seed: number, fuzz: number, - } } { - watch, - clearConsole, - report, - seed, - fuzz, - } + } } options * @returns { Promise } */ function runTests( @@ -167,14 +164,21 @@ function runTests( { watch, clearConsole, report, seed, fuzz } ) { let watcher = undefined; - let watchedPaths /*: Array */ = []; - let runsExecuted /*: number */ = 0; - let currentRun /*: Promise | void */ = undefined; - let queue /*: typeof Queue */ = []; + /** @type { Array } */ + let watchedPaths = []; + /** @type { number } */ + let runsExecuted = 0; + /** @type { Promise | void } */ + let currentRun = undefined; + /** @type { Queue } */ + let queue = []; const progressLogger = makeProgressLogger(report, clearConsole); - async function run() /*: Promise */ { + /** + * @returns { Promise } + */ + async function run() { try { // Don’t delay the first run (that’s the only time the queue is empty). // Otherwise, wait for a little bit to batch events that happened roughly @@ -238,7 +242,7 @@ function runTests( const mainModule = Generate.getMainModule(project.generatedCodeDir); const dest = path.join(project.generatedCodeDir, 'elmTestOutput.js'); - await Generate.generateElmJson(dependencyProvider, project); + Generate.generateElmJson(dependencyProvider, project); progressLogger.log('Compiling'); diff --git a/lib/Solve.js b/lib/Solve.js index 02fefa4b..253c8990 100644 --- a/lib/Solve.js +++ b/lib/Solve.js @@ -4,17 +4,10 @@ const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); const ElmJson = require('./ElmJson'); -const Project = require('./Project'); -const { DependencyProvider } = require('./DependencyProvider.js'); - -// These value are used _only_ in flow types. 'use' them with the javascript -// void operator to keep eslint happy. -void Project; -void DependencyProvider; /** - * @param { TODO } string - * @returns { TODO } + * @param { string } string + * @returns { string } */ function sha256(string) { return crypto.createHash('sha256').update(string).digest('hex'); diff --git a/lib/Supervisor.js b/lib/Supervisor.js index ea909f82..2eaa28f5 100644 --- a/lib/Supervisor.js +++ b/lib/Supervisor.js @@ -326,8 +326,8 @@ function run(elmTestVersion, pipeFilename, report, processes, dest, watch) { } /** - * @param { TODO } text - * @returns { TODO } + * @param { string } text + * @returns { string } */ function makeWindowsSafe(text) { return process.platform === 'win32' ? windowsify(text) : text; @@ -343,11 +343,11 @@ var windowsSubstitutions = [ ]; /** - * @param { TODO } str - * @returns { TODO } + * @param { string } str + * @returns { string } */ function windowsify(str) { - return windowsSubstitutions.reduce(function (result /*: string */, sub) { + return windowsSubstitutions.reduce(function (result, sub) { return result.replace(sub[0], sub[1]); }, str); } diff --git a/lib/elm-test.js b/lib/elm-test.js index 4c9a222a..17a14b92 100644 --- a/lib/elm-test.js +++ b/lib/elm-test.js @@ -16,22 +16,19 @@ const Project = require('./Project'); const Report = require('./Report'); const RunTests = require('./RunTests'); -void Report; - -const parsePositiveInteger = - (minimum /*: number */) => - (string /*: string */) /*: number */ => { - const number = Number(string); - if (!/^\d+$/.test(string)) { - throw new InvalidOptionArgumentError('Expected one or more digits.'); - } else if (!Number.isFinite(number)) { - throw new InvalidOptionArgumentError('Expected a finite number.'); - } else if (number < minimum) { - throw new InvalidOptionArgumentError(`Expected at least ${minimum}.`); - } else { - return number; - } - }; +/** @type { (minimum: number) => (string: string) => number } */ +const parsePositiveInteger = (minimum) => (string) => { + const number = Number(string); + if (!/^\d+$/.test(string)) { + throw new InvalidOptionArgumentError('Expected one or more digits.'); + } else if (!Number.isFinite(number)) { + throw new InvalidOptionArgumentError('Expected a finite number.'); + } else if (number < minimum) { + throw new InvalidOptionArgumentError(`Expected at least ${minimum}.`); + } else { + return number; + } +}; /** * @param { string } dir diff --git a/lib/supports-color.js b/lib/supports-color.js index b37762b4..2c154ec1 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -15,7 +15,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI */ const { env } = process; -function supportsColor() /*: boolean */ { +/** + * @returns { boolean } + */ +function supportsColor() { if ('FORCE_COLOR' in env && env.FORCE_COLOR !== '') { return env.FORCE_COLOR !== 'false' && env.FORCE_COLOR !== '0'; } @@ -73,4 +76,4 @@ function supportsColor() /*: boolean */ { return false; } -module.exports = (supportsColor() /*: boolean */); +module.exports = supportsColor(); From 03e92cdc4f144492aab3189928d270dcbd33c232 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sat, 11 Jul 2026 19:03:51 +0200 Subject: [PATCH 05/14] Remove @flow comments --- lib/Compile.js | 2 -- lib/DependencyProvider.js | 2 -- lib/ElmCompiler.js | 2 -- lib/ElmHome.js | 2 -- lib/ElmJson.js | 2 -- lib/FindTests.js | 2 -- lib/Generate.js | 2 -- lib/Install.js | 2 -- lib/Parser.js | 2 -- lib/Project.js | 2 -- lib/Report.js | 2 -- lib/RunTests.js | 2 -- lib/Solve.js | 2 -- lib/Supervisor.js | 2 -- lib/SyncGet.js | 2 -- lib/SyncGetWorker.js | 2 -- lib/chalk.js | 1 - lib/elm-test.js | 2 -- lib/supports-color.js | 1 - 19 files changed, 36 deletions(-) diff --git a/lib/Compile.js b/lib/Compile.js index b6095376..016cae9b 100644 --- a/lib/Compile.js +++ b/lib/Compile.js @@ -1,5 +1,3 @@ -//@flow - const { supportsColor } = require('./chalk'); const spawn = require('cross-spawn'); const ElmCompiler = require('./ElmCompiler'); diff --git a/lib/DependencyProvider.js b/lib/DependencyProvider.js index 2ee8efb5..e89541cf 100644 --- a/lib/DependencyProvider.js +++ b/lib/DependencyProvider.js @@ -1,5 +1,3 @@ -// @flow - const fs = require('fs'); const path = require('path'); const wasm = require('elm-solve-deps-wasm'); diff --git a/lib/ElmCompiler.js b/lib/ElmCompiler.js index 861f4540..b630b1f0 100644 --- a/lib/ElmCompiler.js +++ b/lib/ElmCompiler.js @@ -1,5 +1,3 @@ -// @flow - 'use strict'; var spawn = require('cross-spawn'); diff --git a/lib/ElmHome.js b/lib/ElmHome.js index 99d1df80..71603d04 100644 --- a/lib/ElmHome.js +++ b/lib/ElmHome.js @@ -1,5 +1,3 @@ -// @flow - const path = require('path'); const os = require('os'); diff --git a/lib/ElmJson.js b/lib/ElmJson.js index 7f975fb2..4ae02db7 100644 --- a/lib/ElmJson.js +++ b/lib/ElmJson.js @@ -1,5 +1,3 @@ -// @flow - const fs = require('fs'); const path = require('path'); diff --git a/lib/FindTests.js b/lib/FindTests.js index bf734027..c6c8a67b 100644 --- a/lib/FindTests.js +++ b/lib/FindTests.js @@ -1,5 +1,3 @@ -// @flow - const gracefulFs = require('graceful-fs'); const fs = require('fs'); const { globSync } = require('tinyglobby'); diff --git a/lib/Generate.js b/lib/Generate.js index 32d17b35..6a4304bf 100644 --- a/lib/Generate.js +++ b/lib/Generate.js @@ -1,5 +1,3 @@ -// @flow - const { supportsColor } = require('./chalk'); const fs = require('fs'); const path = require('path'); diff --git a/lib/Install.js b/lib/Install.js index 84cef6bc..494a76aa 100644 --- a/lib/Install.js +++ b/lib/Install.js @@ -1,5 +1,3 @@ -// @flow - const spawn = require('cross-spawn'); const fs = require('fs'); const path = require('path'); diff --git a/lib/Parser.js b/lib/Parser.js index a36d5cd1..1f6844d2 100644 --- a/lib/Parser.js +++ b/lib/Parser.js @@ -1,5 +1,3 @@ -// @flow - // Useful for debugging. const LOG_ERRORS = 'ELM_TEST_LOG_PARSE_ERRORS' in process.env; diff --git a/lib/Project.js b/lib/Project.js index 7a266275..d0c99271 100644 --- a/lib/Project.js +++ b/lib/Project.js @@ -1,5 +1,3 @@ -// @flow - const fs = require('fs'); const path = require('path'); const ElmJson = require('./ElmJson'); diff --git a/lib/Report.js b/lib/Report.js index 5ba1d9fc..488f005b 100644 --- a/lib/Report.js +++ b/lib/Report.js @@ -1,5 +1,3 @@ -// @flow - /** * @typedef { 'console' | 'json' | 'junit' } Report */ diff --git a/lib/RunTests.js b/lib/RunTests.js index 0f73bb4e..3ad3a686 100644 --- a/lib/RunTests.js +++ b/lib/RunTests.js @@ -1,5 +1,3 @@ -// @flow - const chalk = require('./chalk'); const path = require('path'); const readline = require('readline'); diff --git a/lib/Solve.js b/lib/Solve.js index 253c8990..26497c7d 100644 --- a/lib/Solve.js +++ b/lib/Solve.js @@ -1,5 +1,3 @@ -// @flow - const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); diff --git a/lib/Supervisor.js b/lib/Supervisor.js index 2eaa28f5..a816165e 100644 --- a/lib/Supervisor.js +++ b/lib/Supervisor.js @@ -1,5 +1,3 @@ -// @flow - const chalk = require('./chalk'); const child_process = require('child_process'); const fs = require('fs'); diff --git a/lib/SyncGet.js b/lib/SyncGet.js index 3aa33a46..4fa8e07b 100644 --- a/lib/SyncGet.js +++ b/lib/SyncGet.js @@ -1,5 +1,3 @@ -// @flow - const path = require('path'); const { Worker, diff --git a/lib/SyncGetWorker.js b/lib/SyncGetWorker.js index 701a85c3..3b73c49a 100644 --- a/lib/SyncGetWorker.js +++ b/lib/SyncGetWorker.js @@ -1,5 +1,3 @@ -// @flow - const { parentPort, workerData } = require('worker_threads'); const https = require('https'); diff --git a/lib/chalk.js b/lib/chalk.js index 1ce0fde7..6ce8465b 100644 --- a/lib/chalk.js +++ b/lib/chalk.js @@ -1,4 +1,3 @@ -// @flow const supportsColor = require('./supports-color'); // Find more colors/styles in diff --git a/lib/elm-test.js b/lib/elm-test.js index 17a14b92..d00c941a 100644 --- a/lib/elm-test.js +++ b/lib/elm-test.js @@ -1,5 +1,3 @@ -// @flow - const { InvalidOptionArgumentError, Option, program } = require('commander'); const fs = require('fs'); const os = require('os'); diff --git a/lib/supports-color.js b/lib/supports-color.js index 2c154ec1..7a683d77 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -1,4 +1,3 @@ -// @flow /** Based on https://github.com/chalk/supports-color v10.2.2 but adapted to use CommonJs and simplified to our needs (only knowing whether stdout supports colors). From 222790d168d8e8308010805cbaf68cd6c50c47e7 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sat, 11 Jul 2026 19:21:47 +0200 Subject: [PATCH 06/14] Replace Flow with TypeScript --- .flowconfig | 7 - .github/workflows/check.yml | 4 +- eslint.config.mjs | 1 - lib/ElmCompiler.js | 11 +- lib/RunTests.js | 2 - package-lock.json | 616 ++++++++++++++++++++++++++++++++++-- package.json | 7 +- prettier.config.js | 8 - tsconfig.json | 25 ++ 9 files changed, 619 insertions(+), 62 deletions(-) delete mode 100644 .flowconfig create mode 100644 tsconfig.json diff --git a/.flowconfig b/.flowconfig deleted file mode 100644 index c1d6060f..00000000 --- a/.flowconfig +++ /dev/null @@ -1,7 +0,0 @@ -[options] -include_warnings=true -exact_by_default=true - -[lints] -all=error -untyped-import=off diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index bd05e9c2..f565a80c 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -47,8 +47,8 @@ jobs: - name: elm-tooling install run: npx --no-install elm-tooling install - - name: Flow - run: npx --no-install flow check + - name: TypeScript + run: npx --no-install tsc - name: ESLint run: npx --no-install eslint --report-unused-disable-directives . diff --git a/eslint.config.mjs b/eslint.config.mjs index 9c9eecda..40da8f43 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,7 +9,6 @@ export default [ '**/elm-stuff', '**/fixtures', '**/templates', - '**/flow-typed', ], }, { diff --git a/lib/ElmCompiler.js b/lib/ElmCompiler.js index b630b1f0..5fd3bf17 100644 --- a/lib/ElmCompiler.js +++ b/lib/ElmCompiler.js @@ -3,12 +3,13 @@ var spawn = require('cross-spawn'); /** + * @typedef { never } TODO * @typedef { { spawn?: ( - string, - Array, - child_process$spawnOpts - ) => child_process$ChildProcess, + cmd: string, + args: Array, + options: TODO + ) => TODO, cwd?: string, pathToElm?: string, output?: string, @@ -109,7 +110,7 @@ function compilerErrorToString(err, pathToElm) { * * @param { Array } sources * @param { Options } options - * @returns { TODO child_process$ChildProcess } + * @returns { TODO } */ function compile(sources, options) { var optionsWithDefaults = Object.assign({}, defaultOptions, options); diff --git a/lib/RunTests.js b/lib/RunTests.js index 3ad3a686..e1914bac 100644 --- a/lib/RunTests.js +++ b/lib/RunTests.js @@ -31,8 +31,6 @@ function getPipeFilename(runsExecuted) { } /** - * This could have been a class, but I couldn’t figure out how to type it with - * Flow comments and Prettier. * This lets you log something like `elm.json changed > Compiling > Starting tests` * where each segment appears over time. * `\r` moves the cursor to the start of the line. This is important when there diff --git a/package-lock.json b/package-lock.json index 5ddb33e8..b79ff15a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,15 +24,17 @@ }, "devDependencies": { "@eslint/js": "9.20.0", + "@types/cross-spawn": "6.0.6", + "@types/node": "26.0.1", "elm-review": "2.13.1", "elm-tooling": "1.18.0", "eslint": "9.20.1", "eslint-plugin-mocha": "10.5.0", - "flow-bin": "0.180.0", "globals": "15.15.0", "mocha": "11.1.0", "prettier": "2.8.1", "strip-ansi": "6.0.0", + "typescript": "7.0.2", "xml2js": "0.5.0" }, "engines": { @@ -395,6 +397,16 @@ "@types/responselike": "^1.0.0" } }, + "node_modules/@types/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -423,12 +435,13 @@ } }, "node_modules/@types/node": { - "version": "22.13.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz", - "integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==", + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz", + "integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==", "dev": true, + "license": "MIT", "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~8.3.0" } }, "node_modules/@types/responselike": { @@ -440,6 +453,346 @@ "@types/node": "*" } }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -1388,18 +1741,6 @@ "dev": true, "license": "ISC" }, - "node_modules/flow-bin": { - "version": "0.180.0", - "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.180.0.tgz", - "integrity": "sha512-jEZoIwOxzrtQ0erUu94nEzlqUoX7OAMeVs0CjO0rN6b7SDBhI5IysVRvGSQkkFWBJpy5VQ9lvzBYzq5Sq9vcmg==", - "dev": true, - "bin": { - "flow": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/folder-hash": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/folder-hash/-/folder-hash-3.3.5.tgz", @@ -2689,11 +3030,47 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" }, "node_modules/uri-js": { "version": "4.4.1", @@ -3121,6 +3498,15 @@ "@types/responselike": "^1.0.0" } }, + "@types/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -3149,12 +3535,12 @@ } }, "@types/node": { - "version": "22.13.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz", - "integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==", + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz", + "integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==", "dev": true, "requires": { - "undici-types": "~6.20.0" + "undici-types": "~8.3.0" } }, "@types/responselike": { @@ -3166,6 +3552,146 @@ "@types/node": "*" } }, + "@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "dev": true, + "optional": true + }, + "@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "dev": true, + "optional": true + }, + "@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "dev": true, + "optional": true + }, + "@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "dev": true, + "optional": true + }, + "@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "dev": true, + "optional": true + }, + "@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "dev": true, + "optional": true + }, + "@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "dev": true, + "optional": true + }, + "@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "dev": true, + "optional": true + }, + "@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "dev": true, + "optional": true + }, + "@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "dev": true, + "optional": true + }, + "@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "dev": true, + "optional": true + }, + "@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "dev": true, + "optional": true + }, + "@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "dev": true, + "optional": true + }, + "@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "dev": true, + "optional": true + }, + "@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "dev": true, + "optional": true + }, + "@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "dev": true, + "optional": true + }, + "@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "dev": true, + "optional": true + }, + "@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "dev": true, + "optional": true + }, + "@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "dev": true, + "optional": true + }, + "@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "dev": true, + "optional": true + }, "acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -3824,12 +4350,6 @@ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true }, - "flow-bin": { - "version": "0.180.0", - "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.180.0.tgz", - "integrity": "sha512-jEZoIwOxzrtQ0erUu94nEzlqUoX7OAMeVs0CjO0rN6b7SDBhI5IysVRvGSQkkFWBJpy5VQ9lvzBYzq5Sq9vcmg==", - "dev": true - }, "folder-hash": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/folder-hash/-/folder-hash-3.3.5.tgz", @@ -4729,10 +5249,38 @@ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true }, + "typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "dev": true, + "requires": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, "undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", "dev": true }, "uri-js": { diff --git a/package.json b/package.json index 23c7ae09..44645a52 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,11 @@ "scripts": { "prepare": "elm-tooling install", "test": "npm run check && npm run test-only", - "flow": "flow", "lint": "eslint --report-unused-disable-directives .", "review": "cd elm && elm-review", "elm-test": "cd elm && node ../bin/elm-test", "test-only": "mocha tests && npm run elm-test", - "check": "flow check && npm run lint && npm run format:check && npm run review", + "check": "tsc && npm run lint && npm run format:check && npm run review", "format:check": "prettier --check . && elm-format elm --validate", "format:write": "prettier --write . && elm-format elm --yes" }, @@ -55,15 +54,17 @@ }, "devDependencies": { "@eslint/js": "9.20.0", + "@types/cross-spawn": "6.0.6", + "@types/node": "26.0.1", "elm-review": "2.13.1", "elm-tooling": "1.18.0", "eslint": "9.20.1", "eslint-plugin-mocha": "10.5.0", - "flow-bin": "0.180.0", "globals": "15.15.0", "mocha": "11.1.0", "prettier": "2.8.1", "strip-ansi": "6.0.0", + "typescript": "7.0.2", "xml2js": "0.5.0" } } diff --git a/prettier.config.js b/prettier.config.js index d48f083b..da98b339 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -1,12 +1,4 @@ module.exports = { singleQuote: true, proseWrap: 'never', - overrides: [ - { - files: '*.js', - options: { - parser: 'flow', - }, - }, - ], }; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..4f25c949 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "esnext", + "lib": ["esnext"], + "types": ["node"], + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noImplicitReturns": true, + "noImplicitOverride": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true, + "strict": true, + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + "useUnknownInCatchVariables": true, + "allowJs": true, + "checkJs": true, + "noEmit": true, + }, + "include": ["lib/**/*"], +} From 76652a150b385c9ad72a3f16fdaf7bab67608bd7 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sat, 11 Jul 2026 21:46:05 +0200 Subject: [PATCH 07/14] Fix most type errors --- eslint.config.mjs | 1 + lib/Compile.js | 15 ++++---- lib/DependencyProvider.js | 7 ++-- lib/ElmCompiler.js | 39 +++++++++++--------- lib/ElmHome.js | 6 ++-- lib/ElmJson.js | 23 ++++++------ lib/FindTests.js | 2 +- lib/Generate.js | 4 +-- lib/Install.js | 6 ++++ lib/Parser.js | 70 ++++++++++++++++-------------------- lib/Project.js | 2 +- lib/RunTests.js | 7 ++-- lib/Solve.js | 4 +-- lib/Supervisor.js | 13 ++++++- lib/SyncGet.js | 8 ++++- lib/SyncGetWorker.js | 4 +++ lib/elm-test.js | 10 +++--- lib/supports-color.js | 6 ++-- package-lock.json | 75 +++++++++++++++++++++++++++++++++++++++ package.json | 3 ++ tsconfig.json | 4 +-- 21 files changed, 209 insertions(+), 100 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 40da8f43..89d59854 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -14,6 +14,7 @@ export default [ { rules: { ...js.configs.recommended.rules, + 'no-fallthrough': 'off', // Caught by TypeScript instead. 'no-inner-declarations': 'off', 'no-prototype-builtins': 'off', 'no-unused-vars': ['error', { caughtErrorsIgnorePattern: '^_' }], diff --git a/lib/Compile.js b/lib/Compile.js index 016cae9b..4d0717ce 100644 --- a/lib/Compile.js +++ b/lib/Compile.js @@ -44,14 +44,12 @@ function compileSources( report ) { return new Promise((resolve, reject) => { - const compilerReport = report === 'json' ? report : undefined; - const compileProcess = ElmCompiler.compile(testFilePaths, { output: '/dev/null', cwd: projectRootDir, spawn: spawnCompiler({ ignoreStdout: false, cwd: projectRootDir }), pathToElm: pathToElmBinary, - report: compilerReport, + reportJson: report === 'json', processOpts: processOptsForReporter(report), }); @@ -70,8 +68,8 @@ function compileSources( * @returns { ( pathToElm: string, processArgs: Array, - processOpts: TODO_child_process$spawnOpts - ) => TODO } + processOpts: import('child_process').SpawnOptions + ) => import('child_process').ChildProcess } */ function spawnCompiler({ ignoreStdout, cwd }) { return (pathToElm, processArgs, processOpts) => { @@ -84,6 +82,7 @@ function spawnCompiler({ ignoreStdout, cwd }) { const stdout = ignoreStdout ? 'ignore' : supportsColor ? 'inherit' : 'pipe'; const stderr = supportsColor ? 'inherit' : 'pipe'; + /** @type { import('child_process').SpawnOptions } */ const finalOpts = { env: process.env, ...processOpts, @@ -93,11 +92,11 @@ function spawnCompiler({ ignoreStdout, cwd }) { const child = spawn(pathToElm, processArgs, finalOpts); - if (stdout === 'pipe') { + if (stdout === 'pipe' && child.stdout !== null) { child.stdout.on('data', (data) => process.stdout.write(data)); } - if (stderr === 'pipe') { + if (stderr === 'pipe' && child.stderr !== null) { child.stderr.on('data', (data) => process.stderr.write(data)); } @@ -107,7 +106,7 @@ function spawnCompiler({ ignoreStdout, cwd }) { /** * @param { import('./Report').Report } report - * @returns { child_process$spawnOpts } + * @returns { import('child_process').SpawnOptions } */ function processOptsForReporter(report) { if (Report.isMachineReadable(report)) { diff --git a/lib/DependencyProvider.js b/lib/DependencyProvider.js index e89541cf..7896fab0 100644 --- a/lib/DependencyProvider.js +++ b/lib/DependencyProvider.js @@ -184,6 +184,7 @@ class OfflineAvailableVersionLister { */ function readVersionsInElmHomeAndSort(pkg) { const pkgPath = ElmHome.packagePath(pkg); + /** @type {Array} */ let offlineVersions; try { offlineVersions = fs.readdirSync(pkgPath); @@ -220,6 +221,7 @@ class DependencyProvider { useTest, extra, fetchElmJsonOffline, + /** @type { (pkg: string) => Array } */ (pkg) => lister.list( pkg, @@ -251,6 +253,7 @@ class DependencyProvider { useTest, extra, fetchElmJsonOnline, + /** @type { (pkg: string) => Array } */ (pkg) => lister.list( pkg, @@ -467,8 +470,8 @@ function homeElmJsonPath(pkg, version) { } } */ function splitPkgVersion(str) { - const parts = str.split('@'); - return { pkg: parts[0], version: parts[1] }; + const [pkg = 'unknown', version = '99.99.99'] = str.split('@'); + return { pkg, version }; } module.exports = { diff --git a/lib/ElmCompiler.js b/lib/ElmCompiler.js index 5fd3bf17..05edb647 100644 --- a/lib/ElmCompiler.js +++ b/lib/ElmCompiler.js @@ -3,29 +3,24 @@ var spawn = require('cross-spawn'); /** - * @typedef { never } TODO * @typedef { { - spawn?: ( + spawn: ( cmd: string, args: Array, - options: TODO - ) => TODO, + options: import('child_process').SpawnOptions + ) => import('child_process').ChildProcess, cwd?: string, - pathToElm?: string, + pathToElm: string, output?: string, - report?: 'json', - processOpts?: child_process$spawnOpts, + reportJson?: boolean, + processOpts?: import('child_process').SpawnOptions, } } Options */ /** @type { Options } */ var defaultOptions = { spawn: spawn, - cwd: undefined, pathToElm: 'elm', - output: undefined, - report: undefined, - processOpts: undefined, }; /** @@ -42,8 +37,8 @@ function compilerArgsFromOptions(options) { args.push('--output', options.output); } - if (options.report != null) { - args.push('--report', options.report); + if (options.reportJson === true) { + args.push('--report', 'json'); } return args; @@ -52,7 +47,7 @@ function compilerArgsFromOptions(options) { /** * @param { Array } sources * @param { Options } options - * @returns { TODO } + * @returns { import('child_process').ChildProcess } */ function runCompiler(sources, options) { var pathToElm = options.pathToElm; @@ -77,7 +72,12 @@ function runCompiler(sources, options) { * @returns { string } */ function compilerErrorToString(err, pathToElm) { - if (typeof err === 'object' && typeof err.code === 'string') { + if ( + typeof err === 'object' && + err !== null && + 'code' in err && + typeof err.code === 'string' + ) { switch (err.code) { case 'ENOENT': return ( @@ -96,7 +96,12 @@ function compilerErrorToString(err, pathToElm) { 'Error attempting to run Elm compiler "' + pathToElm + '":\n' + err ); } - } else if (typeof err === 'object' && typeof err.message === 'string') { + } else if ( + typeof err === 'object' && + err !== null && + 'message' in err && + typeof err.message === 'string' + ) { return JSON.stringify(err.message); } else { return ( @@ -110,7 +115,7 @@ function compilerErrorToString(err, pathToElm) { * * @param { Array } sources * @param { Options } options - * @returns { TODO } + * @returns { import('child_process').ChildProcess } */ function compile(sources, options) { var optionsWithDefaults = Object.assign({}, defaultOptions, options); diff --git a/lib/ElmHome.js b/lib/ElmHome.js index 71603d04..dd76a0ff 100644 --- a/lib/ElmHome.js +++ b/lib/ElmHome.js @@ -29,7 +29,7 @@ function defaultUnixElmHome() { * @returns { string } */ function defaultWindowsElmHome() { - const appData = process.env.APPDATA; + const appData = process.env['APPDATA']; const dir = appData === undefined ? path.join(os.homedir(), 'AppData', 'Roaming') @@ -54,8 +54,8 @@ function packagePath(pkg) { } } */ function splitAuthorPkg(pkgIdentifier) { - const parts = pkgIdentifier.split('/'); - return { author: parts[0], pkg: parts[1] }; + const [author = 'unknown', pkg = 'unknown'] = pkgIdentifier.split('/'); + return { author, pkg }; } module.exports = { diff --git a/lib/ElmJson.js b/lib/ElmJson.js index 4ae02db7..a4455497 100644 --- a/lib/ElmJson.js +++ b/lib/ElmJson.js @@ -15,13 +15,13 @@ const path = require('path'); 'source-directories': Array, dependencies: DirectAndIndirectDependencies, 'test-dependencies': DirectAndIndirectDependencies, - [key: string]: unknown, // TODO + [key: string]: unknown, } | { type: 'package', dependencies: Dependencies, 'test-dependencies': Dependencies, - [key: string]: unknown, // TODO + [key: string]: unknown, } } ElmJson */ @@ -36,7 +36,7 @@ function getPath(dir) { /** * @param { string } dir - * @param { typeof ElmJson } elmJson + * @param { ElmJson } elmJson * @returns { void } */ function write(dir, elmJson) { @@ -53,7 +53,7 @@ function write(dir, elmJson) { /** * @param { string } dir - * @returns { typeof ElmJson } + * @returns { ElmJson } */ function read(dir) { const elmJsonPath = getPath(dir); @@ -77,7 +77,7 @@ function readHelper(elmJsonPath) { 'the file' ); - switch (json.type) { + switch (json['type']) { case 'application': return { ...json, @@ -86,7 +86,7 @@ function readHelper(elmJsonPath) { json['source-directories'] ), dependencies: parseDirectAndIndirectDependencies( - json.dependencies, + json['dependencies'], 'dependencies' ), 'test-dependencies': parseDirectAndIndirectDependencies( @@ -99,7 +99,7 @@ function readHelper(elmJsonPath) { return { ...json, type: 'package', - dependencies: parseDependencies(json.dependencies, 'dependencies'), + dependencies: parseDependencies(json['dependencies'], 'dependencies'), 'test-dependencies': parseDependencies( json['test-dependencies'], 'test-dependencies' @@ -109,7 +109,7 @@ function readHelper(elmJsonPath) { default: throw new Error( `Expected "type" to be "application" or "package", but got: ${stringify( - json.type + json['type'] )}` ); } @@ -158,8 +158,8 @@ function parseSourceDirectories(json) { function parseDirectAndIndirectDependencies(json, what) { const jsonObject = parseObject(json, what); return { - direct: parseDependencies(jsonObject.direct, `${what}->"direct"`), - indirect: parseDependencies(jsonObject.indirect, `${what}->"indirect"`), + direct: parseDependencies(jsonObject['direct'], `${what}->"direct"`), + indirect: parseDependencies(jsonObject['indirect'], `${what}->"indirect"`), }; } @@ -170,6 +170,7 @@ function parseDirectAndIndirectDependencies(json, what) { */ function parseDependencies(json, what) { const jsonObject = parseObject(json, what); + /** @type { Dependencies } */ const result = {}; for (const [key, value] of Object.entries(jsonObject)) { @@ -234,7 +235,7 @@ function requireElmTestPackage(dir, elmJson) { } /** - * @param { typeof ElmJson } elmJson + * @param { ElmJson } elmJson * @returns { string | undefined } */ function getElmExplorationsTestPackageVersionOrRange(elmJson) { diff --git a/lib/FindTests.js b/lib/FindTests.js index c6c8a67b..2e883aeb 100644 --- a/lib/FindTests.js +++ b/lib/FindTests.js @@ -193,7 +193,7 @@ ${ /** * @param { string } filePath - * @param { string } matchingSourceDirs + * @param { Array } matchingSourceDirs * @param { string } testsDir * @returns { string } */ diff --git a/lib/Generate.js b/lib/Generate.js index 6a4304bf..de5f8199 100644 --- a/lib/Generate.js +++ b/lib/Generate.js @@ -80,7 +80,7 @@ function getGeneratedSrcDir(generatedCodeDir) { } /** - * @param { DependencyProvider } dependencyProvider + * @param { import('./DependencyProvider').DependencyProvider } dependencyProvider * @param { import('./Project').Project } project * @returns { void } */ @@ -264,7 +264,7 @@ function makeList(parts) { } /** - * @param { number } indent + * @param { string } indent * @param { string } string * @returns { string } */ diff --git a/lib/Install.js b/lib/Install.js index 494a76aa..3e71d4bb 100644 --- a/lib/Install.js +++ b/lib/Install.js @@ -16,6 +16,8 @@ function rmDirSync(dir) { if (fs.rmSync !== undefined) { fs.rmSync(dir, { recursive: true, force: true }); } else if (fs.existsSync(dir)) { + // @ts-expect-error TypeScript says that `rmdirSync` only takes one argument – because + // legacy versions of Node.js do – but we need to support those. fs.rmdirSync(dir, { recursive: true }); } } @@ -84,6 +86,10 @@ function install(project, pathToElmBinary, packageName) { } }); } else { + /** + * @param { 'direct' | 'indirect' } directness + * @returns { void } + */ function moveToTestDeps(directness) { Object.keys(newElmJson['dependencies'][directness]).forEach(function ( key diff --git a/lib/Parser.js b/lib/Parser.js index 1f6844d2..15f89d45 100644 --- a/lib/Parser.js +++ b/lib/Parser.js @@ -27,8 +27,8 @@ const LOG_ERRORS = 'ELM_TEST_LOG_PARSE_ERRORS' in process.env; * @param { string } filePath * @param { ( path: string, - options?: { encoding?: string } - ) => TODO stream$Readable & { close(): void } } createReadStream + options?: { encoding?: BufferEncoding } + ) => import('stream').Readable & { close(): void } } createReadStream * @returns { Promise> } */ function extractExposedPossiblyTests(filePath, createReadStream) { @@ -103,7 +103,7 @@ function extractExposedPossiblyTests(filePath, createReadStream) { } break; default: - unreachable(flushCommand.tag); + unreachable(flushCommand, 'tag'); } } @@ -122,7 +122,7 @@ function extractExposedPossiblyTests(filePath, createReadStream) { } /** - * @param { Token | undefined } token + * @param { Token } [token] * @returns { OnParserTokenResult | undefined } */ function flush(token) { @@ -184,14 +184,14 @@ function extractExposedPossiblyTests(filePath, createReadStream) { } switch (parserState.tag) { case 'ModuleDeclaration': { - const rawResult = parseModuleDeclaration( - token, - parserState.lastToken - ); - const result = - typeof rawResult === 'string' - ? { tag: 'Token', token: rawResult } - : rawResult; + const result = parseModuleDeclaration(token, parserState.lastToken); + if (typeof result === 'string') { + parserState.lastToken = result; + if (result === 'LowerName') { + exposedPossiblyTests.push(lastLowerName); + } + return undefined; + } switch (result.tag) { case 'ParseError': case 'CriticalParseError': @@ -199,42 +199,31 @@ function extractExposedPossiblyTests(filePath, createReadStream) { return result; case 'NextParserState': parserState = { tag: 'Rest', lastToken: 'Initial' }; - break; - case 'Token': - parserState.lastToken = result.token; - if (result.token === 'LowerName') { - exposedPossiblyTests.push(lastLowerName); - } - break; + return undefined; default: unreachable(result); } - break; } case 'Rest': { - const rawResult = parseRest(token, parserState.lastToken); - const result = - typeof rawResult === 'string' - ? { tag: 'Token', token: rawResult } - : rawResult; + const result = parseRest(token, parserState.lastToken); + if (typeof result === 'string') { + parserState.lastToken = result; + if (result === 'PotentialTestDeclaration=') { + exposedPossiblyTests.push(lastLowerName); + } + return undefined; + } switch (result.tag) { case 'ParseError': return result; - case 'Token': - parserState.lastToken = result.token; - if (result.token === 'PotentialTestDeclaration=') { - exposedPossiblyTests.push(lastLowerName); - } - break; default: unreachable(result); } - break; } default: - unreachable(parserState.tag); + unreachable(parserState, 'tag'); } } }); @@ -305,10 +294,13 @@ function isUpperName(string) { /** * @param { never } value + * @param { string } [property] * @returns { never } */ -function unreachable(value) { - throw new Error(`Unreachable: ${value}`); +function unreachable(value, property) { + throw new Error( + `Unreachable: ${property === undefined ? value : value[property]}` + ); } /** @@ -352,7 +344,7 @@ function stringify(json) { /** * @param { unknown } actual - * @returns { string } + * @returns { ParseError } */ function backslashError(actual) { return expected( @@ -717,7 +709,7 @@ function tokenize(char, tokenizerState) { } default: - return unreachable(tokenizerState.tag); + return unreachable(tokenizerState, 'tag'); } } @@ -725,14 +717,14 @@ function tokenize(char, tokenizerState) { * @param { string } previousChar * @param { string } char * @param { Array } cmds - * @returns { [ TokenizerState, Array ] } + * @returns { [ TokenizerState, Array ] | ParseError } */ function tokenizeInitial(previousChar, char, cmds) { const result = tokenize(char, { tag: 'Initial', otherTokenChars: previousChar, }); - if (result.tag === 'ParseError') { + if (!Array.isArray(result)) { return result; } const [nextTokenizerState, nextCmds] = result; diff --git a/lib/Project.js b/lib/Project.js index d0c99271..d614273d 100644 --- a/lib/Project.js +++ b/lib/Project.js @@ -8,7 +8,7 @@ const ElmJson = require('./ElmJson'); testsDir: string, generatedCodeDir: string, testsSourceDirs: Array, - elmJson: TODO ElmJson.ElmJson, + elmJson: import('./ElmJson').ElmJson, } } Project */ diff --git a/lib/RunTests.js b/lib/RunTests.js index e1914bac..af051f33 100644 --- a/lib/RunTests.js +++ b/lib/RunTests.js @@ -51,6 +51,7 @@ function getPipeFilename(runsExecuted) { } } */ function makeProgressLogger(report, clearConsole) { + /** @type { Array } */ const items = []; return { log(message) { @@ -92,7 +93,7 @@ function makeProgressLogger(report, clearConsole) { } /** - * @template { T } + * @template T * @param { Array } from * @param { Array } to * @returns { { added: Array, removed: Array } } @@ -137,7 +138,7 @@ function watcherEventMessage(queue) { } /** - * @param { DependencyProvider } dependencyProvider + * @param { import('./DependencyProvider').DependencyProvider } dependencyProvider * @param { string } projectRootDir * @param { string } pathToElmBinary * @param { Array } testFileGlobs @@ -159,6 +160,7 @@ function runTests( processes, { watch, clearConsole, report, seed, fuzz } ) { + /** @type { import('chokidar').FSWatcher | undefined } */ let watcher = undefined; /** @type { Array } */ let watchedPaths = []; @@ -305,6 +307,7 @@ function runTests( Project.getTestsDir(projectRootDir), ]; + /** @type { (event: 'added' | 'changed' | 'removed') => (absoluteFilePath: string) => void } */ const rerun = (event) => (absoluteFilePath) => { if ( absoluteFilePath.endsWith('.elm') || diff --git a/lib/Solve.js b/lib/Solve.js index 26497c7d..416f9be0 100644 --- a/lib/Solve.js +++ b/lib/Solve.js @@ -12,7 +12,7 @@ function sha256(string) { } /** - * @param { DependencyProvider } dependencyProvider + * @param { import('./DependencyProvider').DependencyProvider } dependencyProvider * @param { import('./Project').Project } project * @returns { import('./ElmJson').DirectAndIndirectDependencies } */ @@ -50,7 +50,7 @@ function getDependenciesCached(dependencyProvider, project) { } /** - * @param { DependencyProvider } dependencyProvider + * @param { import('./DependencyProvider').DependencyProvider } dependencyProvider * @param { import('./ElmJson').ElmJson } elmJson * @returns { string } */ diff --git a/lib/Supervisor.js b/lib/Supervisor.js index a816165e..31a3da1a 100644 --- a/lib/Supervisor.js +++ b/lib/Supervisor.js @@ -16,6 +16,7 @@ const Report = require('./Report'); */ function run(elmTestVersion, pipeFilename, report, processes, dest, watch) { return new Promise(function (resolve) { + /** @type { number | null } */ var nextResultToPrint = null; var finishedWorkers = 0; var closedWorkers = 0; @@ -25,6 +26,7 @@ function run(elmTestVersion, pipeFilename, report, processes, dest, watch) { var testsToRun = -1; var initializedWorkers = -1; var startingTime = Date.now(); + /** @type { Array } */ var workers = []; function printResult(result) { @@ -144,6 +146,10 @@ function run(elmTestVersion, pipeFilename, report, processes, dest, watch) { flushResults(); } + /** + * @param { import('net').Socket } socket + * @returns { void } + */ function initWorker(socket) { socket.setEncoding('utf8'); socket.setNoDelay(true); @@ -208,6 +214,7 @@ function run(elmTestVersion, pipeFilename, report, processes, dest, watch) { // hex-encoded unicode codepoint for the invalid character. For // example, the start of a terminal escape (`\u{001B}` in Elm) will be output as a // literal `\u{001B}`. + /** @type { (char: string) => string } */ var invalidCharReplacement = function (char) { return ( '\\u{' + @@ -221,6 +228,7 @@ function run(elmTestVersion, pipeFilename, report, processes, dest, watch) { // when using the junit report, so `require` it late. require('xmlbuilder') .create(xml, { + // @ts-expect-error The type annotation says that `invalidCharReplacement` should be a string, but we have always passed a function. This got noticed when migrating from Flow to TypeScript. invalidCharReplacement: invalidCharReplacement, }) .end() @@ -331,7 +339,10 @@ function makeWindowsSafe(text) { return process.platform === 'win32' ? windowsify(text) : text; } -// Fix Windows Unicode problems. Credit to https://github.com/sindresorhus/figures for the Windows compat idea! +/** + * Fix Windows Unicode problems. Credit to https://github.com/sindresorhus/figures for the Windows compat idea! + * @type { Array<[RegExp, string]> } + */ var windowsSubstitutions = [ [/[↓✗►]/g, '>'], [/╵│╷╹┃╻/g, '|'], diff --git a/lib/SyncGet.js b/lib/SyncGet.js index 4fa8e07b..04106d12 100644 --- a/lib/SyncGet.js +++ b/lib/SyncGet.js @@ -27,11 +27,17 @@ function startWorker() { workerData: { sharedLock, requestPort: workerPort }, transferList: [workerPort], }); + /** + * @param { string } url + * @returns { string } + */ function get(url) { worker.postMessage(url); Atomics.wait(sharedLockArray, 0, 0); // blocks until notified at index 0. const response = receiveMessageOnPort(localPort); - if (response.message.error) { + if (response === undefined) { + throw new Error(`No message on port ${localPort} available.`); + } else if (response.message.error) { throw response.message.error; } else { return response.message; diff --git a/lib/SyncGetWorker.js b/lib/SyncGetWorker.js index 3b73c49a..216c41b0 100644 --- a/lib/SyncGetWorker.js +++ b/lib/SyncGetWorker.js @@ -4,6 +4,10 @@ const https = require('https'); const { sharedLock, requestPort } = workerData; const sharedLockArray = new Int32Array(sharedLock); +if (parentPort === null) { + throw new Error('parentPort is null!'); +} + parentPort.on('message', async (url) => { try { const response = await getBody(url); diff --git a/lib/elm-test.js b/lib/elm-test.js index d00c941a..1926bd4f 100644 --- a/lib/elm-test.js +++ b/lib/elm-test.js @@ -184,7 +184,7 @@ function main() { ) .action(() => { const options = program.opts(); - const pathToElmBinary = getPathToElmBinary(options.compiler); + const pathToElmBinary = getPathToElmBinary(options['compiler']); const project = getProject('init'); try { Install.install(project, pathToElmBinary, ElmJson.ELM_TEST_PACKAGE); @@ -210,7 +210,7 @@ function main() { ) .action((packageName) => { const options = program.opts(); - const pathToElmBinary = getPathToElmBinary(options.compiler); + const pathToElmBinary = getPathToElmBinary(options['compiler']); const project = getProject('install'); try { const result = Install.install(project, pathToElmBinary, packageName); @@ -231,7 +231,7 @@ function main() { .description('Check files matching the globs for compilation errors') .action((testFileGlobs) => { const options = program.opts(); - const pathToElmBinary = getPathToElmBinary(options.compiler); + const pathToElmBinary = getPathToElmBinary(options['compiler']); const project = getProject('make'); const make = async () => { Generate.generateElmJson(dependencyProvider, project); @@ -242,7 +242,7 @@ function main() { ), project.generatedCodeDir, pathToElmBinary, - options.report + options['report'] ); }; make().then( @@ -260,7 +260,7 @@ function main() { .command('__elmTestCommand__ [globs...]', { hidden: true, isDefault: true }) .action((testFileGlobs) => { const options = program.opts(); - const pathToElmBinary = getPathToElmBinary(options.compiler); + const pathToElmBinary = getPathToElmBinary(options['compiler']); const projectRootDir = getProjectRootDir('tests'); const processes = Math.max(1, os.cpus().length); RunTests.runTests( diff --git a/lib/supports-color.js b/lib/supports-color.js index 7a683d77..cfe01e9c 100644 --- a/lib/supports-color.js +++ b/lib/supports-color.js @@ -18,8 +18,8 @@ const { env } = process; * @returns { boolean } */ function supportsColor() { - if ('FORCE_COLOR' in env && env.FORCE_COLOR !== '') { - return env.FORCE_COLOR !== 'false' && env.FORCE_COLOR !== '0'; + if ('FORCE_COLOR' in env && env['FORCE_COLOR'] !== '') { + return env['FORCE_COLOR'] !== 'false' && env['FORCE_COLOR'] !== '0'; } if (process.argv.includes('--no-color')) { @@ -59,7 +59,7 @@ function supportsColor() { return true; } - if (env.TERM === 'dumb') { + if (env['TERM'] === 'dumb') { return false; } diff --git a/package-lock.json b/package-lock.json index b79ff15a..0a871808 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,10 @@ "devDependencies": { "@eslint/js": "9.20.0", "@types/cross-spawn": "6.0.6", + "@types/graceful-fs": "4.1.9", "@types/node": "26.0.1", + "@types/split": "1.0.5", + "@types/which": "3.0.4", "elm-review": "2.13.1", "elm-tooling": "1.18.0", "eslint": "9.20.1", @@ -413,6 +416,16 @@ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", @@ -453,6 +466,34 @@ "@types/node": "*" } }, + "node_modules/@types/split": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/split/-/split-1.0.5.tgz", + "integrity": "sha512-gMiDr4vA6YofTpAkPQtP+5pvStIf3CMYphf32YAG/3RwogNL8ii1CQKDc+sxN62KuxPoRaJXcf2zDCDkEBH4FA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/through": "*" + } + }, + "node_modules/@types/through": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", + "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/which": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.4.tgz", + "integrity": "sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript/typescript-aix-ppc64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", @@ -3513,6 +3554,15 @@ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, + "@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", @@ -3552,6 +3602,31 @@ "@types/node": "*" } }, + "@types/split": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/split/-/split-1.0.5.tgz", + "integrity": "sha512-gMiDr4vA6YofTpAkPQtP+5pvStIf3CMYphf32YAG/3RwogNL8ii1CQKDc+sxN62KuxPoRaJXcf2zDCDkEBH4FA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/through": "*" + } + }, + "@types/through": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", + "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/which": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.4.tgz", + "integrity": "sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w==", + "dev": true + }, "@typescript/typescript-aix-ppc64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", diff --git a/package.json b/package.json index 44645a52..9375a3b4 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,10 @@ "devDependencies": { "@eslint/js": "9.20.0", "@types/cross-spawn": "6.0.6", + "@types/graceful-fs": "4.1.9", "@types/node": "26.0.1", + "@types/split": "1.0.5", + "@types/which": "3.0.4", "elm-review": "2.13.1", "elm-tooling": "1.18.0", "eslint": "9.20.1", diff --git a/tsconfig.json b/tsconfig.json index 4f25c949..e4ca1725 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "target": "esnext", "lib": ["esnext"], "types": ["node"], - "noUncheckedIndexedAccess": true, + "noUncheckedIndexedAccess": false, // Disabled during the migration from Flow to TypeScript. "exactOptionalPropertyTypes": true, "noImplicitReturns": true, "noImplicitOverride": true, @@ -16,7 +16,7 @@ "noUncheckedSideEffectImports": true, "moduleDetection": "force", "skipLibCheck": true, - "useUnknownInCatchVariables": true, + "useUnknownInCatchVariables": false, // Disabled during the migration from Flow to TypeScript. "allowJs": true, "checkJs": true, "noEmit": true, From c1049fb742dedd67403712939208da913c4d4d0d Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sat, 11 Jul 2026 22:38:15 +0200 Subject: [PATCH 08/14] Work around some more errors --- lib/DependencyProvider.js | 4 ++++ lib/ElmJson.js | 2 +- lib/Generate.js | 2 +- lib/Parser.js | 3 ++- lib/RunTests.js | 16 +++++++++------- lib/Solve.js | 4 ++-- lib/Supervisor.js | 2 +- lib/elm-test.js | 3 ++- 8 files changed, 22 insertions(+), 14 deletions(-) diff --git a/lib/DependencyProvider.js b/lib/DependencyProvider.js index 7896fab0..5fb64873 100644 --- a/lib/DependencyProvider.js +++ b/lib/DependencyProvider.js @@ -197,6 +197,10 @@ function readVersionsInElmHomeAndSort(pkg) { return offlineVersions.sort(flippedSemverCompare); } +/** + * @typedef {DependencyProvider} DependencyProviderType + */ + class DependencyProvider { /** @type { OnlineVersionsCache } */ cache = new OnlineVersionsCache(); diff --git a/lib/ElmJson.js b/lib/ElmJson.js index a4455497..be479a86 100644 --- a/lib/ElmJson.js +++ b/lib/ElmJson.js @@ -198,7 +198,7 @@ function parseObject(json, what) { `Expected ${what} to be an object, but got: ${stringify(json)}` ); } - return json; + return /** @type { Record } */ (json); } /** diff --git a/lib/Generate.js b/lib/Generate.js index de5f8199..f38e5b0e 100644 --- a/lib/Generate.js +++ b/lib/Generate.js @@ -80,7 +80,7 @@ function getGeneratedSrcDir(generatedCodeDir) { } /** - * @param { import('./DependencyProvider').DependencyProvider } dependencyProvider + * @param { import('./DependencyProvider').DependencyProviderType } dependencyProvider * @param { import('./Project').Project } project * @returns { void } */ diff --git a/lib/Parser.js b/lib/Parser.js index 15f89d45..62685936 100644 --- a/lib/Parser.js +++ b/lib/Parser.js @@ -218,7 +218,8 @@ function extractExposedPossiblyTests(filePath, createReadStream) { case 'ParseError': return result; default: - unreachable(result); + // Type assertion needed here due to there only being one error variant. + unreachable(/** @type { never } */ (result), 'tag'); } } diff --git a/lib/RunTests.js b/lib/RunTests.js index af051f33..5d9b8af3 100644 --- a/lib/RunTests.js +++ b/lib/RunTests.js @@ -138,18 +138,20 @@ function watcherEventMessage(queue) { } /** - * @param { import('./DependencyProvider').DependencyProvider } dependencyProvider - * @param { string } projectRootDir - * @param { string } pathToElmBinary - * @param { Array } testFileGlobs - * @param { number } processes - * @param { { + * @typedef { { watch: boolean, clearConsole: boolean, report: import('./Report').Report, seed: number, fuzz: number, - } } options + } } Options + + * @param { import('./DependencyProvider').DependencyProviderType } dependencyProvider + * @param { string } projectRootDir + * @param { string } pathToElmBinary + * @param { Array } testFileGlobs + * @param { number } processes + * @param { Options } options * @returns { Promise } */ function runTests( diff --git a/lib/Solve.js b/lib/Solve.js index 416f9be0..b8e35401 100644 --- a/lib/Solve.js +++ b/lib/Solve.js @@ -12,7 +12,7 @@ function sha256(string) { } /** - * @param { import('./DependencyProvider').DependencyProvider } dependencyProvider + * @param { import('./DependencyProvider').DependencyProviderType } dependencyProvider * @param { import('./Project').Project } project * @returns { import('./ElmJson').DirectAndIndirectDependencies } */ @@ -50,7 +50,7 @@ function getDependenciesCached(dependencyProvider, project) { } /** - * @param { import('./DependencyProvider').DependencyProvider } dependencyProvider + * @param { import('./DependencyProvider').DependencyProviderType } dependencyProvider * @param { import('./ElmJson').ElmJson } elmJson * @returns { string } */ diff --git a/lib/Supervisor.js b/lib/Supervisor.js index 31a3da1a..ec956bad 100644 --- a/lib/Supervisor.js +++ b/lib/Supervisor.js @@ -218,7 +218,7 @@ function run(elmTestVersion, pipeFilename, report, processes, dest, watch) { var invalidCharReplacement = function (char) { return ( '\\u{' + - char.codePointAt(0).toString(16).padStart(4, '0') + + (char.codePointAt(0) || 0).toString(16).padStart(4, '0') + '}' ); }; diff --git a/lib/elm-test.js b/lib/elm-test.js index 1926bd4f..85a8c3c7 100644 --- a/lib/elm-test.js +++ b/lib/elm-test.js @@ -269,7 +269,8 @@ function main() { pathToElmBinary, testFileGlobs, processes, - options + // The flag validations are supposed to make this type assertion safe here. + /** @type { import('./RunTests').Options } */ (options) ).then( (code) => process.exit(code), (error) => { From dc6eded22dec1cddb40b3e8d9dc6aa1aa2244948 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 12 Jul 2026 09:29:43 +0200 Subject: [PATCH 09/14] Two explicit any --- lib/Supervisor.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/Supervisor.js b/lib/Supervisor.js index ec956bad..e67bff62 100644 --- a/lib/Supervisor.js +++ b/lib/Supervisor.js @@ -22,6 +22,7 @@ function run(elmTestVersion, pipeFilename, report, processes, dest, watch) { var closedWorkers = 0; var results = new Map(); var failures = 0; + /** @type { Array<{ labels: Array, todo: string }> } */ var todos = []; var testsToRun = -1; var initializedWorkers = -1; @@ -29,6 +30,10 @@ function run(elmTestVersion, pipeFilename, report, processes, dest, watch) { /** @type { Array } */ var workers = []; + /** + * @param { any } result This `any` became explicit instead of implicit when migrating from Flow to TypeScript. + * @returns { void } + */ function printResult(result) { switch (report) { case 'console': @@ -98,6 +103,10 @@ function run(elmTestVersion, pipeFilename, report, processes, dest, watch) { ); } + /** + * @param { any } response This `any` became explicit instead of implicit when migrating from Flow to TypeScript. + * @returns { void } + */ function handleResults(response) { // TODO print progress bar - e.g. "Running test 5 of 20" on a bar! // -- yikes, be careful though...test the scenario where test From c974127a68d1a24711301312aba75b0c3871a32d Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 12 Jul 2026 09:34:36 +0200 Subject: [PATCH 10/14] Fix error in Install.js --- lib/Install.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/Install.js b/lib/Install.js index 3e71d4bb..9709b38a 100644 --- a/lib/Install.js +++ b/lib/Install.js @@ -86,12 +86,15 @@ function install(project, pathToElmBinary, packageName) { } }); } else { + // This variable is needed for TypeScript to remember that `newElmJson` has + // been narrowed to `application` inside the `moveToTestDeps` function. + const newElmJson_ = newElmJson; /** * @param { 'direct' | 'indirect' } directness * @returns { void } */ function moveToTestDeps(directness) { - Object.keys(newElmJson['dependencies'][directness]).forEach(function ( + Object.keys(newElmJson_['dependencies'][directness]).forEach(function ( key ) { // If we didn't have this dep before, move it to test-dependencies. @@ -101,13 +104,13 @@ function install(project, pathToElmBinary, packageName) { // https://github.com/rtfeldman/node-test-runner/issues/282 if ( directness === 'direct' || - !newElmJson['test-dependencies']['direct'].hasOwnProperty(key) + !newElmJson_['test-dependencies']['direct'].hasOwnProperty(key) ) { - newElmJson['test-dependencies'][directness][key] = - newElmJson['dependencies'][directness][key]; + newElmJson_['test-dependencies'][directness][key] = + newElmJson_['dependencies'][directness][key]; } - delete newElmJson['dependencies'][directness][key]; + delete newElmJson_['dependencies'][directness][key]; } }); } From 08ded3e13e991b5453b64bda688b1ff65030dce5 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 12 Jul 2026 10:10:03 +0200 Subject: [PATCH 11/14] Type check tests as well --- package-lock.json | 34 ++++++++++++++++ package.json | 2 + tests/DependencyProvider.js | 4 ++ tests/ElmJson.js | 2 +- tests/Parser.js | 15 ++++++-- tests/ci.js | 49 +++++++++++++++++++++-- tests/flags.js | 77 +++++++++++++++++++++++++++++++++++-- tests/test-quality.js | 20 +++++++++- tests/util.js | 2 +- tsconfig.json | 5 ++- 10 files changed, 194 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0a871808..1cd4388b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,9 +26,11 @@ "@eslint/js": "9.20.0", "@types/cross-spawn": "6.0.6", "@types/graceful-fs": "4.1.9", + "@types/mocha": "10.0.10", "@types/node": "26.0.1", "@types/split": "1.0.5", "@types/which": "3.0.4", + "@types/xml2js": "0.4.14", "elm-review": "2.13.1", "elm-tooling": "1.18.0", "eslint": "9.20.1", @@ -447,6 +449,13 @@ "@types/node": "*" } }, + "node_modules/@types/mocha": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "26.0.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz", @@ -494,6 +503,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/xml2js": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz", + "integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript/typescript-aix-ppc64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", @@ -3584,6 +3603,12 @@ "@types/node": "*" } }, + "@types/mocha": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true + }, "@types/node": { "version": "26.0.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz", @@ -3627,6 +3652,15 @@ "integrity": "sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w==", "dev": true }, + "@types/xml2js": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz", + "integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@typescript/typescript-aix-ppc64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", diff --git a/package.json b/package.json index 9375a3b4..821e57cb 100644 --- a/package.json +++ b/package.json @@ -56,9 +56,11 @@ "@eslint/js": "9.20.0", "@types/cross-spawn": "6.0.6", "@types/graceful-fs": "4.1.9", + "@types/mocha": "10.0.10", "@types/node": "26.0.1", "@types/split": "1.0.5", "@types/which": "3.0.4", + "@types/xml2js": "0.4.14", "elm-review": "2.13.1", "elm-tooling": "1.18.0", "eslint": "9.20.1", diff --git a/tests/DependencyProvider.js b/tests/DependencyProvider.js index 3ce7f395..4719bc1f 100644 --- a/tests/DependencyProvider.js +++ b/tests/DependencyProvider.js @@ -9,6 +9,10 @@ describe('DependencyProvider', () => { describe('prioritizePinnedIndirectVersion', () => { const versions = ['1.0.5', '1.0.4', '1.0.3', '1.0.2', '1.0.1', '1.0.0']; + /** + * @param { string | undefined } pinnedVersion + * @param { Array } expectedResult + */ const testPinning = (pinnedVersion, expectedResult) => { assert.deepStrictEqual( prioritizePinnedIndirectVersion(versions, pinnedVersion), diff --git a/tests/ElmJson.js b/tests/ElmJson.js index 296218a7..4e0c37ee 100644 --- a/tests/ElmJson.js +++ b/tests/ElmJson.js @@ -46,7 +46,7 @@ describe('handling invalid elm.json', () => { }, (error) => { assert.strictEqual( - error.message.replace( + /** @type { Error } */ (error).message.replace( // Handle slightly different JSON.parse error messages on different Node.js versions. /^.+ in JSON at position .+$/gm, '(the JSON parse error)' diff --git a/tests/Parser.js b/tests/Parser.js index 0a2bf531..095d99a1 100644 --- a/tests/Parser.js +++ b/tests/Parser.js @@ -4,14 +4,21 @@ const assert = require('assert'); const stream = require('stream'); const Parser = require('../lib/Parser'); +/** + * @param { string } elmCode + * @param { Array } expectedExposedNames + */ async function testParser(elmCode, expectedExposedNames) { const exposed = await Parser.extractExposedPossiblyTests( 'SomeFile.elm', (_, options) => { - const readable = stream.Readable.from([elmCode], { - ...options, - autoDestroy: true, - }); + const readable = + /** @type { import('stream').Readable & { close(): void } } */ ( + stream.Readable.from([elmCode], { + ...options, + autoDestroy: true, + }) + ); readable.close = readable.destroy; return readable; } diff --git a/tests/ci.js b/tests/ci.js index fab04477..e113901c 100755 --- a/tests/ci.js +++ b/tests/ci.js @@ -17,14 +17,27 @@ const resultSuccess = 0; const resultErrored = 1; const resultFailureThreshold = 2; +/** + * @param { Array } args + * @param { string } cwd + * @returns { import('child_process').SpawnSyncReturns } + */ function execElmTest(args, cwd = '.') { return spawn.sync( elmtestPath, args, - Object.assign({ encoding: 'utf-8', cwd }, spawnOpts) + Object.assign( + /** @type { const } */ ({ encoding: 'utf-8', cwd }), + spawnOpts + ) ); } +/** + * @param { string } message + * @param { import('child_process').SpawnSyncReturns } runResult + * @returns { string } + */ function getDetailedMessage(message, runResult) { return ( message + @@ -37,6 +50,10 @@ function getDetailedMessage(message, runResult) { ); } +/** + * @param { import('child_process').SpawnSyncReturns } runResult + * @returns { void } + */ function assertTestSuccess(runResult) { const msg = 'Expected success (exit code ' + @@ -50,6 +67,10 @@ function assertTestSuccess(runResult) { ); } +/** + * @param { import('child_process').SpawnSyncReturns } runResult + * @returns { void } + */ function assertTestErrored(runResult) { const msg = 'Expected error (exit code ' + @@ -63,6 +84,10 @@ function assertTestErrored(runResult) { ); } +/** + * @param { import('child_process').SpawnSyncReturns } runResult + * @returns { void } + */ function assertTestFailure(runResult) { const msg = 'Expected failure (exit code >= ' + @@ -70,11 +95,16 @@ function assertTestFailure(runResult) { '), but got ' + runResult.status; assert.ok( - runResult.status >= resultFailureThreshold, + runResult.status !== null && runResult.status >= resultFailureThreshold, getDetailedMessage(msg, runResult) ); } +/** + * @param { import('../lib/Report').Report} reporter + * @param { import('child_process').SpawnSyncReturns } runResult + * @returns { void } + */ function assertDistributionShown(reporter, runResult) { const msg = getDetailedMessage( 'Expected to show distribution table', @@ -95,6 +125,11 @@ function assertDistributionShown(reporter, runResult) { } } +/** + * @param { import('../lib/Report').Report} reporter + * @param { import('child_process').SpawnSyncReturns } runResult + * @returns { void } + */ function assertDistributionNotShown(reporter, runResult) { const msg = getDetailedMessage( 'Expected to show distribution table', @@ -115,6 +150,10 @@ function assertDistributionNotShown(reporter, runResult) { } } +/** + * @param { string } dir + * @returns { Array } + */ function readdir(dir) { return fs .readdirSync(dir) @@ -309,6 +348,7 @@ describe('Testing elm-test on single Elm files', () => { describe('Distribution report tests', () => { const cwd = fixturesDir; + /** @type { Record> } */ const distributionReportFiles = { console: { 'ReportDistributionPassing.elm': { showDistribution: true }, @@ -333,7 +373,10 @@ describe('Distribution report tests', () => { }, }; - for (const [reporter, tests] of Object.entries(distributionReportFiles)) { + for (const [reporterKey, tests] of Object.entries(distributionReportFiles)) { + const reporter = /** @type { import('../lib/Report').Report } */ ( + reporterKey + ); for (const [test, { showDistribution }] of Object.entries(tests)) { const testFile = path.join(cwd, 'tests', 'Distribution', test); it(`Distribution report test for test: ${test}, reporter: ${reporter}`, () => { diff --git a/tests/flags.js b/tests/flags.js index 9b49751c..f9373e97 100644 --- a/tests/flags.js +++ b/tests/flags.js @@ -16,6 +16,10 @@ const elmTestPath = path.join(rootDir, 'bin', 'elm-test'); const scratchDir = path.join(fixturesDir, 'scratch'); const scratchElmJsonPath = path.join(scratchDir, 'elm.json'); +/** + * @param { Array } args + * @param { (code: number | null) => void } callback + */ function elmTestWithYes(args, callback) { const child = spawn( elmTestPath, @@ -23,6 +27,11 @@ function elmTestWithYes(args, callback) { Object.assign({ encoding: 'utf-8', cwd: scratchDir }, spawnOpts) ); + if (child.stdin === null) { + throw new Error('child.stdin is null!'); + } + + // @ts-expect-error TypeScript claims this method does not exist, but the code runs and the tests pass. child.stdin.setEncoding('utf-8'); child.stdin.write(os.EOL); child.stdin.end(); @@ -31,14 +40,29 @@ function elmTestWithYes(args, callback) { }); } +/** + * + * @param { Array } args + * @param { string } [cwd] + * @param { import('child_process').SpawnOptions } extraOpts + * @returns + */ function execElmTest(args, cwd = fixturesDir, extraOpts = {}) { return spawn.sync( elmTestPath, args, - Object.assign({ encoding: 'utf-8', cwd }, spawnOpts, extraOpts) + Object.assign( + /** @type { const } */ ({ encoding: 'utf-8', cwd }), + spawnOpts, + extraOpts + ) ); } +/** + * @param { string } dirPath + * @returns { void } + */ function rimraf(dirPath) { // We can replace this with just `fs.rmSync(dirPath, { recursive: true, force: true })` // when Node.js 12 is EOL 2022-04-30 and support for Node.js 12 is dropped. @@ -48,20 +72,34 @@ function rimraf(dirPath) { if (fs.rmSync !== undefined) { fs.rmSync(dirPath, { recursive: true, force: true }); } else if (fs.existsSync(dirPath)) { + // @ts-expect-error TypeScript says that `rmdirSync` only takes one argument – because + // legacy versions of Node.js do – but we need to support those. fs.rmdirSync(dirPath, { recursive: true }); } } +/** + * @param { string } dirPath + * @returns { void } + */ function ensureEmptyDir(dirPath) { rimraf(dirPath); fs.mkdirSync(dirPath, { recursive: true }); } +/** + * @param { string } filePath + * @returns { void } + */ function touch(filePath) { const now = new Date(); fs.utimesSync(filePath, now, now); } +/** + * @param { string } filePath + * @returns { any } + */ function readJson(filePath) { return JSON.parse(fs.readFileSync(filePath, 'utf8')); } @@ -169,7 +207,11 @@ describe('flags', () => { elmTestPath, ['install', "elm/regex; printf 'FINDME'; printf 'TWICE'"], Object.assign( - { encoding: 'utf-8', input: 'y\n', cwd: fixturesDir }, + /** @type { const } */ ({ + encoding: 'utf-8', + input: 'y\n', + cwd: fixturesDir, + }), spawnOpts ) ); @@ -485,6 +527,10 @@ describe('flags', () => { Object.assign({ encoding: 'utf-8', cwd: fixturesDir }, spawnOpts) ); + if (child.stdout === null) { + throw new Error('child.stdout is null!'); + } + child.on('close', (code, signal) => { // don't send error when killed after test passed if (code !== null || signal !== 'SIGTERM') { @@ -563,6 +609,14 @@ describe('flags', () => { Object.assign({ encoding: 'utf-8', cwd: scratchDir }, spawnOpts) ); + if (child.stdout === null) { + throw new Error('child.stdout is null!'); + } + + if (child.stderr === null) { + throw new Error('child.stderr is null!'); + } + child.on('close', (code, signal) => { // don't send error when killed after test passed if (code !== null || signal !== 'SIGTERM') { @@ -634,7 +688,12 @@ describe('flags', () => { // Run with a constant seed so we can compare outputs (the seed is printed). const baseArgs = ['--seed=1', path.join('tests', 'Passing', 'One.elm')]; - // Replace printed duration with a fixed value so we can compare outputs. + /** + * Replace printed duration with a fixed value so we can compare outputs. + * + * @param { string } string + * @returns { string } + */ const fixDuration = (string) => string.replace(/\d+ ms/g, '123 ms'); // This has no colors because in the tests `elm-test` is not connected to a terminal. @@ -649,6 +708,12 @@ describe('flags', () => { 'colorFlag.stdout should have color' ); + /** + * @param { string } name + * @param { Array } args + * @param { Record } env + * @returns { void } + */ const shouldNotHaveColor = (name, args, env) => { const runResult = execElmTest(baseArgs.concat(args), fixturesDir, { env: Object.assign({}, spawnOpts.env, env), @@ -661,6 +726,12 @@ describe('flags', () => { ); }; + /** + * @param { string } name + * @param { Array } args + * @param { Record } env + * @returns { void } + */ const shouldHaveColor = (name, args, env) => { const runResult = execElmTest(baseArgs.concat(args), fixturesDir, { env: Object.assign({}, spawnOpts.env, env), diff --git a/tests/test-quality.js b/tests/test-quality.js index 0dd5555d..c404d1a6 100644 --- a/tests/test-quality.js +++ b/tests/test-quality.js @@ -7,19 +7,35 @@ const spawn = require('cross-spawn'); const { spawnOpts } = require('./util'); +/** + * @param { Array } args + * @param { string } cwd + * @returns { import('child_process').SpawnSyncReturns } + */ function execElmJson(args, cwd) { return spawn.sync( 'elm-json', args, - Object.assign({ encoding: 'utf-8', cwd: cwd }, spawnOpts) + Object.assign( + /** @type { const } */ ({ encoding: 'utf-8', cwd: cwd }), + spawnOpts + ) ); } +/** + * @param { Array } args + * @param { string } cwd + * @returns { import('child_process').SpawnSyncReturns } + */ function execElm(args, cwd) { return spawn.sync( 'elm', args, - Object.assign({ encoding: 'utf-8', cwd: cwd }, spawnOpts) + Object.assign( + /** @type { const } */ ({ encoding: 'utf-8', cwd: cwd }), + spawnOpts + ) ); } diff --git a/tests/util.js b/tests/util.js index cfead24b..1549e0c2 100644 --- a/tests/util.js +++ b/tests/util.js @@ -3,7 +3,7 @@ const path = require('path'); const fixturesDir = path.join(__dirname, 'fixtures'); const dummyBinPath = path.join(fixturesDir, 'dummy-bin'); -const newPath = process.env.PATH + path.delimiter + dummyBinPath; +const newPath = process.env['PATH'] + path.delimiter + dummyBinPath; const spawnOpts = { silent: true, env: Object.assign({}, process.env, { diff --git a/tsconfig.json b/tsconfig.json index e4ca1725..db9aea79 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "module": "commonjs", "target": "esnext", "lib": ["esnext"], - "types": ["node"], + "types": ["node", "mocha"], "noUncheckedIndexedAccess": false, // Disabled during the migration from Flow to TypeScript. "exactOptionalPropertyTypes": true, "noImplicitReturns": true, @@ -21,5 +21,6 @@ "checkJs": true, "noEmit": true, }, - "include": ["lib/**/*"], + "include": ["lib/**/*", "tests/**/*"], + "exclude": ["tests/fixtures/**/*"], } From 2a480bf3124c2b767a27c7edee5ba443b572ce79 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 12 Jul 2026 10:24:11 +0200 Subject: [PATCH 12/14] Remove leftover Flow comment --- lib/DependencyProvider.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/DependencyProvider.js b/lib/DependencyProvider.js index 5fb64873..66ea970c 100644 --- a/lib/DependencyProvider.js +++ b/lib/DependencyProvider.js @@ -426,7 +426,6 @@ function parseVersions(key, json) { } } - // TODO $FlowFixMe[incompatible-return]: We dynamically checked that `json` is an `Array`. return json; } From 0ce1b54624dbb530a7101dc893f0d0cbb8f9e860 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 12 Jul 2026 10:34:14 +0200 Subject: [PATCH 13/14] Document workaround --- lib/DependencyProvider.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/DependencyProvider.js b/lib/DependencyProvider.js index 66ea970c..a0954e63 100644 --- a/lib/DependencyProvider.js +++ b/lib/DependencyProvider.js @@ -198,6 +198,9 @@ function readVersionsInElmHomeAndSort(pkg) { } /** + * When doing `import('./DependencyProvider').DependencyProvider`, + * TypeScript says `DependencyProvider` is not exported for some reason. + * But `import('./DependencyProvider').DependencyProviderType` works. * @typedef {DependencyProvider} DependencyProviderType */ From 76fe43f691a5eda60fd0998dc22bfff3049c3e19 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 12 Jul 2026 10:35:34 +0200 Subject: [PATCH 14/14] Remove empty comment line --- tests/flags.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/flags.js b/tests/flags.js index f9373e97..2a8bc350 100644 --- a/tests/flags.js +++ b/tests/flags.js @@ -41,7 +41,6 @@ function elmTestWithYes(args, callback) { } /** - * * @param { Array } args * @param { string } [cwd] * @param { import('child_process').SpawnOptions } extraOpts