| 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} 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: (actualFailure: Failure, exp: { |
| fileName?: string, |
| start?: number, |
| end?: number, |
| matchedCode?: string |
| }) => { |
| const actualDiagnostic = actualFailure.toDiagnostic(); |
| let regrets = ''; |
| if (exp === undefined) { |
| regrets += 'The matcher requires two arguments. '; |
| } |
| if (exp.fileName) { |
| if (!actualDiagnostic.file) { |
| regrets += `Expected diagnostic to have a source file, but it had ${ |
| actualDiagnostic.file}. `; |
| } else if (!actualDiagnostic.file.fileName.endsWith(exp.fileName)) { |
| regrets += `Expected ${ |
| actualDiagnostic.file.fileName} to end with ${exp.fileName}. `; |
| } |
| } |
| if (exp.start !== undefined && actualDiagnostic.start !== exp.start) { |
| regrets += expectation('start', exp.start, actualDiagnostic.start); |
| } |
| if (exp.end !== undefined && actualDiagnostic.end !== exp.end) { |
| regrets += expectation('end', exp.end, actualDiagnostic.end); |
| } |
| if (exp.matchedCode) { |
| if (!actualDiagnostic.file) { |
| regrets += `Expected diagnostic to have a source file, but it had ${ |
| actualDiagnostic.file}. `; |
| } else if (actualDiagnostic.start === undefined) { |
| // I don't know how this could happen, but typings say so. |
| regrets += `Expected diagnostic to have a starting position. `; |
| } else { |
| const foundMatchedCode = actualDiagnostic.file.getFullText().slice( |
| Number(actualDiagnostic.start), actualDiagnostic.end); |
| if (foundMatchedCode != exp.matchedCode) { |
| regrets += `Expected diagnostic to match ${ |
| exp.matchedCode}, but was ${foundMatchedCode} (from ${ |
| Number( |
| actualDiagnostic.start)} to ${actualDiagnostic.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: { |
| [i: string]: any, // the rest |
| fileName?: string, |
| start?: number, |
| end?: number, |
| matchedCode?: string, |
| }): void; |
| } |
| } |
| } |