UNPKG

vite-esbuild-typescript-checker

Version:

* Speeds up [TypeScript](https://github.com/Microsoft/TypeScript) type checking * Supports [Vue Single File Component](https://vuejs.org/v2/guide/single-file-components.html) * Displays nice error messages with the [code frame](https://babeljs.io/docs/en/

205 lines (204 loc) 7.05 kB
import ts from 'typescript'; import pc from 'picocolors'; import fs from 'fs'; import os from 'os'; import { getVueExtension } from './vue/type-script-vue-extension.js'; import { codeFrameColumns } from '@babel/code-frame'; import moment from 'moment'; import path from 'path'; export function deduplicateAndSortIssues(issues) { const sortedIssues = issues.filter(isIssue).sort(compareIssues); return sortedIssues.filter((issue, index)=>index === 0 || !equalsIssues(issue, sortedIssues[index - 1])); } export function compareIssues(issueA, issueB) { return compareIssueSeverities(issueA.severity, issueB.severity) || compareStrings(issueA.file, issueB.file) || compareIssueLocations(issueA.location, issueB.location) || compareStrings(issueA.code, issueB.code) || compareStrings(issueA.message, issueB.message) || 0 /* EqualTo */ ; } export function compareIssueSeverities(severityA, severityB) { const [priorityA, priorityB] = [ severityA, severityB ].map((severity)=>[ 'warning' /* 0 */ , 'error' /* 1 */ ].indexOf(severity)); return Math.sign(priorityB - priorityA); } export function compareIssueLocations(locationA, locationB) { if (locationA === locationB) { return 0; } if (!locationA) { return -1; } if (!locationB) { return 1; } return compareIssuePositions(locationA.start, locationB.start) || compareIssuePositions(locationA.end, locationB.end); } export function compareIssuePositions(positionA, positionB) { if (positionA === positionB) { return 0; } if (!positionA) { return -1; } if (!positionB) { return 1; } return Math.sign(positionA.line - positionB.line) || Math.sign(positionA.column - positionB.column); } export function compareStrings(stringA, stringB) { if (stringA === stringB) { return 0; } if (stringA === undefined || stringA === null) { return -1; } if (stringB === undefined || stringB === null) { return 1; } return stringA.toString().localeCompare(stringB.toString()); } export function equalsIssues(issueA, issueB) { return compareIssues(issueA, issueB) === 0; } export function isIssue(value) { return !!value && typeof value === 'object' && isIssueSeverity(value.severity) && !!value.code && !!value.message; } export function isIssueSeverity(value) { return [ 'error', 'warning' ].includes(value); } export function createIssueFromDiagnostic(diagnostic) { let file; let location; if (diagnostic.file) { file = diagnostic.file.fileName; if (diagnostic.start && diagnostic.length) { const { line: startLine, character: startCharacter } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); const { line: endLine, character: endCharacter } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start + diagnostic.length); location = { start: { line: startLine + 1, column: startCharacter + 1 }, end: { line: endLine + 1, column: endCharacter + 1 } }; } } return { code: 'TS' + String(diagnostic.code), // we don't handle Suggestion and Message diagnostics severity: diagnostic.category === 0 ? 'warning' : 'error', message: ts.flattenDiagnosticMessageText(diagnostic.messageText, os.EOL), file, location }; } export function getDiagnosticsOfProgram(program) { const programDiagnostics = []; const config = { syntactic: true, declaration: true, global: true, semantic: true }; if (config.syntactic) { programDiagnostics.push(...program.getSyntacticDiagnostics()); } if (config.global) { programDiagnostics.push(...program.getGlobalDiagnostics()); } if (config.semantic) { programDiagnostics.push(...program.getSemanticDiagnostics()); } if (config.declaration) { programDiagnostics.push(...program.getDeclarationDiagnostics()); } return programDiagnostics; } export function afterProgramEmitAndDiagnostics(program, port) { const diagnostics = getDiagnosticsOfProgram(program); const typescriptVueExtension = getVueExtension(); let issuesReport = []; let issues = diagnostics.map((diagnostic)=>createIssueFromDiagnostic(diagnostic)); issues = typescriptVueExtension.extendIssues(issues); if (issues.length) { issuesReport = issues.map((issue)=>{ issue.formattedColor = createCodeFrameFormatter({ highlightCode: true, forceColor: true })(issue); return issue; }); port.postMessage({ type: 'diagnostic', data: issuesReport }); } const doneTime = new Date().getTime(); if (issuesReport.length) port.postMessage({ type: 'info', data: pc.red(`[${moment().format('Y-MM-DD H:mm:ss')}] Found ${issuesReport.length} errors.`) }); else port.postMessage({ type: 'info', data: pc.blue(`[${moment().format('Y-MM-DD H:mm:ss')}] Found ${issuesReport.length} errors.`) }); port.postMessage({ type: 'done-time', data: doneTime }); } export class IssueError { issue; file; message; constructor(issue){ this.issue = issue; this.file = issue.file; this.message = issue.formattedColor; if (issue.file && issue.location) this.file += `:${pc.green(formatIssueLocation(issue.location))}`; Error.captureStackTrace(this, this.constructor); } } export function formatIssueLocation(location) { return `${location.start.line}:${location.start.column}`; } export function createCodeFrameFormatter(options) { const basicFormatter = createBasicFormatter(); return function codeFrameFormatter(issue) { const source = issue.file && fs.existsSync(issue.file) && fs.readFileSync(issue.file, 'utf-8'); let frame = ''; if (source && issue.location) { frame = codeFrameColumns(source, issue.location, { highlightCode: true, ...options || {} }).split('\n').map((line)=>' ' + line).join(os.EOL); } const lines = [ basicFormatter(issue, { highlightCode: true, ...options || {} }) ]; if (frame) { lines.push(frame); } lines.push('\n'); return lines.join(os.EOL); }; } export function createBasicFormatter() { return function basicFormatter(issue, options) { return (options?.highlightCode ? pc.gray(issue.code + ': ') : issue.code + ': ') + issue.message; }; } export function forwardSlash(input) { return path.normalize(input).replace(/\\+/g, '/'); }