blob: 8adb90a01ab08887460f5ed5652da5beb0726860 [file] [log] [blame]
import 'jasmine';
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as os from 'os';
import * as ts from 'typescript';
import {Checker} from '../../checker';
import {Failure, Fix} from '../../failure';
import {AbstractRule} from '../../rule';
/**
* Turns the provided source (as strings) into a ts.Program. The source files
* will be named `.../file_${n}.ts`, with n the index of the source file in
* the `sourceCode` array.
*/
export function compile(...sourceCode: string[]): ts.Program {
const temporaryFolder = os.tmpdir() +
`/tslint_test_input_${crypto.randomBytes(16).toString('hex')}`;
const fullPaths: string[] = [];
sourceCode.forEach((s, i) => {
fullPaths.push(`${temporaryFolder}/file_${i}.ts`);
});
let error: Error|undefined = undefined;
let program: ts.Program|undefined = undefined;
try { // Wrap it all in a try/finally to clean up the temp files afterwards
fs.mkdirSync(temporaryFolder);
sourceCode.forEach((s, i) => {
fs.writeFileSync(fullPaths[i], s);
});
program = ts.createProgram(fullPaths, {});
if (ts.getPreEmitDiagnostics(program).length !== 0) {
throw new Error(
'Your program does not compile cleanly. Diagnostics:\n' +
ts.formatDiagnostics(
ts.getPreEmitDiagnostics(program), ts.createCompilerHost({})));
}
} catch (e) {
error = e;
} finally {
fullPaths.forEach(p => fs.unlinkSync(p));
fs.rmdirSync(temporaryFolder);
}
if (program && !error) {
return program;
} else {
throw error;
}
}
function check(rule: AbstractRule, program: ts.Program): Failure[] {
const checker = new Checker(program);
rule.register(checker);
return program.getSourceFiles()
.map(s => checker.execute(s))
.reduce((prev, cur) => prev.concat(cur));
}
/** Builds and run the given Rule upon the source files that were provided. */
export function compileAndCheck(
rule: AbstractRule, ...sourceCode: string[]): Failure[] {
const program = compile(...sourceCode);
return check(rule, program);
}
// Custom matcher for Jasmine, for a better experience matching fixes.
export const customMatchers: jasmine.CustomMatcherFactories = {
toBeFailureMatching(): jasmine.CustomMatcher {
return {
compare: (actual: ts.Diagnostic&{end: number, fix?: Fix}, exp: {
fileName?: string, start: number, end: number,
}) => {
let regrets = '';
if (exp === undefined) {
regrets += 'The rule requires two arguments. ';
}
if (exp.fileName) {
if (!actual.file) {
regrets += 'Expected diagnostic to have a source file. ';
} else if (!actual.file.fileName.endsWith(exp.fileName)) {
regrets += `Expected ${actual.file.fileName} to end with ${
exp.fileName}. `;
}
}
if (exp.start && actual.start !== exp.start) {
regrets += expectation('start', exp.start, actual.start);
}
if (exp.end && actual.end !== exp.end) {
regrets += expectation('end', exp.end, actual.end);
}
return {pass: regrets === '', message: regrets};
}
};
}
};
function expectation(fieldname: string, expectation: any, actual: any) {
return `Expected .${fieldname} to be ${expectation}, was ${actual}. `;
}
// And the matching type
declare global {
namespace jasmine {
interface Matchers<T> {
toBeFailureMatching(expected: {
fileName?: string,
start: number,
end: number,
[i: string]: any // the rest
}): void;
}
}
}