tsd-check
Version:
Check TypeScript type definitions
100 lines (99 loc) • 3.94 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const path = require("path");
const typescript_1 = require("typescript");
const interfaces_1 = require("./interfaces");
// List of diagnostic codes that should be ignored
const ignoredDiagnostics = new Set([
interfaces_1.DiagnosticCode.AwaitIsOnlyAllowedInAsyncFunction
]);
const diagnosticCodesToIgnore = new Set([
interfaces_1.DiagnosticCode.ArgumentTypeIsNotAssignableToParameterType,
interfaces_1.DiagnosticCode.PropertyDoesNotExistOnType,
interfaces_1.DiagnosticCode.CannotAssignToReadOnlyProperty
]);
/**
* Extract all the `expectError` statements and convert it to a range map.
*
* @param program - The TypeScript program.
*/
const extractExpectErrorRanges = (program) => {
const expectedErrors = new Map();
for (const sourceFile of program.getSourceFiles()) {
for (const statement of sourceFile.statements) {
if (statement.kind !== typescript_1.SyntaxKind.ExpressionStatement || !statement.getText().startsWith('expectError')) {
continue;
}
const location = {
fileName: statement.getSourceFile().fileName,
start: statement.getStart(),
end: statement.getEnd()
};
const pos = statement.getSourceFile().getLineAndCharacterOfPosition(statement.getStart());
expectedErrors.set(location, {
fileName: location.fileName,
line: pos.line + 1,
column: pos.character
});
}
}
return expectedErrors;
};
/**
* Check if the provided diagnostic should be ignored.
*
* @param diagnostic - The diagnostic to validate.
* @param expectedErrors - Map of the expected errors.
* @return Boolean indicating if the diagnostic should be ignored or not.
*/
const ignoreDiagnostic = (diagnostic, expectedErrors) => {
if (ignoredDiagnostics.has(diagnostic.code)) {
// Filter out diagnostics which are present in the `ignoredDiagnostics` set
return true;
}
if (!diagnosticCodesToIgnore.has(diagnostic.code)) {
return false;
}
const diagnosticFileName = diagnostic.file.fileName;
for (const [location] of expectedErrors) {
const start = diagnostic.start;
if (diagnosticFileName === location.fileName && start > location.start && start < location.end) {
// Remove the expected error from the Map so it's not being reported as failure
expectedErrors.delete(location);
return true;
}
}
return false;
};
/**
* Get a list of TypeScript diagnostics within the current context.
*
* @param context - The context object.
* @returns List of diagnostics
*/
exports.getDiagnostics = (context) => {
const fileNames = context.testFiles.map(fileName => path.join(context.cwd, fileName));
const result = [];
const program = typescript_1.createProgram(fileNames, context.config.compilerOptions);
const diagnostics = program
.getSemanticDiagnostics()
.concat(program.getSyntacticDiagnostics());
const expectedErrors = extractExpectErrorRanges(program);
for (const diagnostic of diagnostics) {
if (!diagnostic.file || ignoreDiagnostic(diagnostic, expectedErrors)) {
continue;
}
const position = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
result.push({
fileName: diagnostic.file.fileName,
message: typescript_1.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
severity: 'error',
line: position.line + 1,
column: position.character
});
}
for (const [, diagnostic] of expectedErrors) {
result.push(Object.assign({}, diagnostic, { message: 'Expected an error, but found none.', severity: 'error' }));
}
return result;
};