diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 456231e..554191c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,6 +26,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Typecheck + run: pnpm typecheck + - name: Test run: pnpm test -- --coverage diff --git a/package.json b/package.json index 6c1c4c8..76b689a 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,8 @@ "prepack": "pnpm build", "release": "np", "start": "pnpm build -- --watch", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "dependencies": { "chalk": "^5.3.0", diff --git a/src/index.ts b/src/index.ts index cb8a183..cce0ef7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import * as fs from 'fs/promises'; import { Plugin } from 'vite'; import { execaCommand } from 'execa'; import { npmRunPathEnv } from 'npm-run-path'; +import * as stream from 'stream'; import chalk from 'chalk'; import parseCompilerLog from './parseCompilerLog.js'; @@ -31,7 +32,7 @@ async function launchReScript( let compileOnce = (_value: unknown) => {}; - function dataListener(chunk: any) { + function dataListener(chunk: stream.Readable) { const output = chunk.toString().trimEnd(); if (!silent) { // eslint-disable-next-line no-console @@ -134,7 +135,7 @@ export default function createReScriptPlugin(config?: Config): Plugin { ); }, // Hook that resolves `.bs.js` imports to their `.res` counterpart - async resolveId(source, importer, options: any) { + async resolveId(source, importer, options) { if (source.endsWith('.res')) usingLoader = true; if (options.isEntry || !importer) return null; if (!importer.endsWith('.res')) return null; diff --git a/src/parseCompilerLog.ts b/src/parseCompilerLog.ts index 3c0c6ee..5b7b8cb 100644 --- a/src/parseCompilerLog.ts +++ b/src/parseCompilerLog.ts @@ -13,10 +13,10 @@ const codeRegex = /^ +([0-9]+| +|\.) (│|┆)/; const warningErrorRegex = /Warning number \d+ \(configured as error\)/; // Returns true if the line indicates the start of an error block -function isErrorLine(line: string) { - if (line.startsWith(" We've found a bug for you!")) return true; - if (line.startsWith(' Syntax error!')) return true; - if (warningErrorRegex.test(line)) return true; +function isErrorLine(line: string | undefined) { + if (line?.startsWith(" We've found a bug for you!")) return true; + if (line?.startsWith(' Syntax error!')) return true; + if (line && warningErrorRegex.test(line)) return true; return false; } @@ -34,7 +34,7 @@ export default function parseCompilerLog( // Optimization; only parse log when compiler is done if (lines[lines.length - 1]?.startsWith('#Done(')) { let foundError = false; - let path = ''; + let path: string | undefined; let startLine = 0; // There can be additional messages such as hints @@ -58,39 +58,41 @@ export default function parseCompilerLog( // Optimization; the next line is always the file + range, which means it // can be parsed now and the next iteration (line) can be skipped. - path = lines[i + 1].trim(); + path = lines?.[i + 1]?.trim(); // Extract the start line - const match = path.match(fileAndRangeRegex); - if (match) startLine = parseInt(match[2], 10); + const match = path?.match(fileAndRangeRegex); + if (match) startLine = Number(match[2]); // Skip the next line since it was handled here i += 1; } else if (!foundError) { // Only run below checks once an error has been found - } else if (line.startsWith(' Warning number ')) { + } else if (line?.startsWith(' Warning number ')) { // Reached the end of the error break; - } else if (line.startsWith('#Done(')) { + } else if (line?.startsWith('#Done(')) { // Reached the end of the log file break; } else { // This can now only be code lines or messages - const match = line.match(codeRegex); + const match = line?.match(codeRegex); if (match) { // Replace strange vertical bars with regular ones in order to match // the code frame regex defined in the vite overlay file: // https://github.com/vitejs/vite/blob/96591bf9989529de839ba89958755eafe4c445ae/packages/vite/src/client/overlay.ts#L116 - let codeFrameLine = line.replace('┆', '|').replace('│', '|'); + let codeFrameLine = line?.replace('┆', '|').replace('│', '|'); // Since the red color indicator is lost when parsing the log file, // this adds a pointer (`>`) to the line where the error starts. - if (parseInt(match[1], 10) === startLine) { - codeFrameLine = `> ${codeFrameLine.substring(2)}`; + if (Number(match[1]) === startLine) { + codeFrameLine = `> ${codeFrameLine?.substring(2)}`; } - frame.push(codeFrameLine); - } else if (line.startsWith(' ')) { + if (codeFrameLine) { + frame.push(codeFrameLine); + } + } else if (line?.startsWith(' ')) { // It has to be a message by now messages.push(line.trim()); } diff --git a/test/index.test.ts b/test/index.test.ts index 78bb055..60474f6 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -12,6 +12,9 @@ describe('@jihchi/vite-plugin-rescript', () => { it('invokes closeBundle hook without crashing', async () => { const actual = Plugin(); expect(actual).toHaveProperty('closeBundle'); - await expect(actual.closeBundle()).resolves.toEqual(undefined); + await expect( + // @ts-expect-error + actual.closeBundle() + ).resolves.toEqual(undefined); }); });