tslint-clean-code
Version:
TSLint rules for enforcing Clean Code
182 lines (166 loc) • 6.99 kB
text/typescript
// tslint:disable no-flag-args max-func-args
import * as Lint from 'tslint';
import * as fs from 'fs';
import * as chai from 'chai';
import * as ts from 'typescript';
import { ErrorTolerantWalker } from '../utils/ErrorTolerantWalker';
/**
* Test Utilities.
*/
export namespace TestHelper {
let program: ts.Program;
/* tslint:disable:prefer-const */
/**
* This setting must point to your rule .js files. 3rd party libraries may reuse this class and change value.
*/
export let RULES_DIRECTORY: string = 'dist/src/';
/**
* This setting must point to your formatter .js files. 3rd party libraries may reuse this class and change value.
*/
export let FORMATTER_DIRECTORY: string = 'customFormatters/';
/**
* You must specify an encoding for file read/writes. 3rd party libraries may reuse this class and change value.
*/
export let FILE_ENCODING: string = 'utf8';
/* tslint:enable:prefer-const */
export interface FailurePosition {
character: number;
line: number;
position?: number;
}
export interface ExpectedFailure {
ruleName: string;
name: string;
failure?: string;
ruleSeverity?: string;
endPosition?: FailurePosition;
startPosition: FailurePosition;
}
export function assertNoViolation(ruleName: string, inputFileOrScript: string, useTypeChecker: boolean = false) {
runRuleAndEnforceAssertions(ruleName, null, inputFileOrScript, [], useTypeChecker);
}
export function assertNoViolationWithOptions(
ruleName: string,
options: any[],
inputFileOrScript: string,
useTypeChecker: boolean = false
) {
runRuleAndEnforceAssertions(ruleName, options, inputFileOrScript, [], useTypeChecker);
}
export function assertViolationsWithOptions(
ruleName: string,
options: any[],
inputFileOrScript: string,
expectedFailures: ExpectedFailure[],
useTypeChecker: boolean = false
) {
runRuleAndEnforceAssertions(ruleName, options, inputFileOrScript, expectedFailures, useTypeChecker);
}
export function assertViolations(
ruleName: string,
inputFileOrScript: string,
expectedFailures: ExpectedFailure[],
useTypeChecker: boolean = false
) {
runRuleAndEnforceAssertions(ruleName, null, inputFileOrScript, expectedFailures, useTypeChecker);
}
export function assertViolationsWithTypeChecker(ruleName: string, inputFileOrScript: string, expectedFailures: ExpectedFailure[]) {
runRuleAndEnforceAssertions(ruleName, null, inputFileOrScript, expectedFailures, true);
}
export function runRule(
ruleName: string,
userOptions: string[],
inputFileOrScript: string,
useTypeChecker: boolean = false
): Lint.LintResult {
const configuration: Lint.Configuration.IConfigurationFile = {
extends: [],
jsRules: new Map<string, Partial<Lint.IOptions>>(),
linterOptions: {},
rules: new Map<string, Partial<Lint.IOptions>>(),
rulesDirectory: [],
};
if (userOptions != null && userOptions.length > 0) {
//options like `[4, 'something', false]` were passed, so prepend `true` to make the array like `[true, 4, 'something', false]`
configuration.rules.set(ruleName, {
ruleName,
ruleArguments: userOptions,
});
} else {
configuration.rules.set(ruleName, {
ruleName,
});
}
const options: Lint.ILinterOptions = {
formatter: 'json',
fix: false,
rulesDirectory: RULES_DIRECTORY,
formattersDirectory: FORMATTER_DIRECTORY,
};
const debug: boolean = ErrorTolerantWalker.DEBUG;
ErrorTolerantWalker.DEBUG = true; // never fail silently
let result: Lint.LintResult;
if (useTypeChecker) {
//program = Lint.Linter.createProgram([ ], './dist/test-data');
program = ts.createProgram([inputFileOrScript], {});
}
if (inputFileOrScript.match(/.*\.ts(x)?$/)) {
const contents = fs.readFileSync(inputFileOrScript, FILE_ENCODING);
const linter = new Lint.Linter(options, useTypeChecker ? program : undefined);
linter.lint(inputFileOrScript, contents, configuration);
result = linter.getResult();
} else {
let filename: string;
if (inputFileOrScript.indexOf('import React') > -1) {
filename = 'file.tsx';
} else {
filename = 'file.ts';
}
const linter = new Lint.Linter(options, useTypeChecker ? program : undefined);
linter.lint(filename, inputFileOrScript, configuration);
result = linter.getResult();
}
ErrorTolerantWalker.DEBUG = debug;
return result;
}
function runRuleAndEnforceAssertions(
ruleName: string,
userOptions: string[],
inputFileOrScript: string,
expectedFailures: ExpectedFailure[],
useTypeChecker: boolean = false
) {
const lintResult: Lint.LintResult = runRule(ruleName, userOptions, inputFileOrScript, useTypeChecker);
const actualFailures: ExpectedFailure[] = JSON.parse(lintResult.output);
// All the information we need is line and character of start position. For JSON comparison
// to work, we will delete the information that we are not interested in from both actual and
// expected failures.
actualFailures.forEach(
(actual: ExpectedFailure): void => {
delete actual.startPosition.position;
delete actual.endPosition;
// Editors start counting lines and characters from 1, but tslint does it from 0.
// To make thing easier to debug, align to editor values.
actual.startPosition.line = actual.startPosition.line + 1;
actual.startPosition.character = actual.startPosition.character + 1;
}
);
expectedFailures.forEach(
(expected: ExpectedFailure): void => {
delete expected.startPosition.position;
delete expected.endPosition;
if (!expected.ruleSeverity) {
expected.ruleSeverity = 'ERROR';
}
}
);
const errorMessage = `Wrong # of failures: \n${JSON.stringify(actualFailures, null, 2)}`;
chai.assert.equal(actualFailures.length, expectedFailures.length, errorMessage);
expectedFailures.forEach(
(expected: ExpectedFailure, index: number): void => {
const actual = actualFailures[index];
chai.assert.deepEqual(actual, expected);
}
);
}
}