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
JavaScript
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, '/');
}