typescript-docs-verifier
Version:
Verifies that typescript examples in markdown files actually compile.
142 lines • 6.68 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SnippetCompiler = exports.CompilationError = void 0;
const fs_1 = __importDefault(require("fs"));
const typescript_1 = __importDefault(require("typescript"));
const CodeBlockExtractor_1 = require("./CodeBlockExtractor");
const LocalImportSubstituter_1 = require("./LocalImportSubstituter");
const CodeCompiler_1 = require("./CodeCompiler");
class CompilationError extends Error {
constructor(diagnosticText, diagnostics) {
var _a;
super(diagnosticText);
this.name = this.constructor.name;
this.diagnosticText = diagnosticText;
this.diagnosticCodes = (_a = diagnostics === null || diagnostics === void 0 ? void 0 : diagnostics.map(({ code }) => code)) !== null && _a !== void 0 ? _a : [];
this.diagnostics = diagnostics !== null && diagnostics !== void 0 ? diagnostics : [];
}
}
exports.CompilationError = CompilationError;
class SnippetCompiler {
constructor(workingDirectory, packageDefinition, project) {
this.workingDirectory = workingDirectory;
this.packageDefinition = packageDefinition;
const configOptions = SnippetCompiler.loadTypeScriptConfig(packageDefinition.packageRoot, project);
this.compilerOptions = configOptions.options;
}
static loadTypeScriptConfig(packageRoot, project) {
const configFile = typescript_1.default.findConfigFile(packageRoot, typescript_1.default.sys.fileExists, project);
if (!configFile) {
throw new Error(`Unable to find TypeScript configuration file in ${packageRoot}`);
}
const fileContents = fs_1.default.readFileSync(configFile, "utf-8");
const { config: configJSON, error } = typescript_1.default.parseConfigFileTextToJson(configFile, fileContents);
if (error) {
throw new Error(`Error reading tsconfig from ${configFile}: ${typescript_1.default.flattenDiagnosticMessageText(error.messageText, typescript_1.default.sys.newLine)}`);
}
const parsedConfig = typescript_1.default.parseJsonConfigFileContent(configJSON, {
fileExists: typescript_1.default.sys.fileExists,
readDirectory: typescript_1.default.sys.readDirectory,
readFile: typescript_1.default.sys.readFile,
useCaseSensitiveFileNames: typescript_1.default.sys.useCaseSensitiveFileNames,
}, packageRoot);
return parsedConfig;
}
async compileSnippets(documentationFiles) {
const results = [];
const examples = await this.extractAllCodeBlocks(documentationFiles);
for (const example of examples) {
const result = await this.testCodeCompilation(example);
results.push(result);
// Yield to event loop
await new Promise((resolve) => setImmediate(resolve));
}
return results;
}
async extractAllCodeBlocks(documentationFiles) {
const importSubstituter = new LocalImportSubstituter_1.LocalImportSubstituter(this.packageDefinition);
const codeBlocks = await Promise.all(documentationFiles.map(async (file) => await this.extractFileCodeBlocks(file, importSubstituter)));
return codeBlocks.flat();
}
async extractFileCodeBlocks(file, importSubstituter) {
const blocks = await CodeBlockExtractor_1.CodeBlockExtractor.extract(file);
return blocks.map(({ code, type }, index) => {
return {
file,
type,
snippet: code,
index: index + 1,
sanitisedCode: this.sanitiseCodeBlock(importSubstituter, code),
};
});
}
sanitiseCodeBlock(importSubstituter, block) {
const localisedBlock = importSubstituter.substituteLocalPackageImports(block);
return localisedBlock;
}
async testCodeCompilation(example) {
try {
const { hasError, diagnostics } = await (0, CodeCompiler_1.compile)({
compilerOptions: this.compilerOptions,
workingDirectory: this.workingDirectory,
code: example.sanitisedCode,
type: example.type,
});
if (!hasError) {
return {
snippet: example.snippet,
file: example.file,
index: example.index,
linesWithErrors: [],
};
}
const linesWithErrors = new Set();
const enrichedDiagnostics = diagnostics.map((diagnostic) => {
if (typeof diagnostic.start !== "undefined") {
const startLine = [...example.sanitisedCode.substring(0, diagnostic.start)].filter((char) => char === typescript_1.default.sys.newLine).length + 1;
linesWithErrors.add(startLine);
}
return {
...diagnostic,
file: diagnostic.file
? {
...diagnostic.file,
fileName: `${example.file} → Code Block ${example.index}`,
}
: undefined,
};
});
const formatter = process.stdout.isTTY
? typescript_1.default.formatDiagnosticsWithColorAndContext
: typescript_1.default.formatDiagnostics;
const diagnosticText = formatter(enrichedDiagnostics, {
getCanonicalFileName: (fileName) => fileName,
getCurrentDirectory: () => this.workingDirectory,
getNewLine: () => typescript_1.default.sys.newLine,
});
const error = new CompilationError(`⨯ Unable to compile TypeScript:\n${diagnosticText}`, enrichedDiagnostics);
return {
snippet: example.snippet,
file: example.file,
error: error,
index: example.index,
linesWithErrors: [...linesWithErrors],
};
}
catch (rawError) {
const error = rawError instanceof Error ? rawError : new Error(String(rawError));
return {
snippet: example.snippet,
error: error,
linesWithErrors: [],
file: example.file,
index: example.index,
};
}
}
}
exports.SnippetCompiler = SnippetCompiler;
//# sourceMappingURL=SnippetCompiler.js.map