diff --git a/lib/app-wrapper.js b/lib/app-wrapper.js index d584fbea1..6c773e1e2 100644 --- a/lib/app-wrapper.js +++ b/lib/app-wrapper.js @@ -109,9 +109,7 @@ function subscribe(port) { function unsubscribe(port) { // eslint-disable-next-line promise/prefer-await-to-callbacks -- Callbacks are still needed here. return (callback) => { - listeners[port] = listeners[port].filter( - (/** @type {Listened} */ fn) => fn === callback - ); + listeners[port] = listeners[port].filter((fn) => fn === callback); }; } diff --git a/lib/build.js b/lib/build.js index 80141af98..cda241d9d 100644 --- a/lib/build.js +++ b/lib/build.js @@ -1,10 +1,10 @@ /** * @import {AppHash, BuildResult} from './types/build'; - * @import {ApplicationElmJson} from './types/content'; + * @import {ApplicationElmJson, ElmJson} from './types/content'; * @import {VersionString} from './types/version'; * @import {Options, Template} from './types/options'; * @import {Path} from './types/path'; - * @import {CompileOptions} from '../vendor/types/node-elm-compiler'; + * @import {CompileOptions, Sources} from '../vendor/types/node-elm-compiler'; */ const path = require('node:path'); const crypto = require('node:crypto'); @@ -204,10 +204,7 @@ I can help set you up with an initial configuration if you run ${chalk.magenta(' */ async function buildFromGitHubTemplate(options, template) { Spinner.setText('Fetching template information'); - const commit = await RemoteTemplate.getRelevantCommit( - options, - options.template - ); + const commit = await RemoteTemplate.getRelevantCommit(options, template); const reviewElmJson = await RemoteTemplate.getRemoteElmJson( options, template, @@ -333,6 +330,11 @@ async function createTemplateProject( Benchmark.end(options, 'Create template project'); } +/** + * @param {Options} options + * @param {string} userSrc + * @param {ApplicationElmJson} elmJson + */ function updateSourceDirectories(options, userSrc, elmJson) { let sourceDirectories = [ ...elmJson['source-directories'].map((directory) => @@ -355,13 +357,22 @@ function updateSourceDirectories(options, userSrc, elmJson) { }; } +/** + * @param {Options} options + * @param {string} dest + * @param {string} elmModulePath + * @param {Sources} compileTargets + * @param {string} elmBinary + * @param {boolean} isReviewApp + * @returns {Promise} + */ async function compileElmProject( options, dest, elmModulePath, compileTargets, elmBinary, - isReviewAppApp + isReviewApp ) { /** @type {CompileOptions} */ const compileOptions = { @@ -379,6 +390,7 @@ async function compileElmProject( } }; + /** @type {string | null} */ const resolvedElmModulePath = await new Promise((resolve) => { const compileProcess = elmCompiler.compile(compileTargets, compileOptions); @@ -437,13 +449,13 @@ async function compileElmProject( } }); }); - return await OptimizeJs.optimize( - options, - resolvedElmModulePath, - isReviewAppApp - ); + return await OptimizeJs.optimize(options, resolvedElmModulePath, isReviewApp); } +/** + * @param {Options} options + * @param {string} stderr + */ function compilationError(options, stderr) { if (stderr.includes('DEBUG REMNANTS')) { return { @@ -457,7 +469,7 @@ function compilationError(options, stderr) { return { title: 'MODULE NOT FOUND', // prettier-ignore - message: `A module is missing in your configuration. Maybe you forgot to add some dependencies that contain the rules you wished to enable? If so, run ${chalk.magenta('elm install')} with the package name from inside ${chalk.yellow(options.userSrc(null))}.` + message: `A module is missing in your configuration. Maybe you forgot to add some dependencies that contain the rules you wished to enable? If so, run ${chalk.magenta('elm install')} with the package name from inside ${chalk.yellow(options.userSrc())}.` }; } @@ -480,6 +492,11 @@ function compilationError(options, stderr) { }; } +/** + * @param {Options} options + * @param {Path} reviewElmJsonPath + * @param {ElmJson} reviewElmJson + */ function validateElmReviewVersion(options, reviewElmJsonPath, reviewElmJson) { if (options.localElmReviewSrc) { return; @@ -533,6 +550,11 @@ of ${chalk.yellow(path.dirname(reviewElmJsonPath))}.` MinVersion.validate(options, reviewElmJsonPath, elmReviewVersion); } +/** + * @param {Options} options + * @param {ApplicationElmJson} reviewElmJson + * @returns {Promise} + */ async function buildElmParser(options, reviewElmJson) { const elmSyntaxVersion = reviewElmJson.dependencies.direct['stil4m/elm-syntax'] || @@ -566,7 +588,7 @@ async function buildElmParser(options, reviewElmJson) { }) ]); - return await compileElmProject( + await compileElmProject( options, buildFolder, elmParserPath, diff --git a/lib/cache.js b/lib/cache.js index f88b66ac1..dcae076cc 100644 --- a/lib/cache.js +++ b/lib/cache.js @@ -6,7 +6,7 @@ const FS = require('./fs-wrapper'); const AppState = require('./state'); /** - * @template {object} T + * @template T * @param {Path} folder * @param {string} key * @param {() => Promise} fn @@ -39,7 +39,7 @@ const ensuredFolders = new Set(); * Cache a file on the filesystem. * * @param {string} filepath - Path to the file ending in ".json" - * @param {object} content + * @param {unknown} content * @returns {Promise} */ async function cacheJsonFile(filepath, content) { diff --git a/lib/elm-app-worker.js b/lib/elm-app-worker.js index c56716fbb..e8e50831a 100644 --- a/lib/elm-app-worker.js +++ b/lib/elm-app-worker.js @@ -1,5 +1,7 @@ /** * @import {MessagePort} from 'worker_threads'; + * @import {Ports} from './types/app'; + * @import {SendPort} from './types/promisify-port'; */ const {parentPort, workerData} = require('node:worker_threads'); const ElmCommunication = require('./elm-communication'); @@ -31,13 +33,17 @@ if (parentPort) { * @returns {void} */ function subscribe(parentPort) { - parentPort.on('message', async ([port, data]) => { - if (port === 'startReview' || port === 'startGeneratingSuppressions') { - await loadCachePromise; - } + parentPort.on( + 'message', - app.ports[port].send(data); - }); + async (/** @type {[keyof Ports, unknown]} */ [port, data]) => { + if (port === 'startReview' || port === 'startGeneratingSuppressions') { + await loadCachePromise; + } + + /** @type {SendPort} */ (app.ports[port]).send(data); + } + ); app.ports.requestReadingFiles.subscribe((data) => { parentPort.postMessage(['requestReadingFiles', data]); diff --git a/lib/elm-files.js b/lib/elm-files.js index 39fca8a5d..c1948cbf3 100644 --- a/lib/elm-files.js +++ b/lib/elm-files.js @@ -1,5 +1,5 @@ /** - * @import {ElmFile, Source, Readme, ElmJson, ElmJsonData, SourceDirectories} from './types/content'; + * @import {ElmFile, Source, Readme, ElmJson, ElmJsonData, SourceDirectories, Ast} from './types/content'; * @import {ReviewOptions} from './types/options'; * @import {Path} from './types/path'; */ @@ -189,7 +189,7 @@ async function readFile( path: OsHelpers.makePathOsAgnostic(relativeFilePath), source, lastUpdatedTime, - ast: cachedAst || (await readAst(options, elmParserPath, source)) + ast: cachedAst ?? (await readAst(options, elmParserPath, source)) }; } @@ -199,7 +199,7 @@ async function readFile( * @param {ReviewOptions} options * @param {Path} elmParserPath * @param {Source} source - * @returns {Promise} + * @returns {Promise} */ async function readAst(options, elmParserPath, source) { const hash = Hash.hash(source); diff --git a/lib/fs-wrapper.js b/lib/fs-wrapper.js index 5b0fc066c..3472e0622 100644 --- a/lib/fs-wrapper.js +++ b/lib/fs-wrapper.js @@ -1,6 +1,6 @@ /** - * @import {Path} from './types/path'; * @import {Replacer, Reviver} from './types/json'; + * @import {Path} from './types/path'; */ const fs = require('graceful-fs'); const fsp = fs.promises; @@ -51,7 +51,7 @@ async function readFile(file, options) { * Write a JSON file. * * @param {string} file - * @param {object} content + * @param {unknown} content * @param {string | number | undefined} [space=undefined] * @param {Replacer | undefined} [replacer=undefined] * @returns {Promise} diff --git a/lib/init.js b/lib/init.js index 11dd18a26..dae014243 100644 --- a/lib/init.js +++ b/lib/init.js @@ -1,3 +1,8 @@ +/** + * @import {Options, Template} from './types/options'; + * @import {Path} from './types/path'; + */ + const path = require('node:path'); const fs = require('graceful-fs'); // TODO(@lishaduck) [engine:node@>=16.7.0]: Use native `cp`. @@ -10,6 +15,10 @@ const RemoteTemplate = require('./remote-template'); const {getElmBinary, getElmVersion} = require('./elm-binary'); const TemplateDependencies = require('./template-dependencies'); +/** + * @param {Options} options + * @returns {Promise} + */ async function promptAndCreate(options) { const directory = options.initPath(); @@ -38,6 +47,11 @@ async function promptAndCreate(options) { logInit(options, directory); } +/** + * @param {Options} options + * @param {Template} template + * @param {string} directory + */ async function createFromTemplate(options, template, directory) { const configDirectory = path.join(directory, 'src'); @@ -73,6 +87,10 @@ async function createFromTemplate(options, template, directory) { ); } +/** + * @param {Options} options + * @param {Path} directory + */ function logInit(options, directory) { const message = options.template ? templateInitMessage(options, directory) @@ -96,6 +114,10 @@ ${options.template ? templateRecommendation : ''}` ); } +/** + * @param {Options} options + * @param {Path} directory + */ function regularInitMessage(options, directory) { return `You can now define your review configuration by editing ${chalk.green( Anonymize.path(options, path.join(directory, 'src/ReviewConfig.elm')) @@ -104,6 +126,10 @@ function regularInitMessage(options, directory) { const orange = chalk.keyword('orange'); +/** + * @param {Options} options + * @param {Path} directory + */ function templateInitMessage(options, directory) { return `You chose to use someone's review configuration which can be great to get started but don't forget to review the configuration to make sure it fits your needs, @@ -124,6 +150,11 @@ I recommend you use a mix of the following approaches: )}). `; +/** + * @param {Options} options + * @param {Path} directory + * @param {string} template + */ async function create(options, directory, template) { const configDirectory = path.join(directory, 'src'); @@ -135,6 +166,10 @@ async function create(options, directory, template) { createReviewConfig(configDirectory, template); } +/** + * @param {Options} options + * @param {Path} directory + */ async function createElmJson(options, directory) { const elmBinary = await getElmBinary(options); const elmVersion = getElmVersion(elmBinary); @@ -149,6 +184,10 @@ async function createElmJson(options, directory) { ); } +/** + * @param {Path} directory + * @param {string} template + */ function createReviewConfig(directory, template) { fsExtra.copyFileSync( path.join(__dirname, '../init-templates/', template), diff --git a/lib/min-version.js b/lib/min-version.js index 44bad626b..c134f3137 100644 --- a/lib/min-version.js +++ b/lib/min-version.js @@ -8,9 +8,14 @@ const chalk = require('chalk'); const ErrorMessage = require('./error-message'); const PathHelpers = require('./path-helpers'); -const minimalVersion = {major: 2, minor: 14}; -// prettier-ignore -const supportedRange = `${minimalVersion.major}.${minimalVersion.minor}.0 <= v < ${minimalVersion.major + 1}.0.0` +const minimalVersion = /** @type {const} */ ({major: 2, minor: 14}); + +// TS can't do arithmetic, so make sure the cast is right when bumping the major. +const nextMajor = /** @type {3} */ (minimalVersion.major + 1); + +const supportedRange = /** @type {const} */ ( + `${minimalVersion.major}.${minimalVersion.minor}.0 <= v < ${nextMajor}.0.0` +); /** * If given an input version string smaller than the hardcoded `minimalVersion`, diff --git a/lib/new-package.js b/lib/new-package.js index 49d34b2e5..809e15346 100644 --- a/lib/new-package.js +++ b/lib/new-package.js @@ -1,5 +1,7 @@ /** - * @import {ElmJson} from './types/content'; + * @import {ApplicationElmJson, PackageName} from './types/content'; + * @import {Path} from './types/path'; + * @import {Options, RuleType} from './types/options'; */ const childProcess = require('node:child_process'); const path = require('node:path'); @@ -14,6 +16,9 @@ const MinVersion = require('./min-version'); const NewRule = require('./new-rule'); const Spinner = require('./spinner'); +/** + * @param {Options} options + */ async function create(options) { const onCancelOptions = { onCancel: () => { @@ -25,7 +30,7 @@ async function create(options) { let canceled = false; const authorName = - options.prefilledAnswers.authorName || + options.prefilledAnswers.authorName ?? (await prompts( [ { @@ -49,7 +54,7 @@ async function create(options) { } const license = - options.prefilledAnswers.license || + options.prefilledAnswers.license ?? (await prompts( [ { @@ -73,7 +78,7 @@ async function create(options) { return; } - const ruleType = options.ruleType || (await NewRule.askForRuleType()); + const ruleType = options.ruleType ?? (await NewRule.askForRuleType()); if (!ruleType) { return; } @@ -98,6 +103,9 @@ I hope you'll enjoy working with ${chalk.greenBright('elm-review')}! ❤️ ); } +/** + * @param {string} packageName + */ function validatePackageName(packageName) { if (!packageName.startsWith('elm-review-')) { throw new ErrorMessage.CustomError( @@ -146,10 +154,24 @@ const elmToolingJson = { } }; +/** + * @param {Path} dir + * @param {string} fileName + * @param {string} content + * @returns {Promise} + */ async function writeFile(dir, fileName, content) { await FS.writeFile(path.join(dir, fileName), content); } +/** + * @param {Options} options + * @param {string} authorName + * @param {PackageName} packageName + * @param {string} ruleName + * @param {RuleType} ruleType + * @param {string} license + */ async function createProject( options, authorName, @@ -196,7 +218,7 @@ async function createProject( // Adding package to the example's elm.json const previewElmJsonPath = path.join(pathToPreview, 'elm.json'); - const previewElmJson = /** @type {ElmJson} */ ( + const previewElmJson = /** @type {ApplicationElmJson} */ ( await FS.readJsonFile(previewElmJsonPath) ); previewElmJson['source-directories'] = [ @@ -320,6 +342,12 @@ ElmjutsuDumMyM0DuL3.elm Spinner.succeed(); } +/** + * @param {string} authorName + * @param {PackageName} packageName + * @param {string} ruleName + * @param {string} license + */ function elmJson(authorName, packageName, ruleName, license) { return { type: 'package', @@ -340,6 +368,11 @@ function elmJson(authorName, packageName, ruleName, license) { }; } +/** + * @param {string} authorName + * @param {PackageName} packageName + * @param {string} ruleName + */ function readme(authorName, packageName, ruleName) { return `# ${packageName} @@ -373,6 +406,10 @@ elm-review --template ${authorName}/${packageName}/example `; } +/** + * @param {Options} options + * @param {PackageName} packageName + */ function packageJson(options, packageName) { return { name: packageName, diff --git a/lib/new-rule.js b/lib/new-rule.js index 1c2d5800c..31c76c8c8 100644 --- a/lib/new-rule.js +++ b/lib/new-rule.js @@ -194,10 +194,18 @@ async function addRule(options, elmJson, ruleName, ruleType) { if (elmJson.type === 'package' && packageNameRegex.test(elmJson.name)) { console.log('Exposing the rule in elm.json'); - if (!elmJson['exposed-modules'].includes(ruleName)) { + + const exposedModules = Array.isArray(elmJson['exposed-modules']) + ? elmJson['exposed-modules'] + : Object.values(elmJson['exposed-modules']).reduce((acc, items) => [ + ...acc, + ...items + ]); + + if (!exposedModules.includes(ruleName)) { const newElmJson = { ...elmJson, - 'exposed-modules': [...elmJson['exposed-modules'], ruleName].sort() + 'exposed-modules': [...exposedModules, ruleName].sort() }; writeFile(dir, 'elm.json', JSON.stringify(newElmJson, null, 4)); diff --git a/lib/optimize-js.js b/lib/optimize-js.js index 551b2194f..dfa72a890 100644 --- a/lib/optimize-js.js +++ b/lib/optimize-js.js @@ -1,17 +1,28 @@ +/** + * @import {Options} from './types/options'; + * @import {Optimization} from './types/optimize-js'; + */ + const Benchmark = require('./benchmark'); const FS = require('./fs-wrapper'); -async function optimize(options, elmModulePath, isReviewAppApp) { +/** + * @param {Options} options + * @param {string | null} elmModulePath + * @param {boolean} isReviewApp + * @returns {Promise} + */ +async function optimize(options, elmModulePath, isReviewApp) { if (options.debug || !elmModulePath) { return elmModulePath; } - const timerId = isReviewAppApp + const timerId = isReviewApp ? 'optimizing review application' : 'optimizing parser application'; Benchmark.start(options, timerId); const originalSource = await FS.readFile(elmModulePath); - const replacements = isReviewAppApp + const replacements = isReviewApp ? [ ...performanceReplacements, ...cacheReplacements(options.localElmReviewSrc) @@ -411,6 +422,10 @@ const performanceReplacements = [ } ]; +/** + * @param {string | undefined} localElmReviewSrc + * @returns {Optimization[]} + */ function cacheReplacements(localElmReviewSrc) { const packageName = localElmReviewSrc ? '$author$project' diff --git a/lib/parse-elm.js b/lib/parse-elm.js index 450cf5c8c..1cc71ccb9 100644 --- a/lib/parse-elm.js +++ b/lib/parse-elm.js @@ -4,7 +4,7 @@ */ /** - * @import {Source, ElmFile} from './types/content'; + * @import {Source, ElmFile, Ast} from './types/content'; * @import {ParseJob} from './types/parse-elm'; * @import {Path} from './types/path'; */ @@ -58,15 +58,17 @@ function terminateWorkers() { /** * @param {Path} elmParserPath * @param {Source} source - * @returns {Promise} + * @returns {Promise} */ async function parse(elmParserPath, source) { return await new Promise((resolve, reject) => { const availableWorker = findInactiveWorker(); + /** @type {ParseJob} */ const queueItem = { source, elmParserPath, + callback: (/** @type {Error | undefined} */ error, result) => { if (error === undefined) { resolve(result); diff --git a/lib/project-dependencies.js b/lib/project-dependencies.js index 689645718..f1b0917fe 100644 --- a/lib/project-dependencies.js +++ b/lib/project-dependencies.js @@ -1,5 +1,16 @@ +/** + * @import {ElmJson} from './types/content'; + * @import {Options} from './types/options'; + * @import {VersionString} from './types/version'; + */ + const ProjectJsonFiles = require('./project-json-files'); +/** + * @param {Options} options + * @param {ElmJson} elmJson + * @param {VersionString} elmVersion + */ async function collect(options, elmJson, elmVersion) { const dependenciesEntries = elmJson.type === 'application' @@ -19,7 +30,9 @@ async function collect(options, elmJson, elmVersion) { const projectDepsPromises = Object.entries(dependenciesEntries).map( async ([name, constraint]) => { - const packageVersion = constraint.split(' ')[0]; + const packageVersion = /** @type {VersionString} */ ( + constraint.split(' ')[0] + ); const [docsJson, dependencyElmJson] = await Promise.all([ ProjectJsonFiles.getDocsJson( @@ -66,6 +79,10 @@ I will try to review the project anyway, but you might get unexpected results… return projectDeps; } +/** + * @param {string} name + * @param {VersionString} packageVersion + */ function defaultElmJson(name, packageVersion) { return { type: 'package', diff --git a/lib/project-json-files.js b/lib/project-json-files.js index 527ffce56..a53036a3e 100644 --- a/lib/project-json-files.js +++ b/lib/project-json-files.js @@ -118,7 +118,7 @@ function getPackagePathInElmHome(elmVersion, name) { * @param {Options} options * @param {VersionString} elmVersion * @param {string} name - * @param {string} packageVersion + * @param {VersionString} packageVersion * @returns {Promise} */ async function getDocsJson(options, elmVersion, name, packageVersion) { diff --git a/lib/remote-template.js b/lib/remote-template.js index 55c47a091..424ae9124 100644 --- a/lib/remote-template.js +++ b/lib/remote-template.js @@ -18,6 +18,8 @@ const TemplateDependencies = require('./template-dependencies'); // GET LATEST INFORMATION ABOUT REPOSITORY /** + * @param {Options} options + * @param {Template} template * @returns {Promise} */ async function getRelevantCommit(options, template) { @@ -36,33 +38,42 @@ async function getRelevantCommit(options, template) { /** * @returns {Promise} + * @param {Options} options + * @param {Template} template */ async function findDefaultBranch(options, template) { Debug.log('Fetching default branch'); - const body = await makeGitHubApiRequest( - options, - `https://api.github.com/repos/${template.repoName}`, - () => repoNotFoundErrorMessage(template.repoName) + const body = /** @type {{default_branch: string}} */ ( + await makeGitHubApiRequest( + options, + `https://api.github.com/repos/${template.repoName}`, + () => repoNotFoundErrorMessage(template.repoName) + ) ); return body.default_branch; } /** * @returns {Promise} + * @param {Options} options + * @param {Template} template + * @param {string} reference */ async function getLatestCommitForReference(options, template, reference) { Debug.log(`Fetching commit ${reference}`); - const body = await makeGitHubApiRequest( - options, - `https://api.github.com/repos/${template.repoName}/commits/${reference}`, - (responseBody) => { - if (responseBody.message === 'Not Found') { - // This error means that the repo itself was not found - return repoNotFoundErrorMessage(template.repoName); - } + const body = /** @type {{sha: string}} */ ( + await makeGitHubApiRequest( + options, + `https://api.github.com/repos/${template.repoName}/commits/${reference}`, + (responseBody) => { + if (responseBody.message === 'Not Found') { + // This error means that the repo itself was not found + return repoNotFoundErrorMessage(template.repoName); + } - return commitNotFoundErrorMessage(template.repoName, reference); - } + return commitNotFoundErrorMessage(template.repoName, reference); + } + ) ); return body.sha; } @@ -108,6 +119,10 @@ async function getRemoteElmJson( return TemplateDependencies.update(options, elmJson); } +/** + * @param {Template} template + * @param {string} commit + */ async function downloadTemplateElmJson(template, commit) { const {repoName, pathToFolder} = template; const pathToFolderAsUrl = pathToFolder ? `/${pathToFolder}` : ''; @@ -165,6 +180,9 @@ elm-review in your project: elm-review init --template ` }; +/** + * @param {string} repoName + */ function repoNotFoundErrorMessage(repoName) { return { title: 'REPOSITORY NOT FOUND', @@ -176,6 +194,10 @@ with private ones at the moment.` }; } +/** + * @param {unknown} repoName + * @param {unknown} reference + */ function commitNotFoundErrorMessage(repoName, reference) { return { title: 'BRANCH OR COMMIT NOT FOUND', @@ -189,6 +211,13 @@ Check the spelling and make sure it has been pushed.` // DOWNLOAD TEMPLATE FILES +/** + * @param {Options} options + * @param {Template} template + * @param {string} commit + * @param {string} basePath + * @param {ApplicationElmJson} reviewElmJson + */ async function downloadSourceDirectories( options, template, @@ -209,6 +238,13 @@ async function downloadSourceDirectories( ); } +/** + * @param {Options} options + * @param {Template} template + * @param {string} commit + * @param {string} basePath + * @param {string} directory + */ async function downloadDirectory( options, template, @@ -220,12 +256,15 @@ async function downloadDirectory( const destinationDirectory = directory.split('..').join('parent'); await FS.mkdirp(path.join(basePath, destinationDirectory)); - const fileListing = await makeGitHubApiRequest( - options, - `https://api.github.com/repos/${repoName}/contents${pathToFolder}/${directory}?ref=${commit}` - .split('//') - .join('/') - ); + const fileListing = + /** @type {({ type: string; name: string; download_url: string; })[]} */ ( + await makeGitHubApiRequest( + options, + `https://api.github.com/repos/${repoName}/contents${pathToFolder}/${directory}?ref=${commit}` + .split('//') + .join('/') + ) + ); await Promise.all( fileListing.map(async (fileOrDir) => { @@ -283,7 +322,7 @@ async function downloadFile(url, dest) { * @param {Options} options * @param {string} url * @param {((arg: JsonResponse) => {title: string, message: string})} [handleNotFound] - * @returns {Promise} + * @returns {Promise} */ async function makeGitHubApiRequest(options, url, handleNotFound) { /** @type {OptionsOfJSONResponseBody}} */ diff --git a/lib/result-cache-worker.js b/lib/result-cache-worker.js index a0876b0c5..c560d7c11 100644 --- a/lib/result-cache-worker.js +++ b/lib/result-cache-worker.js @@ -1,3 +1,6 @@ +/** + * @import {MessagePort} from 'node:worker_threads'; + */ const path = require('node:path'); const {parentPort} = require('node:worker_threads'); const fs = require('graceful-fs'); @@ -7,6 +10,9 @@ if (parentPort) { subscribe(parentPort); } +/** + * @param {MessagePort} parentPort + */ function subscribe(parentPort) { parentPort.on('message', ({filePath, cacheEntry, cacheKey}) => { try { @@ -17,7 +23,9 @@ function subscribe(parentPort) { filePath, JSON.stringify(cacheEntry, ResultCacheJson.replacer, 0), 'utf8', - () => parentPort?.postMessage(cacheKey) + () => { + parentPort?.postMessage(cacheKey); + } ); }); } diff --git a/lib/result-cache.js b/lib/result-cache.js index d78980b8c..f1677e2ab 100644 --- a/lib/result-cache.js +++ b/lib/result-cache.js @@ -1,16 +1,23 @@ /** * @import {Options} from './types/options'; * @import {Path} from './types/path'; + * @import {CacheData, CacheEntry, CacheKey, RuleId, RuleName} from './types/result-cache'; */ const path = require('node:path'); +const {Worker} = require('node:worker_threads'); const AppState = require('./state'); const Benchmark = require('./benchmark'); const Debug = require('./debug'); const ResultCacheJson = require('./result-cache-json'); const FS = require('./fs-wrapper'); -let worker = null; +/** @type {Worker} */ +let worker; + +/** @type {Map} */ const resultCache = new Map(); + +/** @type {Map unknown>} */ const promisesToResolve = new Map(); // TODO(@jfmengels): Create an alternate version of Options used in @@ -20,8 +27,8 @@ const promisesToResolve = new Map(); * Load cache results. * * @param {Options} options - * @param {string[]} ignoredDirs - * @param {string[]} ignoredFiles + * @param {Path[]} ignoredDirs + * @param {Path[]} ignoredFiles * @param {Path} cacheFolder * @returns {Promise} */ @@ -39,7 +46,6 @@ async function load(options, ignoredDirs, ignoredFiles, cacheFolder) { } if (!worker) { - const {Worker} = require('node:worker_threads'); worker = new Worker(path.resolve(__dirname, 'result-cache-worker.js')); worker.on('message', (cacheKey) => { const promise = promisesToResolve.get(cacheKey); @@ -55,11 +61,18 @@ async function load(options, ignoredDirs, ignoredFiles, cacheFolder) { resultCache.clear(); globalThis.elmJsonReplacer = ResultCacheJson.replacer; - globalThis.loadResultFromCache = (ruleName, ruleId) => { + globalThis.loadResultFromCache = ( + /** @type {RuleName} */ ruleName, + /** @type {RuleId} */ ruleId + ) => { return resultCache.get(key(ruleName, ruleId)); }; - globalThis.saveResultToCache = async (ruleName, ruleId, cacheEntry) => { + globalThis.saveResultToCache = async ( + /** @type {RuleName} */ ruleName, + /** @type {RuleId} */ ruleId, + /** @type {CacheEntry} */ cacheEntry + ) => { const cacheKey = key(ruleName, ruleId); AppState.writingToFileSystemCacheStarted(cacheKey); @@ -80,6 +93,7 @@ async function load(options, ignoredDirs, ignoredFiles, cacheFolder) { }; Benchmark.start(options, 'Load results cache'); + /** @type {string[]} */ let files = await FS.readdir(cacheFolder).catch(() => []); if (files.length === 0) return; const {rulesFilter} = options; @@ -90,28 +104,35 @@ async function load(options, ignoredDirs, ignoredFiles, cacheFolder) { } await Promise.all( - files.map(async (name) => { - await FS.readJsonFile( - path.join(cacheFolder, name), - ResultCacheJson.reviver - ) - .then((entry) => { - resultCache.set(name.slice(0, -5), entry); - }) - .catch(() => { - Debug.log( - `Ignoring results cache for ${name} as it could not be read` - ); - }); + files.map(async (/** @type {string} */ name) => { + try { + const entry = await FS.readJsonFile( + path.join(cacheFolder, name), + ResultCacheJson.reviver + ); + resultCache.set(name.slice(0, -5), entry); + } catch { + Debug.log(`Ignoring results cache for ${name} as it could not be read`); + } }) ); Benchmark.end(options, 'Load results cache'); } +/** + * + * @param {RuleName} ruleName + * @param {RuleId} ruleId + * @returns {CacheKey} + */ function key(ruleName, ruleId) { - return ruleName + '-' + ruleId; + return /** @type {CacheKey} */ (ruleName + '-' + ruleId); } +/** + * @param {Options} options + * @param {CacheData} data + */ async function saveToFile(options, data) { if (worker) { return await new Promise((resolve) => { diff --git a/lib/review-dependencies.js b/lib/review-dependencies.js index 3e524be84..3f05a1f2e 100644 --- a/lib/review-dependencies.js +++ b/lib/review-dependencies.js @@ -1,5 +1,14 @@ +/** + * @import {ApplicationDependencyList, PackageElmJson, PackageName} from './types/content'; + * @import {VersionString} from './types/version'; + */ + const ProjectJsonFiles = require('./project-json-files'); +/** + * @param {ApplicationDependencyList} reviewDirectDependencies + * @param {VersionString} elmVersion + */ async function collectRuleLinks(reviewDirectDependencies, elmVersion) { const elmJsonForReviewDependenciesPromises = Object.entries( reviewDirectDependencies @@ -24,6 +33,10 @@ async function collectRuleLinks(reviewDirectDependencies, elmVersion) { ); } +/** + * @param {ApplicationDependencyList} reviewDirectDependencies + * @param {PackageElmJson[]} elmJsonForReviewDependencies + */ function computeLinksToRuleDocs( reviewDirectDependencies, elmJsonForReviewDependencies @@ -42,19 +55,28 @@ function computeLinksToRuleDocs( ? dep['exposed-modules'] : Object.values(dep['exposed-modules']).reduce((acc, items) => [ ...acc, - items + ...items ]); for (const moduleName of exposedModules) { - acc[moduleName] = linkToModule(depName, packageVersion, moduleName); + acc[moduleName] = linkToModule( + /** @type {PackageName} */ (depName), + packageVersion, + moduleName + ); } return acc; }, - {} + /** @type {Record} */ ({}) ); } +/** + * @param {PackageName} dependencyName + * @param {VersionString} packageVersion + * @param {string} moduleName + */ function linkToModule(dependencyName, packageVersion, moduleName) { const urlModuleName = moduleName.split('.').join('-'); return `https://package.elm-lang.org/packages/${dependencyName}/${packageVersion}/${urlModuleName}`; diff --git a/lib/template-dependencies.js b/lib/template-dependencies.js index 1042b0507..1a6cf1c59 100644 --- a/lib/template-dependencies.js +++ b/lib/template-dependencies.js @@ -253,7 +253,7 @@ function filterOutDuplicateDependenciesForSection( ) { return Object.fromEntries( Object.entries(testDependencies).filter( - ([pkg]) => !regularDependencies[pkg] + ([pkg]) => !regularDependencies[/** @type {PackageName} */ (pkg)] ) ); } @@ -266,13 +266,17 @@ function filterOutDuplicateDependenciesForSection( * @returns {ApplicationElmJson} */ function update(options, elmJson) { + /** @type {PackageDependencyList} */ const extra = { 'stil4m/elm-syntax': '7.0.0 <= v < 8.0.0', 'jfmengels/elm-review': MinVersion.supportedRange }; for (const [pkg, version] of Object.entries(elmJson.dependencies.direct)) { - extra[pkg] = `${version} <= v < ${nextVersion(version, 'major')}`; + extra[/** @type {PackageName} */ (pkg)] = `${version} <= v < ${nextVersion( + version, + 'major' + )}`; } delete elmJson.dependencies.direct['jfmengels/elm-review']; diff --git a/lib/types/app.ts b/lib/types/app.ts index 986a53d9d..89a20f7c0 100644 --- a/lib/types/app.ts +++ b/lib/types/app.ts @@ -5,14 +5,15 @@ import type { ElmJsonData, LinksToRuleDocs, NonElmFiles, - Readme -} from './content.js'; -import type {Flags} from './flags.js'; -import type {Path} from './path.js'; -import type {SendPort, SubscribePort} from './promisify-port.js'; -import type {FilesProposedByCurrentFix} from './state.js'; -import type {StyledMessage} from './styled-message.js'; -import type {SuppressedErrorsFile} from './suppressed.js'; + Readme, + Source +} from './content.ts'; +import type {Flags} from './flags.ts'; +import type {Path} from './path.ts'; +import type {SendPort, SubscribePort} from './promisify-port.ts'; +import type {FilesProposedByCurrentFix} from './state.ts'; +import type {StyledMessage} from './styled-message.ts'; +import type {SuppressedErrorsFile} from './suppressed.ts'; export type Elm = { Elm: { @@ -54,7 +55,7 @@ export type Ports = { acknowledgeFileReceipt: SubscribePort; askConfirmationToFix: SubscribePort; - cacheFile: SubscribePort<{source: string; ast: Ast}>; + cacheFile: SubscribePort; fixConfirmationStatus: SubscribePort; askForFixConfirmationStatus: SendPort; abort: SubscribePort; @@ -66,9 +67,11 @@ export type Ports = { export type FileReceipt = { path: Path; - cacheRequest: {source: string; ast: Ast} | null; + cacheRequest: CacheRequest | null; }; +export type CacheRequest = {source: Source; ast: Ast}; + export type AutofixRequest = { confirmationMessage: StyledMessage; clearFixLine: boolean; diff --git a/lib/types/build.ts b/lib/types/build.ts index 1f8e1697b..d3b7506f8 100644 --- a/lib/types/build.ts +++ b/lib/types/build.ts @@ -1,5 +1,5 @@ -import type {ApplicationElmJson} from './content.js'; -import type {Path} from './path.js'; +import type {ApplicationElmJson} from './content.ts'; +import type {Path} from './path.ts'; export type BuildResult = { elmModulePath: Path | null; diff --git a/lib/types/content.ts b/lib/types/content.ts index d96f2968a..11b53d666 100644 --- a/lib/types/content.ts +++ b/lib/types/content.ts @@ -1,5 +1,5 @@ -import type {VersionRange, VersionString} from './version.js'; -import type {Path} from './path.js'; +import type {VersionRange, VersionString} from './version.ts'; +import type {Path} from './path.ts'; export type File = { path: Path; @@ -9,7 +9,7 @@ export type File = { export type ElmFile = { path: Path; source: Source; - ast?: Ast; + ast?: Ast | null; lastUpdatedTime?: Date | null; }; @@ -54,7 +54,7 @@ export type PackageElmJson = { summary: string; license: string; version: VersionString; - 'exposed-modules': (string | Record)[]; + 'exposed-modules': string[] | Record; 'elm-version': VersionRange; dependencies: PackageDependencyList; 'test-dependencies': PackageDependencyList; @@ -75,4 +75,4 @@ export type LinksToRuleDocs = Record; export type Source = string; -export type Ast = unknown; +export type Ast = Record; diff --git a/lib/types/flags.ts b/lib/types/flags.ts index 0f83f5142..2ca0c7880 100644 --- a/lib/types/flags.ts +++ b/lib/types/flags.ts @@ -1,5 +1,5 @@ -import type {FixMode} from './fix.js'; -import type {OptionsBase} from './options.js'; +import type {FixMode} from './fix.ts'; +import type {OptionsBase} from './options.ts'; export type Flags = OptionsBase & { resultCacheFolder: string; diff --git a/lib/types/optimize-js.ts b/lib/types/optimize-js.ts new file mode 100644 index 000000000..448a9d89c --- /dev/null +++ b/lib/types/optimize-js.ts @@ -0,0 +1,4 @@ +export type Optimization = { + target: string; + replacement: string; +}; diff --git a/lib/types/options.ts b/lib/types/options.ts index 8ee368fec..4abdfabde 100644 --- a/lib/types/options.ts +++ b/lib/types/options.ts @@ -1,5 +1,5 @@ -import type {AppHash} from './build.js'; -import type {Path} from './path.js'; +import type {AppHash} from './build.ts'; +import type {Path} from './path.ts'; export type OptionsBase = { debug: boolean; diff --git a/lib/types/parse-elm.ts b/lib/types/parse-elm.ts index 24269c9f3..09cfcb255 100644 --- a/lib/types/parse-elm.ts +++ b/lib/types/parse-elm.ts @@ -1,16 +1,15 @@ -import {ElmFile, Source} from './content.js'; -import {Path} from './path.js'; -import type {SendPort, SubscribePort} from './promisify-port.js'; +import type {Ast, ElmFile, Source} from './content.ts'; +import type {Path} from './path.ts'; +import type {SendPort, SubscribePort} from './promisify-port.ts'; export type ParseJob = { elmParserPath: Path; source: Source; - callback: Callback; -}; -type Callback = { - (error: Error): void; - (error: undefined, result: ElmFile): void; + callback: { + (error: Error): void; + (error: undefined, result: Ast): void; + }; }; export type ParserApp = { diff --git a/lib/types/result-cache.ts b/lib/types/result-cache.ts new file mode 100644 index 000000000..445fb28fa --- /dev/null +++ b/lib/types/result-cache.ts @@ -0,0 +1,32 @@ +import type {Replacer} from './json.ts'; +import type {Path} from './path.ts'; + +declare global { + // eslint-disable-next-line no-var -- See discussion at typescript-eslint/typescript-eslint#7941. + var loadResultFromCache: + | (() => null) + | ((ruleName: RuleName, ruleId: RuleId) => CacheEntry); + + // eslint-disable-next-line no-var -- See discussion at typescript-eslint/typescript-eslint#7941. + var saveResultToCache: + | (() => void) + | (( + ruleName: RuleName, + ruleId: RuleId, + cacheEntry: CacheEntry + ) => Promise); + + // eslint-disable-next-line no-var -- See discussion at typescript-eslint/typescript-eslint#7941. + var elmJsonReplacer: Replacer; +} + +export type RuleName = string; +export type RuleId = string; +export type CacheEntry = unknown; +export type CacheKey = `${RuleName}-${RuleId}}`; + +export type CacheData = { + filePath: Path; + cacheEntry: CacheEntry; + cacheKey: CacheKey; +}; diff --git a/lib/types/state.ts b/lib/types/state.ts index 325e082c5..5fe394b89 100644 --- a/lib/types/state.ts +++ b/lib/types/state.ts @@ -1,5 +1,5 @@ -import type {ElmFile, File, Readme} from './content.js'; -import type {Path} from './path.js'; +import type {ElmFile, File, Readme} from './content.ts'; +import type {Path} from './path.ts'; export type Model = { elmFilesCacheForWatch: Map; diff --git a/lib/types/suppressed.ts b/lib/types/suppressed.ts index 9269d1e94..027506802 100644 --- a/lib/types/suppressed.ts +++ b/lib/types/suppressed.ts @@ -1,4 +1,4 @@ -import type {Path} from './path.js'; +import type {Path} from './path.ts'; export type SuppressedErrorsFile = { rule: string; diff --git a/lib/types/watch.ts b/lib/types/watch.ts index b9542a8cb..e432a7ab2 100644 --- a/lib/types/watch.ts +++ b/lib/types/watch.ts @@ -1,11 +1,11 @@ -import type {App} from './app.js'; +import type {App} from './app.ts'; import type { ApplicationElmJson, ElmFile, ElmJsonData, ExtraFileRequest -} from './content.js'; -import type {Path} from './path.js'; +} from './content.ts'; +import type {Path} from './path.ts'; export type WatchOptions = { app: App; diff --git a/new-package/elm-review-package-tests/check-previews-compile.js b/new-package/elm-review-package-tests/check-previews-compile.js index 73621f648..7df56f84e 100644 --- a/new-package/elm-review-package-tests/check-previews-compile.js +++ b/new-package/elm-review-package-tests/check-previews-compile.js @@ -14,6 +14,9 @@ for (const example of findPreviewConfigurations()) { checkThatExampleCompiles(example); } +/** + * @param {string} exampleConfiguration + */ function checkThatExampleCompiles(exampleConfiguration) { const exampleConfigurationElmJson = require(`${exampleConfiguration}/elm.json`); @@ -62,10 +65,17 @@ and make the necessary changes to make it compile.` } } +/** + * @param {string} config + */ function success(config) { console.log(`${Ansi.green('✔')} ${path.relative(root, config)}/ compiles`); } +/** + * @param {string} exampleConfiguration + * @param {Record} previewDependencies + */ function checkDepsAreCompatible(exampleConfiguration, previewDependencies) { for (const [depName, constraint] of Object.entries(packageDependencies)) { if (!(depName in previewDependencies)) { @@ -94,6 +104,12 @@ function checkDepsAreCompatible(exampleConfiguration, previewDependencies) { } } +/** + * @param {string} exampleConfiguration + * @param {string} depName + * @param {string} constraint + * @param {string} version + */ function checkConstraint(exampleConfiguration, depName, constraint, version) { const [minVersion] = constraint.split(' <= v < ').map(splitVersion); const previewVersion = splitVersion(version); @@ -110,6 +126,9 @@ function checkConstraint(exampleConfiguration, depName, constraint, version) { } } +/** + * @param {string} version + */ function splitVersion(version) { return version.split('.').map((n) => Number.parseInt(n, 10)); } diff --git a/new-package/elm-review-package-tests/helpers/ansi.js b/new-package/elm-review-package-tests/helpers/ansi.js index fee727b7d..5eaa78f5a 100644 --- a/new-package/elm-review-package-tests/helpers/ansi.js +++ b/new-package/elm-review-package-tests/helpers/ansi.js @@ -1,13 +1,22 @@ +/** + * @param {string} text + */ function red(text) { - return '\u001B[31m' + text + '\u001B[39m'; + return `\u001B[31m${text}\u001B[39m`; } +/** + * @param {string} text + */ function green(text) { - return '\u001B[32m' + text + '\u001B[39m'; + return `\u001B[32m${text}\u001B[39m`; } +/** + * @param {string} text + */ function yellow(text) { - return '\u001B[33m' + text + '\u001B[39m'; + return `\u001B[33m${text}\u001B[39m`; } module.exports = { diff --git a/new-package/maintenance/update-examples-from-preview.js b/new-package/maintenance/update-examples-from-preview.js index 9d16a5bec..bd82369e8 100755 --- a/new-package/maintenance/update-examples-from-preview.js +++ b/new-package/maintenance/update-examples-from-preview.js @@ -16,6 +16,18 @@ if (require.main === module) { // Find all elm.json files +/** + * @typedef {object} ApplicationElmJson + * @property {string[]} source-directories + * @property {DependencyList} dependencies + */ + +/** + * @typedef {object} DependencyList + * @property {Record} direct + * @property {Record} indirect + */ + function copyPreviewsToExamples() { const previewFolders = findPreviewConfigurations(); for (const folder of previewFolders) { @@ -23,6 +35,9 @@ function copyPreviewsToExamples() { } } +/** + * @param {string} pathToPreviewFolder + */ function copyPreviewToExample(pathToPreviewFolder) { const pathToExampleFolder = `${pathToPreviewFolder}/`.replace( /preview/g, @@ -32,7 +47,9 @@ function copyPreviewToExample(pathToPreviewFolder) { fs.copySync(pathToPreviewFolder, pathToExampleFolder, {overwrite: true}); const pathToElmJson = path.resolve(pathToExampleFolder, 'elm.json'); - const elmJson = fs.readJsonSync(pathToElmJson); + const elmJson = /** @type {ApplicationElmJson} */ ( + fs.readJsonSync(pathToElmJson) + ); // Remove the source directory pointing to the package's src/ elmJson['source-directories'] = elmJson['source-directories'].filter( diff --git a/package.json b/package.json index 8595bba12..9f30f108f 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "test": "turbo run testing check-engines --continue", "test-run": "(cd test/ && ./run.sh)", "test-run-record": "(cd test/ && ./run.sh record)", - "tsc": "tsc && tsc --project tsconfig.no-implicit-any.json", + "tsc": "tsc", "tsc-watch": "tsc --watch" }, "dependencies": { diff --git a/test/jest-helpers/types/cli.ts b/test/jest-helpers/types/cli.ts index 52b452e90..a986985d1 100644 --- a/test/jest-helpers/types/cli.ts +++ b/test/jest-helpers/types/cli.ts @@ -1,4 +1,4 @@ -import type {ReportMode} from '../../../lib/types/options.js'; +import type {ReportMode} from '../../../lib/types/options.ts'; export type Options = { project?: string; diff --git a/test/run-snapshots/elm-review-something-for-new-rule/elm-review-package-tests/check-previews-compile.js b/test/run-snapshots/elm-review-something-for-new-rule/elm-review-package-tests/check-previews-compile.js index 7ea9eddc2..a577b2c98 100644 --- a/test/run-snapshots/elm-review-something-for-new-rule/elm-review-package-tests/check-previews-compile.js +++ b/test/run-snapshots/elm-review-something-for-new-rule/elm-review-package-tests/check-previews-compile.js @@ -13,6 +13,9 @@ for (const example of findPreviewConfigurations()) { checkThatExampleCompiles(example); } +/** + * @param {string} exampleConfiguration + */ function checkThatExampleCompiles(exampleConfiguration) { const exampleConfigurationElmJson = require(`${exampleConfiguration}/elm.json`); @@ -61,10 +64,17 @@ and make the necessary changes to make it compile.` } } +/** + * @param {string} config + */ function success(config) { console.log(`${Ansi.green('✔')} ${path.relative(root, config)}/ compiles`); } +/** + * @param {string} exampleConfiguration + * @param {Record} previewDependencies + */ function checkDepsAreCompatible(exampleConfiguration, previewDependencies) { for (const [depName, constraint] of Object.entries(packageDependencies)) { if (!(depName in previewDependencies)) { @@ -93,6 +103,12 @@ function checkDepsAreCompatible(exampleConfiguration, previewDependencies) { } } +/** + * @param {string} exampleConfiguration + * @param {string} depName + * @param {string} constraint + * @param {string} version + */ function checkConstraint(exampleConfiguration, depName, constraint, version) { const [minVersion] = constraint.split(' <= v < ').map(splitVersion); const previewVersion = splitVersion(version); @@ -109,6 +125,9 @@ function checkConstraint(exampleConfiguration, depName, constraint, version) { } } +/** + * @param {string} version + */ function splitVersion(version) { return version.split('.').map((n) => Number.parseInt(n, 10)); } diff --git a/test/run-snapshots/elm-review-something-for-new-rule/elm-review-package-tests/helpers/ansi.js b/test/run-snapshots/elm-review-something-for-new-rule/elm-review-package-tests/helpers/ansi.js index fee727b7d..5eaa78f5a 100644 --- a/test/run-snapshots/elm-review-something-for-new-rule/elm-review-package-tests/helpers/ansi.js +++ b/test/run-snapshots/elm-review-something-for-new-rule/elm-review-package-tests/helpers/ansi.js @@ -1,13 +1,22 @@ +/** + * @param {string} text + */ function red(text) { - return '\u001B[31m' + text + '\u001B[39m'; + return `\u001B[31m${text}\u001B[39m`; } +/** + * @param {string} text + */ function green(text) { - return '\u001B[32m' + text + '\u001B[39m'; + return `\u001B[32m${text}\u001B[39m`; } +/** + * @param {string} text + */ function yellow(text) { - return '\u001B[33m' + text + '\u001B[39m'; + return `\u001B[33m${text}\u001B[39m`; } module.exports = { diff --git a/test/run-snapshots/elm-review-something-for-new-rule/maintenance/update-examples-from-preview.js b/test/run-snapshots/elm-review-something-for-new-rule/maintenance/update-examples-from-preview.js index 9d16a5bec..bd82369e8 100755 --- a/test/run-snapshots/elm-review-something-for-new-rule/maintenance/update-examples-from-preview.js +++ b/test/run-snapshots/elm-review-something-for-new-rule/maintenance/update-examples-from-preview.js @@ -16,6 +16,18 @@ if (require.main === module) { // Find all elm.json files +/** + * @typedef {object} ApplicationElmJson + * @property {string[]} source-directories + * @property {DependencyList} dependencies + */ + +/** + * @typedef {object} DependencyList + * @property {Record} direct + * @property {Record} indirect + */ + function copyPreviewsToExamples() { const previewFolders = findPreviewConfigurations(); for (const folder of previewFolders) { @@ -23,6 +35,9 @@ function copyPreviewsToExamples() { } } +/** + * @param {string} pathToPreviewFolder + */ function copyPreviewToExample(pathToPreviewFolder) { const pathToExampleFolder = `${pathToPreviewFolder}/`.replace( /preview/g, @@ -32,7 +47,9 @@ function copyPreviewToExample(pathToPreviewFolder) { fs.copySync(pathToPreviewFolder, pathToExampleFolder, {overwrite: true}); const pathToElmJson = path.resolve(pathToExampleFolder, 'elm.json'); - const elmJson = fs.readJsonSync(pathToElmJson); + const elmJson = /** @type {ApplicationElmJson} */ ( + fs.readJsonSync(pathToElmJson) + ); // Remove the source directory pointing to the package's src/ elmJson['source-directories'] = elmJson['source-directories'].filter( diff --git a/test/run-snapshots/elm-review-something/elm-review-package-tests/check-previews-compile.js b/test/run-snapshots/elm-review-something/elm-review-package-tests/check-previews-compile.js index 7ea9eddc2..a577b2c98 100644 --- a/test/run-snapshots/elm-review-something/elm-review-package-tests/check-previews-compile.js +++ b/test/run-snapshots/elm-review-something/elm-review-package-tests/check-previews-compile.js @@ -13,6 +13,9 @@ for (const example of findPreviewConfigurations()) { checkThatExampleCompiles(example); } +/** + * @param {string} exampleConfiguration + */ function checkThatExampleCompiles(exampleConfiguration) { const exampleConfigurationElmJson = require(`${exampleConfiguration}/elm.json`); @@ -61,10 +64,17 @@ and make the necessary changes to make it compile.` } } +/** + * @param {string} config + */ function success(config) { console.log(`${Ansi.green('✔')} ${path.relative(root, config)}/ compiles`); } +/** + * @param {string} exampleConfiguration + * @param {Record} previewDependencies + */ function checkDepsAreCompatible(exampleConfiguration, previewDependencies) { for (const [depName, constraint] of Object.entries(packageDependencies)) { if (!(depName in previewDependencies)) { @@ -93,6 +103,12 @@ function checkDepsAreCompatible(exampleConfiguration, previewDependencies) { } } +/** + * @param {string} exampleConfiguration + * @param {string} depName + * @param {string} constraint + * @param {string} version + */ function checkConstraint(exampleConfiguration, depName, constraint, version) { const [minVersion] = constraint.split(' <= v < ').map(splitVersion); const previewVersion = splitVersion(version); @@ -109,6 +125,9 @@ function checkConstraint(exampleConfiguration, depName, constraint, version) { } } +/** + * @param {string} version + */ function splitVersion(version) { return version.split('.').map((n) => Number.parseInt(n, 10)); } diff --git a/test/run-snapshots/elm-review-something/elm-review-package-tests/helpers/ansi.js b/test/run-snapshots/elm-review-something/elm-review-package-tests/helpers/ansi.js index fee727b7d..5eaa78f5a 100644 --- a/test/run-snapshots/elm-review-something/elm-review-package-tests/helpers/ansi.js +++ b/test/run-snapshots/elm-review-something/elm-review-package-tests/helpers/ansi.js @@ -1,13 +1,22 @@ +/** + * @param {string} text + */ function red(text) { - return '\u001B[31m' + text + '\u001B[39m'; + return `\u001B[31m${text}\u001B[39m`; } +/** + * @param {string} text + */ function green(text) { - return '\u001B[32m' + text + '\u001B[39m'; + return `\u001B[32m${text}\u001B[39m`; } +/** + * @param {string} text + */ function yellow(text) { - return '\u001B[33m' + text + '\u001B[39m'; + return `\u001B[33m${text}\u001B[39m`; } module.exports = { diff --git a/test/run-snapshots/elm-review-something/maintenance/update-examples-from-preview.js b/test/run-snapshots/elm-review-something/maintenance/update-examples-from-preview.js index 9d16a5bec..bd82369e8 100755 --- a/test/run-snapshots/elm-review-something/maintenance/update-examples-from-preview.js +++ b/test/run-snapshots/elm-review-something/maintenance/update-examples-from-preview.js @@ -16,6 +16,18 @@ if (require.main === module) { // Find all elm.json files +/** + * @typedef {object} ApplicationElmJson + * @property {string[]} source-directories + * @property {DependencyList} dependencies + */ + +/** + * @typedef {object} DependencyList + * @property {Record} direct + * @property {Record} indirect + */ + function copyPreviewsToExamples() { const previewFolders = findPreviewConfigurations(); for (const folder of previewFolders) { @@ -23,6 +35,9 @@ function copyPreviewsToExamples() { } } +/** + * @param {string} pathToPreviewFolder + */ function copyPreviewToExample(pathToPreviewFolder) { const pathToExampleFolder = `${pathToPreviewFolder}/`.replace( /preview/g, @@ -32,7 +47,9 @@ function copyPreviewToExample(pathToPreviewFolder) { fs.copySync(pathToPreviewFolder, pathToExampleFolder, {overwrite: true}); const pathToElmJson = path.resolve(pathToExampleFolder, 'elm.json'); - const elmJson = fs.readJsonSync(pathToElmJson); + const elmJson = /** @type {ApplicationElmJson} */ ( + fs.readJsonSync(pathToElmJson) + ); // Remove the source directory pointing to the package's src/ elmJson['source-directories'] = elmJson['source-directories'].filter( diff --git a/tsconfig.json b/tsconfig.json index 9223b5167..21c100fe3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,11 +13,8 @@ "noImplicitThis": true, "resolveJsonModule": true, "skipLibCheck": true, - "strictBindCallApply": true, - "strictFunctionTypes": true, - "strictPropertyInitialization": true, - "strictNullChecks": true, - "strictBuiltinIteratorReturn": true, + "strict": true, + "useUnknownInCatchVariables": false, "noUncheckedSideEffectImports": true, "noFallthroughCasesInSwitch": true, "noUnusedParameters": true, diff --git a/tsconfig.no-implicit-any.json b/tsconfig.no-implicit-any.json deleted file mode 100644 index 4ac7ad0c3..000000000 --- a/tsconfig.no-implicit-any.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "noImplicitAny": true, - "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.no-implicit-any.json" - }, - "include": [ - "lib/types/**/*.ts", - "lib/anonymize.js", - "lib/benchmark.js", - "lib/cache.js", - "lib/debug.js", - "lib/dependency-provider.js", - "lib/elm-communication.js", - "lib/elm-files.js", - "lib/extra-files.js", - "lib/error-message.js", - "lib/flags.js", - "lib/fs-wrapper.js", - "lib/hash.js", - "lib/help.js", - "lib/load-compiled-app.js", - "lib/min-version.js", - "lib/new-rule.js", - "lib/npx.js", - "lib/options.js", - "lib/os-helpers.js", - "lib/parse-elm.js", - "lib/parse-elm-worker.js", - "lib/path-helpers.js", - "lib/project-json-files.js", - "lib/promisify-port.js", - "lib/report.js", - "lib/result-cache-json.js", - "lib/spinner.js", - "lib/state.js", - "lib/styled-message.js", - "lib/suppressed-errors.js", - "lib/sync-get.js", - "lib/sync-get-worker.js", - "lib/utils.js", - "test/jest-helpers/cli.js", - "test/jest-helpers/types/cli.ts", - "test/flags.test.js", - "test/help.test.js", - "test/path-helpers.test.js", - "test/review-local-path.js", - "test/review.test.js", - "test/snapshotter.js", - "test/suppress.test.js", - "test/version.test.js", - "vendor/exit.js", - "vendor/node-elm-compiler.js", - "jest.config.js" - ] -} diff --git a/turbo.json b/turbo.json index 17c3ce63f..3c87cf5f7 100644 --- a/turbo.json +++ b/turbo.json @@ -30,8 +30,18 @@ "test-run" ] }, + "testing:offline": { + "dependsOn": [ + "elm-tests", + "elm-format", + "tsc", + "eslint-check", + "prettier-check", + "jest" + ] + }, "eslint-check": { - "inputs": ["lib/", "vendor/", ".eslintrc.js"] + "inputs": ["lib/", "vendor/", ".eslintrc.js", "tsconfig.json"] }, "eslint-fix": { "inputs": [ @@ -103,6 +113,7 @@ "tsconfig.*.json" ], "outputs": ["node_modules/.cache/tsbuildinfo.json"], + "cache": false, "persistent": true }, "elm-tests": { diff --git a/vendor/node-elm-compiler.js b/vendor/node-elm-compiler.js index cee54136a..94fab216f 100644 --- a/vendor/node-elm-compiler.js +++ b/vendor/node-elm-compiler.js @@ -133,6 +133,7 @@ function compilerErrorToString(err, pathToElm) { /** * @param {Sources} sources * @param {CompileOptions} options + * @returns {ChildProcess} */ function compile(sources, options) { var optionsWithDefaults = prepareOptions(options, options.spawn || spawn); @@ -141,7 +142,7 @@ function compile(sources, options) { try { return runCompiler(sources, optionsWithDefaults, pathToElm).on( 'error', - function (/** @type {unknown} */ err) { + function (err) { throw err; } );